Control-plane API reference

Every route below accepts X-API-Key: sk_live_... (or sk_test_... in a test account). Publishable (pk_) keys are rejected on every route on this page. Raw machine-readable source: openapi/confcall-control-plane.json — contract-tested against the live controller auth attributes, so this table can't silently overclaim or underclaim what a key can do.

Auth legend: public no key needed · sk_ only no JWT alternative exists · JWT or sk_ either works, scoped to the caller's own room/account · JWT only not yet callable with an API key.

Create a room

sk_ only POST /api/embed/rooms

curl -X POST https://meet.confcall.app/api/embed/rooms \
  -H "X-API-Key: $CONFCALL_API_KEY" -H "Content-Type: application/json" \
  -d '{"roomName": "Acme onboarding call"}'

# 201 { "roomId": "room-abc123", "embedUrl": "https://meet.confcall.app/embed/room/room-abc123" }

Creates an embed-flagged room owned by the key's account (not a human user).

Mint a join session

sk_ only POST /api/embed/sessions

curl -X POST https://meet.confcall.app/api/embed/sessions \
  -H "X-API-Key: $CONFCALL_API_KEY" -H "Content-Type: application/json" \
  -d '{"roomId": "room-abc123", "externalUserId": "acme-user-42", "role": "host", "ttlSeconds": 3600}'

# 200 { "token": "...", "expiresAt": "...", "embedUrl": "https://meet.confcall.app/embed/room/room-abc123" }

Mints a short-lived, room-scoped join token for one of your users. role is host, co-host, or participant (default). A room that doesn't exist, isn't an embed room, or isn't owned by your account all return the same 404, so this can't be used to probe other accounts' room ids. Feed token into the widget's auth-token attribute — see Widget.

Room metadata & agent status

RouteAuthNotes
GET /api/rooms/{roomId} public Room name, share link, timestamps, and (if the caller owns/co-hosts it) co-organizers.
GET /api/rooms/{roomId}/ai-agent/status public Degrades to { agentPresent: false, available: false } rather than erroring.

Room management (post-Phase-1)

These routes used to be JWT-only; an sk_ key for the room's owning account can now call them directly, scoped by account ownership — a different account's key gets 403.

RouteAuthScopeNotes
DELETE /api/rooms/{roomId} JWT or sk_ confcall:rooms Deletes the room record. Doesn't force-disconnect an in-progress live call — that's a SignalR hub RPC, not REST.
POST /api/rooms/{roomId}/ai-agent/invite JWT or sk_ confcall:rooms Body: { mode?, inlineConfig?: { language } }. 409 if the room is E2EE-enabled.
POST /api/rooms/{roomId}/ai-agent/remove JWT or sk_ confcall:rooms
GET /api/rooms/{roomId}/artifacts JWT or sk_ confcall:history Summarized session list for the room (not full transcripts).
GET /api/artifacts/{sessionId} JWT or sk_ confcall:history Full transcript, summary, decisions, follow-ups, action items for one session.
GET /api/recordings?roomId= JWT or sk_ Without roomId, lists every recording the caller/key's account can see.
GET /api/recordings/{id}/stream JWT or sk_ Pre-signed playback URL (S3/Azure) or a signed token for the legacy local-file endpoint.

Still JWT-only

These two remain human-session-only — not yet callable with an sk_ key:

RouteAuthUse instead
GET /api/recordings/{id} JWT only GET /api/recordings?roomId= for the same fields via the list response.
GET /api/recordings/{id}/download JWT only GET /api/recordings/{id}/stream for a playable reference.

Errors

Every route on this page returns JSON error bodies shaped { "error": "..." }. A room/session/recording that exists but belongs to a different account returns 403; one that doesn't exist at all returns 404 — the two are kept distinct except where a route explicitly collapses them to avoid leaking existence (embed session minting, noted above).