Retrieve a paginated list of your orders, newest first, with optional filters. Pages by cursor (see Pagination): send back meta.next_cursor as cursor until meta.has_more is false, a keyset walk that cannot repeat or skip a row while orders are being written. Reconciliation should use the cursor. The deprecated page walk is still answered as before.
- Credential
- An API key in
x-api-key - Scopes
orders:read
Parameters
statusquery · stringFilter by order status. An unknown value is refused with 400 validation_error, never ignored — an unrecognised status would otherwise match no row and answer 200 with an empty page, which is indistinguishable from having no such orders. Current statuses: pending_approval, pending_payment, paid, accepted, in_production, quality_check, shipped, delivered, cancelled, issue_flagged, rerouting. Historical values, accepted so orders older than this vocabulary stay reconcilable but no longer written: pending, processing, on-hold, completed, refunded, failed.
One of
pending_approvalpending_paymentpaidacceptedin_productionquality_checkshippeddeliveredcancelledissue_flaggedreroutingpendingprocessingon-holdcompletedrefundedfailedreferencequery · stringExact match on the
referenceyou supplied at create. Use it to reconcile your own order ids against ours — including after a create response you never received.fromquery · string (date-time)Filter orders created on or after this date. A value that is not a date, or a
fromlater thanto, returns 400 validation_error — it is never dropped, because an unfiltered page is indistinguishable from a windowed one.toquery · string (date-time)Filter orders created on or before this date. A value that is not a date returns 400 validation_error.
cursorquery · stringOpaque cursor: send back
meta.next_cursorfrom the previous page for the page after it; omit it for the first page. Good only on this list, with a key of the same account and mode (test or live), and with the same filters (onlylimitmay change); an altered or foreign cursor, or one sent with other filters, is refused with 400 validation_error. Wins overpagewhen both are sent.limitquery · integerOrders per page: 20 by default, 100 at most. A value above 100 is clamped to 100 and one below 1 to 1, a fraction is truncated, and a missing, zero or non-numeric value falls back to 20; none is refused.
Default 20
pagequery · integerdeprecatedDeprecated: walk with
cursor. The page number of the offset walk this operation always had, answered as before (limit20 by default, 100 at most;offset, a row count, is accepted in its place). An order written while you page shifts every offset behind it; a cursor cannot be shifted. Ignored whencursoris sent, and a cursor walk'smetacarries nopageorpages.Default 1
Request
GET /api/v1/orders
curl "https://portal.printedsimply.com/api/v1/orders" \
-H "x-api-key: ps_test_YOUR_KEY"Response 200
Paginated list of orders. meta carries has_more, next_cursor (null on the last page), total and limit — plus page and pages except on a cursor walk. Each row carries the reference you supplied at create (null if you supplied none) and the guarantee in force on that order (null when it carries none).
data is a list; each entry:
order_idstringrequiredThe fulfilment network's order id — the one
GET /v1/order/{id}and every other order operation take.portal_order_idstringrequiredThis platform's own id for the order.
providerstringrequiredThe fulfilment lane that holds the order.
made_byobject | nullrequiredThe independent network shop that produced an order, or null when an in-house lane did.
GET /v1/ordersandGET /v1/order/{id}build it from the same helper and both$refthis, so they cannot describe one fact two ways — which they did, one of them calling it a string. Every order placed on this portal is made at our own plant, so it is null on them; a shop is named only on an order placed before that.made_by.namestringrequiredThe shop's display name.
made_by.slugstringrequiredThe shop's directory slug.
made_by.profile_urlstring (uri)requiredIts public profile page on this platform.
guaranteeobject | nullrequiredThe delivery guarantee in force on this order, or null when the order carries none (guarantees are issued only on lanes whose measured on-time record clears the floor, and only while the feature is enabled). The keys mirror the
guaranteeblock returned by POST /orders/quote, so this is the object you were quoted, read back, plus what has since happened to it.guarantee.guarantee_idstringrequiredSame id the quote issued.
guarantee.deliver_bystring (date-time)requiredThe date this order is guaranteed to arrive by.
guarantee.quality_standardstringrequiredThe quality standard guaranteed ("standard" in v1).
guarantee.confidencenumberrequiredLower bound on the lane's on-time probability at issue time (Wilson), between 0 and 1.
guarantee.remedyobjectrequiredThe promise. cap_cents is the ceiling on what a breach can pay — never an amount owed or paid; read
remediesfor what was actually recorded.guarantee.remedy.typestringrequiredcredit is the only remedy issued today, and the one that settles as a real refund. remake and expedite are reserved for the ops-driven remedies; one would settle as not_applicable rather than pay.
One of
creditremakeexpediteguarantee.remedy.cap_centsintegerrequiredguarantee.statusstringrequiredissued while the promise is open, honored once the order was delivered on time, remedied once it was not. There is deliberately no
breachedvalue: a breach and its capped remedy are recorded in one transaction (so two overlapping sweeps cannot both pay out), so the terminal state of a broken promise isremediedand whether the money moved is the remedy's own status.One of
issuedhonoredremediedguarantee.issued_atstring (date-time)requiredguarantee.resolved_atstring (date-time) | nullrequiredWhen the guarantee was honored or remedied; null while still open.
guarantee.remediesobject[]requiredRemedies recorded against this guarantee. Empty unless it was breached.
guarantee.remedies[].typestringrequiredOne of
creditremakeexpediteguarantee.remedies[].amount_centsintegerrequiredWhat was recorded, capped at the order value at issue.
guarantee.remedies[].statusstringrequiredapplied is the only value that means money moved — a real refund on the order's charge. not_applicable is the ordinary sandbox outcome (a test order has no charge) and also covers a remake/expedite, which is an ops action rather than an automatic payment. failed is an owed remedy that has not been paid, kept distinct from not_applicable so it stays visible.
One of
pendingappliedfailednot_applicableguarantee.remedies[].applied_atstring (date-time) | nullrequiredguarantee.remedies[].created_atstring (date-time)requiredreferencestring | nullrequiredThe
referenceyou supplied at creation, echoed back. Null on orders placed without one, and on every order that predates the column.vatstring | nullrequiredThe tax-registration number declared on this order; null when none was supplied.
landed_costobject | nullrequiredWhat the border cost on this order, as recorded for the parcel that ships. Null on a domestic shipment — there is no border, so there is nothing to state, and a row of zeros would read like a customs assessment that came to nothing. When a cross-border order has no stored figures,
recordedis false and onlydestination_countryandnot_recorded_reasonare present: the duty, tax and FX rates that applied on the day are not recoverable, and backfilling them with today's rates would be a fabricated record rather than a missing one. That is what an order placed before these figures were stored looks like, and also a free replacement order, which carries no charged goods value to assess. The figures are a snapshot, never recomputed on read, so a stale figure cannot be mistaken for a current one — but they ARE re-recorded when the parcel itself changes, because the border is costed against the destination, the goods and the shipping charge. Change the address, add or cancel a line, change a quantity or change the shipping method and the order is costed again at that moment;recorded_atmoves with it, and it is the order's creation time until something does. An order re-routed to a domestic address goes back to null.estimatedstays true throughout: on DAP the courier bills the recipient directly, so we never learn what customs actually assessed.landed_cost.recordedbooleanrequiredFalse when no landed cost was stored for this order.
landed_cost.destination_countrystringISO 3166-1 alpha-2.
landed_cost.not_recorded_reasonstringPresent only when
recordedis false.landed_cost.recorded_atstring (date-time)When the figures were computed — never the moment of the read. The order's creation time, or the moment of the last change to its destination, lines or shipping charge.
landed_cost.duty_basisstringDAP today: goods and shipping are collected at checkout, and the courier bills the recipient for duties and import tax on delivery.
One of
DDPDAPlanded_cost.duty_ratenumberlanded_cost.tax_ratenumberlanded_cost.breakdownobjectlanded_cost.breakdown.goods_centsintegerlanded_cost.breakdown.shipping_centsintegerlanded_cost.breakdown.duties_centsintegerlanded_cost.breakdown.tax_centsintegerlanded_cost.total_landed_centsintegerWhat the buyer ends up paying in total.
landed_cost.pre_collected_centsintegerCollected at checkout. Equals goods + shipping — never more than was charged.
landed_cost.due_on_delivery_centsintegerBilled by the courier on delivery. Sums with
pre_collected_centsto the total.landed_cost.presentmentobjectlanded_cost.presentment.currencystringlanded_cost.presentment.amount_centsintegerlanded_cost.presentment.minor_unitsintegerlanded_cost.presentment.fx_ratenumberlanded_cost.estimatedbooleanlanded_cost.notestringstatusstringrequiredThe order's lifecycle status, from the same vocabulary the
statusfilter above accepts.One of
pending_approvalpending_paymentpaidacceptedin_productionquality_checkshippeddeliveredcancelledissue_flaggedreroutingpendingprocessingon-holdcompletedrefundedfailedissue_reasonstring | nullrequiredWhy the order is
issue_flagged, asGET /v1/order/{id}reports it; null when it is not flagged, or no reason fits.One of
artwork_unprintableaddress_undeliverableshipment_exceptionon_holdcancellation_pendingtotalstringrequiredWhat the order was charged, formatted for display — a dollar string such as
$42.00, NOT an integer of cents.total_centsbeside it carries the same money as an integer, under the nameGET /v1/order/{id}uses; read that one. Kept because the shape is published and changing it would break every caller reading it.shippingstringrequiredThe shipping charge, formatted the same way as
total;shipping_centsis the same money in cents.total_centsintegerrequiredWhat the order was charged, in cents — the same money as
total, and the same fieldGET /v1/order/{id}carries.shipping_centsintegerrequiredThe shipping charge, in cents — the same money as
shipping.is_testbooleanrequiredTrue on a sandbox order. The same value as
test_modebeside it; kept because it is published.test_modebooleanrequiredTrue on a sandbox order, false on a live one —
is_testunder the name the create response andGET /v1/order/{id}use. Present on every row here (the order read omits it on a live order).channelstringrequiredThe surface that placed the order. On
hosted,acpanducpa shopper paid your store, andtotalis that sale; on every other channel you placed the order andtotalis what it cost you.unknownis an order older than the record.One of
acpucpmcpverified_agentoauth_appapidashboardautopilothostedstorefrontunknowndrop_idstring | nullrequiredThe drop a shopper bought this order on; null on an order no drop sold.
itemsobject[]requiredThe order's lines. Each carries the id our item and return operations resolve — see OrderLine.
items[].idstringrequiredThis line's id. Use it as
{itemId}on PUT /v1/{id}/item/{itemId} and DELETE /v1/{id}/item/{itemId}/cancel.items[].skustringrequireditems[].quantityintegerrequiredat least 1
items[].unit_price_centsintegerrequireditems[].artworkobjectThe line's artwork declaration, as the order accepted it: how the artwork was made, and who approved it and when. Absent on a line that declared none.
items[].artwork.provenancestringHow the artwork was made:
human(made by a person),ai_assisted(a person made it with a model's help) orai_generated(a model made it).One of
humanai_assistedai_generateditems[].artwork.approved_bystringWho approved the artwork for print: a name or an email address, at most 200 characters. Control and invisible formatting characters are removed and whitespace collapsed before it is stored. Send it with approved_at.
non-empty · at most 200 characters
items[].artwork.approved_atstring (date-time)When the artwork was approved: an ISO 8601 date-time with a time zone, not in the future. Stored and echoed in UTC. Send it with approved_by.
line_itemsobject[]requiredThe same lines as
items, under the nameGET /v1/order/{id}publishes them as, so one name reads the lines from either.The same fields as
itemsactionsobjectrequiredWhich changes this order still takes, read from the same checks the operations refuse on, so a client can show or offer an action without trying it first.
truemeans the order's own state does not refuse it; the request is still checked as usual (the key's scope; the line an item action names, which must exist and, on a live order at our own plant, carry its artwork; and on a live order the facility, which can refuse a change with 502 fulfillment_error).falsenames the refusal the operation would give. Live PrintedSimply orders have add_item, update_item and upgrade_shipping false; only supported no-cost or downward shipping requests remain requestable. On other orders a shopper paid for (a drop, a checkout link or an agent checkout),update_itemandupdate_shippingstay true because a decrease or a downgrade is allowed, while an increase or an upgrade is refused 409 shopper_paid_order, asadd_itemis.actions.cancelbooleanrequiredDELETE /order/{id}/cancel. True only before production (pending_payment, paid or accepted); false once the order is cancelled (400 already_cancelled) or past that (409 not_cancellable).
actions.update_addressbooleanrequiredPUT /order/{id}/address. False on an order an independent shop made (501 not_supported_by_provider) and once the order is past the point of change (409 order_not_modifiable).
actions.update_shippingbooleanrequiredPUT /order/{id}/shipping. False for the reasons
update_addressis.actions.upgrade_shippingbooleanrequiredPositive-cost PUT /order/{id}/shipping upgrades. False for live PrintedSimply orders (501 not_supported_by_provider), shopper-paid orders (409 shopper_paid_order), and when update_shipping is false. A downgrade or unchanged method can still be allowed when this is false.
actions.add_itembooleanrequiredPOST /{id}/item. False for the reasons
update_addressis, on live PrintedSimply orders (501 not_supported_by_provider), and on an order a shopper paid for (409 shopper_paid_order).actions.update_itembooleanrequiredPUT /{id}/item/{itemId}. False for the reasons
update_addressis and on live PrintedSimply orders (501 not_supported_by_provider). An unchanged quantity is still an allowed no-op.actions.cancel_itembooleanrequiredDELETE /{id}/item/{itemId}/cancel. False for the reasons
update_addressis.tracking_tokenstring | nullrequiredThe opaque token for this order's public tracking page; null until one is issued.
tracking_numberstring | nullrequiredThe carrier's tracking number; null until the order ships.
tracking_carrierstring | nullrequiredThe carrier that holds the shipment; null until the order ships.
tracking_urlstring | nullrequiredThe carrier's own tracking URL; null until the order ships.
trackingobjectrequiredWhere the parcel is. The same block on
GET /v1/order/{id}and on everyGET /v1/ordersrow.number,carrierandurlare null until the order ships, and equal the flattracking_number,tracking_carrierandtracking_urlbeside it. On a live order whose facility reports its owntrackingobject, that object's keys are kept and these are added where it has none.tracking.numberstring | nullrequiredThe carrier's tracking number; null until the order ships.
tracking.carrierstring | nullrequiredThe carrier that holds the shipment; null until the order ships.
tracking.urlstring | nullrequiredThe carrier's tracking link; null until the order ships.
tracking.page_urlstring (uri) | nullrequiredThe order's hosted tracking page, the link to give your customer. It exists from the moment the order is placed and shows each step, including before the carrier has a number; the create response's
tracking_urlopens the same page. Null only on an order with no tracking token.estimated_deliveryobject | nullrequiredWhen the order should arrive, as an estimate: the producing facility's production estimate plus the shipping method's transit window (our plant's standard parcel from the plant's own transit estimate), in business days counted in UTC, Monday to Friday, never on a day GET /calendar lists (the federal holidays, each on the weekday it is observed, and the plant's own closure days), counted from when the order was placed (when a person approved it, for an order placed with approval). The same function POST /orders/quote dates an order with, so it is the quote's
estimated_deliveryfor the order as placed. Not a promise, and it does not move when an order runs late. Null on a lane that is not one of our own facilities (it sets its own times), on an order with no known shipping method, and whenever the order is not on its way:pending_approval,pending_payment,delivered,cancelled,issue_flagged,reroutingand every legacy status. Present (non-null) only onpaid,accepted,in_production,quality_checkandshipped.estimated_delivery.earlieststring (date)requiredYYYY-MM-DD, UTC: the soonest the order should arrive.
estimated_delivery.lateststring (date)requiredYYYY-MM-DD, UTC: the latest it should, before anything goes wrong.
estimated_delivery.ships_bystring (date)requiredYYYY-MM-DD, UTC: the day it should leave the facility, when its production estimate ends, counted the same way.
estimated_delivery.is_estimatebooleanrequiredAlways true: say it as an estimate wherever it is shown.
Always
truereordered_fromstring | nullrequiredOn an order POST /order/{id}/reorder placed: the source order's
portal_order_id, the value the reorder's own answer stated. Null on any other order.created_atstring (date-time)requiredupdated_atstring (date-time)required
meta
has_morebooleanrequiredWhether another page follows this one. Decided from one row past this page, never from a page that came back short — so a page after
has_more: truecan come back empty only if rows were deleted in between, and the walk still ends at the firsthas_more: false.next_cursorstring | nullrequiredSend it back as
cursorfor the next page; null exactly whenhas_moreis false. Opaque: pass it back unchanged. Good only on the list that returned it, with a key of the same account and mode (test or live), and with the same filters.limitintegerThe page size applied, which is not necessarily the one asked for: a larger
limitis clamped. Absent only on a GET /products request withoutcursorthat sent nolimit, which answers every match.totalintegerEvery row matching the request's filters, on the lists that count them (orders, an order's events, assets and products).
pageintegerdeprecatedThe page number of the deprecated offset walk, on the lists that published it (orders and an order's events, on every request without
cursor; products, on a request that sentpage). Absent on a cursor walk, where no page number would be true.pagesintegerdeprecatedtotaloverlimit, rounded up, besidepage(orders and an order's events). Absent on a cursor walk.sincestring | nullOn GET /order/{id}/events only: where to ask from next — the position of the last event in this answer, or, when it has none, the position the request read from. Send it back as
since(withwait_secondsto wait for the next event). Unlikenext_cursorit is not null at the end of the history; it is null only when the order has no events and the request named no position. Opaque, and bound like a cursor to this order, account and key mode.
{
"status": "success",
"data": [
{
"order_id": "test_ord_wVK9ZiG-VYiPrlSeBUBmrUVk",
"portal_order_id": "ord_wVK9ZiG-VYiPrlSeBUBmrUVk",
"provider": "printedsimply",
"made_by": null,
"guarantee": null,
"reference": null,
"vat": null,
"landed_cost": null,
"status": "paid",
"issue_reason": null,
"total": "$72.99",
"shipping": "$8.99",
"total_cents": 7299,
"shipping_cents": 899,
"is_test": true,
"test_mode": true,
"channel": "api",
"drop_id": null,
"items": [
{
"id": "itm_fowT5-1iZwSnQcGX0ByoA64l",
"sku": "sticker-die-cut-3x3",
"quantity": 100,
"unit_price_cents": 64
}
],
"line_items": [
{
"id": "itm_fowT5-1iZwSnQcGX0ByoA64l",
"sku": "sticker-die-cut-3x3",
"quantity": 100,
"unit_price_cents": 64
}
],
"actions": {
"cancel": true,
"update_address": true,
"update_shipping": true,
"upgrade_shipping": true,
"add_item": true,
"update_item": true,
"cancel_item": true
},
"tracking_token": "trk_AquJFqDLDkisK6Jh-HABHz9L",
"tracking_number": null,
"tracking_carrier": null,
"tracking_url": null,
"tracking": {
"number": null,
"carrier": null,
"url": null,
"page_url": "https://portal.printedsimply.com/track/eyJ2IjoxLCJ0IjoidHJhY2tpbmciLCJ0b2siOiJ0cmtfQXF1SkZxRExEa2lzSzZKaC1IQUJIejlMIn0.199c0c58721512fcccd784902c422ca4e827c6d09b7e495bbb4db9b06ae62003"
},
"estimated_delivery": {
"earliest": "2026-10-20",
"latest": "2026-10-26",
"ships_by": "2026-10-14",
"is_estimate": true
},
"reordered_from": null,
"created_at": "2026-10-10T08:09:14.588Z",
"updated_at": "2026-10-10T08:09:14.588Z"
}
],
"request_id": "4e3753e1-48dd-4313-bfd8-48e4af4e116a",
"meta": {
"page": 1,
"pages": 1,
"limit": 20,
"total": 1,
"has_more": false,
"next_cursor": null
}
}Errors
Invalid request body or parameters. When the request failed the operation's schema,
detailscarriesformErrors(problems with the request as a whole),fieldErrors(messages keyed by top-level field) anderrors: one{ pointer, code, message }per refused value,pointeran RFC 6901 JSON pointer into the body (or into the object of query parameters, for a query parameter) —/items/0/sku, with~written~0and/written~1, and""for the request as a whole. POST /v1/orders/bulk answerserrorsas one{ index, details }per refused order, eachdetailswith its own pointers from the request body (/orders/3/items/0/sku); POST /v1/orders/multi-ship keepserrorsas the{ formErrors, fieldErrors }it always published; POST /v1/mockups/generate and POST /v1/products/recommend keep their fields at the top ofdetails, witherrorsbeside them. A refusal of a rule the schema cannot state (an asset that does not exist, an expiry too soon) names it inmessageand carries noerrors.401Unauthorized — invalid or missing API key
Rate limit exceeded. Two windows can produce this status and
error.codesays which:rate_limit_exceededfor the per-minute sliding window,daily_quota_exceededfor the plan's daily quota. Both carryRetry-After; the daily one also carriesX-Daily-LimitandX-Daily-Remaining. The per-minute limits are plan-based — see Rate Limits above, or call GET /v1/me for this credential's own figures.