MediaSFU API Reference for AI Coding Tools
This page gives an AI coding assistant, or a developer in a hurry, everything
needed to build a working MediaSFU integration from one context window. The
same content is available as plain text at
/docs/llms-full.txt.
MediaSFU is real-time video, voice, AI agents, phone (SIP/PSTN) calls,
recording, live translation and HLS in one platform. You call its HTTP API from
your server and render rooms with an SDK (React, Angular, Vue, React Native,
Expo, Flutter, Kotlin, Swift, Unity) or the @mediasfu/widgets web components.
Ground rules
- Base URL:
https://mediasfu.com. Every API path starts with/v1/. - Auth header:
Authorization: Bearer <api-username>:<api-key>. Both values are at mediasfu.com/api-keys. The key is 64 characters. - Server only. Call the API from your backend. Never put the API key in browser, mobile or desktop code. For a browser that must act directly, issue a short-lived, scoped disposable key.
- Environments: a sandbox key creates sandbox rooms (IDs start with
s), a production key creates production rooms (IDs start withp, billed). Start with sandbox. - Actions: each resource takes an
actionfield: in the JSON body forPOST, in the query string forGET. - Responses: success is HTTP 2xx with
success: trueand the payload beside it. Failure is{ "success": false, "error": "…", "code": "NOT_FOUND" }. Branch oncode:BAD_REQUEST,UNAUTHORIZED,FORBIDDEN,NOT_FOUND,CONFLICT,RATE_LIMITED,INTERNAL_ERROR. - Retries: send an
Idempotency-Keyheader (8–128 visible ASCII characters) on room create and join so a retry returns the same room.
Rooms
Everything happens in a room: meetings, webinars, broadcasts, phone calls and agent sessions.
| Do | Request |
|---|---|
| Create | POST /v1/rooms {"action":"create","userName":"host","duration":30,"capacity":5,"eventType":"conference"} |
| Join | POST /v1/rooms {"action":"join","userName":"guest1","meetingID":"s1a2b3c4"} |
| List | GET /v1/rooms?action=get&eventStatus=active&startIndex=0&pageSize=20 |
| Get one | GET /v1/rooms?action=get&meetingID=s1a2b3c4 |
| Counts | GET /v1/rooms/stats |
| End and remove | POST /v1/rooms {"action":"delete","meetingID":"s1a2b3c4"} |
Create fields:
| Field | Required | Values |
|---|---|---|
userName | yes | Host name, unique among your active rooms |
duration | yes | Minutes, 5–1440 |
capacity | yes | Maximum participants |
eventType | yes | conference, webinar, broadcast, chat |
scheduledDate | no | ISO 8601 start time |
secureCode | no | Host secret; generated if omitted |
recordOnly | no | true for a recording-only room |
recordingParams | no | recordingAudioSupport, recordingVideoSupport, recordingHLSSupport, recordingPreferredOrientation |
meetingRoomParams | no | audioSetting, videoSetting, screenshareSetting (allow/approval/disallow), chatSetting, targetResolution (QnHD, sd, hd, fhd, qhd), addCoHost |
supportSIP, directionSIP | no | Let phone callers into the room: true; inbound, outbound or both |
webhookUrl, webhookSecret | no | Webhook for this room only (see Webhooks) |
A successful create returns roomName, meetingID, secureCode and
publicURL. Send people to publicURL for a ready-made room, or pass the
create/join result to an SDK.
Use QnHD resolution for low-bandwidth audiences (rural clinics, mobile data).
Show the room in your app
React (mediasfu-reactjs). The browser calls your backend; your backend adds
the API key and forwards the same payload to POST https://mediasfu.com/v1/rooms.
import { ModernMediasfuGeneric } from 'mediasfu-reactjs';
import type { CreateRoomOnMediaSFUType, JoinRoomOnMediaSFUType } from 'mediasfu-reactjs';
const viaBackend = (path: string) => async ({ payload }: { payload: unknown }) => {
const response = await fetch(path, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload),
});
if (!response.ok) throw new Error(`MediaSFU request failed: ${response.status}`);
return response.json();
};
export function Meeting() {
return (
<ModernMediasfuGeneric
connectMediaSFU
createMediaSFURoom={viaBackend('/api/mediasfu/create-room') as CreateRoomOnMediaSFUType}
joinMediaSFURoom={viaBackend('/api/mediasfu/join-room') as JoinRoomOnMediaSFUType}
/>
);
}
The backend routes are in Copy-paste recipes and Secure backend. Other SDKs: SDK guides. No SDK at all: participants over HTTP (WHIP/WHEP).
AI agents and phone calls
-
Add a number. Bring your own SIP number with
POST /v1/sipconfigs{"action":"create","contactNumber":"+15551234567","provider":"twilio","supportSipActive":true,"supportSipNameCalls":true,"allowOutgoing":true}. Attach an AI agent to the number in the dashboard (Agents). MediaSFU-provided numbers are for outgoing calls without an agent. -
Inbound: callers to that number reach the attached agent. No code needed.
-
Outbound: a host must be connected in a live room created with
"supportSIP": true. Then:curl -X POST https://mediasfu.com/v1/sipcall/outgoingCall \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $MEDIASFU_API_USERNAME:$MEDIASFU_API_KEY" \
-d '{"calledDid":"+15557654321","callerIdNumber":"+15551234567","roomName":"s1a2b3c4","initiatorName":"host","callPurpose":"Confirm tomorrow 10am appointment","callObjective":"Confirmed or rescheduled"}'callerIdNumberis your SIP number (E.164).initiatorNameis alphanumeric and must be the name of someone already in the room. Optional context for the agent:callPurpose(≤500),callObjective(≤300),referenceId(≤100),urgency(low/normal/high/urgent),callScript(≤2000),customData(up to 20 keys). -
Control a live call with its
sipCallId:Do Request Read state GET /v1/sipcall/:sipCallId/stateStart or stop the agent POST /v1/sipcall/:sipCallId/start-agentor/stop-agentHand to a person or back POST /v1/sipcall/:sipCallId/switch-source{"targetType":"human","humanName":"Pat"}Hold, unhold POST /v1/sipcall/:sipCallId/hold,/unholdEnd POST /v1/sipcall/:sipCallId/endList calls GET /v1/sipcall/list?page=1&limit=20&direction=incomingTotals GET /v1/sipcall/summaryA
429carriesretryAfterMs; wait that long before the next control.
Recordings
Turn recording on with recordingParams at create (or from the room UI).
After recording.completed arrives, list files:
GET /v1/recordings?action=get&name=<meetingID> returns recordings[] with
url, size and duration. Without name it pages through all recordings
(startIndex, pageSize).
Webhooks
Room events. Set an account default with
POST /v1/eventssettings {"action":"update","webhookUrl":"https://example.com/hooks/mediasfu","webhookSecret":"…"}
(use "action":"create" the first time, with type, audioSetting and
videoSetting), or per room with webhookUrl on create. The URL must be public
HTTPS with no port, credentials or fragment. Send an empty string to clear.
MediaSFU POSTs JSON:
{
"id": "6f1c…",
"event": "room.ended",
"roomName": "s1a2b3c4",
"timestamp": "2026-10-13T18:00:00.000Z",
"data": {}
}
Events: room.created, room.ended, participant.joined, participant.left,
recording.started, recording.paused, recording.resumed,
recording.stopped, recording.processing, recording.completed,
recording.failed, meeting.expired, translation.started.
Headers: x-webhook-event, x-webhook-id, x-webhook-timestamp and, when a
secret is set, x-webhook-signature = lowercase hex HMAC-SHA256 of the raw
body. Return 2xx quickly; a 5xx is retried three times (2 s, 8 s, 30 s). Use
id to ignore duplicates.
import crypto from 'node:crypto';
export function verifyMediaSfuWebhook(rawBody, signature, secret) {
const expected = crypto.createHmac('sha256', secret).update(rawBody).digest('hex');
return typeof signature === 'string' && signature.length === expected.length &&
crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected));
}
Call events. Set webhookUrl on the SIP number's config in the dashboard.
MediaSFU POSTs eventType sip:callStateUpdate (with sipCallId, roomName,
calledDid, currentState.status, currentState.durationSeconds,
currentState.callEnded), sip:conversationSummary (summary text and
transcript), sip:humanInterventionRequired and sip:callbackRequested. A call
has ended when currentState.callEnded is true.
Usage and balance
| Do | Request |
|---|---|
| Remaining minutes | GET /v1/balance (optional scope=sandbox or production) |
| One room's itemised usage | GET /v1/room/logs?roomName=<meetingID>&fieldKey=usageLogs&envelope=1 |
| One room's recording usage | same, with fieldKey=recordUsageLogs |
Each usage entry records the interval with producers, viewers, quality and rate. Prices: mediasfu.com/pricing.
Common mistakes
| Symptom | Cause | Fix |
|---|---|---|
401 on every call | Header is Bearer <key> only, or username and key swapped | Send Bearer <api-username>:<api-key> |
401 "demo key" | Demo keys can't create rooms over the API | Use a sandbox or production key |
401 with a production key | Called over http | Use https://mediasfu.com |
| Production create rejected from a hosted app | The calling domain isn't allowed | Add it with /v1/domains |
Outbound call 404 "Initiator not found" | Nobody named initiatorName is in the room | Join the room as that name first |
Outbound call 400 "does not support SIP" | Room created without supportSIP | Create with "supportSIP": true |
| Webhook URL rejected | Not public HTTPS, or has a port | Use a public HTTPS hostname on 443 |
| API key in browser network tab | Frontend calls MediaSFU directly | Route through your backend or use a disposable key |
Use MediaSFU from an AI assistant
Give your assistant MediaSFU knowledge and account tools with the MCP server:
npx -y @mediasfu/mcp-sdk-tools
Account actions (rooms, calls, recordings, webhooks, usage) use your own key from the client's environment. Setup: MediaSFU MCP.
More
- Copy-paste recipes: working server code for each flow above.
- HTTP API reference: every field of every resource.
- AI call control: operator handoff patterns.
- Starter projects: runnable apps.