# Synento --- --- > Synento is an agent-native video API. Browsers encode with WebCodecs and send binary frames over a WebSocket, and every session is appended to durable streams, so each session is already its own recording. Replay, windowed playback and MP4 export read the same streams that carried the live call. --- --- Key facts for agents: the fastest path is the remote MCP server at https://api.synento.com/mcp (Streamable HTTP, OAuth 2.1): it provisions, mints a restricted rk_live_ key for the app's .env, and verifies media with verify_session. Without MCP: the API key is a server-side secret (never send it to a browser); browsers join with short-lived connection tokens minted per session and user; POST /v1/sessions accepts an Idempotency-Key header; every response is the envelope { data, error }; verify an integration by reading GET /v1/sessions/{id}/manifest — a track is listed only once media reached it, so check each participant has both cam and mic. --- --- # Synento Documentation Source: https://synento.com/docs > Synento is an agent-native video API — video that agents can set up independently. Add live video, session replay, and on-demand playback to any application with a few API calls. Synento is an agent-native video API — video that agents can set up independently. Add live video, session replay, and on-demand playback to any application with a few API calls. ## Quick Links - [Getting Started](https://synento.com/docs/getting-started.md) — Set up your first project and stream in minutes - [Core Concepts](https://synento.com/docs/concepts.md) — Understand projects, sessions, and the stream-native recording model - [API Reference](https://synento.com/docs/api-reference.md) — Complete endpoint documentation with request/response examples ## SDK packages | Package | Use | |---------|-----| | [`@synento/client`](https://www.npmjs.com/package/@synento/client) | Browser: publish and receive live audio, video, and screen share | | [`@synento/react`](https://www.npmjs.com/package/@synento/react) | React provider, hooks, and components over `@synento/client` | | [`@synento/node`](https://www.npmjs.com/package/@synento/node) | Server: create sessions, mint connection tokens, manage projects | | [`@synento/player`](https://www.npmjs.com/package/@synento/player) | Embeddable replay engine with layout composition | | [`@synento/cli`](https://www.npmjs.com/package/@synento/cli) | Command-line project and session management | ## Guides - [Webhooks](https://synento.com/docs/guides/webhooks.md) — Set up event-driven integrations with webhooks - [Recordings & Replay](https://synento.com/docs/guides/recordings-replay.md) — How stream-based recording and replay work - [Access Control](https://synento.com/docs/guides/access-control.md) — Public sessions, expiring links, and visibility - [Session Management](https://synento.com/docs/guides/sessions.md) — Deep dive into session lifecycle and controls - [CLI Guide](https://synento.com/docs/guides/cli.md) — Use the Synento CLI for project and session management - [SDK Guide](https://synento.com/docs/guides/sdk.md) — Integrate using the JavaScript/TypeScript SDK - [Browser Support](https://synento.com/docs/browser-support.md) — Which browsers work, what the SDK reports, and what has been verified ## Examples Copy-paste examples ready to run: - [Web Quickstart](https://synento.com/docs/examples/web-quickstart.md) — Embed a live video stream in any web page - [SDK Basic Usage](https://synento.com/docs/examples/sdk-basic.md) — Create sessions and manage them with the SDK - [SDK Advanced Patterns](https://synento.com/docs/examples/sdk-advanced.md) — Replay, exports, and webhooks with the SDK - [CLI Examples](https://synento.com/docs/examples/cli-examples.md) — Common CLI workflows ## Key Features | Feature | Description | |---------|-------------| | Live Streaming | Multi-participant WebSocket-based video streaming with adaptive scaling | | Stream-Native Recording | Separated tracks (cam/mic/screen) + typed event timeline — layout-agnostic | | Composable Replay | Configurable layouts (grid, active-speaker, presenter, screen-share) at playback time | | Director Track | Deterministic active-speaker switching from persisted layout decisions | | Replay | Instant replay with speed control, seek, layout switching, and windowed reading | | MP4 Export | Async export with layout and orientation parameters; local or S3/R2 artifact storage | | Public Sessions & Links | Share sessions publicly or via expiring token-based replay links | | Webhooks | Event-driven callbacks for session lifecycle events | | SDK & CLI | TypeScript SDK and command-line tool for programmatic access | ## API Overview Synento exposes all functionality through REST APIs. Authentication is via API keys with the `Authorization: Bearer sk_...` header. All JSON responses follow a standard envelope format: ```json { "data": { ... }, "error": null } // Success { "data": null, "error": { "code": "...", "message": "..." } } // Error ``` Key endpoint categories: - `POST /v1/projects` — Create projects - `POST /v1/sessions` — Create live sessions - `GET/POST /v1/sessions/:id/connection` — Get connection tokens for WebSocket streaming - `GET /v1/sessions/:id/manifest` — Session reconstruction contract (tracks, bounds, capabilities) - `GET /v1/sessions/:id/playback` — Windowed replay of recorded sessions - `GET /v1/sessions/:id/export` — Enqueue MP4 export (layout, orientation) - `GET /v1/exports/:jobId` — Poll export status and download URL See the full [API Reference](https://synento.com/docs/api-reference.md) for all 40+ endpoints. --- # Getting Started Source: https://synento.com/docs/getting-started > Get from zero to your first live video stream in under 5 minutes. Get from zero to your first live video stream in under 5 minutes. > **Using a coding agent?** Connect it to Synento over MCP and it can do all of > this for you — including proving the video works. See > [Connect your agent](https://synento.com/docs/guides/mcp.md). ## Prerequisites - A Synento account ([sign up free](https://synento.com/signup): 3,000 participant-minutes a month, no card) - A server-side key — an `sk_live_` API key from the dashboard or CLI, or an `rk_live_` restricted key (one project; enough for everything on this page) ## Option A: Use the SDK (Recommended) ### 1. Install the packages ```bash npm install @synento/node # server-side: create sessions, mint tokens npm install @synento/client # browser: publish and receive live audio/video ``` React applications can use [`@synento/react`](https://www.npmjs.com/package/@synento/react) for hooks and components, and [`@synento/player`](https://www.npmjs.com/package/@synento/player) for embedded replay. ### 2. Mint connection tokens on your server Your API key is a **server-side secret**. It never goes to the browser. Instead, your backend exposes one small route that creates a session and mints a short-lived connection token for the user asking to join. ```typescript // app/api/synento/route.ts (Next.js App Router) import { createSynentoClient, resolveWsUrl } from "@synento/node"; // Reads SYNENTO_API_KEY and SYNENTO_API_URL from the environment. const synento = createSynentoClient(); export const dynamic = "force-dynamic"; // or the route is cached and serves a stale token export async function POST(request: Request) { const { sessionId, userId } = await request.json(); const token = await synento.createConnectionToken(sessionId, { userId }); return Response.json({ token: token.accessToken, wsUrl: resolveWsUrl({ apiBaseUrl: process.env.SYNENTO_API_URL ?? "https://api.synento.com", requestOrigin: request.headers.get("origin"), sessionId, userId, }), }); } ``` Use `resolveWsUrl` rather than assembling the URL yourself — it handles the loopback-hostname case that otherwise makes local development fail in a way that produces no error at all, just a call that never connects. ### 3. Join from the browser ```typescript import { SynentoRoom } from "@synento/client"; const room = new SynentoRoom({ userId: "alice", // Re-invoked on every reconnect, so tokens rotate automatically. getToken: async () => { const res = await fetch("/api/synento", { body: JSON.stringify({ sessionId, userId: "alice" }), method: "POST", }); return res.json(); // { wsUrl, token } }, }); room.on("participantsChanged", (ids) => console.log("in the room:", ids)); await room.connect(); // Publish. The second argument is optional — pass your local preview element // if you have one. A stream with no video tracks publishes audio only. const stream = await navigator.mediaDevices.getUserMedia({ audio: true, video: true }); await room.publishCamera(stream, previewVideoEl); ``` Media travels as **binary frames**, not JSON — `@synento/client` handles the framing, decoding and reconnection. Do not open a raw `WebSocket` and try to `JSON.parse` messages off it. **Recording needs no code.** The session's durable streams *are* the recording, so there is no start-recording call and no recording flag. ### 4. See it running [demo.synento.com](https://demo.synento.com) is a two-way call built on exactly these packages. Open it in two tabs, join the same session, then leave and replay it: the recording exists the moment the call does. ## Option B: Use HTTP API Directly (cURL) ### 1. Create a project ```bash curl -X POST https://api.synento.com/v1/projects \ -H "Authorization: Bearer sk_live_xxxxx" \ -H "Content-Type: application/json" \ -d '{"name": "My Project"}' ``` ### 2. Create a session ```bash curl -X POST https://api.synento.com/v1/projects/{projectId}/sessions \ -H "Authorization: Bearer sk_live_xxxxx" \ -H "Content-Type: application/json" \ -d '{"name": "Demo Session"}' ``` ### 3. Get a connection token ```bash curl -X POST https://api.synento.com/v1/sessions/{sessionId}/connection \ -H "Authorization: Bearer sk_live_xxxxx" \ -H "Content-Type: application/json" \ -d '{"user_id": "viewer-1"}' ``` ### 4. Connect to WebSocket streaming Once you have the `access_token` from step 3, open a WebSocket. Prefer sending the token as a **subprotocol**, so it stays out of URLs and access logs: ``` wss://api.synento.com/ws?session={sessionId}&user_id=viewer-1 Sec-WebSocket-Protocol: synento.v1, synento.token.{accessToken} ``` The query-string form (`&access_token=...`) is still accepted, but logs the token. What travels over that socket is **binary**, in a self-describing frame format — not JSON. Unless you are implementing a client from scratch, use [`@synento/client`](https://www.npmjs.com/package/@synento/client), which handles framing, codec negotiation, decoding, backpressure and reconnection. ## Option C: Use the CLI ```bash # Login with your API key npx synento login sk_live_xxxxx # Create a project (interactive or with name argument) npx synento create-project "My Project" # Create a session (requires project ID) npx synento create-session "Demo Session" -p {projectId} ``` ## Option D: Let your coding agent do it Connect your agent to Synento's MCP server and ask it to add video to your app: ```bash claude mcp add --transport http synento https://api.synento.com/mcp ``` It mints a restricted key for your `.env`, writes the server route and the browser code above, and then checks the recording with `verify_session` to confirm camera and microphone actually arrived. See [Connect your agent](https://synento.com/docs/guides/mcp.md) for other clients and what you approve. ## Next Steps - Read [Core Concepts](https://synento.com/docs/concepts.md) to understand the architecture - Browse the full [API Reference](https://synento.com/docs/api-reference.md) - Try the copy-paste examples in [examples/](https://synento.com/docs/examples/web-quickstart.md) - Set up webhooks for event-driven workflows — [Webhooks Guide](https://synento.com/docs/guides/webhooks.md) ## Environment Variables | Variable | Description | Default | |----------|-------------|---------| | `SYNENTO_API_KEY` | Secret key. **Server-side only** — never expose it to a browser. | — | | `SYNENTO_API_URL` | Base URL of the Synento API. | `https://api.synento.com` | Both are read automatically by `createSynentoClient()` when you pass no arguments. ## Troubleshooting ### "Connection refused" on WebSocket - If you set `SYNENTO_API_URL`, check it is `https://api.synento.com` - Check that the connection token has not expired ### "401 Unauthorized" on API calls - Verify your API key starts with `sk_` - Check that the Authorization header uses `Bearer ` (no space before the key) ### Sessions not appearing in replay - Ensure the session has ended (replay reads completed sessions) - Check that events were actually sent during the stream --- # Web Quickstart — Live Video in a Web Page Source: https://synento.com/docs/examples/web-quickstart > Join a live Synento session from the browser with two-way audio and video, using a short-lived connection token minted by your server. Join a live Synento session from the browser with two-way audio and video. It takes two pieces: a small server route that mints a short-lived connection token, and a page that uses `@synento/client` to publish and receive media. ## Never put your API key in the browser Your `sk_live_…` key can create sessions, read recordings, and change billing. It must stay on your server. The browser only ever receives a **connection token**: a short-lived credential scoped to one session and one user. ``` browser ──POST /api/connection──▶ your server ──sk_live_…──▶ Synento API ◀── { wsUrl, token } ──────────────────────────────────────────┘ ``` ## 1. Install ```bash npm install @synento/client # browser npm install @synento/node # your server ``` ## 2. Mint a token on your server This example is a Next.js route handler; any server framework works the same way. Create the session once (or look up an existing one), then mint a token for the user who is joining. ```ts // app/api/connection/route.ts import { createSynentoClient, resolveWsUrl } from '@synento/node' // Reads SYNENTO_API_KEY and SYNENTO_API_URL from the environment. const synento = createSynentoClient() // Without this, Next may statically optimise the route and serve a stale token. export const dynamic = 'force-dynamic' export async function POST(request: Request) { const { sessionId, userId } = await request.json() // Authenticate the caller here — whoever gets a token can join this session. const token = await synento.createConnectionToken(sessionId, { userId }) return Response.json({ expiresIn: token.expiresIn, token: token.accessToken, // Let the SDK build this. Assembling the URL by hand works in production // and then fails on localhost, because the browser and the API commonly // disagree about how to spell loopback — and the rejection arrives as a // close(1008) *after* a successful upgrade, so it surfaces as a call that // silently never connects rather than as an error. wsUrl: resolveWsUrl({ apiBaseUrl: process.env.SYNENTO_API_URL ?? 'https://api.synento.com', requestOrigin: request.headers.get('origin'), sessionId, userId, }), }) } ``` ## 3. Join from the browser `SynentoRoom` handles encoding, transport, decoding, backpressure, and reconnection. Note that `getToken` is a *function*: the SDK calls it again on every reconnect, so tokens rotate without any work from you. ```ts import { SynentoRoom } from '@synento/client' const support = SynentoRoom.checkSupport() if (!support.supported) { throw new Error(support.reason) // e.g. this browser lacks WebCodecs } const userId = 'alice' const sessionId = 'YOUR_SESSION_ID' const room = new SynentoRoom({ userId, getToken: async () => { const res = await fetch('/api/connection', { body: JSON.stringify({ sessionId, userId }), headers: { 'Content-Type': 'application/json' }, method: 'POST', }) if (!res.ok) throw new Error('could not mint a connection token') return res.json() // { wsUrl, token } }, }) // Render each remote participant into a canvas you provide. room.on('participantsChanged', (participants) => { for (const id of participants) { if (document.querySelector(`canvas[data-user="${id}"]`)) continue const canvas = document.createElement('canvas') canvas.width = 640 canvas.height = 480 canvas.dataset.user = id document.body.append(canvas) room.attachParticipant(id, canvas) } }) await room.connect() // Publish camera and microphone. The