# Asynchronous checks

> `POST https://checksharecode.co.uk/api/check/async` queues a check and answers at once with its id. We run it, retry it if gov.uk fails, then post the result to your callback URL, or keep it for you to fetch with `GET /api/check/async/:id`. Same request body, same result and same error codes as `POST /api/check`.

Base URL: `https://checksharecode.co.uk` · Auth: `authorization: Bearer <key>` · Web version: https://checksharecode.co.uk/docs/api/check-async

Use it when you would rather not hold a request open while gov.uk answers, when you check many applicants at once, or when you want us to retry a check gov.uk failed. It works on every plan, and with sandbox keys too.

## Headers

- `Authorization` (string, required): Bearer followed by your key, as for `POST /api/check`. Example: `Bearer rtw_live_…`
- `Idempotency-Key` (string): Any string up to 255 characters, unique per check you mean to run. Sending the same key again returns the first check with `200`, so a retry after a network error never runs or charges a second check. Example: `applicant-4411-check-1`

## Body

- `share_code, date_of_birth, company_name` (string, required): The same three fields, with the same rules, as [POST /api/check](https://checksharecode.co.uk/docs/api/check.md#body).
- `callback_url` (string): Where we post the result when the check ends. Without it we use your default URL for the mode, set on [Webhooks](https://checksharecode.co.uk/app/webhooks), and without that you poll. See [Webhooks](https://checksharecode.co.uk/docs/webhooks.md). Example: `https://api.acme.example/hooks/rtw?applicant=4411`
- `client_reference` (string): Your own id for the check, up to 200 characters, given back in every response and webhook. Use an opaque id, never the applicant’s name, share code or date of birth. Example: `applicant-4411`

Request:

```bash
curl https://checksharecode.co.uk/api/check/async \
  -H "Authorization: Bearer $CHECKSHARECODE_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: applicant-4411-check-1" \
  -d '{
    "share_code": "AB1CD2EF3",
    "date_of_birth": "1990-01-01",
    "company_name": "Acme Ltd",
    "callback_url": "https://api.acme.example/hooks/rtw?applicant=4411",
    "client_reference": "applicant-4411"
  }'
```

202 Accepted:

```json
{
  "id": "chk_5f2a8c1e9b7d4a6f8e3c2b1a0d9f8e7c",
  "status": "queued",
  "mode": "live",
  "client_reference": "applicant-4411",
  "callback_url": "https://api.acme.example/hooks/rtw?applicant=4411",
  "attempts": 0,
  "result": null,
  "error": null,
  "result_expires_at": null,
  "result_expired": false,
  "created_at": "2026-09-28T10:00:00.000Z",
  "completed_at": null
}
```

The `202` has a `Location` header with the check’s URL. Bad input, a bad key, an exhausted quota and rate limits come back straight away with the usual error, so you never wait for a webhook to learn a request was wrong.

## Get a check

`GET /api/check/async/:id` with the same key returns the check as it stands. If you set a callback URL you don’t need to poll: the webhook carries this same object.

GET /api/check/async/:id, 200 OK:

```json
{
  "id": "chk_5f2a8c1e9b7d4a6f8e3c2b1a0d9f8e7c",
  "status": "succeeded",
  "mode": "live",
  "client_reference": "applicant-4411",
  "callback_url": "https://api.acme.example/hooks/rtw?applicant=4411",
  "attempts": 1,
  "result": { …the same object POST /api/check returns… },
  "error": null,
  "result_expires_at": "2026-10-05T10:00:14.000Z",
  "result_expired": false,
  "created_at": "2026-09-28T10:00:00.000Z",
  "completed_at": "2026-09-28T10:00:14.000Z"
}
```

| Field | Type | Meaning |
| --- | --- | --- |
| id | string | The check’s id, `chk_` and 32 characters. |
| status | "queued" \| "running" \| "succeeded" \| "failed" | Where the check is. `succeeded` and `failed` are final. |
| mode | "live" \| "test" | Whether a live or a sandbox key queued it. |
| result | object \| null | When `succeeded`: exactly what `POST /api/check` returns, photo and PDF included. |
| error | { code, message } \| null | When `failed`: the same code and message `POST /api/check` would have returned. |
| attempts | integer | How many times we ran it. More than 1 means we retried. |
| callback_url | string \| null | Where the result is posted. `null` means poll only. |
| client_reference | string \| null | Your id for the check, as you sent it. |
| result_expires_at | date-time \| null | When we delete the result: 7 days after the check ends. |
| result_expired | boolean | `true` once the result is deleted. `status` and `error` stay readable. |
| created_at, completed_at | date-time | When it was queued, and when it ended. |

## Outcomes and retries

- **Same outcomes as the sync API.** What `POST /api/check` answers with `200` ends as `succeeded`. What it answers with an error ends as `failed` with that error code, so `NOT_FOUND` and `DOB_MISMATCH` are `failed` checks. Each is charged the way `POST /api/check` charges it.
- **We retry for you.** `TIMEOUT`, `BUSY` and `INTERNAL` are run again, up to 3 attempts in all, after about 30 seconds and then 2 minutes. `GOVUK_UNEXPECTED` is run once more. Only the final outcome is charged.
- **Retries add time.** A check that needs every retry takes a few minutes to end.

## How long we keep the result

The result, photo and PDF included, is kept encrypted for 7 days after the check ends, so we can deliver it to you. Then it is deleted, with your `client_reference` and `callback_url`: `result` becomes `null` and `result_expired` becomes `true`. Save what you need when it arrives. The check’s `status` and error code stay readable for 30 days.

## Limits and errors

- **Checks in progress count.** A queued or running live check counts against your monthly allowance, so a free account can’t queue more checks than it has left (`402 QUOTA_EXCEEDED`).
- **100 in progress at once.** Past that, `POST` answers `429 RATE_LIMITED` with `Retry-After`.
- **Unknown ids.** `GET` answers `404 CHECK_NOT_FOUND` when no check with that id belongs to this key’s account and mode. A sandbox key can’t read a live check, and the other way round.
- **Sandbox keys** run the same flow with the [test codes](https://checksharecode.co.uk/docs/quickstart.md#test-codes), so you can build your webhook handler before you spend a live check. A sandbox key your agent got without an account can poll, but can’t use `callback_url` until it is claimed.

See also: [POST /api/check](https://checksharecode.co.uk/docs/api/check.md) · [Webhooks](https://checksharecode.co.uk/docs/webhooks.md)
