Developers

REST API and webhooks.

Bearer-token auth, rate-limited, signed webhooks. This page documents exactly what ships today, nothing more.

Authentication

All API requests use Laravel Sanctum personal access tokens. Create one in the admin under API tokens, copy it once, and pass it as a bearer token in the Authorization header. Tokens act with the full permissions of the user who created them, so create them from an account with the access you want the integration to have. Revoking a token from the admin takes effect immediately, and deactivating the staff member who owns it cuts API access at the same time.

curl https://printersfriend.com/api/v1/me \
  -H "Authorization: Bearer <your-token>"

Base URL and versioning

The base URL is https://printersfriend.com/api/v1. The API is versioned by URL prefix; breaking changes will ship in a new major version. There is no separate sandbox environment today: use a trial workspace for testing.

Errors

Errors follow the standard Laravel JSON format: 401 unauthenticated, 403 forbidden, 404 not found, 422 validation, 429 rate limited, 5xx server.

// 422 example
{
  "message": "The given data was invalid.",
  "errors": {
    "to_stage": ["The selected to stage is invalid."]
  }
}

Pagination

List endpoints use standard Laravel page-number pagination. Pass ?page=2 to move through results. Responses include the usual current_page, last_page, total and links fields. Page size is fixed per endpoint: 50 for customers and orders, 100 for inventory.

Rate limits

60 requests per minute on read endpoints, 20 per minute on write endpoints, counted per authenticated user. Hitting a limit returns 429 with a Retry-After header in seconds.

Me

GET/api/v1/me

Returns the id, name and email of the user the token belongs to. Useful as a connectivity check.

Customers

GET/api/v1/customers

List customer organisations, ordered by name, 50 per page.

ParamTypeDescription
searchstringFilter by organisation name
pageintegerPage number
{
  "data": [
    {
      "id": 12,
      "name": "Northside FC",
      "slug": "northside-fc",
      "price_list": "Club rate",
      "discount": 10,
      "orders_count": 14,
      "open_orders": 2
    }
  ],
  "current_page": 1,
  "last_page": 1,
  "total": 1
}

GET/api/v1/customers/{id}

Get a single organisation with contacts, price list, payment terms, default carrier and lifetime paid invoice value (lifetime_value_cents).

Orders

GET/api/v1/orders

List orders, newest first, 50 per page, with lines and customer attached.

ParamTypeDescription
stagestringartwork, print_queue, in_production, qc_pack, dispatched, cancelled
customerstringFilter by customer organisation slug
pageintegerPage number

GET/api/v1/orders/{id}

Get a single order with lines, totals (including currency), stage history and invoices.

POST/api/v1/orders

Create an order in the artwork stage. Body: { "customer_id": 12, "lines": [{ "description": "Crew tee, 2 col front", "quantity": 60, "unit_price_cents": 1850 }], "due_by": "2026-07-01", "notes": "Club drop 3" }. Totals and tax are calculated server side.

POST/api/v1/orders/{id}/advance

Advance an order to another production stage. Body: { "to_stage": "in_production" }. Fires the standard side effects: customer comms, stock consumption, and the order.stage_changed or order.dispatched webhook.

Inventory

GET/api/v1/inventory

List SKUs with live stock, 100 per page. Each SKU includes on_hand, reserved, available, reorder_point and a status field.

ParamTypeDescription
searchstringFilter by SKU code
lowbooleanOnly SKUs at or below their reorder point
pageintegerPage number

Webhooks: Subscribing

Create a subscription in the admin under Webhook subscriptions. Each subscription has a target URL, an event list (or * for all events), and a per-subscription signing secret generated on create. Subscriptions can optionally be scoped to a single customer organisation.

Events

Two events are delivered today, both fired when an order moves stage:

The payload carries order_number, from_stage, to_stage, customer and due_by. The subscription form also lists invoice.issued, invoice.paid and artwork.approved: you can subscribe to them now, and delivery for those events is planned. We will update this page when they go live.

Signatures

Every webhook POST includes:

Verify with:

// Node.js verification
const crypto = require('crypto');
const sig = req.headers['x-pf-signature'];
const expected = 'sha256=' + crypto.createHmac('sha256', secret).update(req.rawBody).digest('hex');
if (!crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected))) {
  return res.status(401).end();
}

Retries

If your endpoint returns anything other than 2xx, we retry with exponential backoff: 1m, 5m, 15m, 1h, 4h (5 attempts in total). Every delivery attempt is recorded with its response status, visible in the admin. After 20 consecutive failures the subscription is deactivated; re-enable it from the admin once your endpoint is healthy.

SDKs

There are no official SDKs yet. The API is plain REST with bearer-token auth, so any HTTP client works out of the box. Email developers@printersfriend.com to tell us which language you want first.

Need help?

API questions go to developers@printersfriend.com with response within 1 business day. Status and incidents live on the status page.