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
{
  "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.

{
  "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.

{
  "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.

{
  "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.

{
  "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.

{
  "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
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
rate_limited The caller is over the rate limit for its key kind. 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
unauthorized The Authorization header is missing, malformed or names an unknown key. unauthorized

Every error body carries the same shape; the full list is on Errors.