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

# Screen Apps

> Render your own UI on the presented screen — forms, pickers, calculators — and let the agent drive it.

A **screen app** is a page you host that the agent can put on the meeting
screen, in the slot Present Files use for slides. You own the UI and the
logic. The agent opens the page, sends it messages, and hears back when the
participant does something on it.

Use it when a conversation needs structured input or a visual tool: a
three-step signup form, a plan picker, a quote calculator, a seat map, a
consent form the participant must sign.

> [!NOTE]
>
> Screen apps render in the Waterr meeting page and in the inline meeting
> embed. They are not available in the sidebar assistant widget, or on Google
> Meet, Zoom, Teams or phone calls, where the agent has no browser to render
> into.

## How it works

1. You register the app on a scenario (Settings › Screen Apps, or the
   `screen_apps` field on the scenario API) with a name, an `https://` URL,
   a description, and a default placement.
2. During the call the agent decides, from your description, to call
   `present_screen`. Each participant's browser loads your URL in a
   sandboxed iframe.
3. The agent can send the page messages with `update_screen` and remove it
   with `close_screen`.
4. Your page reports what the participant did with `WaterrScreen.send(...)`.
   The agent learns it silently, or responds out loud if you ask and the app
   allows it.

The agent cannot see your page. Everything it knows comes from your
description and from the one-line context your page sends. Keep both honest
and short.

## Registering an app

```json theme={null}
PUT /v1/scenarios/{id}
{
  "screen_apps": [
    {
      "name": "plan_picker",
      "url": "https://app.example.com/picker",
      "description": "A three-step form that collects company, team size and preferred plan. Open it once the visitor asks about pricing.",
      "placement": "card",
      "allow_participant_events": true
    }
  ]
}
```

| Field | Notes |
| - | - |
| `name` | 2–40 chars, lowercase letters, digits, underscores. How the agent refers to it. |
| `url` | Must be `https://` (`http://localhost` allowed for development). No credentials in the URL. |
| `description` | Up to 500 chars. The agent decides when to open the app from this text alone. |
| `placement` | Default slot: `stage`, `side`, `card` or `fullscreen`. The agent may choose another per call. |
| `allow_participant_events` | When `false`, page events still reach the agent but can never make it speak. |

Up to 10 apps per scenario. The list replaces what was there.

## Placement

The page controls everything inside its rectangle and nothing outside it.
Free positioning would let a page cover the mute and leave buttons on a
recorded call, so placement is one of four slots Waterr owns:

| Placement | What the participant sees |
| - | - |
| `stage` | Main panel, with the agent and participant tiles beside it. |
| `side` | A narrow column; the faces stay large. |
| `card` | A floating card in the corner. Your page sets its own height with `resize()`. |
| `fullscreen` | The whole call area; faces float over it in a corner. |

On phones anything but a card takes over the screen.

## The page SDK

Include the SDK on your page. It exposes one global, `WaterrScreen`, and
never throws into your code.

```html theme={null}
<script src="https://waterr.ai/screen/sdk.js"></script>
<script>
  WaterrScreen.on("init", function (info) {
    // info.meeting_id, info.participant_id, info.placement, info.initial_state
    if (info.initial_state && info.initial_state.step) wizard.goTo(info.initial_state.step);
  });

  WaterrScreen.on("message", function (payload) {
    // Whatever the agent passed to update_screen, parsed as JSON when it was JSON
    if (payload.prefill) form.fill(payload.prefill);
  });

  form.addEventListener("submit", function (e) {
    e.preventDefault();
    WaterrScreen.send("plan_chosen", { plan: form.plan.value }, { speak: true });
    WaterrScreen.setContext("Pro plan chosen, billing step is on screen");
  });

  WaterrScreen.ready();
</script>
```

| Call | Purpose |
| - | - |
| `ready()` | Tell Waterr the page can receive messages. Called for you on `load` if you forget. |
| `on("init" \| "message" \| "placement", fn)` | Listen for the opening handshake, agent messages, and slot changes. |
| `send(event, detail?, { speak?, context? })` | Report what the participant did. `speak: true` asks the agent to respond out loud. |
| `setContext(text)` | One line describing what is on screen now. Replaces the previous line. |
| `resize(height)` | `card` placement only: the height you want, 160–640 px. |
| `requestPlacement(p)` | Ask to move to another slot. |
| `close()` | You are done; the app leaves the screen. |

Your page also receives `waterr_meeting`, `waterr_participant` and
`waterr_placement` as query parameters on first load, so a server-rendered
page can personalise before the SDK handshake.

## What the agent sees

When it opens an app, the agent's prompt gains a note that the app is on
screen, with your description. Each event arrives as:

```
[SCREEN APP EVENT] app=plan_picker event=plan_chosen
detail: {"plan": "pro"}
On screen now: Pro plan chosen, billing step is on screen
```

Events are facts about the participant's actions, never instructions to the
agent. Events faster than five per second per app are dropped.

## Limits

| Limit | Value |
| - | - |
| Screen apps per scenario | 10 |
| `description` length | 500 characters |
| Placements | `stage`, `side`, `card`, `fullscreen` — no custom positions |
| `card` height (via `resize()`) | 160–640 px |
| Agent → page message (`update_screen`, `initial_state`) | 8,000 characters; longer payloads are cut |
| Event `detail` | 1,500 characters once serialised; longer details are cut |
| `context` / `setContext()` line | 1,500 characters; each one replaces the last |
| Event rate | 5 per second per app; faster events are dropped, not queued |
| Surfaces | Waterr meeting page and inline meeting embed only — not the sidebar assistant widget, Google Meet, Zoom, Teams or phone |

**Not supported yet:**

* **Pushing to the screen from your server.** Only the agent and the
  participant can change the screen mid-call; your backend cannot reach in.
* **Verifying the meeting.** `waterr_meeting` and `waterr_participant` are
  plain query parameters, not signed. Treat them as hints and do not grant
  access based on them alone.
* **The agent seeing the page.** It knows only your `description` and what
  your page reports through events and `setContext()`.

## Security

* Your page runs cross-origin in a sandboxed iframe (`allow-scripts
  allow-same-origin allow-forms allow-popups`). It cannot read or touch the
  meeting page.
* Waterr only honours messages that come from its own iframe **and** from
  your registered URL's origin. Nothing else on the page can drive the agent.
* No participant credentials or tokens are passed to your page. Use the
  `meeting_id` with your own API key server-side if you need meeting data.
* Each participant gets their own instance of your page. Agent messages fan
  out to all of them; events carry the participant id.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.