# Read a booked recorder

Canonical page: https://agentsky.dev/tools/recall/bot-get
JSON record: https://gateway.agentsky.dev/v1/endpoints/recall/bot-get

Read a booked recorder is a tool from Recall, callable through the AgentSky API as recall/bot-get and billed per call ($0).

Reads one booked recorder: where it is in its lifecycle, whether it was admitted, how long it has held its seat, and the recording it produced if any. Reading costs nothing. Booking has no separate fee. A $2 prepayment is required; unused funds are returned at settlement. Usage is billed when the recording settles: $0.60 per hour the recorder holds a seat (from joining until supplier completion, including waiting and processing; fatal bot usage is waived) plus $0.18 per hour of recording lifecycle time, rounded to the nearest second without a minimum. Recording stops after two hours. A recorder that is never sent costs nothing; one that waits in a room nobody opens still costs the seat it held.

The recorder's state, its join and leave times, and any recording it produced.

## Facts

- **Id:** `recall/bot-get`
- **Provider:** Recall
- **Kind:** tool
- **Billed per:** call
- **Timeout:** 5 min
- **Max request:** 1 MB

## Call it

`POST https://gateway.agentsky.dev/v1/run` with `{"provider": "recall", "endpoint": "bot-get", "input": {…}}` and `Authorization: Bearer $AGENTSKY_API_KEY`.

### Input fields

| Field | Type | Required | Description |
|---|---|---|---|
| `run_id` | string | yes | The gateway runId returned by bot-create. The booking must belong to this user and, for session tokens, this session. |
| `bot_id` | string | yes | The id returned when the recorder was booked. |

## Example

### curl

```bash
# Set once per job; reuse this key and the identical body after a timeout.
: "${AGENTSKY_IDEMPOTENCY_KEY:?Set a unique, saved key for this job}"
curl --fail-with-body https://gateway.agentsky.dev/v1/run \
  -H "authorization: Bearer $AGENTSKY_API_KEY" \
  -H "content-type: application/json" \
  -H "Idempotency-Key: $AGENTSKY_IDEMPOTENCY_KEY" \
  -d '{"provider":"recall","endpoint":"bot-get","input":{"run_id":"run_11111111222233334444555555555555","bot_id":"11111111-2222-3333-4444-555555555555"}}'
# If status is READY/RUNNING, poll GET /v1/runs/{runId} with the same API key.
```

### JavaScript

```js
// Node.js 20+. Save one unique key per job; reuse it with the identical body.
const key = process.env.AGENTSKY_IDEMPOTENCY_KEY;
if (!key || !process.env.AGENTSKY_API_KEY) {
  throw new Error("Set AGENTSKY_API_KEY and a saved AGENTSKY_IDEMPOTENCY_KEY first");
}
const base = "https://gateway.agentsky.dev";
const headers = {
  authorization: `Bearer ${process.env.AGENTSKY_API_KEY}`,
  "content-type": "application/json",
  "Idempotency-Key": key,
};
const body = JSON.stringify({
  "provider": "recall",
  "endpoint": "bot-get",
  "input": {
    "run_id": "run_11111111222233334444555555555555",
    "bot_id": "11111111-2222-3333-4444-555555555555"
  }
});
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));

async function request(path, options = {}) {
  for (let attempt = 0; attempt < 3; attempt++) {
    let response;
    let data;
    try {
      response = await fetch(base + path, {
        ...options, headers, signal: AbortSignal.timeout(180_000),
      });
      const text = await response.text();
      try { data = JSON.parse(text); } catch { /* Proxy timeouts may return HTML. */ }
    } catch (error) {
      if (attempt === 2) throw error; // Resume with the same key and body.
      await sleep(2000);
      continue;
    }
    // Failed runs can have non-2xx HTTP status. Never regenerate them automatically.
    if (data?.runId && data?.status) return data;
    if (response.ok && data) throw new Error("Unexpected response: missing runId/status");
    if (attempt < 2 && (response.status >= 500 || response.status === 408 || response.status === 429)) {
      await sleep(2000);
      continue; // POST retries keep the original key and body.
    }
    throw new Error(`HTTP ${response.status}: ${JSON.stringify(data ?? "Non-JSON response; retry with the same key and body")}`);
  }
}

let run = await request("/v1/run", { method: "POST", body });
console.log("Save runId:", run.runId);
const deadline = Date.now() + 30 * 60_000;
while (["READY", "RUNNING"].includes(run.status)) {
  if (Date.now() >= deadline) {
    throw new Error(`Still pending: poll /v1/runs/${run.runId} later; do not create a new job`);
  }
  await sleep(5000);
  run = await request(`/v1/runs/${encodeURIComponent(run.runId)}`);
}
console.log("Status:", run.status, "Charge:", run.price?.amount);
if (run.status !== "COMPLETED") {
  throw new Error(`Run ${run.runId}: ${JSON.stringify(run.error ?? run.status)}`);
}
console.log(run.output); // Output shape depends on the endpoint.
// READY/RUNNING price is provisional. Use the terminal receipt for reconciliation.
```

## Pricing

$0 per call

Read the final customer charge from price.amount.value after the run finishes. A RUNNING response is not the final bill.

## Questions

### What does Read a booked recorder do?

Reads one booked recorder: where it is in its lifecycle, whether it was admitted, how long it has held its seat, and the recording it produced if any. Reading costs nothing. Booking has no separate fee. A $2 prepayment is required; unused funds are returned at settlement. Usage is billed when the recording settles: $0.60 per hour the recorder holds a seat (from joining until supplier completion, including waiting and processing; fatal bot usage is waived) plus $0.18 per hour of recording lifecycle time, rounded to the nearest second without a minimum. Recording stops after two hours. A recorder that is never sent costs nothing; one that waits in a room nobody opens still costs the seat it held.

### How much does Read a booked recorder cost?

$0 per call, using your plan when signed in. The customer charge is returned as price.amount.value with its currency; READY/RUNNING values are provisional.

### What does Read a booked recorder take?

run_id and bot_id are required; the rest of the fields are optional. Send them in the input object of a POST to /v1/run.

### What are the limits on Read a booked recorder?

The catalog lists an execution timeout of 5 min. A request can return READY or RUNNING before completion; poll GET /v1/runs/{runId} with the same API key until it reaches a terminal status. An HTTP timeout does not prove the generation stopped.

### How do I retry safely and check the final charge?

Save a unique Idempotency-Key for each job. After a network error or HTML timeout, resend the identical POST body with the same key and API key to recover the existing run, then poll its runId. A new key can start another billable generation. An HTTP error or timeout does not establish the run outcome or final charge. Pending runs can still settle; check the terminal price.amount and billing state, and retain the runId for reconciliation. Do not automatically create a new job after a terminal failure.
