Developer docs
Sandbox · API v2PlaygroundGet API keys

Run a print device

Everything a device integrator (or the operator supporting one) needs: enrolment, the polling loop, what to send at each state, and how every failure mode resolves without a database console.

1 · Enrol the device, bind its credential

bash
# Mint a key with aidc:write, then enrol the device and bind the key to it
curl -X POST "$BASE/api/v2/devices" \
  -H "Authorization: Bearer $ADMIN_KEY" -H "Content-Type: application/json" \
  -d '{ "label": "PEX-2000-LINE-3", "kind": "label_printer",
        "connectMode": "raw_9100", "model": "Industrial label printer",
        "apiKeyId": "key_01H…" }'

The bound key IS the device's identity: claims, heartbeats and results made with it are attributed to this device (actor_kind: "device", D20), and revoking or disabling the device cuts that credential out of the loop within one lease TTL. An unbound aidc:write key can still issue jobs and report as a human operator, it just cannot claim.

2 · The loop a client runs

flow
loop:
  POST /aidc/jobs/claim { "limit": 1, "lease_seconds": 120 }
  ── no jobs? sleep, poll again (an empty claim is a 200, not an error)
  ── got a job:
     1. VERIFY the envelope signature (eddsa-jcs-2022) BEFORE printing
     2. render          → POST …/result { status: RENDERED,        evidence: render_manifest,  attempt_id }
     3. spool           → POST …/result { status: SPOOL_SUBMITTED, evidence: spool_receipt,    attempt_id }
     4. paper came out  → POST …/result { status: PRINTED_ATTESTED,                            attempt_id }
     5. scan the label  → POST …/result { status: SCAN_VERIFIED,   evidence: scan_result,      attempt_id }
     6. seal            → POST …/result { status: SEALED,          evidence: evidence_bundle,  attempt_id }
  long run? POST …/heartbeat { attempt_id, lease_seconds } before the lease expires
  anything broke? POST …/result { status: FAILED, error_message, attempt_id }

Always send Idempotency-Key on results. Retrying after a timeout is then always safe, the server replays the completed transition instead of double-applying it ("replayed": true in the response).

3 · The error catalogue, and what each one means you should do

RefusalMeaningDo
403 DEVICE_NOT_ENROLLEDThe key is not bound to a device.Enrol the device / bind api_key_id (§1).
403 DEVICE_DISABLEDKill switch, an operator disabled or retired this device.Stop the client; resolve with the operator; re-enable via POST /devices/{id}/status.
409 STALE_ATTEMPTThe lease expired and another claim superseded yours.Drop the job locally, the work now belongs to the new attempt. Never re-report.
410 ENVELOPE_EXPIREDThe envelope's TTL passed before the result arrived.Re-claim; a fresh envelope carries a fresh expiry.
409 LEASE_NOT_HELDHeartbeat for a lease you no longer hold.Same as stale attempt, re-claim.
422 MISSING_EVIDENCEThe target state requires its artifact (render_manifest, spool_receipt, scan_result, evidence_bundle).Send the evidence, the state is unreachable without it, by design.
403 ACTOR_NOT_ALLOWEDPRINTED_ATTESTED asserted by something that is not a device or a human.Only an authenticated device (ODV) or operator may claim paper came out.
409 ILLEGAL_TRANSITIONThe FSM forbids that edge (the message lists what is legal).Fix the client's state model.
422 SERIAL_NOT_ALLOCATED / PASSPORT_NOT_READYCreation gates (issuer side).Allocate via carriers/batch; anchor a validation + publish (F6).

4 · Resolving a stuck job

  • Device crashed mid-job? Nothing to do. When the lease expires the job re-enters the claim pool; the next poll picks it up with a fresh attempt_id and the attempt budget increments.
  • Job keeps failing? After max_attempts the reaper parks it in DEAD_LETTER and the serial lands in GET /carriers/export?printState=failed, the re-print file. Query dead-lettered jobs with GET /aidc/jobs?status=DEAD_LETTER; last_error carries the final failure.
  • Need to stop a device NOW? POST /devices/{id}/status { "status": "disabled" }, it cannot claim again, and in-flight work is reclaimable after its lease.
  • Wrong label in the world? A SEALED job is terminal and its serial is printed on the spine. Recovery runs through the void/reissue axis on the serialization record, never by mutating the job.
Build against this contract. Norruva tests the claim, lease, heartbeat, evidence chain and idempotent reporting described here with an internal test client. That client is not distributed and is not a supported production deployable, so build your device integration directly against the contract on this page.
Was this page helpful?
Thanks, noted.Feedback goes to the docs team by email.