# Agent Second Screen > Agent Second Screen is a passive display screen mounted in a car. An AI assistant drives it > remotely; nobody touches the screen. The person in front of it is driving, so > they glance at it for a second or two and then look back at the road. You write > what it shows. ## Pairing (no setup needed) There is nothing to install and nothing to configure. Pairing is one GET, so a fetch tool is the only capability you need. 1. If the user has not already given you three words, ask them to open `https://www.agentsecondscreen.com` in the car's browser. For a fullscreen page they can bookmark `https://www.youtube.com/redirect?q=https://www.agentsecondscreen.com/` instead. The screen then shows three large words. 2. Ask the user to read you the three words showing on the screen. 3. Fetch this, with those words in place of WORD WORD WORD: `https://www.agentsecondscreen.com/api/v1/do?op=pair&code=WORD+WORD+WORD` Add `&label=Model+3` to give the screen a name. 4. Tell the user the screen is paired, and ask what they would like on it. 5. Hold the token in your context for the rest of the conversation and never show it to the user. If any later call answers `unauthorized`, ask for the code showing now and pair again. The reply is plain text: a token starting with `tb_`, then ready-made URLs with that token already filled in for reading the screen and writing to it. Copy the shape of those URLs for everything after. Pass the words exactly as the user said them. A mis-heard word is repaired on the server — by its first three letters, or by a one-letter slip — so never guess at a correction of your own. A code lives 10 minutes and rotates when it is used or after 5 wrong attempts, so one code pairs once. Malformed input costs no attempt. The screen tells the driver: "Tell your AI: go to www.agentsecondscreen.com and pair with the code WORD WORD WORD". So the user may well hand you this site and the three words in the same sentence — when that happens, skip to step 3 and pair straight away. ## Access paths, least setup first - [URL API](https://www.agentsecondscreen.com/api/v1/do?op=state): one GET, no body, no headers — works with a fetch tool alone. Everything is a query parameter and the answer is plain text. Start here. - [REST API](https://www.agentsecondscreen.com/openapi.json): `POST /api/v1/pair` with `{"code":"three words","label":"Model 3"}` returns `{"token":"tb_…"}`. `GET /api/v1/state` and `POST /api/v1/command` both take `Authorization: Bearer tb_…`. A command body is one object discriminated by `op`: set_screen, set_pane, patch_pane, remove_pane, clear, set_layout, set_theme, unpair, revoke_others. - [MCP connector](https://www.agentsecondscreen.com/api/mcp): optional, and it needs setup the user does by hand — a custom connector added in the AI app before you can use it. Streamable HTTP at `https://www.agentsecondscreen.com/api/mcp`; in Claude.ai it goes under Settings → Connectors → Add custom connector, authentication "No sign-in". Eleven tools: pair, get_state, set_screen, set_pane, patch_pane, remove_pane, clear, set_layout, set_theme, unpair, revoke_others. There `pair` is a tool, and the token travels as the `token` argument on every call after it. `https://www.agentsecondscreen.com/openapi.json` is this same contract as a machine-readable spec. ## URL API examples Replace `tb_TOKEN` with the token you got from pairing. ``` https://www.agentsecondscreen.com/api/v1/do?op=pair&code=amber%20lantern%20drift&label=Model%203 https://www.agentsecondscreen.com/api/v1/do?token=tb_TOKEN&op=state https://www.agentsecondscreen.com/api/v1/do?token=tb_TOKEN&op=md&name=eta&title=ETA&text=%23%23%2012%20minutes https://www.agentsecondscreen.com/api/v1/do?token=tb_TOKEN&op=append&name=eta&text=-%20arriving%20early https://www.agentsecondscreen.com/api/v1/do?token=tb_TOKEN&op=clear https://www.agentsecondscreen.com/api/v1/do?token=tb_TOKEN&cmd=%7B%22op%22%3A%22set_screen%22%2C%22panes%22%3A%7B%22eta%22%3A%7B%22kind%22%3A%22markdown%22%2C%22title%22%3A%22ETA%22%2C%22markdown%22%3A%22%23%23%2012%20minutes%5Cn%5Cn-%20I-25%20clear%22%7D%2C%22charge%22%3A%7B%22kind%22%3A%22widget%22%2C%22widget%22%3A%7B%22type%22%3A%22progress%22%2C%22value%22%3A64%2C%22label%22%3A%22Charging%22%7D%7D%7D%7D ``` That last one is a `set_screen` with two panes. Its `cmd` parameter, before URL-encoding, is: ```json {"op":"set_screen","panes":{"eta":{"kind":"markdown","title":"ETA","markdown":"## 12 minutes\n\n- I-25 clear"},"charge":{"kind":"widget","widget":{"type":"progress","value":64,"label":"Charging"}}}} ``` Only five flat ops exist — `pair`, `state`, `md`, `append`, `clear` — and every other command travels as `cmd=`, the same shape the REST API takes. Add `&format=json` for JSON instead of prose. Errors come back as `ERROR : ` with a 4xx or 5xx status; a bad request lists every accepted form. ## Driving the screen THE SCREEN The screen shows one page of named panes, tiled. A name is a lowercase slug like `eta` or `next-turn` and is a stable handle. Keep the whole screen to 1-3 panes: the driver gets one glance at it, and every extra pane shrinks the type. COMPOSING `set_screen` puts up a whole screen in one call — `{"panes": {...}}`, plus an optional `layout`. It replaces every pane and the layout together, so there is no half-built state and nothing stale is left behind. Reach for it first, every time the screen should show something new. The order of the panes in the object is the order they are tiled, and the first one gets the primary (largest) tile. After that: `patch_pane` to update part of one pane, `set_pane` to add or replace a single pane on a screen already up, and `set_layout`, `remove_pane` or `clear` for a correction. Read the summary every call returns. It lists the panes, names any pane the layout has no room for (a hidden pane is not on screen at all), says roughly how big one tile is in pixels so you can size type and pictures, and reports what the screen last displayed and whether it is live. Call `get_state` when you have lost track of what is showing. PANE KINDS - markdown: the default. Use it for almost all text. Short lines, big ideas, headings and bullets rather than paragraphs. - widget: native pieces the screen animates on its own — clock, countdown, progress, image. Prefer one over markdown whenever the value moves with the clock, because the screen keeps it current without you. The `image` widget is the simplest way to show a picture: give it an https URL or a `data:` URI and it is fitted to the tile (letterboxed, never clipped), with a caption if you want one. - html: a custom visual, for when layout itself carries meaning. Inline all CSS and JS in the html string. The pane has NO network access: no external stylesheets, scripts, fonts or images, so an image must be a `data:` URI. For a drawing (a gauge, a route sketch, a chart) prefer inline `` over a bitmap: it stays sharp on the car's high-density screen and costs a fraction of the bytes. Size type in `em` rather than fixed px — 1em is the board's own type size and scales with the tile, so the same html fits any tile. Images, SVG and video inside the pane are fitted for you (max-width and max-height 100%), so nothing is clipped. - url: embeds a live site in a frame. Only works for sites that allow framing, and most large ones refuse. Prefer fetching the content yourself and writing a markdown pane. Set `refreshSeconds` if the page needs to reload. LAYOUT The screen is roughly 16:10 landscape. Placement belongs to the layout, not to the pane. `auto` is the default and the right answer most of the time: one pane fills the screen, two sit side by side, three give the first a full-height column beside two stacked, four make a 2x2, and so on up to eight. `focus` shows one named pane full-screen — use it when there is one thing that matters right now. `grid` is a deliberate arrangement: `{"type":"grid","columns":2,"rows":2,"cells":{"eta":{"col":1,"row":1,"rowSpan":2}}}`, at most 4 columns by 3 rows. Panes with no cell fill the remaining cells in order, and a pane the grid has no room for is hidden — the summary names it. WRITING FOR THE SCREEN Write for a glance, not a read. A pane should be legible in about one second: a number, a short phrase, a few bullets. No paragraphs, no long tables, no "as I mentioned earlier". Put the single most important fact in the biggest pane. Drop detail rather than shrinking type. UPDATING Keep a screen alive with `patch_pane` rather than rebuilding it — a new value on a progress widget, a line appended to a list. It is cheaper, the tile keeps its place, and it does not flash the screen. When the point of the screen has changed, do not edit your way there: call `set_screen` again. A change made while the screen is offline is stored and appears the moment it reconnects, which the summary tells you. ## Handling the token The token is a bearer secret for one screen. Hold it in your own context for the conversation, do not print it in a reply, and do not paste a URL containing it into anywhere a person or a chat client can see it — a link unfurler that fetched it would be running the command. Call `unpair` when the user is done. ## Notes - [robots.txt](https://www.agentsecondscreen.com/robots.txt): search indexers are kept out of `/api/`; an assistant fetching a URL on a user's behalf is not. - Responses are never cached. There is no CORS layer; call it server to server.