Printago API
POST /v1/qr-tags/execute

Run a scanned tag's action

Runs one action from a POST /v1/qr-tags/resolve response, for the same one or two tags. Needs qr.scan, the QR Workflows add-on and the action's own permission (for example printer.control for Pause, printer.edit for Move). Send the choice an action's param asks for in params (see resolve); a missing one is a 400 with code: "param_required". The live state is checked again before anything is sent, so an action that was available at resolve time can still be refused here. A move or swap writes both slots in one transaction: if either write fails, neither slot changes, and the printers are only told about the new filament once both are written. Each action is recorded in the audit log with source mobile_scan (or web for a blank set up with a target) and the caller as the actor. The response carries the tag's refreshed display and its actions evaluated again after this one ran, so a client can redraw without calling resolve (which would count another scan). Live printer status can lag the command by a few seconds. If the printer can't be read back after the action ran, the action still counts as done: tags is empty and actions is left out, so resolve again with refresh: true to redraw.

To set a blank up for a target from a resolve (something with no tag to scan), send the blank's id alone in tagIds with that target and one of the actions that set a blank up. Any other action, or more tags, is a 400; a target that isn't in the store is a 404 with code: "target_not_found". A blank isn't saved until it is set up, so its ID, read from its QR code, has no tag yet: an action that sets a blank up takes one ID with no tag as that blank. Setting it up saves the tag, only when no tag with that ID exists yet. When two phones set up the same blank at once, one wins and the other gets a 409 with code: "tag_already_set_up"; resolve the tag again to see what it became. Registering a spool with a blank (pair.bindBlankAsNewSpool) sets the blank up first, in the same transaction as the new spool, so the phone that loses creates no spool. After a blank is set up the response's tags holds just that tag, as what it is now, with its own actions. Loading a spool that sits in another slot moves it and frees that slot; loading it into the slot it is already in changes nothing and says so. A spool registered with a blank takes the blank's code as its label code; with the Filament Stock spool tracking add-ons it comes out of the filament's bulk stock when bulk holds its weight, and the result message says so.

Errors carry a machine-readable code next to message:

Status code Meaning
403 no_permission The caller lacks the action's permission.
404 unknown_tag A tag ID isn't in this store. An action that sets a blank up takes one ID with no tag, the blank's, so for it this means neither ID has a tag.
404 target_not_found The target a blank is being set up for isn't in this store.
400 param_required The action needs a choice (a slot, a job, a macro, a weight, ...) and params doesn't carry it.
400 bad_grams grams isn't a number from the action's min to 50,000.
409 state_changed The printer or slots no longer allow the action (it went offline, finished printing, the slots filled up, ...). message says why. Resolve again.
409 slot_gone The picked slot (or printer) is no longer there. Printago never falls back to another slot. Resolve again.
409 tag_revoked, tag_orphaned The tag was revoked, or its target was deleted.
409 tag_already_set_up Another request set this blank tag up first. Nothing was changed by this one.
409 tag_id_taken Another store's tag already has this blank's ID. Print a new blank.
409 idempotency_in_progress A request with this Idempotency-Key is still running.
422 action_not_offered The action is unknown, runs in the app (printer.open), or isn't one these tags offer: a printer action sent with a slot tag is refused, never run on the slot's printer.
422 idempotency_mismatch This Idempotency-Key was already used for a different request.

Send an Idempotency-Key header (any string up to 128 characters) and reuse it when you retry the same tap. A key that already finished returns its first response without running the action again; a key whose attempt failed is freed so the retry runs. Keys expire after 24 hours. Without the header every call runs the action.

Requires: qr.scan

Header Parameters

idempotency-key optional
string
One key per user action, reused when that action is retried. A key that already finished returns its first response without running again.

Request Body

The tag IDs, the action ID and its parameters

ExecuteQrActionRequest
actionrequired
string
An action id from the resolve response, such as printer.pause
paramsoptional
Record<string, string | number | boolean>
Construct a type with a set of properties K of type T
tagIdsrequired
string[]
The scanned tags' ids. A blank that isn't set up yet has no row: send the id from its code.
min items: 1max items: 2
targetoptional
object
The blank's target from a resolve with `target`, for an action that sets the blank up
entityIdrequired
string
The printer's ID
pattern: ^[a-z0-9]{24}$
entityTyperequired
"printer"
amsIndexrequired
integer
The AMS unit, from 0; -1 for an external spool holder
min: -1max: 63
entityIdrequired
string
The printer's ID
pattern: ^[a-z0-9]{24}$
entityTyperequired
"printer_slot"
slotIndexrequired
integer
The slot within the unit, or the external holder, from 0
min: 0max: 63
entityIdrequired
string
The filament variant's ID
pattern: ^[a-z0-9]{24}$
entityTyperequired
"material_variant"
entityIdrequired
string
The spool's (material instance's) ID
pattern: ^[a-z0-9]{24}$
entityTyperequired
"material_instance"

Example Request

application/json
{
  "tagIds": [
    "string"
  ],
  "action": "string",
  "params": {},
  "target": {
    "entityType": "printer",
    "entityId": "string"
  }
}

Examples

Pause a print

Request
POST /v1/qr-tags/execute
Idempotency-Key: 6f1c9a52-0d3e-4a7b-9b8e-3f2d1c0a9e87

{ "tagIds": ["k3q9x2m8v7c1n4b6z0w5y2ta"], "action": "printer.pause" }
Response
{
  "action": "printer.pause",
  "result": {
    "status": "done",
    "message": "Pause sent to X1C-01"
  },
  "tags": [
    {
      "id": "k3q9x2m8v7c1n4b6z0w5y2ta",
      "status": "active",
      "entityType": "printer",
      "entityId": "p7h2j4k6m8n0q2r4s6t8v0wa",
      "label": "X1C-01",
      "printer": {
        "id": "p7h2j4k6m8n0q2r4s6t8v0wa",
        "name": "X1C-01",
        "provider": "Bambu",
        "state": "Busy",
        "statusLabel": "Printing 42%",
        "printing": true,
        "paused": false,
        "progress": 42,
        "jobName": "bracket.3mf"
      }
    }
  ],
  "actions": [
    {
      "id": "printer.stop",
      "label": "Stop",
      "kind": "builtin",
      "runs": "server",
      "permission": "printer.control",
      "default": false,
      "destructive": true,
      "confirm": true,
      "confirmMessage": "Stop the print on X1C-01? It can't be resumed.",
      "available": true
    },
    {
      "id": "printer.open",
      "label": "Open printer",
      "kind": "builtin",
      "runs": "app",
      "permission": "printer.view",
      "default": true,
      "destructive": false,
      "confirm": false,
      "available": true
    }
  ]
}

Assign a variant to the slot picked in the sheet

Request
{
  "tagIds": [
    "u8qaisrqkwr6ib1p1jfjeiov",
    "nvioxp9s1d7o3van7wqhkmtp"
  ],
  "action": "pair.assignVariantToPrinter",
  "params": {
    "amsIndex": 0,
    "slotIndex": 1
  }
}
Response
{
  "action": "pair.assignVariantToPrinter",
  "result": {
    "status": "done",
    "message": "Assigned PLA Matte Ivory White to A1/2 on X1C-01"
  },
  "tags": [
    "…"
  ],
  "actions": [
    "…"
  ]
}

Send the next job

Request
{
  "tagIds": [
    "u8qaisrqkwr6ib1p1jfjeiov"
  ],
  "action": "printer.sendJob",
  "params": {
    "jobId": "tbb93f18fxpfa86eerjcnnq5"
  }
}
Response
{
  "action": "printer.sendJob",
  "result": {
    "status": "done",
    "message": "Sent Single Plate Sample to X1C-01"
  },
  "tags": [
    "…"
  ],
  "actions": [
    "…"
  ]
}

Register a spool with a blank tag

Request
POST /v1/qr-tags/execute
Idempotency-Key: 3c0f2e9a1b7d4e6f8a2c5b9d1e3f7a0c

{ "tagIds": ["v61yt345k7x9a5eaw1tuw1vy", "r4vydpzb0g2gq0a3yq8kkxjm"], "action": "pair.bindBlankAsNewSpool", "params": { "grams": 1000 } }
Response
{
  "action": "pair.bindBlankAsNewSpool",
  "result": {
    "status": "done",
    "message": "Registered a spool of PLA Basic Jade White with 1,000 g"
  },
  "tags": [
    {
      "id": "v61yt345k7x9a5eaw1tuw1vy",
      "status": "active",
      "entityType": "material_instance",
      "entityId": "z5y7mec4haa3tnu7xzqbwamd",
      "label": "PLA Basic · Jade White",
      "printer": null,
      "spool": {
        "id": "z5y7mec4haa3tnu7xzqbwamd",
        "remainingGrams": 1000,
        "nominalGrams": null,
        "labelCode": "Q9FJ-2JA4",
        "empty": false,
        "location": null,
        "variant": {
          "name": "Jade White",
          "materialName": "PLA Basic",
          "…": "…"
        }
      }
    }
  ],
  "actions": [
    "…"
  ]
}

Set a blank up for a printer from its web page

Request
POST /v1/qr-tags/execute
Idempotency-Key: 9d1e3f7a0c3c0f2e9a1b7d4e6f8a2c5b

{ "tagIds": ["qvq9w2cmd8j16ijv05rjgq5z"], "action": "pair.bindBlankToPrinter", "target": { "entityType": "printer", "entityId": "p7h2j4k6m8n0q2r4s6t8v0wa" } }
Response
{
  "action": "pair.bindBlankToPrinter",
  "result": {
    "status": "done",
    "message": "Set up as the tag for X1C-01"
  },
  "tags": [
    {
      "id": "qvq9w2cmd8j16ijv05rjgq5z",
      "status": "active",
      "entityType": "printer",
      "entityId": "p7h2j4k6m8n0q2r4s6t8v0wa",
      "label": "X1C-01",
      "printer": {
        "…": "…"
      }
    }
  ],
  "actions": [
    "…"
  ]
}

The same blank set up by another phone a moment earlier

json
{
  "statusCode": 409,
  "code": "tag_already_set_up",
  "message": "This tag was set up a moment ago. Scan it again to see what it's for."
}

A slot the printer no longer has

Request
{
  "tagIds": [
    "u8qaisrqkwr6ib1p1jfjeiov",
    "nvioxp9s1d7o3van7wqhkmtp"
  ],
  "action": "pair.assignVariantToPrinter",
  "params": {
    "amsIndex": 1,
    "slotIndex": 0
  }
}
Response
{
  "statusCode": 409,
  "code": "slot_gone",
  "message": "X1C-01 doesn't have slot A2/1 anymore"
}

The printer changed since the scan

Request
{
  "tagIds": [
    "k3q9x2m8v7c1n4b6z0w5y2ta"
  ],
  "action": "printer.pause"
}
Response
{
  "statusCode": 409,
  "code": "state_changed",
  "message": "Nothing is printing"
}

Response Schema

QrExecuteResult
actionrequired
enum
printer.confirmReadyprinter.openprinter.unreadyprinter.pauseprinter.resumeprinter.stopprinter.homeprinter.runQueueprinter.enableprinter.disableprinter.sendJobprinter.runMacroprinter.maintenanceDoneprinter.unloadFilamentprinter.clearAllSlotsslot.assignslot.loadslot.clearslot.refreshRfidslot.openPrintervariant.assignvariant.registerSpoolspool.updateGramsspool.removespool.loadspool.markEmptyspool.openPrinterpair.assignVariantToPrinterpair.assignVariantToSlotpair.moveSlotpair.swapSlotspair.loadSpoolIntoPrinterpair.loadSpoolIntoSlotpair.bindBlankAsNewSpoolpair.bindBlankToVariantpair.bindBlankToPrinterpair.bindBlankToSlotpair.bindBlankToSpool
actionsoptional
object[]
The tag's actions re-evaluated after this one ran, so the app can refresh without resolving again. Absent when the printer couldn't be read back; resolve with `refresh: true` for them.
availablerequired
boolean
confirmrequired
boolean
confirmMessageoptional
string
defaultrequired
boolean
The main button. At most one action has it, and only an available one.
destructiverequired
boolean
detailoptional
string
A second line the app shows under the action's button
idrequired
enum
printer.confirmReadyprinter.openprinter.unreadyprinter.pauseprinter.resumeprinter.stopprinter.homeprinter.runQueueprinter.enableprinter.disableprinter.sendJobprinter.runMacroprinter.maintenanceDoneprinter.unloadFilamentprinter.clearAllSlotsslot.assignslot.loadslot.clearslot.refreshRfidslot.openPrintervariant.assignvariant.registerSpoolspool.updateGramsspool.removespool.loadspool.markEmptyspool.openPrinterpair.assignVariantToPrinterpair.assignVariantToSlotpair.moveSlotpair.swapSlotspair.loadSpoolIntoPrinterpair.loadSpoolIntoSlotpair.bindBlankAsNewSpoolpair.bindBlankToVariantpair.bindBlankToPrinterpair.bindBlankToSlotpair.bindBlankToSpool
kindrequired
"webhook" | "builtin"
webhookbuiltin
labelrequired
string
paramoptional
object
The choice to make before running it; the app opens a sheet with the default picked
Variant 1object
defaultrequirednullable
object
amsIndexrequired
number
slotIndexrequired
number
kindrequired
"slot"
optionsrequired
object[]
amsIndexrequired
number
-1 for an external spool holder
assignedrequirednullable
object
What Printago has assigned to a slot, as the app shows it.
grouprequired
string
Slots are listed by unit: "AMS 1", "External"
loadedrequired
boolean
The printer reports a spool in the slot
refrequired
string
The slot as the app names it: A1/3, S1
slotIndexrequired
number
Variant 2object
defaultrequirednullable
object
amsIndexrequired
number
printerIdrequired
string
slotIndexrequired
number
kindrequired
"printer_slot"
optionsrequired
object[]
namerequired
string
printerIdrequired
string
slotsrequired
object[]
Variant 3object
defaultrequirednullable
string
keyrequired
"enrollmentId" | "macroId" | "jobId"
enrollmentIdmacroIdjobId
kindrequired
"choice"
optionsrequired
object[]
hintoptional
string
A short second line, such as "Overdue" or "3 plates"
labelrequired
string
valuerequired
string
titlerequired
string
The sheet's title, such as "Send a job"
Variant 4object
kindrequired
"variant"
spoolsoptional
object[]
coderequirednullable
string
Its label code
idrequired
string
locationrequirednullable
object
The slot a spool is loaded in.
remainingGramsrequired
number
variantIdrequired
string
Variant 5object
defaultrequired
number
The weight the entry opens with
kindrequired
"grams"
minrequired
number
The least weight it accepts: 1 to register a spool, 0 to update one
permissionrequired
enum
organization.ownerorganization.editorganization.deleteuser.viewuser.inviteuser.edituser.deletepermission.viewpermission.grantpermission.revokepart.viewpart.createpart.editpart.deletepart.exportsku.viewsku.createsku.editsku.deletematerial.viewmaterial.creatematerial.editmaterial.deletematerial.instance.viewmaterial.instance.creatematerial.instance.editmaterial.instance.deleteprinter.viewprinter.createprinter.editprinter.deleteprinter.controlprinter.readyprinter.configprinter.statsprinter.cameraprofile.viewprofile.createprofile.editprofile.deletequeue.viewqueue.managequeue.overridejob.createjob.edit.ownjob.edit.alljob.delete.ownjob.delete.allbuild.viewbuild.createbuild.editbuild.deletesettings.viewsettings.editintegration.viewintegration.managesubscription.viewsubscription.manageanalytics.viewreports.generateaudit.log.viewaudit.logs.configureorder.vieworder.createorder.editorder.deleteorder.printapiKey.viewapiKey.createapiKey.editapiKey.deletefile.downloadmaintenance.viewmaintenance.createmaintenance.editmaintenance.deletemaintenance.completerouting.managecustomizer.managemacro.viewmacro.createmacro.editmacro.deletemacro.runcard.viewcard.createcard.editcard.deletelayout.viewlayout.createlayout.editlayout.deleteclasswork.viewerclasswork.editorsequence.viewsequence.editqr.scanqr.manageinternal.shopifyqueue.adminpart.viewerpart.editorsku.viewersku.editororders.adminmaterial.viewermaterial.editorprinter.viewerprinter.editorprofile.viewerprofile.editorsettings.adminsubscription.admin
reasonoptional
string
Why the action is unavailable, in words the app shows as-is
reasonCodeoptional
enum
printingbusyofflineno_permissionmissing_entitlementstatus_unknownnot_printingalready_pausednot_pausedalready_readynot_readyneeds_setupprinter_disabledalready_enabledalready_disablednot_supportedno_pending_jobsno_macrosno_maintenanceno_assigned_slotsno_slotsno_printersslot_missingslot_unassignedslot_emptyslot_readingnot_ams_slotno_temperaturespool_emptyalready_emptyspool_elsewhere
runsrequired
"server" | "app"
serverapp
resultrequired
object
messagerequired
string
statusrequired
"done"
tagsrequired
object[]
The tags with their target's state after the action; empty when it couldn't be read
blankoptional
object
Blank tags that aren't set up yet
coderequirednullable
string
The short code printed on the tag
entityIdrequirednullable
string
entityTyperequired
"blank" | "printer" | "printer_slot" | "material_variant" | "material_instance"
blankprinterprinter_slotmaterial_variantmaterial_instance
idrequired
string
labelrequirednullable
string
printerrequirednullable
object
The live printer of a printer or slot tag; null for other tags or when the printer is gone
idrequired
string
jobNamerequirednullable
string
namerequired
string
pausedrequired
boolean
printingrequired
boolean
progressrequirednullable
number
0 to 100 while printing
providerrequirednullable
string
staterequired
enum
OfflineStaleReadyBusyWaitingDisabledNeedsConfiguration
statusLabelrequired
string
"Printing 42%", "Paused", "Waiting for confirmation", ...
statusUnknownoptional
boolean
The live status couldn't be read; `statusLabel` is "Status unavailable" and `state` is Printago's last record
slotoptionalnullable
object
Slot tags: the slot, what is assigned to it and what the printer sees
amsIndexrequired
number
-1 for an external spool holder
assignedrequirednullable
object
What Printago has assigned to a slot, as the app shows it.
brandrequirednullable
string
colorrequirednullable
string
#RRGGBBAA of the first color; null when only a material type is assigned
instanceIdoptionalnullable
string
The spool loaded in the slot, when one is
namerequired
string
The material and variant names, such as "PLA Basic Red"
remainingGramsoptionalnullable
number
What that spool has left
existsrequired
boolean
False when the printer doesn't have this slot right now
loadedrequired
boolean
The printer reports a spool in the slot
refrequired
string
A1/3, S1
slotIndexrequired
number
spooloptionalnullable
object
Spool tags
emptyrequired
boolean
No filament left
idrequired
string
labelCoderequirednullable
string
locationrequirednullable
object
The slot a spool is loaded in.
amsIndexrequired
number
printerIdrequired
string
printerNamerequired
string
refrequired
string
A1/3, S1
slotIndexrequired
number
nominalGramsrequirednullable
number
What it held when full, when known
remainingGramsrequired
number
variantrequirednullable
object
The spool's filament; null when its variant is gone
brandrequired
string
colorrequirednullable
string
#RRGGBBAA, or several joined with ";"
idrequired
string
materialIdrequired
string
materialNamerequired
string
namerequired
string
typerequired
string
statusrequired
"blank" | "active" | "revoked" | "orphaned"
blankactiverevokedorphaned
variantoptionalnullable
object
Variant tags
brandrequired
string
colorrequirednullable
string
#RRGGBBAA, or several joined with ";"
idrequired
string
materialIdrequired
string
materialNamerequired
string
namerequired
string
typerequired
string

Example Response

201 OK — application/json
{
  "action": "printer.confirmReady",
  "result": {
    "status": "done",
    "message": "string"
  },
  "tags": [
    {
      "id": "string",
      "status": "blank",
      "entityType": "blank",
      "entityId": "string",
      "label": "string",
      "printer": {
        "id": "string",
        "name": "string",
        "provider": "string",
        "state": "Offline",
        "statusLabel": "string",
        "statusUnknown": true,
        "printing": true,
        "paused": true,
        "progress": 1,
        "jobName": "string"
      },
      "slot": {
        "amsIndex": 1,
        "slotIndex": 1,
        "ref": "string",
        "exists": true,
        "assigned": {
          "name": "string",
          "brand": "string",
          "color": "string",
          "instanceId": "string",
          "remainingGrams": 1
        },
        "loaded": true
      },
      "variant": {
        "id": "string",
        "name": "string",
        "materialId": "string",
        "materialName": "string",
        "brand": "string",
        "type": "string",
        "color": "string"
      },
      "spool": {
        "id": "string",
        "variant": {
          "id": "string",
          "name": "string",
          "materialId": "string",
          "materialName": "string",
          "brand": "string",
          "type": "string",
          "color": "string"
        },
        "remainingGrams": 1,
        "nominalGrams": 1,
        "labelCode": "string",
        "empty": true,
        "location": {
          "printerId": "string",
          "printerName": "string",
          "amsIndex": 1,
          "slotIndex": 1,
          "ref": "string"
        }
      },
      "blank": {
        "code": "string"
      }
    }
  ],
  "actions": [
    {
      "id": "printer.confirmReady",
      "label": "string",
      "kind": "webhook",
      "runs": "server",
      "permission": "organization.owner",
      "default": true,
      "destructive": true,
      "confirm": true,
      "confirmMessage": "string",
      "available": true,
      "reasonCode": "printing",
      "reason": "string",
      "detail": "string",
      "param": {
        "kind": "slot",
        "options": [
          {
            "amsIndex": 1,
            "slotIndex": 1,
            "ref": "string",
            "group": "string",
            "assigned": {
              "name": "string",
              "brand": "string",
              "color": "string",
              "instanceId": "string",
              "remainingGrams": 1
            },
            "loaded": true
          }
        ],
        "default": {
          "amsIndex": 1,
          "slotIndex": 1
        }
      }
    }
  ]
}