Developer documentation

PrinterFlo API reference

The PrinterFlo REST API gives you programmatic access to your shop's jobs, customers, invoices, payments, and proofs. 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.

On the Proofs plan, keys are under Proofs → Settings → API keys. A Proofs plan key reads /me, /customers and /proofs; the order, invoice and payment endpoints answer it with 403.

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 /proofs Find a customer's proofs by email or order
GET /proofs/{token} One proof by its link's token

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.

GET /proofs

Your shop's proofs that have gone to a customer, newest change first. A proof you haven't sent yet is never listed. Built for a store plugin that shows a customer "your proof is ready" with a link to approve it, and for polling with Zapier. Filters, all optional and combined:

status draft on a proof that went out means you're readying a new version; its link still shows the version last sent, which is version. Viewed isn't a status: see viewed_at. Send the customer to url, their page to see and answer the proof. Proof pages can't be shown inside another site's frame, so link to them.

curl "https://app.printerflo.com/api/v1/proofs?order_ref=1047&email=pat@example.com" \
  -H "Authorization: Bearer pfl_live_YOUR_KEY"

{
  "proofs": [
    {
      "id": "…",
      "number": 12,
      "title": "Storefront sign",
      "status": "sent",
      "version": 2,
      "order_ref": "1047",
      "order_id": null,
      "customer": { "name": "Pat Lee", "email": "pat@example.com" },
      "sent_at": "2026-10-29T14:02:11+00:00",
      "viewed_at": null,
      "decided_at": null,
      "decided_by_name": null,
      "created_at": "…",
      "updated_at": "…",
      "token": "…",
      "url": "https://app.printerflo.com/proof/r/…"
    }
  ]
}

GET /proofs/{token}

One proof by its token (the end of its url), in the same shape under "proof". A token that isn't one of your sent proofs answers 404.

POST /proofs

Makes a proof from your files and emails it to the customer, in one call: the same email and proof page as Send on a proof in PrinterFlo. Built for tools that hand files over from elsewhere (a design app, Zapier). Needs Proofs on your account (the Proofs plan, or the Proofs add-on). Body:

Or send the files themselves as multipart/form-data: title, customer_email, customer_name, customer_company, message, order_ref, due, send, and one files part per file, under 4 MB in all per call. The proofs endpoints answer CORS, so a browser tool can call them with your key.

curl https://app.printerflo.com/api/v1/proofs \
  -H "Authorization: Bearer pfl_live_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "title": "Storefront sign",
        "customer": { "email": "pat@example.com", "name": "Pat Lee" },
        "files": [{ "url": "https://files.example.com/storefront.pdf" }],
        "order_ref": "1047" }'

201 { "proof": { "id": "…", "number": 13, "status": "sent", "version": 1, …, "url": "https://app.printerflo.com/proof/r/…",
                 "shop_url": "https://app.printerflo.com/art-proofs/…" } }

If the email can't go out, nothing is made and the call answers 502; retry it. Up to 60 proofs an hour per shop go out this way (then 429), within your daily email limit.

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.