object: "qr_code".
Example
Identifiers
Always
"qr_code".Stable UUID. Use this in
{id} path params — but public_slug is accepted there too.The short slug shown in the scan URL. 7–10 characters of
[a-zA-Z0-9]. Stable for the life of the code — never regenerated, never reused. Path params accept this in lieu of the UUID.Fully qualified short URL. The address printed on the physical QR. Convenience field — derived from
public_slug.Label
Operator-facing label. Free-form. Not rendered to scanning visitors. Set it during print to keep dashboard navigation sane.
Free-form description. Operator-facing only.
Destination
Where scans redirect. Always present as an object. Exactly one of
chatbot, page, knowledge_base, or url is non-null when type is set.Status & group
Parent QR Group id, or
null if the code stands alone. When the group’s destination_type is set and the code’s own destination_type is null, scans inherit from the group. Detach by PATCHing group_id to null.Stats
Lifetime count of redirected scans. Increments on
qr_code.scanned. Read-only.ISO 8601 timestamp of the most recent scan, or
null if never scanned. Read-only.Timestamps
When the code was minted. ISO 8601.
Last mutation timestamp. ISO 8601.
Scan object
GET /qr-codes/{id}/scans returns immutable scan rows in cursor-paginated reverse-chronological order.
Snapshot of where this specific scan went. Captured at scan time so historical rows survive future destination changes.
The merged query params from the redirect: static params on the QR (and group) merged with whatever the visitor brought on the short URL. Live values win on key collision.
Coarse geolocation derived from the visitor IP. All three fields may be
null if the lookup fails.Raw
User-Agent header from the visitor’s request. null when absent.IP address and
Referer are deliberately not exposed on the wire — they’re collected for fraud detection but kept internal.Properties and URL params
Both are flat lists of{ key, value, sort_order } rows hung off the QR. Replace as a set with PUT:

