> ## Documentation Index
> Fetch the complete documentation index at: https://docs.anny.co/llms.txt
> Use this file to discover all available pages before exploring further.

# Display Integration

Build a native room display integration that shows resource information, active and upcoming bookings, and allows on-premises booking creation via QR code or NFC.

**Capabilities:**

* Display room information and occupancy
* Show active and upcoming bookings
* Create bookings via QR code, NFC, or guest access
* Check-in and check-out bookings

***

## Pairing a New Display

<Steps>
  <Step title="Register or fetch the display panel">
    If no `displayPanel` UUID is stored on-device, register a new display:

    ```http theme={null}
    POST /api/v1/display-panels
    Authorization: Bearer {token}

    {
      "data": {
        "type": "display-panels",
        "attributes": {
          "panel_type": "app"
        }
      }
    }
    ```

    Store the returned `uuid` and `secret` locally on-device. The response also includes `meta.pairing_code` and `meta.public_url`.

    If a UUID is already stored, fetch the existing panel:

    ```http theme={null}
    GET /api/v1/display-panels/{panel_uuid}?secret={secret}&include=organization
    ```
  </Step>

  <Step title="Display pairing options">
    If `displayPanel.organization` is `null`, the panel is not yet paired. Show the 6-digit `pairing_code` from the response metadata and optionally a QR code:

    ```http theme={null}
    https://app.anny.co/displays/panels?code={pairing_code}
    ```

    Users can also enter the code manually at `https://app.anny.co/displays/panels`.
  </Step>

  <Step title="Subscribe to pairing updates">
    Subscribe to WebSocket updates to detect when the display is paired:

    | Setting          | Value                                                             |
    | ---------------- | ----------------------------------------------------------------- |
    | WebSocket server | `wss://ws.anny.co` (prod) or `wss://ws.staging.anny.co` (staging) |
    | Channel          | `display-panels.{panel_uuid}` (public channel)                    |
    | Events           | `.display-panels.updated`, `.display-panels.deleted`              |
    | App key          | `REQUEST`                                                         |
    | Protocol         | Pusher-compatible                                                 |

    The event payload contains the JSON:API-encoded display panel. Alternatively, poll `GET /api/v1/display-panels/{uuid}` on app startup.
  </Step>

  <Step title="Obtain a Bearer token">
    After pairing, the display panel includes a `personal_access_token`. Use this as the Bearer token for authenticated endpoints. Fetch the panel config on each app startup as the token may rotate.
  </Step>
</Steps>

***

## Displaying the Grid

Display panels can show one or many resources simultaneously. Each resource is a **widget** in the `widgets` array of the display panel:

| Property            | Type    | Description                                                  |
| ------------------- | ------- | ------------------------------------------------------------ |
| `id`                | string  | Widget ID                                                    |
| `resourceId`        | string  | Numeric resource ID                                          |
| `orderIndex`        | integer | Display order                                                |
| `showOrganizer`     | boolean | Show booking customer name                                   |
| `allowGuestBooking` | boolean | Allow booking without authentication                         |
| `allowAdhocBooking` | boolean | Allow immediate booking without respecting booking intervals |

Build a grid from the widgets. Use `max_widgets_per_page` to set page size, and provide pagination or auto-rotation if more widgets exist.

`secondary_widgets` defines fallback resources shown when all primary resources are booked.

### Load Resource Data

```http theme={null}
GET /api/v1/resources?filter[ids]={ids}&include=category,cover_image,resource_properties.property,parent,services
Authorization: Bearer {token}
```

### Load Booking Previews

```http theme={null}
GET /api/v1/booking-previews?filter[resource_id]={id}&filter[date]={date}
Authorization: Bearer {token}
```

Apply widget settings to the display:

* Show/hide customer name based on `showOrganizer`
* Show/hide booking description based on `displayPanel.anonymize`; use a generic "Booked" label when anonymized

### Real-time Booking Updates

Join a private resource channel for each displayed resource using the panel's Bearer token:

| Setting          | Value                                      |
| ---------------- | ------------------------------------------ |
| WebSocket server | `wss://ws.anny.co`                         |
| Channel          | `resources.{resourceId}` (private channel) |
| Auth URL         | `https://b.anny.co/api/broadcasting/auth`  |
| Events           | `.bookings.updated`, `.bookings.completed` |
| App key          | `REQUEST`                                  |

On each WebSocket event, reload the booking previews for the resource.

***

## Creating Bookings

### Time Selection

**Quick selection:** Show up to 4 quick booking duration buttons.

1. Find the service with the smallest `min_duration`
2. Calculate options based on `min_duration`, `max_duration`, and booking `interval`

Example — min: 15, max: 60, interval: 15 → show **15, 30, 45, 60**

**Adhoc booking** (when `allowAdhocBooking` is enabled): Start immediately at the current minute without time selection. Pass `?adhoc=true` on the instant booking endpoint.

**Advanced selection:**

* `GET /api/v1/resources/{id}/start-times` — get available start times for a date
* `GET /api/v1/resources/{id}/end-times` — get available end times for a selected start (flexible duration services)

<Tabs>
  <Tab title="Authenticated Bookings">
    Pass the QR code or NFC payload as a `code` query parameter:

    ```http theme={null}
    POST /api/v1/bookings/instant?code={scanned_code}&adhoc=true
    Authorization: Bearer {token}

    {
      "resource_id": "15",
      "service_id": "8",
      "start_date": "2025-10-16T09:00:00+02:00",
      "end_date": "2025-10-16T09:30:00+02:00"
    }
    ```
  </Tab>

  <Tab title="Guest Bookings">
    When `allowGuestBooking` is enabled, omit the code. The booking is created as an anonymous "Ad-hoc booking".
  </Tab>
</Tabs>

***

## Managing Bookings from the Display

### Check-In

```http theme={null}
POST /api/v1/bookings/{booking_id}/check-in
Authorization: Bearer {token}
X-Supports-Dynamic-Form: true
```

If the booking still needs check-in-timed legal documents or custom fields, send `X-Supports-Dynamic-Form: true`. The response `meta.status` is `requires_details` with a `form_url`. Collect the details, then call check-in again with `form_data`. Without the header the API returns HTTP 400 `scans.check_in_requires_details_unsupported` instead. The same delayed-form flow is available on `POST /api/v1/scans` — see [X-Supports-Dynamic-Form](/developers/guides/admin/booking-lifecycle#x-supports-dynamic-form) and [Scan](/developers/models/scans).

### Check-Out

```http theme={null}
POST /api/v1/bookings/{booking_id}/check-out
Authorization: Bearer {token}
```

### Cancel

Bookings without an associated customer that are not blockers (`is_blocker=false`) can be cancelled directly from the display.

### Extend Duration

Bookings can be extended if `booking.charged_duration < service.max_duration`.

```http theme={null}
PATCH /api/v1/bookings/{booking_id}
Authorization: Bearer {token}

{
  "data": {
    "type": "bookings",
    "id": "{booking_id}",
    "attributes": {
      "end_date": "{new_end_datetime}"
    }
  }
}
```

***

## Map Display Mode

When configured for map mode, the display shows an interactive floor plan. See the [Map Client guide](/developers/guides/internal/map-client) for the full implementation.

***

## Unpairing

When the admin deletes the panel via the dashboard:

* The app receives a `.display-panels.deleted` WebSocket event, **or**
* `GET /api/v1/display-panels/{uuid}` returns `404`

On either signal, clear the stored UUID and secret and return to the pairing screen.
