Reference
REST API reference
Everything your server can do with an API key: rooms and their settings, join tokens, the waiting room, moderation, whiteboards and saved copies of them, breakout rooms, recordings, live streams, webhooks, your workspace’s people and usage. JSON in, JSON out.
Basics
| Base URL | https://api.inlinemeet.com/v1 |
| Authentication | Authorization: Bearer imk_… on every request — API keys |
| Requests | JSON bodies, with Content-Type: application/json |
| Responses | JSON. Times are ISO 8601, in UTC. |
| Scope | A key sees only its own workspace’s rooms, recordings and saved whiteboards. |
Call the API from your server only. A key in a browser can mint any token, for anyone.
All endpoints
- POST/v1/roomsCreate a room
- GET/v1/rooms/:codeGet a room
- PATCH/v1/rooms/:codeUpdate a room
- PATCH/v1/rooms/:code/settingsChange settings from the call
- POST/v1/rooms/:code/endEnd the call for everyone
- DELETE/v1/rooms/:codeClose a room
- GET/v1/templatesList templates
- GET/v1/templates/:idGet a template
- POST/v1/templatesCreate a template
- PATCH/v1/templates/:idUpdate a template
- DELETE/v1/templates/:idDelete a template
- POST/v1/rooms/:code/tokensCreate a join token
- GET/v1/rooms/:code/lobbyList who is waiting
- POST/v1/rooms/:code/lobby/admitLet someone in
- POST/v1/rooms/:code/lobby/denyTurn someone away
- GET/v1/rooms/:code/lobby/tickets/:ticketPoll a ticket
- DELETE/v1/rooms/:code/lobby/tickets/:ticketStop waiting
- GET/v1/rooms/:code/whiteboardGet the whiteboard
- PATCH/v1/rooms/:code/whiteboardSave whiteboard edits
- POST/v1/rooms/:code/whiteboard/snapshotsSave a copy of the whiteboard
- GET/v1/rooms/:code/whiteboard/snapshotsList a room’s saved whiteboards
- GET/v1/whiteboard/snapshotsList all saved whiteboards
- GET/v1/whiteboard/snapshots/:idGet a saved whiteboard
- PATCH/v1/whiteboard/snapshots/:idRename or relabel
- DELETE/v1/whiteboard/snapshots/:idDelete a saved whiteboard
- GET/v1/rooms/:code/breakoutsGet the breakout rooms
- POST/v1/rooms/:code/breakoutsOpen breakout rooms
- POST/v1/rooms/:code/breakouts/moveMove someone
- POST/v1/rooms/:code/breakouts/joinMove yourself
- POST/v1/rooms/:code/breakouts/broadcastMessage every room
- DELETE/v1/rooms/:code/breakoutsClose the breakout rooms
- POST/v1/rooms/:code/companionsPair a tablet
- POST/v1/rooms/:code/companions/resumeTake a tablet back
- POST/v1/rooms/:code/companions/disconnectDisconnect a tablet
- GET/v1/rooms/:code/participantsList participants
- POST/v1/rooms/:code/muteMute
- POST/v1/rooms/:code/participants/:identity/microphoneAllow one person to speak
- POST/v1/rooms/:code/participants/:identity/permissionsGive one person permissions
- POST/v1/rooms/:code/participants/:identity/removeRemove someone
- POST/v1/rooms/:code/recordingsStart a recording
- GET/v1/rooms/:code/recordingsList a room’s recordings
- POST/v1/rooms/:code/recordings/:id/stopStop a recording
- POST/v1/rooms/:code/recordings/:id/abortAbort a recording
- GET/v1/rooms/:code/recordings/:id/fileGet a playback link
- PATCH/v1/rooms/:code/recordings/:idUpdate labels
- DELETE/v1/rooms/:code/recordings/:idDelete a recording
- GET/v1/recordingsList all recordings
- POST/v1/rooms/:code/streamsGo live
- GET/v1/rooms/:code/streamsThe room’s streams
- GET/v1/rooms/:code/streams/:idGet a stream
- POST/v1/rooms/:code/streams/:id/destinationsAdd destinations while live
- DELETE/v1/rooms/:code/streams/:id/destinations/:destinationIdTake a destination off
- POST/v1/rooms/:code/streams/:id/stopStop a stream
- GET/v1/stream-destinationsList saved destinations
- POST/v1/stream-destinationsSave a destination
- DELETE/v1/stream-destinations/:idForget a saved destination
- POST/v1/rooms/:code/webinar/startStart a webinar
- GET/v1/rooms/:code/ctaThe call to action
- POST/v1/rooms/:code/ctaPut up a call to action
- DELETE/v1/rooms/:code/ctaTake it down
- POST/v1/rooms/:code/cta/clickCount a press
- GET/v1/brandYour brand
- PUT/v1/brandSet your brand
- GET/v1/webhookGet webhook settings
- PUT/v1/webhookSet the endpoint
- POST/v1/webhook/testSend a test event
- POST/v1/webhook/secretRotate the signing secret
- GET/v1/membersList people
- GET/v1/usageGet usage
Rooms
A room is a call, named by a code you choose. Rooms are yours alone: two workspaces can both have a room called algebra-101.
Create a room
Creates a room, or returns it if it already exists — so it’s safe to call on every join. Returns 201 for a new room and 200 for an existing one; a closed room is reopened.
| Body | Description |
|---|---|
codestring | 1–64 letters, digits, - or _. Omit it and one is generated for you. |
namestring, up to 120 | A human-readable name. Updated if you send a different one. |
scopestring, up to 64 | Puts the room in a scope: it shows that scope’s brand, streams to its saved destinations, and is set up from its templates — template must be one of the scope’s. An existing room without one takes it; one in another scope is a 409. One of your own customers’ — letters, digits, : . _ -, such as school:42. See scopes. |
templatestring | null | One of your templates, by id: a new room is set up from it — its settings, look and scenes, layout, where it streams and whether it goes live by itself, its call to action — with any settings you send here over it. Leave it out and a new room is set up from your default template, if you have one (in a scope, the scope’s); null sets it up from none. An existing room is left as it is (so this stays safe to call on every join); send template to update a room to set it up again. See Templates. |
participantsCanUnmuteboolean | Optional, as for updating a room. false mutes every participant and stops them unmuting until it’s true again. |
waitingRoomboolean | Optional, as for updating a room. Participants wait until a moderator lets them in: creating their token answers 202 with a ticket instead. Turning it off lets in everyone waiting. |
passwordstring, 4–64 | null | Optional, as for updating a room. Participants must give it to join (relayed to the token request). Stored only as a hash. null removes it. |
autoRecordboolean | Optional, as for updating a room. Start recording when a moderator joins — once per session of the call. Turned on with a moderator already there, it starts at once. |
whiteboardLockedboolean | Optional, as for updating a room. Only moderators may change the whiteboard. Moderators can still let one person draw (permissions.whiteboard). |
whiteboardFollowHostboolean | Optional, as for updating a room. With the whiteboard locked: a moderator opening it brings everyone to it, kept on their view, until they close it. Moderators can still stop presenting from the board. |
whiteboardHideCursorsboolean | Optional, as for updating a room. With the whiteboard locked: only moderators’ cursors are shown — and anyone a moderator let draw. A moderator can show them again from the board. |
recordingActivityboolean | object | Optional, as for updating a room. What recordings show besides the call, over the picture: true for chat, Q&A, polls and reactions; false for nothing; or an object of chat, qna, polls, reactions and hands, each true or false, changing only those. Rooms that can’t be recorded refuse it. See how it looks. |
streamActivityboolean | object | Optional, as for updating a room. The same, for a live stream. Never set, a stream shows chat, Q&A, polls and reactions. |
webinarboolean | Optional, as for updating a room. A webinar: participants are its audience — they watch, chat, ask and raise a hand, and publish nothing until a moderator brings them on stage. Paid plans. |
backstageboolean | Optional, as for updating a room. A webinar opens backstage: participants’ tokens answer 202 with reason: "backstage" until a moderator starts it; moderators and speakers (stage) go straight in. Turning it off lets in everyone waiting. |
lookboolean | object | Optional, as for updating a room. Your brand on the call: logo (in the call’s top bar, and the corner of streams and recordings) and titles (each speaker’s name and title as they come on), each true or false — true or false sets both; never set, both are on. And lines: what each scene says instead of its usual line — { "starting": "We go live at 4pm sharp" }, up to 80 characters each; "" or null puts a scene’s usual line back. And theme: the colours of the room’s recordings and streams, as the call’s --im-* tokens without the prefix, replacing any it had, or null for your brand’s. A template usually sets it. Only your app sets theme, never the call. Only the parts you name change. |
layout'auto' | 'grid' | 'spotlight' | 'sidebar' | Optional, as for updating a room. How the call is laid out for everyone until each person picks their own. The call UI’s defaultLayout, when you pass it, wins. |
scene'starting' | 'break' | 'ending' | null | Optional, as for updating a room. A scene streams and recordings show instead of the call — “Starting soon”, “We’ll be right back”, “Thanks for watching” — with the call’s sound off. null goes back to the call. A webinar shows “Starting soon” on its own while it’s backstage. |
allowLiveStreamingboolean | Whether the call offers its hosts going live — the Live tab’s stream — where your plan has it. On unless you turn it off; turning it off stops a stream in progress. Hosts can’t turn it back on from the call. |
allowWebinarboolean | Whether the call can be made a webinar — the switch in its Settings tab — where your plan has them. On unless you turn it off; turning it off makes a room that is a webinar a meeting again. |
{
"code": "algebra-101",
"name": "Algebra 101",
"archived": false,
"createdAt": "2026-09-24T13:58:10.412Z",
"participantsCanUnmute": true,
"waitingRoom": false,
"requiresPassword": false,
"autoRecord": false,
"whiteboardLocked": false,
"whiteboardFollowHost": false,
"whiteboardHideCursors": false,
"recordingActivity": { "chat": false, "qna": false, "polls": false, "reactions": false, "hands": false },
"streamActivity": { "chat": true, "qna": true, "polls": true, "reactions": true, "hands": false },
"webinar": false,
"backstage": false,
"onAirAt": null,
"look": { "logo": true, "titles": true },
"scene": null,
"layout": "auto",
"allowLiveStreaming": true,
"allowWebinar": true,
"scope": null,
"template": null,
"prepared": null,
"streamId": null,
"recording": null
}Errors: invalid_code, invalid_scope, scope_mismatch, invalid_setting, invalid_password, recordings_disabled, template_not_found, webinar_disabled, plan_required, plus the authentication errors.
Get a room
The room, plus participantCount — how many people are connected right now — and the recording in progress, if there is one.
{
"code": "algebra-101",
"name": "Algebra 101",
"archived": false,
"createdAt": "2026-09-24T13:58:10.412Z",
"participantsCanUnmute": false,
"waitingRoom": true,
"requiresPassword": false,
"autoRecord": true,
"whiteboardLocked": false,
"recordingActivity": { "chat": true, "qna": true, "polls": true, "reactions": true, "hands": false },
"recording": {
"id": "66f1c0a4e1b2c3d4e5f60718",
"startedAt": "2026-09-24T14:00:03.000Z",
"startedByName": "Alex Morgan",
"auto": true
},
"participantCount": 18
}Errors: room_not_found, plus the authentication errors.
Update a room
Every field is optional, and every setting applies to the live call at once. Moderators are exempt from all of them. See moderation.
| Body | Description |
|---|---|
participantsCanUnmuteboolean | false mutes every participant and stops them unmuting until it’s true again. |
waitingRoomboolean | Participants wait until a moderator lets them in: creating their token answers 202 with a ticket instead. Turning it off lets in everyone waiting. |
passwordstring, 4–64 | null | Participants must give it to join (relayed to the token request). Stored only as a hash. null removes it. |
autoRecordboolean | Start recording when a moderator joins — once per session of the call. Turned on with a moderator already there, it starts at once. |
whiteboardLockedboolean | Only moderators may change the whiteboard. Moderators can still let one person draw (permissions.whiteboard). |
whiteboardFollowHostboolean | With the whiteboard locked: a moderator opening it brings everyone to it, kept on their view, until they close it. Moderators can still stop presenting from the board. |
whiteboardHideCursorsboolean | With the whiteboard locked: only moderators’ cursors are shown — and anyone a moderator let draw. A moderator can show them again from the board. |
recordingActivityboolean | object | What recordings show besides the call, over the picture: true for chat, Q&A, polls and reactions; false for nothing; or an object of chat, qna, polls, reactions and hands, each true or false, changing only those. Rooms that can’t be recorded refuse it. See how it looks. |
streamActivityboolean | object | The same, for a live stream. Never set, a stream shows chat, Q&A, polls and reactions. |
webinarboolean | A webinar: participants are its audience — they watch, chat, ask and raise a hand, and publish nothing until a moderator brings them on stage. Paid plans. |
backstageboolean | A webinar opens backstage: participants’ tokens answer 202 with reason: "backstage" until a moderator starts it; moderators and speakers (stage) go straight in. Turning it off lets in everyone waiting. |
lookboolean | object | Your brand on the call: logo (in the call’s top bar, and the corner of streams and recordings) and titles (each speaker’s name and title as they come on), each true or false — true or false sets both; never set, both are on. And lines: what each scene says instead of its usual line — { "starting": "We go live at 4pm sharp" }, up to 80 characters each; "" or null puts a scene’s usual line back. And theme: the colours of the room’s recordings and streams, as the call’s --im-* tokens without the prefix, replacing any it had, or null for your brand’s. A template usually sets it. Only your app sets theme, never the call. Only the parts you name change. |
layout'auto' | 'grid' | 'spotlight' | 'sidebar' | How the call is laid out for everyone until each person picks their own. The call UI’s defaultLayout, when you pass it, wins. |
scene'starting' | 'break' | 'ending' | null | A scene streams and recordings show instead of the call — “Starting soon”, “We’ll be right back”, “Thanks for watching” — with the call’s sound off. null goes back to the call. A webinar shows “Starting soon” on its own while it’s backstage. |
allowLiveStreamingboolean | Whether the call offers its hosts going live — the Live tab’s stream — where your plan has it. On unless you turn it off; turning it off stops a stream in progress. Hosts can’t turn it back on from the call. |
allowWebinarboolean | Whether the call can be made a webinar — the switch in its Settings tab — where your plan has them. On unless you turn it off; turning it off makes a room that is a webinar a meeting again. |
namestring, up to 120 | Rename the room. |
templatestring | Set the room up again from one of your templates — live, for anyone already in the call — with any settings you send here over it. A room in a scope takes only that scope’s. |
{
"code": "algebra-101",
"name": "Algebra 101",
"archived": false,
"createdAt": "2026-09-24T13:58:10.412Z",
"participantsCanUnmute": false,
"waitingRoom": false,
"requiresPassword": false,
"autoRecord": false,
"whiteboardLocked": false,
"whiteboardFollowHost": false,
"whiteboardHideCursors": false,
"recordingActivity": { "chat": false, "qna": false, "polls": false, "reactions": false, "hands": false },
"streamActivity": { "chat": true, "qna": true, "polls": true, "reactions": true, "hands": false },
"webinar": false,
"backstage": false,
"onAirAt": null,
"look": { "logo": true, "titles": true },
"scene": null,
"layout": "auto",
"allowLiveStreaming": true,
"allowWebinar": true,
"scope": null,
"template": null,
"prepared": null,
"streamId": null,
"recording": null
}Errors: room_not_found, invalid_setting, invalid_password, recordings_disabled, template_not_found, webinar_disabled, plan_required, plus the authentication errors.
Change settings from the call
Where your call UI backend forwards its Settings tab (PATCH <base>/settings): the settings above, but never allowLiveStreaming, allowWebinar, template or name — so a host can change only what a host may, whatever their browser sends. Answers the room’s settings.
| Body | Description |
|---|---|
participantsCanUnmuteboolean | false mutes every participant and stops them unmuting until it’s true again. |
waitingRoomboolean | Participants wait until a moderator lets them in: creating their token answers 202 with a ticket instead. Turning it off lets in everyone waiting. |
passwordstring, 4–64 | null | Participants must give it to join (relayed to the token request). Stored only as a hash. null removes it. |
autoRecordboolean | Start recording when a moderator joins — once per session of the call. Turned on with a moderator already there, it starts at once. |
whiteboardLockedboolean | Only moderators may change the whiteboard. Moderators can still let one person draw (permissions.whiteboard). |
whiteboardFollowHostboolean | With the whiteboard locked: a moderator opening it brings everyone to it, kept on their view, until they close it. Moderators can still stop presenting from the board. |
whiteboardHideCursorsboolean | With the whiteboard locked: only moderators’ cursors are shown — and anyone a moderator let draw. A moderator can show them again from the board. |
recordingActivityboolean | object | What recordings show besides the call, over the picture: true for chat, Q&A, polls and reactions; false for nothing; or an object of chat, qna, polls, reactions and hands, each true or false, changing only those. Rooms that can’t be recorded refuse it. See how it looks. |
streamActivityboolean | object | The same, for a live stream. Never set, a stream shows chat, Q&A, polls and reactions. |
webinarboolean | A webinar: participants are its audience — they watch, chat, ask and raise a hand, and publish nothing until a moderator brings them on stage. Paid plans. |
backstageboolean | A webinar opens backstage: participants’ tokens answer 202 with reason: "backstage" until a moderator starts it; moderators and speakers (stage) go straight in. Turning it off lets in everyone waiting. |
lookboolean | object | Your brand on the call: logo (in the call’s top bar, and the corner of streams and recordings) and titles (each speaker’s name and title as they come on), each true or false — true or false sets both; never set, both are on. And lines: what each scene says instead of its usual line — { "starting": "We go live at 4pm sharp" }, up to 80 characters each; "" or null puts a scene’s usual line back. And theme: the colours of the room’s recordings and streams, as the call’s --im-* tokens without the prefix, replacing any it had, or null for your brand’s. A template usually sets it. Only your app sets theme, never the call. Only the parts you name change. |
layout'auto' | 'grid' | 'spotlight' | 'sidebar' | How the call is laid out for everyone until each person picks their own. The call UI’s defaultLayout, when you pass it, wins. |
scene'starting' | 'break' | 'ending' | null | A scene streams and recordings show instead of the call — “Starting soon”, “We’ll be right back”, “Thanks for watching” — with the call’s sound off. null goes back to the call. A webinar shows “Starting soon” on its own while it’s backstage. |
Errors: room_not_found, invalid_setting, invalid_password, recordings_disabled, webinar_disabled, plan_required, plus the authentication errors.
End the call for everyone
What the call UI’s End for everyone asks for when a moderator leaves — check the caller is one before forwarding it. Disconnects everyone, in breakout rooms too, and tells them the call has ended; stops a recording in progress, which is finished and kept like any other; tells people in the waiting room (their ticket answers 410 call_ended); and gives the host licence back. The tokens people hold can no longer move them between rooms. The room stays: the next token you mint starts a new call in it.
{ "ok": true }Errors: room_not_found, plus the authentication errors.
Close a room
Ends the call for everyone, as above — disconnecting everyone, in breakout rooms too, and stopping a recording in progress, which is finished and kept like any other — then archives the room. Creating the room again reopens it.
{ "ok": true }Errors: room_not_found, plus the authentication errors.
Templates
Named setups for your meetings and webinars — who gets in, the layout, your brand and scenes, where it streams and whether it goes live by itself, what’s recorded, a call to action — made on the dashboard or here. Name one by template when you create a room. One meeting template can be the default ("default": true): new rooms that name no template are set up from it. See Templates.
Scopes. If your app serves customers of its own — schools, classes, clients — give each a scope (scope, such as school:42) and its templates, its brand and its saved stream destinations are its alone: listed, read, changed and used only with that scope, and only by rooms you create in it. Without a scope, every endpoint means your workspace’s own, which the dashboard shows. Your API key reaches every scope, so it’s your app that decides which one a request is for. See Templates → scopes.
List templates
The workspace’s templates — or a scope’s — most recently changed first.
| Query | Description |
|---|---|
kind'meeting' | 'webinar' | Only templates of this kind. |
scopestring, up to 64 | That scope’s templates instead of the workspace’s own. One of your own customers’ — letters, digits, : . _ -, such as school:42. See scopes. |
{ "templates": [{
"id": "6703a1f0e1b2c3d4e5f60b12",
"name": "Product launch webinar",
"kind": "webinar",
"scope": null,
"default": false,
"setup": {
"waitingRoom": false,
"backstage": true,
"registration": false,
"joinBeforeHost": false,
"participantsCanUnmute": true,
"guestScreenShare": false,
"whiteboardLocked": true,
"whiteboardFollowHost": true,
"whiteboardHideCursors": false,
"layout": "spotlight",
"autoRecord": true,
"recordingActivity": { "chat": true, "qna": true, "polls": true, "reactions": false, "hands": false },
"look": { "logo": true, "titles": true, "lines": { "starting": "We go live at 4pm sharp" } },
"scene": "starting",
"stream": {
"savedIds": ["6702c0d1e1b2c3d4e5f60a77"],
"orientation": "landscape",
"activity": { "chat": true, "qna": true, "polls": true, "reactions": true, "hands": false },
"autoStart": true
},
"cta": { "headline": "See it on your own data", "label": "Book a demo", "url": "https://acme.example/demo" }
},
"createdAt": "2026-10-09T10:12:00.000Z",
"updatedAt": "2026-10-09T11:40:00.000Z"
}] }Get a template
One template, with its whole setup.
| Query | Description |
|---|---|
scopestring, up to 64 | Needed for a scope’s template — the same goes for changing or deleting one. One of your own customers’ — letters, digits, : . _ -, such as school:42. See scopes. |
{
"id": "6703a1f0e1b2c3d4e5f60b12",
"name": "Product launch webinar",
"kind": "webinar",
"scope": null,
"default": false,
"setup": {
"waitingRoom": false,
"backstage": true,
"registration": false,
"joinBeforeHost": false,
"participantsCanUnmute": true,
"guestScreenShare": false,
"whiteboardLocked": true,
"whiteboardFollowHost": true,
"whiteboardHideCursors": false,
"layout": "spotlight",
"autoRecord": true,
"recordingActivity": { "chat": true, "qna": true, "polls": true, "reactions": false, "hands": false },
"look": { "logo": true, "titles": true, "lines": { "starting": "We go live at 4pm sharp" } },
"scene": "starting",
"stream": {
"savedIds": ["6702c0d1e1b2c3d4e5f60a77"],
"orientation": "landscape",
"activity": { "chat": true, "qna": true, "polls": true, "reactions": true, "hands": false },
"autoStart": true
},
"cta": { "headline": "See it on your own data", "label": "Book a demo", "url": "https://acme.example/demo" }
},
"createdAt": "2026-10-09T10:12:00.000Z",
"updatedAt": "2026-10-09T11:40:00.000Z"
}Errors: template_not_found, plus the authentication errors.
Create a template
Whatever setup leaves out is as a plain meeting has it. Saved destinations must be the workspace’s own — or, for a scope’s template, the scope’s; others are dropped.
| Body | Description |
|---|---|
namerequiredstring, up to 80 | What it’s called, on the dashboard. |
scopestring, up to 64 | Makes it that scope’s template, kept apart from every other’s and from the dashboard. One of your own customers’ — letters, digits, : . _ -, such as school:42. See scopes. |
kindrequired'meeting' | 'webinar' | A webinar template makes webinars: an audience, and a stage. |
defaultboolean | Makes this meeting template the default — of the workspace, or of its scope — in place of the one before: new rooms that name no template are set up from it. |
setupobject | Its setup — any of the fields below. |
setup.waitingRoom, setup.backstage, setup.autoRecord, setup.whiteboardLocked, setup.whiteboardFollowHost, setup.whiteboardHideCursorsboolean | As the room settings of the same names. backstage is a webinar’s. |
setup.participantsCanUnmute, setup.guestScreenShareboolean | Who may speak up and share their screen (a meeting’s). |
setup.registration, setup.joinBeforeHostboolean | For meetings booked on the dashboard with it: guests register (a webinar’s), or may start it without the host. |
setup.layout'auto' | 'grid' | 'spotlight' | 'sidebar' | How the call opens for everyone. |
setup.scene'starting' | 'break' | 'ending' | null | The scene the stream and the recording open on. |
setup.lookobject | As the room setting: logo, titles and each scene’s lines. |
setup.recordingActivity, setup.streamActivityobject | As the room settings. |
setup.streamobject | savedIds — up to 5 of your saved destinations; orientation — landscape, portrait or null; autoStart — go live as a moderator arrives. |
setup.ctaobject | null | A call to action ready to put up: { headline?, label, url }. |
{
"id": "6703a1f0e1b2c3d4e5f60b12",
"name": "Product launch webinar",
"kind": "webinar",
"scope": null,
"default": false,
"setup": {
"waitingRoom": false,
"backstage": true,
"registration": false,
"joinBeforeHost": false,
"participantsCanUnmute": true,
"guestScreenShare": false,
"whiteboardLocked": true,
"whiteboardFollowHost": true,
"whiteboardHideCursors": false,
"layout": "spotlight",
"autoRecord": true,
"recordingActivity": { "chat": true, "qna": true, "polls": true, "reactions": false, "hands": false },
"look": { "logo": true, "titles": true, "lines": { "starting": "We go live at 4pm sharp" } },
"scene": "starting",
"stream": {
"savedIds": ["6702c0d1e1b2c3d4e5f60a77"],
"orientation": "landscape",
"activity": { "chat": true, "qna": true, "polls": true, "reactions": true, "hands": false },
"autoStart": true
},
"cta": { "headline": "See it on your own data", "label": "Book a demo", "url": "https://acme.example/demo" }
},
"createdAt": "2026-10-09T10:12:00.000Z",
"updatedAt": "2026-10-09T11:40:00.000Z"
}Errors: invalid_name, invalid_kind, invalid_default, invalid_setting, invalid_scope, too_many_templates, plus the authentication errors.
Update a template
name, kind, default and setup are each optional; in setup, only the parts you name change. Rooms already set up from it keep their setup.
| Body | Description |
|---|---|
namestring, up to 80 | A new name. |
kind'meeting' | 'webinar' | What it’s for. The default stays a meeting template: send default: false with webinar. |
defaultboolean | true makes it the default, in place of the one before; false stops it being the default, leaving none. |
setupobject | As for creating one. |
{
"id": "6703a1f0e1b2c3d4e5f60b12",
"name": "Product launch webinar",
"kind": "webinar",
"scope": null,
"default": false,
"setup": {
"waitingRoom": false,
"backstage": true,
"registration": false,
"joinBeforeHost": false,
"participantsCanUnmute": true,
"guestScreenShare": false,
"whiteboardLocked": true,
"whiteboardFollowHost": true,
"whiteboardHideCursors": false,
"layout": "spotlight",
"autoRecord": true,
"recordingActivity": { "chat": true, "qna": true, "polls": true, "reactions": false, "hands": false },
"look": { "logo": true, "titles": true, "lines": { "starting": "We go live at 4pm sharp" } },
"scene": "starting",
"stream": {
"savedIds": ["6702c0d1e1b2c3d4e5f60a77"],
"orientation": "landscape",
"activity": { "chat": true, "qna": true, "polls": true, "reactions": true, "hands": false },
"autoStart": true
},
"cta": { "headline": "See it on your own data", "label": "Book a demo", "url": "https://acme.example/demo" }
},
"createdAt": "2026-10-09T10:12:00.000Z",
"updatedAt": "2026-10-09T11:40:00.000Z"
}Errors: template_not_found, invalid_name, invalid_kind, invalid_default, default_template, invalid_setting, plus the authentication errors.
Delete a template
Rooms set up from it keep their setup. Deleting the default leaves none.
{ "ok": true }Errors: template_not_found, plus the authentication errors.
Join tokens
A join token lets one person into one room, with exactly the rights you give them.
Create a join token
Mint one per join, from your server, after your own permission check. Hand url and token to the browser. Roles and permissions explains every option. The first token for a call that isn’t running takes one of your host licences.
| Body | Description |
|---|---|
identityrequiredstring, 1–128 | Who this connection is. Unique per tab. |
namestring, up to 120 | Display name. |
role'moderator' | 'participant' | Default participant. |
publish{ microphone?, camera?, screenShare? } | What a participant may publish. Omit for listen-only. |
metadataobject | string, < 4 KB | Shown to other participants’ UIs. Never an access decision. |
ttlSecondsnumber | Default 21600 (6 h), max 86400 (24 h). |
contact{ email?, phone? } | How to reach this person about the review they give after the call (gatherReviewsAfterCall in CallRoom). Kept by inlinemeet for your dashboard’s Call reviews; never in the token, so nobody else in the call sees it. |
passwordstring | What the person typed, when the room has a password. Moderators need none. |
bypassboolean | Skip the room’s password and waiting room — for someone your app already trusts, such as an enrolled student. |
stageboolean | A webinar speaker: on stage from the start, with what publish grants, and let in while the webinar is backstage. |
titlestring, up to 60 | Their title, under their name on streams and recordings as they come on — “Head of Research”. They can change it in the call. |
{
"token": "eyJhbGciOiJIUzI1NiJ9…",
"url": "wss://livekit.inlinemeet.com",
"identity": "student-981:7f3c",
"role": "participant",
"participantsCanUnmute": true
}Errors: room_not_found, invalid_identity, invalid_role, metadata_too_large, password_required, wrong_password, lobby_full, licence_limit, media_server_starting, media_server_unavailable, plus the authentication errors.
Waiting room
With a room’s waitingRoom on, creating a participant’s token answers 202 { "waiting": true, "ticket": "…", "pollAfterMs": 2000 } instead of a token. Pass that to the browser; it polls with the ticket until a moderator lets it in, and is then handed the token — minted from the request you made. Someone let in once isn’t held again under the same identity. Check the caller is a moderator before forwarding list, admit and deny.
A webinar that is backstage holds its audience the same way, with "reason": "backstage" on the 202 (and startsAt, when it’s due at a set time). Those tickets aren’t a moderator’s to decide: they’re counted, not listed, and all answer with tokens once it starts.
List who is waiting
First to arrive first. Someone whose browser stops polling drops off within half a minute. backstage counts those waiting for a webinar to start.
{
"waiting": [
{ "id": "66f1c3b2a9e4f1a2b3c4d5e6", "name": "Aisha Khan", "since": 1790000000000 }
],
"backstage": 0
}Errors: room_not_found, plus the authentication errors.
Let someone in
Their next poll returns their token.
| Body | Description |
|---|---|
idstring | Who, from the list. Omit to let everyone in. |
{ "admitted": 1 }Errors: room_not_found, not_waiting, plus the authentication errors.
Turn someone away
Their next poll answers 403 denied.
| Body | Description |
|---|---|
idrequiredstring | Who, from the list. |
{ "ok": true }Errors: room_not_found, not_waiting, plus the authentication errors.
Poll a ticket
The waiting browser’s question, proxied by your server: { "waiting": true } until a moderator decides, then the same body creating the token would have returned. Add what your token route adds (permissions, and so on) before passing it on.
{ "waiting": true, "pollAfterMs": 2000 }Errors: room_not_found, ticket_not_found, denied, call_ended, plus the authentication errors.
Stop waiting
The ticket stops working and they leave the list.
{ "ok": true }Errors: room_not_found, plus the authentication errors.
Whiteboard
Each room keeps its whiteboard between calls. Browsers share drawing with each other directly; these routes are how they save and load it. The call UI uses them when its backend has whiteboard: true.
Get the whiteboard
Every board and what is on it. Deleted boards are listed without their contents, so a browser holding a stale copy learns they are gone.
{
"boards": [
{
"id": "main", "name": "Board 1", "order": 0, "v": 3, "n": 18342,
"elements": [
{ "id": "k3P9x", "type": "rect", "v": 2, "n": 991, "z": 1, "x": 40, "y": 60,
"w": 180, "h": 110, "color": "blue", "fill": "tint", "size": "m", "dash": "solid", "label": "Plan" }
]
}
]
}Errors: room_not_found, plus the authentication errors.
Save whiteboard edits
Merged element by element — the higher version wins, and on a tie the lower n — so saves from several browsers in any order converge. Anything malformed is dropped. Up to 20 boards and 5,000 elements a board.
| Body | Description |
|---|---|
boardsrequired[{ id, meta?, elements? }] | The boards and elements that changed. |
identitystring | Who made the edits — set it from your own session. While the whiteboard is locked, only a moderator’s are taken. Omit it to save as your app. |
{ "ok": true }Errors: room_not_found, plan_required, whiteboard_locked, too_many_boards, board_full, too_large, busy, plus the authentication errors.
Saved whiteboards
A room’s whiteboard carries on from call to call; a saved copy keeps its boards as they were at one moment, however the room draws on afterwards — so a lesson’s drawings can be shown on the lesson’s page, to anyone, with no call. The call UI offers moderators Save a copy when its backend has whiteboardSnapshots: true, and its WhiteboardView shows what you fetch here. Saved copies outlive their room, and stay readable whatever your plan.
Save a copy of the whiteboard
Copies the room’s boards as inlinemeet has stored them — an edit made in the last second may not be in it yet. Boards with nothing on them are left out. Returns the copy without its drawings. Up to 500 per room.
| Body | Description |
|---|---|
titlestring | Up to 120 characters. Defaults to the room’s name. |
boardsstring[] | Only these boards, by id. Default: every board with something on it. |
savedBystring | Your id for who saved it. |
savedByNamestring | Their display name. |
labelsobject | Up to 20 string pairs of your own, to find it again by. |
{
"id": "66f1d2b7e1b2c3d4e5f60a31",
"roomCode": "algebra-101",
"title": "Week 3 — factorising",
"savedBy": "teacher-12",
"savedByName": "Alex Morgan",
"createdAt": "2026-09-24T14:46:12.000Z",
"count": 42,
"labels": { "lessonId": "week-3" },
"boards": [
{ "id": "main", "name": "Board 1", "order": 0, "count": 30 },
{ "id": "k9Qm2", "name": "Worked examples", "order": 1, "count": 12 }
]
}Errors: room_not_found, plan_required, invalid_labels, invalid_request, whiteboard_empty, too_many_snapshots, plus the authentication errors.
List a room’s saved whiteboards
Newest first, without their drawings. Works after the room is closed.
{ "snapshots": [{
"id": "66f1d2b7e1b2c3d4e5f60a31",
"roomCode": "algebra-101",
"title": "Week 3 — factorising",
"savedBy": "teacher-12",
"savedByName": "Alex Morgan",
"createdAt": "2026-09-24T14:46:12.000Z",
"count": 42,
"labels": { "lessonId": "week-3" },
"boards": [
{ "id": "main", "name": "Board 1", "order": 0, "count": 30 },
{ "id": "k9Qm2", "name": "Worked examples", "order": 1, "count": 12 }
]
}] }Errors: room_not_found, plus the authentication errors.
List all saved whiteboards
Across every room, newest first, without their drawings. Filters combine.
| Query | Description |
|---|---|
roomstring, repeatable | Only these rooms. |
label.<key>string | Label <key> equals this value. |
limitnumber | Up to 500, default 200. |
{ "snapshots": [{
"id": "66f1d2b7e1b2c3d4e5f60a31",
"roomCode": "algebra-101",
"title": "Week 3 — factorising",
"savedBy": "teacher-12",
"savedByName": "Alex Morgan",
"createdAt": "2026-09-24T14:46:12.000Z",
"count": 42,
"labels": { "lessonId": "week-3" },
"boards": [
{ "id": "main", "name": "Board 1", "order": 0, "count": 30 },
{ "id": "k9Qm2", "name": "Worked examples", "order": 1, "count": 12 }
]
}] }Errors: invalid_label, plus the authentication errors.
Get a saved whiteboard
With its drawings: each board also has elements, back to front, in the same shape as the room’s whiteboard. Hand it to the call UI’s WhiteboardView as it is.
| Query | Description |
|---|---|
boardstring, repeatable | Only these boards. |
{
"id": "66f1d2b7e1b2c3d4e5f60a31",
"title": "Week 3 — factorising",
…
"boards": [
{
"id": "main", "name": "Board 1", "order": 0, "count": 30,
"elements": [
{ "id": "k3P9x", "type": "rect", "v": 2, "n": 991, "z": 1, "x": 40, "y": 60,
"w": 180, "h": 110, "color": "blue", "fill": "tint", "size": "m", "dash": "solid", "label": "Plan" },
…
]
}
]
}Errors: snapshot_not_found, plus the authentication errors.
Rename or relabel
Labels are merged into its own; an empty value removes that key. Returns the saved whiteboard, without drawings.
| Body | Description |
|---|---|
titlestring | A new title. |
labelsobject | e.g. { "lessonId": "week-4", "draft": "" } |
{
"id": "66f1d2b7e1b2c3d4e5f60a31",
"roomCode": "algebra-101",
"title": "Week 3 — factorising",
"savedBy": "teacher-12",
"savedByName": "Alex Morgan",
"createdAt": "2026-09-24T14:46:12.000Z",
"count": 42,
"labels": { "lessonId": "week-3" },
"boards": [
{ "id": "main", "name": "Board 1", "order": 0, "count": 30 },
{ "id": "k9Qm2", "name": "Worked examples", "order": 1, "count": 12 }
]
}Errors: snapshot_not_found, invalid_title, invalid_labels, plus the authentication errors.
Delete a saved whiteboard
For good. The room’s own whiteboard is untouched.
{ "ok": true }Errors: snapshot_not_found, plus the authentication errors.
Breakout rooms
Split a call into smaller rooms and bring everyone back. Each breakout is a media-server room of its own; moving someone hands their browser a token for the new room, and the call UI switches over in a second or so, keeping their microphone and camera as they were. Up to 20 rooms at a time, closed after an optional time limit. Recording covers the main room only. Part of paid plans — a room without them answers 402 plan_required. The call UI uses these routes when its backend has breakouts: true; everything but move yourself is a moderator’s action, so check that before forwarding.
Get the breakout rooms
Whether they are open, and who is assigned to each. Someone not listed is in the main room.
{
"breakouts": {
"open": true,
"allowChoose": false,
"endsAt": "2026-09-24T14:25:00.000Z",
"openedAt": "2026-09-24T14:10:00.000Z",
"rooms": [
{ "id": "b1", "name": "Group A", "participants": [{ "identity": "student-981:7f3c", "name": "Aisha Khan" }] },
{ "id": "b2", "name": "Group B", "participants": [] }
]
}
}Errors: room_not_found, plan_required, plus the authentication errors.
Open breakout rooms
Opens the rooms and moves everyone assigned who is in the call now. Moderators stay in the main room unless you assign them. Someone assigned who isn’t in the call yet is taken to their room when they join (the call UI does this). Rooms get ids b1, b2, … in the order given.
| Body | Description |
|---|---|
roomsrequirednumber | [{ name? }] | 1 to 20 rooms — a count, or a list naming them. Unnamed rooms are “Room 1”, “Room 2”, …. |
assignmentsobject | Who goes where: identity → a room’s position in rooms (from 0) or its id. |
autoboolean | Spread everyone else in the call — moderators excepted — evenly across the rooms, at random. |
allowChooseboolean | Let participants pick a room themselves. |
durationSecnumber | null | Close the rooms after this long: 60 to 86400 seconds. Default: no limit. |
{ "breakouts": { "open": true, "rooms": [ … ], … } }Errors: room_not_found, plan_required, invalid_rooms, invalid_assignments, invalid_duration, breakouts_open, licence_limit, busy, plus the authentication errors.
Move someone
To a breakout, or back to the main room. Someone in the call is moved straight away (moved: true); someone who has joined before but isn’t connected now is assigned, and goes there when they rejoin.
| Body | Description |
|---|---|
identityrequiredstring | Who to move. |
torequiredstring | A room’s id, or "main". |
{ "moved": true, "breakouts": { … } }Errors: room_not_found, plan_required, invalid_identity, invalid_target, breakouts_closed, breakout_not_found, participant_not_found, busy, plus the authentication errors.
Move yourself
Someone asking to change rooms. A moderator may go to any room; a participant to the one they were assigned, to any when allowChoose is on, or back to the main room — which is where a browser left in a breakout after it closed goes too. Answers with credentials for that room, shaped like a token’s, from the same grant they joined with. Set identity from your own session — it is who moves.
| Body | Description |
|---|---|
identityrequiredstring | Who is moving: someone who has joined this room. |
torequiredstring | A room’s id, or "main". |
{
"url": "wss://livekit.inlinemeet.com",
"token": "eyJhbGciOi…",
"identity": "student-981:7f3c",
"name": "Aisha Khan",
"role": "participant",
"participantsCanUnmute": true,
"breakout": "b1"
}Errors: room_not_found, plan_required, invalid_identity, invalid_target, breakouts_closed, breakout_not_found, not_your_room, participant_not_found, licence_limit, plus the authentication errors.
Message every room
A banner on every screen, in every room and the main one — “Two minutes left”.
| Body | Description |
|---|---|
messagerequiredstring | Up to 300 characters. |
fromstring | Whose name to show with it. |
{ "ok": true }Errors: room_not_found, plan_required, invalid_message, breakouts_closed, plus the authentication errors.
Close the breakout rooms
Everyone comes back to the main room. Closing rooms that aren’t open does nothing. Rooms with a time limit close themselves.
{ "breakouts": { "open": false, … } }Errors: room_not_found, plan_required, busy, plus the authentication errors.
Drawing tablets
A moderator in the call on a laptop pairs a tablet — an iPad with a pencil, say — that joins the call to draw on the whiteboard for them: no camera or microphone, no sound, and never shown as a person. The tablet’s side is a page InlineMeet serves; the code says which call it is. Check the caller is the moderator named by identity before forwarding: a code makes a device part of the call as them. The call UI uses these routes when its backend has companions: true. Part of paid plans, with the whiteboard.
Pair a tablet
A code for the tablet. Show qr for it to scan — it opens url — or have it type code in at inlinemeet.com/pen. Unused, the code works until expiresAt (five minutes); the first device to use it is the only one that can, from then on. The tablet joins whichever room of the call the moderator is in, goes where they go, and lasts as long as their token. Pairing again disconnects the last tablet: one per moderator. Keep resumeKey with the moderator’s browser.
| Body | Description |
|---|---|
identityrequiredstring | The moderator the tablet draws for. They must be in the call. |
{
"code": "K7PX-2MWD",
"url": "https://inlinemeet.com/pen/K7PX-2MWD",
"expiresAt": 1790253000000,
"qr": { "size": 29, "path": "M0 0h7v1h-7zM9 0h1v1h-1z…" },
"resumeKey": "q3Yc…",
"companionIdentity": "pen~4f1c9a2b7d3e"
}Errors: room_not_found, invalid_identity, participant_not_found, not_moderator, is_companion, not_in_call, plan_required, plus the authentication errors.
Take a tablet back
A moderator who rejoined the call as someone new — a reload mints a new identity — makes their tablet theirs again. The tablet waits two minutes for this before leaving the call. Answers whether it is in the call now.
| Body | Description |
|---|---|
identityrequiredstring | The moderator, as they are now. |
keyrequiredstring | The resumeKey from pairing. |
{ "companionIdentity": "pen~4f1c9a2b7d3e", "connected": true }Errors: room_not_found, invalid_identity, invalid_key, participant_not_found, not_moderator, pairing_not_found, plus the authentication errors.
Disconnect a tablet
Takes the moderator’s tablet out of the call and ends its code. Ending the call does the same for every tablet.
| Body | Description |
|---|---|
identityrequiredstring | The moderator whose tablet it is. |
{ "ok": true, "removed": 1 }Errors: room_not_found, invalid_identity, participant_not_found, not_moderator, plus the authentication errors.
Participants and moderation
Who is in a call, and the moderator actions on them — muting, letting one person speak or present, removing them.
List participants
Everyone connected right now, in the main room and every breakout — breakout is the room’s id, or null in the main room. microphoneMuted is null for someone not publishing a microphone at all. A moderator’s drawing tablet is listed with companionOf, the moderator it draws for — it isn’t a person, so leave it out of a head count.
{
"participants": [
{
"identity": "student-981:7f3c",
"name": "Aisha Khan",
"metadata": { "seat": 14 },
"joinedAt": "2026-09-24T14:01:12.000Z",
"microphoneMuted": true,
"breakout": null
}
]
}Errors: room_not_found, plus the authentication errors.
Mute
Mutes one participant, or with no identity every regular participant. Returns the identities whose microphone was live.
| Body | Description |
|---|---|
identitystring | Who to mute. Omit to mute everyone but the moderators. |
{ "muted": ["student-981:7f3c"] }Errors: room_not_found, participant_not_found, plus the authentication errors.
Allow one person to speak
While microphones are locked, lets one participant unmute — or, with false, takes it back and mutes them. Lasts for their current connection.
| Body | Description |
|---|---|
allowedrequiredboolean | true to let them speak, false to take it back. |
{ "ok": true, "allowed": true }Errors: room_not_found, participant_not_found, is_moderator, plus the authentication errors.
Give one person permissions
Lets one participant do more than their token granted — or takes it back. Anything left out stays as it is. Lasts for their current connection and carries into breakout rooms; someone who rejoins has what their new token grants. Taking screen sharing back stops a share in progress.
| Body | Description |
|---|---|
screenShareboolean | Share their screen. Enforced by the media server. |
pollsboolean | Ask polls, like a moderator. Paid plans. |
qnaboolean | Answer, file and dismiss Q&A questions. Paid plans. |
whiteboardboolean | Draw on the whiteboard while it is locked. Paid plans. |
stageboolean | In a webinar: come up on stage from the audience, publishing what their token granted — or go back, muted. |
{
"identity": "student-981:7f3c",
"permissions": { "screenShare": true, "polls": true, "qna": false, "whiteboard": false, "stage": false }
}Errors: room_not_found, participant_not_found, is_moderator, plan_required, not_webinar, plus the authentication errors.
Remove someone
Disconnects one participant, from the main room or a breakout. The token they hold can no longer move them between rooms or re-mint on a waiting-room ticket. Whether they may come back is yours to decide: don’t mint them another token. The media server can’t revoke the one they have, so keep token lifetimes short where that matters. Moderators can’t be removed.
{ "ok": true }Errors: room_not_found, participant_not_found, is_moderator, plus the authentication errors.
Recordings
Recordings are made on the media server and outlive their room. The recording guide explains the lifecycle; webhooks tell you when one is ready.
Start a recording
Starts recording the whole room — every voice, and the screen share — until it is stopped or the room closes. One per room at a time.
| Body | Description |
|---|---|
audioOnlyboolean | Sound only, as Ogg/Opus — a much smaller file. Default false: an MP4 with video. |
startedBystring | Your id for who started it. |
startedByNamestring | Their display name. |
labelsobject | Up to 20 string pairs of your own. |
{ "recordingId": "66f1c0a4e1b2c3d4e5f60718", "status": "recording" }Errors: room_not_found, recordings_disabled, invalid_labels, recording_in_progress, room_not_live, recorder_unavailable, storage_unavailable, plus the authentication errors.
List a room’s recordings
Newest first. Works after the room is closed.
{ "recordings": [{
"id": "66f1c0a4e1b2c3d4e5f60718",
"roomCode": "algebra-101",
"status": "ready",
"hasVideo": true,
"mimeType": "video/mp4",
"startedBy": "teacher-12",
"startedByName": "Alex Morgan",
"startedAt": "2026-09-24T14:00:03.000Z",
"endedAt": "2026-09-24T14:47:40.000Z",
"durationSec": 2857,
"sizeBytes": 123731968,
"labels": { "courseId": "alg-1" },
"autoStarted": false
}] }Errors: room_not_found, plus the authentication errors.
Stop a recording
Returns the recording, now processing; once the file is written and compressed it becomes ready — usually within a few minutes — and your webhook receives recording.ready. Idempotent. (…/complete is a deprecated alias.)
{
"id": "66f1c0a4e1b2c3d4e5f60718",
"roomCode": "algebra-101",
"status": "processing",
"hasVideo": true,
"mimeType": "video/mp4",
"startedBy": "teacher-12",
"startedByName": "Alex Morgan",
"startedAt": "2026-09-24T14:00:03.000Z",
"endedAt": "2026-09-24T14:47:40.000Z",
"labels": { "courseId": "alg-1" },
"autoStarted": false
}Errors: recording_not_found, plus the authentication errors.
Abort a recording
Stops a recording and throws it away — nothing is kept, and no webhook is sent. A no-op for one that already finished.
{ "ok": true }Errors: recording_not_found, plus the authentication errors.
Get a playback link
A link that plays the file in the browser, valid for 15 minutes.
| Query | Description |
|---|---|
download1 | A link that saves the file instead of playing it. |
{
"url": "https://…/66f1c0a4e1b2c3d4e5f60718.mp4?X-Amz-Expires=900&…",
"expiresAt": "2026-09-24T15:02:40.000Z",
"mimeType": "video/mp4",
"sizeBytes": 123731968,
"durationSec": 2857
}Errors: recording_not_found, recording_not_ready, plus the authentication errors.
Update labels
Merges labels into the recording’s own; an empty value removes that key. Returns the recording.
| Body | Description |
|---|---|
labelsrequiredobject | e.g. { "lessonId": "week-3", "draft": "" } |
{
"id": "66f1c0a4e1b2c3d4e5f60718",
"roomCode": "algebra-101",
"status": "ready",
"hasVideo": true,
"mimeType": "video/mp4",
"startedBy": "teacher-12",
"startedByName": "Alex Morgan",
"startedAt": "2026-09-24T14:00:03.000Z",
"endedAt": "2026-09-24T14:47:40.000Z",
"durationSec": 2857,
"sizeBytes": 123731968,
"labels": { "courseId": "alg-1" },
"autoStarted": false
}Errors: recording_not_found, invalid_labels, plus the authentication errors.
Delete a recording
Deletes the file and its record, for good — stopping it first if it is still running.
{ "ok": true }Errors: recording_not_found, plus the authentication errors.
List all recordings
Across every room, newest first. Filters combine.
| Query | Description |
|---|---|
roomstring, repeatable | Only these rooms. |
label.<key>string | Label <key> equals this value. |
statusstring | waiting, recording, processing, ready, failed or aborted. |
limitnumber | Up to 500, default 200. |
{ "recordings": [{
"id": "66f1c0a4e1b2c3d4e5f60718",
"roomCode": "algebra-101",
"status": "ready",
"hasVideo": true,
"mimeType": "video/mp4",
"startedBy": "teacher-12",
"startedByName": "Alex Morgan",
"startedAt": "2026-09-24T14:00:03.000Z",
"endedAt": "2026-09-24T14:47:40.000Z",
"durationSec": 2857,
"sizeBytes": 123731968,
"labels": { "courseId": "alg-1" },
"autoStarted": false
}] }Errors: invalid_status, invalid_label, plus the authentication errors.
Live streams
A room’s call streamed live over RTMP to YouTube, Facebook, Instagram and the rest — up to five at once. The live streaming guide covers platforms, keys and what shows on screen. Stream keys are never returned once given: each destination shows only keyHint.
Go live
Streams the room’s call to every destination at once, until it is stopped, every destination has failed, or the call ends. One stream per room at a time.
| Body | Description |
|---|---|
destinationsrequiredarray | 1–5 of { platform, key, server?, name?, save? } — server where the platform gives each stream its own — or { savedId }. |
orientationstring | landscape (1280×720) or portrait (720×1280). Default: portrait only when every destination is Instagram or TikTok. |
activityboolean | object | What shows over the picture, kept as the room’s streamActivity. |
startedBystring | Your id for who started it. |
startedByNamestring | Their display name. |
{
"id": "6702b1c4e1b2c3d4e5f60719",
"roomCode": "product-launch",
"status": "starting",
"orientation": "landscape",
"startedBy": "host-7",
"startedByName": "Alex Morgan",
"startedAt": "2026-10-09T16:00:02.000Z",
"liveAt": "2026-10-09T16:00:06.000Z",
"endedAt": null,
"durationSec": 1260,
"error": null,
"destinations": [
{ "id": "d_3f9a1c22b0", "platform": "youtube", "name": "Our channel", "server": "rtmps://a.rtmps.youtube.com:443/live2",
"keyHint": "…x7q2", "status": "connecting", "error": null, "savedId": null }
]
}Errors: room_not_found, plan_required, invalid_destinations, invalid_destination, too_many_destinations, invalid_orientation, destination_not_found, stream_in_progress, room_not_live, recorder_unavailable, plus the authentication errors.
The room’s streams
The stream running now (or null), the room’s 20 most recent, and the workspace’s saved destinations — and, for a room set up from a template, remembered: the destinations and picture it was prepared with, which the call UI’s Live tab starts from.
{ "stream": {
"id": "6702b1c4e1b2c3d4e5f60719",
"roomCode": "product-launch",
"status": "live",
"orientation": "landscape",
"startedBy": "host-7",
"startedByName": "Alex Morgan",
"startedAt": "2026-10-09T16:00:02.000Z",
"liveAt": "2026-10-09T16:00:06.000Z",
"endedAt": null,
"durationSec": 1260,
"error": null,
"destinations": [
{ "id": "d_3f9a1c22b0", "platform": "youtube", "name": "Our channel", "server": "rtmps://a.rtmps.youtube.com:443/live2",
"keyHint": "…x7q2", "status": "live", "error": null, "savedId": null }
]
}, "streams": […], "saved": […],
"remembered": { "savedIds": ["6702c0d1e1b2c3d4e5f60a77"], "orientation": "landscape" } }Errors: room_not_found, plus the authentication errors.
Get a stream
Its status, and each destination’s: connecting, live, failed (with error), removed or ended.
{
"id": "6702b1c4e1b2c3d4e5f60719",
"roomCode": "product-launch",
"status": "live",
"orientation": "landscape",
"startedBy": "host-7",
"startedByName": "Alex Morgan",
"startedAt": "2026-10-09T16:00:02.000Z",
"liveAt": "2026-10-09T16:00:06.000Z",
"endedAt": null,
"durationSec": 1260,
"error": null,
"destinations": [
{ "id": "d_3f9a1c22b0", "platform": "youtube", "name": "Our channel", "server": "rtmps://a.rtmps.youtube.com:443/live2",
"keyHint": "…x7q2", "status": "live", "error": null, "savedId": null }
]
}Errors: stream_not_found, plus the authentication errors.
Add destinations while live
They join the stream as it is. Returns the stream.
| Body | Description |
|---|---|
destinationsrequiredarray | As for going live. |
{
"id": "6702b1c4e1b2c3d4e5f60719",
"roomCode": "product-launch",
"status": "live",
"orientation": "landscape",
"startedBy": "host-7",
"startedByName": "Alex Morgan",
"startedAt": "2026-10-09T16:00:02.000Z",
"liveAt": "2026-10-09T16:00:06.000Z",
"endedAt": null,
"durationSec": 1260,
"error": null,
"destinations": [
{ "id": "d_3f9a1c22b0", "platform": "youtube", "name": "Our channel", "server": "rtmps://a.rtmps.youtube.com:443/live2",
"keyHint": "…x7q2", "status": "live", "error": null, "savedId": null }
]
}Errors: stream_not_found, stream_ended, invalid_destination, too_many_destinations, destination_failed, plus the authentication errors.
Take a destination off
The others carry on. Taking off the last one still on air stops the stream. Returns the stream.
{
"id": "6702b1c4e1b2c3d4e5f60719",
"roomCode": "product-launch",
"status": "live",
"orientation": "landscape",
"startedBy": "host-7",
"startedByName": "Alex Morgan",
"startedAt": "2026-10-09T16:00:02.000Z",
"liveAt": "2026-10-09T16:00:06.000Z",
"endedAt": null,
"durationSec": 1260,
"error": null,
"destinations": [
{ "id": "d_3f9a1c22b0", "platform": "youtube", "name": "Our channel", "server": "rtmps://a.rtmps.youtube.com:443/live2",
"keyHint": "…x7q2", "status": "live", "error": null, "savedId": null }
]
}Errors: stream_not_found, destination_not_found, plus the authentication errors.
Stop a stream
Takes it off every platform at once. Idempotent. Returns the stream, ended.
{
"id": "6702b1c4e1b2c3d4e5f60719",
"roomCode": "product-launch",
"status": "ended",
"orientation": "landscape",
"startedBy": "host-7",
"startedByName": "Alex Morgan",
"startedAt": "2026-10-09T16:00:02.000Z",
"liveAt": "2026-10-09T16:00:06.000Z",
"endedAt": null,
"durationSec": 1260,
"error": null,
"destinations": [
{ "id": "d_3f9a1c22b0", "platform": "youtube", "name": "Our channel", "server": "rtmps://a.rtmps.youtube.com:443/live2",
"keyHint": "…x7q2", "status": "ended", "error": null, "savedId": null }
]
}Errors: stream_not_found, plus the authentication errors.
List saved destinations
The workspace’s saved destinations — or a scope’s — most recently used first. Never their keys.
| Query | Description |
|---|---|
scopestring, up to 64 | That scope’s destinations instead of the workspace’s own — the same goes for forgetting one. One of your own customers’ — letters, digits, : . _ -, such as school:42. See scopes. |
{ "destinations": [{ "id": "6702c0…", "platform": "youtube", "name": "Our channel", "server": "rtmps://a.rtmps.youtube.com:443/live2", "keyHint": "…x7q2", "lastUsedAt": "2026-10-09T16:00:02.000Z", "createdAt": "2026-10-01T09:12:00.000Z" }] }Save a destination
Checked as a stream’s destination is, then kept so a stream can name it by savedId.
| Body | Description |
|---|---|
platformrequiredstring | youtube, facebook, instagram, linkedin, twitch, x, tiktok, kick or custom. |
keyrequiredstring | The stream key. |
serverstring | The stream URL, where the platform gives each stream its own. |
namestring | What to call it. |
scopestring, up to 64 | Saves it for that scope: only its rooms and templates can use it. One of your own customers’ — letters, digits, : . _ -, such as school:42. See scopes. |
{ "id": "6702c0…", "platform": "youtube", "name": "Our channel", "server": "rtmps://a.rtmps.youtube.com:443/live2", "keyHint": "…x7q2", "lastUsedAt": null, "createdAt": "2026-10-09T15:58:00.000Z" }Errors: invalid_destination, too_many_destinations, plus the authentication errors.
Forget a saved destination
Key and all. A stream already using it carries on.
{ "ok": true }Errors: destination_not_found, plus the authentication errors.
Webinars and broadcasts
Running a webinar from your own app: starting it from backstage, a call to action for everyone watching, and your brand on its streams and recordings. Scenes and the look are room settings.
Start a webinar
Takes a webinar that opened backstage on air: everyone waiting is let in at once (their tickets answer with tokens), a stream showing “Starting soon” goes over to the call, and auto-record starts. Idempotent — admitted is 0 the second time.
{ "onAirAt": "2026-10-22T16:00:04.000Z", "admitted": 186 }Errors: room_not_found, not_webinar, plus the authentication errors.
The call to action
The one up now, or null, and how often it has been pressed since it went up — and preset: the one the room was prepared with by its template, which the call UI offers ready to put up, or null.
{ "cta": { "id": "cta_9f2c4a1b0e", "headline": "See it on your own data", "label": "Book a demo", "url": "https://acme.example/demo" }, "clicks": 64,
"preset": { "headline": "See it on your own data", "label": "Book a demo", "url": "https://acme.example/demo" } }Errors: room_not_found, plus the authentication errors.
Put up a call to action
A button for everyone in the call, and a banner with a QR code on its streams and recordings. Replaces any before it, and its count starts again. Any room — not only webinars.
| Body | Description |
|---|---|
labelrequiredstring, up to 32 | The button: “Book a demo”. |
urlrequiredstring | What it opens. https:// is added when left off. |
headlinestring, up to 90 | A line above it: “See it on your own data”. |
{ "cta": { "id": "cta_9f2c4a1b0e", "headline": "See it on your own data", "label": "Book a demo", "url": "https://acme.example/demo" }, "clicks": 0 }Errors: room_not_found, invalid_cta, plus the authentication errors.
Take it down
From the call, the stream and the recording.
{ "ok": true }Errors: room_not_found, plus the authentication errors.
Count a press
Someone in the call pressed it — your call UI backend’s cta.click forwards it. A press on one that has since been replaced counts for nothing (counted: false).
| Body | Description |
|---|---|
idrequiredstring | The call to action’s id, as the call showed it. |
identitystring | Who pressed it, for the report. |
{ "counted": true }Errors: room_not_found, plus the authentication errors.
Your brand
The logo and accent colour your calls, streams and recordings show, where a room’s look lets them, and the call colours your recordings and streams wear.
| Query | Description |
|---|---|
scopestring, up to 64 | That scope’s own brand: null where it has none, its rooms showing the workspace’s there. One of your own customers’ — letters, digits, : . _ -, such as school:42. See scopes. |
{ "color": "#0ea5a4", "logoUrl": "https://inlinemeet.com/brand/66f0a1…/3f9a1c22b0e4d5c6", "theme": null }Set your brand
For every room in the workspace: the logo in the corner, and the colour of speakers’ titles, the call-to-action banner and the scenes. Anything left out stays as it is. The dashboard’s Settings page sets the same.
| Body | Description |
|---|---|
logostring | null | A data: URL of a PNG, JPEG or WebP under 300 KB — a light logo on a transparent background reads best. null removes it. |
colorstring | null | #rrggbb. null goes back to inlinemeet’s. |
themeobject | null | For every room in the workspace (a template’s own colours take its place in the rooms made from it). The colours you give the call where you embed it, so your recordings and streams match: the call’s --im-* tokens, named without the prefix — { "surface": "#ffffff", "ink": "#111827", "accent": "#1e5b22", "warn-fill": "#fcc223" }. Chat, questions, polls, raised hands, speakers’ titles and the scenes are then drawn in them. Colours are hex, rgb() or hsl(); avatar-s, avatar-l, avatar-ink-s and avatar-ink-l are percentages. The font, radius and canvas don’t carry over. A token left out is the call’s own; each change replaces the whole theme, and null goes back to inlinemeet’s. A scope’s theme stands whole over the workspace’s. |
scopestring, up to 64 | Sets that scope’s brand instead, for its rooms; whatever it hasn’t got of its own comes from the workspace’s. One of your own customers’ — letters, digits, : . _ -, such as school:42. See scopes. |
{ "color": "#0ea5a4", "logoUrl": "https://inlinemeet.com/brand/66f0a1…/3f9a1c22b0e4d5c6", "theme": { "surface": "#ffffff", "accent": "#1e5b22" } }Errors: invalid_logo, invalid_color, invalid_theme, plus the authentication errors.
Webhooks
Where we send events such as recording.ready. The webhooks guide covers the events and verifying them.
Get webhook settings
Your endpoint, its signing secret and the 20 latest deliveries. url and secret are null when webhooks are off.
{
"url": "https://example.com/webhooks/inlinemeet",
"secret": "whsec_…",
"deliveries": [
{
"id": "evt_9f2c41d07a6b3e8c15d2a0f4",
"type": "recording.ready",
"status": "delivered",
"attempts": 1,
"lastError": null,
"createdAt": "2026-09-24T14:47:46.120Z",
"deliveredAt": "2026-09-24T14:47:46.410Z",
"nextAttemptAt": null
}
]
}Set the endpoint
A public https URL. An empty string turns webhooks off. The secret is made the first time and kept when the URL changes.
| Body | Description |
|---|---|
urlrequiredstring | e.g. https://example.com/webhooks/inlinemeet |
{ "url": "https://example.com/webhooks/inlinemeet", "secret": "whsec_…" }Errors: invalid_request, invalid_webhook_url, plus the authentication errors.
Send a test event
Sends a signed webhook.test event now and reports how your endpoint answered. Never retried.
{ "ok": true, "status": 200 }Rotate the signing secret
Replaces the secret and returns the new one. Everything is signed with it from now on, retries included.
{ "url": "https://example.com/webhooks/inlinemeet", "secret": "whsec_…" }Errors: webhook_not_configured, plus the authentication errors.
People
Who is in your workspace, for when your app has to reach them.
List people
Everyone who has joined the workspace, the owner first, then account managers and meeting bookers. Pending invitations aren’t listed.
{
"members": [
{ "id": "6ad739b1…", "name": "Sara Malik", "email": "sara@example.com", "role": "owner", "joinedAt": "2026-09-02T10:14:00.000Z" },
{ "id": "6ad73a02…", "name": "Omar Haq", "email": "omar@example.com", "role": "manager", "joinedAt": "2026-09-20T08:40:00.000Z" }
]
}Usage
The same numbers your bill is made from.
Get usage
Participant-minutes and sessions for a month, what you store now, and your host licences in use. See Usage and limits.
| Query | Description |
|---|---|
periodYYYY-MM | The month. Defaults to the current one (UTC). |
{
"period": "2026-09",
"participantMinutes": 7420.5,
"participantSessions": 311,
"storedGb": 4.812,
"includedMinutes": 30000,
"licences": { "total": 3, "inUse": 1 }
}Errors: invalid_period, plus the authentication errors.
Errors
Every error has the same shape, with a stable machine-readable code and a message meant for people:
{ "error": { "code": "unauthorized", "message": "Unknown or revoked API key." } }| Status | Code | Means |
|---|---|---|
| 400 | invalid_code | A room code isn’t 1–64 letters, digits, - or _. |
| 400 | invalid_identity | identity is missing or longer than 128 characters. |
| 400 | invalid_role | role isn’t moderator or participant. |
| 400 | invalid_setting | A setting has the wrong type — waitingRoom, autoRecord and the like must be true or false; recordingActivity takes only its five parts. |
| 400 | invalid_name, invalid_kind | A template’s name is empty or over 80 characters, or its kind isn’t meeting or webinar. |
| 404 | template_not_found | No template with that id in your workspace — or in the scope asked for, or the room’s. |
| 400 | invalid_scope | A scope isn’t 1–64 letters, digits, :, ., _ or -, starting with a letter or digit. |
| 409 | scope_mismatch | The room already belongs to another scope. |
| 409 | too_many_templates | A workspace keeps up to 100 templates, and each scope 100 of its own. Delete one first. |
| 400 | invalid_default | default isn’t true or false — or is true for a webinar template: only a meeting template can be the default. |
| 409 | default_template | The default can’t become a webinar template. Send default: false with it, or make another the default first. |
| 400 | invalid_permission | A permission — screenShare, polls, qna, whiteboard — isn’t true or false. |
| 400 | invalid_password | A room password must be 4 to 64 characters. |
| 400 | metadata_too_large | Token metadata is 4 KB or more. |
| 400 | invalid_webhook_url | The webhook URL isn’t a public https address. The message says why. |
| 400 | invalid_labels, invalid_label, invalid_status, invalid_period | A label, filter or period is malformed. |
| 400 | invalid_title | A saved whiteboard’s new title is empty. |
| 400 | invalid_request | The body is missing something it needs. |
| 400 | invalid_rooms, invalid_assignments, invalid_duration | Breakout rooms asked for in a form the API can’t take — 1 to 20 rooms, assignments as an object, a time limit of 60 to 86400 seconds. |
| 400 | invalid_target, invalid_message | to isn’t a room’s id or "main"; or a message to every room is empty. |
| 400 | invalid_key | A drawing tablet’s resume key is missing. |
| 400 | invalid_destinations, invalid_destination, invalid_orientation, invalid_activity | A live stream’s destinations, shape or on-screen activity are malformed — an address that isn’t rtmp(s)://, a missing key, a private address. The message says why. |
| 400 | too_many_destinations | A stream goes to at most five places at once; a workspace saves up to 50. |
| 400 | invalid_cta | A call to action without a label or a link, a label over 32 characters, or a link that isn’t http(s). The message says which. |
| 400 | invalid_logo, invalid_color | The logo isn’t a PNG, JPEG or WebP data: URL under 300 KB; or the colour isn’t #rrggbb. |
| 400 | invalid_theme | The theme names a token the call doesn’t have, or a value that isn’t a colour (or, for the avatar tokens, a percentage). The message says which. |
| 401 | unauthorized | No API key, or an unknown or revoked one. |
| 401 | password_required | The room has a password and none was given. |
| 402 | plan_required | The workspace’s plan doesn’t include the API — or, for whiteboard, breakout and permission routes, those features. |
| 403 | tenant_suspended | The workspace is suspended. |
| 403 | recordings_disabled | This room can’t be recorded — or set to record automatically. |
| 403 | streaming_disabled, webinar_disabled | Going live, or making the call a webinar, is turned off in this room (allowLiveStreaming, allowWebinar). |
| 403 | snapshots_disabled | This room’s whiteboard can’t be saved through the API — a meeting hosted at /meet. |
| 403 | wrong_password | That isn’t the room’s password. |
| 403 | denied | A moderator turned this person away from the waiting room. |
| 403 | whiteboard_locked | Only moderators can change the whiteboard right now. |
| 403 | not_your_room | The host assigns the breakout rooms in this call; that isn’t this person’s. |
| 403 | not_moderator, is_companion | Only a moderator can pair a drawing tablet — and a tablet can’t pair another. |
| 404 | room_not_found, recording_not_found, participant_not_found, snapshot_not_found, stream_not_found, destination_not_found | No such thing — or not in the call right now. |
| 404 | breakout_not_found | No breakout room with that id. |
| 404 | pairing_not_found | That drawing tablet isn’t paired any more: it was disconnected, paired again, or its call ended. |
| 404 | ticket_not_found, not_waiting | The waiting-room ticket expired, or that person is no longer waiting. |
| 409 | recording_in_progress | A recording is already running in this room. |
| 409 | stream_in_progress, stream_ended | A live stream is already running in this room — add destinations to it; or the stream has ended. |
| 409 | not_webinar | Only a webinar has a stage, or goes on air. Turn webinar mode on first. |
| 502 | destination_failed | The media server wouldn’t stream to that address. The message says why. |
| 409 | room_not_live | Nobody is in the call, so there’s nothing to record yet. |
| 409 | recording_not_ready | The recording isn’t ready yet. |
| 409 | webhook_not_configured | Set a webhook URL first. |
| 409 | is_moderator | Moderators can always speak and share, and can’t be removed; there’s nothing to allow. |
| 409 | too_many_boards, busy | The room has 20 boards already; or another change was being made at the same moment — retry. |
| 409 | whiteboard_empty, too_many_snapshots | There is nothing on the whiteboard to save; or the room keeps 500 saved copies already — delete some. |
| 409 | breakouts_open, breakouts_closed | Breakout rooms are already open (close them first), or aren’t open. |
| 409 | not_in_call | The moderator isn’t in the call, so there’s nothing for a tablet to join. |
| 409 | licence_limit | Every host licence is in use, so another call can’t start until one ends. Joining a running call never needs one. |
| 410 | call_ended | A moderator ended the call while this person was in the waiting room. Joining again asks afresh. |
| 413 | board_full, too_large | The board is at its limit, or the save is too big to take at once. |
| 429 | rate_limited | Too many wrong passwords for this room. Wait and retry. |
| 503 | lobby_full | Too many people are waiting to join this room. |
| 503 | media_server_starting | Every media server is busy, so the call can’t open until another starts — about a minute. Retry after Retry-After (3 s); the call UI shows “Getting a server ready” meanwhile. Only a call that isn’t running yet waits. |
| 503 | media_server_unavailable, storage_unavailable, recorder_unavailable | A dependency is briefly unavailable or busy. Retry with backoff. |
Formats and limits
| Thing | Rule |
|---|---|
| Room code | 1–64 characters: letters, digits, - and _. Case-sensitive. |
| Identity | 1–128 characters. URL-encode it in paths. |
| Token lifetime | 6 hours by default, 24 hours at most. |
| Token metadata | Under 4 KB. |
| Labels | Up to 20 per recording; keys 1–40 of letters, digits, - and _; values up to 200 characters. |
| Recordings | One at a time per room. MP4 (H.264/AAC, 720p) or, audio-only, Ogg/Opus. |
| Recording links | From …/file: 15 minutes. In a webhook: 24 hours. |
| Live streams | One at a time per room, to up to 5 rtmp:// or rtmps:// destinations on the public internet. 720p at 30 fps, landscape or portrait. |
| Webhooks | A 2xx within 10 seconds, or retried for about two days. Public https URLs only. |