QR labels and binding
The printed QR
Every device leaves the factory with a QR sticker containing a URL:
https://web.linter-iot-stg.icent.digital/v/<payload>The payload has eight segments separated by dots:
order.mac.client.icc.partNumber.subsidiary.distributor.cif| # | Segment | Example | Notes |
|---|---|---|---|
| 1 | order | PED-2026-04-1832 | Manufacturing order / purchase order |
| 2 | mac | a4cf12bb73d2 | Normalized to lowercase → deviceId |
| 3 | client | Autocolor | Informational label only: it does not assign the device |
| 4 | icc | 8934… | ICC of the SIM (empty when the device is WiFi) |
| 5 | partNumber | EJET | Must exist in the device-type catalog |
| 6 | subsidiary | ES | Code of the subsidiary the device is sold through. Required on new labels. |
| 7 | distributor | PC | Code of the distributor, optional. It must belong to that subsidiary. |
| 8 | cif | B12345678 | Fiscal ID (CIF) of the end client, optional, when it is already known |
For example PED-2026-04-1832.a4cf12bb73d2.Autocolor..EJET.ES.PC is a WiFi
device (empty ICC) for subsidiary ES and distributor PC, with no end client
yet. The payload travels URL-encoded as a single path segment.
- Empty segments stay empty between the dots, and empty trailing segments are
left out. QRs printed with the earlier five-segment layout
(
order.mac.client.icc.partNumber) are still valid: they simply carry no commercial chain. - No value can contain a dot, because the dot is the separator. The label generator removes dots from the free fields (order, client, ICC) and rejects codes, CIFs and part numbers that contain one.
- Codes are matched exactly first and then ignoring case (only when a single record matches); CIFs exactly, then in upper case, then ignoring case. The label always prints them as they are stored.
- The API and the web never return the commercial chain (names or codes) to client roles or on the public pre-login page. The codes themselves are still readable in the printed QR.
The API accepts the QR both as the URL and as the raw payload in every binding endpoint.
Generating labels
Labels are generated by GET /v1/labels (ROOT only). The web application’s
Labels page uses it with the user’s session; a factory can call it directly
with a ROOT API token, either in the key query parameter (the
Generation URL (for the factory) shown on that page) or as
Authorization: Bearer lit_….
curl -o label.svg "https://api.linter-iot-stg.icent.digital/v1/labels?mac=a4cf12bb73d2&partNumber=EJET&filial=ES&distribuidor=PC&pedido=PED-2026-04-1832&key=lit_…"| Parameter | Required | Notes |
|---|---|---|
mac | Yes | MAC of the device (4–32 characters; separators are removed) |
partNumber | Yes | Model part number (up to 40) |
filial | Yes | Subsidiary code (up to 40) |
distribuidor | Distributor code of that subsidiary (up to 40) | |
cif | Fiscal ID of the end client (up to 40) | |
pedido | Order (up to 60) | |
cliente | Client text printed in the QR (up to 60); informational | |
icc | ICC of the SIM (up to 30) | |
register | 1 or true: also register the device (below). 0/false or absent: label only. | |
text | Text printed next to the QR (up to 220; \n breaks lines) | |
base | Web URL for the QR (default: the platform’s web address) | |
format | svg (default) or png | |
width | PNG width in pixels, 200–4000 (default 1000) | |
variant | Label layout (1) | |
key | ROOT API token, as an alternative to the Authorization header |
The response is the image (image/svg+xml or image/png). Errors:
| Status | code | When |
|---|---|---|
401 / 403 | unauthorized / forbidden | No valid credential, or not ROOT |
422 | subsidiary_code_required | filial is missing |
422 | code_has_dot | A code, CIF or part number contains a dot |
422 | subsidiary_code_unknown | No subsidiary with that code |
422 | distributor_code_unknown | No distributor with that code |
422 | chain_incoherent | The distributor belongs to another subsidiary |
422 | client_fiscal_id_unknown | No client with that CIF |
Factory registration
With register=1 the same call also registers the device under the
printed chain, so it appears in the lists and the tree of its subsidiary,
distributor or client before anyone scans it. The web’s preview never
registers anything; its download does when Register the device on the
platform when downloading is ticked (the default).
- New MAC → the device is created in
REGISTEREDstate with the model, the order, the client text, the ICC and the chain. - Existing device without a client, or with the same client → its chain and factory data are updated (a label without a distributor removes the current one). Its status does not change.
- Existing device bound to another client (or a label without a client
for a device that has one) →
409 already_bound: reassign it from the device page instead. - With a client, that client is also added to the chain’s portfolio.
The result travels in the response headers, which are readable from the browser (CORS):
| Header | Value |
|---|---|
x-device-registration | created, updated or unchanged |
x-device-id | The device’s _id (as in GET /v1/devices/:id) |
curl -s -D - -o label.png "https://api.linter-iot-stg.icent.digital/v1/labels?mac=a4cf12bb73d2&partNumber=EJET&filial=ES®ister=1&format=png&width=2000&key=lit_…" \ | grep -i '^x-device'# x-device-registration: created# x-device-id: 6718a0c2e4b0a1b2c3d4e5f6Besides the label errors above, registration can answer 422 mac_invalid
(the MAC must be 12 hexadecimal characters), 422 part_number_unknown and
409 already_bound. The label is rendered first, so a failed request
registers nothing. Each registration that creates or changes a device is
recorded in the audit log (device.registered).
To reprint the label of an existing device, GET /v1/devices/:id returns its
subsidiaryCode, distributorCode and clientFiscalId (not to client
roles).
Flow
- Scan — the end client (or the installer) opens the QR URL:
- Without a session: the public page offers sign in or create an
account (registration creates a client + user in
PENDINGstate). - With a session: straight to the wizard with the device preloaded.
- Without a session: the public page offers sign in or create an
account (registration creates a client + user in
- Request —
POST /v1/bindings/scan { qr }creates an Assignment inPENDING_REVIEW. If notification rules are configured, Linter receives an email with the ICC to activate the SIM.- Commercial roles (subsidiary, distributor, installer) also designate the
end client in the same call:
client: { email, companyName?, fiscalId?, description? }. With an already-registered email the device is bound to that client; with a new one the client is created inPENDING(deduplicated by fiscal ID) with that email as contact. When they send noclientand the QR carries the CIF of a client in their portfolio, that client is used. - The request takes its commercial chain from the device when it is already registered under a subsidiary (and is not moving to another client); otherwise from the chain printed in the QR (codes that do not resolve are ignored, they never block the scan); otherwise from the client’s commercial links (or, without links, the scanner’s own scope).
- For every role except client roles,
POST /v1/bindings/previewalso returns that chain (chain: { subsidiaryName, distributorName? }) and the QR’s client when it is in their portfolio (qrClient: { _id, name }).
- Commercial roles (subsidiary, distributor, installer) also designate the
end client in the same call:
- Approval — ROOT reviews under Bindings and approves
(
POST /v1/assignments/:id/approve): theDeviceis materialized inACTIVEstate with the request’s whole chain and, if the requester came from self-service registration, their account and their client are activated at the same time. If the request was filed by a commercial role, the client is additionally linked to their chain (visible in their portfolio). The requester receives an email. - Reinstallation — binding a MAC to a different client deactivates the previous assignment (history is never deleted) and requires explicit confirmation.
An administrator can also bind directly (POST /v1/bindings, auto-approved).
The distributor must belong to the subsidiary: when only the distributor is
given its subsidiary is filled in, and a mismatch is 422 chain_incoherent.
Related endpoints
| Endpoint | Use |
|---|---|
GET /v1/labels | Label with the QR (SVG/PNG), optionally registering the device (ROOT) |
POST /v1/public/qr-preview | Catalog identity of the QR (public, no commercial data) |
POST /v1/bindings/preview | Identity + binding state (authenticated); commercial chain for every role except client roles |
POST /v1/bindings/scan | Create the request (PENDING_REVIEW); commercial roles add client |
POST /v1/bindings/client-lookup | Does the email belong to a registered client? (commercial roles) |
POST /v1/bindings | Direct registration by an administrator (auto-approved) |
GET /v1/bindings/options | Subsidiaries and distributors with their code, clients with their fiscalId |
GET /v1/assignments | Binding queue for the scope |
POST /v1/assignments/:id/approve / …/reject | ROOT’s decision |