total2257 API
API version 2 · machine-readable spec: /openapi.json · interactive reference: /docs · /redoc
This is how compliance records get into total2257.com, where the compliance team reviews them. A record describes one production: its performers, their identity documents and the paperwork behind them. Records can come from any company workflow; each sends them in the one format described here.
When a workflow finishes a production's paperwork, it sends the record here. The API checks that every performer was 18 or older on their production date, stores the record under a new id, and answers with a verdict per performer. Failing records show up on the compliance team's to-do list at total2257.com.
Quick start
curl -s https://api.total2257.com/records \
-H "Authorization: Bearer $API_TOKEN" \
-F record=@record.json \
-F pdf=@packet.pdf \
-F id-1.jpg=@id-1.jpg -F id-2.jpg=@id-2.jpg
# -> 202 {"id": "0d6c…", "status": "queued", "queue_position": 1, "verdict_pass": true, "performers": [...]}
# Location: /records/0d6c…
Every call needs Authorization: Bearer <token> except /healthz, this page,
/docs, /redoc and /openapi.json.
Endpoints
| Endpoint | Does |
|---|---|
POST /records | Send a record. 202 at once with its id, verdicts and a Location header; it is stored shortly after. A Server-Timing header says how long each step took. |
GET /records/{id} | Where the record stands (queued, storing, stored, failed) and, once stored, what was stored without personal details: title, studio, notes, stage names and verdicts. |
GET /healthz | Whether the service, its database and its file storage answer (200 when all do, 503 otherwise), and how many records are waiting to be stored. |
GET / | This page. |
The request
POST /records takes multipart/form-data:
| Part | Required | What |
|---|---|---|
record | yes | The record as JSON (below). A file part or a plain field; over 10 MB it must be a file part. |
pdf | no | The production's packet PDF. Recommended: it is the evidence. |
| any name | no | One part per ID image (JPEG, PNG or WebP), under the name an id_documents[].image gives. Every image part must be referenced. |
Every request creates a new record with its own id. Give each one a ref_id (with its source) and sending the same one twice is refused with 409, so retrying is always safe.
The record
Dates are YYYY-MM-DD. When a document gives a date any other way, put its text in the matching
*_raw field instead (or as well). Required: title, studio,
production_dates and performers; everything else is optional. The full schema is in
/docs.
{
"ref_id": "your own reference",
"source": "2257ex-api",
"title": "Example Movie 5",
"studio": "Example Studio",
"production_dates": ["2024-05-04", "2024-05-05"],
"metadata": { "scene": "3" },
"performers": [{
"stage_names": ["Example Name"],
"legal_name": "…",
"date_of_birth": "1999-02-16",
"production_dates": ["2024-05-04"],
"id_documents": [{
"type": "driver license", "number": "…", "legal_name": "…",
"date_of_birth": "1999-02-16", "expires": "2027-02-16",
"page": 14, "image": "id-1.jpg"
}],
"documents": [{ "kind": "model release", "pages": [10], "form_date": "2024-05-04" }],
"notes": "…"
}],
"pages": [{ "page": 1, "what": "model release" }],
"files": [{ "name": "release.pdf", "pages": [1, 2] }]
}
| Field | What |
|---|---|
ref_id, source | Your own reference and the name of the workflow that produced the record. A ref_id must be unique for its source: sending the same pair again gets 409 Duplicate Record (with existing_id) and stores nothing, so a retry after a dropped connection is safe. Without ref_id nothing is checked. |
title, studio | Required. Name the production in lists and search. |
metadata | Anything else worth keeping about the production. |
production_dates | Required. A list of every shoot day, at least one, even for a one-day shoot. The earliest becomes the record's production date and is the day every performer must be 18 or older on; all of them are kept. A performer's own production_dates win for that performer. |
performers[].id_documents | Each identity document seen for the performer. The verdict uses their date_of_births. |
pages, files | What is on each page of the packet PDF, and the files it was assembled from. |
title, studio, metadata,
ref_id and source. They are shown in lists and search at total2257.com, so they must never
carry legal names, birth dates or ID numbers. Personal details belong in the performer fields.The response
202 Accepted comes back as soon as the record is received and checked; there is nothing more to send.
Storing it happens shortly after, in order: GET /records/{id} says when it is stored.
{"id": "0d6c1f7e-…", "status": "queued", "queue_position": 1, "verdict_pass": false,
"performers": [{"idx": 1, "verdict": "pass"}, {"idx": 2, "verdict": "under_18"}]}
| Field | What |
|---|---|
id | The record's id, a UUID, now and once stored. GET /records/{id} reads it back. |
status, queue_position | queued, and how many records are ahead of it plus one. |
verdict_pass | True when there is at least one performer and every verdict is pass. |
performers | One verdict per performer, numbered by position in performers (from 1). |
Following it: GET /records/{id} answers status queued or storing (ask again after
Retry-After, 30 seconds), then stored with the full record. In the rare case it says
failed, the record could not be stored: send it again.
Verdicts
The youngest ID reading governs: disagreeing ID documents pass only when every reading does.
| Code | Means |
|---|---|
pass | An ID document with a birth date, and 18 or older on the production date. |
under_18 | Under 18 on the production date. |
no_id_dob | No ID document with a birth date in the record. |
id_dob_unparsed | An ID birth date was sent only as date_of_birth_raw. |
Errors
Every error is RFC 9457 application/problem+json: {"type", "title", "status"}, plus
detail on 4xx and 503. detail names the field at fault and never quotes what you sent.
| Status | Title | When / what to do |
|---|---|---|
| 400 | Bad Request | The multipart body is malformed. |
| 401 | Unauthorized | Missing or wrong bearer token. |
| 404 | Not Found | No record with that id. |
| 409 | Duplicate Record | This source + ref_id is already stored or waiting to be stored; existing_id names it. Nothing was stored. |
| 413 | Content Too Large | Over a limit. |
| 415 | Unsupported Media Type | Not multipart/form-data. |
| 422 | Unprocessable Content | No record, not JSON, a required field missing (title, studio, production_dates, performers), or a field of the wrong kind (e.g. a date not as YYYY-MM-DD); an image that is missing, not an image, or not referenced. |
| 500 | Internal Server Error | A problem on our side. Nothing is half-stored; send the request again. |
| 503 | Too Busy | 25 records (8 GiB) are already waiting to be stored. Nothing was stored; send it again after Retry-After (300) seconds. |
Limits
| Limit | Value |
|---|---|
| Request body | 1 GiB |
| Files | 500 images, plus the PDF |
record as a plain field | 10 MB (no limit as a file part beyond the body's) |
| Records waiting to be stored | 25 records or 8 GiB; beyond that, 503 Too Busy |