Developers

REST API and webhooks.

Versioned, OpenAPI 3.1, rate-limited, signed webhooks. Built for the integrations the apparel industry actually needs.

Authentication

All API requests use Laravel Sanctum personal access tokens. Create one in Settings → API tokens. Pass it as a bearer token in the Authorization header.

curl https://api.printersfriend.com/v1/me \
  -H "Authorization: Bearer pfat_live_abcd1234..."

Tokens can be scoped to a subset of permissions (read-only, customers-only, orders-write). Rotate them any time. Revoking a token takes effect within 60 seconds.

Base URL and versioning

Production: https://api.printersfriend.com/v1. Sandbox: https://sandbox.api.printersfriend.com/v1.

The API is versioned by URL prefix. Breaking changes ship in a new major version (v2) with at least 12 months of overlap. Additive changes (new fields, new endpoints) happen within a version with no notice.

Errors

Errors follow the standard Laravel JSON validation format. HTTP status codes match RFC 9110: 400 for malformed input, 401 unauthenticated, 403 forbidden, 404 not found, 422 validation, 429 rate limited, 5xx server.

// 422 example
{
  "message": "The given data was invalid.",
  "errors": {
    "email": ["The email must be a valid email address."]
  }
}

Pagination

List endpoints are paginated with cursor-based pagination. Pass ?cursor=<cursor>&per_page=50. The response includes next_cursor and prev_cursor. Default page size is 25, max 100.

Rate limits

60 requests per minute per token on read endpoints. 20 per minute on write endpoints. Burst of 10. Webhooks and bulk endpoints have separate higher limits documented per endpoint. Hitting a limit returns 429 with Retry-After set in seconds.

Customers

GET/v1/customers

List organisations.

ParamTypeDescription
searchstringFilter by name or contact email
price_liststringFilter by price list slug
cursorstringPagination cursor
{
  "data": [
    {
      "id": "org_abc123",
      "name": "Bondi Surf Club",
      "price_list": "club-rate",
      "primary_contact": { "name": "Tess Walker", "email": "tess@bondisurf.au" },
      "ytd_spend_cents": 184500
    }
  ],
  "next_cursor": "eyJpZCI6Im9yZ19hYmMxMjMifQ"
}

GET/v1/customers/{id}

Get a single organisation with contacts, addresses, and pricing rules attached.

Orders

GET/v1/orders

List orders with optional stage filter.

ParamTypeDescription
stagestringartwork, print_queue, in_production, qc_pack, dispatched
customer_idstringFilter to one customer
due_beforeiso dateOnly orders due before this date

GET/v1/orders/{number}

Get a single order with lines, decoration spec, artwork versions and stage history.

POST/v1/orders/{number}/advance

Advance an order to the next production stage. Body: { "to_stage": "in_production" }. Fires order.stage_changed webhook.

Inventory

GET/v1/inventory

List SKUs with live stock. Each SKU has on_hand, reserved, available, incoming, reorder_point.

Webhooks: Subscribing

Create a subscription in the admin (Settings → Webhooks) or via the API. Each subscription has a target URL, an event list, and a per-subscription signing secret.

Events

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, 30m, 2h, 12h. After 5 failed attempts we mark the subscription suspended and email the workspace owner. You can replay any failed delivery from the admin UI for 30 days.

SDKs

Official SDKs (in alphabetical order):

Community SDKs in Go and .NET are linked from the GitHub org. All SDKs are MIT-licensed and follow the same versioning scheme as the API.

Need help?

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