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
?limit=— page size, default 50, max 100. Results are newest-first.?since=— ISO-8601 timestamp; only records created at/after it (ideal for polling).
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
400— invalid input; body is{ "error": "…" }explaining what's wrong.401— missing, unknown, or revoked API key.429— rate limit exceeded; retry after theRetry-Afterseconds.
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.