Place the same order again in one call. The new order is built from what the source order stored: each line's SKU, quantity, artwork (the print_file it was sent) and variable data, its address_to, its shipping method and its vat; a line's quantity, the address, the method and the VAT id can be changed in the body, and a return address (address_from) sent. It is then placed exactly as POST /order places that body, at today's price: the same validation, artwork check, address check, price floor, spend caps, charge on a live key and $0 sandbox order on a test key, and the same webhooks. The answer is POST /order's, with reordered_from naming the source. Not copied: the reference (send a new one), a quote and the return address. POST /order does not store a line's size, color, product_id, design_name, notes or mock_image_url, or any file but the one it prints, so a line that carried them goes again without them: place such an order with POST /order. A line that kept no artwork at all is refused 422 artwork_required with details.items[], on either key, and nothing is charged. The source must be an order of your account in the key's mode: a test key reorders a test order and a live key a live one, and any other id is 404 order_not_found. A SKU no longer sold and artwork that can no longer be fetched or no longer passes the check are refused with the codes POST /order uses.
- Credential
- An API key in
x-api-key - Scopes
orders:write
Parameters
idpath · stringrequiredThe order's
order_id, or itsportal_order_id(ord_…): either resolves the same order. An id the order answered to before it was re-routed resolves too.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
itemsobject[]Quantities to change, one entry per line: name the line by its
line_item_id(fromline_itemson GET /order/{id}) or byskuwhere exactly one line carries it (any case). A line not named keeps its quantity. A name that matches no line, or a sku on more than one, is a 400 validation_error whosedetails.lineslists the source's lines.1 to 100 entries
items[].line_item_idstringThe line's id. Send this or
sku, not both.non-empty
items[].skustringThe line's SKU, when exactly one line carries it. Send this or
line_item_id, not both.non-empty
items[].quantityintegerrequired1 to 10000
address_toobjectShip to this address instead of the source's.
address_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_fromobjectAs on POST /order: the return address for this order. An order does not store one, so it is not copied; without it the account's brand return address applies.
The same fields as
address_toshippingobjectShip by this method instead of the source's. Without
method, the source's.shipping.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
standardpriorityexpresslettervatstringAs on POST /order: the tax registration number (VAT, IOSS or GST) to declare. Without it, the source's goes again.
referencestringYour own identifier for the new order, as on POST /order: one reference is one order, so the source's is never copied, and one another order of the account carries (last 30 days, same key mode, not cancelled) is refused 409 duplicate_reference before anything is charged.
at most 200 characters
allow_duplicate_referencebooleanAs on POST /order.
Default false
payment_intentstringAs on POST /order: after a 402
authentication_required, the approveddetails.payment_intent, sent with the same reorder and a new Idempotency-Key.matches ^pi_[A-Za-z0-9]+$
Request
POST /api/v1/order/{id}/reorder
curl -X POST "https://portal.printedsimply.com/api/v1/order/test_ord_wVK9ZiG-VYiPrlSeBUBmrUVk/reorder" \
-H "x-api-key: ps_test_YOUR_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: reorder-order-$(date +%s)" \
-d '{
"items": [
{
"sku": "sticker-die-cut-3x3",
"quantity": 200
}
]
}'Response 200
The new order, as POST /order answers it, with reordered_from
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_fromstringrequiredOn 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_3WDuvjlojmq4wMObnNbeIMyk",
"portal_order_id": "ord_3WDuvjlojmq4wMObnNbeIMyk",
"provider": "merchfarm",
"tracking_token": "trk__bBBdLzQVxbyNDvtBMfw9N32",
"tracking_url": "https://portal.printedsimply.com/track/eyJ2IjoxLCJ0IjoidHJhY2tpbmciLCJ0b2siOiJ0cmtfX2JCQmRMelFWeGJ5TkR2dEJNZnc5TjMyIiwiZXhwIjoiMjAyNy0wMS0xM1QxNTowMDo0NS4wMDBaIn0.9b18151b2dd9ce9ff005f798c145ddf495f7f532bfa31354836e1b3cfd3c4d9a",
"test_mode": true,
"lifecycle": "simulated",
"charged": "$0.00",
"reordered_from": "ord_wVK9ZiG-VYiPrlSeBUBmrUVk",
"artwork_check": {
"lines": [
{
"item_index": 0,
"sku": "sticker-die-cut-3x3",
"status": "passed",
"width_px": 1200,
"height_px": 1200,
"findings": [],
"print_size_in": [
3,
3
],
"effective_dpi": 400,
"min_dpi": 300
}
],
"would_refuse": false
},
"address_check": {
"valid": true,
"checked": true,
"normalized": {
"name": "Jane Doe",
"address1": "456 OAK AVE",
"city": "AUSTIN",
"state": "TX",
"zip": "78702",
"country": "US"
},
"findings": []
},
"estimated_delivery": {
"earliest": "2026-10-23",
"latest": "2026-10-29",
"ships_by": "2026-10-19",
"is_estimate": true
},
"breakdown": {
"items": "$128.00",
"shipping": "$13.99",
"processing_fee": "$0.00",
"platform_fee": "$0.00",
"card_fee": "$0.00",
"total": "$141.99"
},
"shipping": {
"method": "standard",
"surcharges": []
}
},
"request_id": "4e3753e1-48dd-4313-bfd8-48e4af4e116a"
}Errors
validation_error — the body failed validation (a field POST /order takes and a reorder does not, such as
providerorquote_token, is one), anitemsentry names no line of the source or a sku on more than one (details.lineslists the lines), or the source holds no address or shipping method an order takes (sendaddress_toorshipping.method). Otherwise any 400 POST /order answers for the order built, such as pricing_error or unknown_sku.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).402no_payment_methodpayment_failedauthentication_requiredfraud_reviewspend_cap_exceededspend_cap_unenforceableAs on POST /order: refused before anything was charged (
no_payment_method,payment_failed,authentication_required,fraud_review,spend_cap_exceeded,spend_cap_unenforceable).sandbox_only, as on POST /order; or insufficient_scope.
order_not_found — no order of this account has this id in the key's mode.
invalid_state — every line of the source was cancelled, so there is nothing to place again; or the source is a team-store member order, or has a line whose stored personalization POST /order has no field for (
details.items[].fields). duplicate_reference — as on POST /order, for thereferenceyou sent. 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 — as on POST /order: the order was placed and charged but the confirmation could not be returned or the record written. Do not retry; look the order up first.
502Production system error
provider_unavailable or reference_check_unavailable, as on POST /order. idempotency_unavailable — the request carried an
Idempotency-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.