Skip to content

QR Code Scan Flow

A code is a public, scannable token that resolves through GET /resolve/{value} without authentication. Combined with an action on the code_scanned trigger, codes drive the physical side of the platform: stickers on doors, badges on chargers, links on signs. See Code for the model.

1. Create the Code

POST /code

json
{
  "name": "Meeting Room 1 door sticker",
  "owner_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "styling": {
    "color": { "foreground": "#000000", "background": "transparent" }
  }
}

name and owner_id are required. styling is optional. To attach an action at creation, include action_id and input in the same body — see step 3.

Response:

json
{
  "data": {
    "id": "22222222-3333-4444-5555-666666666666",
    "value": "fX9pZk3Q2mRsVtYwNbCdEhJkLpQuWxZa",
    "name": "Meeting Room 1 door sticker",
    "description": null,
    "data": null,
    "styling": {
      "color": { "foreground": "#000000", "background": "transparent" }
    },
    "asset_id": null,
    "expires_at": null,
    "created_by": "e992bfc1-0336-42c5-bd0a-4f4804a9fd24",
    "owner_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "created_at": "2026-05-15T10:00:00.000Z",
    "updated_at": "2026-05-15T10:00:00.000Z"
  },
  "request_id": "c1d2e3f4-a5b6-7890-cdef-123456789012",
  "count": 1
}

The resolve URL is /resolve/<value>. That string is what the QR image encodes. Capture data.id as code_id.

2. Set the Data Payload

PUT /code/{code_id}

json
{
  "data": { "bookable_id": "c3d4e5f6-a7b8-9012-cdef-345678901234" },
  "description": "Posted next to the meeting room door."
}

data is returned at resolve time and passed into any action bound to the code through ctx.code.data. Use it to carry the context the scan needs, such as which bookable to check into.

3. Attach an Action

POST /code/{code_id}/action

json
{
  "action_id": "33333333-4444-5555-6666-777777777777",
  "input": { "bookable_id": "c3d4e5f6-a7b8-9012-cdef-345678901234" },
  "reason": "Auto check in on room scan"
}

action_id and input are both required. reason is optional. The action must have trigger: "code_scanned". See Bind an Action for creating the action.

As a shorthand, action_id and input can also be passed directly in the POST /code body to attach the action at creation.

Response:

json
{
  "data": {
    "id": "44444444-5555-6666-7777-888888888888",
    "action_id": "33333333-4444-5555-6666-777777777777",
    "object_id": "22222222-3333-4444-5555-666666666666",
    "object_type": "public.code",
    "input": { "bookable_id": "c3d4e5f6-a7b8-9012-cdef-345678901234" },
    "reason": "Auto check in on room scan",
    "priority": 0,
    "owner_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "created_at": "2026-05-15T10:01:00.000Z",
    "updated_at": "2026-05-15T10:01:00.000Z"
  },
  "request_id": "d2e3f4a5-b6c7-8901-def0-234567890123",
  "count": 1
}

4. Render the QR Image

GET /code/{code_id}/data returns the rendered QR image with the styling baked in. Use the URL directly in an <img> tag or send it to a printer.

5. The Scan

A phone scans the sticker and opens the resolve URL. GET /resolve/{value} requires no authentication. The code_scanned trigger fires and every action bound to the code runs. Inside each action, ctx.code.data carries the payload set in step 2 and ctx.input carries the per-attachment input set in step 3.

A typical action posts a checkin against the bookable in ctx.code.data.bookable_id.

The response follows the action result:

OutcomeResponse
Action returns { type: "redirect", url: "..." }302 redirect to that URL
No action bound or action returns no value204 No Content
Action returns a value200 with the result as the body

Expiry

Set expires_at on the code via PUT /code/{id} to stop the code from resolving after a given time. Expired codes return 404 from resolve. Deleted codes do the same. The endpoint never authenticates, so do not put sensitive data directly in data.