fetch, an idempotent order, a tracking link — nothing to install.
In the dashboard, open API keys and create a key in Test mode. It starts with ps_test_ — test keys are free, never charge a card, and never reach a print facility. You can create one before adding a payment method.
Create a test keyexport PRINTEDSIMPLY_API_KEY=ps_test_YOUR_KEYA live key (ps_live_) takes the same requests. A real order also needs a card on file and your own artwork: Going live, at the end of this page, has the four changes.
Every response is the same envelope: status, data, and request_id. Quote the request_id in any support thread and we can find the exact call. Each product's sku is what an order names. Save it as products.ts and run node products.ts: Node 22.18 and later run TypeScript as it is, and fetch is built in, so there is nothing to install. The files have no type annotations, so on Node 18 to 22.17 the same text runs as products.mjs.
const apiKey = process.env.PRINTEDSIMPLY_API_KEY
if (!apiKey) throw new Error("Set PRINTEDSIMPLY_API_KEY to your test key (ps_test_…) first")
const res = await fetch("https://portal.printedsimply.com/api/v1/products?limit=3", {
headers: { "x-api-key": apiKey },
signal: AbortSignal.timeout(30_000),
})
const { data, error } = await res.json()
if (!res.ok) throw new Error(`${res.status} ${error.code}: ${error.message}`)
for (const product of data.products) console.log(product.sku, "-", product.name)Rows carry more than these three fields, the price of one unit among them; POST /v1/orders/quote prices a whole order. The order below names the first row's sticker as sticker-die-cut-3x3: a SKU is matched whatever its case.
The order's reference doubles as its Idempotency-Key: send the same request again and you get this order back, never a second one; the same key with a different body is refused with 422. A new order needs a new reference as well as a new key: one reference is one order for 30 days, so the same reference under another key is refused with 409 duplicate_reference, naming the order that has it. Run it as you ran products.ts: node order.ts.
const apiKey = process.env.PRINTEDSIMPLY_API_KEY
if (!apiKey) throw new Error("Set PRINTEDSIMPLY_API_KEY to your test key (ps_test_…) first")
const order = {
"items": [
{ "sku": "sticker-die-cut-3x3", "quantity": 100,
"artwork_url": "https://printedsimply.com/samples/sticker-3x3.png" }
],
"address_to": {
"name": "Jane Doe", "address1": "123 Main St",
"city": "Austin", "state": "TX", "zip": "78701", "country": "US"
},
"shipping": { "method": "standard" }
}
const res = await fetch("https://portal.printedsimply.com/api/v1/order", {
method: "POST",
headers: {
"x-api-key": apiKey,
"Content-Type": "application/json",
"Idempotency-Key": `order-${Date.now()}`,
},
body: JSON.stringify(order),
signal: AbortSignal.timeout(30_000),
})
const { data, error } = await res.json()
if (!res.ok) throw new Error(`${res.status} ${error.code}: ${error.message}`)
// data.order_id is the order (test_ord_… on a test key); data.tracking_url is its tracking page.
console.log(data.order_id, data.charged)
console.log(data.tracking_url)Move the sandbox order straight to shipped instead of waiting for the schedule: node ship.ts test_ord_… with the order_id order.ts printed.
const apiKey = process.env.PRINTEDSIMPLY_API_KEY
if (!apiKey) throw new Error("Set PRINTEDSIMPLY_API_KEY to your test key (ps_test_…) first")
const orderId = process.argv[2] // the order_id order.ts printed: test_ord_…
const res = await fetch(`https://portal.printedsimply.com/api/v1/order/${orderId}/simulate`, {
method: "POST",
headers: { "x-api-key": apiKey, "Content-Type": "application/json" },
body: JSON.stringify({ target_state: "shipped" }),
signal: AbortSignal.timeout(30_000),
})
const { data, error } = await res.json()
if (!res.ok) throw new Error(`${res.status} ${error.code}: ${error.message}`)
console.log(data.order_id, data.status)Left alone, a sandbox order moves on by itself: a scheduled job takes it to in_production, shipped and delivered over about five minutes, firing the same signed webhooks at each step. simulate does not wait for that schedule. It takes in_production, shipped, delivered, cancelled or issue_flagged, and only on a test key. An order the schedule has already delivered answers 400 already_terminal; to rehearse the step again, place a new order with a new reference and Idempotency-Key (the sample uses one value for both; the same key replays the delivered order, and the same reference under a new key is refused 409 duplicate_reference).
order.ts printed the tracking link. Open it, then read the order: once it has shipped, tracking carries the sandbox tracking number (TRK-TEST-…) and its carrier. Simulate delivered, or wait, to watch it deliver.
const apiKey = process.env.PRINTEDSIMPLY_API_KEY
if (!apiKey) throw new Error("Set PRINTEDSIMPLY_API_KEY to your test key (ps_test_…) first")
const orderId = process.argv[2]
const res = await fetch(`https://portal.printedsimply.com/api/v1/order/${orderId}`, {
headers: { "x-api-key": apiKey },
signal: AbortSignal.timeout(30_000),
})
const { data, error } = await res.json()
if (!res.ok) throw new Error(`${res.status} ${error.code}: ${error.message}`)
console.log(data.status, data.tracking.number, data.tracking.carrier)
console.log(data.tracking.page_url)Sandbox orders run the real lifecycle on a compressed clock: created → in_production → shipped → delivered, firing genuinely HMAC-signed webhooks at every transition through the same delivery pipeline live orders use. Leave the tracking page open.
The sandbox order above never charges, prints or ships. A real one takes four changes.
A live order is charged to the card on file when it is placed. Without one, POST /v1/order answers 402 no_payment_method and nothing is placed.
Open BillingOn the API keys page, create a key in Live mode. It starts with ps_live_ and is shown in full once, so store it where your server reads secrets, never in a browser.
Create a live keySwap in the live key, your customer's address and your artwork: an https link to the file (PNG, PDF or SVG), or the id POST /v1/assets returns, as print_file. A live order's artwork is checked before anything is charged: a line with no artwork answers 422 artwork_required, and a file that cannot be printed answers 422 artwork_not_printable.
A subscription in test mode receives sandbox events only. For real orders, subscribe your endpoint in live mode (or all) on the Webhooks page or with POST /v1/webhooks, and verify X-PS-Signature-V2 on every delivery.
Open Webhooks