Print Shop API
Updated 9/17/2026Print Shop API
If your account owns an approved print shop, these endpoints let you receive and fulfill orders routed to you, and manage your shop's pricing and printer capacity. This is a separate surface from the customer-facing order API — shop endpoints are authorized by shop ownership, not by whether you created the order.
All shop endpoints require an API key or session belonging to the shop's owner, and take a
shopId identifying which of your shops the call applies to.Applying to become a shop
POST /api/v1/shop/apply
json{"name": "Camden Print Co","addressLine1": "12 Camden High Street","addressLine2": "","city": "London","postcode": "NW1 7JE","phone": "+44 20 7946 0958","acceptsPickup": true,"acceptsPost": true}
Creates a shop application (
201 Created). Shops must be approved before they can receive orders — see GET /api/v1/shops, which only lists approved shops.Listing orders routed to your shop
GET /api/v1/shop/orders?shopId={shopId}
Returns orders currently assigned to your shop, scoped to the current mode (test or live, based on your key or dashboard mode toggle):
json{ "orders": [ /* PrintOrder[] */ ] }
Getting a single order
GET /api/v1/shop/orders/{id}
Same
PrintOrder shape as the customer-facing GET /api/v1/orders/{id}. Returns 403 Forbidden if the order isn't assigned to one of your shops, or if the order's isTest flag doesn't match your key's mode.Updating order status
POST /api/v1/shop/orders/{id}/status
json{ "toStatus": "printing", "note": "Queued on printer 2" }
| Field | Type | Required | Notes |
|---|---|---|---|
toStatus | "printing" | "printed" | "ready_for_pickup" | "mailing" | "posted" | "completed" | yes | Must be a valid transition from the order's current status (see below). |
note | string | no | Optional internal note, stored with the status change. |
trackingCarrier | "royal_mail" | "dpd" | "evri" | "parcelforce" | "other" | no | post orders only. Send together with trackingNumber. |
trackingNumber | string | no | 4-40 letters, numbers or dashes; spaces are removed and letters upper-cased. Send together with trackingCarrier. |
Tracking is optional, because not every postage class is tracked. When both fields are sent, the customer sees the carrier, the number and a link to the carrier's tracking page (not for
other). Sending only one of them returns 400 Bad Request, as does sending tracking for a pickup order. The tracking link is built for you; it cannot be supplied.json{ "toStatus": "posted", "trackingCarrier": "royal_mail", "trackingNumber": "AB123456789GB" }
Valid transitions
A shop can move an order forward to any later stage of its flow (so a batch that is already printed and posted can skip straight ahead). Moving backwards, or to a stage that isn't part of the order's flow, returns
400 Bad Request.| Order type | Stages, in order |
|---|---|
| Pickup | sent_to_shop → printing → printed → ready_for_pickup |
| Post | sent_to_shop → printing → printed → mailing → posted → completed |
A pickup order's final step,
completed (shown as "Picked up"), happens only when the customer's pickup code is verified in the shop dashboard. It can't be set through this endpoint.paid → sent_to_shop happens automatically when LetterAPI routes the order to your shop — it's not something you trigger.Moving to
printing or posted fires the corresponding webhook (order.printing, order.posted) to the customer's webhook endpoint — see Webhooks.Managing pricing
GET /api/v1/shop/price-rules?shopId={shopId}PUT /api/v1/shop/price-rules?shopId={shopId}
json// PUT body{"paperSize": "A4","colorMode": "bw","fulfillmentType": "post","postageClass": "second","pricePerPagePence": 12,"costPerPagePence": 4,"envelopeCostPence": 30,"doubleSidedSupported": true,"finishingOptions": ["none", "stapled", "hole_punched"],"finishingPricePence": { "stapled": 5, "hole_punched": 5 }}
One rule per
(paperSize, colorMode, fulfillmentType, postageClass) combination — PUT upserts.| Field | Notes |
|---|---|
postageClass | "none" | "first" | "first_signed" | "second" | "second_signed". Post rules only; none means "any class" and is used when a requested class has no rule. Pickup rules are always none. |
costPerPagePence, envelopeCostPence | Optional. Your own costs, stored privately so you can see your margin. They never affect customer prices. |
ruleId | Optional. Pass the ID of an existing rule to edit it, including changing its paper size, colour, category or postage class. The rule moves to the new combination; it returns 400 if another rule already uses it. |
Managing printer capacity
GET /api/v1/shop/printers?shopId={shopId}PUT /api/v1/shop/printers?shopId={shopId}
json// PUT body{"colorMode": "bw","count": 2,"ratedPagesPerHour": 3000,"isActive": true}
Used by LetterAPI's order-routing logic to avoid over-queuing a shop.
Errors
| Status | Meaning |
|---|---|
400 Bad Request | Invalid body, or an invalid status transition. |
401 Unauthorized | Missing/invalid API key or session. |
403 Forbidden | shopId isn't owned by the caller, or an order's mode doesn't match the caller's key mode. |
404 Not Found | Order doesn't exist. |
LetterAPIPrint and post letters in the UK, from one letter to thousands.
© 2026 LetterAPI. All rights reserved.