Test keys, real behaviour
wpl_test_ keys give you a full sandbox: orders are priced, checked and tracked, but never charged or printed.
Quote exact prices, upload artwork, place orders and follow them from a local press to your customer's door. REST and JSON, documented with OpenAPI, with a sandbox that behaves like production.
wpl_test_ keys give you a full sandbox: orders are priced, checked and tracked, but never charged or printed.
POST /v1/sandbox/jobs/{ref}/advance moves a test order to InProduction, Shipped or Delivered, with a tracking number.
CSV upload, Shopify and WooCommerce webhooks, or any JSON with a field mapping. Every inbound order is logged and replayable.
Hold, release, re-address or cancel before production; raise a problem with the printer after despatch.
Order events delivered to your URL with X-WPL-Signature: t=…,v1=HMAC-SHA256. Check the signature before acting on an event.
Already approved? Create your keys under Account → Developers in your store. The same guide appears there with your own products filled in.
Base address: https://api.weprintlocal.co.uk/v1. Authenticate with Authorization: Bearer wpl_test_… while you build: test orders behave like real ones but are never charged or printed. Switch to a wpl_live_ key once live trading is enabled.
Keep it on your server only, never in a web page or mobile app. wpl_test_ keys work in the sandbox; wpl_live_ keys place real orders.
export WPL_API_KEY="wpl_test_…"Everything your store can sell, with its options and the artwork files each needs (requiredFiles).
curl https://api.weprintlocal.co.uk/v1/products -H "Authorization: Bearer $WPL_API_KEY"Quotes are fully priced (items, delivery, VAT), sealed and held for a while. Order against the quoteId.
curl -X POST https://api.weprintlocal.co.uk/v1/quotes -H "Authorization: Bearer $WPL_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"productCode": "SQ-HARDBACK-BOOK",
"quantity": 1,
"options": {
"pages": "24"
},
"deliveryServiceCode": "STANDARD"
}'Ask for an upload grant for each file the product needs (Cover, Text), then stream the file with the grant in X-Upload-Token.
curl -X POST https://api.weprintlocal.co.uk/v1/files/uploads -H "Authorization: Bearer $WPL_API_KEY" \
-H "Content-Type: application/json" \
-d '{"role":"Cover","fileName":"cover.pdf","contentType":"application/pdf","sizeBytes":123456}'
# → { "fileId": "…", "uploadToken": "…", "uploadPath": "/v1/files/uploads/…/content" }
curl -X PUT "https://api.weprintlocal.co.uk$UPLOAD_PATH" -H "Authorization: Bearer $WPL_API_KEY" \
-H "X-Upload-Token: $UPLOAD_TOKEN" -H "Content-Type: application/pdf" --data-binary @cover.pdfOrder quotes directly with their artwork by role. Send your own reference and an Idempotency-Key so a retry never creates a second order. We deliver within the UK only: countryCode must be GB and postcodes are checked and tidied (an invalid one is a 422 NOT_UK_ADDRESS). Add "draft": true to create the order on hold: it is checked and priced but not sent to a printer until you release it.
curl -X POST https://api.weprintlocal.co.uk/v1/orders -H "Authorization: Bearer $WPL_API_KEY" \
-H "Idempotency-Key: my-order-1001" -H "Content-Type: application/json" \
-d '{"externalReference":"1001",
"lines":[{"quoteId":"$QUOTE_ID","files":{ "Cover": "$COVER_FILE_ID", "Text": "$TEXT_FILE_ID" }}],
"customer":{"name":"Jo Bloggs","email":"jo@example.com"},
"deliveryAddress":{"name":"Jo Bloggs","line1":"1 High St","city":"Leeds","postcode":"LS1 1AA","countryCode":"GB"}}'Status, timeline and, once despatched, the carrier and tracking number on each line. Statuses: AwaitingArtwork, OnHold, Accepted, InProduction, Shipped, Delivered. Each order also has dispatchBy, the UK working day (YYYY-MM-DD) it must be despatched by, and slaStatus (OnTrack, DueToday, Late, Met or Missed). Search and filter the list (q, status, channel, from, to, onHold, tag, sla=Late|DueToday|OnTrack), or download the same selection as CSV.
curl https://api.weprintlocal.co.uk/v1/orders/$ORDER_REFERENCE -H "Authorization: Bearer $WPL_API_KEY"
curl "https://api.weprintlocal.co.uk/v1/orders?page=1&status=Shipped&q=LS1" -H "Authorization: Bearer $WPL_API_KEY"
curl "https://api.weprintlocal.co.uk/v1/orders?sla=Late&tag=rush" -H "Authorization: Bearer $WPL_API_KEY"
curl "https://api.weprintlocal.co.uk/v1/orders/export?from=2026-11-01&to=2026-12-01&sla=Late" -H "Authorization: Bearer $WPL_API_KEY" -o orders.csvTest-key orders never reach a printer, so you move them along yourself to exercise your status handling, webhooks and tracking. Pass an order reference (advances every job on it) or a job reference.
curl -X POST https://api.weprintlocal.co.uk/v1/sandbox/jobs/$ORDER_REFERENCE/advance -H "Authorization: Bearer $WPL_API_KEY" \
-H "Content-Type: application/json" \
-d '{"to":"Shipped","trackingNumber":"TEST123"}' # InProduction | Shipped | DeliveredSend printerCode (for example LEEDS-PRESS) on the order, or on a line to override it, and that printer gets the job. Leave it out and we send it to a printer near the delivery address. A printer that isn't on the network or doesn't make the product is a 422 PRINTER_UNAVAILABLE. If your printer declines, nothing goes to anyone else: you get order.printer_declined, then choose again with PUT /v1/orders/{ref}/lines/{lineNo}/printer (a code, or null to let us choose). CSV orders use a printer_code column; Shopify and WooCommerce use a line property or meta key named printer.
Until a printer accepts an order you can hold, release (this also confirms a draft), re-address or cancel it. Once it is in production these answer 409 (ORDER_IN_PRODUCTION, ADDRESS_LOCKED, ORDER_SHIPPED); the order's can object says what is allowed now. Within 30 days of despatch you can report a problem with a line (Damaged, Misprinted, WrongItem, MissingItems, LostInTransit, Other). The printer decides on a reprint or refund; notes on the order carry the conversation.
curl -X POST https://api.weprintlocal.co.uk/v1/orders/$ORDER_REFERENCE/hold -H "Authorization: Bearer $WPL_API_KEY" -H "Content-Type: application/json" -d '{"reason":"Checking stock"}'
curl -X POST https://api.weprintlocal.co.uk/v1/orders/$ORDER_REFERENCE/release -H "Authorization: Bearer $WPL_API_KEY"
curl -X PUT https://api.weprintlocal.co.uk/v1/orders/$ORDER_REFERENCE/delivery-address -H "Authorization: Bearer $WPL_API_KEY" -H "Content-Type: application/json" \
-d '{"name":"Jo Bloggs","line1":"2 Park Row","city":"Leeds","postcode":"LS1 5HD","countryCode":"GB"}'
curl -X POST https://api.weprintlocal.co.uk/v1/orders/$ORDER_REFERENCE/cancel -H "Authorization: Bearer $WPL_API_KEY" -H "Content-Type: application/json" -d '{"reason":"Customer changed their mind"}'
curl -X POST https://api.weprintlocal.co.uk/v1/orders/$ORDER_REFERENCE/issues -H "Authorization: Bearer $WPL_API_KEY" -H "Content-Type: application/json" \
-d '{"lineNo":1,"kind":"Damaged","description":"Cover creased on arrival","affectedQuantity":1}'
curl -X POST https://api.weprintlocal.co.uk/v1/orders/$ORDER_REFERENCE/notes -H "Authorization: Bearer $WPL_API_KEY" -H "Content-Type: application/json" -d '{"body":"Gift: no prices in the parcel"}'
curl https://api.weprintlocal.co.uk/v1/orders/$ORDER_REFERENCE/activity -H "Authorization: Bearer $WPL_API_KEY"Each order has a dispatchBy day (UK working days) and a slaStatus: OnTrack, DueToday or Late while it is open, then Met or Missed once despatched. Filter the list and the CSV export with sla=Late and tag=rush. Tags are your own short labels; sending tags replaces them all (an invalid tag is a 422). You can cancel one item until a printer starts it (can.cancelItems); after that it is a 409 ITEM_IN_PRODUCTION or ITEM_SHIPPED. Nothing is refunded automatically, and cancelling the last item cancels the order. A duplicate (can.duplicate) is a new order on your account, not charged to a card, with duplicatedFrom set; it starts on hold so you can check it, then release it. Saved views are shared with your team or private to one user.
# Tags: replace the whole set (letters, numbers and - _ . : up to 40 characters; at most 20)
curl -X PUT https://api.weprintlocal.co.uk/v1/orders/$ORDER_REFERENCE/tags -H "Authorization: Bearer $WPL_API_KEY" -H "Content-Type: application/json" -d '{"tags":["rush","school:leeds"]}'
# Cancel one item (line 2) before a printer starts it; the rest of the order carries on
curl -X POST https://api.weprintlocal.co.uk/v1/orders/$ORDER_REFERENCE/lines/2/cancel -H "Authorization: Bearer $WPL_API_KEY" -H "Content-Type: application/json" -d '{"reason":"Customer no longer needs it"}'
# Duplicate: same items, artwork and address as a new order on account at today's prices; starts on hold unless "hold": false
curl -X POST https://api.weprintlocal.co.uk/v1/orders/$ORDER_REFERENCE/duplicate -H "Authorization: Bearer $WPL_API_KEY" -H "Content-Type: application/json" -d '{"hold":true}'
# Download one of the order's artwork files (fileId from lines[].files)
curl https://api.weprintlocal.co.uk/v1/orders/$ORDER_REFERENCE/files/$FILE_ID -H "Authorization: Bearer $WPL_API_KEY" -o artwork.pdf
# Saved views of the orders list (filters are the list's query parameters)
curl https://api.weprintlocal.co.uk/v1/order-views -H "Authorization: Bearer $WPL_API_KEY"
curl -X POST https://api.weprintlocal.co.uk/v1/order-views -H "Authorization: Bearer $WPL_API_KEY" -H "Content-Type: application/json" \
-d '{"name":"Late rush orders","shared":true,"filters":{"sla":"Late","tag":"rush"}}'
curl -X DELETE https://api.weprintlocal.co.uk/v1/order-views/$VIEW_ID -H "Authorization: Bearer $WPL_API_KEY"Already taking orders somewhere else? Map your SKUs to our products once (POST /v1/intake/sku-mappings, or a CSV with columns sku, product_code, opt:<option>), then send orders in. Each inbound order is logged with any issues and can be replayed after you fix a mapping. Connections for Shopify, WooCommerce and mapped JSON give you a webhook URL to paste into that platform — manage them under Account → Integrations.
curl -X POST https://api.weprintlocal.co.uk/v1/intake/csv -H "Authorization: Bearer $WPL_API_KEY" -H "Content-Type: text/csv" --data-binary @orders.csv
# orders.csv — rows sharing order_ref form one order
order_ref,customer_name,customer_email,address1,city,postcode,country,sku,quantity,artwork_url
1001,Jo Bloggs,jo@example.com,1 High St,Leeds,LS1 1AA,GB,BOOK-21,1,https://example.com/jo.pdfcurl -X POST https://api.weprintlocal.co.uk/v1/intake/orders -H "Authorization: Bearer $WPL_API_KEY" -H "Content-Type: application/json" \
-d '[{"externalReference":"1002","customer":{"name":"Jo Bloggs","email":"jo@example.com"},
"delivery":{"name":"Jo Bloggs","line1":"1 High St","city":"Leeds","postcode":"LS1 1AA","countryCode":"GB"},
"lines":[{"sku":"BOOK-21","quantity":1,"artwork":[{"role":"Cover","url":"https://example.com/art.pdf"}]}]}]'Subscribe a public https:// URL to order events under Account → Webhooks & postbacks or POST /v1/webhooks. The signing secret is shown once. Every delivery carries X-WPL-Signature: t=<unix>,v1=<hex> — an HMAC-SHA256 of t + "." + body.
order.received | We have the order: checked, priced and given its reference and dispatch-by day. Sent once, when the order is created. |
order.status_changed | Every time the order moves to a new status. Subscribe to this alone if you only want one event. (Not sent to postback URLs, which get the specific events instead.) |
order.accepted | Paid and artwork checked; a local printer has been chosen. |
order.in_production | The printer has started making it. |
order.item_status_changed | One item moved on: accepted, in production, shipped (with tracking), delivered, cancelled or failed. Also sent for reprints. |
order.delayed | The printer told us an item will be late, with the reason and the new dispatch-by day. |
order.printer_declined | The printer you named can’t take an item. Nothing goes to another printer: choose another printer for it, or let us choose one near the delivery address. |
order.shipped | Despatched, with carrier and tracking number per line. |
order.delivered | The carrier confirmed delivery. |
order.cancelled | Cancelled by you, the customer or our team. Nothing is refunded automatically. |
order.artwork_failed | A file failed our print checks and needs replacing. |
order.failed | Something went wrong that our team is looking at. |
order.on_hold | Held by you, by a connection set to hold new orders, or created as an API draft. Nothing is printed until it is released. |
order.address_updated | The delivery address was changed before production; the order may move to a different local printer. |
order.refunded | Money went back to the card, or a credit was added to your next invoice. |
order.issue_updated | A problem with a dispatched item was reported, or the printer answered it (a reprint, a refund they paid you directly, or a reply). |
import { createHmac, timingSafeEqual } from "node:crypto";
export function verify(rawBody, header, secret, toleranceSeconds = 300) {
const parts = Object.fromEntries(header.split(",").map((p) => p.split("=")));
const t = Number(parts.t);
if (!t || Math.abs(Date.now() / 1000 - t) > toleranceSeconds) return false;
const expected = createHmac("sha256", secret).update(`${t}.${rawBody}`).digest("hex");
return parts.v1?.length === expected.length &&
timingSafeEqual(Buffer.from(parts.v1, "hex"), Buffer.from(expected, "hex"));
}A postback URL gets every event about an order without subscribing to events — all of the ones above except order.status_changed. Set a default under Account → Webhooks & postbacks (or PUT /v1/postback-settings), give a sales-channel connection its own, or send postbackUrl with an API order to override both for that order. It must be a public https:// address (anything else is a 422). Postbacks are signed with your store's postback secret, which is separate from each webhook subscription's secret; rotate it with POST /v1/postback-settings/rotate-secret (the new secret is shown once).
curl -X POST https://api.weprintlocal.co.uk/v1/orders -H "Authorization: Bearer $WPL_API_KEY" \
-H "Idempotency-Key: my-order-1001" -H "Content-Type: application/json" \
-d '{"externalReference":"1001","postbackUrl":"https://example.com/weprintlocal/postbacks",
"lines":[{"quoteId":"$QUOTE_ID","externalLineId":"A-1","files":{ "Cover": "$COVER_FILE_ID", "Text": "$TEXT_FILE_ID" }}],
"customer":{"name":"Jo Bloggs","email":"jo@example.com"},
"deliveryAddress":{"name":"Jo Bloggs","line1":"1 High St","city":"Leeds","postcode":"LS1 1AA","countryCode":"GB"}}'Every body is { id, event, createdUtc, data }. The id is unique to that delivery and stays the same on every retry and when you press "Send again", so store the ids you have handled and ignore repeats. The headers are X-WPL-Event, X-WPL-Delivery (the same id) and X-WPL-Signature. Events may arrive out of order after a retry: use createdUtc and the status in data, not arrival order.
order.received — Sent once, as soon as the order is created: its reference, the channel it came through and the day it must be dispatched by. externalLineId is your own line id, if you sent one.
{
"id": "5b0f6a5e-3c1d-4f7a-9d1e-2a6c8b7e4f10",
"event": "order.received",
"createdUtc": "2026-11-10T09:14:03.120Z",
"data": {
"orderReference": "WPL-7Q2M9X4KCB",
"externalReference": "1001",
"isTest": false,
"channel": "API",
"dispatchBy": "2026-11-13",
"customerName": "Jo Bloggs",
"items": [
{
"lineNo": 1,
"externalLineId": "A-1",
"quoteId": "3f7e2a10-8c4b-4e0e-9a51-7d2b6c1e9f33"
}
]
}
}order.item_status_changed — One item moved on. status is accepted, in_production, shipped, delivered, cancelled or failed; reprint is true when the item is a reprint; shipment is filled in once the item has shipped (otherwise null).
{
"id": "c2d4e6f8-1a3b-4c5d-8e7f-9a0b1c2d3e4f",
"event": "order.item_status_changed",
"createdUtc": "2026-11-12T15:42:10.004Z",
"data": {
"orderReference": "WPL-7Q2M9X4KCB",
"externalReference": "1001",
"isTest": false,
"item": {
"lineNo": 1,
"externalLineId": "A-1",
"productCode": "SQ-HARDBACK-BOOK",
"quantity": 2,
"status": "shipped",
"reprint": false,
"shipment": {
"carrier": "Royal Mail",
"trackingNumber": "RM123456785GB",
"trackingUrl": "https://www.royalmail.com/track-your-item#/tracking-results/RM123456785GB",
"shippedUtc": "2026-11-12T15:41:57.000Z"
}
}
}
}order.shipped (with shipments) — The whole order has been despatched. shipments has one entry per item with its carrier and tracking. Status events (accepted, in_production, shipped, delivered, cancelled …) all have this shape.
{
"id": "9e8d7c6b-5a4f-4e3d-a2c1-b0a9f8e7d6c5",
"event": "order.shipped",
"createdUtc": "2026-11-12T15:42:11.310Z",
"data": {
"orderReference": "WPL-7Q2M9X4KCB",
"externalReference": "1001",
"isTest": false,
"previousStatus": "InProduction",
"status": "Shipped",
"paymentStatus": "Paid",
"shipments": [
{
"lineNo": 1,
"externalLineId": "A-1",
"carrier": "Royal Mail",
"trackingNumber": "RM123456785GB",
"trackingUrl": "https://www.royalmail.com/track-your-item#/tracking-results/RM123456785GB",
"shippedUtc": "2026-11-12T15:41:57.000Z"
},
{
"lineNo": 2,
"externalLineId": "A-2",
"carrier": "DPD",
"trackingNumber": "15501234567890",
"trackingUrl": null,
"shippedUtc": "2026-11-12T15:40:02.000Z"
}
],
"updatedUtc": "2026-11-12T15:42:11.000Z"
}
}order.delayed — The printer told us an item will be late. cause is their reason in plain words; dispatchBy is the new day it should be dispatched by.
{
"id": "1f2e3d4c-5b6a-4798-8a7b-6c5d4e3f2a1b",
"event": "order.delayed",
"createdUtc": "2026-11-11T11:05:44.870Z",
"data": {
"orderReference": "WPL-7Q2M9X4KCB",
"externalReference": "1001",
"isTest": false,
"item": {
"lineNo": 1,
"externalLineId": "A-1"
},
"cause": "Waiting for cover board stock",
"dispatchBy": "2026-11-17"
}
}Verify the signature. X-WPL-Signature: t=<unix>,v1=<hex> where v1 is the HMAC-SHA256 of t + "." + body with the secret. Check it against the raw body, and reject old timestamps.
import { createHmac, timingSafeEqual } from "node:crypto";
// Use the raw request body (exactly the bytes received), not re-serialised JSON.
export function verify(rawBody, header, secret, toleranceSeconds = 300) {
const parts = Object.fromEntries(header.split(",").map((p) => p.split("=")));
const t = Number(parts.t);
if (!t || Math.abs(Date.now() / 1000 - t) > toleranceSeconds) return false;
const expected = createHmac("sha256", secret).update(`${t}.${rawBody}`).digest("hex");
return parts.v1?.length === expected.length &&
timingSafeEqual(Buffer.from(parts.v1, "hex"), Buffer.from(expected, "hex"));
}
// Express: app.post("/postbacks", express.raw({ type: "application/json" }), (req, res) => {
// if (!verify(req.body.toString("utf8"), req.get("x-wpl-signature") ?? "", process.env.WPL_POSTBACK_SECRET)) return res.sendStatus(401);
// const event = JSON.parse(req.body);
// if (alreadySeen(event.id)) return res.sendStatus(200); // a retry or "Send again": ignore
// remember(event.id); handle(event); res.sendStatus(200);
// });<?php
function wpl_verify(string $rawBody, string $header, string $secret, int $tolerance = 300): bool {
$parts = [];
foreach (explode(',', $header) as $pair) {
[$k, $v] = array_pad(explode('=', $pair, 2), 2, '');
$parts[$k] = $v;
}
$t = (int)($parts['t'] ?? 0);
if ($t === 0 || abs(time() - $t) > $tolerance) return false;
$expected = hash_hmac('sha256', $t . '.' . $rawBody, $secret);
return hash_equals($expected, $parts['v1'] ?? '');
}
$raw = file_get_contents('php://input');
if (!wpl_verify($raw, $_SERVER['HTTP_X_WPL_SIGNATURE'] ?? '', getenv('WPL_POSTBACK_SECRET'))) {
http_response_code(401);
exit;
}
$event = json_decode($raw, true);
// De-duplicate on $event['id'], then handle $event['event'] and $event['data'].
http_response_code(200);Retries. Answer with any 2xx within 10 seconds. Anything else (or no answer) is tried again after 30 seconds, then 1, 2, 4, 8 minutes and so on, doubling up to at most 6 hours apart, for up to 7 days. After that the delivery is marked Failed. A delivery to a webhook you paused or deleted is Dropped. Any delivery can be sent again.
Delivery log. Every webhook and postback we have sent, newest first, with each attempt (time, status code, error, time taken) and the exact body. Filter by status (Pending, Retrying, Delivered, Failed, Dropped), event and orderReference; page and pageSize (up to 100) page through it.
curl "https://api.weprintlocal.co.uk/v1/webhook-deliveries?status=Failed&pageSize=20" -H "Authorization: Bearer $WPL_API_KEY"
curl "https://api.weprintlocal.co.uk/v1/webhook-deliveries?orderReference=$ORDER_REFERENCE" -H "Authorization: Bearer $WPL_API_KEY"
# Send one again now (same body and id, freshly signed) → 202
curl -X POST https://api.weprintlocal.co.uk/v1/webhook-deliveries/$DELIVERY_ID/redeliver -H "Authorization: Bearer $WPL_API_KEY"
# Default postback URL (null clears it) and its signing secret
curl https://api.weprintlocal.co.uk/v1/postback-settings -H "Authorization: Bearer $WPL_API_KEY"
curl -X PUT https://api.weprintlocal.co.uk/v1/postback-settings -H "Authorization: Bearer $WPL_API_KEY" -H "Content-Type: application/json" -d '{"url":"https://example.com/weprintlocal/postbacks"}'
curl -X POST https://api.weprintlocal.co.uk/v1/postback-settings/rotate-secret -H "Authorization: Bearer $WPL_API_KEY"const res = await fetch("https://api.weprintlocal.co.uk/v1/quotes", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.WPL_API_KEY}`,
"Content-Type": "application/json"
},
body: JSON.stringify({
"productCode": "SQ-HARDBACK-BOOK",
"quantity": 1,
"options": {
"pages": "24"
},
"deliveryServiceCode": "STANDARD"
})
});
const quote = await res.json();
console.log(quote.total, quote.currency);Every endpoint, field and error is described in the OpenAPI document — import it into Postman or generate a client: download openapi.json. Errors always return { code, message, fieldErrors, correlationId } — quote the correlationId if you contact us.
Merchant keys (wpl_test_/wpl_live_) call the store, quote, file, order, sandbox, intake and webhook endpoints. Printers in our network use a wpp_live_ key for the job endpoints.
GET /v1/store | The store named by the caller (theme, currency, support contact) |
GET /v1/products | Products this store can sell, with availability from its price books |
GET /v1/products/{productCode} | A product with its options and delivery services |
GET /v1/storefront/hosts/{host} | The store served on a custom domain (storefront only) |
POST /v1/quotes | Price a configuration. Quotes are immutable and expire; add one to a basket or order to buy it. |
GET /v1/quotes/{quoteId} | A quote issued to this store |
POST /v1/baskets | Create a basket. The returned token is its only credential: keep it server-side (e.g. an HttpOnly cookie) and send it as X-Basket-Token. |
GET /v1/baskets/{basketId} | The basket with totals, per-item issues and whether it can be checked out. |
POST /v1/baskets/{basketId}/items | Add a quote to the basket, optionally with its artwork. |
PUT /v1/baskets/{basketId}/items/{itemId}/files | Attach (or replace) artwork on a basket item. |
POST /v1/baskets/{basketId}/items/{itemId}/requote | Re-price an item (e.g. after its quote expired) with the same configuration; artwork is kept. |
DELETE /v1/baskets/{basketId}/items/{itemId} | Remove an item from the basket. |
POST /v1/files/uploads | Step 1: request a short-lived upload grant for one artwork file (role as the product defines, e.g. Cover, Text, Artwork). |
PUT /v1/files/uploads/{fileId}/content | Step 2: stream the file bytes (Content-Type application/pdf, image/png, image/jpeg or application/octet-stream) with the grant in X-Upload-Token. |
GET /v1/files/{fileId} | Upload and artwork-check status of a file. |
GET /v1/orders | Your store's orders, newest first, with search and filters (merchant key, or signed-in store staff). |
POST /v1/orders | Place an order from a ready basket, or directly from quotes (API). Repeats with the same basket, Idempotency-Key or externalReference return the existing order with 200. |
GET /v1/orders/export | Download your orders as CSV (same filters as the list; up to 5,000 rows, one row per line). |
GET /v1/orders/{orderReference} | Order status, artwork, timeline, shipment tracking, issues and refunds. Shoppers (via the store) send X-Order-Token or X-Basket-Token. |
POST /v1/orders/{orderReference}/cancel | Cancel an order before any printer has accepted it. Nothing is refunded automatically: refunds are agreed between you and the printer. Shoppers can only cancel unpaid orders. |
POST /v1/orders/{orderReference}/hold | Put an order on hold: nothing goes to a printer until you release it. Only before a printer accepts it. |
POST /v1/orders/{orderReference}/release | Release a held order or confirm a draft, so it goes on to production. |
PUT /v1/orders/{orderReference}/delivery-address | Change the delivery address (UK only) until a printer accepts the order. The order is re-routed to the best printer for the new address. |
GET /v1/orders/{orderReference}/issues | Problems reported on this order and how they were resolved |
POST /v1/orders/{orderReference}/issues | Report a problem with a dispatched item (within 30 days): damaged, misprinted, wrong item, missing items or lost in transit. The printer who made it sees the report and answers it (a reprint, a refund or a reply); we carry the messages. |
GET /v1/orders/{orderReference}/notes | Notes on the order shared between your team and ours |
POST /v1/orders/{orderReference}/notes | Add a note to the order (visible to your team and ours) |
GET /v1/orders/{orderReference}/activity | Everything that happened to the order, newest first |
PUT /v1/orders/{orderReference}/tags | Replace the order's tags (short labels to group and find orders) |
POST /v1/orders/{orderReference}/lines/{lineNo}/cancel | Cancel one item before a printer has started it; the rest of the order carries on. Nothing is refunded automatically. |
PUT /v1/orders/{orderReference}/lines/{lineNo}/printer | Choose the printer for one item (printerCode), or send null to let us choose one near the delivery address. Use it when the printer you named declined (order.printer_declined), or before a printer has the job. |
POST /v1/orders/{orderReference}/duplicate | Place the same items, artwork and address again as a new order, at today's prices (on account). hold: start it on hold to check first. |
GET /v1/orders/{orderReference}/files/{fileId} | Download one of the order's artwork files |
GET /v1/order-views | Saved order views for your store |
POST /v1/order-views | Save the current filters as a named view |
DELETE /v1/order-views/{id} | Delete a saved view |
POST /v1/sandbox/jobs/{jobReference}/advance | Advance a sandbox job (test keys only) to InProduction, Shipped or Delivered |
POST /v1/intake/orders | Submit orders in the canonical format; each is accepted, rejected (with issues) or a duplicate |
POST /v1/intake/csv | Upload a CSV of orders (one row per line; rows sharing order_ref form one order). Columns: order_ref, customer_name, customer_email, phone, address1, address2, city, region, postcode, country, product_code | sku, quantity, delivery_service, artwork_url, cover_url, opt:<option>… |
GET /v1/intake/records | Recent inbound orders and what happened to them (newest first) |
GET /v1/intake/records/{recordId} | One inbound order with its original payload |
POST /v1/intake/records/{recordId}/replay | Process a rejected order again (after fixing SKU mappings, products or the connection) |
GET /v1/intake/connections | Your sales-channel connections |
POST /v1/intake/connections | Connect a Shopify shop, WooCommerce site or any JSON source; returns its inbound webhook URL |
PATCH /v1/intake/connections/{connectionId} | Rename, reconfigure or switch off a connection |
DELETE /v1/intake/connections/{connectionId} | Disconnect (the URL stops accepting orders; history is kept) |
PUT /v1/intake/connections/{connectionId}/secret | Set or rotate the signing secret (paste Shopify's / WooCommerce's, or omit `secret` to generate one for JSON sources — shown once) |
GET /v1/intake/sku-mappings | Your SKU → product mappings |
POST /v1/intake/sku-mappings | Map a marketplace SKU to a product and fixed options |
PATCH /v1/intake/sku-mappings/{mappingId} | Change a SKU mapping |
DELETE /v1/intake/sku-mappings/{mappingId} | Remove a SKU mapping |
POST /v1/intake/sku-mappings/import | Import SKU mappings from CSV (columns: sku, product_code, opt:<option>…); existing SKUs are updated |
POST /v1/intake/shopify/{token} | Shopify orders/create webhook (X-Shopify-Hmac-Sha256) |
POST /v1/intake/woocommerce/{token} | WooCommerce order.created webhook (X-WC-Webhook-Signature) |
POST /v1/intake/json/{token} | Any JSON order, mapped by the connection’s field mapping (X-WPL-Signature when a secret is set) |
GET /v1/webhooks | Your webhook subscriptions. |
POST /v1/webhooks | Subscribe a URL to order events. The signing secret is returned once. |
PATCH /v1/webhooks/{id} | Change a subscription’s URL, events, or pause it. |
DELETE /v1/webhooks/{id} | Delete a subscription. |
POST /v1/webhooks/{id}/rotate-secret | Issue a new signing secret (returned once); the old one stops working immediately. |
POST /v1/webhooks/{id}/ping | Send a signed webhook.ping event to the subscription to test your endpoint. |
GET /v1/webhook-deliveries | Every webhook and postback sent for your store, newest first, with each attempt |
POST /v1/webhook-deliveries/{id}/redeliver | Send a delivery again now (same body and id, freshly signed) |
GET /v1/postback-settings | Where your order postbacks go and whether a signing secret exists |
PUT /v1/postback-settings | Set (or clear) the default postback URL for your orders |
POST /v1/postback-settings/rotate-secret | Issue a new postback signing secret (returned once) |
GET /v1/printer/me | Your printer profile, capacity and status |
PATCH /v1/printer/me | Pause or resume new offers, change capacity or contact details |
GET /v1/printer/jobs | Your jobs, newest first |
GET /v1/printer/jobs/{jobReference} | One of your jobs: options, artwork links, delivery, due date, history |
GET /v1/printer/jobs/{jobReference}/files/{role} | Download artwork for a job you hold (while offered or in progress) |
GET /v1/printer/issues | Problems reported on items you made (open ones by default) |
GET /v1/printer/jobs/{jobReference}/issues | Problems reported on this job |
POST /v1/printer/jobs/{jobReference}/issues/{issueReference}/resolve | Answer a problem on your job: Reprint (a new job for the affected quantity, offered to you), Refund (you have refunded the merchant yourself; nothing is paid through weprintlocal) or Reject (with your reason). The merchant sees your note. |
POST /v1/printer/jobs/{jobReference}/accept | Accept an offered job |
POST /v1/printer/jobs/{jobReference}/reject | Decline an offer (or hand back an accepted job before production); it's re-routed |
POST /v1/printer/jobs/{jobReference}/status | Report progress: InProduction, Shipped (tracking required unless untracked), Delivered or Failed |