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 payload ("t": "blank") 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.

A blank's ID can be any 1 to 24 letters, digits, - or _, so a code you make yourself, such as https://app.printago.io/qr/shelf-7?v=1&t=blank&s={storeId}, works like one Printago printed. Its ID can't be one another store's tag already uses: setting it up is then a 409 with code: "tag_id_taken".

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 (v1)

payload is compact JSON, 165 characters or fewer, so it scans quickly. A blank tag's is a link instead, with the same keys as query parameters: https://app.printago.io/qr/{id}?v=1&t=blank&s={storeId}, on the web app of the Printago environment that printed it.

Key Meaning
v Payload version, 1. A reader that sees a higher number should ask the user to update rather than guess.
t Entity type of the tag when it was printed: printer, printer_slot, material_variant, material_instance or blank.
id The QR tag ID (qr_tags.id). This is what POST /v1/qr-tags/resolve looks up.
s The store ID the tag belongs to.
d Hints for instant display: {"printerId"} for a printer, {"printerId", "amsIndex", "slotIndex"} for a slot, {"variantId"} for a variant, {"instanceId"} for a spool, {} for a blank. The tag record on the server is the source of truth, so never act on t or d alone.

Encode payload byte for byte; don't reformat it. Text that isn't JSON with a numeric v, or an http(s) link to /qr/{id} with a numeric v in its query, is not a Printago tag.

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": "k3q9x2m8v7c1n4b6z0w5y2ta",
      "status": "active",
      "entityType": "printer",
      "entityId": "p7h2j4k6m8n0q2r4s6t8v0wa",
      "label": "X1C-01",
      "batchId": "a9s8d7f6g5h4j3k2l1z0x9cv",
      "payloadVersion": 1,
      "payload": "{\"v\":1,\"t\":\"printer\",\"id\":\"k3q9x2m8v7c1n4b6z0w5y2ta\",\"s\":\"cm1r8s2t4u6v8w0x2y4z6a8b\",\"d\":{\"printerId\":\"p7h2j4k6m8n0q2r4s6t8v0wa\"}}"
    },
    {
      "id": "r5t7y9u1i3o5p7a9s1d3f5gh",
      "status": "active",
      "entityType": "printer",
      "entityId": "q1w3e5r7t9y1u3i5o7p9a1sd",
      "label": "X1C-02",
      "batchId": "b4t8c2h6x0z4v8n2m6k0j4ha",
      "payloadVersion": 1,
      "payload": "{\"v\":1,\"t\":\"printer\",\"id\":\"r5t7y9u1i3o5p7a9s1d3f5gh\",\"s\":\"cm1r8s2t4u6v8w0x2y4z6a8b\",\"d\":{\"printerId\":\"q1w3e5r7t9y1u3i5o7p9a1sd\"}}"
    }
  ]
}

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": "uyk6qzl3cah7b8nccab3yigm",
      "status": "active",
      "entityType": "printer_slot",
      "entityId": "p7h2j4k6m8n0q2r4s6t8v0wa",
      "slotAmsIndex": 0,
      "slotSlotIndex": 0,
      "label": "A1/1 · X1C-01",
      "displaySnapshot": {
        "name": "X1C-01",
        "provider": "Bambu",
        "slotRef": "A1/1"
      },
      "payload": "{\"v\":1,\"t\":\"printer_slot\",\"id\":\"uyk6qzl3cah7b8nccab3yigm\",\"s\":\"cm1r8s2t4u6v8w0x2y4z6a8b\",\"d\":{\"printerId\":\"p7h2j4k6m8n0q2r4s6t8v0wa\",\"amsIndex\":0,\"slotIndex\":0}}"
    },
    {
      "id": "nvioxp9s1d7o3van7wqhkmtp",
      "status": "active",
      "entityType": "material_variant",
      "entityId": "v2n4m6b8c0x2z4a6s8d0f2gh",
      "label": "PLA Basic · Black",
      "displaySnapshot": {
        "name": "Black",
        "materialName": "PLA Basic",
        "brand": "Bambu Lab",
        "color": "#000000FF"
      },
      "payload": "{\"v\":1,\"t\":\"material_variant\",\"id\":\"nvioxp9s1d7o3van7wqhkmtp\",\"s\":\"cm1r8s2t4u6v8w0x2y4z6a8b\",\"d\":{\"variantId\":\"v2n4m6b8c0x2z4a6s8d0f2gh\"}}"
    }
  ]
}

A batch of blank tags

Request
{
  "blankCount": 2
}
Response
{
  "batchId": "zt2xuatx1n9w7jsrihpojftt",
  "tags": [
    {
      "id": "v61yt345k7x9a5eaw1tuw1vy",
      "status": "blank",
      "entityType": "blank",
      "entityId": null,
      "label": "Q9FJ-2JA4",
      "displaySnapshot": {
        "code": "Q9FJ-2JA4"
      },
      "boundAt": null,
      "payload": "https://app.printago.io/qr/v61yt345k7x9a5eaw1tuw1vy?v=1&t=blank&s=nlxbd7jgazwhsb3yt12fhba8"
    },
    {
      "id": "qvq9w2cmd8j16ijv05rjgq5z",
      "status": "blank",
      "entityType": "blank",
      "entityId": null,
      "label": "KBWP-MR7C",
      "displaySnapshot": {
        "code": "KBWP-MR7C"
      },
      "boundAt": null,
      "payload": "https://app.printago.io/qr/qvq9w2cmd8j16ijv05rjgq5z?v=1&t=blank&s=nlxbd7jgazwhsb3yt12fhba8"
    }
  ]
}

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": "PLA Basic · Jade White",
      "displaySnapshot": {
        "code": "HY7Y-MFHE",
        "name": "Jade White",
        "brand": "Bambu Lab",
        "color": "#E8EFE8FF",
        "materialName": "PLA Basic"
      },
      "boundAt": "2026-09-30T04:33:01.192Z",
      "payload": "…"
    }
  ]
}

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}$
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
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",
      "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"
    }
  ]
}