{"openapi":"3.1.0","info":{"title":"total2257 API","version":"2","description":"How compliance records get into total2257.com. A record describes one production's performers and their identity documents, from any company workflow. Each record is checked (every performer 18+ on their production date) and answered with a verdict per performer. Personal details are never returned. The guide is at [/](/)."},"servers":[{"url":"/","description":"This server"}],"security":[{"bearer":[]}],"paths":{"/records":{"post":{"operationId":"createRecord","summary":"Send a record","description":"Send one record as JSON in the `record` part, with its packet PDF in `pdf` and each ID image as its own file part, named by `id_documents[].image`. The record is checked and answered at once (`202`, with its id and verdicts); storing it happens shortly after. Follow `Location` to see when it is stored. When 25 records (or 8 GiB) are waiting to be stored, new ones get `503 Too Busy`: send it again after `Retry-After` seconds.","requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/CreateRecordRequest"},"encoding":{"record":{"contentType":"application/json"},"pdf":{"contentType":"application/pdf"}}}}},"responses":{"202":{"description":"Accepted: checked, and will be stored. Nothing more to send.","headers":{"Location":{"$ref":"#/components/headers/Location"},"Retry-After":{"$ref":"#/components/headers/RetryAfter"},"Server-Timing":{"$ref":"#/components/headers/ServerTiming"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Accepted"}}}},"400":{"description":"The multipart body is malformed.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"401":{"description":"Missing or wrong bearer token.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"409":{"description":"A record with this `source` and `ref_id` is already stored or waiting to be stored. Nothing was stored; `detail` and the `existing_id` extension name it.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}},"headers":{"Server-Timing":{"$ref":"#/components/headers/ServerTiming"}}},"413":{"description":"Over a limit: 1 GiB body, 500 files, or a `record` field over 10 MB (send it as a file part).","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"415":{"description":"The body is not multipart/form-data.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"422":{"description":"The record is missing, not JSON, or does not match the format (including a missing `title`, `studio` or `production_dates`); a named image is missing or not an image; a file part is not referenced. `detail` names the field.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"500":{"description":"A problem on our side. Nothing is half-stored; send the request again.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"503":{"description":"Too Busy: 25 records (or 8 GiB) are already waiting to be stored. Nothing was stored; send it again after `Retry-After` seconds.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}},"headers":{"Retry-After":{"$ref":"#/components/headers/RetryAfter"}}}}}},"/records/{id}":{"get":{"operationId":"getRecord","summary":"Where a record stands, and what was stored (no personal details)","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"},"description":"The id from `Location`."}],"responses":{"200":{"description":"`status` says where the record stands: `stored` (the full record), `queued` / `storing` (not stored yet; ask again after `Retry-After`), or `failed` (it could not be stored: send it again).","headers":{"Retry-After":{"$ref":"#/components/headers/RetryAfter"}},"content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/Record"},{"$ref":"#/components/schemas/Pending"},{"$ref":"#/components/schemas/FailedRecord"}]}}}},"401":{"description":"Missing or wrong bearer token.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"No record with this id.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/healthz":{"get":{"operationId":"health","summary":"Liveness, database and file storage reachability, queue size","security":[],"responses":{"200":{"description":"The database and file storage answer.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Health"}}}},"503":{"description":"The database or file storage does not answer; the body says which.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Health"}}}}}}}},"components":{"securitySchemes":{"bearer":{"type":"http","scheme":"bearer","description":"The API token. Not needed for /healthz or the docs."}},"headers":{"Location":{"description":"Where the record can be read: `/records/{id}`.","schema":{"type":"string"}},"RetryAfter":{"description":"Seconds to wait before asking again (30 while a record waits to be stored, 300 after Too Busy).","schema":{"type":"integer"}},"ServerTiming":{"description":"How long each step of this request took (W3C Server-Timing): `receive` (upload + parsing), `validate`, `seal`, `spool`, `total`, each `name;dur=<ms>`. Steps a refused request never reached are left out.","schema":{"type":"string"}}},"schemas":{"CreateRecordRequest":{"type":"object","required":["record"],"properties":{"record":{"$ref":"#/components/schemas/RecordInput"},"pdf":{"type":"string","format":"binary","description":"The packet PDF (optional, recommended: it is the evidence)."}},"additionalProperties":{"type":"string","format":"binary","description":"ID images (JPEG, PNG or WebP), one part each, under the name an `id_documents[].image` gives. Every image part must be referenced."}},"RecordInput":{"type":"object","required":["title","studio","production_dates","performers"],"description":"One production's compliance record. Dates are `YYYY-MM-DD`; anything else goes in the matching `*_raw` field.","properties":{"ref_id":{"type":"string","description":"Your own reference. When given, it must be unique for its `source` (a missing source counts as one source): sending the same `source` + `ref_id` again gets 409 Duplicate Record and stores nothing. Compared after trimming spaces."},"source":{"type":"string","examples":["2257ex-api"],"description":"Free text: which workflow produced the record. Scopes `ref_id` uniqueness."},"title":{"type":"string","minLength":1,"description":"Required. Shown in lists and search: never personal details."},"studio":{"type":"string","minLength":1,"description":"Required. Shown in lists and search: never personal details."},"production_dates":{"type":"array","items":{"type":"string","format":"date"},"minItems":1,"description":"Required. Every shoot day, at least one. The earliest is the record's production date and the day the age check must pass; all of them are kept. A performer's own `production_dates` win for that performer.","examples":[["2019-07-24"],["2019-07-24","2019-07-26"]]},"production_dates_raw":{"type":"string","description":"The production date(s) as written on the document, when it is not a clean date (or alongside it)."},"metadata":{"type":"object","additionalProperties":true,"description":"Anything else worth keeping. Shown in lists and search: never personal details."},"performers":{"type":"array","items":{"$ref":"#/components/schemas/PerformerInput"}},"pages":{"type":"array","items":{"$ref":"#/components/schemas/PageEntry"},"description":"What is on each page of the packet PDF (optional)."},"files":{"type":"array","items":{"$ref":"#/components/schemas/SourceFile"},"description":"The source files the packet was assembled from (optional)."}}},"PerformerInput":{"type":"object","properties":{"stage_names":{"type":"array","items":{"type":"string"}},"legal_name":{"type":"string"},"date_of_birth":{"type":"string","format":"date","description":"As stated on the release or form."},"date_of_birth_raw":{"type":"string","description":"The birth date as written on the document, when it is not a clean date (or alongside it)."},"production_dates":{"type":"array","items":{"type":"string","format":"date"},"minItems":1,"description":"Optional: the performer's own shoot days when they differ from the record's, at least one if sent. The earliest is used for the age check.","examples":[["2019-07-24"],["2019-07-24","2019-07-26"]]},"production_dates_raw":{"type":"string","description":"The production date(s) as written on the document, when it is not a clean date (or alongside it)."},"id_documents":{"type":"array","items":{"$ref":"#/components/schemas/IdDocumentInput"},"description":"The verdict uses the youngest `date_of_birth` among these."},"documents":{"type":"array","items":{"$ref":"#/components/schemas/DocumentInput"}},"notes":{"type":"string"}}},"IdDocumentInput":{"type":"object","properties":{"type":{"type":"string","examples":["driver license"]},"number":{"type":"string"},"legal_name":{"type":"string"},"date_of_birth":{"type":"string","format":"date","description":"As read from the ID."},"date_of_birth_raw":{"type":"string","description":"The birth date as written on the document, when it is not a clean date (or alongside it)."},"expires":{"type":"string","format":"date","description":"Expiration date."},"expires_raw":{"type":"string","description":"The expiration date as written on the document, when it is not a clean date (or alongside it)."},"unreadable_fields":{"type":"array","items":{"type":"string"}},"page":{"type":"integer","description":"Page of the packet PDF it is on."},"image":{"type":"string","description":"Name of the file part holding this ID's image."}}},"DocumentInput":{"type":"object","additionalProperties":true,"properties":{"kind":{"type":"string","examples":["model release","2257 form"]},"pages":{"type":"array","items":{"type":"integer"}},"stage_name":{"type":"string"},"legal_name":{"type":"string"},"id_number":{"type":"string"},"date_of_birth":{"type":"string","format":"date","description":"As stated on the document."},"date_of_birth_raw":{"type":"string","description":"The birth date as written on the document, when it is not a clean date (or alongside it)."},"production_date":{"type":"string","format":"date","description":"As stated on the document."},"production_date_raw":{"type":"string","description":"The production date as written on the document, when it is not a clean date (or alongside it)."},"form_date":{"type":"string","format":"date","description":"Date the document was signed."},"form_date_raw":{"type":"string","description":"The form date as written on the document, when it is not a clean date (or alongside it)."}}},"PageEntry":{"type":"object","properties":{"page":{"type":"integer"},"what":{"type":"string"}}},"SourceFile":{"type":"object","properties":{"name":{"type":"string"},"pages":{"type":"array","items":{"type":"integer"}}}},"Verdict":{"type":"string","enum":["pass","under_18","no_id_dob","id_dob_unparsed","no_production_date","production_date_unparsed"],"description":"Per performer: `pass` = an ID birth date and 18+ on the production date (the youngest ID reading governs); `under_18`; `no_id_dob` = no ID document with a birth date in the record; `id_dob_unparsed` = only a `date_of_birth_raw`; `no_production_date` and `production_date_unparsed` cannot occur now that `production_dates` is required."},"PerformerVerdict":{"type":"object","required":["idx","verdict"],"properties":{"idx":{"type":"integer","minimum":1,"description":"Position in `performers`, from 1."},"verdict":{"$ref":"#/components/schemas/Verdict"}}},"Accepted":{"type":"object","required":["id","status","queue_position","verdict_pass","performers"],"properties":{"id":{"type":"string","format":"uuid","description":"The record's id, now and once stored."},"status":{"type":"string","const":"queued"},"queue_position":{"type":"integer","minimum":1,"description":"1 = stored next."},"verdict_pass":{"type":"boolean","description":"True when there is at least one performer and every verdict is `pass`."},"performers":{"type":"array","items":{"$ref":"#/components/schemas/PerformerVerdict"}}}},"Pending":{"type":"object","required":["id","status","queue_position"],"properties":{"id":{"type":"string","format":"uuid"},"status":{"type":"string","enum":["queued","storing"]},"queue_position":{"type":"integer","minimum":1}}},"FailedRecord":{"type":"object","required":["id","status","detail"],"properties":{"id":{"type":"string","format":"uuid"},"status":{"type":"string","const":"failed"},"detail":{"type":"string"}}},"Record":{"type":"object","required":["id","status","title","studio","production_date","metadata","created_at","verdict_pass","has_pdf","performers"],"properties":{"id":{"type":"string","format":"uuid"},"status":{"type":"string","const":"stored"},"ref_id":{"type":["string","null"]},"source":{"type":["string","null"]},"title":{"type":"string"},"studio":{"type":"string"},"metadata":{"type":"object","additionalProperties":true},"production_date":{"type":"string","format":"date","description":"The earliest of the `production_dates` sent."},"production_dates_raw":{"type":["string","null"]},"created_at":{"type":"string","format":"date-time"},"verdict_pass":{"type":"boolean"},"has_pdf":{"type":"boolean"},"performers":{"type":"array","items":{"type":"object","required":["idx","stage_names","verdict"],"properties":{"idx":{"type":"integer"},"stage_names":{"type":"array","items":{"type":"string"},"description":"A stage name equal to a legal name on the record is withheld."},"verdict":{"$ref":"#/components/schemas/Verdict"}}}}}},"Health":{"type":"object","description":"Checked at most every 30 seconds; the last result is reused in between.","required":["status","db","storage","spool"],"properties":{"status":{"type":"string","enum":["ok","degraded"]},"db":{"type":"string","enum":["ok","down"]},"storage":{"type":"string","enum":["ok","down"]},"spool":{"type":"string","enum":["ok","down"],"description":"Where records wait to be stored."},"queue":{"type":["object","null"],"description":"Records waiting to be stored (null if it could not be read).","properties":{"packets":{"type":"integer"},"bytes":{"type":"integer"}}}}},"Problem":{"type":"object","description":"RFC 9457 problem details. `detail` appears on 4xx and 503 only and never quotes submitted content.","required":["type","title","status"],"properties":{"type":{"type":"string","examples":["about:blank"]},"title":{"type":"string"},"status":{"type":"integer"},"detail":{"type":"string"}}}}}}