Version v1 · Base URL https://app.podmf.com/api/v1
The POD Mfg Partner API lets a brand that doesn't sell on Shopify connect its own system: read your published products and variants (with your retail and wholesale prices), map your own SKUs, send orders (we charge your card on file and make them exactly like a Shopify order), and receive signed webhooks when orders and products change.
Phase 1:GET /me, GET /products, GET /products/{id}, GET /variants/{id}, PATCH /variants/{id} (external_sku only). Phase 2:POST /orders, GET /orders, GET /orders/{id}, POST /orders/estimate, POST /orders/{id}/cancel, POST /orders/{id}/retry_payment. Phase 3:/webhook_endpoints (create, list, get, update, delete, rotate the secret), GET /events, GET /events/{id}, POST /events/{id}/resend, and the webhooks themselves (see webhooks). Phase 4 (this version of the spec): test-mode simulations POST /orders/{id}/simulate/ship, …/simulate/payment_failed, …/simulate/cancel.
Webhooks. Add an endpoint (here or on the Developers page) and pick the events it wants (["*"] = all). Live endpoints must be https; every endpoint must be a public address (no private, loopback, link-local or internal hosts; redirects are not followed). Each delivery is a POST of the event envelope { id, type, created_at, livemode, api_version, data } with PODMFG-Signature: t=<unix seconds>,v1=<hex HMAC-SHA256 of "<t>.<raw body>" keyed with your endpoint's secret>, PODMFG-Event-Id and PODMFG-Event-Type. Verify the signature over the raw body and reject a t more than 5 minutes off (samples: https://podmf.com/docs/partner-api-quickstart#verify). Answer any 2xx within 10 seconds; anything else is retried after 1 m, 5 m, 30 m, 2 h, 6 h, 12 h, 24 h and 24 h (9 tries, about 3 days). After that the delivery has failed: we email you and it shows on the Developers page, where you (or POST /events/{id}/resend) can send it again. An endpoint whose deliveries fail for good 5 times in a row is turned off (we email you). Delivery is at least once: dedupe on the event id. Order isn't guaranteed: use order_status and the times in data. Test-mode events (test orders) go only to test endpoints; product events are sent in both modes. Events are kept 30 days (GET /events to backfill).
Orders. A live order is charged at once (wholesale + shipping + tax) to the card you saved on the Developers page, then goes to production. A declined card leaves the order payment_failed; we email your billing contact and retry automatically after 1, 6 and 24 hours, then hold it (on_hold, payment_failed_final): fix the card and call retry_payment. Nothing unpaid is ever made. A test-key order is never charged and never made: its payment is simulated (status: paid, charge.status: simulated); the simulate endpoints (Sandbox) then ship it, fail its payment or cancel it, with the same webhooks a live order gets. Send Idempotency-Key on every POST: a retry with the same key and body returns the first answer (Idempotent-Replayed: true) and never makes a second order or charge. external_order_id is unique per brand and mode (409 duplicate_external_order_id). US addresses only (422 country_not_supported); an address we can't ship to is 422 address_invalid; one a person should check (no house number, territories, APO/FPO) is accepted and held (on_hold, address_review); a blank out of stock at the supplier holds the order (blank_out_of_stock). Sales tax is not charged in v1 (tax: 0).
Keys. Make keys on the Developers page in POD Mfg. Send one as Authorization: Bearer podmfg_test_… (test mode: orders are never charged or produced) or podmfg_live_…. A key is shown once; we keep only a hash. Keys made for the Catalog API (podk_…) keep working here with the products:read scope.
Conventions. JSON only. Times are ISO 8601 UTC. Money is integer cents with a currency (USD). Ids are prefixed strings (prd_, var_, ord_, shp_, evt_, whk_, mer_, con_). Lists are cursor-paginated: ?limit=50&starting_after=<id> → { data, has_more, next_cursor }. Every response carries X-Request-Id; include it when you contact us. Rate limit: 10 requests a second per key, bursts of 50 (X-RateLimit-Limit, X-RateLimit-Remaining, Retry-After on 429). Within v1, changes are additive only: ignore fields you don't know.
401No key, or not a valid one (api_key_missing, api_key_invalid).
403The key lacks a scope (missing_scope), the brand hasn't accepted the Terms of Service (terms_required), or a simulation was asked with a live key (test_mode_only).
429Over 10 requests a second (bursts of 50) for this key, or too many bad keys from your address (rate_limited).
500Our fault (internal_error). Safe to retry reads.
Products
GET/products
List published products
Your published products, newest first, each with its variants. Needs products:read. Unknown query parameters are refused (400 parameter_unknown).
Parameter
In
Type
Notes
limit
query
integer
Page size, 1 to 100.
starting_after
query
string
The next_cursor of the previous page (the last id you got).
updated_since
query
string
Only products changed at or after this time (ISO 8601).
401No key, or not a valid one (api_key_missing, api_key_invalid).
403The key lacks a scope (missing_scope), the brand hasn't accepted the Terms of Service (terms_required), or a simulation was asked with a live key (test_mode_only).
429Over 10 requests a second (bursts of 50) for this key, or too many bad keys from your address (rate_limited).
500Our fault (internal_error). Safe to retry reads.
GET/products/{id}
One published product
The product with its variants, mockup images and prices. Needs products:read.
401No key, or not a valid one (api_key_missing, api_key_invalid).
403The key lacks a scope (missing_scope), the brand hasn't accepted the Terms of Service (terms_required), or a simulation was asked with a live key (test_mode_only).
404No such resource for your brand (resource_missing). Another brand's ids are always 404.
429Over 10 requests a second (bursts of 50) for this key, or too many bad keys from your address (rate_limited).
500Our fault (internal_error). Safe to retry reads.
GET/variants/{id}
One variant
A variant of a published product. Needs products:read.
401No key, or not a valid one (api_key_missing, api_key_invalid).
403The key lacks a scope (missing_scope), the brand hasn't accepted the Terms of Service (terms_required), or a simulation was asked with a live key (test_mode_only).
404No such resource for your brand (resource_missing). Another brand's ids are always 404.
429Over 10 requests a second (bursts of 50) for this key, or too many bad keys from your address (rate_limited).
500Our fault (internal_error). Safe to retry reads.
PATCH/variants/{id}
Set your own SKU for a variant
The only change allowed on products in v1. external_sku is unique among your variants (409 external_sku_taken); null clears it. Orders may name a variant by its id, our sku or your external_sku. Needs orders:write.
Parameter
In
Type
Notes
id (required)
path
string
Bodyobject
Field
Type
Notes
external_sku (required)
string | null
Printable ASCII; surrounding spaces are trimmed. At most 100 characters.
401No key, or not a valid one (api_key_missing, api_key_invalid).
403The key lacks a scope (missing_scope), the brand hasn't accepted the Terms of Service (terms_required), or a simulation was asked with a live key (test_mode_only).
404No such resource for your brand (resource_missing). Another brand's ids are always 404.
409The change clashes with existing data or the order's state (external_sku_taken, duplicate_external_order_id, idempotency_key_reused, idempotency_key_in_use, order_not_cancellable, charge_in_progress, order_not_payable, test_mode, order_state, too_many_webhook_endpoints, no_webhook_endpoint).
429Over 10 requests a second (bursts of 50) for this key, or too many bad keys from your address (rate_limited).
500Our fault (internal_error). Safe to retry reads.
Orders
GET/orders
List orders
Your orders in the key's mode (a test key sees test orders only), newest first. Needs orders:read.
Parameter
In
Type
Notes
limit
query
integer
Page size, 1 to 100.
starting_after
query
string
The next_cursor of the previous page (the last id you got).
401No key, or not a valid one (api_key_missing, api_key_invalid).
403The key lacks a scope (missing_scope), the brand hasn't accepted the Terms of Service (terms_required), or a simulation was asked with a live key (test_mode_only).
429Over 10 requests a second (bursts of 50) for this key, or too many bad keys from your address (rate_limited).
500Our fault (internal_error). Safe to retry reads.
POST/orders
Create an order
Needs orders:write. Each line names exactly one of variant_id, sku (ours) or external_sku (yours); unknown fields are refused. Answers 201 with the order, its status and the charge result: a live order is charged now (charge.statussucceeded → status: paid; failed → status: payment_failed, retried automatically; pending → Stripe is still working on it). A held order (on_hold) isn't charged until we release it.
Parameter
In
Type
Notes
Idempotency-Key
header
string
Any unique string (a UUID works), 1 to 255 printable characters. Kept 24 hours per brand and mode. Same key + same body → the first answer again; same key + another body → 409 idempotency_key_reused; while the first is still running → 409 idempotency_key_in_use. A request that failed frees its key.
401No key, or not a valid one (api_key_missing, api_key_invalid).
403The key lacks a scope (missing_scope), the brand hasn't accepted the Terms of Service (terms_required), or a simulation was asked with a live key (test_mode_only).
409The change clashes with existing data or the order's state (external_sku_taken, duplicate_external_order_id, idempotency_key_reused, idempotency_key_in_use, order_not_cancellable, charge_in_progress, order_not_payable, test_mode, order_state, too_many_webhook_endpoints, no_webhook_endpoint).
422Well-formed but can't be done (address_invalid, country_not_supported, variant_not_found, sku_not_found, external_sku_not_found, variant_not_available, price_unavailable, shipping_method_unavailable, personalization_unsupported, amount_too_small, url_not_allowed).
429Over 10 requests a second (bursts of 50) for this key, or too many bad keys from your address (rate_limited).
500Our fault (internal_error). Safe to retry reads.
503Payments, shipping quotes or webhook set-up are offline for a moment (payments_unavailable, shipping_unavailable, webhooks_unavailable). Retry with the same Idempotency-Key.
POST/orders/estimate
Price an order without making it
The same body as POST /orders (external_order_id optional). Answers the wholesale total, shipping and tax, and whether the order would be held. Nothing is stored or charged. Needs orders:write or orders:read.
401No key, or not a valid one (api_key_missing, api_key_invalid).
403The key lacks a scope (missing_scope), the brand hasn't accepted the Terms of Service (terms_required), or a simulation was asked with a live key (test_mode_only).
422Well-formed but can't be done (address_invalid, country_not_supported, variant_not_found, sku_not_found, external_sku_not_found, variant_not_available, price_unavailable, shipping_method_unavailable, personalization_unsupported, amount_too_small, url_not_allowed).
429Over 10 requests a second (bursts of 50) for this key, or too many bad keys from your address (rate_limited).
500Our fault (internal_error). Safe to retry reads.
503Payments, shipping quotes or webhook set-up are offline for a moment (payments_unavailable, shipping_unavailable, webhooks_unavailable). Retry with the same Idempotency-Key.
GET/orders/{id}
One order
Status, the price snapshot, charges, refunds and shipments. Needs orders:read.
401No key, or not a valid one (api_key_missing, api_key_invalid).
403The key lacks a scope (missing_scope), the brand hasn't accepted the Terms of Service (terms_required), or a simulation was asked with a live key (test_mode_only).
404No such resource for your brand (resource_missing). Another brand's ids are always 404.
429Over 10 requests a second (bursts of 50) for this key, or too many bad keys from your address (rate_limited).
500Our fault (internal_error). Safe to retry reads.
POST/orders/{id}/cancel
Cancel an order before production
Allowed until the order is in_production; a paid order is refunded in full. Cancelling a cancelled order answers it again. After production starts: 409 order_not_cancellable (contact us). Needs orders:write.
Parameter
In
Type
Notes
id (required)
path
string
Idempotency-Key
header
string
Any unique string (a UUID works), 1 to 255 printable characters. Kept 24 hours per brand and mode. Same key + same body → the first answer again; same key + another body → 409 idempotency_key_reused; while the first is still running → 409 idempotency_key_in_use. A request that failed frees its key.
401No key, or not a valid one (api_key_missing, api_key_invalid).
403The key lacks a scope (missing_scope), the brand hasn't accepted the Terms of Service (terms_required), or a simulation was asked with a live key (test_mode_only).
404No such resource for your brand (resource_missing). Another brand's ids are always 404.
409The change clashes with existing data or the order's state (external_sku_taken, duplicate_external_order_id, idempotency_key_reused, idempotency_key_in_use, order_not_cancellable, charge_in_progress, order_not_payable, test_mode, order_state, too_many_webhook_endpoints, no_webhook_endpoint).
429Over 10 requests a second (bursts of 50) for this key, or too many bad keys from your address (rate_limited).
500Our fault (internal_error). Safe to retry reads.
POST/orders/{id}/retry_payment
Charge a failed order again
For a live order that is payment_failed, or on_hold with payment_failed_final (e.g. after you saved a new card). Answers the paid order, or 402 payment_failed when the card is declined again. Test orders: a simulated failure (POST /orders/{id}/simulate/payment_failed) retries into simulated success (order.paid); any other test order is 409 test_mode. Needs orders:write.
Parameter
In
Type
Notes
id (required)
path
string
Idempotency-Key
header
string
Any unique string (a UUID works), 1 to 255 printable characters. Kept 24 hours per brand and mode. Same key + same body → the first answer again; same key + another body → 409 idempotency_key_reused; while the first is still running → 409 idempotency_key_in_use. A request that failed frees its key.
401No key, or not a valid one (api_key_missing, api_key_invalid).
402The card was declined again (payment_error, payment_failed). The order's charge says why.
403The key lacks a scope (missing_scope), the brand hasn't accepted the Terms of Service (terms_required), or a simulation was asked with a live key (test_mode_only).
404No such resource for your brand (resource_missing). Another brand's ids are always 404.
409The change clashes with existing data or the order's state (external_sku_taken, duplicate_external_order_id, idempotency_key_reused, idempotency_key_in_use, order_not_cancellable, charge_in_progress, order_not_payable, test_mode, order_state, too_many_webhook_endpoints, no_webhook_endpoint).
429Over 10 requests a second (bursts of 50) for this key, or too many bad keys from your address (rate_limited).
500Our fault (internal_error). Safe to retry reads.
Webhooks
GET/events
List events
The events of the key's mode, newest first, exactly as they were (or would have been) sent: to poll, or to backfill after your endpoint was down. Kept 30 days. Needs orders:read.
Parameter
In
Type
Notes
limit
query
integer
Page size, 1 to 100.
starting_after
query
string
The next_cursor of the previous page (the last id you got).
401No key, or not a valid one (api_key_missing, api_key_invalid).
403The key lacks a scope (missing_scope), the brand hasn't accepted the Terms of Service (terms_required), or a simulation was asked with a live key (test_mode_only).
429Over 10 requests a second (bursts of 50) for this key, or too many bad keys from your address (rate_limited).
500Our fault (internal_error). Safe to retry reads.
401No key, or not a valid one (api_key_missing, api_key_invalid).
403The key lacks a scope (missing_scope), the brand hasn't accepted the Terms of Service (terms_required), or a simulation was asked with a live key (test_mode_only).
404No such resource for your brand (resource_missing). Another brand's ids are always 404.
429Over 10 requests a second (bursts of 50) for this key, or too many bad keys from your address (rate_limited).
500Our fault (internal_error). Safe to retry reads.
POST/events/{id}/resend
Send an event again
To one endpoint (webhook_endpoint_id) or to every active endpoint of the key's mode that wants the event's type. Each delivery starts the retry schedule again; it goes out within seconds. 409 no_webhook_endpoint when no active endpoint wants it. Needs orders:write.
Parameter
In
Type
Notes
id (required)
path
string
Idempotency-Key
header
string
Any unique string (a UUID works), 1 to 255 printable characters. Kept 24 hours per brand and mode. Same key + same body → the first answer again; same key + another body → 409 idempotency_key_reused; while the first is still running → 409 idempotency_key_in_use. A request that failed frees its key.
401No key, or not a valid one (api_key_missing, api_key_invalid).
403The key lacks a scope (missing_scope), the brand hasn't accepted the Terms of Service (terms_required), or a simulation was asked with a live key (test_mode_only).
404No such resource for your brand (resource_missing). Another brand's ids are always 404.
409The change clashes with existing data or the order's state (external_sku_taken, duplicate_external_order_id, idempotency_key_reused, idempotency_key_in_use, order_not_cancellable, charge_in_progress, order_not_payable, test_mode, order_state, too_many_webhook_endpoints, no_webhook_endpoint).
429Over 10 requests a second (bursts of 50) for this key, or too many bad keys from your address (rate_limited).
500Our fault (internal_error). Safe to retry reads.
GET/webhook_endpoints
List webhook endpoints
Your endpoints in the key's mode, newest first. Never the secrets. Needs orders:write.
Parameter
In
Type
Notes
limit
query
integer
Page size, 1 to 100.
starting_after
query
string
The next_cursor of the previous page (the last id you got).
401No key, or not a valid one (api_key_missing, api_key_invalid).
403The key lacks a scope (missing_scope), the brand hasn't accepted the Terms of Service (terms_required), or a simulation was asked with a live key (test_mode_only).
429Over 10 requests a second (bursts of 50) for this key, or too many bad keys from your address (rate_limited).
500Our fault (internal_error). Safe to retry reads.
POST/webhook_endpoints
Add a webhook endpoint
In the key's mode (a test key makes a test endpoint). Live endpoints must be https; the host must be a public address. Answers the endpoint with its signing secret, the only time it is shown (a replay with the same Idempotency-Key answers secret: null). At most 16 endpoints per mode. Needs orders:write.
Parameter
In
Type
Notes
Idempotency-Key
header
string
Any unique string (a UUID works), 1 to 255 printable characters. Kept 24 hours per brand and mode. Same key + same body → the first answer again; same key + another body → 409 idempotency_key_reused; while the first is still running → 409 idempotency_key_in_use. A request that failed frees its key.
401No key, or not a valid one (api_key_missing, api_key_invalid).
403The key lacks a scope (missing_scope), the brand hasn't accepted the Terms of Service (terms_required), or a simulation was asked with a live key (test_mode_only).
409The change clashes with existing data or the order's state (external_sku_taken, duplicate_external_order_id, idempotency_key_reused, idempotency_key_in_use, order_not_cancellable, charge_in_progress, order_not_payable, test_mode, order_state, too_many_webhook_endpoints, no_webhook_endpoint).
422Well-formed but can't be done (address_invalid, country_not_supported, variant_not_found, sku_not_found, external_sku_not_found, variant_not_available, price_unavailable, shipping_method_unavailable, personalization_unsupported, amount_too_small, url_not_allowed).
429Over 10 requests a second (bursts of 50) for this key, or too many bad keys from your address (rate_limited).
500Our fault (internal_error). Safe to retry reads.
503Payments, shipping quotes or webhook set-up are offline for a moment (payments_unavailable, shipping_unavailable, webhooks_unavailable). Retry with the same Idempotency-Key.
401No key, or not a valid one (api_key_missing, api_key_invalid).
403The key lacks a scope (missing_scope), the brand hasn't accepted the Terms of Service (terms_required), or a simulation was asked with a live key (test_mode_only).
404No such resource for your brand (resource_missing). Another brand's ids are always 404.
429Over 10 requests a second (bursts of 50) for this key, or too many bad keys from your address (rate_limited).
500Our fault (internal_error). Safe to retry reads.
PATCH/webhook_endpoints/{id}
Change a webhook endpoint
Any of url, enabled_events, description (null clears it), status (active / disabled). Turning an endpoint back on forgets its failures and resumes deliveries that were waiting. Needs orders:write.
401No key, or not a valid one (api_key_missing, api_key_invalid).
403The key lacks a scope (missing_scope), the brand hasn't accepted the Terms of Service (terms_required), or a simulation was asked with a live key (test_mode_only).
404No such resource for your brand (resource_missing). Another brand's ids are always 404.
422Well-formed but can't be done (address_invalid, country_not_supported, variant_not_found, sku_not_found, external_sku_not_found, variant_not_available, price_unavailable, shipping_method_unavailable, personalization_unsupported, amount_too_small, url_not_allowed).
429Over 10 requests a second (bursts of 50) for this key, or too many bad keys from your address (rate_limited).
500Our fault (internal_error). Safe to retry reads.
DELETE/webhook_endpoints/{id}
Delete a webhook endpoint
Nothing more is sent to it; deliveries still waiting end as failed. Needs orders:write.
Parameter
In
Type
Notes
id (required)
path
string
Answersobject
200Deleted.
401No key, or not a valid one (api_key_missing, api_key_invalid).
403The key lacks a scope (missing_scope), the brand hasn't accepted the Terms of Service (terms_required), or a simulation was asked with a live key (test_mode_only).
404No such resource for your brand (resource_missing). Another brand's ids are always 404.
429Over 10 requests a second (bursts of 50) for this key, or too many bad keys from your address (rate_limited).
500Our fault (internal_error). Safe to retry reads.
POST/webhook_endpoints/{id}/rotate_secret
Rotate an endpoint's signing secret
A new secret, shown once in this answer; deliveries are signed with it from now on (the old one stops at once; update your handler first, or accept both while you switch). Needs orders:write.
Parameter
In
Type
Notes
id (required)
path
string
Idempotency-Key
header
string
Any unique string (a UUID works), 1 to 255 printable characters. Kept 24 hours per brand and mode. Same key + same body → the first answer again; same key + another body → 409 idempotency_key_reused; while the first is still running → 409 idempotency_key_in_use. A request that failed frees its key.
401No key, or not a valid one (api_key_missing, api_key_invalid).
403The key lacks a scope (missing_scope), the brand hasn't accepted the Terms of Service (terms_required), or a simulation was asked with a live key (test_mode_only).
404No such resource for your brand (resource_missing). Another brand's ids are always 404.
409The change clashes with existing data or the order's state (external_sku_taken, duplicate_external_order_id, idempotency_key_reused, idempotency_key_in_use, order_not_cancellable, charge_in_progress, order_not_payable, test_mode, order_state, too_many_webhook_endpoints, no_webhook_endpoint).
429Over 10 requests a second (bursts of 50) for this key, or too many bad keys from your address (rate_limited).
500Our fault (internal_error). Safe to retry reads.
503Payments, shipping quotes or webhook set-up are offline for a moment (payments_unavailable, shipping_unavailable, webhooks_unavailable). Retry with the same Idempotency-Key.
Sandbox
Test-mode simulations, so you can build your webhook handler end to end before going live.
POST/orders/{id}/simulate/cancel
Test mode: POD Mfg cancels a test order
Cancels the order as POD Mfg staff would (reason: cancelled_by_pod_mfg) and sends order.cancelled, so you can test an order you did not cancel yourself. Allowed before production, like POST /orders/{id}/cancel. Test keys and test orders only (a live key: 403 test_mode_only; a live order's id is not found with a test key). No body. Needs orders:write. Wrong state: 409 order_state.
Parameter
In
Type
Notes
id (required)
path
string
Idempotency-Key
header
string
Any unique string (a UUID works), 1 to 255 printable characters. Kept 24 hours per brand and mode. Same key + same body → the first answer again; same key + another body → 409 idempotency_key_reused; while the first is still running → 409 idempotency_key_in_use. A request that failed frees its key.
401No key, or not a valid one (api_key_missing, api_key_invalid).
403The key lacks a scope (missing_scope), the brand hasn't accepted the Terms of Service (terms_required), or a simulation was asked with a live key (test_mode_only).
404No such resource for your brand (resource_missing). Another brand's ids are always 404.
409The change clashes with existing data or the order's state (external_sku_taken, duplicate_external_order_id, idempotency_key_reused, idempotency_key_in_use, order_not_cancellable, charge_in_progress, order_not_payable, test_mode, order_state, too_many_webhook_endpoints, no_webhook_endpoint).
429Over 10 requests a second (bursts of 50) for this key, or too many bad keys from your address (rate_limited).
500Our fault (internal_error). Safe to retry reads.
POST/orders/{id}/simulate/payment_failed
Test mode: fail a test order's payment
The simulated payment fails: the order becomes payment_failed (status_reason: card_declined, charge.status: failed) and order.payment_failed is sent. Then POST /orders/{id}/retry_payment simulates success (order.paid), or cancel it. No card is involved. Needs the order paid. Test keys and test orders only (a live key: 403 test_mode_only; a live order's id is not found with a test key). No body. Needs orders:write. Wrong state: 409 order_state.
Parameter
In
Type
Notes
id (required)
path
string
Idempotency-Key
header
string
Any unique string (a UUID works), 1 to 255 printable characters. Kept 24 hours per brand and mode. Same key + same body → the first answer again; same key + another body → 409 idempotency_key_reused; while the first is still running → 409 idempotency_key_in_use. A request that failed frees its key.
401No key, or not a valid one (api_key_missing, api_key_invalid).
403The key lacks a scope (missing_scope), the brand hasn't accepted the Terms of Service (terms_required), or a simulation was asked with a live key (test_mode_only).
404No such resource for your brand (resource_missing). Another brand's ids are always 404.
409The change clashes with existing data or the order's state (external_sku_taken, duplicate_external_order_id, idempotency_key_reused, idempotency_key_in_use, order_not_cancellable, charge_in_progress, order_not_payable, test_mode, order_state, too_many_webhook_endpoints, no_webhook_endpoint).
429Over 10 requests a second (bursts of 50) for this key, or too many bad keys from your address (rate_limited).
500Our fault (internal_error). Safe to retry reads.
POST/orders/{id}/simulate/ship
Test mode: mark a test order shipped
Makes one simulated parcel for the whole order (carrier USPS, service Ground Advantage, a fake tracking number TEST + 16 digits) and moves the order to shipped. An order.shipped webhook goes to your test endpoints through the same pipeline (signing, retries, delivery log) as a real shipment. Nothing is made or shipped. Needs the order paid. Test keys and test orders only (a live key: 403 test_mode_only; a live order's id is not found with a test key). No body. Needs orders:write. Wrong state: 409 order_state.
Parameter
In
Type
Notes
id (required)
path
string
Idempotency-Key
header
string
Any unique string (a UUID works), 1 to 255 printable characters. Kept 24 hours per brand and mode. Same key + same body → the first answer again; same key + another body → 409 idempotency_key_reused; while the first is still running → 409 idempotency_key_in_use. A request that failed frees its key.
401No key, or not a valid one (api_key_missing, api_key_invalid).
403The key lacks a scope (missing_scope), the brand hasn't accepted the Terms of Service (terms_required), or a simulation was asked with a live key (test_mode_only).
404No such resource for your brand (resource_missing). Another brand's ids are always 404.
409The change clashes with existing data or the order's state (external_sku_taken, duplicate_external_order_id, idempotency_key_reused, idempotency_key_in_use, order_not_cancellable, charge_in_progress, order_not_payable, test_mode, order_state, too_many_webhook_endpoints, no_webhook_endpoint).
429Over 10 requests a second (bursts of 50) for this key, or too many bad keys from your address (rate_limited).
500Our fault (internal_error). Safe to retry reads.
Webhook events
Every delivery is a signed POST of the event envelope. How to check the signature, with code:
verify a webhook.
Your bank wants you to confirm the card: save it again on the Developers page, then retry.
status (always)
string
simulated / not_charged are test mode (never a real charge); a test order's failed is a simulated decline (simulate/payment_failed). One of: succeeded, failed, pending, not_attempted, simulated, not_charged.
Error
Field
Type
Notes
error (always)
object
code (always)
string
Example: variant_not_found.
message (always)
string
param (always)
string | null
Example: line_items[0].variant_id.
request_id (always)
string
Example: req_abc123.
type (always)
string
One of: authentication_error, permission_error, invalid_request_error, payment_error, rate_limit_error, api_error.
Event
The envelope of every webhook. data by type. order.shipped: { order_id, external_order_id, shipment: { id, carrier, service, tracking_number, tracking_url, shipped_at, line_items: [{ external_line_id, variant_id, quantity }] }, order_status } (one event per shipment: a partial shipment is its own event). Other order.*: { order_id, external_order_id, order_status, previous_status, reason, occurred_at, order } (order = the Order object when the event was made); order.payment_failed adds failure_code, failure_message, next_retry_at; order.cancelled adds refunded_amount, currency. product.*: { product_id, product_status, product } (product = the Product object; null once unpublished). ping: { webhook_endpoint_id, message }.