This reference documents the Print-queue API that a print station polls to pick up finished print orders: GET /api/v1/print-queue to claim a page of orders, POST /api/v1/print-queue/:print_id/ack to acknowledge each one, and GET /api/v1/print-queue/:print_id to re-read a single order. It is written for whoever builds or operates the poller.
The queue fills up when a Templified user fires a Send To preset that contains a GFITpro Auto Print destination. Each finished render is parked in the queue under the user's main event code (or the routing code of the Print Location they selected). Your poller drains it.
Base URL
The API is served from the same host as the Templified web app — there is no separate API host.
- Production:
https://studio.templified.io - Staging:
https://studio.staging.templified.io
All endpoints below live under /api/v1/print-queue.
Authentication
These routes do not use a Templified API key. They authenticate with the org's GFIT key — the same key the owner pasted into Templified when connecting GFcrew — sent as a Bearer token on every request:
Authorization: Bearer YOUR_GFIT_KEY
The server resolves the key to an organization, so no org identifier is needed in the request. Disconnecting GFcrew in Templified invalidates the key immediately.
| Status | error.code |
Meaning |
|---|---|---|
401 |
missing_authorization |
No Authorization header was sent. |
401 |
invalid_authorization |
The header is not in Bearer <key> form, or the token is empty. |
401 |
invalid_credentials |
The key does not match any connected organization. |
Every error response on these routes uses the shape { "error": { "code": "…", "message": "…" } }.
GET /api/v1/print-queue — claim a page of orders
A poll is not a read: it atomically claims the next page of waiting orders and leases them to you. Orders you claim disappear from every other poll until your lease expires. Each claimed order comes back with a claim_token you must echo on the acknowledge call.
Query parameters
| Parameter | Required | Default | Description |
|---|---|---|---|
main_event_code |
Yes | — | The event code (or Print Location routing code) to drain. Missing or blank returns 400 missing_main_event_code. |
destination_event_code |
No | — | Narrows the page further within the main event code. Omit to receive every destination code under the main code. |
page_size |
No | 25 |
Maximum orders per poll. Clamped to 100; any value below 1 or unparseable falls back to 25. |
claimed_by |
No | synthesized | This machine's identity. Maximum 64 characters — longer values are truncated to 64. See below. |
claimed_by — identify the machine, and keep it stable
claimed_by is optional, but you should always send it. It is not just a label: it is the owner of the event lease (see the next section) and the value support uses to trace a claim back to a station.
Send a value that is stable for the life of the machine and distinct per machine — a hostname, a printer name, or a device id you persist on disk. Do not generate it per poll, per process start, or per session. The server identifies a poller by this string, so a value that changes between polls is treated as a different machine, and your second poll will be refused as if a second station had appeared.
Values longer than 64 characters are truncated to 64 before use — so two long ids sharing a 64-character prefix collapse to the same identity. Keep it short.
If you omit claimed_by, the server synthesizes one for you from the request's source IP and a short hash of the User-Agent header, in the form auto:<ip>:<ua-hash>. That is a usable fallback for a single station, but it is not a safe identity: two machines behind the same NAT running the same app version produce the same synthesized value, so the queue will treat them as one poller and let both drain it. Send an explicit claimed_by and the synthesized value is never used.
Example
GET /api/v1/print-queue?main_event_code=1408&claimed_by=FRONTDESK-01&page_size=25
Authorization: Bearer YOUR_GFIT_KEY
HTTP/1.1 200 OK
{
"page_size": 25,
"items": [
{
"print_id": "cmp7a1b2c0001abcd",
"main_event_code": "1408",
"destination_event_code": "9001",
"player_id": "12",
"file_url": "https://cdn.templified.io/org_abc/renders/abcd1234.jpg",
"qty": 2,
"design_name": "8x10 Team Sheet",
"template_name": "Team Sheets — Spring 2026",
"preset_name": "Print 8x10",
"instance_id": "cmp7000xxxx",
"created_at": "2026-07-21T19:23:11.123Z",
"claim_token": "8f9e7d6c-5b4a-3210-9f8e-7d6c5b4a3210",
"claim_expires_at": "2026-07-21T19:24:11.456Z",
"attempt_count": 1
}
]
}
| Field | Type | Description |
|---|---|---|
print_id |
string | Stable identifier for this print order. Never reused. Use it to dedupe. |
main_event_code |
string | The code this order was queued under. |
destination_event_code |
string or null | Destination code, when one was set. |
player_id |
string or null | Player identifier carried from the design or render, when present. |
file_url |
string | URL of the finished image to print. |
qty |
number | Number of copies to print. One order, N copies — not N orders. |
design_name |
string | Name of the design that produced this order. |
template_name |
string | Name of the template the design was built from. |
preset_name |
string or null | Name of the Send To preset that queued the order, captured at queue time so a later rename does not change it. |
instance_id |
string or null | Identifier of the Studio design behind the order, when there is one. |
created_at |
ISO 8601 string | When the order was queued. Orders are handed out oldest first. |
claim_token |
string | Echo this on the acknowledge call. One token covers the whole page claimed in that poll. |
claim_expires_at |
ISO 8601 string | When this order's lease runs out and it becomes claimable again. |
attempt_count |
number | How many times this order has been claimed. Incremented at claim time, not at acknowledge time — a lease that expires unacknowledged still burns an attempt. |
An empty queue returns 200 OK with "items": []. Poll again shortly.
One poller per event
Only one machine may drain a given main event code at a time. The first caller to poll a code takes a lease on it, keyed to that caller's claimed_by value. While that lease is live, a poll from a different claimed_by on the same code is refused with 409 Conflict and the error code event_already_claimed:
HTTP/1.1 409 Conflict
{
"error": {
"code": "event_already_claimed",
"message": "Event 1408 is already being polled by FRONTDESK-01 (last seen 5s ago). Stop this poller — running a second poller on one code makes them compete for the same prints.",
"claimed_by": "FRONTDESK-01",
"last_seen_at": "2026-07-21T18:23:11.123Z"
}
}
Alongside code and message, the error object carries two extra fields: claimed_by — the identity of the machine that currently holds the event — and last_seen_at, an ISO 8601 timestamp of that machine's most recent poll (null when no recent poll has been recorded). The message is written to be shown to an operator verbatim: it names the machine to go turn off, and appends a "last seen Ns ago" / "last seen Nm ago" hint when a timestamp is available.
The refused caller claims nothing. The lease is taken before any order is handed out, so a rejected poll returns zero orders — two stations can never split an event between them, and can never both print the same order. Refusal is the safe outcome, not a partial one.
What to do when you get it
Stop the poll loop for that code and show the operator the message. Do not retry silently — a retry loop just hammers a locked event. The two fixes are:
-
Stop the duplicate poller. Use the machine named in
claimed_by, and shut the second one down. Once the incumbent stops or dies, its lease lapses and your next poll succeeds. - Point this machine at a different main event code — typically its own Print Location routing code, so each station owns its own code.
The lease is keyed on main_event_code alone. destination_event_code does not get its own lease, so you cannot run two machines against one main event code and split the work by destination code — the second machine is locked out, not partitioned. One poller per code, always.
Refreshing your own lease always succeeds: a poll from the same claimed_by extends the lease rather than colliding with it. This is exactly why the identity must be stable — an identity that changes each poll makes your own station look like an intruder.
Two lease windows — and they are different lengths
There are two independent timers, and confusing them causes real bugs:
| Lease | Window | What it covers |
|---|---|---|
| Event claim (who owns the code) | 90 seconds | Your exclusive ownership of a main_event_code. Refreshed on every poll. When it lapses, any machine may take the code. |
| Per-order claim | 60 seconds | Your hold on each individual order in the page you just claimed, reported per order as claim_expires_at. When it lapses, the order returns to the queue. |
The practical consequence for both: act faster than the window.
- Poll more often than 90 seconds. A poll every 5–15 seconds is the right cadence. Each poll refreshes the event lease, so a station that polls at that rate survives several missed or slow polls without losing its code. Go quiet for more than 90 seconds and another machine can claim the event out from under you.
- Acknowledge each order well inside 60 seconds. Acknowledge as soon as the file download finishes — not after the paper comes out of the printer. The 60-second window is sized for a download plus a small margin, not for physical printing. Miss it and the order reappears on someone's next poll and gets printed twice.
Duplicates are possible by design
Delivery is at-least-once. An order can reappear if your poller died after claiming but before acknowledging, or if your acknowledgement reached the server but its response never reached you and the lease then expired. Dedupe on your side by remembering recently completed print_id values for the last few hours and skipping any re-claim that matches. A genuine reprint — an operator explicitly sending a finished design again — always arrives as a new print_id.
POST /api/v1/print-queue/:print_id/ack — acknowledge one order
Acknowledgement is per order, not per page. Send POST to the order's own path with the token from the claim.
POST /api/v1/print-queue/cmp7a1b2c0001abcd/ack
Authorization: Bearer YOUR_GFIT_KEY
Content-Type: application/json
{
"claim_token": "8f9e7d6c-5b4a-3210-9f8e-7d6c5b4a3210",
"status": "success",
"failure_reason": null
}
HTTP/1.1 200 OK
{ "status": "completed", "print_id": "cmp7a1b2c0001abcd" }
| Field | Type | Required | Description |
|---|---|---|---|
claim_token |
string | Yes | Must match the token returned when the order was claimed. Missing or empty returns 400 missing_claim_token. |
status |
string | No | Exactly "success" or "failed" — no other value is accepted. Omitted defaults to "success". |
failure_reason |
string | No | Free text stored with the order for support. Only meaningful with "failed". |
Response bodies
-
"success"→{ "status": "completed", "print_id": "…" }. The order is done and will not be handed out again. -
"failed"with attempts remaining →{ "status": "requeued", "print_id": "…", "attempt_count": 2 }. The order returns to the queue and will reappear on a later poll. -
"failed"with attempts exhausted (attempt_countalready at 3 or more) →{ "status": "failed", "print_id": "…", "attempt_count": 3 }. The order is permanently failed and will not reappear.
GET /api/v1/print-queue/:print_id — re-read one order
Returns the same order object as the poll endpoint, for the given print_id in your organization. This is a plain read: it does not claim the order, take a lease, or touch attempt_count. Use it to recover the file URL after a network blip without disturbing the queue. An unknown id returns 404 not_found.
Error codes
| Status | error.code |
Endpoint | Meaning and what to do |
|---|---|---|---|
400 |
missing_main_event_code |
Poll |
main_event_code was absent or blank. Fix the request. |
400 |
missing_claim_token |
Acknowledge |
claim_token was absent or empty in the body. |
400 |
invalid_status |
Acknowledge |
status was something other than "success" or "failed". |
401 |
missing_authorization |
All | No Authorization header. |
401 |
invalid_authorization |
All | Header was not Bearer <key>, or the token was empty. |
401 |
invalid_credentials |
All | Unknown GFIT key. Re-check the key, or reconnect GFcrew in Templified. |
404 |
not_found |
Acknowledge, re-read | No order with that print_id in this organization. |
409 |
event_already_claimed |
Poll | Another machine holds the event lease. Nothing was claimed. Stop this poller or move it to a different code — do not retry blindly. |
409 |
claim_mismatch |
Acknowledge | The token does not match the active lease — most often the lease expired and a later poll reclaimed the order. Don't retry the acknowledgement; poll again to re-claim. |
410 |
gone |
Acknowledge | The order is already in a terminal state (completed, cancelled, or permanently failed). Treat as a no-op and move on. |
500 |
internal_error |
All | Server-side failure. Back off and retry. |
Comments
0 comments
Please sign in to leave a comment.