Printago API
POST /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.

Requires: qr.manage

Request Body

Targets to tag and whether to reuse active tags, or a count of blank tags

CreateQrTagsRequest
blankCountoptional
integer

Return this many blank tags (1 to 300) to print instead of tagging targets. Nothing is saved.

min: 1max: 300
reuseActiveoptional
boolean

Return each target's newest active tag instead of minting another. Defaults to true.

targetsoptional
object[]
min items: 1max items: 500

Example Request

application/json
{
  "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.

Request
{
  "targets": [
    {
      "entityType": "printer",
      "entityId": "p7h2j4k6m8n0q2r4s6t8v0wa"
    },
    {
      "entityType": "printer",
      "entityId": "q1w3e5r7t9y1u3i5o7p9a1sd"
    }
  ]
}
Response
{
  "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

Request
{
  "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"
    }
  ]
}
Response
{
  "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

Request
{
  "blankCount": 2
}
Response
{
  "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.

Request
{
  "targets": [
    {
      "entityType": "material_instance",
      "entityId": "z5y7mec4haa3tnu7xzqbwamd"
    }
  ]
}
Response
{
  "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

Request
{
  "targets": [
    {
      "entityType": "printer_slot",
      "entityId": "q1w3e5r7t9y1u3i5o7p9a1sd",
      "amsIndex": 0,
      "slotIndex": 0
    }
  ]
}
Response
{
  "statusCode": 400,
  "code": "unknown_slot",
  "message": "Prusa XL has no slot A1/1 (amsIndex 0, slotIndex 0)"
}

Response Schema

CreateQrTagsResponse
batchIdrequired
string
Shared by every tag minted in this request
pattern: ^[a-z0-9]{24}$
tagsrequired
object[]
batchIdrequirednullable
string

The batch the tag was minted in. Reused tags keep the batch they were first minted in.

pattern: ^[a-z0-9]{24}$
boundAtrequirednullable
string
format: date-time
boundByrequirednullable
string
createdAtrequired
string
format: date-time
displaySnapshotrequirednullable
object
Caption data captured when a tag is created, so a tag still describes its target after the target is deleted.
brandoptional
string
codeoptional
string

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.

coloroptionalnullable
string
#RRGGBBAA, or several joined with ";" for multicolor filament
materialNameoptional
string
Variant tags
nameoptional
string
The printer's name for printer and slot tags; the variant's name for variant and spool tags
provideroptionalnullable
string
slotRefoptional
string
Slot tags: the slot as the app names it, such as A1/3 or S1
entityIdrequirednullable
string
The printerId for printer_slot tags; null while blank. A blank keeps its printed payload (t: 'blank') after it is set up; this row says what it is.
pattern: ^[a-z0-9]{24}$
entityTyperequired
"blank" | "printer" | "printer_slot" | "material_variant" | "material_instance"
blankprinterprinter_slotmaterial_variantmaterial_instance
idrequired
string
pattern: ^([A-Za-z0-9_-]{1,24}|[A-Za-z][A-Za-z0-9]*-[0-9A-Ha-hJ-Kj-kM-Nm-nP-Tp-tV-Zv-z]{20})$
labelrequirednullable
string
lastScannedAtrequirednullable
string
format: date-time
payloadrequired
string

The text to encode in the QR code. See the payload format above.

payloadVersionrequired
number
printedAtrequirednullable
string
format: date-time
printerDeviceIdrequirednullable
string
Printer and slot tags: the printer's device_id when it has one. A tag whose printer was deleted follows the store's printer with this id.
revokedAtrequirednullable
string
format: date-time
revokedByrequirednullable
string
scanCountrequired
number
slotAmsIndexrequirednullable
number
Slot identity for printer_slot tags; amsIndex -1 is the external spool
slotSlotIndexrequirednullable
number
statusrequired
"blank" | "active" | "revoked" | "orphaned"
blankactiverevokedorphaned
storeIdrequired
string
pattern: ^[a-z0-9]{24}$
updatedAtrequired
string
format: date-time

Example Response

201 OK — application/json
{
  "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"
    }
  ]
}