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.

Personal details (legal names, birth dates, ID numbers, ID images) are protected on arrival and can be viewed only by signed-in staff at total2257.com, one performer at a time. This API never returns them.

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

EndpointDoes
POST /recordsSend 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 /healthzWhether 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:

PartRequiredWhat
recordyesThe record as JSON (below). A file part or a plain field; over 10 MB it must be a file part.
pdfnoThe production's packet PDF. Recommended: it is the evidence.
any namenoOne 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] }]
}
FieldWhat
ref_id, sourceYour 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, studioRequired. Name the production in lists and search.
metadataAnything else worth keeping about the production.
production_datesRequired. 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_documentsEach identity document seen for the performer. The verdict uses their date_of_births.
pages, filesWhat is on each page of the packet PDF, and the files it was assembled from.
Keep personal details out of 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"}]}
FieldWhat
idThe record's id, a UUID, now and once stored. GET /records/{id} reads it back.
status, queue_positionqueued, and how many records are ahead of it plus one.
verdict_passTrue when there is at least one performer and every verdict is pass.
performersOne 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.

CodeMeans
passAn ID document with a birth date, and 18 or older on the production date.
under_18Under 18 on the production date.
no_id_dobNo ID document with a birth date in the record.
id_dob_unparsedAn 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.

StatusTitleWhen / what to do
400Bad RequestThe multipart body is malformed.
401UnauthorizedMissing or wrong bearer token.
404Not FoundNo record with that id.
409Duplicate RecordThis source + ref_id is already stored or waiting to be stored; existing_id names it. Nothing was stored.
413Content Too LargeOver a limit.
415Unsupported Media TypeNot multipart/form-data.
422Unprocessable ContentNo 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.
500Internal Server ErrorA problem on our side. Nothing is half-stored; send the request again.
503Too Busy25 records (8 GiB) are already waiting to be stored. Nothing was stored; send it again after Retry-After (300) seconds.

Limits

LimitValue
Request body1 GiB
Files500 images, plus the PDF
record as a plain field10 MB (no limit as a file part beyond the body's)
Records waiting to be stored25 records or 8 GiB; beyond that, 503 Too Busy