The API does what uploading a file on the Trace screen does: it starts a job, traces it with your print defaults, and hands back the same print-ready PDF. It is for a customer system or a shop script that uploads artwork and waits for the file, rather than a person at a screen.
Every address below starts with https://cutline.michaelagbozo.com/api/v1.
Getting a key
Make one under Settings → API tokens. It is shown once, when you make it, so copy it then. Send it on every request as a bearer token:
Authorization: Bearer cutl_your_key_here
Each key is given one or both of two permissions when it is made:
| Permission | What it reaches |
|---|---|
| jobs:read | Your account, your projects, your jobs and their proof pictures. |
| jobs:write | Starting a job, a shape or a batch, re-tracing one, and downloading the finished PDF or a batch's zip. |
Downloading needs jobs:write even though it only fetches a file: the first download marks the job as downloaded and counts it, exactly as it does on the screens.
A key is the account. Anything it does counts as you doing it, so give one out no more widely than you would the password, and delete it from Settings the moment it is not needed.
Limits
The same limits the screens have: up to 6 jobs waiting or tracing at once, and up to 60 new jobs an hour. On top of that, 120 requests a minute per account. Going over any of them answers 429 with a sentence saying which one, and nothing is lost — try again shortly.
Starting a job
Send the artwork as a multipart upload. PDF, PNG, JPEG, WebP or TIFF, up to 90 MB and 20 megapixels. Give the finished height or width in centimetres, or leave both out and the size is read from the filename the way the screens read it.
curl -sS -X POST https://cutline.michaelagbozo.com/api/v1/jobs \
-H "Authorization: Bearer cutl_your_key_here" \
-H "Idempotency-Key: leaf-reprint-01" \
-F "artwork=@LEAF_30CM.png" \
-F "height_cm=30" \
-F "parameters[alpha_cut]=240"
It answers 202 with the job, including its id and status. Send an Idempotency-Key and a retried upload returns the job the first one made, instead of starting a second.
| Field | What it is |
|---|---|
| artwork | Required. The file. |
| height_cm, width_cm | Optional, 1–1000 cm. One or the other — the artwork decides the second. |
| parameters[…] | Optional settings, laid over your print defaults. The table further down lists them. |
| project_id | Optional. One of your own projects. |
Shapes
A shape is a cut drawn around the artwork rather than traced from it — a rectangle, rounded corners, an arch, a circle or an ellipse, with folds or a scalloped edge if you want them. It is what the Shape screen makes, and it has its own address because its settings are not the trace settings.
curl -sS -X POST https://cutline.michaelagbozo.com/api/v1/shapes \
-H "Authorization: Bearer cutl_your_key_here" \
-F "artwork=@BANNER_200h.pdf" \
-F "parameters[shape]=rounded" \
-F "parameters[radius_cm]=5"
What comes back is a job like any other: wait for it, read it and download it through the job addresses below. To change its numbers afterwards, post them to /shapes/JOB_ID/retrace — anything you leave out stays as it was.
Batches
Up to 40 files in one request, sent as artwork[]. Each file becomes its own job, sized from its own filename and given its own copy of your print defaults, exactly as a drop on the batch screen is. There are no settings and no size fields for the whole batch — files in one drop need different tuning, so re-trace any one of them afterwards. quantities[] says how many pieces each file was ordered, by position.
curl -sS -X POST https://cutline.michaelagbozo.com/api/v1/batches \
-H "Authorization: Bearer cutl_your_key_here" \
-F "artwork[]=@LEAF_30CM.png" \
-F "artwork[]=@SIGN_250b.pdf" \
-F "quantities[1]=25"
Each file is answered on its own: one that cannot start says why, and the rest start anyway. Ask for /batches/BATCH_ID until it reads finished, then collect every ready PDF as one zip from /batches/BATCH_ID/download. The zip carries a manifest.csv with each file's quantity and page size.
Waiting for it
A trace takes five to forty seconds, so ask for the job every few seconds until its status is no longer queued or tracing — or have it sent to you instead, with a webhook (below).
curl -sS https://cutline.michaelagbozo.com/api/v1/jobs/JOB_ID \
-H "Authorization: Bearer cutl_your_key_here"
When it reads ready, the report on the job carries the same figures as the preflight on the Trace screen — page and artwork size, paths and how many close, and the effective resolution. Read them before you print, as you would on the screen.
Collecting the file
curl -sS -o LEAF_32h_21b.pdf https://cutline.michaelagbozo.com/api/v1/jobs/JOB_ID/download \
-H "Authorization: Bearer cutl_your_key_here"
Before the job is ready this answers 409. Files are kept seven days, the same as on Recent; after that it answers 404.
Being told instead of asking
Under Settings → Webhooks, add a URL of your own and pick what it hears about. Each job and batch is then posted to it, signed, as it lands. Traced files, shapes and panels all send the same job events.
| Event | When |
|---|---|
| job.ready | A job finished and its PDF is ready. |
| job.failed | A job could not be cut. The job says why. |
| job.needs_size | A job stopped because nothing said how big to cut it. Sent instead of job.failed. |
| batch.finished | Every file in a batch has stopped moving. |
The body is JSON: an id, the type, when it happened, and data — which is exactly what asking for that job, or that batch, would have answered at that moment. Each request carries a Cutline-Delivery header that stays the same on every retry, so you can ignore one you have already handled.
Checking the signature
Every endpoint has a signing secret, shown once when you add it. Each request carries a Cutline-Signature header of the form t=TIMESTAMP,v1=SIGNATURE, where the signature is an HMAC-SHA256, in hex, of the timestamp, a full stop and the raw request body, keyed with that secret. Refuse anything whose signature does not match, or whose timestamp is more than five minutes from your clock. Hash the body exactly as it arrived, not the JSON after you have parsed it.
$payload = file_get_contents('php://input');
$header = $_SERVER['HTTP_CUTLINE_SIGNATURE'] ?? '';
$secret = getenv('CUTLINE_WEBHOOK_SECRET');
$parts = [];
foreach (explode(',', $header) as $item) {
[$key, $value] = explode('=', $item, 2);
$parts[$key][] = $value;
}
$timestamp = (int) ($parts['t'][0] ?? 0);
$expected = hash_hmac('sha256', $timestamp.'.'.$payload, $secret);
$genuine = abs(time() - $timestamp) <= 300
&& array_filter($parts['v1'] ?? [], fn ($v) => hash_equals($expected, $v)) !== [];
http_response_code($genuine ? 204 : 400);
For a day after you make a new secret, each request carries two signatures, one for each key. Accept it if either matches, and swap the key on your side whenever suits you inside that day.
Answering
Answer with any 2xx within ten seconds, and do the work afterwards. Redirects are not followed. Anything else is tried again after 1 minute, 5 minutes, 30 minutes, 2 hours and 6 hours, then given up; every attempt is in the endpoint's delivery log. An endpoint still failing after a day of that is switched off and you are emailed. Switch it back on and send a test once it is fixed.
Webhooks go only to https addresses on the public internet. A URL that points, or later starts pointing, at a private, loopback or link-local address is refused — checked when you save it and again before every request.
Every call
| Call | Permission | What it does |
|---|---|---|
| GET /me | jobs:read | Who the key belongs to, what it may do, and your limits. |
| GET /projects | jobs:read | Your projects, 25 to a page. |
| GET /clients | jobs:read | Your clients and their machine profiles, 50 to a page. Pass a client_id when you start a job and it starts from that profile. |
| GET /jobs | jobs:read | Your jobs, 25 to a page. Filter with status, project_id, batch_id or since (a date and time). |
| GET /jobs/{id} | jobs:read | One job as it stands now. |
| GET /jobs/{id}/proof | jobs:read | The proof picture, as a PNG. |
| POST /jobs | jobs:write | Start a job. |
| POST /jobs/{id}/retrace | jobs:write | Trace the same upload again with new settings. A shape job re-runs through /shapes/{id}/retrace instead. |
| GET /jobs/{id}/download | jobs:write | The finished PDF, named NAME_Hh_Wb.pdf. |
| POST /shapes | jobs:write | Start a shape job. |
| POST /shapes/{id}/retrace | jobs:write | Draw a shape job again with new numbers. |
| POST /batches | jobs:write | Start up to 40 files at once. |
| GET /batches/{id} | jobs:read | Every job in a batch, and whether it has finished. |
| GET /batches/{id}/download | jobs:write | Every finished PDF in the batch as one zip, once the batch has finished. |
Settings you can send
Each one is optional and is laid over your print defaults, so leave out anything you have already settled. Sizes are centimetres; tolerances and offsets are millimetres.
| Setting | Default | On the screen |
|---|---|---|
| alpha_cut | 128 | Alpha cut, 0–255 |
| min_area | 0.02 | Minimum area, a fraction of the largest piece |
| open_mm | 0.8 | Opening, mm |
| relief_mm | 0 | Relief, mm |
| threshold | 16 | Background tolerance, used only without transparency |
| crop_bottom | 0 | Cut off the bottom, a fraction of the height |
| smooth_mm | 0.5 | Edge smoothing, mm |
| fit_error_mm | 0.25 | Curve fit tolerance, mm |
| offset_mm | 0 | Cut offset, mm. Negative pulls the cut inside the print. |
| clearance_cm | 2 | Page clearance, cm |
| stroke_pt | 3 | Stroke weight, pt |
| cut_holes | true | Gaps: cut the gaps the artwork encloses |
| hole_min_area_cm2 | 2 | Smallest gap to cut, cm² |
| hole_min_web_mm | 3 | Thinnest material to leave around a gap, mm |
| mirrored | false | Flip, for second-surface work. Left to right only. |
| weeding_gap_mm | 0 | Weeding box gap, mm. 0 is no box. |
| weeding_per_piece | false | One weeding box per separate piece |
| folds | 0 | Fold lines, 0–40 |
| fold_dir | vertical | vertical or horizontal |
| fold_spacing_cm | 0 | Gap between folds, cm. 0 spreads them evenly. |
| fold_inset_mm | 3 | Stop short of the cut, mm |
| fold_stroke_pt | 3 | Score weight, pt |
| on_existing_cut | fail | fail refuses artwork that already carries a cut path; ignore traces it anyway |
| on_printed_size | fail | fail stops when a size printed on the artwork disagrees with the job; ignore traces at the job's size |
Note that min_area is a fraction and hole_min_area_cm2 is square centimetres. They are two different measurements on the same job.
When something is refused
| Status | Meaning |
|---|---|
| 401 | No key, or a key that is not valid. Being signed in on the site does not count. |
| 403 | The key lacks the permission the call needs, or the account's email is not verified yet. |
| 404 | No such job — including a job that belongs to another account. |
| 409 | Downloading before the job is ready, a batch zip before the batch has finished, or re-tracing through the other kind of job's address. |
| 422 | A setting out of range, no size anywhere, or artwork over 20 megapixels. The message says which. |
| 429 | Over one of the limits above. |
Not in the API yet
- Panelling a piece wider than the press. Panelled jobs can be read and downloaded, not started or re-run.
- Adding or changing webhook endpoints. They are set up under Settings → Webhooks; the API only sends to them.
- Sending proofs to customers, and cancelling or deleting a job.
The rules that apply on the screens apply to work sent through a key — see the acceptable use policy.