Change the shipping method for an order that has not yet gone into production.
This call moves money. The difference between the order's current shipping rate and the new one is settled immediately: an upgrade charges the delta to your card on file, a downgrade refunds it across the order's outstanding charges. The new method is priced as the order path prices it: by the parcel's weight class, and for items made at the partner facility at no less than the partner's own estimate. Naming the method the order is recorded as shipping by is not a change: it moves no money, whatever the rates have done since the order was placed. An order placed before methods were recorded carries none, so a change on it is priced; asking such an order for standard, the cheapest parcel, never charges: it refunds only what the order paid above today's standard rate. That makes it subject to the same controls as any other charge — the API key's per-order and daily spend caps are checked *before* the fulfillment system is told anything, so an over-cap upgrade returns 402 spend_cap_exceeded with the cap and the day's spend so far, and neither charges the card nor changes the shipping method. An upgrade with no payment method on file returns 402 no_payment_method; a declined card returns 402 payment_failed, and neither consumes the key's daily budget. The account-level sandbox switch applies here as it does to every charging endpoint: a live key on an account restricted to sandbox returns 403 sandbox_only, and neither the card nor the fulfillment system is touched.
A sandbox order settles nothing: the change is reflected on the order record and Stripe is never called. An order produced by an independent shop cannot have its shipping changed after placement (501 not_supported_by_provider) — the published provider contract has no endpoint for it.
- 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, required
carrierstringmethodstringThe 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
standardpriorityexpressletter
Request
PUT /api/v1/order/{id}/shipping
curl -X PUT "https://portal.printedsimply.com/api/v1/order/test_ord_wVK9ZiG-VYiPrlSeBUBmrUVk/shipping" \
-H "x-api-key: ps_test_YOUR_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: update-order-shipping-$(date +%s)" \
-d '{
"method": "standard"
}'Response 200
Shipping updated
data
order_idstringrequiredThe order's id, the one every order operation takes. On a live order, the facility's reply supplies it when it carries one.
portal_order_idstringrequiredThis platform's own id for the order (
ord_…). On a live order, the facility's reply supplies it when it carries one.statusstringThe order's status; the amendment does not move it. Always present on a sandbox answer, one of pending_approval, pending_payment, paid, accepted, in_production, quality_check, shipped, delivered, cancelled, issue_flagged, rerouting, pending, processing, on-hold, completed, refunded, failed. On a live answer, the facility's own value when its reply carries one, and otherwise absent.
providerstringAlways
merchfarmon a sandbox answer. On a live answer, the facility's own value when its reply carries one, and otherwise absent.shipping_centsintegerThe order's shipping line after the change, in cents. Always present on a sandbox answer; on a live one, the facility's own figure when its reply carries one, and otherwise absent.
total_centsintegerThe order's total after the change, in cents. Always present on a sandbox answer; on a live one, the facility's own figure when its reply carries one, and otherwise absent.
test_modebooleanPresent and true on a sandbox order; absent on a live one, as on GET /order/{id}.
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",
"status": "paid",
"provider": "merchfarm",
"shipping_cents": 1399,
"total_cents": 7799,
"test_mode": 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).402Payment failed or no payment method configured
403sandbox_onlysandbox_only — the account is restricted to sandbox mode and the request used a live key.
404Resource not found
order_not_modifiable — the order has gone into production (or later), past the point where its shipping can change. shopper_paid_order — a shipping upgrade on an order a shopper paid for (a drop, a checkout link or an agent checkout); it would be charged to your card and refunded to the shopper. A downgrade is allowed and refunds the shopper's own charge.
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.
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.not_supported_by_provider — the order was produced by an independent network shop, which does not accept amendments at all. Never retryable, unlike the 409. Live PrintedSimply paid shipping upgrades are also refused before any charge because the plant handler is not confirmed.
fulfillment_error — the facility could not be reached or refused the change. Where an upgrade had already been charged the message says so and the payment was refunded; retry with a NEW Idempotency-Key.
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.