/v1/qr-tags
Create QR tags
Creates QR tags and returns the text to print in each QR code. Printago mints every tag ID and the batch ID; you only name the targets, or ask for a batch of blank tags, which aren't saved. Needs the QR Workflows add-on. Send targets or blankCount, not both; neither or both is a 400.
A target is one of:
| entityType | Other fields | The tag stands for |
|---|---|---|
printer |
entityId: the printer ID |
A printer. |
printer_slot |
entityId: the printer ID, amsIndex, slotIndex |
One filament slot of a printer: amsIndex is the AMS (or MMU, CFS) unit from 0, or -1 for an external spool holder, and slotIndex the slot in it from 0. The app names it A1/3 (AMS 1, slot 3) or S1 (first external holder). |
material_variant |
entityId: the variant ID |
One color of a material. |
material_instance |
entityId: the spool (material instance) ID |
One physical spool. The tag's displaySnapshot.code is the spool's label code, when it has one. |
Blank tags
blankCount (1 to 300) returns that many blank tags to print, and saves none of them: printing blanks changes nothing in the store, and a blank isn't listed by GET /v1/qr-tags until it is set up. A blank tag stands for nothing yet: its status and entityType are blank, its entityId is null, and displaySnapshot.code is a short code such as 7F3K-9QXP to print on it, so two blanks can be told apart. The code is read from the tag's ID, so the same ID always has the same code. A blank is set up by its first pair scan in the Printago app: scanned with a printer, a slot, a filament or a spool, POST /v1/qr-tags/execute saves it as the tag for that target, active; scanned with a filament, the default action registers a new spool with it. A blank's payload is a link to its page on Printago on the web, so a phone's camera opens it there, or in the Printago app when it's installed, and the tag can be set up by picking what it's for instead of scanning it (a resolve and execute with a target). A blank keeps its printed link after it is set up; its tag record says what it is now. Open /qr-tags/print?blanks=<count> in Printago on the web to print blanks.
New tag IDs use the registered PTG- prefix followed by 20 Crockford Base32 characters. A link names no store: an unclaimed tag can be set up by the first store that claims it, and a tag another store owns returns 404 unknown_tag from execute, the same as a tag that doesn't exist; an unregistered issuer prefix returns 400 unknown_issuer. See QR tag link format.
A slot tag names the slot by its position, not by a slot row, so it keeps working when a printer that reports its AMS at runtime (Bambu Lab, Creality CFS) recreates its slots. The slot must be one the printer has now, from its capabilities, its slots, or what it reports; any other slot is a 400 with code: "unknown_slot", and no tags are created. Send every slot of a printer in one request to print a full set.
By default (reuseActive: true) a target that already has an active tag gets that tag back, so printing a sheet again produces the same QR codes and earlier labels keep working. Send reuseActive: false to mint a fresh tag for every target, for example when a label was damaged. Revoke the old tag with PATCH /v1/qr-tags/revoke so it stops working.
Each target appears once in the response, in request order, even if you list it twice. Any other entityType is a 400. If a printer, variant or spool isn't in your store the whole request is a 404 whose message lists the missing IDs, and no tags are created.
Payload format
Every newly minted tag returns an uppercase HTTPS link in payload, regardless of tag type:
HTTPS://APP.PRINTAGO.IO/QR/PTG-0123456789ABC8RT5N6V
The path contains the tag ID. Labels printed before October 2026 also carry their store (/S:{STOREID}), which is still read. The server's tag record determines its type and target. label is the short code from the last eight ID characters, such as C8RT-5N6V; a tag for an existing spool keeps the spool's label code. Batch IDs and entity IDs remain unchanged in format.
Encode the returned payload byte for byte, including its case. Reusing a tag with a legacy ID returns a query link, /qr/{id}?v=1&t={type}&s={storeId}. Readers accept query links as well as path links. See QR tag link format for optional fields and decoding rules.
Send scanned text straight to POST /v1/qr-tags/resolve instead of parsing it yourself. Resolve checks the version, the store, whether the tag was revoked and whether its target still exists, and returns what the scanner can do now.
Request Body
Targets to tag and whether to reuse active tags, or a count of blank tags
Return this many blank tags (1 to 300) to print instead of tagging targets. Nothing is saved.
Return each target's newest active tag instead of minting another. Defaults to true.
Example Request
{
"targets": [
{
"entityType": "printer",
"entityId": "string"
}
],
"blankCount": 1,
"reuseActive": true
}
Examples
Tag two printers
The first printer already had an active tag, so it comes back unchanged; the second is minted in this request's batch.
{
"targets": [
{
"entityType": "printer",
"entityId": "p7h2j4k6m8n0q2r4s6t8v0wa"
},
{
"entityType": "printer",
"entityId": "q1w3e5r7t9y1u3i5o7p9a1sd"
}
]
}
{
"batchId": "b4t8c2h6x0z4v8n2m6k0j4ha",
"tags": [
{
"id": "PTG-0123456789ABC8RT5N6V",
"status": "active",
"entityType": "printer",
"entityId": "p7h2j4k6m8n0q2r4s6t8v0wa",
"label": "C8RT-5N6V",
"batchId": "a9s8d7f6g5h4j3k2l1z0x9cv",
"payloadVersion": 1,
"payload": "HTTPS://APP.PRINTAGO.IO/QR/PTG-0123456789ABC8RT5N6V"
},
{
"id": "PTG-0123456789AB7F3K9QXP",
"status": "active",
"entityType": "printer",
"entityId": "q1w3e5r7t9y1u3i5o7p9a1sd",
"label": "7F3K-9QXP",
"batchId": "b4t8c2h6x0z4v8n2m6k0j4ha",
"payloadVersion": 1,
"payload": "HTTPS://APP.PRINTAGO.IO/QR/PTG-0123456789AB7F3K9QXP"
}
]
}
Tag every slot of a printer, and a filament color
{
"targets": [
{
"entityType": "printer_slot",
"entityId": "p7h2j4k6m8n0q2r4s6t8v0wa",
"amsIndex": 0,
"slotIndex": 0
},
{
"entityType": "printer_slot",
"entityId": "p7h2j4k6m8n0q2r4s6t8v0wa",
"amsIndex": 0,
"slotIndex": 1
},
{
"entityType": "printer_slot",
"entityId": "p7h2j4k6m8n0q2r4s6t8v0wa",
"amsIndex": 0,
"slotIndex": 2
},
{
"entityType": "printer_slot",
"entityId": "p7h2j4k6m8n0q2r4s6t8v0wa",
"amsIndex": 0,
"slotIndex": 3
},
{
"entityType": "printer_slot",
"entityId": "p7h2j4k6m8n0q2r4s6t8v0wa",
"amsIndex": -1,
"slotIndex": 0
},
{
"entityType": "material_variant",
"entityId": "v2n4m6b8c0x2z4a6s8d0f2gh"
}
]
}
{
"batchId": "c5v7b9n1m3q5w7e9r1t3y5ua",
"tags": [
{
"id": "PTG-0123456789ABABCD2345",
"status": "active",
"entityType": "printer_slot",
"entityId": "p7h2j4k6m8n0q2r4s6t8v0wa",
"slotAmsIndex": 0,
"slotSlotIndex": 0,
"label": "ABCD-2345",
"displaySnapshot": {
"name": "X1C-01",
"provider": "Bambu",
"slotRef": "A1/1",
"code": "ABCD-2345"
},
"payload": "HTTPS://APP.PRINTAGO.IO/QR/PTG-0123456789ABABCD2345"
},
{
"id": "PTG-0123456789ABEFGH6789",
"status": "active",
"entityType": "material_variant",
"entityId": "v2n4m6b8c0x2z4a6s8d0f2gh",
"label": "EFGH-6789",
"displaySnapshot": {
"name": "Black",
"materialName": "PLA Basic",
"brand": "Bambu Lab",
"color": "#000000FF",
"code": "EFGH-6789"
},
"payload": "HTTPS://APP.PRINTAGO.IO/QR/PTG-0123456789ABEFGH6789"
}
]
}
A batch of blank tags
{
"blankCount": 2
}
{
"batchId": "zt2xuatx1n9w7jsrihpojftt",
"tags": [
{
"id": "PTG-0123456789ABJKMN2345",
"status": "blank",
"entityType": "blank",
"entityId": null,
"label": "JKMN-2345",
"displaySnapshot": {
"code": "JKMN-2345"
},
"boundAt": null,
"payload": "HTTPS://APP.PRINTAGO.IO/QR/PTG-0123456789ABJKMN2345"
},
{
"id": "PTG-0123456789ABNPQR6789",
"status": "blank",
"entityType": "blank",
"entityId": null,
"label": "NPQR-6789",
"displaySnapshot": {
"code": "NPQR-6789"
},
"boundAt": null,
"payload": "HTTPS://APP.PRINTAGO.IO/QR/PTG-0123456789ABNPQR6789"
}
]
}
Tag a spool
The spool was registered from a blank, so its existing tag comes back: set up, with the blank's code.
{
"targets": [
{
"entityType": "material_instance",
"entityId": "z5y7mec4haa3tnu7xzqbwamd"
}
]
}
{
"batchId": "m98bnxulntrqpazg0fxhocpk",
"tags": [
{
"status": "active",
"entityType": "material_instance",
"entityId": "z5y7mec4haa3tnu7xzqbwamd",
"label": "HY7Y-MFHE",
"displaySnapshot": {
"code": "HY7Y-MFHE",
"name": "Jade White",
"brand": "Bambu Lab",
"color": "#E8EFE8FF",
"materialName": "PLA Basic"
},
"boundAt": "2026-09-30T04:33:01.192Z",
"payload": "HTTPS://APP.PRINTAGO.IO/QR/PTG-0123456789ABHY7YMFHE",
"id": "PTG-0123456789ABHY7YMFHE"
}
]
}
A slot the printer doesn't have
{
"targets": [
{
"entityType": "printer_slot",
"entityId": "q1w3e5r7t9y1u3i5o7p9a1sd",
"amsIndex": 0,
"slotIndex": 0
}
]
}
{
"statusCode": 400,
"code": "unknown_slot",
"message": "Prusa XL has no slot A1/1 (amsIndex 0, slotIndex 0)"
}
Response Schema
The batch the tag was minted in. Reused tags keep the batch they were first minted in.
The short code printed on a blank tag, read from its ID. It stays after the blank is set up, and a spool registered with the blank takes it as its label code.
The text to encode in the QR code. See the payload format above.
Example Response
{
"batchId": "string",
"tags": [
{
"payload": "string",
"id": "string",
"status": "blank",
"entityType": "blank",
"entityId": "string",
"printerDeviceId": "string",
"slotAmsIndex": 1,
"slotSlotIndex": 1,
"label": "string",
"batchId": "string",
"payloadVersion": 1,
"displaySnapshot": {
"name": "string",
"provider": "string",
"slotRef": "string",
"materialName": "string",
"brand": "string",
"color": "string",
"code": "string"
},
"printedAt": "2025-01-15T12:00:00.000Z",
"boundAt": "2025-01-15T12:00:00.000Z",
"boundBy": "string",
"revokedAt": "2025-01-15T12:00:00.000Z",
"revokedBy": "string",
"lastScannedAt": "2025-01-15T12:00:00.000Z",
"scanCount": 1,
"storeId": "string",
"updatedAt": "2025-01-15T12:00:00.000Z",
"createdAt": "2025-01-15T12:00:00.000Z"
}
]
}