Appearance
REST API
Read your customers, orders and inventory, create orders, and advance an order's stage, over HTTPS with a bearer token. Everything the API returns belongs to the workspace the token was created in.
Plan requirement
The API is included on the Premium and Enterprise plans. The plan is checked on every request, so a downgrade stops access immediately, even for existing tokens.
Common Uses
| Want | Use |
|---|---|
| Push orders from your own website | POST /v1/orders |
| Advance stages from a warehouse system | POST /v1/orders/{order}/advance |
| Stock levels on another site | GET /v1/inventory |
| Look up a customer or an order from your own system | GET /v1/customers/{id} and GET /v1/orders/{order} |
| React to what happens in the shop | Webhook events |
For anything event driven, subscribe to webhooks. Polling /v1/orders every minute uses your rate limit and still tells you late.
Base URL and Authentication
https://yourshop.printersfriend.com/api/v1Create a token under Insight, then API tokens, and pass it as a bearer token:
bash
curl -H "Authorization: Bearer {token}" \
-H "Accept: application/json" \
https://yourshop.printersfriend.com/api/v1/me
Insight, then API tokens. Every token shows the date it expires.
The token is shown once, under Your new token, with the notice "Token created. Copy it now, it will not be shown again." Copy it before leaving the page.
Every request is checked for a valid token, an active user, the workspace the token belongs to, and whether the plan includes the API. Tokens expire after 365 days by default; the API tokens page shows the date next to each one.
Rate Limits
| Endpoint group | Limit |
|---|---|
| Reads | 60 requests per minute |
| Writes | 20 requests per minute |
Keyed per user. A request over the limit answers 429; wait before retrying.
Endpoints
| Method | Path | Purpose |
|---|---|---|
| GET | /v1/me | The authenticated user: id, name, email |
| GET | /v1/customers | List customers |
| GET | /v1/customers/{id} | One customer, with contacts |
| GET | /v1/orders | List orders |
| GET | /v1/orders/{order} | One order, with lines, stage history, invoices and artwork |
| POST | /v1/orders | Create an order |
| POST | /v1/orders/{order}/advance | Advance an order's stage |
| GET | /v1/inventory | List SKUs with stock |
{id} and {order} are the numeric ids the list and create responses return. Document numbers such as ORD-2026-0001 are not accepted in a path.
GET /v1/customers
| Query parameter | Effect |
|---|---|
search | Case-insensitive name match |
Paginated, 50 per page. Each row:
json
{
"id": 1,
"name": "Northside Football Club",
"slug": "northside-fc",
"price_list": "Club rate",
"discount": "12.00",
"orders_count": 4,
"open_orders": 2
}GET /v1/customers/
Adds lifetime value, payment terms, default carrier, the price list and contacts:
json
{
"id": 1,
"name": "Northside Football Club",
"slug": "northside-fc",
"lifetime_value_cents": 924000,
"price_list": { "name": "Club rate", "discount_percent": "12.00" },
"payment_terms": "net_30",
"default_carrier": "Australia Post",
"contacts": [
{ "name": "Megan Cole", "email": "megan@northsidefc.au", "phone": "+61408221559", "is_primary": true }
]
}GET /v1/orders
| Query parameter | Effect |
|---|---|
stage | Filter by stage code, for example in_production |
customer | Filter by customer slug |
Paginated, 50 per page. Each row:
json
{
"id": 12,
"number": "ORD-2026-0012",
"stage": "in_production",
"substage": "Press 2",
"due_by": "2026-10-20T00:00:00+00:00",
"dispatched_at": null,
"carrier": null,
"tracking_number": null,
"totals": { "subtotal_cents": 100000, "tax_cents": 10000, "total_cents": 110000, "currency": "AUD" },
"customer": { "id": 1, "name": "Northside Football Club", "slug": "northside-fc" },
"lines": [
{ "description": "Gildan 5000 black L, front print 1 colour", "quantity": 120, "unit_price_cents": 833, "line_total_cents": 100000 }
]
}GET /v1/orders/
The same shape plus stage_history (each move with from, to and at), invoices (number, status, total) and artwork.
POST /v1/orders
json
{
"customer_id": 1,
"due_by": "2026-10-20",
"notes": "Rush job, confirmed with Megan",
"lines": [
{ "description": "Gildan 5000 black L, front print 1 colour", "quantity": 120, "unit_price_cents": 833 }
]
}| Field | Rules |
|---|---|
customer_id | Required. An existing customer id |
lines | Required, at least one |
lines[].description | Required, up to 255 characters |
lines[].quantity | Required, 1 to 10,000 |
lines[].unit_price_cents | Required, 0 or more |
due_by | Optional date |
notes | Optional, up to 2,000 characters, stored as internal notes |
The order is created in the Artwork stage with the next order number, and tax is applied from your tax scheme.
API orders are priced by you
unit_price_cents is taken as given. The pricing rules that price a quote built in the workspace do not re-price an API line. For engine pricing, build a quote in the workspace and convert it; use the API for orders whose prices the calling system has already decided.
A create that would exceed your plan's open order limit answers 422 with the reason in message.
POST /v1/orders/{order}/advance
json
{ "to_stage": "in_production" }to_stage is one of artwork, print_queue, in_production, qc_pack or dispatched. Cancelling is not available through the API. The move fires the same side effects as the workspace: customer messages, stock consumption, the invoice on dispatch, outbound webhooks and stage history. A move the workspace would refuse answers 422 with the reason.
GET /v1/inventory
| Query parameter | Effect |
|---|---|
low | Only SKUs at or under their reorder point |
search | Case-insensitive code match |
Paginated, 100 per page:
json
{
"code": "GIL-5000-BLK-L",
"style": "5000 Heavy Cotton Tee",
"brand": "Gildan",
"colour": "Black",
"colour_hex": "#000000",
"size": "L",
"on_hand": 145,
"reserved": 24,
"available": 121,
"reorder_point": 60,
"status": "ok"
}Errors
| Code | Meaning |
|---|---|
| 401 | Missing, invalid or expired token |
| 403 | The user behind the token has been deactivated |
| 404 | Not found, or belongs to another workspace |
| 422 | Validation failed, a plan limit was hit, or the plan does not include the API. Read message |
| 429 | Rate limited. Back off |
Related Pages
- API tokens: create and revoke tokens, and the one year expiry
- Webhook events: the five signed events and how to verify a delivery
- Plans and limits: which plans include the API, and the open order limit a create counts against