# GET /v1/scans/{id}

Fetch a previous scan

Returns a scan made earlier with a live key and kept under a non-zero retention window, while that window lasts; anything else is `404`. The image crops are never stored, so every image slot comes back null, and neither is the engine's own output, so `quality` reads `not_checked`.

Reads back a result the service still holds. It never re-runs recognition and is never billed.

A scan is readable only while its retention window is open: the `retain_hours` the creating request asked for, or the account's own history-retention setting when it asked for none. A scan created with `retain_hours: 0` was never written down.

Image crops are not stored. A result read back here carries `images: null` whatever the creating response returned.

## Request

| | |
|---|---|
| Method | `GET` |
| Path | `/v1/scans/{id}` |
| Authentication | `Authorization: Bearer <api key>` |

### Path parameters

| Name | Type | Required | Description |
|---|---|---|---|
| `id` | string | yes | Scan identifier: a UUID version 7 (RFC 9562), canonical lower-case `8-4-4-4-12`. Its leading 48 bits are the millisecond the scan was made, so ids sort in the order the scans happened — but treat the value as opaque: nothing else about it is part of the contract. |

## Responses

### 200

The scan as it was returned when it was created.

Body: `Scan`.

| Field | Type | Description |
|---|---|---|
| `meta` | ScanMeta |  |
| `document` | ScanDocument \| null |  |
| `holder` | ScanHolder \| null |  |
| `fields` | ScanField[] | Every field the engine extracted off the printed document, re-keyed to our vocabulary — the open set. Always present; empty when nothing was extracted. A field read in more than one language appears once per language, so `name` repeats and only `id` is unique. |
| `mrz` | ScanMrz |  |
| `images` | ScanImages | Image crops, returned in the recognition response only. Each is scaled down by height, proportionally and never upwards, to at most 250 px for `document_crop` and 100 px for every other crop, then re-encoded with every metadata block dropped. |
| `quality` | ScanQuality |  |
| `authenticity` | ScanAuthenticity |  |

```json
{
  "meta": {
    "schema_version": "1.0",
    "id": "01a0af18-cd8d-7a61-9f2d-4c7b8e105da3",
    "status": "recognized",
    "billed": true,
    "confidence": "high",
    "timing": {
      "upload_ms": 198,
      "processing_ms": 812,
      "total_ms": 1024
    },
    "created_at": "2026-09-17T10:15:00Z",
    "reference": "order-1042"
  },
  "document": {
    "kind": "passport",
    "country": "GRC",
    "country_name": "Greece",
    "issuing_state": "GRC",
    "type_name": "Greece - Passport",
    "type_confidence": "high",
    "is_expired": false,
    "days_remaining": 2001
  },
  "holder": {
    "given_names": "ELENI SOFIA",
    "surname": "PARADEIGMA",
    "full_name": "PARADEIGMA ELENI SOFIA",
    "birth_date": "1994-03-08",
    "sex": "F",
    "nationality": "GRC"
  },
  "fields": [
    {
      "id": "surname@0",
      "name": "surname",
      "label": "Surname",
      "category": "identity",
      "value": "PARADEIGMA",
      "language": null,
      "confidence": "high"
    },
    {
      "id": "surname@1032",
      "name": "surname",
      "label": "Surname",
      "category": "identity",
      "value": "ΠΑΡΑΔΕΙΓΜΑ",
      "language": "Greek",
      "confidence": "high"
    },
    {
      "id": "document_number@0",
      "name": "document_number",
      "label": "Document number",
      "category": "document",
      "value": "AM7304518",
      "language": null,
      "confidence": "high"
    },
    {
      "id": "days_to_expire@0",
      "name": "days_to_expire",
      "label": "Days to expire",
      "category": "dates",
      "value": "2001",
      "language": null,
      "confidence": "high"
    },
    {
      "id": "mrz@0",
      "name": "mrz",
      "label": "MRZ",
      "category": "document",
      "value": "P<GRCPARADEIGMA<<ELENI<SOFIA<<<<<<<<<<<<<<<<AM73045184GRC9403084F3203101PN48291630<<<<72",
      "language": null,
      "confidence": "high"
    }
  ],
  "mrz": {
    "status": "passed",
    "reason": null,
    "lines": [
      "P<GRCPARADEIGMA<<ELENI<SOFIA<<<<<<<<<<<<<<<<",
      "AM73045184GRC9403084F3203101PN48291630<<<<72"
    ],
    "text": "P<GRCPARADEIGMA<<ELENI<SOFIA<<<<<<<<<<<<<<<<AM73045184GRC9403084F3203101PN48291630<<<<72"
  },
  "images": {
    "document_crop": "data:image/jpeg;base64,/9j/4AAQSkZJRg==",
    "rear": null,
    "main_photo": "data:image/jpeg;base64,/9j/4AAQSkZJRg==",
    "signature": "data:image/jpeg;base64,/9j/4AAQSkZJRg==",
    "watermark_face": "data:image/jpeg;base64,/9j/4AAQSkZJRg==",
    "barcode": null,
    "chip": null
  },
  "quality": {
    "overall": "pass"
  },
  "authenticity": {
    "overall": "not_checked",
    "checks": []
  }
}
```

### 401

Missing or malformed `Authorization: Bearer <api key>` header, or an unknown key.

Body: `ErrorResponse`.

```json
{
  "error": {
    "code": "unauthorized",
    "message": "Send your API key as `Authorization: Bearer <key>`.",
    "docs_url": "https://doc.cheap/docs/errors/unauthorized",
    "request_id": "req_9e6b1f7c-2d4a-4b83-9c51-7f0ad3e8b642",
    "event_id": null
  }
}
```

### 404

No scan with this id exists, or its retention window has passed.

Body: `ErrorResponse`.

```json
{
  "error": {
    "code": "not_found",
    "message": "No scan with id 01a0af18-cd8d-7a61-9f2d-4c7b8e105da3 exists or it has expired.",
    "docs_url": "https://doc.cheap/docs/errors/not_found",
    "request_id": "req_9e6b1f7c-2d4a-4b83-9c51-7f0ad3e8b642",
    "event_id": null
  }
}
```

### 429

Rate limit exceeded for this key or IP.

Body: `ErrorResponse`.

```json
{
  "error": {
    "code": "rate_limited",
    "message": "Sandbox limit of 10 requests per hour per IP reached.",
    "docs_url": "https://doc.cheap/docs/errors/rate_limited",
    "request_id": "req_9e6b1f7c-2d4a-4b83-9c51-7f0ad3e8b642",
    "event_id": null
  }
}
```

### 500

Unexpected failure on the server; the scan was not charged.

Body: `ErrorResponse`.

```json
{
  "error": {
    "code": "internal_error",
    "message": "Unexpected error.",
    "docs_url": "https://doc.cheap/docs/errors/internal_error",
    "request_id": "req_9e6b1f7c-2d4a-4b83-9c51-7f0ad3e8b642",
    "event_id": null
  }
}
```

### 503

Temporarily unable to serve the request: a store this service depends on is unreachable. Nothing was charged and it may be retried, after the `Retry-After` seconds where that header is present.

Body: `ErrorResponse`.

```json
{
  "error": {
    "code": "service_unavailable",
    "message": "The service is temporarily unable to handle this request; nothing was charged. Retry shortly.",
    "docs_url": "https://doc.cheap/docs/errors/service_unavailable",
    "request_id": "req_9e6b1f7c-2d4a-4b83-9c51-7f0ad3e8b642",
    "event_id": null
  }
}
```

## Error codes reachable here

| Code | When | Page |
|---|---|---|
| `internal_error` | The service failed in a way it does not model. | [internal_error](/errors/internal_error) |
| `not_found` | No scan with that id was ever stored — it was created with `retain_hours: 0`, or it belongs to another account — or it was stored and its retention window has since passed. The two cases are not distinguished. | [not_found](/errors/not_found) |
| `rate_limited` | The caller is over the rate limit for its key kind. | [rate_limited](/errors/rate_limited) |
| `service_unavailable` | A store this endpoint reads from is unreachable, so the answer could not be produced. Nothing was charged; retry after the `Retry-After` seconds. | [service_unavailable](/errors/service_unavailable) |
| `unauthorized` | The `Authorization` header is missing, malformed or names an unknown key. | [unauthorized](/errors/unauthorized) |

Every error body carries the same shape; the full list is on [Errors](/reference/errors).
