2257 packet API
Processing version 9 · result field reference · machine-readable spec: /openapi.json · interactive reference: /docs
POST the files of one movie's 2257 records packet. The API combines them into one PDF (the page numbers in every answer refer to it), has a local vision model extract every performer, cuts and checks each ID copy, and serves the result and the PDF back. Extraction takes minutes, so the API is asynchronous: you POST, then poll.
Quick start
# 1. submit the packet: files in page order, plus any metadata you like
curl -s -F "files=@release.pdf" -F "files=@ID front.heic" -F "files=@cast list.xlsx" \
-F "title=Example Movie 5" -F "scene=1" -F "scene=3" \
http://127.0.0.1:8257/extractions
# -> 202 {"id": "9f2c…", "status": "queued", ...} Location: /extractions/9f2c…
# 2. poll every Retry-After seconds until status is done
curl -s http://127.0.0.1:8257/extractions/9f2c…
# 3. fetch the combined PDF
curl -s -o packet.pdf http://127.0.0.1:8257/extractions/9f2c…/pdf
When the server runs with a token, send Authorization: Bearer <token> on every call. This page,
/docs, /redoc, /openapi.json and /healthz are readable without it.
Endpoints
| Endpoint | Does |
|---|---|
POST /extractions | Submit a packet. 202 with the job id and a Location header. |
GET /extractions/{id} | Job status while queued or running; the full result when done; the error when failed. Field reference. |
GET /extractions/{id}/pdf | The combined PDF (application/pdf). |
GET /healthz | Liveness, the current processing version and the queue depth. |
GET / | This page. |
Job lifecycle and polling
queued → running → done or failed. One job runs at a time (one GPU); the rest wait in
arrival order. A typical packet takes 3 to 10 minutes once it starts.
| State | GET /extractions/{id} returns |
|---|---|
queued | 200 {"id", "status", "version", "created", "queue_position"} and Retry-After: 30.
queue_position counts from 1 and excludes the running job: 1 means "next after the one running now". |
running | 200 {"id", "status", "version", "created", "started"} and Retry-After: 30. |
done | 200 with the result and an Expires-At header. |
failed | 500 Extraction Failed (problem+json) with the reason in detail. |
Poll at the Retry-After interval. Timestamps are ISO 8601 UTC: created, started once running,
finished and expires_at once done or failed. Derive queue time and run time from them.
The request
POST /extractions with multipart/form-data.
Files: parts named files
- Send each file as its own part named
files. Part order is page order in the combined PDF. - Accepted types: arw, avif, bmp, cr2, cr3, csv, dng, doc, docx, gif, heic, heif, jp2, jpeg, jpg, jxl, nef, orf, pdf, png, raf, rtf, rw2, tif, tiff, tsv, txt, webp, xlsx. A file's content decides its type, not its name: a
.jpgthat is really a PNG is read as a PNG (andrender_notessays so). Images are read by ImageMagick, so beyond the list above almost any raster format works, including camera RAW (.arw,.cr2,.nef,.dng, ...), HEIC, AVIF and JPEG 2000. Every image is decoded in full before it goes in. - A file that is no accepted type, or can't be read in full (a truncated JPEG, a broken PDF), is left out of the combined PDF and the
job goes ahead without it. Its
page_mapentry has"pages": []andexcludedwith the reason, in the 202 and in the result. Checkpage_mapforexcluded; fix the file and re-submit if it matters. Only when no file is usable is the request refused (422 Unsupported File). - PDF pages go in as-is. Each image becomes one letter-size page (landscape when wider than tall), turned upright, at full resolution: an upright JPEG goes in
byte for byte; any other image is converted to a quality-95 JPEG with full color resolution. A multi-page TIFF gives one page per frame; other
multi-frame images (an animated GIF) give their first frame, with a
render_notesentry. Office and text files are rendered by LibreOffice; spreadsheets print landscape, one page wide, so a row never splits across pages. - Identical files (same bytes) are included once;
page_maplists every uploaded name and the pages it landed on.
Metadata: every other field
- Any field that is not a file is metadata: any keys, any number, none required, no key is special.
- A key sent more than once keeps all its values, returned as a list.
- The model sees the metadata as context from the submitter, labelled as not coming from the documents. Every value it reports must still come from the documents themselves.
- It comes back unchanged as
metadatain the 202 and in the result. - Limits: 50 keys, keys up to 100 characters, values up to 2000. A file part under any name other
than
filesis refused (422 Invalid Metadata) rather than ignored.
The result
A done job returns one JSON document. Keep it: it is self-contained (the ID crop images are inside it) except for the PDF. Every field of it, with a complete example, is explained here.
| Field | Meaning |
|---|---|
status, version | "done", and the processing version that produced it. |
created, started, finished, expires_at | Timestamps; expires_at repeats the Expires-At header. |
packet_sha256 | SHA-256 of the combined PDF. |
pages | Page count of the combined PDF. |
page_map | One entry per uploaded file: {"file", "pages"}, plus duplicate_of for a file identical to an earlier one,
render_notes when a file's content isn't the type its name says or an office file could not be rendered as intended,
and excluded (with "pages": []) when the file was left out because it couldn't be read. |
metadata | Your metadata, as sent. |
answer | What the model found: the page inventory, performers, unassigned items (see below). |
id_crops | Every ID copy cut from the PDF: {"performer", "copy", "page", "status", "note", "jpeg_base64"}.
performer and copy count from 1 in answer order; page is where the crop was verified to be. |
pdf | Path of the combined PDF: /extractions/{id}/pdf. |
answer
pages: the model's inventory, one entry per page,{"page", "what"}, written before anything else.performers: one entry per person, with stage names, legal name, date of birth, ID type, number and expiration, production date, everyid_copiesentry (page, outline box, the fields as printed on that copy) and everydocumentsentry (releases, forms, cast lists).unassigned: IDs or forms the model could not tie to a performer.production_date: for the movie as a whole, if stated.- Page numbers refer to the combined PDF.
Dates
Every date is reported twice: as printed (for example date_of_birth) and parsed (date_of_birth_parsed, ISO YYYY-MM-DD,
or null when it can't be read). Numeric dates are read month-first, U.S. style (11/02/1986 is November 2, 05/04/19 is May 4),
unless that is impossible (21/11/1986 has no month 21, so it is November 21); two-digit years
pivot at 50 (69 is 1969, 30 is 2030), a birth date is never in the future, a production date listing several shoot days gives the earliest, and a
date printed without a day rounds the cautious way: a birth date to the last day of its month, an expiration or
production date to the first. Examples.
id_crops status
verified | Checked on its own: an ID, showing the copy's recorded date of birth. |
repaired | The first crop wasn't; a neighbouring page or a photo file was, and the crop was taken from there. |
failed | No crop could be vouched for; note says what the check saw. Advisory: the extraction stands. |
Identity and caching
- The job id is the extraction's identity, which is its inputs. With no metadata it is
packet_sha256. With metadata it is a hash of the packet and the metadata, since the metadata changes what the model is told. - Sending the same files and metadata again while the job exists returns the same id and does not re-extract.
- Sending them again after a
failedjob retries it. After expiry, or once the processing version has changed, they are processed afresh.
Errors
Every error is RFC 9457 application/problem+json:
{"type", "title", "status", "detail", "instance"}, plus extension members where noted.
| Status | Title | When / what to do |
|---|---|---|
| 401 | Unauthorized | Missing or wrong bearer token. |
| 404 | Not Found | Unknown id, or the job expired. Re-submit if you still need it. |
| 413 | Content Too Large | Over the size or file-count limit. |
| 422 | Unsupported File | None of the files is usable (each is no accepted type or can't be read), or there are no files; detail gives each file's reason. A packet with at least one usable file is accepted, with the others marked excluded in page_map. |
| 422 | Invalid Metadata | A file under a part name other than files, too many keys, or an oversized key or value. |
| 422 | Validation Error | The request isn't usable, for example no files at all. |
| 500 | Extraction Failed | The job failed; detail says why, with id and the timestamps. Re-submit the same inputs to retry. |
| 500 | Internal Server Error | A bug on our side: the API was about to send a response its own schema doesn't describe, and sent this instead. Report it with the time and the request. |
| 503 | Service Unavailable | The queue is full; retry after Retry-After seconds. |
Processing version
version is one integer for the whole of the processing: assembly, model, prompt, crop checks, date parsing and
response shape. It is raised whenever any of that changes what you get back. Each job reports the version that
processed it; /healthz reports the current one. The current version is 9.
Limits
| Request size | 500 MB |
| Files per request | 200 |
| Jobs waiting | 32 (then 503) |
| Metadata | 50 keys, 100-character keys, 2000-character values |
| Retention | 3 hours after a job finishes |
| Polling interval | Retry-After: 30 seconds |