Skip to content

Test mode

Build and test your whole integration for free — test keys, test credits, simulated production, lifecycle controls and magic values.

Test mode is a complete copy of the platform for development. Use a test key (sk_test_…) and every request runs the real pipeline — validation, pricing, credit reservations, print-file generation, events and webhooks — but production is simulated: nothing is printed, shipped or charged, and your customers never get emails.

What is separate in test mode

Shared between test and live Separate per mode
Products, variants and SKUs Orders, shipments and order events
Files (artwork) Wallet, transactions and credits
Catalog and your wholesale prices Webhook endpoints, events and deliveries
Account settings and branding Idempotency keys and API request logs

Because products are shared, you can build and test everything in test mode and go live by swapping the key — no need to recreate products.

Test credits

The test wallet starts empty. Add free test credits — $1,000 per call by default:

curl -X POST https://swaglists.com/api/v1/test/wallet/fund \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "amount": 250000 }'

This endpoint needs a full-access test key. In the dashboard, switch to test mode and use Wallet → Add test credits. Orders waiting for credits (on_hold / insufficient_funds) resume automatically after credits are added — which makes this the easiest way to test your on-hold handling: create an order with an empty test wallet, then fund it.

Simulated production

A confirmed test order goes through the same states as a live one. On its own it advances roughly like this:

After Order status What happens
seconds processing Credits reserved, order accepted by the simulator (credits captured).
~2 minutes in_production order.in_production
~5 minutes shipped A shipment with a test tracking number: shipment.created, order.shipped
~12 minutes shipped Shipment out_for_delivery: shipment.updated
~15 minutes delivered shipment.delivered, order.delivered

Tracking numbers are generated test numbers — the carrier's tracking page will not find them.

The simulator advances on its own clock, and we check its progress periodically — just like the production of a live order. When it moves several steps between two checks, you still get every step, in order and exactly once — the same lifecycle guarantee as live orders, where data.object already shows the latest state (a late order.shipped can carry "status": "delivered").

Advance an order on demand

Don't want to wait? POST /test/orders/{id}/advance moves an order one step forward and sends the same webhooks as a real order. Call it repeatedly: processing → in_production → shipped → delivered. The {id} can also be ext:<your external_id>.

curl -X POST https://swaglists.com/api/v1/test/orders/ext:quickstart-1001/advance \
  -H "Authorization: Bearer $API_KEY"

It needs the orders:write scope and returns 409 order_not_advanceable for drafts, finished orders (delivered, canceled, failed) and orders on hold for a reason that needs action (for example waiting for credits — fund the wallet instead).

Advancing an order moves it ahead of the simulator's clock. When the simulator later reaches the same steps, nothing is sent again: no second shipment, no repeated event. Only real changes — for example an updated estimated delivery date — still produce a shipment.updated.

Simulate problems

Send a JSON body to choose what the simulated factory does next. Every outcome goes through the same pipeline as a real factory update — timeline, webhooks, credit refunds and notifications — and the simulator keeps to it afterwards:

outcome When What happens
next (default) any step above The next step: in_production → shipped → delivered, or resume an order held by the factory.
fail_production before it ships The items can't be produced: they become failed and their test credits are returned. As with a real factory failure the order goes on_hold (hold_reason: fulfillment_issue) for our team to follow up.
partial_shipment before it ships, 2+ units About half of the units ship now (shipment.created, order.partially_shipped); the rest ship on the normal schedule.
delivery_exception a package in transit The carrier reports a problem: shipment.updated with "status": "exception". It stays that way until you advance again, which delivers it.
return_to_sender a package in transit The package goes back to the sender: shipment.updated with "status": "returned". The order stays shipped; there's nothing more to simulate.
Shell
curl -X POST https://swaglists.com/api/v1/test/orders/ext:quickstart-1001/advance \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "outcome": "partial_shipment", "expected_status": "in_production" }'

An outcome that doesn't fit the order's current state returns 409 order_not_advanceable. Add expected_status — the status you last saw — to make the call safe to retry: if the order has moved on in the meantime (the simulator also advances on its own), nothing happens and the call returns 409 order_status_changed with the current status.

Magic values

Put one of these words anywhere in recipient.name of a test order to simulate production problems (case-insensitive, e.g. "Jane FAIL_SUBMIT Doe"):

recipient.name contains What happens
FAIL_SUBMIT Production rejects the order: it ends failed (order.failed), reserved test credits are released.
RETRY_SUBMIT The first two attempts to send the order to production fail temporarily, then it succeeds — the order stays processing a little longer (shows our automatic retries).
HOLD_ORDER Production puts the order on hold: status: on_hold, hold_reason: fulfillment_issue (order.on_hold).
NO_CANCEL Cancellation is refused as if production had started: POST /orders/{id}/cancel returns 409 order_not_cancelable.

Use them to test that your integration handles failures, holds and refused cancellations — before a real customer runs into them.

Other ways to test

  • Insufficient credits — create an order with an empty test wallet. With the default setting the order is held (on_hold, hold_reason: insufficient_funds); if your account rejects such orders (Settings → Orders), the API answers 402 insufficient_funds. Then fund the wallet — a rejected order must be sent again with a new Idempotency-Key, because the original key replays the 402 for 24 hours.
  • Drafts — send "confirm": false, change the draft with PATCH /orders/{id}, then POST /orders/{id}/confirm.
  • Cancellations — cancel an order while it is processing (before it reaches in_production).
  • Webhooks — test endpoints may use http:// URLs. POST /webhook_endpoints/{id}/test sends a webhook_endpoint.test event to one endpoint. Replay any event from Developers → Events.
  • Idempotency — send the same request twice with the same Idempotency-Key and check you get Idempotent-Replayed: true.

Limits of test mode

  • Test orders are never produced, so print quality, packaging and delivery times can only be checked with a live order — order a sample to yourself before launch.
  • Test tracking numbers do not work on carrier sites; the branded tracking page (tracking_page_url) works.
  • Customer shipment emails are only sent for live orders.