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 }'await fetch("https://swaglists.com/api/v1/test/wallet/fund", {
method: "POST",
headers: { Authorization: `Bearer ${process.env.API_KEY}`, "Content-Type": "application/json" },
body: JSON.stringify({ amount: 250000 }), // $2,500.00; omit for the default grant
});requests.post(
"https://swaglists.com/api/v1/test/wallet/fund",
headers={"Authorization": f"Bearer {os.environ['API_KEY']}"},
json={"amount": 250000}, # $2,500.00; omit for the default grant
)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"const res = await fetch("https://swaglists.com/api/v1/test/orders/ext:quickstart-1001/advance", {
method: "POST",
headers: { Authorization: `Bearer ${process.env.API_KEY}` },
});
const order = await res.json();
console.log(order.status); // in_production, then shipped, then deliveredorder = requests.post(
"https://swaglists.com/api/v1/test/orders/ext:quickstart-1001/advance",
headers={"Authorization": f"Bearer {os.environ['API_KEY']}"},
).json()
print(order["status"]) # in_production, then shipped, then deliveredIt 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. |
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 answers402 insufficient_funds. Then fund the wallet — a rejected order must be sent again with a newIdempotency-Key, because the original key replays the 402 for 24 hours. - Drafts — send
"confirm": false, change the draft withPATCH /orders/{id}, thenPOST /orders/{id}/confirm. - Cancellations — cancel an order while it is
processing(before it reachesin_production). - Webhooks — test endpoints may use
http://URLs.POST /webhook_endpoints/{id}/testsends awebhook_endpoint.testevent to one endpoint. Replay any event from Developers → Events. - Idempotency — send the same request twice with the same
Idempotency-Keyand check you getIdempotent-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.