Atomara API Reference

Interactive reference needs JavaScript. The same contract follows as text, and as an OpenAPI document or a Postman collection.

Endpoints

Base URL https://atomara.io/api/v1. Every request carries Authorization: Bearer <key>.

OperationWhat it does
POST /documentsCreate a document
GET /documentsList documents
GET /documents/{id}Retrieve a document
DELETE /documents/{id}Delete a document
GET /usageRetrieve usage

Introduction

The 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.

The same result is in your Atomara dashboard, so the person reviewing a flagged application reads exactly what your code read.

How teams fit it into tenant screening, lending, onboarding, marketplaces, car rental and KYC: Atomara for platforms.

Base URL   https://atomara.io/api/v1
Format     JSON over HTTPS; uploads as multipart/form-data
Auth       Authorization: Bearer atm_live_…

What a check can and cannot tell you

A 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.

That 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.

Quickstart

1. Create a key. Sign in to the dashboard, open Developers and create a key. It is shown once — store it in your secrets manager.

2. Send a document.

curl https://atomara.io/api/v1/documents \
  -H "Authorization: Bearer $ATOMARA_API_KEY" \
  -H "Idempotency-Key: tenant-123-payslip" \
  -F [email protected] \
  -F external_id=tenant-123-payslip

Send 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.

Using Postman? Import the Postman collection, 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.

3. Read the result. Most documents are analysed while the request is open (up to 20 seconds) and come back 200 with status: "completed":

{
  "id": "doc_3f9a6c1e8b2d4f7a9c0e1b3d5f7a9c1e",
  "object": "document",
  "status": "completed",
  "external_id": "tenant-123-payslip",
  "verdict": "inconclusive",
  "score": 75,
  "signals": []
}

The full object has more fields — see The Document object.

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.

Authentication

Every request carries your secret key as a Bearer token:

Authorization: Bearer atm_live_…

A missing, malformed or revoked key gets 401 unauthorized.

Verdicts and scores

A completed document carries three layers of answer — use the one that fits your workflow.

verdict — the decision

verdict Meaning
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).
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.
tampered Strong evidence that the content was changed after the document was produced.
inconclusive Nothing proves the file genuine and nothing proves it edited. With no signals, the best outcome an unsigned file can have.
unsupported The file has no text layer the engine can analyse. Not charged.

score — the same answer as a number

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:

score verdict
81–100 authentic
56–80 inconclusive
31–55 suspicious
0–30 tampered

score is null while the document is processing, for unsupported files, and for scans and photos (see Scans and photos).

A 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.

A 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).

Raw engine values

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.

signals — the evidence

Each 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.

Scans and photos

A 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.

Such a document comes back with:

The verdict of an image

file.document_class verdict What it means
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.
scanned_with_text_layer — a scan with a recognised-text (OCR) layer inconclusive Checked as an image; nothing was scored against it.

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.

An 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.

What 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.

A PDF with no text layer and no image the engine can analyse is unsupported and is not charged.

Processing

POST /documents waits for the analysis for up to 20 seconds:

Outcome HTTP status
Analysis finished in time 200 completed (or failed — see error)
Analysis still running 202 processing

A 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.

Set 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): you get the document that was already created, not a second charge.

Errors

A failed request answers with a 4xx or 5xx status and one body shape:

{
  "error": {
    "code": "not_a_pdf",
    "message": "The file is not a PDF. Only application/pdf is accepted.",
    "request_id": "req_01k6n8x3b2c9d7e5f4g3h2j1km"
  }
}

Branch on code; the message is for a developer to read and may be reworded.

Request ids

Every 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.

HTTP code Meaning
400 invalid_request A parameter is missing or malformed. The message names it.
400 not_a_pdf The uploaded file is not a PDF. Only application/pdf is accepted.
401 unauthorized The API key is missing, malformed or revoked.
402 quota_exceeded The account has no checks left. Nothing was stored.
404 not_found No such document in your organization — or no such route.
405 method_not_allowed The HTTP method is not supported on this path.
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.
413 file_too_large The file exceeds the upload size limit.
429 rate_limited Too many requests for this key. Wait for the Retry-After seconds.
500 internal_error Something failed on our side. Safe to retry, ideally with the same Idempotency-Key.

A document that failed

An 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.

error.code Meaning
password_required The PDF is password-protected and cannot be opened.
not_a_pdf The file claims to be a PDF but cannot be parsed as one.
file_too_large The file exceeds the engine's size limit.
too_many_pages The PDF has more pages than the engine analyses.
engine_error The analysis could not be completed. Send the document again.

Retrying

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.

Limits and quotas

Limit Value
File types application/pdf only (checked from the file's bytes, not its name)
File size 10 MB
Pages 100 per document
Requests 60 per minute per key
external_id 191 characters
metadata 2 KB, a JSON object

Over the request limit, the answer is 429 rate_limited with a Retry-After header: wait that many seconds.

Checks

One document is one check, whatever its page count. Checks are shared between the API and the dashboard; GET /usage returns the balance.

Not charged:

With no checks left, POST /documents answers 402 quota_exceeded and stores nothing. Reading, listing and deleting documents keep working.

Idempotency

Networks 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.

Within 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.

Use 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.

Data retention and privacy

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.

Details: Privacy Policy · Security.

Versioning and changelog

The version is in the path: /api/v1. Within v1, changes are additive only:

Write 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.

1.2.0 — 2026-10-16

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.

1.1.0 — 2026-10-04

Every 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.

1.0.0 — 2026-10-03

First release: documents (create, retrieve, list, delete) and usage.