{"openapi":"3.1.0","info":{"title":"Atomara API","version":"1.1.0","description":"# Introduction\n\nThe Atomara API checks PDF documents — bank statements, payslips, employment letters, invoices, receipts — for signs that they were edited after they were produced. Send a file, get back a verdict, a score from 0 to 100, and every finding behind them, with the page and the area on the page where it was found.\n\nThe same result is in your Atomara dashboard, so the person reviewing a flagged application reads exactly what your code read.\n\nHow teams fit it into tenant screening, lending, onboarding, marketplaces, car rental and KYC: [Atomara for platforms](/platforms).\n\n```\nBase URL   https://atomara.io/api/v1\nFormat     JSON over HTTPS; uploads as multipart/form-data\nAuth       Authorization: Bearer atm_live_…\n```\n\n## What a check can and cannot tell you\n\nA check reads the file itself: how it was written, what changed between its saved revisions, whether the text and the numbers on its pages are consistent, whether a digital signature still covers it, and — for issuers we hold genuine reference documents of — whether its structure matches theirs. It does **not** contact the bank or employer that supposedly issued the document.\n\nThat has consequences worth knowing before you write any decision logic. **`authentic` has two bases:** `signature` (an intact digital signature) and `reference_match` (the file matches, in every structural detail, a reference template learned from genuine documents of its issuer, with no sign of editing). For `reference_match`, read `reference_match.attack_tested`: when it is `false`, a copy with changed amounts, dates or details has the same structure, so the values are **not** verified and the `summary` says so. An ordinary unsigned PDF of an issuer we hold no template for, with nothing wrong with it, comes back `inconclusive` with no signals — the best result it can get, not a warning. See [Verdicts and scores](#description/verdicts-and-scores).\n\n# Quickstart\n\n**1. Create a key.** Sign in to the dashboard, open [Developers](https://atomara.io/account/developers) and create a key. It is shown once — store it in your secrets manager.\n\n**2. Send a document.**\n\n```bash\ncurl https://atomara.io/api/v1/documents \\\n  -H \"Authorization: Bearer $ATOMARA_API_KEY\" \\\n  -H \"Idempotency-Key: tenant-123-payslip\" \\\n  -F file=@payslip.pdf \\\n  -F external_id=tenant-123-payslip\n```\n\nSend the file as `multipart/form-data` and **do not set the `Content-Type` header yourself** — your HTTP client writes it, with the `boundary` the body needs, when it builds the form. A hand-written `Content-Type: multipart/form-data` drops the boundary, the body cannot be read, and the answer is `400 invalid_request`.\n\n**Using Postman?** Import the [Postman collection](/docs/api/postman.json), not the OpenAPI file: Postman's OpenAPI import adds exactly that header to every upload. In the collection, set the `apiKey` variable, pick a PDF for `file` in *Create a document* and send — the new document's id is saved to `documentId` for the other requests.\n\n**3. Read the result.** Most documents are analysed while the request is open (up to 20 seconds) and come back `200` with `status: \"completed\"`:\n\n```json\n{\n  \"id\": \"doc_3f9a6c1e8b2d4f7a9c0e1b3d5f7a9c1e\",\n  \"object\": \"document\",\n  \"status\": \"completed\",\n  \"external_id\": \"tenant-123-payslip\",\n  \"verdict\": \"inconclusive\",\n  \"score\": 75,\n  \"signals\": []\n}\n```\n\nThe full object has more fields — see [The Document object](#model/document).\n\n**4. Handle the slow case.** If the analysis takes longer, the answer is `202` with `status: \"processing\"`. Nothing failed: keep the `id` and poll `GET /documents/{id}` every few seconds until `status` is `completed` or `failed`. See [Processing](#description/processing).\n\n# Authentication\n\nEvery request carries your secret key as a Bearer token:\n\n```\nAuthorization: Bearer atm_live_…\n```\n\n- Keys belong to your organization, not to a person. Documents sent with any of them are visible to every key of the organization and in the dashboard.\n- A key's secret is shown **once**, when it is created. Atomara stores only a hash of it and cannot show it again. Lost it? Create a new one.\n- Up to 10 active keys per organization — one per environment or service is a good habit.\n- **Rotate** by creating the new key, deploying it, then revoking the old one on the Developers page. Revocation takes effect immediately.\n- Keep keys on your server. Never ship one in a browser or mobile app: anyone holding it can spend your checks and read your documents.\n\nA missing, malformed or revoked key gets `401 unauthorized`.\n\n# Verdicts and scores\n\nA completed document carries three layers of answer — use the one that fits your workflow.\n\n## `verdict` — the decision\n\n| `verdict` | Meaning |\n|---|---|\n| `authentic` | Either a valid digital signature covers the whole file (`basis: signature`), or the file matches, in every structural detail, a reference template learned from genuine documents of its issuer, with no sign of editing (`basis: reference_match`; read `reference_match.attack_tested` before trusting the values). |\n| `suspicious` | Findings that point to editing, below the tampered threshold — or, with `basis: image_container`, a picture of a page that cannot be checked structurally. Review by hand. |\n| `tampered` | Strong evidence that the content was changed after the document was produced. |\n| `inconclusive` | Nothing proves the file genuine and nothing proves it edited. With no signals, the best outcome an unsigned file can have. |\n| `unsupported` | The file has no text layer the engine can analyse. Not charged. |\n\n## `score` — the same answer as a number\n\n`score` runs from 0 (edited) to 100 (intact) and is the number the dashboard shows. The verdict picks the band; the evidence moves the score inside it, so a score never contradicts its verdict:\n\n| `score` | `verdict` |\n|---|---|\n| 81–100 | `authentic` |\n| 56–80 | `inconclusive` |\n| 31–55 | `suspicious` |\n| 0–30 | `tampered` |\n\n`score` is `null` while the document is processing, for `unsupported` files, and for scans and photos (see [Scans and photos](#description/scans-and-photos)).\n\nA sensible starting policy: accept `inconclusive` with no scored `tamper` signals, send `suspicious` to manual review, and treat `tampered` as a rejection candidate that a person confirms. Atomara flags; your team decides.\n\nA photo or a scan with no text layer is `suspicious` by rule rather than on findings — `basis: \"image_container\"` marks that case (see [Scans and photos](#description/scans-and-photos)).\n\n## Raw engine values\n\n`tamper_score`, `integrity_score` and `confidence` are the engine's own outputs, each from 0 to 1. They are there for teams that want to calibrate their own thresholds; `score` is derived from them.\n\n## `signals` — the evidence\n\nEach signal is one finding: a stable `id`, the engine `module` that raised it, its `kind` (`tamper` or `integrity`), its `severity`, and — when the engine can pin it down — the `page` and `bbox` where it is. A signal with `scored: false` is supporting evidence: shown to explain the picture, not counted in the score. Signal ids are stable: the same id always means the same check, so they are safe to branch on and to log.\n\n# Scans and photos\n\nA photo or a scan of a paper document, saved as a PDF, is accepted and analysed — but as an image. Its pages carry no text layer written by the software that produced the original, so the forensic checks that read that layer have nothing to read.\n\nSuch a document comes back with:\n\n- `file.document_class` set to `raster` or `scanned_with_text_layer`;\n- `basis: \"image_container\"`;\n- `score: null` — a number would grade something the method did not measure;\n- the signals the engine *could* raise, for example about the tool that wrapped the image into a PDF.\n\n## The verdict of an image\n\n| `file.document_class` | `verdict` | What it means |\n|---|---|---|\n| `raster` — only pictures of pages: a photo, a screenshot, a scan with no text layer | `suspicious` | Nothing in the file can be checked structurally, so it is sent to manual review. No edit was found. |\n| `scanned_with_text_layer` — a scan with a recognised-text (OCR) layer | `inconclusive` | Checked as an image; nothing was scored against it. |\n\n`suspicious` together with `basis: \"image_container\"` therefore says \"this is a picture, a person should look at it\" — not \"signs of editing were found\". Branch on `basis` to tell the two apart: a `suspicious` document with `basis: \"negative\"` carries scored findings.\n\nAn image can still earn a verdict on evidence. When its signals score high enough on their own — an editor's traces in the container, something laid over the page image — it comes back `suspicious` or `tampered` with `basis: \"negative\"` and a `score`, like any other document.\n\nWhat a picture cannot show is which document it was taken of. If your applicants can upload either, ask for the original PDF the bank or employer issued: it is the version that can be checked in depth.\n\nA PDF with no text layer and no image the engine can analyse is `unsupported` and is not charged.\n\n# Processing\n\n`POST /documents` waits for the analysis for up to 20 seconds:\n\n| Outcome | HTTP | `status` |\n|---|---|---|\n| Analysis finished in time | `200` | `completed` (or `failed` — see `error`) |\n| Analysis still running | `202` | `processing` |\n\nA `202` is not an error. The analysis continues in the background and the result is stored on the same document. Poll `GET /documents/{id}` — every 3–5 seconds is plenty — until `status` is `completed` or `failed`.\n\nSet your HTTP client's timeout above 20 seconds, or a slow analysis will look like a dropped connection. If a connection does drop, retry with the same `Idempotency-Key` (see [Idempotency](#description/idempotency)): you get the document that was already created, not a second charge.\n\n# Errors\n\nA failed request answers with a 4xx or 5xx status and one body shape:\n\n```json\n{\n  \"error\": {\n    \"code\": \"not_a_pdf\",\n    \"message\": \"The file is not a PDF. Only application/pdf is accepted.\",\n    \"request_id\": \"req_01k6n8x3b2c9d7e5f4g3h2j1km\"\n  }\n}\n```\n\nBranch on `code`; the `message` is for a developer to read and may be reworded.\n\n## Request ids\n\nEvery response — success or error — carries an `X-Request-Id` header (`req_…`), and an error body repeats it as `error.request_id`. Log it next to your own request: it is what to quote when you contact support, and the dashboard's **API → Requests** page finds any request by it, with what you sent and what you got back, for 30 days.\n\n| HTTP | `code` | Meaning |\n|---|---|---|\n| 400 | `invalid_request` | A parameter is missing or malformed. The message names it. |\n| 400 | `not_a_pdf` | The uploaded file is not a PDF. Only `application/pdf` is accepted. |\n| 401 | `unauthorized` | The API key is missing, malformed or revoked. |\n| 402 | `quota_exceeded` | The account has no checks left. Nothing was stored. |\n| 404 | `not_found` | No such document in your organization — or no such route. |\n| 405 | `method_not_allowed` | The HTTP method is not supported on this path. |\n| 409 | `idempotency_key_reused` | The `Idempotency-Key` was already used, within 24 hours, for a different upload (another file, `external_id` or `metadata`). Nothing was stored; send a new key. |\n| 413 | `file_too_large` | The file exceeds the upload size limit. |\n| 429 | `rate_limited` | Too many requests for this key. Wait for the `Retry-After` seconds. |\n| 500 | `internal_error` | Something failed on our side. Safe to retry, ideally with the same `Idempotency-Key`. |\n\n## A document that failed\n\nAn upload can be accepted and its analysis still fail — a password-protected PDF, say. That is not a failed request: the answer is `200` with `status: \"failed\"` and the reason in the document's own `error`. A failed document is never charged.\n\n| `error.code` | Meaning |\n|---|---|\n| `password_required` | The PDF is password-protected and cannot be opened. |\n| `not_a_pdf` | The file claims to be a PDF but cannot be parsed as one. |\n| `file_too_large` | The file exceeds the engine's size limit. |\n| `too_many_pages` | The PDF has more pages than the engine analyses. |\n| `engine_error` | The analysis could not be completed. Send the document again. |\n\n## Retrying\n\n`429`, `500` and dropped connections are safe to retry with exponential backoff. Retry `POST /documents` with the same `Idempotency-Key`. Do not retry other 4xx errors unchanged — fix the request.\n\n# Limits and quotas\n\n| Limit | Value |\n|---|---|\n| File types | `application/pdf` only (checked from the file's bytes, not its name) |\n| File size | 10 MB |\n| Pages | 100 per document |\n| Requests | 60 per minute per key |\n| `external_id` | 191 characters |\n| `metadata` | 2 KB, a JSON object |\n\nOver the request limit, the answer is `429 rate_limited` with a `Retry-After` header: wait that many seconds.\n\n## Checks\n\nOne document is one check, whatever its page count. Checks are shared between the API and the dashboard; `GET /usage` returns the balance.\n\nNot charged:\n\n- documents that end `failed`, and `unsupported` files;\n- a repeated request with the same `Idempotency-Key`.\n\nWith no checks left, `POST /documents` answers `402 quota_exceeded` and stores nothing. Reading, listing and deleting documents keep working.\n\n# Idempotency\n\nNetworks drop connections. To make `POST /documents` safe to retry, send an `Idempotency-Key` header — any unique string up to 255 characters; a UUID or your own record id works well.\n\nWithin 24 hours, a request with a key that was already used returns the original document instead of creating a new one, and charges nothing. After 24 hours the key is forgotten.\n\nUse one key per document you mean to check, and reuse it only for retries of that same upload. A key that comes back within 24 hours with a different file, `external_id` or `metadata` is refused with `409 idempotency_key_reused` — nothing is stored or charged, and you never receive the verdict of a document you did not send. Generate the key per upload; never hard-code one.\n\n# Data retention and privacy\n\n- Files and results are kept for **30 days** after upload, then deleted automatically and the personal data around them anonymised.\n- `DELETE /documents/{id}` removes the file and its result immediately, without waiting for the 30 days. A deleted document answers `404` from then on.\n- Documents are not shared with third parties and are never used to train models.\n- Traffic is HTTPS only. Files are stored outside any public path and are reachable only through your API keys and your dashboard account.\n\n`metadata` and `external_id` are returned as you sent them and deleted with the document. Prefer references (an applicant id) to personal data (a name) in both.\n\nDetails: [Privacy Policy](https://atomara.io/privacy) · [Security](https://atomara.io/security).\n\n# Versioning and changelog\n\nThe version is in the path: `/api/v1`. Within `v1`, changes are additive only:\n\n- new endpoints, new optional parameters, new fields in responses;\n- new values in fields documented as open (`basis`, `document_class`) and new signal ids.\n\nWrite your client to ignore fields it does not know. A change that removes or renames a field, or changes what one means, ships as a new version (`/api/v2`) and is announced to account owners by e-mail before `v1` changes.\n\n## 1.2.0 — 2026-10-16\n\n`authentic` can now rest on `basis: reference_match`: the file matches, in every structural detail, a reference template learned from genuine documents of its issuer, with no sign of editing. A new `reference_match` object (`issuer`, `references`, `attack_tested`) is set for that basis and `null` otherwise; when `attack_tested` is `false` the values are not verified and the `summary` says so. `integrity_score` and `confidence` stay within 0–1.\n\n## 1.1.0 — 2026-10-04\n\nEvery response carries an `X-Request-Id` header, and every error body carries the same id as `error.request_id`. The dashboard's request log finds a request by it.\n\n## 1.0.0 — 2026-10-03\n\nFirst release: documents (create, retrieve, list, delete) and usage.","contact":{"name":"Atomara support","email":"hello@atomara.io"}},"servers":[{"url":"https://atomara.io/api/v1","description":"Production"}],"tags":[{"name":"Documents","description":"Send a PDF, read its verdict, list and delete documents."},{"name":"Account","description":"Your organization's check balance."}],"paths":{"/documents":{"post":{"operationId":"documents.create","description":"Upload a PDF and get its analysis. The request waits for the engine for up to 20 seconds:\nmost documents come back `completed` with a `200`. If the analysis takes longer, the answer\nis a `202` with `status: processing` — the analysis continues, and `GET /documents/{id}`\nreturns the result once it is ready.\n\nOne document is one check. Documents the engine cannot analyse (`unsupported`, `failed`)\nare not charged.","summary":"Create a document","tags":["Documents"],"parameters":[{"name":"Idempotency-Key","in":"header","description":"A new unique string for every upload, up to 255 characters — a fresh UUID or your own record id. Repeating the same upload with the same key within 24 hours returns the original document and charges nothing — safe to retry after a timeout. The same key with a different file, `external_id` or `metadata` is refused with `409 idempotency_key_reused`.","schema":{"type":"string"},"example":"NEW-KEY-FOR-EVERY-UPLOAD"}],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/StoreDocumentRequest"}}}},"responses":{"200":{"description":"The analysis finished within the wait window. `status` is `completed` or `failed`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Document"},"examples":{"completed":{"summary":"Analysed within the wait window","value":{"id":"doc_3f9a6c1e8b2d4f7a9c0e1b3d5f7a9c1e","object":"document","status":"completed","external_id":"tenant-123","metadata":{"applicant_id":"A-1029"},"created_at":"2026-10-03T10:15:00Z","file":{"name":"statement-september.pdf","size_bytes":184223,"sha256":"b5d4045c3f466fa91fe2cc6abe79232a1a57cdf104f7a26e716e0a1e2789df78","pages":3,"document_class":"born_digital"},"verdict":"tampered","basis":"negative","reference_match":null,"score":3,"tamper_score":0.87,"integrity_score":0.05,"confidence":0.82,"summary":"The file was re-saved by an editor after issue, and a balance on page 2 does not add up.","signals":[{"id":"editor_producer_blacklist","module":"M4","kind":"tamper","severity":"high","scored":true,"evidence":"Producer: Adobe Acrobat Pro DC"},{"id":"invariant_balance","module":"M8","kind":"tamper","severity":"medium","scored":true,"page":2,"bbox":[72,412.5,188,426],"evidence":"Closing balance: expected 4310.50, found 6310.50"}],"engine":{"version":"1.0.0","rules_revision":"2026-07-07"},"report_url":"https://atomara.io/account/uploads/3f9a6c1e8b2d4f7a9c0e1b3d5f7a9c1e","error":null}}}}},"headers":{"X-Request-Id":{"description":"This request's id (`req_…`). Quote it to support; find it in the dashboard's API request log.","schema":{"type":"string"}}}},"202":{"description":"The analysis is still running. Poll `GET /documents/{id}`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Document"},"examples":{"processing":{"summary":"Still running","value":{"id":"doc_3f9a6c1e8b2d4f7a9c0e1b3d5f7a9c1e","object":"document","status":"processing","external_id":"tenant-123","metadata":{"applicant_id":"A-1029"},"created_at":"2026-10-03T10:15:00Z","file":{"name":"statement-september.pdf","size_bytes":184223,"sha256":"b5d4045c3f466fa91fe2cc6abe79232a1a57cdf104f7a26e716e0a1e2789df78","pages":null,"document_class":null},"verdict":null,"basis":null,"reference_match":null,"score":null,"tamper_score":null,"integrity_score":null,"confidence":null,"summary":null,"signals":[],"engine":null,"report_url":"https://atomara.io/account/uploads/3f9a6c1e8b2d4f7a9c0e1b3d5f7a9c1e","error":null}}}}},"headers":{"X-Request-Id":{"description":"This request's id (`req_…`). Quote it to support; find it in the dashboard's API request log.","schema":{"type":"string"}}}},"400":{"description":"Bad Request — `invalid_request`, `not_a_pdf`","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"invalid_request":{"summary":"invalid_request","value":{"error":{"code":"invalid_request","message":"Idempotency-Key must be 1 to 255 characters.","request_id":"req_01k6n8x3b2c9d7e5f4g3h2j1km"}}},"not_a_pdf":{"summary":"not_a_pdf","value":{"error":{"code":"not_a_pdf","message":"The uploaded file is not a PDF. Only `application/pdf` is accepted.","request_id":"req_01k6n8x3b2c9d7e5f4g3h2j1km"}}}}}},"headers":{"X-Request-Id":{"description":"This request's id (`req_…`). Quote it to support; find it in the dashboard's API request log.","schema":{"type":"string"}}}},"401":{"description":"Unauthorized — `unauthorized`","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"unauthorized":{"summary":"unauthorized","value":{"error":{"code":"unauthorized","message":"Missing, invalid or revoked API key. Send it as \"Authorization: Bearer <key>\".","request_id":"req_01k6n8x3b2c9d7e5f4g3h2j1km"}}}}}},"headers":{"X-Request-Id":{"description":"This request's id (`req_…`). Quote it to support; find it in the dashboard's API request log.","schema":{"type":"string"}}}},"402":{"description":"Payment Required — `quota_exceeded`","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"quota_exceeded":{"summary":"quota_exceeded","value":{"error":{"code":"quota_exceeded","message":"No checks left on this account. Contact us to add more.","request_id":"req_01k6n8x3b2c9d7e5f4g3h2j1km"}}}}}},"headers":{"X-Request-Id":{"description":"This request's id (`req_…`). Quote it to support; find it in the dashboard's API request log.","schema":{"type":"string"}}}},"409":{"description":"Conflict — `idempotency_key_reused`","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"idempotency_key_reused":{"summary":"idempotency_key_reused","value":{"error":{"code":"idempotency_key_reused","message":"The `Idempotency-Key` was already used, within 24 hours, for a different upload (another file, `external_id` or `metadata`). Nothing was stored; send a new key.","request_id":"req_01k6n8x3b2c9d7e5f4g3h2j1km"}}}}}},"headers":{"X-Request-Id":{"description":"This request's id (`req_…`). Quote it to support; find it in the dashboard's API request log.","schema":{"type":"string"}}}},"413":{"description":"Content Too Large — `file_too_large`","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"file_too_large":{"summary":"file_too_large","value":{"error":{"code":"file_too_large","message":"The file exceeds the upload size limit.","request_id":"req_01k6n8x3b2c9d7e5f4g3h2j1km"}}}}}},"headers":{"X-Request-Id":{"description":"This request's id (`req_…`). Quote it to support; find it in the dashboard's API request log.","schema":{"type":"string"}}}},"429":{"description":"Too Many Requests — `rate_limited`","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"rate_limited":{"summary":"rate_limited","value":{"error":{"code":"rate_limited","message":"Too many requests for this key. Wait for the `Retry-After` seconds.","request_id":"req_01k6n8x3b2c9d7e5f4g3h2j1km"}}}}}},"headers":{"Retry-After":{"description":"Seconds to wait before the next request.","schema":{"type":"integer"}},"X-Request-Id":{"description":"This request's id (`req_…`). Quote it to support; find it in the dashboard's API request log.","schema":{"type":"string"}}}},"500":{"description":"Internal Server Error — `internal_error`","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"internal_error":{"summary":"internal_error","value":{"error":{"code":"internal_error","message":"The file could not be stored. Try again.","request_id":"req_01k6n8x3b2c9d7e5f4g3h2j1km"}}}}}},"headers":{"X-Request-Id":{"description":"This request's id (`req_…`). Quote it to support; find it in the dashboard's API request log.","schema":{"type":"string"}}}}}},"get":{"operationId":"documents.list","description":"Documents sent with your organization's keys, newest first, a page at a time.\nWhile `has_more` is `true`, pass `next_cursor` as `cursor` to get the next page.\nDeleted documents are not listed.","summary":"List documents","tags":["Documents"],"parameters":[{"name":"limit","in":"query","description":"How many documents to return, newest first.","schema":{"type":"integer","default":20,"minimum":1,"maximum":100}},{"name":"cursor","in":"query","description":"Where to continue from: the `next_cursor` of the previous page. Omit for the first page.","schema":{"type":"string"}},{"name":"external_id","in":"query","description":"Only documents sent with this `external_id`.","schema":{"type":"string","maxLength":191},"example":"tenant-123-payslip"}],"responses":{"200":{"description":"A page of documents.","content":{"application/json":{"schema":{"type":"object","properties":{"object":{"type":"string","description":"Always `list`.","const":"list"},"data":{"type":"array","description":"The page of documents, newest first.","items":{"$ref":"#/components/schemas/Document"}},"has_more":{"type":"boolean","description":"Whether another page follows."},"next_cursor":{"type":["string","null"],"description":"Pass as `cursor` to get the next page. `null` on the last page.","examples":["eyJpZCI6MTI4LCJfcG9pbnRzVG9OZXh0SXRlbXMiOnRydWV9"]}},"required":["object","data","has_more","next_cursor"]},"examples":{"page":{"summary":"A page of documents","value":{"object":"list","data":[{"id":"doc_3f9a6c1e8b2d4f7a9c0e1b3d5f7a9c1e","object":"document","status":"completed","external_id":"tenant-123","metadata":{"applicant_id":"A-1029"},"created_at":"2026-10-03T10:15:00Z","file":{"name":"statement-september.pdf","size_bytes":184223,"sha256":"b5d4045c3f466fa91fe2cc6abe79232a1a57cdf104f7a26e716e0a1e2789df78","pages":3,"document_class":"born_digital"},"verdict":"tampered","basis":"negative","reference_match":null,"score":3,"tamper_score":0.87,"integrity_score":0.05,"confidence":0.82,"summary":"The file was re-saved by an editor after issue, and a balance on page 2 does not add up.","signals":[{"id":"editor_producer_blacklist","module":"M4","kind":"tamper","severity":"high","scored":true,"evidence":"Producer: Adobe Acrobat Pro DC"},{"id":"invariant_balance","module":"M8","kind":"tamper","severity":"medium","scored":true,"page":2,"bbox":[72,412.5,188,426],"evidence":"Closing balance: expected 4310.50, found 6310.50"}],"engine":{"version":"1.0.0","rules_revision":"2026-07-07"},"report_url":"https://atomara.io/account/uploads/3f9a6c1e8b2d4f7a9c0e1b3d5f7a9c1e","error":null}],"has_more":true,"next_cursor":"eyJpZCI6MTI4LCJfcG9pbnRzVG9OZXh0SXRlbXMiOnRydWV9"}}}}},"headers":{"X-Request-Id":{"description":"This request's id (`req_…`). Quote it to support; find it in the dashboard's API request log.","schema":{"type":"string"}}}},"400":{"description":"Bad Request — `invalid_request`","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"invalid_request":{"summary":"invalid_request","value":{"error":{"code":"invalid_request","message":"cursor is not valid.","request_id":"req_01k6n8x3b2c9d7e5f4g3h2j1km"}}}}}},"headers":{"X-Request-Id":{"description":"This request's id (`req_…`). Quote it to support; find it in the dashboard's API request log.","schema":{"type":"string"}}}},"401":{"description":"Unauthorized — `unauthorized`","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"unauthorized":{"summary":"unauthorized","value":{"error":{"code":"unauthorized","message":"Missing, invalid or revoked API key. Send it as \"Authorization: Bearer <key>\".","request_id":"req_01k6n8x3b2c9d7e5f4g3h2j1km"}}}}}},"headers":{"X-Request-Id":{"description":"This request's id (`req_…`). Quote it to support; find it in the dashboard's API request log.","schema":{"type":"string"}}}},"429":{"description":"Too Many Requests — `rate_limited`","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"rate_limited":{"summary":"rate_limited","value":{"error":{"code":"rate_limited","message":"Too many requests for this key. Wait for the `Retry-After` seconds.","request_id":"req_01k6n8x3b2c9d7e5f4g3h2j1km"}}}}}},"headers":{"Retry-After":{"description":"Seconds to wait before the next request.","schema":{"type":"integer"}},"X-Request-Id":{"description":"This request's id (`req_…`). Quote it to support; find it in the dashboard's API request log.","schema":{"type":"string"}}}},"500":{"description":"Internal Server Error — `internal_error`","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"internal_error":{"summary":"internal_error","value":{"error":{"code":"internal_error","message":"Something failed on our side. Safe to retry, ideally with the same `Idempotency-Key`.","request_id":"req_01k6n8x3b2c9d7e5f4g3h2j1km"}}}}}},"headers":{"X-Request-Id":{"description":"This request's id (`req_…`). Quote it to support; find it in the dashboard's API request log.","schema":{"type":"string"}}}}}}},"/documents/{id}":{"get":{"operationId":"documents.retrieve","description":"Returns the document and, once `status` is `completed`, its verdict, score and signals.\nWhile it is `processing`, poll every few seconds.","summary":"Retrieve a document","tags":["Documents"],"parameters":[{"name":"id","in":"path","required":true,"description":"The document id returned on upload.","schema":{"type":"string"},"example":"doc_3f9a6c1e8b2d4f7a9c0e1b3d5f7a9c1e"}],"responses":{"200":{"description":"The document. `status` is `completed` or `failed`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Document"},"examples":{"completed":{"summary":"Analysed","value":{"id":"doc_3f9a6c1e8b2d4f7a9c0e1b3d5f7a9c1e","object":"document","status":"completed","external_id":"tenant-123","metadata":{"applicant_id":"A-1029"},"created_at":"2026-10-03T10:15:00Z","file":{"name":"statement-september.pdf","size_bytes":184223,"sha256":"b5d4045c3f466fa91fe2cc6abe79232a1a57cdf104f7a26e716e0a1e2789df78","pages":3,"document_class":"born_digital"},"verdict":"tampered","basis":"negative","reference_match":null,"score":3,"tamper_score":0.87,"integrity_score":0.05,"confidence":0.82,"summary":"The file was re-saved by an editor after issue, and a balance on page 2 does not add up.","signals":[{"id":"editor_producer_blacklist","module":"M4","kind":"tamper","severity":"high","scored":true,"evidence":"Producer: Adobe Acrobat Pro DC"},{"id":"invariant_balance","module":"M8","kind":"tamper","severity":"medium","scored":true,"page":2,"bbox":[72,412.5,188,426],"evidence":"Closing balance: expected 4310.50, found 6310.50"}],"engine":{"version":"1.0.0","rules_revision":"2026-07-07"},"report_url":"https://atomara.io/account/uploads/3f9a6c1e8b2d4f7a9c0e1b3d5f7a9c1e","error":null}}}}},"headers":{"X-Request-Id":{"description":"This request's id (`req_…`). Quote it to support; find it in the dashboard's API request log.","schema":{"type":"string"}}}},"202":{"description":"The analysis is still running.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Document"},"examples":{"processing":{"summary":"Still running","value":{"id":"doc_3f9a6c1e8b2d4f7a9c0e1b3d5f7a9c1e","object":"document","status":"processing","external_id":"tenant-123","metadata":{"applicant_id":"A-1029"},"created_at":"2026-10-03T10:15:00Z","file":{"name":"statement-september.pdf","size_bytes":184223,"sha256":"b5d4045c3f466fa91fe2cc6abe79232a1a57cdf104f7a26e716e0a1e2789df78","pages":null,"document_class":null},"verdict":null,"basis":null,"reference_match":null,"score":null,"tamper_score":null,"integrity_score":null,"confidence":null,"summary":null,"signals":[],"engine":null,"report_url":"https://atomara.io/account/uploads/3f9a6c1e8b2d4f7a9c0e1b3d5f7a9c1e","error":null}}}}},"headers":{"X-Request-Id":{"description":"This request's id (`req_…`). Quote it to support; find it in the dashboard's API request log.","schema":{"type":"string"}}}},"401":{"description":"Unauthorized — `unauthorized`","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"unauthorized":{"summary":"unauthorized","value":{"error":{"code":"unauthorized","message":"Missing, invalid or revoked API key. Send it as \"Authorization: Bearer <key>\".","request_id":"req_01k6n8x3b2c9d7e5f4g3h2j1km"}}}}}},"headers":{"X-Request-Id":{"description":"This request's id (`req_…`). Quote it to support; find it in the dashboard's API request log.","schema":{"type":"string"}}}},"404":{"description":"Not Found — `not_found`","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"not_found":{"summary":"not_found","value":{"error":{"code":"not_found","message":"No such document.","request_id":"req_01k6n8x3b2c9d7e5f4g3h2j1km"}}}}}},"headers":{"X-Request-Id":{"description":"This request's id (`req_…`). Quote it to support; find it in the dashboard's API request log.","schema":{"type":"string"}}}},"429":{"description":"Too Many Requests — `rate_limited`","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"rate_limited":{"summary":"rate_limited","value":{"error":{"code":"rate_limited","message":"Too many requests for this key. Wait for the `Retry-After` seconds.","request_id":"req_01k6n8x3b2c9d7e5f4g3h2j1km"}}}}}},"headers":{"Retry-After":{"description":"Seconds to wait before the next request.","schema":{"type":"integer"}},"X-Request-Id":{"description":"This request's id (`req_…`). Quote it to support; find it in the dashboard's API request log.","schema":{"type":"string"}}}},"500":{"description":"Internal Server Error — `internal_error`","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"internal_error":{"summary":"internal_error","value":{"error":{"code":"internal_error","message":"Something failed on our side. Safe to retry, ideally with the same `Idempotency-Key`.","request_id":"req_01k6n8x3b2c9d7e5f4g3h2j1km"}}}}}},"headers":{"X-Request-Id":{"description":"This request's id (`req_…`). Quote it to support; find it in the dashboard's API request log.","schema":{"type":"string"}}}}}},"delete":{"operationId":"documents.delete","description":"Deletes the stored file, the visual report and the analysis immediately, instead of waiting\nfor the 30-day retention to run out. A deleted document answers `404` from then on.\nDeleting does not refund the check.","summary":"Delete a document","tags":["Documents"],"parameters":[{"name":"id","in":"path","required":true,"description":"The document id returned on upload.","schema":{"type":"string"},"example":"doc_3f9a6c1e8b2d4f7a9c0e1b3d5f7a9c1e"}],"responses":{"200":{"description":"The document was deleted.","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","examples":["doc_3f9a6c1e8b2d4f7a9c0e1b3d5f7a9c1e"]},"object":{"type":"string","description":"Always `document`.","const":"document"},"deleted":{"type":"boolean","description":"Always `true`."}},"required":["id","object","deleted"]}}},"headers":{"X-Request-Id":{"description":"This request's id (`req_…`). Quote it to support; find it in the dashboard's API request log.","schema":{"type":"string"}}}},"401":{"description":"Unauthorized — `unauthorized`","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"unauthorized":{"summary":"unauthorized","value":{"error":{"code":"unauthorized","message":"Missing, invalid or revoked API key. Send it as \"Authorization: Bearer <key>\".","request_id":"req_01k6n8x3b2c9d7e5f4g3h2j1km"}}}}}},"headers":{"X-Request-Id":{"description":"This request's id (`req_…`). Quote it to support; find it in the dashboard's API request log.","schema":{"type":"string"}}}},"404":{"description":"Not Found — `not_found`","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"not_found":{"summary":"not_found","value":{"error":{"code":"not_found","message":"No such document.","request_id":"req_01k6n8x3b2c9d7e5f4g3h2j1km"}}}}}},"headers":{"X-Request-Id":{"description":"This request's id (`req_…`). Quote it to support; find it in the dashboard's API request log.","schema":{"type":"string"}}}},"429":{"description":"Too Many Requests — `rate_limited`","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"rate_limited":{"summary":"rate_limited","value":{"error":{"code":"rate_limited","message":"Too many requests for this key. Wait for the `Retry-After` seconds.","request_id":"req_01k6n8x3b2c9d7e5f4g3h2j1km"}}}}}},"headers":{"Retry-After":{"description":"Seconds to wait before the next request.","schema":{"type":"integer"}},"X-Request-Id":{"description":"This request's id (`req_…`). Quote it to support; find it in the dashboard's API request log.","schema":{"type":"string"}}}},"500":{"description":"Internal Server Error — `internal_error`","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"internal_error":{"summary":"internal_error","value":{"error":{"code":"internal_error","message":"Something failed on our side. Safe to retry, ideally with the same `Idempotency-Key`.","request_id":"req_01k6n8x3b2c9d7e5f4g3h2j1km"}}}}}},"headers":{"X-Request-Id":{"description":"This request's id (`req_…`). Quote it to support; find it in the dashboard's API request log.","schema":{"type":"string"}}}}}}},"/usage":{"get":{"operationId":"usage.retrieve","description":"How many checks the account has, has used and has left — the same numbers the dashboard\nshows. Checks are shared between the API and documents uploaded in the dashboard.","summary":"Retrieve usage","tags":["Account"],"responses":{"200":{"description":"The account's check balance.","content":{"application/json":{"schema":{"type":"object","properties":{"object":{"type":"string","description":"Always `usage`.","const":"usage"},"allowance":{"type":"integer","description":"Checks granted to the account in total.","examples":[1000]},"used":{"type":"integer","description":"Checks spent, by the API and the dashboard together.","examples":[324]},"remaining":{"type":"integer","description":"Checks left. At `0`, `POST /documents` answers `402 quota_exceeded`.","examples":[676]},"api_documents":{"type":"integer","description":"Documents ever sent through the API, charged or not.","examples":[311],"minimum":0}},"required":["object","allowance","used","remaining","api_documents"]}}},"headers":{"X-Request-Id":{"description":"This request's id (`req_…`). Quote it to support; find it in the dashboard's API request log.","schema":{"type":"string"}}}},"401":{"description":"Unauthorized — `unauthorized`","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"unauthorized":{"summary":"unauthorized","value":{"error":{"code":"unauthorized","message":"Missing, invalid or revoked API key. Send it as \"Authorization: Bearer <key>\".","request_id":"req_01k6n8x3b2c9d7e5f4g3h2j1km"}}}}}},"headers":{"X-Request-Id":{"description":"This request's id (`req_…`). Quote it to support; find it in the dashboard's API request log.","schema":{"type":"string"}}}},"429":{"description":"Too Many Requests — `rate_limited`","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"rate_limited":{"summary":"rate_limited","value":{"error":{"code":"rate_limited","message":"Too many requests for this key. Wait for the `Retry-After` seconds.","request_id":"req_01k6n8x3b2c9d7e5f4g3h2j1km"}}}}}},"headers":{"Retry-After":{"description":"Seconds to wait before the next request.","schema":{"type":"integer"}},"X-Request-Id":{"description":"This request's id (`req_…`). Quote it to support; find it in the dashboard's API request log.","schema":{"type":"string"}}}},"500":{"description":"Internal Server Error — `internal_error`","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"internal_error":{"summary":"internal_error","value":{"error":{"code":"internal_error","message":"Something failed on our side. Safe to retry, ideally with the same `Idempotency-Key`.","request_id":"req_01k6n8x3b2c9d7e5f4g3h2j1km"}}}}}},"headers":{"X-Request-Id":{"description":"This request's id (`req_…`). Quote it to support; find it in the dashboard's API request log.","schema":{"type":"string"}}}}}}}},"components":{"schemas":{"Document":{"type":"object","properties":{"id":{"type":"string","description":"Unique identifier of the document.","examples":["doc_3f9a6c1e8b2d4f7a9c0e1b3d5f7a9c1e"]},"object":{"type":"string","description":"Always `document`.","const":"document"},"status":{"$ref":"#/components/schemas/DocumentStatus"},"external_id":{"type":["string","null"],"description":"The reference you sent with the upload, or `null`.","examples":["tenant-123-payslip"]},"metadata":{"type":"object","description":"The key/value data you sent with the upload, returned as-is. `{}` when none.","examples":[{"applicant_id":"A-1029"}],"additionalProperties":[]},"created_at":{"type":"string","format":"date-time","description":"When the document was received, in UTC.","examples":["2026-10-03T10:15:00Z"]},"file":{"type":"object","properties":{"name":{"type":"string","description":"The file name as uploaded.","examples":["payslip-september.pdf"]},"size_bytes":{"type":"integer","examples":[184223]},"sha256":{"type":"string","description":"SHA-256 of the uploaded bytes, hex. The same file always has the same hash.","examples":["9f2c4e1a7b3d5f6e8a0c2b4d6f8e1a3c5b7d9f0e2a4c6b8d0f1e3a5c7b9d2f4e"]},"pages":{"type":["integer","null"],"description":"Page count, once the engine has opened the file.","examples":[2]},"document_class":{"type":["string","null"],"description":"How the pages were produced, once known: `born_digital` (a text layer written by software),\n`scanned_with_text_layer` or `raster` (pictures of paper — a scan or a photo),\n`no_text_layer`. New values may be added.","examples":["born_digital"]}},"required":["name","size_bytes","sha256","pages","document_class"]},"verdict":{"anyOf":[{"$ref":"#/components/schemas/Verdict"},{"type":"null"}]},"basis":{"type":["string","null"],"description":"What the verdict rests on, as named by the engine: `signature` (an intact digital signature),\n`reference_match` (the file matches a reference template of its issuer — see `reference_match`),\n`negative` (scored findings), `integrity_only`, `image_container` (a scan or photo, checked as an image),\n`inconclusive`. New values may be added.","examples":["inconclusive"]},"reference_match":{"type":["object","null"],"description":"Set only with `basis: reference_match`, otherwise `null`. `issuer` is the issuer's slug,\n`references` how many genuine documents taught the template. `attack_tested` is `true` when copies\nof those documents with a changed digit all failed the template; when it is `false`, a file with\nchanged amounts, dates or details has the same structure as the genuine one — the structure is\nconfirmed, the values are not. Never a confirmation from the issuer.","examples":[null],"properties":{"issuer":{"type":"string"},"references":{"type":"integer"},"attack_tested":{"type":"boolean"}},"required":["issuer","references","attack_tested"]},"score":{"type":["integer","null"],"description":"Authenticity score from 0 (edited) to 100 (intact) — the number the dashboard shows.\n0–30 tampered, 31–55 suspicious, 56–80 inconclusive, 81–100 authentic.\n`null` while processing, for `unsupported`, and for scans and photos (`basis: image_container`).","examples":[75]},"tamper_score":{"type":["number","null"],"description":"Raw engine output, 0–1: how strongly the scored findings point to editing.","examples":[0]},"integrity_score":{"type":["number","null"],"description":"Raw engine output, 0–1: how much of the file's structure and provenance checks out.","examples":[0.55]},"confidence":{"type":["number","null"],"description":"Raw engine output, 0–1: the engine's confidence in its verdict.","examples":[0.55]},"summary":{"type":["string","null"],"description":"One-paragraph explanation of the verdict, in English.","examples":["No signs of editing were found. The issuer is not confirmed."]},"signals":{"type":"array","description":"Every finding behind the verdict, in the engine's order. Empty while processing and when nothing fired.","items":{"$ref":"#/components/schemas/Signal"}},"engine":{"type":["object","null"],"description":"Which engine build produced the result. `null` until completed.","properties":{"version":{"type":["string","null"]},"rules_revision":{"type":["string","null"]}},"required":["version","rules_revision"]},"report_url":{"type":"string","description":"The human-readable report in the Atomara dashboard. Requires signing in to the account that owns the key."},"error":{"type":["object","null"],"description":"Why the analysis failed. Set only when `status` is `failed`.","properties":{"code":{"$ref":"#/components/schemas/DocumentErrorCode"},"message":{"type":"string","description":"What happened, in English.","examples":["The PDF is password-protected and cannot be analysed."]}},"required":["code","message"]}},"required":["id","object","status","external_id","metadata","created_at","file","verdict","basis","reference_match","score","tamper_score","integrity_score","confidence","summary","signals","engine","report_url","error"],"title":"Document"},"DocumentErrorCode":{"type":"string","description":"Why a document ended in `status: failed`. Unlike `ErrorCode`, this is not a failed request: the upload was accepted (HTTP 200) and the analysis itself could not run. A failed document is never charged. Published contract: add cases, never rename one.\n| |\n|---|\n| `password_required` <br/> The PDF is password-protected and cannot be opened. |\n| `not_a_pdf` <br/> The file claims to be a PDF but cannot be parsed as one. |\n| `file_too_large` <br/> The file exceeds the engine's size limit. |\n| `too_many_pages` <br/> The PDF has more pages than the engine analyses. |\n| `engine_error` <br/> The analysis could not be completed. Send the document again. |","enum":["password_required","not_a_pdf","file_too_large","too_many_pages","engine_error"],"title":"DocumentErrorCode"},"DocumentStatus":{"type":"string","description":"Where a document is in its analysis.\n| |\n|---|\n| `processing` <br/> Accepted; the analysis is still running. Poll `GET /documents/{id}`. |\n| `completed` <br/> The analysis finished. `verdict`, `score` and `signals` are set. |\n| `failed` <br/> The analysis could not run. `error` says why; the document was not charged. |","enum":["processing","completed","failed"],"title":"DocumentStatus"},"Error":{"type":"object","description":"The body of every 4xx and 5xx response.","properties":{"error":{"type":"object","properties":{"code":{"$ref":"#/components/schemas/ErrorCode"},"message":{"type":"string","description":"What went wrong, in English, for a developer to read. Branch on `code`, never on the message."},"request_id":{"type":"string","example":"req_01k6n8x3b2c9d7e5f4g3h2j1km","description":"This request's id, the same as the `X-Request-Id` header. Quote it to support; find it in the dashboard's API request log."}},"required":["code","message"]}},"required":["error"],"title":"Error"},"ErrorCode":{"type":"string","description":"Why a request failed. The HTTP status always matches the code.\n| |\n|---|\n| `invalid_request` <br/> A parameter is missing or malformed. The message names it. |\n| `not_a_pdf` <br/> The uploaded file is not a PDF. Only `application/pdf` is accepted. |\n| `unauthorized` <br/> The API key is missing, malformed or revoked. |\n| `quota_exceeded` <br/> The account has no checks left. Nothing was stored. |\n| `not_found` <br/> No such document in your organization — or no such route. |\n| `method_not_allowed` <br/> The HTTP method is not supported on this path. |\n| `idempotency_key_reused` <br/> The `Idempotency-Key` was already used, within 24 hours, for a different upload (another file, `external_id` or `metadata`). Nothing was stored; send a new key. |\n| `file_too_large` <br/> The file exceeds the upload size limit. |\n| `rate_limited` <br/> Too many requests for this key. Wait for the `Retry-After` seconds. |\n| `internal_error` <br/> Something failed on our side. Safe to retry, ideally with the same `Idempotency-Key`. |","enum":["invalid_request","not_a_pdf","unauthorized","quota_exceeded","not_found","method_not_allowed","idempotency_key_reused","file_too_large","rate_limited","internal_error"],"title":"ErrorCode"},"Signal":{"type":"object","properties":{"id":{"type":"string","description":"Stable identifier of the check that fired. The same id always means the same finding.","examples":["edited_glyph_span"]},"module":{"type":"string","description":"The engine module that raised it.","examples":["M5"]},"kind":{"anyOf":[{"$ref":"#/components/schemas/SignalKind"},{"type":"null"}]},"severity":{"anyOf":[{"description":"How much the finding weighs. `null` for integrity signals, which are not graded.","$ref":"#/components/schemas/SignalSeverity"},{"type":"null"}]},"scored":{"type":"boolean","description":"Whether the finding entered the score. `false` marks supporting evidence: shown, not counted."},"page":{"type":"integer","description":"1-based page the finding is on. Absent when it concerns the whole file.","examples":[2]},"bbox":{"type":"array","description":"Area on the page, in PDF points from the bottom-left corner: `[x0, y0, x1, y1]`. Absent when the finding has no area.","examples":[[72,510.4,240.1,524]],"items":{"type":"number"}},"field":{"type":"string","description":"For a value changed between revisions: the field that changed.","examples":["net_pay"]},"old_value":{"type":"string","description":"For a value changed between revisions: what it read before.","examples":["1,240.00"]},"new_value":{"type":"string","description":"For a value changed between revisions: what it reads now.","examples":["3,240.00"]},"evidence":{"type":"string","description":"What the engine saw, in English.","examples":["Glyphs on this line were set in a different font subset than the rest of the table."]}},"required":["id","module","kind","severity","scored"],"title":"Signal"},"SignalKind":{"type":"string","description":"| |\n|---|\n| `tamper` <br/> Evidence that content was changed. Moves `tamper_score`. |\n| `integrity` <br/> Evidence about the file's structure and provenance. Moves `integrity_score`. |","enum":["tamper","integrity"],"title":"SignalKind"},"SignalSeverity":{"type":"string","enum":["high","medium","low"],"title":"SignalSeverity"},"StoreDocumentRequest":{"type":"object","properties":{"file":{"type":"string","format":"binary","contentMediaType":"application/octet-stream","description":"The PDF to analyse, as a multipart file. Up to 10 MB and 100 pages; one document is one check,\nwhatever its page count. Scans and photos saved as PDF are accepted and analysed as images."},"external_id":{"type":["string","null"],"description":"Your own reference for this document — an applicant, a case, a row in your database.\nReturned on every read; filter `GET /documents` by it.","examples":["tenant-123-payslip"],"maxLength":191},"metadata":{"type":["string","null"],"description":"Up to 2 KB of your own key/value data: a JSON object, sent as a string field —\nfor example `{\"applicant_id\":\"A-1029\"}`. Stored and returned as-is; never analysed.","examples":[{"applicant_id":"A-1029"}]}},"required":["file"],"title":"StoreDocumentRequest"},"Verdict":{"type":"string","description":"The engine's verdict, passed through under its own names. Published contract: add cases, never rename one.\n| |\n|---|\n| `authentic` <br/> Either a valid digital signature covers the whole file (`basis: signature`), or the file matches, in every structural detail, a reference template learned from genuine documents of its issuer, with no sign of editing (`basis: reference_match`; read `reference_match.attack_tested` before trusting the values). |\n| `suspicious` <br/> Findings that point to editing, below the tampered threshold — or, with `basis: image_container`, a picture of a page that cannot be checked structurally. Review by hand. |\n| `tampered` <br/> Strong evidence that the content was changed after the document was produced. |\n| `inconclusive` <br/> Nothing proves the file genuine and nothing proves it edited. With no signals, the best outcome an unsigned file can have. |\n| `unsupported` <br/> The file has no text layer the engine can analyse. Not charged. |","enum":["authentic","suspicious","tampered","inconclusive","unsupported"],"title":"Verdict"}},"securitySchemes":{"apiKey":{"type":"http","scheme":"bearer","bearerFormat":"atm_live_…","description":"Your secret API key, created on the Developers page of the dashboard: `Authorization: Bearer atm_live_…`."}}},"security":[{"apiKey":[]}]}