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

# Live view

> Stream a phone's screen into your own app over WebRTC.

Live view streams a phone's screen to a browser or app you build, over WebRTC. The API carries the signaling between your app and the phone. The video itself travels over the WebRTC connection.

## Who can use it

| Phone | With an API key |
| - | - |
| **Your own iPhone** | Available. |
| **Your own Android phone** | Available. Pauses while the phone is locked and resumes when it is unlocked. |
| **Lusor Phone** | Not available. Every `/stream/*` route returns `403 live_view_not_available_for_api_keys`. |

A live view of a Lusor Phone can send taps and text over its own connection, outside the metering that direct control uses, so API keys cannot open one. To watch a Lusor Phone from your code, use `GET …/screenshot` or a [capture](https://docs.lusor.ai/phones/chains-and-capture.md). People signed in to the Lusor app can still watch any phone there.

## Routes

A live view belongs to a session on the phone. Every route is under `/v1/sessions/{id}/stream/`, where `{id}` is the session id. A session without a phone returns `409 no_device`.

| Route | Purpose |
| - | - |
| `GET …/events` | A Server-Sent Events stream of the phone's signaling messages. Open it first. |
| `GET …/ice-servers` | The STUN and TURN servers for your `RTCPeerConnection`. |
| `POST …/start` | Ask the phone to start streaming. Optional `width`, `height`, `fps`, `viewer_id`. |
| `POST …/signal` | Send your WebRTC answer or ICE candidates to the phone. |
| `POST …/request_frame` | Ask the phone for a fresh keyframe, which recovers a frozen picture without reconnecting. |
| `POST …/keepalive` | Send an extra keep-alive. Optional: Lusor already sends one every 25 seconds while the stream is active. |
| `POST …/connect` | Reconnect without starting over. Same body as `start`. |
| `POST …/stop` | Stop the live view. |

## Flow

**1. Open the event stream**

`GET /v1/sessions/{id}/stream/events` with your API key. Each event is named `signal`, and its `data` is one JSON message from the phone with an `action`: `webrtc_offer`, `webrtc_ice`, `stream_ready`, `stream_error`, `stream_stopped` or `ice_update_applied`. A keep-alive comment arrives every 15 seconds.

**2. Get the ICE servers**

`GET /v1/sessions/{id}/stream/ice-servers`. Pass `data.iceServers` to your `RTCPeerConnection` unchanged. If it is `null`, TURN is not available, so fall back to STUN.

**3. Start the stream**

`POST /v1/sessions/{id}/stream/start`. The phone answers on the event stream with a `webrtc_offer`.

**4. Answer the offer**

Set the offer as the remote description, create an answer, and send it with `POST /v1/sessions/{id}/stream/signal` and `{ "message": { "type": "webrtc_answer", … } }`. Send each of your ICE candidates the same way with `"type": "webrtc_ice"`. Apply the phone's `webrtc_ice` events as they arrive.

**5. Stop when done**

`POST /v1/sessions/{id}/stream/stop`.

`signal` passes the rest of `message` to the phone unchanged. Any `message.type` other than `webrtc_answer` or `webrtc_ice` returns `400`.

## Several viewers

Pass the same `viewer_id` to `start`, `connect`, `keepalive`, `request_frame` and `stop` when several viewers watch one phone. It defaults to the session id.

## Credential expiry

The TURN credential from `ice-servers` has an `expiresAt` time (Unix seconds). Renewing it during a live view is not available through the API yet. After `expiresAt`, start a new live view.


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