POST /v1/session/{session_id}/view-token
Response (200):live_view_url is a path: prefix https://back.tilion.dev. The same token opens the WebSocket streams: wss://back.tilion.dev/v1/session/{id}/live?token=<token> and wss://back.tilion.dev/v1/session/{id}/webrtc?token=<token>. Errors: 404 or 410 for an unknown or ended session; 503 view tokens are not available on this deployment.
GET /v1/session/{session_id}/live-view
A ready-made interactive viewer page (text/html). Auth: ?token= from /view-token (no Bearer). Without a token: 401 missing view token; mint one with POST /v1/session/{id}/view-token. With a bad token: 403 invalid or expired view token. Those two answers come before any session lookup, so the status never reveals whether an id exists; an ended session with a valid token answers 410.
WS /v1/session/{session_id}/live
JPEG screencast of the page, interactive. URL:wss://back.tilion.dev/v1/session/{session_id}/live?token=<token> with the token from /view-token. The stream is a viewer: it does not hold the browser, so REST drive calls and fetch proceed normally while it is attached.
What a client receives: text frames, each one base64 of a JPEG of the current page at the viewport size, delivered continuously at about 2 frames per second; consecutive frames can be byte-identical on a static page, so de-duplicate client side if you are storing them. Frames carry no timestamp; stamp them on receipt. The stream ends when the session ends.
What a client sends, as JSON text frames:
{"t": "a"}after rendering a frame. Once you have sent one ack, frames are paced to your acks, so a slow viewer gets the newest frame instead of a growing backlog. A client that never acks gets every frame.- Mouse:
{"t": "m", "type": "mousePressed" | "mouseReleased" | "mouseMoved" | "mouseWheel", "x": 120, "y": 80, "button": "left", "clickCount": 1, "dx": 0, "dy": 120}(page coordinates;buttonandclickCountfor press and release;dx,dyfor wheel). - Keyboard:
{"t": "k", "type": "keyDown" | "keyUp" | "char", "key": "a", "code": "KeyA", "text": "a", "keyCode": 65}.
{"detail": {"error": "invalid_token", "message": "missing, expired or invalid token for this session; mint a new one"}}; an ended session is refused with its 410. The bytes streamed are billed as egress.
WS /v1/session/{session_id}/webrtc
A smooth video of the session over WebRTC, for viewers that can take it; the JPEG/live socket is the fallback. Auth: ?token=. The first message after the handshake:
RTCPeerConnection with those iceServers (the TURN credential is short-lived and minted per connection), send {"type": "offer", "sdp": "..."} and receive {"type": "answer", "sdp": "..."}; the video track then flows peer to peer, and input rides this socket with the same {"t": "m" ...} and {"t": "k" ...} events as /live. When HD streaming is not available for this session right now the socket closes with code 1013 right after the handshake: keep the /live view you already have. Video bytes are billed as egress.
POST /v1/session/{session_id}/vnc-token
For afull_browser: true session: a token and URL to watch and drive the whole Chrome window (noVNC), not just the page.
Response (200):
https://back.tilion.dev plus vnc_view_url in a browser or an iframe; it loads the viewer and connects it to the window. Errors: 404 full-browser (noVNC) view is not enabled on this deployment; 404 or 410 for the session; 503 when the token cannot be minted.
GET /v1/session/{session_id}/vnc/{path}
The noVNC viewer and its assets, served for one session. Auth:?token= from /vnc-token on the first load, which also sets a cookie scoped to this path so the viewer’s own module imports work; without either, 403 invalid or missing view token. Directories are not listed. Assets are billed as egress.
WS /v1/session/{session_id}/vnc-ws
The VNC transport the viewer uses; binary frames (RFB). Auth:?token= from /vnc-token. An attached VNC viewer holds the browser: REST drive calls answer 409 session_driven_by_client until it disconnects. Bytes are billed as egress.
Recordings
Setrecord: true on session creation to capture a frame replay. record: false disables capture; null uses the deployment default. A successful recording request returns replay_url, a tokenized path to an HTML player. Prefix it with https://back.tilion.dev. The player displays captured frames; the replay URL itself is not a video file.
Capture stops at the plan’s recorded-minute cap while the browser session can continue:
GET /v1/recordings
Auth: Bearer API key. Returns{"recordings": [...]}, most recent first, up to 100 rows. Add ?session_id=<id> to filter. Each row carries session_id, rec_id, status, frame_count, duration_s, bytes, tier, cap_minutes, created_at and expires_at. Counts may not be final while capture is running.
GET /v1/recordings/{session_id}/{rec_id}/view
Auth: Bearer API key. Mint recording-scoped URLs:
Prefix each returned path with
https://back.tilion.dev and use it as returned, including its token. Mint fresh URLs when they expire. Unknown or inaccessible recordings answer 404.
GET /v1/recordings/{session_id}/{rec_id}/download
Use the tokenizeddownload_url from /view. The default format is ZIP (application/zip), including captured frames, manifest.json and replay.html. A running recording can be downloaded with the frames captured so far. Close the session to finalize its recording.
format=mp4 requests H.264 export. While recording is running it answers 409 recording_in_progress. After close, an available encoder can return 202 {"status": "processing"} while encoding; retry the same URL until it returns video/mp4. If the encoder is unavailable, the route answers 503 encoder not available; use the ZIP or replay player. Invalid or expired authorization answers 403; a missing recording answers 404.
DELETE /v1/recordings/{session_id}/{rec_id}
Auth: Bearer API key. Delete the recording; success is204 No Content.