> ## Documentation Index
> Fetch the complete documentation index at: https://docs.oneshotagent.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Timeouts and retries

> Retry a paid call without paying twice, and recover a call that timed out.

A timeout on your side does not mean the call failed. The work may already be accepted, and an accepted job can finish and be charged after you stop waiting. Recover the original call before you send it again.

## Idempotency keys

Pass `idempotencyKey` (the `Idempotency-Key` header) and save it before the call goes out.

```ts theme={null}
const key = `${runId}:${rowId}:profile`;
const result = await agent.personProfile({
  email,
  idempotencyKey: key,
  totalTimeoutMs: 120_000,
  onRequestCreated: ({ idempotencyKey }) => saveKey(idempotencyKey),
  onAccepted: ({ request_id, receipt_id }) => saveHandles(request_id, receipt_id),
});
```

* Same key, same input: you get the original `202`, job and receipt, with `Idempotent-Replay: true`. No second charge.
* Same key, different input: `422 idempotency_key_reuse`.
* Same key while the first call is still running: `409 request_in_flight`. Wait and retry.
* The SDK makes a new key for each call when you pass none. To retry across processes, reuse your saved key.

## Timeouts

| Option | Covers |
| - | - |
| `timeout` | Polling for the job result, in seconds |
| `totalTimeoutMs` | The whole call: quote, signing, submission and response, in milliseconds |

When `totalTimeoutMs` runs out, the SDK throws `RequestTimeoutError` with `phase`, `elapsedMs`, `idempotencyKey`, `requestId`, `receiptId` and `acceptance`: `accepted` when the server returned a request id, `unknown` otherwise.

## Recover a call

```ts theme={null}
const accepted = await agent.recoverRequest({ endpoint: 'lookup/person-profile', idempotencyKey: key });
const done = await agent.waitForResult(accepted.request_id);
```

`GET /v1/submissions/recover?endpoint=...&key=...` returns the original handles and status. It needs no new quote or payment, and a read proof is enough.

Endpoints: `lookup/person-profile`, `lookup/person-email`, `lookup/person-email-status`, `lookup/place-permits`, `physical-mail/send`, `solve`, `predict`, `sms/send`, `voice/call`, `email/send`, `linkedin/reply`, `linkedin/message`, `linkedin/profile-view`, `linkedin/react`, `linkedin/invite`, `linkedin/invite/withdraw`, `crm/notes`, `commerce/buy`, `browser`. For a commerce or browser call made without a key, use `quote:<X-Quote-ID>`.

| Response | Meaning |
| - | - |
| `200` | The original call, with its current status |
| `409 request_in_flight` | Still being accepted. Retry shortly |
| `404 submission_not_found`, `acceptance: unknown` | No record found. This does not prove nothing was accepted or charged: check your receipts before you buy again |

## Service status

`agent.getServiceStatus()` (`GET /v1/status`, no auth) returns `status` (`healthy` or `degraded`), `observed_at` and per-dependency state, with `503` when degraded. Use it to pause a batch, not as a health check for your own load balancer.

Every response carries a `Server-Timing` header with the time spent per phase.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.