# QR codes (`qr_code`)

Show a QR code to scan: a ticket, a check-in, Wi-Fi details, a link or a pickup code. Display only: the page asks for no answer.

**When to use it:** You show a QR code to scan: a ticket, a check-in code, Wi-Fi login details, a pickup number, or a link the person opens from another device. It only displays information; the person does not answer.

**When not to use it:** Do not use it to collect anything or to ask a question: a QR code returns no answer (use `form` or a picker). Do not use it for a link the person only needs to tap in chat: send the link itself.

**Gather first:**

- The exact text to encode: the ticket URL, the `WIFI:T:WPA;S:<network>;P:<password>;;` string, or the check-in number, copied exactly as the scanner expects it.
- A short label that says what the code is, and optionally a short code the person can type if scanning fails.

**Writing good content:**

- Put the scannable text in `value` only. It is not shown in link previews, but `details`, `label` and `caption` may be (the caption appears unless validity text replaces it), so never put a password or ticket secret in them.
- `value` can be an https link, a Wi-Fi string, or plain text such as a ticket or pickup number. Links must be https; javascript:, data:, file:, vbscript: and blob: are rejected. `value` is also limited by UTF-8 bytes per `errorCorrection` level: L 2,953, M 2,331, Q 1,663, H 1,273.
- Set `code` to a short code for typing by hand when the scanner has a manual-entry option. Without it the page shows the value as text, or as an open-link button for an https link.
- Add `validFrom` and `validUntil` (with the local UTC offset) for tickets and time-limited passes; they are shown as text and the code does not expire.
- Use `details` for name, seat or venue, and `caption` for one instruction such as "Show this at the door."

**Display-only:** the page shows information and has no Submit button. It accepts no answer, so no result ever comes back.

## Fields

Every page also has `version` (always `1`), `title`, and optionally `description`, `branding`, `cover` (a header photo), `expiresAt` and `returnLink`; see [Page fields](/docs/api#page-fields). These are the fields of `page` (the page type and its data):

| Field | Description |
| --- | --- |
| `value` | *string, required.* The text the QR code encodes, up to 1,000 characters and, as UTF-8 bytes, at most 2,953 (errorCorrection L), 2,331 (M), 1,663 (Q) or 1,273 (H). Required. An https URL ("https://tickets.example.com/t/8F3K2"), a Wi-Fi login ("WIFI:T:WPA;S:Cafe Guest;P:espresso42;;"), or plain text such as a ticket or pickup number. Web links must be https; javascript:, data:, file:, vbscript: and blob: URLs are rejected. Not shown in link previews. Limits: 1-1000 characters. |
| `label` | *string, required.* What the code is, shown above it, e.g. "Your ticket" or "Guest Wi-Fi". Required. Limits: 1-60 characters. |
| `caption` | *string.* Optional sentence under the label, e.g. "Show this at the door." Include when helpful; omit otherwise. Limits: 1-200 characters. |
| `code` | *string.* Optional short code shown in monospace under the QR for typing in by hand, e.g. "8F3K-2Q". Omit and the page shows value as text instead. Limits: 1-32 characters. |
| `details` | *object[].* Optional facts shown under the code, up to 6, e.g. [{ "label": "Name", "value": "Alex Kim" }, { "label": "Seat", "value": "12B" }]. They appear in the link preview, so never put passwords or other secrets here; those belong only in value. Limits: 1-6 items. |
| `details[].label` | *string, required.* Row name, e.g. "Seat". Limits: 1-40 characters. |
| `details[].value` | *string, required.* Row value, e.g. "12B". Limits: 1-80 characters. |
| `validFrom` | *ISO 8601 timestamp.* Optional start of validity, ISO 8601 with offset, e.g. "2026-11-14T18:00:00-08:00". Shown as text. |
| `validUntil` | *ISO 8601 timestamp.* Optional end of validity, same format. Must be after validFrom. Shown as text; the code itself does not expire. |
| `timeZone` | *string.* Optional IANA time zone for validFrom and validUntil, e.g. "America/Los_Angeles". Omit and the offset written in them is used. |
| `errorCorrection` | *"L" \| "M" \| "Q" \| "H".* Optional error correction: L (7%), M (15%), Q (25%) or H (30%) of the code can be damaged and still scan. Omit for M. |

**Why these are required:** The text to encode and what it is. Web links must be https; `javascript:`, `data:`, `vbscript:`, `file:` and `blob:` values are rejected. Display-only: no answer.

**When optional fields are left out:** No caption; without `code` the value itself is shown as text under the code (as a link when it is https); no validity line or details; error correction `M`. Times use the timestamp's UTC offset without `timeZone`. The link preview shows `label` and `details`, never `value`.

## Minimal example

Only the required fields:

```json
{
  "version": 1,
  "title": "Your pickup code",
  "page": {
    "type": "qr_code",
    "value": "PICKUP 4821",
    "label": "Pickup code"
  }
}
```

## Full example

```json
{
  "version": 1,
  "title": "Your ticket to Rooftop supper club",
  "description": "Show this at the door. One code admits both of you.",
  "branding": {
    "name": "Juniper Events",
    "accentColor": "#0b7a5c"
  },
  "cover": {
    "url": "https://images.example.test/rooftop-supper.jpg",
    "alt": "A long dinner table set on a rooftop at dusk",
    "focalPoint": {
      "x": 0.5,
      "y": 0.3
    }
  },
  "page": {
    "type": "qr_code",
    "value": "https://tickets.juniper.example/t/8F3K2Q",
    "label": "Your ticket",
    "caption": "Scan at the door of The Lantern Room.",
    "code": "8F3K-2Q",
    "details": [
      {
        "label": "Name",
        "value": "Alex Kim"
      },
      {
        "label": "Admits",
        "value": "2 people"
      },
      {
        "label": "Venue",
        "value": "The Lantern Room, 220 Pine Street"
      }
    ],
    "validFrom": "2026-11-14T18:30:00-08:00",
    "validUntil": "2026-11-14T23:00:00-08:00",
    "timeZone": "America/Los_Angeles"
  }
}
```

## Send it

### The pagesystems package and MCP

With the [`pagesystems` package](/docs#quickstart-eve) (eve, AI SDK and other frameworks) or the [MCP server](/docs#quickstart-mcp), the model calls `get_page_type_schema` with `"qr_code"` (the same JSON as [`GET /v1/page-types/qr_code`](/docs/api#endpoints)), then `create_page` with this input (no `version` or `branding`; branding comes from your dashboard):

```json
{
  "title": "Your ticket to Rooftop supper club",
  "description": "Show this at the door. One code admits both of you.",
  "cover": {
    "url": "https://images.example.test/rooftop-supper.jpg",
    "alt": "A long dinner table set on a rooftop at dusk",
    "focalPoint": {
      "x": 0.5,
      "y": 0.3
    }
  },
  "page": {
    "type": "qr_code",
    "value": "https://tickets.juniper.example/t/8F3K2Q",
    "label": "Your ticket",
    "caption": "Scan at the door of The Lantern Room.",
    "code": "8F3K-2Q",
    "details": [
      {
        "label": "Name",
        "value": "Alex Kim"
      },
      {
        "label": "Admits",
        "value": "2 people"
      },
      {
        "label": "Venue",
        "value": "The Lantern Room, 220 Pine Street"
      }
    ],
    "validFrom": "2026-11-14T18:30:00-08:00",
    "validUntil": "2026-11-14T23:00:00-08:00",
    "timeZone": "America/Los_Angeles"
  }
}
```

### REST

```bash
curl https://pagesystems.ai/v1/pages \
  -H "Authorization: Bearer $PAGE_SYSTEMS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"spec":{"version":1,"title":"Your ticket to Rooftop supper club","description":"Show this at the door. One code admits both of you.","branding":{"name":"Juniper Events","accentColor":"#0b7a5c"},"cover":{"url":"https://images.example.test/rooftop-supper.jpg","alt":"A long dinner table set on a rooftop at dusk","focalPoint":{"x":0.5,"y":0.3}},"page":{"type":"qr_code","value":"https://tickets.juniper.example/t/8F3K2Q","label":"Your ticket","caption":"Scan at the door of The Lantern Room.","code":"8F3K-2Q","details":[{"label":"Name","value":"Alex Kim"},{"label":"Admits","value":"2 people"},{"label":"Venue","value":"The Lantern Room, 220 Pine Street"}],"validFrom":"2026-11-14T18:30:00-08:00","validUntil":"2026-11-14T23:00:00-08:00","timeZone":"America/Los_Angeles"}}}'
```

## Validation errors

A spec that does not validate is refused with `400 invalid_spec`. The message names each problem and its path, so a model can fix the spec and try again. For example:

**Leaving out the required field `value`:**

```text
✖ Invalid input: expected string, received undefined
  → at page.value
```

**Misspelling a field name (unknown keys are rejected):**

```text
✖ Unrecognized key: "colour"
  → at page
```
