POST/v1/orders
Record a paid order; its print files are queued.
What it does
Every line must name a design this organisation issued: a handoff, or a design with its sig. Nothing is recorded when any line is wrong. Each design is frozen as bought and its print file queued; the order.created event fires once. Posting the same order_ref again answers 200 with the existing order and "created": false: a retry never prints twice. Where an order came from is PrintWisp's to say: an order posted here is always recorded with source api, whatever the body says. A test key (sk_test_…) records a test order (ord_t…, oln_t…): kept apart from real orders, never charged, removed with Delete test data. A live key records a real order only once the organisation is live and, for a store key, only while the store is Open.
Send the key: Authorization: Bearer sk_test_… or sk_live_…. Any method but GET or POST answers 405 method_not_allowed: “GET or POST.”
curl -X POST "https://printwisp.com/v1/orders" \
-H "Authorization: Bearer $PRINTWISP_KEY" \
-H "Content-Type: application/json" \
-d '{"order_ref":"WEB-10021","placed_at":"2026-10-05T04:31:00Z","currency":"AUD","customer":{"name":"Jane Citizen","email":"jane@example.com"},"lines":[{"line_ref":"1","qty":1,"amount":"89.00","design":"dsn_5c2a9e01b7d4f638","sig":"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"}]}'
# 201
{
"order": "ord_7d1e4b9a2c6f0583",
"order_ref": "WEB-10021",
"created": true,
"lines": [
{
"line": "oln_41aa8c3e9d2b7f16",
"line_ref": "1",
"design": "dsn_5c2a9e01b7d4f638",
"qty": 1,
"print_status": "queued",
"components": [],
"fields": []
}
]
}<?php
$ch = curl_init('https://printwisp.com/v1/orders');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => json_encode([
'order_ref' => 'WEB-10021',
'placed_at' => '2026-10-05T04:31:00Z',
'currency' => 'AUD',
'customer' => ['name' => 'Jane Citizen', 'email' => 'jane@example.com'],
'lines' => [
[
'line_ref' => '1',
'qty' => 1,
'amount' => '89.00',
'design' => 'dsn_5c2a9e01b7d4f638',
'sig' => 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855'
]
]
]),
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . getenv('PRINTWISP_KEY'), 'Content-Type: application/json'],
]);
$answer = json_decode(curl_exec($ch), true);// Node (your server). The key never reaches a browser.
const r = await fetch('https://printwisp.com/v1/orders', {
method: 'POST',
headers: { Authorization: 'Bearer ' + process.env.PRINTWISP_KEY, 'Content-Type': 'application/json' },
body: JSON.stringify({
order_ref: 'WEB-10021',
placed_at: '2026-10-05T04:31:00Z',
currency: 'AUD',
customer: { name: 'Jane Citizen', email: 'jane@example.com' },
lines: [
{
line_ref: '1',
qty: 1,
amount: '89.00',
design: 'dsn_5c2a9e01b7d4f638',
sig: 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855'
}
]
}),
});
const answer = await r.json();import os, requests
r = requests.post(
"https://printwisp.com/v1/orders",
json={
"order_ref": "WEB-10021",
"placed_at": "2026-10-05T04:31:00Z",
"currency": "AUD",
"customer": { "name": "Jane Citizen", "email": "jane@example.com" },
"lines": [
{
"line_ref": "1",
"qty": 1,
"amount": "89.00",
"design": "dsn_5c2a9e01b7d4f638",
"sig": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
}
]
},
headers={"Authorization": f"Bearer {os.environ['PRINTWISP_KEY']}"},
)
answer = r.json()Parameters
| Parameter | Type | Description |
|---|---|---|
order_ref required | string ≤ 100 | Your own order number. Unique within a store: each store numbers its own orders, and the orders that belong to no store share one numbering. |
lines[] required | array 1–200 | Each with handoff, design, sig, line_ref, qty, amount, currency, extras, sku, variant, unit_price: see below. |
placed_at | date-time | When the shopper paid; now when missing. |
test | boolean | true records a test order (no fee, never counted) even with a live key -- for an order your platform marks as a test. It never turns a test key's order into a real one. |
customer | object | {name, email}; other keys are dropped. |
currency | string | The default for every line's amount. |
fx | object | The exchange rate your platform used for amounts in a currency other than your catalogue's: {base, currency, rate, stale} -- how many currency one base (your catalogue's currency) is; stale: true for a last known rate rather than the day's. PrintWisp converts those lines at it for its fee when it is believable (within 20% of the store's own rate), else at the store's own rate. Optional: an order is never refused for it. |
connection | string | For an account key: a connected store's id (int_…). A store's own key needs none. |
lines[].handoff | string | A hand-off (hnd_…) from a cart PrintWisp filled. Give handoff, or design + sig. |
lines[].design | string | The design (dsn_…), with its sig, when your own cart held it. |
lines[].sig | string | The design's signature, as the designer handed it to your page. |
lines[].line_ref | string ≤ 100 | Your id for the line; l1, l2 … when missing. |
lines[].qty | integer 1–100000 | |
lines[].amount | number | string | What the shopper paid for the line, a number 0 or more: the base of PrintWisp's per-order fee. |
lines[].currency | string | 3 letters; the order's currency when missing. |
lines[].extras | array | Ready lines: the codes of the extras ticked (a product's extras, e.g. ["gift-wrap"]); each adds its price when PrintWisp prices the line. |
lines[].sku | string ≤ 160 | Your SKU, for a design + sig line. On its own (no handoff, no design): a READY product's variant SKU -- a line with nothing to print, priced by PrintWisp from the store unless you send unit_price. |
lines[].variant | string ≤ 100 | A ready product's variant code, instead of sku. |
lines[].unit_price | number | string | Ready lines only: your own price per item, when your shop priced it. |
Response
201 application/json. Created.
Errors
| HTTP | error | message |
|---|---|---|
| 401 | unauthorized | Send Authorization: Bearer <secret key>. Keys are made in PrintWisp, on your store's Overview → API keys. |
| 405 | method_not_allowed | GET or POST. |
| 409 | organisation_not_live | Your organisation is not live yet: record orders with a test key (sk_test_...) until it is. |
| 409 | store_not_open | This store is not Open: record its orders with a test key (sk_test_...) until it is. |
| 422 | invalid_order | order_ref is required: the shop's order number. |
| 422 | invalid_order | An order needs at least one line. |
| 422 | invalid_order | At most 200 lines in one order. |
| 422 | invalid_order | Line 1: qty must be a positive whole number. |
| 422 | invalid_order | Line 1: amount must be a number, 0 or more. |
| 422 | invalid_order | Line 1: no such hand-off hnd_6e4b1a9c3d7f2058. |
| 422 | invalid_order | Line 1: hand-off hnd_6e4b1a9c3d7f2058 never reached a cart. |
| 422 | invalid_order | Line 1: design dsn_5c2a9e01b7d4f638 is not one this shop issued, or its signature is wrong. |
| 422 | invalid_order | Line 1: name a handoff, or a design with its sig. |
| 422 | invalid_order | No such connection: int_2b7c9e0d4a1f6835. |