API Reference
Complete endpoint documentation for the Synento server API. Base URL: https://api.synento.com
Complete endpoint documentation for the Synento server API.
Base URL: https://api.synento.com in production, http://localhost:8080 when
running the server locally.
Authentication
Most authenticated endpoints accept either:
- API key —
Authorization: Bearer sk_live_xxxxxorX-API-Key: sk_live_xxxxx(required for direct API, CLI, and server-side integrations) - Dashboard login session —
synento_sessionHTTP-only cookie set byPOST /v1/auth/login(used by the Synento dashboard)
An API key belongs to a project, but acts for its whole organisation on the management routes (projects, keys, webhooks, members). Keep it on your server.
Two narrower credentials exist for agents:
- Restricted key —
Authorization: Bearer rk_live_xxxxx. One project, and accepted only byPOST /v1/sessions,POST /v1/projects/{projectId}/sessions,POST /v1/sessions/{sessionId}/connection(live and replay) andGET /v1/sessions/{sessionId}/manifest. Every other route refuses it. This is what an agent writes into your app's environment. Manage them withGET/POST /v1/restricted-keysandDELETE /v1/restricted-keys/{keyId}. - Agent token (
sat_…) and OAuth access tokens — accepted only by the MCP endpoint at/mcp, never by the REST API. See Connect your agent. Agent tokens, connected OAuth clients and the agent audit log are managed from a signed-in session:/v1/agent-tokens,/v1/oauth/grants,/v1/agent-activity.
Response Format
All JSON responses use a standard envelope:
{ "data": { ... }, "error": null } // Success
{ "data": null, "error": { "code": "...", "message": "..." } } // ErrorError codes: BAD_REQUEST (400), NOT_FOUND (404), UNAUTHORIZED (401), FORBIDDEN (403), CONFLICT (409), SERVER_ERROR (500).
Health
Get Health Status
| Method | GET |
| Path | /health |
| Auth | None (public) |
Response: plain text ok or error message.
Projects
List Projects
| Method | GET |
| Path | /v1/projects |
| Auth | API key (scoped to organisation) |
Response:
{ "data": [
{ "id": "...", "name": "My App", "org_id": "...", "environment": null, "created_at": "..." }
], "error": null }Create Project
| Method | POST |
| Path | /v1/projects |
| Auth | API key (scoped to organisation) |
Request body:
{ "name": "My App", "environment": "production" }Response (201):
{ "data": { "id": "...", "name": "My App", "org_id": "...", "environment": "production" }, "error": null }Session Management
Create Session
| Method | POST |
| Path | /v1/projects/:projectId/sessions |
| Auth | API key (scoped to project) |
Request body:
{ "name": "Morning Standup" }Response (201):
{ "data": {
"id": "...", "project_id": "...", "name": "Morning Standup",
"created_by": "api-key-user",
"started_at": null, "ended_at": null, "created_at": "..."
}, "error": null }Billing: New session creation is blocked if free tier limits are exceeded and no active Stripe subscription.
Get Session
| Method | GET |
| Path | /v1/projects/:projectId/sessions/:sessionId |
| Auth | API key (scoped to project) |
Response: single Session object in envelope. 404 if not found or wrong project.
List Sessions
| Method | GET |
| Path | /v1/sessions |
| Auth | API key (scoped to project) |
Returns all sessions for the authenticated project, sorted by created_at DESC. No projectId in the path.
Response:
{ "data": [
{ "id": "...", "project_id": "...", "name": "Session 1", ... },
{ "id": "...", "project_id": "...", "name": "Session 2", ... }
], "error": null }Set Session Visibility
| Method | PUT |
| Path | /v1/sessions/:sessionId/visibility |
| Auth | API key (scoped to project) |
Request body:
{ "is_public": true } // or false to make privateResponse:
{ "data": true, "error": null } // updated visibility stateGet Connection Token (Session)
| Method | POST |
| Path | /v1/sessions/:sessionId/connection |
| Auth | API key (scoped to project) |
Request body:
{ "user_id": "alice",
"replay": false, // omit or false for live; true for replay mode
"from_ts": 0, // optional: start replay from this timestamp (ms)
"speed": 1.0, // optional: replay speed multiplier
"focus": "" // optional: focus on specific stream
}Response (200):
{ "data": {
"access_token": "...", // JWT token, short-lived (5 min)
"expires_in": 300, // seconds until expiry
"session": "...", // session ID
}, "error": null }Live Streaming (WebSocket)
Connect via WebSocket
| Method | GET (upgrade) |
| Path | /ws?session={sessionId}&access_token={token}&user_id=alice |
| Auth | Connection token (JWT) from /connection endpoint |
Connects to the live stream. Messages received are JSON events:
{ "type": "message", "stream_type": "media/alice", "timestamp_ms": 1234567, "body": "<base64-jpeg>" }
{ "type": "message", "stream_type": "chat", "timestamp_ms": 1234568, "body": "<base64-json>" }
{ "type": "control", ... } // join/leave/control signals as meta eventsSession Events
Create Session
| Method | POST |
| Path | /v1/sessions |
| Auth | API key (project-scoped) |
Creates a session for the authenticated project. Same response shape as creating a project-scoped session.
Get Session Timeline
| Method | GET |
| Path | /v1/sessions/:sessionId/timeline |
| Auth | API key (project-scoped) or dashboard login session (synento_session cookie) |
Returns all events in a session's timeline. For large sessions, prefer the windowed playback API below.
Replay & Playback
Get Playback Timeline (Metadata)
| Method | GET |
| Path | /v1/sessions/:sessionId/playback/timeline |
| Auth | API key (project-scoped) or public session access |
Returns compact timeline metadata without loading full event data:
{ "data": {
"participant_count": 3,
"stream_ids": ["events", "director", "chat", "media/alice/cam", "media/alice/mic", "media/bob/cam"],
"total_events_estimated": 15420,
"min_ts": 1699876000000,
"max_ts": 1699876300000
}, "error": null }Get Session Manifest
| Method | GET |
| Path | /v1/sessions/:sessionId/manifest |
| Auth | API key (project-scoped) or public session access |
Returns the layout-agnostic reconstruction contract: participants, separated track index (cam/mic/screen), time bounds, stream paths, and capabilities (separated-tracks, event-timeline, director-track, configurable-layouts, captions when present).
{ "data": {
"version": 1,
"session_id": "sess-abc123",
"epoch": 1699876000000,
"start_ts": 1699876000000,
"end_ts": 1699876300000,
"participants": [
{ "user_id": "alice", "tracks": [
{ "kind": "cam", "stream_id": "a/.../media/alice/cam", "start_ts": 1000, "end_ts": 5000 }
]}
],
"streams": { "events": "a/.../events", "chat": "a/.../chat", "director": "a/.../director" },
"capabilities": ["separated-tracks", "event-timeline", "configurable-layouts"]
}, "error": null }Public sessions: GET /v1/public/sessions/:sessionId/manifest.
Get Playback Window (Windowed Stream Reading)
| Method | GET |
| Path | /v1/sessions/:sessionId/playback?from_ts={ms}&window_size=30000 |
| Auth | API key (project-scoped) or public session access |
Query parameters:
from_ts— milliseconds to start reading from (required)window_size— size of window in ms (optional, defaults 30s)
Response:
{ "data": {
"events": [
{ "seq": 100, "timestamp_ms": 1699876030000, "stream_id": "a/.../media/alice/cam",
"body_base64": "<base64-payload>" }
],
"base_seq": 100,
"next_base_seq": null
}, "error": null }Playback windows include events from all separated media tracks, the events timeline, director track, and chat. Media payloads are raw track bytes (WebCodecs EVC1 or MJPEG); timeline events are JSON in the envelope { v, type, ts, actor, data }.
Get Public Playback (Unauthenticated)
| Method | GET |
| Path | /v1/public/sessions/:sessionId/playback?from_ts={ms}&window_size=30000 |
| Auth | None — only works for public sessions (is_public = true) |
Same response shape as authenticated playback, but only accessible for public sessions.
Get Public Playback Timeline (Unauthenticated)
| Method | GET |
| Path | /v1/public/sessions/:sessionId/playback/timeline |
| Auth | None — public sessions only |
Same response as authenticated timeline, for public sessions.
Replay via Expiring Link
| Method | GET |
| Path | /v1/public/sessions/:sessionId/replay?token={linkToken}&from_ts=0&window_size=30000 |
| Auth | None — replay token required (or public session) |
Reads a playback window using an expiring replay link token. Same response shape as public playback.
Export (MP4)
Export is asynchronous. Enqueue a job, poll status, then download the completed MP4.
Send a project API key on every step below.
Enqueue Session Export
| Method | GET |
| Path | /v1/sessions/:sessionId/export |
| Auth | API key (project-scoped) or dashboard login session (synento_session cookie) |
Query parameters (optional):
| Parameter | Values | Default |
|---|---|---|
layout | grid, presenter | grid |
orientation | landscape (1280×720), vertical (720×1280) | landscape |
focus | Participant user id (for presenter layout) | first participant |
Response (202):
{ "data": { "job_id": "550e8400-e29b-41d4-a716-446655440000" }, "error": null }Returns 503 with USAGE_LIMITS_EXCEEDED when free tier limits are exceeded.
Poll Export Job Status
| Method | GET |
| Path | /v1/exports/:jobId |
| Auth | API key (project-scoped) or dashboard login session (synento_session cookie) |
Response:
{ "data": {
"job_id": "550e8400-e29b-41d4-a716-446655440000",
"status": "completed",
"room_id": "sess-abc123",
"download_url": "/v1/exports/550e8400-e29b-41d4-a716-446655440000/download"
}, "error": null }Status values: queued, processing, completed, failed.
Download Export MP4
| Method | GET |
| Path | /v1/exports/:jobId/download |
| Auth | API key (project-scoped) or dashboard login session (synento_session cookie) |
Response: binary MP4 data when the job is completed.
Participant Redaction
Redact Participant
| Method | DELETE |
| Path | /v1/sessions/:sessionId/participants/:userId |
| Auth | API key (project-scoped) |
Deletes all media track streams for the participant (cam, mic, screen) and appends a participant.redacted event to the timeline. The rest of the session remains intact.
Response:
{ "data": { "redacted": "alice", "deletedStreams": 3 }, "error": null }Webhooks
List Webhooks
| Method | GET |
| Path | /v1/webhooks |
| Auth | API key (project-scoped) |
Response: array of active webhooks for the project.
{ "data": [
{ "id": "...", "project_id": "...", "url": "https://example.com/hook",
"events": ["session.created", "session.ended"], "is_active": true }
], "error": null }Create Webhook
| Method | POST |
| Path | /v1/webhooks |
| Auth | API key (project-scoped) |
Request body:
{ "url": "https://example.com/hook",
"events": ["session.created", "session.started", "session.ended"] }Valid event types: session.created, session.started, session.ended, recording.ready, export.completed
Response (201):
{ "data": { "id": "...", "project_id": "...", "url": "...",
"events": ["session.created"], "is_active": true }, "error": null }Usage & Billing
Get Usage Records
| Method | GET |
| Path | /v1/projects/:projectId/usage (project-scoped) or /v1/usage |
| Auth | API key (project-scoped) or dashboard login session (synento_session cookie) |
Response:
{ "data": {
"summary": { "streaming_minutes_total": 125.3, "storage_bytes_total": 524288000 },
"records": [
{ "id": "...", "project_id": "...", "session_id": "...",
"usage_type": "streaming_minutes", "value": 125.3,
"recorded_at": "..." }
]
}, "error": null }Get Billing Summary
| Method | GET |
| Path | /v1/billing/summary |
| Auth | API key (project-scoped) — no projectId in path required |
Response:
{ "data": {
"streaming_minutes_used": 125.3,
"storage_bytes_used": 524288000,
"cost_breakdown": {
"streaming_cost": 12.53,
"storage_cost": 0.03,
"total_due": 12.56
}
}, "error": null }Get Billing Limits
| Method | GET |
| Path | /v1/billing/limits |
| Auth | API key (project-scoped) or dashboard login session (synento_session cookie) |
Response:
{ "data": {
"within_free_tier": true,
"streaming_minutes_used": 125.3,
"storage_bytes_used_gb": 0.5,
"has_active_subscription": false,
"is_blocked": false,
"status_message": ""
}, "error": null }Create Stripe Billing Portal Session (Project-scoped)
| Method | POST |
| Path | /v1/projects/:projectId/billing/portal |
| Auth | API key (project-scoped) or dashboard login session (synento_session cookie) |
Returns a Stripe billing portal URL. Response includes portal_url for redirecting the user to manage their subscription.
Create Stripe Billing Portal Session (Org-scoped)
| Method | POST |
| Path | /v1/billing/portal |
| Auth | API key (project-scoped) — no projectId needed |
Same as above but uses organisation-level Stripe customer. Recommended for dashboards (no projectId required).
API Key Management
List API Keys
| Method | GET |
| Path | /v1/api-keys |
| Auth | API key (project-scoped) — no projectId needed in path |
Returns metadata for keys belonging to the authenticated project. Secrets are not included (only ID and creation date).
Response:
{ "data": [
{ "id": "...", "project_id": "...", "created_at": "..." }
], "error": null }Create API Key
| Method | POST |
| Path | /v1/projects/:projectId/api-keys |
| Auth | API key (project-scoped) |
Response (201): returns the new key's ID and raw secret (sk_live_...). The secret is shown only once — store it securely.
Delete API Key
| Method | DELETE |
| Path | /v1/api-keys/:keyId |
| Auth | API key (project-scoped) — no projectId needed in path |
Response: success confirmation. The deleted key is immediately invalidated.
Authentication & User Management (Email/Password)
Register
| Method | POST |
| Path | /v1/auth/register |
| Auth | None (public) |
Request body: {"email": "user@example.com", "password": "secret123"}
Response (201): creates user + organisation + project, sets session cookie.
{ "data": { "id": "...", "email": "user@example.com", ... }, "error": null }Login
| Method | POST |
| Path | /v1/auth/login |
Request body: {"email": "user@example.com", "password": "secret123"}
Response (200): sets session cookie (synento_session). Session expires in 7 days.
Logout
| Method | POST |
| Path | /v1/auth/logout |
Response (200): clears session cookie.
Get Current User
| Method | GET |
| Path | /v1/me |
Response (200): returns current authenticated user data.
{ "data": { "id": "...", "email": "user@example.com" }, "error": null }Organisation Membership (Invitations)
Send Invitation to Project's Organisation
| Method | POST |
| Path | /v1/projects/:projectId/invitations |
| Auth | API key (project-scoped) |
Request body: {"email": "new@example.com", "role": "member"}
Response (201):
{ "data": { "id": "...", "org_id": "...", "email": "new@example.com",
"role": "member", "created_by": "...", "accepted_at": null }, "error": null }Redeem Invitation Token
| Method | POST |
| Path | /v1/invitations/redeem |
Request body: {"token": "invite-token-xyz"}
Auth: session cookie (user must be logged in). Updates the user's org_id and role.
Response (200): success confirmation with updated org membership.
Public Session Catalog
List Public Sessions
| Method | GET |
| Path | /v1/public/sessions |
| Auth | None (public) |
Returns all sessions with is_public = true, sorted by creation date DESC. No project IDs exposed for privacy.
Response:
{ "data": [
{ "id": "...", "name": "Public Demo", "created_at": "...",
"started_at": "...", "ended_at": "..." }
], "error": null }Replay Links
Create Expiring Replay Link
| Method | POST |
| Path | /v1/sessions/:sessionId/replay-links |
| Auth | API key (project-scoped) |
Request body: {"expires_in_minutes": 60} (optional, defaults to a reasonable TTL)
Response (201):
{ "data": {
"link": "https://host/v1/public/sessions/{sessionId}/replay?token={token}",
"token": "...",
"expires_at": "..."
}, "error": null }The replay link provides unauthenticated windowed playback access until it expires (max 30 days = 43200 minutes).
Synento Documentation
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.
Browser support
Which browsers can run a Synento session, how the SDK reports it, and what has actually been verified.