Quickstart
Five minutes from zero to a working integration. You'll submit a PDF, poll for completion, and download the report — all from the command line.
1. Get an API key
Your account owner mints a key for you via /account/api-keys (or asks hello@preflight.art). Keys look like pf_AbCd… and are shown exactly once at creation — store it in a secrets manager.
preflight:write + preflight:read scopes only — no preflight:delete unless you actively delete jobs.2. Submit a file
Multipart upload to POST /v1/preflight. The profile field picks the rule set:
profile. It is not inferred from the artwork, and the default is digital-labels. A pouch submitted without it is judged against label rules, which is a different bleed requirement (0.0625" instead of 0.125") and a different answer, not an error. Every response echoes profile_id back so you can assert on it.A profile is a format x method pair. The format decides the structural and geometric rules (a pouch is decomposed into panels and gussets; a label is not), the method decides the press physics (plates and trapping on flexo, cylinders on gravure, RIP-managed registration on digital). Call GET /v1/profiles for the live list, which also carries method, format, isDigital and held per entry.
| Format | Flexo | Digital | Gravure | Offset |
|---|---|---|---|---|
| Labels | flexo-labels | digital-labels | — | — |
| Flexible packaging | flexpack | digital-flexpack | gravure-flexpack | offset-flexpacknot released yet |
| Folding cartons | cartons-folding | cartons-folding-digital | — | cartons-folding |
Seven profiles are released. Flexo flexible packaging is flexpack, not flexo-flexpack: it was the first flexpack profile and kept the plain name. One paperboard profile, cartons-folding, covers both flexo and offset cartons, which is why it appears twice in that row.
The one marked not released yet, offset-flexpack, is built but not finished. GET /v1/profiles reports it as held: true, the web picker does not offer it, and we have not yet confirmed it against real formed-tube work. The API will accept it by id so we can test with a customer, but do not build a production integration on it until we tell you it is released. If you print offset flexible packaging today, run flexpack and talk to us. Rotogravure flexible packaging is released: send gravure-flexpack.
curl -X POST https://preflight.art/v1/preflight \
-H "x-api-key: $PREFLIGHT_KEY" \
-F "file=@/path/to/artwork.pdf" \
-F "profile=flexo-labels"Quick check vs. deep report. Add -F "depth=quick" for a fast deterministic intake screen (color space, image resolution, embedded vs. linked images, fonts, page-box bleed, named-dieline presence) that returns in seconds with no AI calls — ideal for an automated accept/reject gate such as a hot folder. Quick findings carry the same shape as the deep report, each with a fix field, plus a quick_verdict (PASS / FIX_REQUIRED / UNREADABLE). Omit depth (or send depth=full) for the complete deep prepress report. An account can be set up to run quick checks when depth is omitted; depth=full always returns the deep report.
Checking the file against the order. Send an order_spec field — a JSON object describing what was bought — and the report compares the artwork against it: the finished size against the traced cut path, the shape against the measured cut geometry, the material against the white-ink requirement for that stock, and backprinted against the file's page structure (page 1 front, page 2 back). Every field is optional; anything that could not be compared is listed in a single "part of the order could not be checked" advisory rather than being quietly assumed to pass. An order size outranks a size in jobDescription, which outranks an NxM token in the file name — and a file name that contradicts an explicit order is reported, because that is what a wrong file attached to an order looks like.
curl -X POST https://preflight.art/v1/preflight \
-H "x-api-key: $PREFLIGHT_KEY" \
-F "file=@/path/to/artwork.pdf" \
-F "profile=digital-stickers" \
-F "depth=quick" \
-F 'order_spec={"width_in":3,"height_in":3,"shape":"circle",
"material":"holographic vinyl","product_type":"die-cut sticker",
"backprinted":false}'
width_in and height_in are inches and must be sent together (0.25–60). shape, material and product_type are free text and are matched against the trade vocabulary — a term that isn't recognized is reported as unchecked, never guessed at. backprinted is a boolean. A malformed order_spec returns 400 INVALID_ORDER_SPEC rather than being partly read.
Packaged Illustrator files. Accepted file types: .pdf, .ai, .eps, and a packaged .zip (the output of Illustrator's File ▸ Package). When a .zip is sent, the server unpacks the artwork plus the Adobe Report.txt and reports the facts a standalone file can't show on its own — missing fonts, protected/Adobe fonts that weren't packaged, missing links, and each linked image's true effective DPI and color mode. The heavy /Links and /Fonts folders inside the zip are skipped, so large packages upload efficiently.
A lone .ai (no package) is bounced for the package. If a standalone .ai places linked images but arrives without the package, the file is returned as FIX_REQUIRED (quick) / CONDITIONAL_PASS (deep) asking for the full File ▸ Package output — because its embedded preview can mask a low-resolution link or a missing/protected font (true link DPI and font licensing live only in the Report.txt). The triggering finding carries requestPackage: true so your integration can prompt the designer for the package instead of treating it as an artwork defect. A genuinely self-contained file (nothing placed as a link) is unaffected and passes normally.
Sending a link instead of a file. If the artwork already sits behind a download link (Zoho WorkDrive, Dropbox, S3, a pre-signed URL), send -F "source_url=https://..." (or "source_url" in a JSON body) in place of file. We download it and run it exactly as an upload: same file types, same size limit, same report and webhooks. The link must download the file directly with no login, over https. Add source_filename if the link does not carry a file name. Errors start with SOURCE_URL_ and are listed on /v1/errors.
Uploading a folder. There is no folder endpoint — HTTP multipart sends a single file — so zip the package folder first and POST the .zip. Only the artwork and the Report.txt are read, so you can zip just those (skipping /Links and /Fonts) to keep the upload small. The same depth=quick / depth=full switch applies to a .zip.
# zip the Illustrator package folder, then preflight it (quick gate)
zip -r MyLabel-package.zip "MyLabel Folder"
curl -X POST https://preflight.art/v1/preflight \
-H "x-api-key: $PREFLIGHT_KEY" \
-F "file=@MyLabel-package.zip" \
-F "profile=flexo-labels" \
-F "depth=quick"
# only the artwork + report are needed — zip just those for a tiny upload
zip MyLabel-min.zip "MyLabel Folder/MyLabel.ai" "MyLabel Folder/MyLabel Report.txt"
import fs from 'node:fs';
const form = new FormData();
form.set('file', new Blob([fs.readFileSync('artwork.pdf')]), 'artwork.pdf');
form.set('profile', 'flexo-labels');
const res = await fetch('https://preflight.art/v1/preflight', {
method: 'POST',
headers: { 'x-api-key': process.env.PREFLIGHT_KEY },
body: form,
});
const { job_id } = await res.json();
console.log('Submitted:', job_id);import os, requests
with open('artwork.pdf', 'rb') as f:
r = requests.post(
'https://preflight.art/v1/preflight',
headers={'x-api-key': os.environ['PREFLIGHT_KEY']},
files={'file': f},
data={'profile': 'flexo-labels'},
)
job_id = r.json()['job_id']
print('Submitted:', job_id)3. Poll for completion
Hit GET /v1/preflight/{job_id}/status every 3–5 seconds until status is complete or error. Typical jobs finish in 60–120 seconds. (The bare GET /v1/preflight/{job_id} path still works as a deprecated alias.)
curl https://preflight.art/v1/preflight/$JOB_ID/status -H "x-api-key: $PREFLIGHT_KEY"
# → { "job_id": "...", "status": "processing", "progress": "Analyzing with Claude..." }
# → { "job_id": "...", "status": "complete", "verdict": "CONDITIONAL_PASS",
# "critical_count": 1, "warning_count": 6, "info_count": 2 }
4. Fetch the report
Two endpoints — pick what your workflow needs:
| Endpoint | Returns | Use it for |
|---|---|---|
GET /v1/preflight/{id}/report | JSON | Drive your own dashboard, store findings in your DB, trigger workflows on verdict. |
GET /v1/preflight/{id}/pdf | PDF binary | Email to designers, archive for compliance, attach to a customer order. |
5. (Optional) Skip polling with a webhook
Configure a webhook URL on your API key (via /account/api-keys → Edit). On every preflight.completed and preflight.failed, we POST a signed event to your endpoint. See the Webhooks Guide for HMAC verification code in Node, Python, and Go.
What's next
- API Reference — every endpoint, every parameter, every response code
- Webhooks Guide — signature verification + retry behavior
- Error Codes — every
codeenum value with what to do