Interactive OpenAPI (try-it-out, schemas):
{BASE}/docs
| Method & path | Purpose |
|---|
POST /v1/verifications | Create verification → handoff token + URL |
GET /v1/verifications/{id} | Status, reasons, extracted fields |
DELETE /v1/verifications/{id}/data | Cryptographic erasure |
POST /v1/attestations/{id} | Issue signed attestation (approved only) |
GET /v1/review/queue | Manual review queue (oldest first) |
POST /v1/review/{id} | Approve/reject with mandatory note |
| Method & path | Purpose |
|---|
GET /v1/capture/status | Bare status + next_step (no PII, ever) |
POST /v1/capture/document?side=front|back | Upload ID photo (multipart file) |
POST /v1/capture/challenge | Get the liveness pose sequence |
POST /v1/capture/selfie?step=N | Upload one challenge frame; processing starts after the last |
| Method & path | Purpose |
|---|
GET / | Landing + transparency statement |
GET /health | Liveness probe |
GET /docs | OpenAPI UI |
GET /v/{token} | Hosted capture page (token enforced by the API it calls) |
GET /v1/attestations/public-key | Ed25519 public key (PEM) |
| Constraint | Value |
|---|
| Content types | image/jpeg, image/png, image/webp |
| Max size | 15 MB |
| Min size | 1 KB (rejects junk/empty captures) |
| Frames per challenge step | Exactly one; re-uploads rejected (409) |
Per client IP, sliding window:
| Scope | Limit |
|---|
/v1/capture/* | 40 requests / minute |
/v1/* overall | 240 requests / minute |
On 429, honour the Retry-After header. Front with a CDN/WAF for volumetric
protection beyond this.
All errors return {"detail": "human-readable reason"} with conventional status
codes — the full table with causes and handling advice:
Errors.