Submitting a case
Four ways to get a case package into Aletheia, plus the outbound webhook
A case package is the documents for one applicant, typically an ID, a bank statement, one or
more payslips, sometimes a pension notice. Aletheia accepts case packages four ways. All four
converge on the same deterministic engine and produce the same ForensicResult schema; auth and
error model (ProblemDetails) are identical across them.
| Path | Direction | Sync? | Use when |
|---|---|---|---|
(a) Stateless POST /analyze | client to us, push | sync | one-shot integrations, demos |
| (b) Stateful v1 lifecycle | client to us, push | async-capable | uploads over time, separated audit, async batch |
| (c) ZIP upload | client to us, push | async (submit + poll) | one signed bundle per case |
| (d) Outbound result webhook | us to client, push | , | register a callback URL, we POST the verdict on completion |
Recommended default: push to us (paths a, b, c) plus register a callback (path d) for async delivery. You do not need to host your documents on a public URL, Aletheia accepts uploads directly.
An inbound URL-fetch option also exists (you host documents at HTTPS URLs and Aletheia fetches them), see below. It is not recommended for new integrations; it puts document hosting, allowlisting, and availability on you. It is offered only where an existing pipeline already serves documents that way.
1. Stateless POST /analyze
One synchronous request, returns the verdict. The simplest integration path.
Required headers: Authorization: Bearer <token>; X-Tenant-ID (Mode B only, optional);
Content-Type: multipart/form-data.
Multipart body:
| Field | Required | Description |
|---|---|---|
files | yes | 1-15 files, repeat the field name per file. PDF, JPG, PNG, HEIC, TIFF. Max 20 MB each, 50 MB total. |
applicant_id | no | Caller-supplied label, echoed in the response |
case_meta | no | JSON string of arbitrary metadata, logged in the audit trail |
There is no per-doc-type multipart field. All files go into files=. Document type is inferred
from the filename stem; filenames that match nothing get an unrecognized_document_type HIGH
finding and the rest of the case still processes.
curl -X POST "$ALETHEIA_BASE_URL/analyze" \
-H "Authorization: Bearer $TOKEN" \
-F "files=@applicant_passport.pdf" \
-F "files=@applicant_statement.pdf" \
-F "applicant_id=CASE-2026-000123"Status codes: 401 (no/invalid bearer), 413 (body over 50 MB), 422 (validation), 429
(rate-limited, Retry-After header), 500 (ProblemDetails includes trace_id).
2. Stateful v1 lifecycle
Same upload mechanics, split into steps: create the applicant (POST /v1/applicants, duplicate
applicant_id returns 409), upload documents (POST /v1/applicants/{id}/documents), run
analysis (POST /v1/applicants/{id}/analyze, returns 409 if no documents were uploaded),
fetch findings later (GET /v1/applicants/{id}/findings).
An async batch variant (POST /v1/batch/analyze) triggers when the applicant list reaches the
tenant's async-batch threshold (default 50); poll GET /v1/batch/jobs/{job_id}.
3. ZIP upload, submit and poll
Submit one .zip holding the full case package. Aletheia unpacks it, infers document type per
entry, and analyzes asynchronously: POST /analyze/zip returns a job_id immediately
(202 Accepted); poll GET /analyze/zip/{job_id} until status is done (result carries the
full ForensicResult) or failed.
Archive rules: entries must be .pdf/.jpg/.jpeg/.png/.heic/.heif/.tif/.tiff; limits are 15
entries, 20 MB per entry, 50 MB total unpacked, 50 MB zip body. Encrypted, empty, nested-zip,
path-traversal, and zip-bomb archives are rejected with 422/413.
If you already run your own OCR and hold a third-party audit trail for it, you can send it
alongside the ZIP as a multipart metadata part or as a metadata.json entry inside the
archive. It is stored and echoed back under intake_metadata in the poll response; the engine
still drives the verdict from Aletheia's own extraction.
4. Outbound result webhook
Aletheia POSTs the verdict to a callback URL you register, use this when you'd rather receive
the result than poll for it. Configured per tenant with a callback URL and a shared HMAC secret;
signed with X-Forensic-Signature (see Webhooks).
Retries: 3 attempts with exponential backoff on any non-2xx response; acknowledge with any 2xx.
5. Inbound URL-fetch, optional
Optional, not recommended for new integrations. In this mode you host documents at HTTPS URLs
and Aletheia fetches them server-side, with an SSRF host-allowlist, HMAC request signing, and
idempotency. The endpoint is POST /v1/integrations/inbound/webhook; the tenant is derived from
the bearer key, and enrolment requires a provisioned inbound webhook secret, signed with the
X-Inbound-Signature header, see Webhooks. The
document host allowlist is registered per tenant during onboarding.
Which path to use
| If you are… | Use |
|---|---|
| just integrating and want the simplest path | (1) stateless /analyze |
| uploading documents incrementally or running async batches | (2) stateful v1 |
| handing off one signed bundle per case | (3) ZIP upload |
| an async result consumer that prefers callbacks over polling | (4) outbound webhook |
| an AI agent integrator | the MCP server wraps path 1 |
| a pipeline that already serves documents from HTTPS URLs | (5) inbound URL-fetch (optional) |