Error Codes
Every API error returns a JSON body with error (human-readable) and code (machine-readable). Match against code in your retry / alerting logic — error messages may change wording over time; codes are stable.
| Code | HTTP | What it means | How to fix |
|---|---|---|---|
API_KEY_REQUIRED |
401 | Missing, malformed, or unrecognised x-api-key header. | Verify the key is exactly as issued (no extra whitespace), being sent as x-api-key (not Authorization), and not revoked. |
V1_NOT_AVAILABLE_ON_THIS_PLAN |
403 | Your account plan does not include v1 API access. | Contact hello@preflight.art to request enterprise access. |
IP_NOT_ALLOWED |
403 | This API key has an IP allowlist and your request originated from outside it. | Add your egress IP to the key allowlist via /account/api-keys → Edit, or remove the allowlist. |
INSUFFICIENT_SCOPE |
403 | Key is restricted to specific scopes and the called endpoint requires a different one. | Issue a new key with the needed scope (preflight:read / preflight:write / preflight:delete), or use an unrestricted key. |
FILE_MISSING |
400 | POST /v1/preflight received neither a file field nor a source_url. | Send the upload as multipart/form-data with a `file` field, or send a `source_url` download link (form field or JSON). |
SOURCE_URL_INVALID |
400 | source_url is empty, too long, not a URL, not https, or carries a user name or password. | Send a full https:// download link. |
SOURCE_URL_BLOCKED |
400 | source_url points at a private, local or reserved network address. | Use a link on the public internet. |
SOURCE_URL_FETCH_FAILED |
400 | The link could not be downloaded: host not found, connection refused, a non-2xx answer, too many redirects, or an empty file. | Open the link in a private browser window. It must download the file with no login. Send a public or pre-signed link. |
SOURCE_URL_NOT_A_FILE |
400 | The link returned a web page instead of a file. | Use the direct download link, not the preview or share page. |
SOURCE_URL_UNSUPPORTED_TYPE |
400 | The downloaded file is not a PDF, .ai, .eps, .png, .jpg or .zip. | Export the artwork as PDF, or pass source_filename with the right extension. |
SOURCE_URL_TOO_LARGE |
413 | The file at the link is over the upload limit. | Reduce the file (flatten, downsample images) and try again. |
SOURCE_URL_TIMEOUT |
400 | The link stopped responding, or the download took over 4 minutes. | Try again; if it keeps happening, upload the file directly. |
FILE_AND_SOURCE_URL |
400 | The request carried both a file and a source_url. | Send one or the other. |
JOB_NOT_FOUND |
404 | No job with that id is accessible to your account. | Double-check the id. A deleted job still returns status=deleted on GET /status, but its report/pdf/source return 404. |
NOT_READY |
409 | You called /report or /pdf before the job finished. | Poll GET /v1/preflight/{id}/status until status is complete. |
REPORT_REBUILDING |
503 | The stored PDF for this job came from a report design we have retired, and rebuilding it in the current design did not finish. | Retry the download in a minute. If it keeps happening, re-run the file. |
ANALYSIS_FAILED |
422 | The engine could not analyze this file. | Check the error message; usually a corrupted PDF, password-protected file, or unsupported format. Re-export and resubmit. |
INVALID_PDF |
400 | Upload header doesnt start with %PDF-. | Confirm the file is a real PDF. .ai/.eps with embedded PDF should work too. |
PAYLOAD_TOO_LARGE |
413 | Uploaded file exceeds the 500 MB limit (or a smaller serverless body cap if reached via a proxy). | Send the file to the Railway endpoint directly, or reduce the file size (flatten, downsample images), and re-upload. |
INVALID_PROFILE |
400 | Unknown profile id passed in form data. | Use one of: digital-labels, flexo-labels, flexpack, cartons-folding. Check GET /v1/profiles. |
INVALID_ORDER_SPEC |
400 | order_spec was not valid JSON, or a field was out of range — a size outside 0.25"–60", a width without a height, or a non-boolean backprinted. | Send inches (not millimetres), send width_in and height_in together, and send backprinted as true or false. The error message names the field. |
RATE_LIMIT_HOURLY |
429 | You exceeded your hourly request cap on this key. | Wait the seconds returned in Retry-After. Contact support to raise the cap if this is recurring. |
RATE_LIMIT_DAILY |
429 | You exceeded your daily request cap on this key. | Wait until daily reset, or contact support. |
API_KEY_REJECTION_RATE_LIMIT |
429 | Too many bad-key attempts from your IP (anti brute-force). | Stop sending bad keys. Wait 15 minutes. Verify the secret store your code reads from has the right value. |
NO_WEBHOOK_URL |
400 | You called /webhooks/test with no URL configured on the key and no body override. | Edit the key to set a webhook URL, or pass {"url": "https://..."} in the body. |
NO_WEBHOOK_SECRET |
400 | The key was issued without a webhook URL, so no signing secret exists. | Re-issue the key with a webhook URL, or rotate it and add one. |
INVALID_WEBHOOK_URL |
400 | Override URL must be https://. | Always use https:// — we never deliver to http:// targets (SSRF + plaintext risk). |
EVENT_NOT_FOUND |
404 | event_id query param doesnt match any delivery for this key. | Verify the event id; only events from this key are visible. |
INVALID_EVENT_ID |
400 | event_id must be an integer. | Pass the numeric event_id returned by POST /v1/webhooks/test. |
ENQUEUE_FAILED |
500 | Internal error while queuing the webhook. | Retry once. If it persists, contact hello@preflight.art with the timestamp. |
REFERENCE_TOO_LONG |
400 | `reference` is longer than 128 characters. | Send your own short id for the work (a quote or PO number). It is refused rather than cut short, so a result is never written back to the wrong record. |
INVALID_REFERENCE |
400 | `reference` is not a string, or contains control characters. | Send plain text. |
REFERENCE_REQUIRED |
400 | POST /v1/intake-links was called without a reference. | Every intake link belongs to a quote or job: send `reference`, or send `quote_request_id` to get one link per quote request. |
QUOTE_REQUEST_ID_REQUIRED |
422 | POST /api/v1/upload-links was called without `quote_request_id`. | Send the id of the quote request the link collects artwork for. The same id always returns the same link. |
INVALID_QUOTE_REQUEST_ID |
422 | `quote_request_id` is not 1 to 128 printable characters. | Send your quote request id as plain text or a number. |
INVALID_FIELD |
422 | A link field (`qre`, `project_name`, `company`, `rep_name`, `rep_email`, `label`, `customer_name`, `job_description`) is not text, is too long, or `rep_email` is not an email address. | The response names the `field`. Send plain text, or null to clear it. |
INVALID_JSON |
422 | The body of POST /api/v1/upload-links is not JSON. | Send `Content-Type: application/json` and a JSON object. |
INTAKE_TOKEN_SECRET_MISSING |
503 | This server is not set up yet for links by quote request, or for the general upload page. | Contact hello@preflight.art. |
METADATA_TOO_LARGE |
400 | `metadata` is over 2 KB as JSON, or has more than 32 keys. | Keep metadata to a few short labels; it is echoed on every webhook. |
INVALID_METADATA |
400 | `metadata` is not a flat JSON object of strings, numbers or booleans. | Send one level of key/value pairs. Nested objects and arrays are not stored. |
INVALID_DEPTH |
400 | An intake link `depth` other than quick or full. | Use "quick" (the default) or "full". |
INVALID_EXPIRY |
400 | `expires_in_days` is not a number above 0 and at most 3650. | Omit it for a link that does not expire. |
INVALID_MAX_UPLOADS |
400 | `max_uploads` is not a whole number from 1 to 100000. | Omit it for unlimited uploads. |
INTAKE_LINK_NOT_FOUND |
404 | No intake link with that id or token is visible to this key. | Links are private to the key that made them. |
INTAKE_LINK_REVOKED |
410 | The link was revoked, or the API key that made it was. | Make a new link for the quote. |
INTAKE_LINK_EXPIRED |
410 | The link passed its `expires_in_days`. | Make a new link for the quote. |
UPLOAD_LIMIT |
410 | The link has taken its `max_uploads`. | Make a new link, or create links without a cap. |
NO_ARTWORK |
400 | Nothing in a drop-page upload could be checked. | The response lists each file and why. Upload a PDF, Illustrator (.ai) or EPS file, or a .zip of them. |
TOO_MANY_FILES |
400 | More than 25 files in one drop-page upload. | Upload in smaller groups, or zip them (up to 25 artworks per upload). |
FILE_TOO_LARGE |
413 | A drop-page file is over the 500 MB limit. | Reduce the file (flatten, downsample images) and upload again. |
ENCRYPTED_ZIP |
400 | The zip is password protected. | Remove the password and upload again. |
INVALID_PACKAGE_ZIP |
400 | The zip could not be read, or has no .ai, .pdf or .eps inside. | The message says what the zip contained. Zip the artwork itself. |
ZIP_TOO_LARGE |
400 | The artwork in the zip adds up to more than twice the upload limit unpacked, the zip unpacks to more than 5 GB, or it unpacks to more than 100 times its own size. | Split it into smaller zips, or zip the files again. |
SUBMISSION_TOO_LARGE |
413 | One drop-page upload is more than 2 GB in all. | Upload the files in smaller groups. |
BAD_SIGNATURE |
403 | A /v1/dl download link was altered or was not issued by us. | Use the link exactly as delivered, or mint a new one with GET /v1/preflight/{id}/links. |
LINK_EXPIRED |
410 | A /v1/dl download link is past its expiry. | Mint a new one with GET /v1/preflight/{id}/links (up to 7 days). |
KEY_REVOKED |
403 | The API key a download link was issued for has been revoked. | Links die with their key. Mint new ones with a current key. |
FILE_NOT_FOUND |
404 | The requested file is gone (past its retention) or was never produced (no report PDF). | Pull files into your own archive when the webhook arrives. |
SUBMISSION_NOT_FOUND |
404 | No submission (upload) with that id is visible to this key. | Use the upload_id from the upload, upload.completed or submission.ready. Submissions are private to the key they were made on. |
SUBMISSION_NOT_READY |
409 | POST /v1/submissions/{id}/resend was called while its files are still being checked. | Wait: submission.ready is sent by itself when every file has finished. |
EVENT_NOT_SUBSCRIBED |
409 | A resend of submission.ready on a key that is not subscribed to it. | Add submission.ready to the key's webhook events on /account/api-keys. It is never sent to a key that has not asked for it. |
SEND_IN_PROGRESS |
409 | The last send of this submission is being delivered at this moment. | Try again in a few seconds. Only one send of a submission is ever in flight. |
SUBMISSION_READ_FAILED |
500 | Internal error while reading a submission. | Retry once. If it persists, contact hello@preflight.art with the timestamp. |
LINK_NOT_FOUND |
404 | A /v1/f or /v1/r link that we did not issue, or a file link opened on the report path (or the other way round). | Use the link exactly as delivered. |
LINK_REVOKED |
410 | The links to this file were turned off with DELETE /v1/files/{id}/links. | Get new ones with GET /v1/files/{id}/links. |
FILE_EXPIRED |
410 | The original file has passed its key's retention period and is no longer stored. | Pull files into your own archive when the webhook arrives. The report stays readable. |
NOT_ARTWORK |
422 | ziflow-source was asked for a reference file (an image, .psd, .docx or .xlsx kept with a submission), not artwork. | Ask for an artwork file. Reference files have a file_link, which opens the original. |
FILE_LINKS_UNAVAILABLE |
503 | This server is not set up yet for links that do not expire. | Contact hello@preflight.art. |
SUBMITTER_REQUIRED |
400 | An upload on the general page without the sender's name, email or company. | The response names the `field`. The page asks for all three before it uploads. |
INVALID_EMAIL |
400 | The email typed on the general page does not look like an email address. | Check it and upload again. `field` is `email`. |
FILE_NAME_REQUIRED |
400 | A file was started (POST /v1/intake/{token}/parts) without `file_name`. | Send the file's name and its size in bytes. |
INVALID_SIZE |
400 | A file was started without its size in bytes. | Send `size` as a whole number of bytes. |
UNSUPPORTED_TYPE |
422 | A file was started whose type the drop page would only skip (an .indd, say). | Nothing was sent. Export it as PDF and upload that. |
TOO_MANY_PARTS |
429 | More than 100 files are on their way up on one link at once. | Finish those first; unfinished ones are removed after a day. |
PART_NOT_FOUND |
404 | That file is not on its way up on this link: never started, already handed over, or removed after a day with no piece. | Start it again. |
OFFSET_MISMATCH |
409 | A piece did not start where the file stops (a piece sent twice, or one skipped). | `received` says where it stops. Send the next piece from there. |
PART_BUSY |
409 | A piece of this file is being written at this moment. | Wait a moment, ask for `received`, and carry on from there. |
PART_OVERFLOW |
413 | The piece goes past the size the file was started with. | Send only the rest of the file. |
CHUNK_TOO_LARGE |
413 | A piece over 16 MB, or longer than its Content-Length. | Send pieces of `chunk_bytes` (8 MB). |
LENGTH_REQUIRED |
411 | A piece was sent without a Content-Length. | Send each piece as a plain body with its length. |
PART_INCOMPLETE |
409 | An upload named a file that has not finished arriving. Nothing was taken. | `part_id` and `received` say which and how far. Finish it and hand over again. |
PART_WRITE_FAILED |
500 | A piece could not be stored. | Send it again. If it persists, contact hello@preflight.art. |
FILE_NOT_IN_SUBMISSION |
404 | `replaces_file_id` is not a file in this submission. | Use a file_id from the submission's files (GET /v1/submissions/{id}). |
FILE_ALREADY_REPLACED |
409 | That file was already replaced by a corrected one. | `replaced_by` names the newer file: replace that one instead. |
NOT_A_LINK_SUBMISSION |
409 | Corrected files can be added to uploads on an intake link only. | Send the corrected file with POST /v1/preflight instead. |
ZIP_NOT_ACCEPTED |
422 | A corrected file was sent as a zip. | Send the file itself (a PDF, .ai or .eps, or a reference file). |
INVALID_FILE |
422 | The corrected file's bytes do not match its name (a .pdf that is not a PDF, say), or its type is not one we take. | The message says what to send. |
CORRECTION_FAILED |
500 | The corrected file could not be added. The submission is as it was. | Retry once. If it persists, contact hello@preflight.art. |
Webhook delivery headers
Two response headers worth wiring into your monitoring:
X-Preflight-Key-Expires-At— present on every response made with an API key inside its rotation grace window. ISO-8601 timestamp of final revocation. Alert if you see this — you should be swapping over to the new key.Retry-After— present on 429 responses. Seconds to wait before retrying.