Kastomise · API v1

Orders, designs and files API

Pull your shop's orders, saved designs and their print files into your own production system (a MES, a RIP hot folder, a print queue), and mark them printed when they come off the press, without opening the dashboard.

Getting started

  1. A shop admin opens Settings → API keys in the dashboard and makes a key with the permissions your system needs.
  2. Copy the key when it's shown: it is shown once. Store it on your server, never in a web page or an app.
  3. Send it with every request, then poll for orders.
curl https://app.kastomise.com/api/v1/orders?limit=5 \
  -H "Authorization: Bearer ksk_…"

Every response is JSON. Times are ISO 8601 in UTC. Money is in cents of the order's currency. Orders are your Shopify orders; designs are the designs shoppers save on your Kastomise Hosted pages, each with a code such as KD-7F3K9Q2M.

Keys and permissions

Send the key as Authorization: Bearer ksk_…. A key in the URL is ignored. A key belongs to one shop and sees only that shop's orders and designs. An admin can revoke it at any time, and it stops working at once. A shop can have 10 active keys: one per system makes it easy to revoke one without stopping the others.

Orders and designs have permissions of their own, and a key's permissions can't be changed: a key made before the designs API reads no design, so make a new key with designs:read. files:read covers the print files of both.

The API is for servers: it sends no CORS headers, so a browser page can't call it.

PermissionScopeAllows
Read ordersorders:readList orders and read one: lines, personalisation, proof, customer and shipping address
Read designsdesigns:readList saved Kastomise Hosted designs and read one: personalisation, proof, print info, the customer’s name and email
Download print filesfiles:readGet short-lived download links for the print files of orders and designs
Mark printedorders:write-printedMark order lines printed, or undo it
Update design statusdesigns:write-statusMark designs ordered or printed, with the order reference and platform

List orders

GET /api/v1/orders · orders:read. Orders changed since a time, oldest change first. An order changes when it arrives and when its print files are ready (files_ready).

ParameterMeaning
sinceISO time with a zone, e.g. 2026-10-20T00:00:00Z. Orders whose updated_at is at or after it.
limit1 to 100, default 50.
cursorThe next_cursor of the page before. It takes over from since; send the same print filter with it.
printwaiting, partial, printed or none (nothing to print).
curl "https://app.kastomise.com/api/v1/orders?since=2026-10-20T00:00:00Z&print=waiting" \
  -H "Authorization: Bearer ksk_…"

{
  "data": [
    {
      "id": "6f1c…",
      "name": "#1042",
      "number": 1042,
      "shopify_order_id": "5550001112223",
      "created_at": "2026-10-20T01:00:00.123+00:00",
      "updated_at": "2026-10-20T01:02:03.456+00:00",
      "status": "pending",
      "financial_status": "paid",
      "fulfillment_status": null,
      "files_ready": true,
      "print_state": "waiting",
      "line_count": 2
    }
  ],
  "has_more": true,
  "next_cursor": "WyIyMDI2LTEw…"
}

While has_more is true, ask again with cursor. An order that changes while you page can come back on a later page: you may see an order twice, never miss one.

Get an order

GET /api/v1/orders/{id} · orders:read. The order with the customer, the shipping address and its lines, numbered as on the dashboard. Each line has what the shopper typed and chose (personalisation), their proof approval (proof), 3D colours, whether it has anything to print (printable) and its printed state, the check of its add-on fee (addon_check), and each print file's name, print details (print) and when it was made again (made_again_at). File links come from /files.

{
  "id": "6f1c…",
  "name": "#1042",
  "print_state": "partial",
  "files_ready": true,
  "customer": {"name": "Grace Hopper", "email": "grace@example.com"},
  "shipping_address": {"name": "Grace Hopper", "address1": "…", "city": "Sydney", …},
  "note": "Gift wrap",
  "tags": "vip",
  "totals": {"currency": "AUD", "subtotal_cents": 9000, "tax_cents": 900, "total_cents": 9900},
  "lines": [
    {
      "id": "a3d9…",
      "line": 1,
      "product": "Framed print",
      "variant": "A3 / Black",
      "quantity": 2,
      "addon_check": {"status": "short", "expected_cents": 1500, "paid_cents": 500,
                      "quantity": 2, "currency": "AUD"},
      "printable": true,
      "printed": null,
      "personalisation": {"v": 1, "zones": [ … ]},
      "proof": {"v": 1, "confirmedAt": "2026-10-20T00:58:11.000Z", "previews": [ … ]},
      "model_colours": [],
      "files": [
        {"id": "e7b2…", "zone_key": "background", "zone_index": 0,
         "file_name": "1042-1-background-qty2.pdf", "made_again_at": null,
         "print": {"width_cm": 20, "height_cm": 15, "bleed_cm": 0.3, "ppi": 300,
                   "format": "cmyk-pdf", "mirrored": false,
                   "min_photo_ppi": 127, "low_res": true}}
      ]
    },
    {
      "id": "b812…",
      "line": 2,
      "printable": true,
      "printed": {"at": "2026-10-20T03:00:00+00:00", "by": "api_key",
                  "api_key": {"prefix": "ksk_ab12cd34", "name": "Production MES"}},
      …
    }
  ]
}

printed.by is user (marked in the dashboard), api_key or unknown. print_state counts printable lines only: none, waiting, partial or printed. Another shop's order id is a 404.

addon_check is our check of the line's add-on fee, when the shop charges for photo layouts: status is ok, short (underpaid: the fee line may have been removed, or the quantity changed in the cart), over, or unverified with a reason. expected_cents is owed for the whole line (the layouts' prices × quantity) and paid_cents what its fee lines paid, both in currency, the order's. It is null when the line priced no add-on. A mismatch is only flagged: the order and its files go through as usual.

print is the file's printed size and resolution: the trim (width_cm, height_cm), bleed_cm, ppi, format (the file the product prints: cmyk-pdf, rgb-png, rgb-jpeg, laser-svg, laser-pdf or cutter-svg), mirrored, and low_res when a photo is under 70 % of the print's PPI (min_photo_ppi is the lowest). An engraving PNG has engraving.bits: 8 for greys on white, 1 for a black-and-white dither. Prints made before print details were recorded have no print. The webhooks carry the same print and addon_check.

Download print files

GET /api/v1/orders/{id}/files · files:read. A short-lived download link for each print file, with the file name to save it under ({order}-{line}-{zone}-qty{n}.{ext}, the same as the dashboard's zip).

ParameterMeaning
kindoriginal (default): the print file, a PDF, PNG, JPEG or SVG as the product is set up (a laser SVG or laser PDF is outlined vectors in mm; a cutter SVG, cutter-svg, is each vinyl colour as one shape in mm, and its DXF is only in the dashboard). preview: the PNG preview.
lineA line id: that line’s files only.
curl https://app.kastomise.com/api/v1/orders/6f1c…/files \
  -H "Authorization: Bearer ksk_…"

{
  "order_id": "6f1c…",
  "kind": "original",
  "expires_in": 3600,
  "files": [
    {
      "id": "e7b2…",
      "line_id": "a3d9…",
      "line": 1,
      "zone_key": "background",
      "zone_index": 0,
      "file_name": "1042-1-background-qty2.pdf",
      "made_again_at": null,
      "url": "https://…amazonaws.com/…&X-Amz-Signature=…",
      "available": true
    }
  ]
}

Download each url with a plain GET and no Authorization header. Links last about an hour (expires_in, in seconds): ask again for fresh ones rather than storing them. A file that can't be fetched right now has url: null and available: false; try again later.

A shop can have a print file made again, from the photos and print record the order keeps for 90 days (in the dashboard, “Make this file again” on the order page): the same print, as the shopper approved it, in the product's print settings now or as it was printed. The new file takes the old one's place, here and in the dashboard's zips, and the shop can switch back to an earlier one. made_again_at is when the file you get was made again, or null for the file made when the order was paid; its file_name and print are that file's (a format change changes the extension). No event is sent when a file is made again: compare made_again_at with what you fetched before.

Mark printed

POST /api/v1/orders/{id}/printed marks lines printed; DELETE on the same path undoes it · orders:write-printed. The body is optional: {"line_ids": ["…"]} (up to 100) for those lines, or nothing for every printable line of the order. Marking a line that is already printed keeps its first mark, so it's safe to retry. The dashboard shows the mark at once.

curl -X POST https://app.kastomise.com/api/v1/orders/6f1c…/printed \
  -H "Authorization: Bearer ksk_…" \
  -H "Content-Type: application/json" \
  -d '{"line_ids": ["a3d9…"]}'

{
  "order_id": "6f1c…",
  "print_state": "printed",
  "changed": ["a3d9…"],
  "lines": [{"id": "a3d9…", "printed_at": "2026-10-20T03:00:00.000Z"}]
}

A line with nothing to print (a gift card in the same order) is a 400 not_printable; a line of another order is a 404. Marking doesn't change the order's updated_at.

List designs

GET /api/v1/designs · designs:read. Designs changed since a time, oldest change first, paged as orders are. A design changes when it is saved, when its order is recorded, printed or cancelled (in the dashboard, by Etsy or through this API), and when its details are edited.

ParameterMeaning
sinceISO time with a zone. Designs whose updated_at is at or after it.
limit1 to 100, default 50.
cursorThe next_cursor of the page before. It takes over from since; send the same status with it.
statussaved, ordered, printed or cancelled.
curl "https://app.kastomise.com/api/v1/designs?since=2026-10-20T00:00:00Z&status=ordered" \
  -H "Authorization: Bearer ksk_…"

{
  "data": [
    {
      "id": "3c9e…",
      "code": "KD-7F3K9Q2M",
      "status": "ordered",
      "created_at": "2026-10-28T09:00:00.123+00:00",
      "saved_at": "2026-10-28T09:00:00.123+00:00",
      "updated_at": "2026-10-29T10:00:00.456+00:00",
      "ordered_at": "2026-10-29T10:00:00.456+00:00",
      "printed_at": null,
      "cancelled_at": null,
      "expires_at": null,
      "files_ready": true,
      "config_id": "9b1d…",
      "config_slug": "framed-print",
      "product": "Framed print",
      "customisation_type": "photo",
      "quantity": 2,
      "order_reference": "#2041",
      "order_platform": "Etsy"
    }
  ],
  "has_more": false,
  "next_cursor": null
}

expires_at is set while a design is Saved: 90 days after it was saved it is deleted with its print files (by a daily job, so up to a day later), unless it is marked ordered or printed first. files_ready is true when its print files were copied to permanent storage as it was saved; without that they are kept for 30 days. config_id is the id of the product setup the design was made with, and config_slug is that setup's slug: the last part of its hosted page's address, set once when the setup is made and never changed. Match a design to your own product by the slug: the id is different on another shop or account, and product, the setup's name, can be renamed. It is null when the setup was deleted. saved_at is when the customer saved the design: the same time as created_at, and it stays that time if the shop later sets the design back to Saved (expires_at counts from then). quantity is what the shop recorded, else what the shopper asked for, else 1. The list has no customer details.

Get a design

GET /api/v1/designs/{code} · designs:read. The design by its code, as the shopper has it (any case; the KD- is optional), with what they typed and chose (personalisation), their proof approval (proof), 3D colours, and each print file's name and print info. File links come from /files.

choices lists what the customer chose on the hosted page before designing, priced or not, in the product setup's own words: the customisation type (kind type), each field's option (option) or number (range, with amount) and each extra switched on (extra). label is what was being chosen and value the choice; key and field are their ids in the product setup, which stay the same if a label is reworded. A choice shows here even when it has no line in price, such as a colour that costs nothing extra. It is [] for an older design, saved before choices were recorded, and for a page that offers none.

{
  "id": "3c9e…",
  "code": "KD-7F3K9Q2M",
  "status": "ordered",
  "saved_at": "2026-10-28T09:00:00.123+00:00",
  "files_ready": true,
  "config_slug": "framed-print",
  "product": "Framed print",
  "quantity": 2,
  "order_reference": "#2041",
  "order_platform": "Etsy",
  "order_notes": "Gold foil on the name",
  "customer": {"name": "Ada Lovelace", "email": "ada@example.com"},
  "note": "It's a gift, please no receipt",
  "personalisation": {"v": 1, "zones": [ … ]},
  "proof": {"v": 1, "confirmedAt": "2026-10-28T08:59:11.000Z", "previews": [ … ]},
  "price": {"currency": "AUD", "quantity": 2, "unit_cents": 6400, "total_cents": 12800,
            "lines": [{"kind": "base", "label": "Base price", "cents": 3900},
                      {"kind": "type", "label": "Text & pictures", "cents": 2500}]},
  "choices": [{"kind": "type", "label": "Customisation", "value": "Text & pictures",
               "key": "text and pictures"},
              {"kind": "option", "label": "Frame colour", "value": "Beige",
               "key": "beige", "field": "frame"}],
  "model_colours": [],
  "files": [
    {"id": "e7b2…", "zone_key": "background",
     "file_name": "kd-7f3k9q2m-background-qty2.pdf",
     "print": {"width_cm": 20, "height_cm": 15, "bleed_cm": 0.3, "ppi": 300,
               "format": "cmyk-pdf", "mirrored": false,
               "min_photo_ppi": 127, "low_res": true}}
  ],
  …
}

customer is the order the shop recorded on the design's page, else what the shopper gave when saving (when the hosted page asks for it): a name and an email, as an order's customer has, never a phone number. note is the shopper's note, order_notes the shop's. print is the same as an order's file's: the printed size and resolution, with low_res when a photo is under 70 % of the print's PPI and engraving.bits on an engraving PNG; an older print has no print. price is what the hosted page showed when the shopper saved: each line, the price of one (unit_cents) and the total for the quantity they asked for (total_cents), in the shop's currency (null when it had none); null for a design saved before prices were kept. On a page selling sizes, a size with its own price is the first line ("kind": "size", its label the size's name) in place of the base price. Another shop's code is a 404.

Download a design’s files

GET /api/v1/designs/{code}/files · files:read, as for an order's files. A short-lived download link for each print file, with the name the dashboard's zip gives it ({code}-{zone}-qty{n}.{ext}). kind is original (default) or preview, as for an order.

curl https://app.kastomise.com/api/v1/designs/KD-7F3K9Q2M/files \
  -H "Authorization: Bearer ksk_…"

{
  "design_id": "3c9e…",
  "code": "KD-7F3K9Q2M",
  "kind": "original",
  "expires_in": 3600,
  "files": [
    {
      "id": "e7b2…",
      "zone_key": "background",
      "file_name": "kd-7f3k9q2m-background-qty2.pdf",
      "url": "https://…amazonaws.com/…&X-Amz-Signature=…",
      "available": true
    }
  ]
}

Mark a design ordered or printed

POST /api/v1/designs/{code}/ordered records the design's order; POST /api/v1/designs/{code}/printed marks it printed · designs:write-status. The body is optional: the design page's order fields, order_reference (an order number or URL, up to 500 characters), order_platform (Etsy, eBay, your website: up to 100), order_quantity (how many to make: a whole number from 1 to 9,999, sent as a number) and order_customer_name (the buyer's name, up to 100 characters). A field you leave out is kept; null clears it, as an empty string clears a text field (but for the reference of a design that already has an order, below); any other field is a 400. The quantity and the name show on the design's page and come back from GET /api/v1/designs/{code} as quantity and customer.name.

curl -X POST https://app.kastomise.com/api/v1/designs/KD-7F3K9Q2M/printed \
  -H "Authorization: Bearer ksk_…"

{
  "design_id": "3c9e…",
  "code": "KD-7F3K9Q2M",
  "status": "printed",
  "changed": true,
  "ordered_at": "2026-10-29T10:00:00.456+00:00",
  "printed_at": "2026-10-30T09:15:02.118+00:00",
  "updated_at": "2026-10-30T09:15:02.120+00:00",
  "order_reference": "#2041",
  "order_platform": "Etsy"
}
curl -X POST https://app.kastomise.com/api/v1/designs/KD-7F3K9Q2M/ordered \
  -H "Authorization: Bearer ksk_…" \
  -H "Content-Type: application/json" \
  -d '{"order_reference": "#2041", "order_platform": "Own website",
       "order_quantity": 2, "order_customer_name": "Ada Lovelace"}'

A design can be marked from Saved, Ordered or Printed. Marking it printed again keeps its first printed_at, so it's safe to retry: a call that changes nothing writes nothing (changed is false). ordered on a printed design undoes the print. A cancelled design is a 409 design_cancelled: its order is reinstated in the dashboard, where Saved and Cancelled are set too. One code is one order: a design already Ordered or Printed under an order_reference answers 409 design_already_ordered to any other reference, and nothing is changed, so a code can't be attached to two orders by accident. The same reference again is a retry and succeeds. On such a design an empty or null order_reference counts as none sent: the design keeps its reference and the rest of the call applies, so a blank field from an automation tool still marks it printed. Its order_quantity and order_customer_name belong to that order: to change either, send the same order_reference with them, or the call is the same 409. Sending the values the design already has changes nothing and succeeds. To move a design to another order, change it on the design's page in the dashboard. A mark that changes the design moves its updated_at, so it comes back in a list with since. The dashboard shows the mark at once, and the shop's API activity names the key.

A polling recipe

  1. Every few minutes, list with since = the last updated_at you processed (start with when you set up), and follow next_cursor to the end.
  2. Skip orders whose files_ready is false; they come back when their files are ready.
  3. For each new order, get it and its /files, download them, and queue the job. Use the order and line ids to avoid doing one twice.
  4. When a line comes off the press, mark it printed.
  5. Designs the same way: list with status=ordered (or saved, if the shop prints a design as soon as it's saved), get each new one and its /files, and mark it printed. A mark brings the design back in the next list: use its id and status to skip one you've done.

Errors

An error is {"error": {"code": "…", "message": "…"}} with the HTTP status:

StatusCodeWhen
400bad_requestA parameter or the body is not valid.
400not_printableA line, or the order, has nothing to print.
401unauthorizedNo key, or a key that is not valid or was revoked.
403insufficient_scopeThe key lacks the permission; the message names it.
403store_inactiveThe shop is not active.
404not_foundNo such order, line or design in this shop, or no such endpoint.
405method_not_allowedAnother method; see the Allow header.
409design_cancelledThe design’s order was cancelled; reinstate it in the dashboard first.
409design_already_orderedThe design is already ordered under another order reference, or the call changes that order’s quantity or name without its reference.
409conflictThe design changed while the request ran; read it and try again.
429rate_limitedToo many requests; wait Retry-After seconds.
500server_errorSomething went wrong on our side; try again.

Rate limits

Each key can make 120 requests a minute, and a shop 300 across all its keys. Over the limit you get a 429 with a Retry-After header in seconds. Every request is recorded for the shop's admins (Settings → API keys, kept 90 days).