Authentication
Every endpoint accepts the lc_session cookie minted by POST /v1/auth/login. The cookie is HttpOnly, Secure, and SameSite=Lax. Browser clients send it automatically when they fetch with credentials: 'include'; server-to-server callers attach it via the Cookie: header or pass the JWT directly with Authorization: Bearer <jwt>.
For machine-to-machine integrations we recommend the workspace API keys path below — a project-scoped key avoids the cookie rotation complexity of the user session.
Errors
Errors follow RFC 7807 problem+json — every non-2xx response includes a JSON body of the form { "type", "title", "status", "detail", "code" }. The code field is a stable string suitable for programmatic dispatch (e.g. slug_taken, forbidden_workspace_role, plan_required).
Endpoints
Each block below has cURL / TypeScript / Python samples — click a tab and the copy button on the right yanks the snippet to your clipboard. Sections are anchored, so you can deep-link a specific endpoint into a support ticket or PR description.
/v1/workspace/meReturns the caller's identity, primary org, and entitlements.
First call most integrations make. Returns the authenticated user, their primary organisation, deployment-mode (cloud or self_host), and the workspace role used to gate every other endpoint on this page.
Try it
curl https://api.levelchat.io/v1/workspace/me \
-b "lc_session=$LC_SESSION"Responses
200OK{
"user_id": "usr_01J7BZ9WK4XRT4...",
"email": "[email protected]",
"display_name": "Alice",
"org": {
"id": "org_01J7BZ9WK4XRT4...",
"name": "Acme",
"role": "Owner"
},
"plan_selected": true,
"deployment_mode": "cloud"
}401Unauthorized— Missing or invalid session/v1/workspace/projectsList every project in the caller’s org.
Try it
curl https://api.levelchat.io/v1/workspace/projects \
-b "lc_session=$LC_SESSION"Responses
200OK{
"items": [
{
"id": "prj_01J7C0...",
"name": "Production",
"slug": "prod",
"environment": "prod",
"created_at": "2026-04-12T09:30:11Z"
}
],
"next_cursor": null
}/v1/workspace/projectsCreate a project — a logical container for rooms, API keys, webhooks.
Body
| Field | Type | Description |
|---|---|---|
| name* | string | 1–80 characters. Shown in Studio + meet dashboard. |
| slug | string | Lowercase URL slug — regex ^[a-z0-9-]{3,63}$. Auto-generated from name when omitted. 409 slug_taken on collision. |
| environment | 'prod' | 'staging' | 'dev'= prod | Free-form label used for filtering in the dashboard. |
* required
Try it
curl -X POST https://api.levelchat.io/v1/workspace/projects \
-b "lc_session=$LC_SESSION" \
-H "content-type: application/json" \
-d '{
"name": "Production",
"slug": "prod",
"environment": "prod"
}'Responses
201Created{
"id": "prj_01J7C0...",
"name": "Production",
"slug": "prod",
"environment": "prod",
"created_at": "2026-04-12T09:30:11Z"
}403Forbidden— Caller role lacks projects:create409Conflict— slug_taken422Validation failed/v1/workspace/roomsList rooms in the caller’s org. Filter by project / state.
Query
| Field | Type | Description |
|---|---|---|
| project_id | string | Restrict to one project. |
| state | 'created' | 'active' | 'ended' | Lifecycle filter. |
| limit | integer= 50 | 1–200. Pages with `next_cursor`. |
Try it
curl "https://api.levelchat.io/v1/workspace/rooms?state=active&limit=20" \
-b "lc_session=$LC_SESSION"Responses
200OK{
"items": [
{
"id": "rm_01J7C1...",
"name": "Weekly standup",
"type": "meeting",
"state": "active",
"project_id": "prj_01J7C0...",
"short_code": "ABCDEF12",
"created_at": "2026-05-07T08:15:00Z"
}
],
"next_cursor": null
}/v1/workspace/roomsCreate a meeting room. Returns a `short_code` for the share link.
Rooms support four canonical topologies: meeting, 1to1, webinar, live (per services/shared-go/capabilities/capabilities.go::RoomType). The legacy aliases broadcast, one-to-one, and p2p normalise at ingress for backwards-compat. The returned short_code is the canonical share-URL fragment — https://meet.your-domain/r/{short_code} — without leaking the room id.
Body
| Field | Type | Description |
|---|---|---|
| project_id* | string | Must belong to caller's org. |
| name | string | Display name. ≤ 200 chars. |
| type | 'meeting' | 'live' | 'webinar' | '1to1'= meeting | Canonical topology — drives the layout in apps/meet + SDK behaviour. Legacy aliases broadcast, one-to-one, and p2p normalise at ingress. |
* required
Try it
curl -X POST https://api.levelchat.io/v1/workspace/rooms \
-b "lc_session=$LC_SESSION" \
-H "content-type: application/json" \
-d '{
"project_id": "prj_01J7C0...",
"name": "Weekly standup",
"type": "meeting"
}'Responses
201Created{
"id": "rm_01J7C1...",
"name": "Weekly standup",
"type": "meeting",
"state": "created",
"project_id": "prj_01J7C0...",
"short_code": "ABCDEF12",
"created_at": "2026-05-07T08:15:00Z"
}403Forbidden— Caller role lacks rooms:create404Not found— project_id not in caller's org422Validation failed/v1/workspace/recordingsList recordings across the caller’s org.
Try it
curl https://api.levelchat.io/v1/workspace/recordings \
-b "lc_session=$LC_SESSION"Responses
200OK{
"items": [
{
"id": "rec_01J7C2...",
"room_id": "rm_01J7C1...",
"state": "completed",
"started_at": "2026-05-07T08:15:30Z",
"duration_sec": 1842,
"size_bytes": 245760000
}
]
}/v1/workspace/recordings/:id/downloadMint a 5-minute presigned S3 URL for the recording artifact.
Bytes flow direct from S3 to the browser — they never traverse admin-api. The returned URL has a 5-minute TTL and a response-content-disposition: attachment header so the browser saves it to disk instead of inlining it.
Path
| Field | Type | Description |
|---|---|---|
| id* | string | Recording id from `list-recordings`. |
* required
Try it
curl -X POST https://api.levelchat.io/v1/workspace/recordings/rec_01J7C2.../download \
-b "lc_session=$LC_SESSION"Responses
200OK{
"download_url": "https://s3.eu-central-1.amazonaws.com/levelchat-recordings/...",
"filename": "weekly-standup-2026-05-07.mp4",
"expires_at": "2026-05-07T08:25:00Z"
}404Not found— Recording not in caller’s org/v1/workspace/webhooksList every webhook endpoint in the org.
Try it
curl https://api.levelchat.io/v1/workspace/webhooks \
-b "lc_session=$LC_SESSION"Responses
200OK{
"items": [
{
"id": "wh_01J7C3...",
"url": "https://api.acme.com/levelchat-events",
"events": ["room.ended", "recording.completed"],
"active": true,
"created_at": "2026-05-01T12:00:00Z"
}
]
}/v1/workspace/webhooksRegister a new webhook endpoint. Returns the signing secret once.
Every delivery includes an X-LC-Webhook-Sig header — base64(HMAC-SHA-256(secret, X-LC-Webhook-Ts + "." + raw_body)) (see /guides/webhooks for the full header set). The secret is shown only on creation; if you lose it, rotate the endpoint instead of trying to re-read it.
Body
| Field | Type | Description |
|---|---|---|
| url* | string | HTTPS callback URL. localhost / 127.0.0.1 rejected in cloud. |
| events* | string[] | One or more of room.created, room.ended, recording.started, recording.completed, recording.failed. |
| description | string | Free-form note shown in the dashboard. |
* required
Try it
curl -X POST https://api.levelchat.io/v1/workspace/webhooks \
-b "lc_session=$LC_SESSION" \
-H "content-type: application/json" \
-d '{
"url": "https://api.acme.com/levelchat",
"events": ["recording.completed"]
}'Responses
201Created{
"id": "wh_01J7C3...",
"url": "https://api.acme.com/levelchat",
"events": ["recording.completed"],
"active": true,
"signing_secret": "whsec_...",
"created_at": "2026-05-07T08:30:00Z"
}422Validation failed— url not HTTPS / events emptyX-LC-Webhook-Sig header doesn't match base64(HMAC-SHA-256(secret, X-LC-Webhook-Ts + "." + raw_body)) (canonical-string format; the timestamp is bound into the signature to defeat replay). Retry schedule per services/webhooks/internal/delivery/retry.go is 0s, 10s, 1m, 5m, 15m, 1h, 4h (7 attempts, ±20% jitter, ~5h 19m total window); HTTP 410 Gone terminates the schedule immediately. See /guides/webhooks for Node / Python / Go verifier examples./v1/workspace/projects/:projectId/api-keysMint a project-scoped API key. Returns the secret once.
Use API keys for server-to-server integrations — they avoid the cookie rotation complexity of lc_session. Keys are scoped to a single project; revoke individual keys when a CI runner or service is decommissioned.
Path
| Field | Type | Description |
|---|---|---|
| projectId* | string | Project that owns the key. |
* required
Body
| Field | Type | Description |
|---|---|---|
| name* | string | Human-readable label, e.g. "ci-runner". |
| scope | 'rooms:read' | 'rooms:write' | 'recordings:read' | 'recordings:write' | 'webhooks:write' | 'all'= all | Permission scope. Most integrations want `all`. |
* required
Try it
curl -X POST https://api.levelchat.io/v1/workspace/projects/prj_01J7C0.../api-keys \
-b "lc_session=$LC_SESSION" \
-H "content-type: application/json" \
-d '{ "name": "ci-runner", "scope": "all" }'Responses
201Created{
"id": "key_01J7C4...",
"name": "ci-runner",
"scope": "all",
"secret": "lc_live_...",
"prefix": "lc_live_5K4F",
"created_at": "2026-05-07T08:45:00Z"
}/v1/workspace/projects/:projectId/api-keys/:id/rotateIssue a new secret and invalidate the previous one.
Try it
curl -X POST https://api.levelchat.io/v1/workspace/projects/prj_01J7C0.../api-keys/key_01J7C4.../rotate \
-b "lc_session=$LC_SESSION"Responses
200OK{
"id": "key_01J7C4...",
"secret": "lc_live_NEW...",
"prefix": "lc_live_9XR8"
}/v1/workspace/projects/:projectId/api-keys/:idPermanently revoke an API key. Cannot be undone.
Try it
curl -X DELETE https://api.levelchat.io/v1/workspace/projects/prj_01J7C0.../api-keys/key_01J7C4... \
-b "lc_session=$LC_SESSION"Responses
204No content404Not found/v1/workspace/meet/invitationsEmail up to 50 recipients a branded invite for a room.
Used by the meet dashboard's “Invite people” modal. Fans out via SMTP async; the response returns immediately with the count of accepted vs. invalid addresses.
Body
| Field | Type | Description |
|---|---|---|
| room_id* | string | Room id (or short_code) to invite people to. |
| recipients* | string[] | 1–50 RFC5322 addresses. Invalid ones are returned in `rejected`. |
| note | string | Optional 0–2000 char personal note rendered in the email body. |
* required
Try it
curl -X POST https://api.levelchat.io/v1/workspace/meet/invitations \
-b "lc_session=$LC_SESSION" \
-H "content-type: application/json" \
-d '{
"room_id": "rm_01J7C1...",
"recipients": ["[email protected]", "[email protected]"],
"note": "Standup at 10:00 EU time"
}'Responses
200OK{
"queued": 2,
"rejected": []
}422Validation failed— 0 valid recipients or > 50See also
- Gateway (REST) — the operator surface (
/v1/admin/*). - Webhooks (AsyncAPI) — full event catalog + signature verification.
- Auth — sessions, magic links, room-token minting.