Submit a print-on-demand order. Charges the default payment method, then submits to the production system. Returns order ID and charge breakdown.
- Credential
- An API key in
x-api-key - Scopes
orders:write
Parameters
Idempotency-Keyheader · string (uuid)Unique key for idempotent requests. If you retry a request with the same key within 24 hours, you receive the original response. The key is bound to the request payload: reusing it for a different payload returns 422
idempotency_key_reuserather than the original response, so a changed request is never silently dropped. The binding is over the JSON's content, not its bytes — keys in another order or other whitespace are the same request. Generate a new key for each new request. On an operation that moves money (an order, a batch, an item, a shipping or address change, a cancellation's refund), a key that cannot be checked because the idempotency ledger is unreachable is answered 503idempotency_unavailablewithRetry-After, and nothing runs or is charged: retry with the same key.
Request body application/json, required
address_toobjectrequiredaddress_to.namestringrequirednon-empty
address_to.companystringaddress_to.address1stringrequirednon-empty
address_to.address2stringaddress_to.citystringrequirednon-empty
address_to.statestringrequirednon-empty
address_to.zipstringrequirednon-empty
address_to.countrystringrequiredISO 3166-1 alpha-2 country code
exactly 2 characters
address_to.phonestringaddress_to.emailstring (email)address_fromobjectThe same fields as
address_toshippingobjectshipping.carrierstringshipping.methodstringThe shipping tiers this platform prices. Per parcel, not per item:
standardby the parcel's weight class (POST /v1/shipping/estimate reports it), and for items made at the partner facility (apparel, mugs) at the larger of that and the partner's own estimate for the parcel;priorityandexpressat their price and never below standard for the same parcel. An unrecognised value is refused with 400 validation_error rather than priced asstandard.standardis always offered;priorityandexpressare offered when priced (each at or above the USPS cost of the service), and until then a request for either is refused 400shipping_method_unavailable, withdetails.offerednaming the methods that are, and nothing is charged.letteris untracked letter mail for stickers and small vinyl decals from our own plant, offered only where the platform prices it, for an order whose every item qualifies, within a unit and an order-value ceiling: otherwise a request for it is refused 400letter_mail_unavailablewithdetails.reason, and nothing is charged. On the API it is never chosen for you — no default, estimate or storefront mapping picks it — and it is placed through POST /v1/order only (bulk and bundle orders ship by parcel). A hosted store is the one exception, and only where its merchant turned on small orders by letter in the dashboard's store editor (off by default, launched stores included): a qualifying basket bought on its store page or a buy link ships as a letter, and the shopper is told before paying. A letter has no carrier tracking: its order events and tracking page state the expected window, and POST /v1/order/{id}/missing-letter reships it once if it does not arrive.One of
standardpriorityexpressletteritemsobject[]required1 to 100 entries
items[].skustringrequirednon-empty
items[].quantityintegerrequired1 to 10000
items[].artwork_urlstring (uri)The artwork for this line, as a public http(s) URL. Printed when set; otherwise the line's
print_fileis. Send one of the two: a line with neither has nothing to print.items[].print_filestringThe artwork for this line: a public http(s) URL, or the id of a file you uploaded with POST /assets (
ast_…), resolved to that file's URL. Read whenartwork_urlis not set.items[].variable_dataobjectA different name, number or QR payload on every unit of this line. Accepted only on flat goods made at our own plant (stickers, decals, magnets, posters); an order carrying it is produced there. Declare the named slots of the line's design, then give every unit a value for every slot: from
units(one entry per unit, so its length must equalquantity), from asequencethat fills one slot, or both. An optionalqr.templatebuilds each unit's QR payload from its values. Preflighted before anything is priced or charged: a value longer than its slot, aunitsarray of the wrong length, an undeclared slot, a missing value or another plant's SKU is refused with 422variable_data_invalid, naming each problem by item, unit and slot. Never changes the price. Live orders and quotes carrying it are refused with 422variable_data_unavailableuntil production is switched on; sandbox (ps_test_*) orders accept it end to end.items[].variable_data.slotsobject[]requiredThe named text slots of the line's design, each with the most characters it holds.
1 to 10 entries
items[].variable_data.slots[].namestringrequiredLower-case letters, digits and underscores, starting with a letter, e.g. name or serial. Each name once.
matches ^[a-z][a-z0-9_]{0,39}$
items[].variable_data.slots[].max_lengthintegerrequiredThe longest value this slot prints. A longer value is refused as
overflow, not shrunk or cut.1 to 200
items[].variable_data.unitsobject[]One object per unit, in print order: { slot name: value }. Its length must equal the line's quantity. Values are strings or numbers, with no line breaks. Omit it when a
sequencefills every slot.1 to 10000 entries
items[].variable_data.sequenceobjectNumbers one slot per unit: prefix + (start + (unit - 1) × step), zero-padded to
paddigits, e.g. A-0001, A-0002. A unit may not also set that slot.items[].variable_data.sequence.slotstringrequiredA declared slot.
non-empty · at most 40 characters
items[].variable_data.sequence.prefixstringPrinted before the number. Default empty.
at most 40 characters
items[].variable_data.sequence.startintegerThe first unit's number. Default 1.
0 to 1000000000
items[].variable_data.sequence.stepintegerAdded per unit. Default 1.
1 to 1000000
items[].variable_data.sequence.padintegerZero-pad the number to this many digits. Default 0 (no padding).
0 to 12
items[].variable_data.qrobjectEach unit's QR payload, built from its values.
items[].variable_data.qr.templatestringrequiredText with {slot} placeholders, e.g. https://example.com/t/{serial}. Every placeholder must name a declared slot.
non-empty · at most 1000 characters
items[].variable_data.qr.max_lengthintegerThe longest payload accepted, for a small sticker whose code must stay scannable. Default 1000.
1 to 1000
items[].artworkobjectOptional: how this line's artwork was made, and who approved it and when. A declaration you state, stored on the line as sent (approved_by sanitized, approved_at in UTC), echoed on the order's reads (
line_items[].artwork) and on itsorder.createdwebhook. It never changes the price, the routing or the artwork check, and a line without it is placed exactly as before. Strict: an unknown key is a 400 validation_error.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.
providerstringWhere the order is made: always our own plant here. Omit it, or send
printedsimply; any other value is refused with 422provider_not_availablebefore anything is priced or charged.Always
printedsimplyDefault printedsimply
referencestringYour own identifier for this order — a marketplace order number, a cart id, whatever you key on. At most 200 characters, and no NUL byte. Stored verbatim, echoed on the create response, on GET /orders and GET /order/{id}, and on the order webhooks, and filterable with GET /orders?reference=. One reference is one order: while an order of your account placed in the last 30 days with a key of the same mode (a test key against test orders) and not cancelled carries it, another order with the same reference is refused 409
duplicate_referencebefore anything is charged, whatever its Idempotency-Key, anddetails.order_idnames the order that has it. Use Idempotency-Key for exactly-once retries of one request; this catches the duplicate sent under a new key. When whether the reference is free cannot be checked, the order is not placed: 503reference_check_unavailable, nothing charged.at most 200 characters
allow_duplicate_referencebooleanPlace the order even when another order already carries its
reference(seereference). For a reference that is not unique on your side, such as order numbers from two stores that each count from 1. Omit it, or send false, to have a duplicate refused with 409duplicate_reference.Default false
vatstringTax-registration number (VAT / IOSS / GST) to declare for this shipment. Stored, echoed on the create response and on GET /orders and GET /order/{id}, and forwarded to the fulfilling facility for the customs declaration. Supply it per order — an international run can ship to several destinations under different registrations. Omit it on domestic shipments.
quote_tokenstringA signed quote from POST /orders/quote. When present, the order is charged exactly that quote's locked total instead of being re-priced and the quoted facility routing is honored. Refused (400) when expired, issued to a different key, or when the order's items or shipping method differ from what was quoted.
payment_intentstringLive keys only. When a live order's card needs 3D Secure, the order is answered 402
authentication_requiredwithdetails.payment_intentanddetails.client_secret: the amount opened on the card as an authorization only, nothing charged. Have the cardholder approve it in their browser with Stripe.js, loaded withdetails.publishable_key(the intent is on our Stripe account), callinghandleNextAction({ clientSecret }), then send the same order again with this field set todetails.payment_intentand a new Idempotency-Key. The order captures that authorization instead of charging the card again. It is refused with 402payment_failedunless it was opened for this account, authorizes exactly this order's total and has not been captured —details.codesays which check failed — and one the cardholder has not finished approving answersauthentication_requiredagain. An approval no order captures is released by the card network on its own. Ignored on a test key, which charges nothing.matches ^pi_[A-Za-z0-9]+$
approvalstringrequiredholds the order for a person's approval before anything is charged: the order is checked and priced exactly as without it (every refusal it would get, it gets now), then recordedpending_approvalwith nothing charged, nothing counted against the key's spend caps and nothing sent to production, and answered 202OrderAwaitingApprovalwithapproval_url, a signed page the person opens with no account. It shows each line's artwork, product, size, quantity and price, the total, the town it ships to and the line's artwork declaration. Approving it places the order through this same operation, as the key it was held with, at the total the person was shown: the checks run again, the card is charged and the order goes to production (order.approval_approved, thenorder.createdandorder.paid). If the price has moved, the card is declined or a spend cap refuses it, nothing is charged,approval.statusreadsfailedwith the reason, and the person may approve it again. Declined, or not decided within 72 hours (approval_expires_at), the order is cancelled with nothing charged (order.approval_declined,order.approval_expired); DELETE /order/{id}/cancel cancels it too, with nothing to refund. A held order takes no address, shipping or line change. Aquote_tokenis honoured at approval only while it is valid (24 hours): approved later, the order places nothing (invalid_quote). Taken on an API key's order only (a platform OAuth token's is 400 validation_error,details.field: approval) and not withpayment_intent. On POST /orders/bulk an entry carrying it fails validation.Always
required
Request
POST /api/v1/order
curl -X POST "https://portal.printedsimply.com/api/v1/order" \
-H "x-api-key: ps_test_YOUR_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: order-$(date +%s)" \
-d '{
"items": [
{
"sku": "sticker-die-cut-3x3",
"quantity": 100,
"artwork_url": "https://portal.printedsimply.com/samples/sticker-3x3.png"
}
],
"address_to": {
"name": "Jane Doe",
"address1": "123 Main St",
"city": "Austin",
"state": "TX",
"zip": "78701",
"country": "US"
},
"shipping": {
"method": "standard"
}
}'Response 200
Order created successfully
data
order_idstringrequiredWordPress order ID
providerstringrequiredThe fulfilment network the order was placed with.
tracking_tokenstringrequiredOpaque token for the public tracking page. Safe to share; it names no account.
tracking_urlstringrequiredThe buyer-facing tracking page for this order.
test_modebooleanTrue when the order was placed with a sandbox key. Present on sandbox orders only.
lifecyclestringsimulatedon a sandbox order — the order was priced and recorded but never sent to a facility.pricing_degradedstringPresent only when the price could not be computed:
provider_unavailableorpricing_unavailable. The breakdown is then an absence of a price, not a price of zero.One of
provider_unavailablepricing_unavailableportal_order_idstringrequiredThis platform's own id for the order (
ord_…).referencestringThe reference you supplied, echoed back. Absent when you supplied none.
vatstringThe tax-registration number you supplied, echoed back. Absent when you supplied none.
chargedstringrequiredAmount charged (e.g. '$12.50').
quote_idstringThe id of the honored quote, echoed when the order was placed with a quote_token. Absent otherwise.
reordered_fromstringOn an order POST /order/{id}/reorder placed: the source order's
portal_order_id(GET /order/{id} takes it). Absent on any other order.variable_dataobject[]One entry per line carrying
variable_data: the variant proofs (first, last and longest unit) of what the order prints. Absent when no line carries it.variable_data[].item_indexintegerrequiredThe line's position in the request's
items.at least 0
variable_data[].skustringrequiredvariable_data[].unitsintegerrequiredUnits on the line, each printed with its own values.
at least 1
variable_data[].slotsobject[]requiredEach declared slot with its
max_lengthand thelongestvalue any unit gives it: the headroom.variable_data[].slots[].namestringrequiredvariable_data[].slots[].max_lengthintegerrequiredvariable_data[].slots[].longestintegerrequiredCharacters in this slot's longest value on the line.
variable_data[].qrobjectPresent when the line has a QR template: the payload ceiling and the longest payload on the line.
variable_data[].qr.max_lengthintegerrequiredvariable_data[].qr.longestintegerrequiredvariable_data[].proofsobject[]requiredThe variant proofs: the first unit, the last, and the longest (the most printed characters, earliest on a tie), with the exact values and QR payload each will print. A unit that is more than one of the three is listed once, with each variant it stands for. Described rather than drawn: a plain line's artwork carries no slot positions to draw a value into.
variable_data[].proofs[].unitintegerrequiredThe unit's position on the line, 1-based, in print order.
at least 1
variable_data[].proofs[].variantsstring[]requiredvariable_data[].proofs[].valuesobjectrequiredSlot name to the text this unit prints.
variable_data[].proofs[].qr_payloadstringWhat this unit's QR code encodes. Present when the line has a QR template.
variable_data[].live_productionbooleanrequiredWhether a LIVE order carrying this line would be accepted now. False while variable-data production is switched off: sandbox orders run end to end, and a live order or quote is refused with 422 variable_data_unavailable.
artwork_checkobjectEach own-plant line's artwork, read and graded before anything was charged (resolution at its print size, a file that is not there or not an image, CMYK, a die-cut sticker's transparency). On a test key,
would_refusesays whether a live order would have been refused, andmissing_artworklists the own-plant lines that carry none. Absent when no line is made at our own plant with artwork, and (on a test key) none is made there without it.artwork_check.linesobject[]requiredOne entry per line made at our own plant (stickers, decals, magnets, posters) that carries artwork, in item order. A line another lane makes is graded there, not here.
artwork_check.lines[].item_indexintegerrequiredThe line's position in the request's
items, 0-based.at least 0
artwork_check.lines[].skustringrequiredartwork_check.lines[].statusstringrequiredpassed: read, nothing to say. warned: read, at least one warning. refused: at least one error (a live order is refused). unverified: the file could not be read just now (see the
unverifiedfinding); the order is not refused for it.One of
passedwarnedrefusedunverifiedartwork_check.lines[].width_pxintegerThe file's width in pixels. Absent when it was not read, or is vector.
at least 1
artwork_check.lines[].height_pxintegerThe file's height in pixels.
at least 1
artwork_check.lines[].print_size_innumber[]The print size resolution was measured against, width and height in inches: the SKU's
WxH, else the line'ssize. Absent when neither names one.2 to 2 entries
artwork_check.lines[].effective_dpiintegerPixels per inch with the artwork printed as large as the print size allows (turned a quarter if that fits it larger).
at least 1
artwork_check.lines[].min_dpiintegerThe product's recommended minimum DPI. Below 75% of it is an error.
at least 1
artwork_check.lines[].findingsobject[]requiredartwork_check.lines[].findings[].codestringrequiredOne of
unreachablenot_an_imagelow_resolutionbelow_recommended_resolutioncmykno_transparencytouches_edgeno_bleedcontent_in_bleedno_cutlinesize_unknownvectorunverifiedformat_not_acceptednot_fetchabledesign_embeds_rastergenerated_designplacement_clippedgeometry_unverifiedartwork_check.lines[].findings[].severitystringrequirederror refuses a live order; warning and info never do.
One of
errorwarninginfoartwork_check.lines[].findings[].messagestringrequiredWhat was found and what to do about it, naming the line.
artwork_check.missing_artworkobject[]Test keys (ps_test_*) only, on POST /order, POST /orders/bulk and POST /{id}/item: the lines made at our own plant that carry no artwork at all, which a live request is refused for with 422
artwork_required. Absent when there are none.artwork_check.missing_artwork[].order_indexintegerPOST /orders/bulk's 422 only: the order's position in
orders.at least 0
artwork_check.missing_artwork[].item_indexintegerrequiredThe line's position in the order's
items, 0-based.at least 0
artwork_check.missing_artwork[].skustringrequiredartwork_check.would_refusebooleanTest keys (ps_test_*) only: whether a live order with these lines would have been refused, with 422
artwork_not_printableor (for a line inmissing_artwork)artwork_required. A test key is never refused for artwork, so placeholder artwork still places a sandbox order. Absent on a live order, which either was refused or was not.address_checkobjectWhat POST /addresses/validate finds in
address_to, warnings included. A live order whose address has anerrorfinding is refused 422 address_invalid before anything is charged; on a test keyvalid: falsehere says a live order would have been.address_check.validbooleanrequiredFalse when any finding is an
error.address_check.checkedbooleanrequiredFalse for an address outside the US, which is passed through as written.
address_check.normalizedobjectrequiredThe address in USPS style: upper case, no punctuation, ZIP+4 hyphenated, the state as its code, a unit typed into address1 moved to address2. Name, company, phone and email are trimmed and otherwise as sent.
address_check.normalized.namestringaddress_check.normalized.companystringaddress_check.normalized.address1stringrequiredaddress_check.normalized.address2stringaddress_check.normalized.citystringrequiredaddress_check.normalized.statestringrequiredaddress_check.normalized.zipstringrequiredaddress_check.normalized.countrystringrequiredaddress_check.normalized.phonestringaddress_check.normalized.emailstringaddress_check.findingsobject[]requiredaddress_check.findings[].codestringrequiredThe
delivery_point_*codes come from delivery-point verification, which only a live POST /order, POST /orders/bulk entry or PUT /order/{id}/address runs, on a US address the other checks pass, and only where the deployment has USPS verification configured:delivery_point_not_found(error) — USPS answered that no delivery point exists at the street address;delivery_point_incomplete(warning) — the building exists but its unit is missing or not confirmed, or more than one address matched;delivery_point_confirmed(info). When USPS cannot be reached the order proceeds without them.One of
zip_formatstate_unknownzip_state_mismatchzip_state_borderzip_unassignedpo_boxmilitary_statemilitaryaddress2_splitnot_checkeddelivery_point_not_founddelivery_point_incompletedelivery_point_confirmedaddress_check.findings[].severitystringrequirederror: the address cannot be right as written, and a live order or quote is refused with 422 address_invalid.warning: worth a look, never refused.info: what normalization did, or that nothing was checked.One of
errorwarninginfoaddress_check.findings[].fieldstringrequiredOne of
address1address2citystatezipcountryaddress_check.findings[].messagestringrequiredbreakdownobjectrequiredbreakdown.itemsstringrequiredbreakdown.shippingstringrequiredbreakdown.processing_feestringrequiredCompatibility field for platform and card-processing surcharges. Zero on orders priced under the current no-added-fees policy; retained on historical records.
breakdown.platform_feestringThe platform fee as a line of its own. Always 0: there is no platform fee on any order, on any plan (since 2026-10-17). Kept so a client that reads it still parses.
breakdown.card_feestringCard processing on everything else the order is charged, passed through at cost.
breakdown.totalstringrequiredshippingobjectrequiredThe shipping the figure in
breakdownis for: its method, and the surcharges that figure already holds, itemised. Nothing here is added to the total.shipping.methodstringrequiredThe shipping method the figure was priced for.
shipping.surchargesobject[]requiredCompatibility itemisation, never added to the total. Current quotes and new orders contain no seasonal peak-postage surcharge: PrintedSimply absorbs the carrier expense, so this array is empty. Historical stored orders may retain their original itemisation; a fee-bearing old quote must be refreshed before a new charge.
shipping.surcharges[].codestringrequiredpeak_season: the carrier's temporary holiday price change.Always
peak_seasonshipping.surcharges[].reasonstringrequiredWhat it is, in words.
shipping.surcharges[].amount_centsintegerrequiredHow much of the shipping figure this is, in cents, folded the way that figure is (all-in where the figure is all-in).
at least 1
shipping.surcharges[].windowobjectrequiredThe days it applies on, both inclusive.
shipping.surcharges[].window.fromstring (date)requiredYYYY-MM-DD, UTC.
shipping.surcharges[].window.throughstring (date)requiredYYYY-MM-DD, UTC.
shipping.surcharges[].sourcestring (uri)requiredWhere the amount and the dates come from: the carrier's own announcement.
shipping.surcharges[].included_instringrequiredAlways
shipping: the figure this is part of. Never an additional charge.Always
shippingestimated_deliveryobject | nullWhen the order should arrive, as an estimate: the quote's
estimated_deliveryfor the order as placed, counted from now. Null on a lane that is not one of our own facilities. GET /order/{id}, GET /orders and the order'sorder.in_production,order.quality_checkandorder.shippedevents carry the same date.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
true
meta
pageintegertotalintegerlimitintegerThe page size actually applied, which is not necessarily the one asked for — the operation's own ceiling wins.
pagesintegertotaloverlimit, rounded up.next_cursorstring | nullOn a cursor-paged operation: send this back as
cursorfor the next page. Null on the last page. Absent on operations that do not page by cursor.has_morebooleanOn a list operation whose
metaisListMeta: whether another page follows this one.
{
"status": "success",
"data": {
"order_id": "test_ord_wVK9ZiG-VYiPrlSeBUBmrUVk",
"portal_order_id": "ord_wVK9ZiG-VYiPrlSeBUBmrUVk",
"provider": "merchfarm",
"tracking_token": "trk_AquJFqDLDkisK6Jh-HABHz9L",
"tracking_url": "https://portal.printedsimply.com/track/eyJ2IjoxLCJ0IjoidHJhY2tpbmciLCJ0b2siOiJ0cmtfQXF1SkZxRExEa2lzSzZKaC1IQUJIejlMIn0.199c0c58721512fcccd784902c422ca4e827c6d09b7e495bbb4db9b06ae62003",
"test_mode": true,
"lifecycle": "simulated",
"charged": "$0.00",
"artwork_check": {
"lines": [
{
"item_index": 0,
"sku": "sticker-die-cut-3x3",
"status": "passed",
"width_px": 1200,
"height_px": 1200,
"print_size_in": [
3,
3
],
"effective_dpi": 400,
"min_dpi": 300,
"findings": []
}
],
"would_refuse": false
},
"breakdown": {
"items": "$64.00",
"shipping": "$8.99",
"processing_fee": "$0.00",
"platform_fee": "$0.00",
"card_fee": "$0.00",
"total": "$72.99"
},
"shipping": {
"method": "standard",
"surcharges": []
},
"estimated_delivery": {
"earliest": "2026-10-20",
"latest": "2026-10-26",
"ships_by": "2026-10-14",
"is_estimate": true
}
},
"request_id": "4e3753e1-48dd-4313-bfd8-48e4af4e116a"
}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.Missing, invalid or expired API key:
missing_api_key(none sent),invalid_api_key(unknown or revoked),key_expired(past the expiry set when it was created; create a new key).The order was refused before anything was charged or placed.
no_payment_method,payment_failedorauthentication_requiredon the card;fraud_reviewfrom the risk gate;spend_cap_exceededwhen the key's per-order or daily cap would be passed, with the cap and the day's spend in the message. The cap counts what this API charges for the order: here every order is made, and charged, at our own plant. A test key meets the spend-cap refusal exactly as a live key would.403sandbox_onlysandbox_only — the account is restricted to sandbox mode and the request used a live key. Use a test key (ps_test_*) or contact support to enable live orders.
duplicate_reference — an order of your account already carries this
reference(placed in the last 30 days with a key of the same mode, not cancelled); nothing was charged.details.order_idanddetails.portal_order_idname it; read it instead of placing it again.details.in_flight: trueinstead means another request with this reference is being placed right now. Sendallow_duplicate_reference: trueto place a second order under the same reference on purpose. Also idempotency_conflict, while a request with the same Idempotency-Key is still in flight.payload_too_large — the request body exceeded this route's byte cap (256 KB unless the route raises it with a written reason). It is refused before it is parsed, so nothing was created. Send fewer rows, or use the endpoint's own asynchronous door where it has one.
422variable_data_invalidvariable_data_unavailableartwork_not_printableartwork_requireddestination_unavailablebelow_cost_flooraddress_invalidmax_provider_cost_requiredsku_not_availableprovider_not_availablevariable_data_invalid or variable_data_unavailable — as POST /orders/quote:
items[].variable_datafailed its preflight, or a live order carries it while production is switched off. artwork_not_printable — a live order's artwork on a line made at our own plant cannot be printed as it is:details.problems[]names each line byitem_indexwith a code (unreachable, not_an_image, low_resolution) and a message saying what it needs, anddetails.artwork_checkcarries every line's verdict. Nothing was charged and the Idempotency-Key is free for the corrected order. artwork_required — a live order has a line made at our own plant with no artwork at all (noartwork_url, noprint_file), so there is nothing to print:details.items[]names every such line byitem_indexandsku, checked before any file is read, and nothing was charged. A test key is never refused for artwork; its 200 carries the same verdict withartwork_check.would_refuse, and a line with no artwork inartwork_check.missing_artwork. destination_unavailable — the address is outside the US and the order would be made at our own plant, which ships to US addresses only:details.reasonisdestination_not_usanddetails.countrynames the destination. A test key gets the same answer. below_cost_floor — the order would be made at our own plant and would cost more to make and ship than it is charged, at worst-case postage, and its loss is not one our cost floor accepts: only today's named losses are, no larger than they are today — the flat standard parcel under its postage, for a mailer up to a pound or a poster tube up to two, and list-priced goods short only of the per-order handling cost (or a partner SKU would be charged below what the partner bills for it).details.skusnames the items anddetails.contribution_centshow far below zero the order falls. Nothing was charged; add units, combine it with another order, or choose another shipping method on offer. A test key gets the same answer. address_invalid — a live order'saddress_tocannot be right as written:details.findings[]names eacherror(zip_format,state_unknown,zip_state_mismatch, ordelivery_point_not_found: USPS has no delivery point at the street address, where the deployment has USPS verification configured) with its field, anddetails.address_checkis the whole check, as POST /addresses/validate answers it. Nothing was charged; a test key is never refused for it and its 200 carriesaddress_check. sku_not_available — a line names a product our own plant does not make: only PrintedSimply's own products (stickers, decals, labels, magnets and posters, as GET /products lists them) are sold here.details.skusnames each one. Asked before anything is priced, so nothing was priced or charged. provider_not_available —providernames a lane other than our own plant. Orders here are made by PrintedSimply: omitprovider, or sendprintedsimply.details.providerechoes what was sent anddetails.availablelists what is accepted. Nothing was priced or charged.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.500order_errororder_error — the order was placed and CHARGED but the confirmation could not be returned or the record could not be written. Do NOT retry: a retry places and charges a second one. Look the order up, or quote the request_id to support. The one exception carries its own message — "Payment refunded. Retry with a new Idempotency-Key."
502Production system error
provider_unavailable — the facility that produces these items is not accepting orders right now (unconfigured, or its circuit breaker is open) or (
details.reason: plant_unreachable, withRetry-After) our plant gave no answer about a SKU — the request did not reach it, timed out, or came back 5xx — which is a fact about the plant and not the SKU: a definite answer that it does not carry the SKU is still a 400 pricing_error. Nothing stands charged: the pre-charge refusal never took money, and the post-charge one refunded before answering. Retry later; a pre-charge 503 may be retried with the same Idempotency-Key, a post-charge 503 needs a new one. A test key is not refused for an unanswering plant: its order is recorded at $0 withpricing_degraded: pricing_unavailable. Only for a SKU the catalog has, though: one the price book does not price, our plant's products do not name and the synced catalog does not hold is refused 400 unknown_sku withdetails.skus, as it is on POST /order, POST /orders/quote and POST /pricing/estimate alike. reference_check_unavailable — the order carries areferenceand whether another order already holds it could not be checked, so it was not placed and nothing was charged; retry the same request afterRetry-After. idempotency_unavailable — the request carried anIdempotency-Keyand the ledger that makes it mean at-most-once could not be reached, so nothing ran and nothing was charged; retry the same request with the same key afterRetry-After.