Developer documentation

PrinterFlo API reference

The PrinterFlo REST API gives you programmatic access to your shop's jobs, customers, invoices, and payments. It powers our Zapier integration and is available to every PrinterFlo account at no extra cost.

Base URL

https://app.printerflo.com/api/v1

All requests and responses are JSON. All timestamps are ISO-8601 with timezone (e.g. 2026-07-12T09:20:08.548+00:00). Money is in integer cents with a currency field.

Authentication

Create an API key in PrinterFlo under Settings → API & Zapier. Keys look like pfl_live_… and are shown once at creation. Send the key as a bearer token on every request:

curl https://app.printerflo.com/api/v1/me \
  -H "Authorization: Bearer pfl_live_YOUR_KEY"

Missing, unknown, or revoked keys get 401 with { "error": "Invalid or missing API key." }. Every key is scoped to one shop; you only ever see your own data. Keys can be revoked any time from the same settings screen.

Rate limits

120 requests per minute per key. Exceeding it returns 429 with a Retry-After: 60 header — back off and retry.

Common parameters

Endpoints

Method Path Purpose
GET /me Verify a key; returns your shop
GET /jobs List orders & quotes
POST /jobs Create a quote
GET /customers List / search customers
POST /customers Find-or-create a customer
GET /invoices List invoices
GET /payments List payments

GET /me

The cheapest auth check. Returns the shop the key belongs to.

{ "org": { "id": "…", "name": "Demo Shop", "currency": "cad" } }

GET /jobs

Orders and quotes, newest first. Filters: ?kind= (quote or sales_order), ?status= (your shop's own stage names), ?since=, and ?updated_since= (records updated at/after a timestamp — how our Zapier order-status trigger polls).

{
  "jobs": [
    {
      "id": "…",
      "number": 42,
      "title": "Banner 3ft x 6ft",
      "kind": "sales_order",
      "status": "processing",
      "source": "staff",
      "po_number": null,
      "total_cents": 36160,
      "currency": "cad",
      "created_at": "2026-07-12T09:20:08+00:00",
      "updated_at": "2026-07-12T10:27:06+00:00",
      "customer": { "id": "…", "name": "Reid Auto Group", "company": null, "email": "…", "phone": "…" },
      "url": "https://app.printerflo.com/jobs/…"
    }
  ]
}

POST /jobs

Creates a quote — the safe entry point for automations: your shop prices and confirms it before anything is billed or produced. The customer is deduped by email within your shop. Returns 201 with the created job.

POST /api/v1/jobs
{
  "title": "Storefront window decals",        // required
  "description": "From website lead form",     // optional
  "po_number": "PO-1234",                      // optional
  "kind": "quote",                             // "quote" (default) or "order" — an order lands on the board
  "customer": {
    "name": "Jane Smith",                      // required
    "email": "jane@example.com",               // used for dedupe
    "phone": "555-0101",
    "company": "Smith Realty"
  },
  "line_items": [                              // optional, up to 50
    {
      "description": "3x6 ft vinyl banner",    // required per line
      "quantity": 2,                           // default 1
      "unit_price": 149,                       // dollars, default 0
      "width": 72, "height": 36, "dimension_unit": "in",
      "artwork_url": "https://files.example.com/banner.pdf",   // https; PDF/PNG/JPG/TIFF/SVG/AI/EPS/PSD, 50 MB max
      "artwork_approved": true                 // customer already signed off: skips proofing, cleared for production
    }
  ]
}

Each line comes back with its id and, where an artwork_url was given, the stored artwork path or an artwork_error. Without artwork_approved the line waits for a proof to be sent and approved, exactly as if a staff member had uploaded it.

POST /jobs/{id}/artwork

Attach artwork to a line of an existing job. Send JSON with a file URL, or multipart with the file itself. line_item_id can be omitted when the job has a single line. Returns 201.

POST /api/v1/jobs/{id}/artwork
Content-Type: application/json
{ "line_item_id": "…", "url": "https://files.example.com/banner.pdf", "approved": false }

POST /api/v1/jobs/{id}/artwork
Content-Type: multipart/form-data
file=<binary>  line_item_id=…  approved=true

201 Created
{ "ok": true, "job_id": "…", "line_item_id": "…", "artwork": "…/items/…/1758…​.pdf", "bytes": 482113, "status": "waiting_proof" }

The response wraps the job:

201 Created
{
  "job": {
    "id": "…", "number": 1042, "title": "Storefront window decals",
    "kind": "quote", "status": "pending", "source": "api",
    "po_number": "PO-1234", "total_cents": 0, "currency": "cad",
    "created_at": "…", "updated_at": "…",
    "customer": { "id": "…", "name": "Jane Smith", "company": "Smith Realty", "email": "…", "phone": "…" },
    "url": "https://app.printerflo.com/jobs/…"
  }
}

GET /customers

Filters: ?email= (exact match, case-insensitive), ?q= (searches name, company, and email), ?since=. Returns id, name, company, email, phone, payment_terms, created_at.

POST /customers

Find-or-create by email: if a customer with the given email exists, it's returned with "existing": true instead of creating a duplicate. Body: name (required), email, phone, company, notes.

{ "customer": { "id": "…", "name": "Jane Smith", … }, "existing": false }

GET /invoices

Filters: ?status=, ?since=. Each invoice includes its totals in cents (subtotal_cents, tax_cents, discount_cents, total_cents, amount_paid_cents), dates (issued_at, due_date, created_at), and the linked job and customer. Note that the embedded job carries job_number, which is the same value the top-level /jobs list calls number.

GET /payments

Filter: ?since=. Each payment has amount_cents, currency, method (e.g. stripe, cash, cheque, terminal), reference, paid_at, created_at, and the linked job with its customer.

Errors

Questions or a missing endpoint?

Email support@printerflo.com — we typically reply within one business day. Prefer no-code? The same API powers our Zapier integration.

ResolutionWire — prediction market receipts · a Base 7 Studio project