Skip to content
REST API

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 URLhttps://api.inlinemeet.com/v1
AuthenticationAuthorization: Bearer imk_… on every request — API keys
RequestsJSON bodies, with Content-Type: application/json
ResponsesJSON. Times are ISO 8601, in UTC.
ScopeA 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

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

POST/v1/rooms

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.

BodyDescription
codestring1–64 letters, digits, - or _. Omit it and one is generated for you.
namestring, up to 120A human-readable name. Updated if you send a different one.
scopestring, up to 64Puts 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 | nullOne 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.
participantsCanUnmutebooleanOptional, as for updating a room. false mutes every participant and stops them unmuting until it’s true again.
waitingRoombooleanOptional, 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 | nullOptional, as for updating a room. Participants must give it to join (relayed to the token request). Stored only as a hash. null removes it.
autoRecordbooleanOptional, 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.
whiteboardLockedbooleanOptional, as for updating a room. Only moderators may change the whiteboard. Moderators can still let one person draw (permissions.whiteboard).
whiteboardFollowHostbooleanOptional, 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.
whiteboardHideCursorsbooleanOptional, 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 | objectOptional, 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 | objectOptional, as for updating a room. The same, for a live stream. Never set, a stream shows chat, Q&A, polls and reactions.
webinarbooleanOptional, 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.
backstagebooleanOptional, 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 | objectOptional, 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' | nullOptional, 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.
allowLiveStreamingbooleanWhether 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.
allowWebinarbooleanWhether 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.
201 Created
{
  "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

GET/v1/rooms/:code

The room, plus participantCount — how many people are connected right now — and the recording in progress, if there is one.

200 OK
{
  "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

PATCH/v1/rooms/:code

Every field is optional, and every setting applies to the live call at once. Moderators are exempt from all of them. See moderation.

BodyDescription
participantsCanUnmutebooleanfalse mutes every participant and stops them unmuting until it’s true again.
waitingRoombooleanParticipants 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 | nullParticipants must give it to join (relayed to the token request). Stored only as a hash. null removes it.
autoRecordbooleanStart recording when a moderator joins — once per session of the call. Turned on with a moderator already there, it starts at once.
whiteboardLockedbooleanOnly moderators may change the whiteboard. Moderators can still let one person draw (permissions.whiteboard).
whiteboardFollowHostbooleanWith 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.
whiteboardHideCursorsbooleanWith the whiteboard locked: only moderators’ cursors are shown — and anyone a moderator let draw. A moderator can show them again from the board.
recordingActivityboolean | objectWhat 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 | objectThe same, for a live stream. Never set, a stream shows chat, Q&A, polls and reactions.
webinarbooleanA 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.
backstagebooleanA 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 | objectYour 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' | nullA 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.
allowLiveStreamingbooleanWhether 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.
allowWebinarbooleanWhether 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 120Rename the room.
templatestringSet 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.
200 OK
{
  "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

PATCH/v1/rooms/:code/settings

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.

BodyDescription
participantsCanUnmutebooleanfalse mutes every participant and stops them unmuting until it’s true again.
waitingRoombooleanParticipants 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 | nullParticipants must give it to join (relayed to the token request). Stored only as a hash. null removes it.
autoRecordbooleanStart recording when a moderator joins — once per session of the call. Turned on with a moderator already there, it starts at once.
whiteboardLockedbooleanOnly moderators may change the whiteboard. Moderators can still let one person draw (permissions.whiteboard).
whiteboardFollowHostbooleanWith 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.
whiteboardHideCursorsbooleanWith the whiteboard locked: only moderators’ cursors are shown — and anyone a moderator let draw. A moderator can show them again from the board.
recordingActivityboolean | objectWhat 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 | objectThe same, for a live stream. Never set, a stream shows chat, Q&A, polls and reactions.
webinarbooleanA 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.
backstagebooleanA 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 | objectYour 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' | nullA 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

POST/v1/rooms/:code/end

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.

200 OK
{ "ok": true }

Errors: room_not_found, plus the authentication errors.

Close a room

DELETE/v1/rooms/:code

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.

200 OK
{ "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

GET/v1/templates

The workspace’s templates — or a scope’s — most recently changed first.

QueryDescription
kind'meeting' | 'webinar'Only templates of this kind.
scopestring, up to 64That scope’s templates instead of the workspace’s own. One of your own customers’ — letters, digits, : . _ -, such as school:42. See scopes.
200 OK
{ "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

GET/v1/templates/:id

One template, with its whole setup.

QueryDescription
scopestring, up to 64Needed 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.
200 OK
{
  "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

POST/v1/templates

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.

BodyDescription
namerequiredstring, up to 80What it’s called, on the dashboard.
scopestring, up to 64Makes 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.
defaultbooleanMakes 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.
setupobjectIts setup — any of the fields below.
setup.waitingRoom, setup.backstage, setup.autoRecord, setup.whiteboardLocked, setup.whiteboardFollowHost, setup.whiteboardHideCursorsbooleanAs the room settings of the same names. backstage is a webinar’s.
setup.participantsCanUnmute, setup.guestScreenSharebooleanWho may speak up and share their screen (a meeting’s).
setup.registration, setup.joinBeforeHostbooleanFor 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' | nullThe scene the stream and the recording open on.
setup.lookobjectAs the room setting: logo, titles and each scene’s lines.
setup.recordingActivity, setup.streamActivityobjectAs the room settings.
setup.streamobjectsavedIds — up to 5 of your saved destinations; orientation — landscape, portrait or null; autoStart — go live as a moderator arrives.
setup.ctaobject | nullA call to action ready to put up: { headline?, label, url }.
201 Created
{
  "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

PATCH/v1/templates/:id

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.

BodyDescription
namestring, up to 80A new name.
kind'meeting' | 'webinar'What it’s for. The default stays a meeting template: send default: false with webinar.
defaultbooleantrue makes it the default, in place of the one before; false stops it being the default, leaving none.
setupobjectAs for creating one.
200 OK
{
  "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

DELETE/v1/templates/:id

Rooms set up from it keep their setup. Deleting the default leaves none.

200 OK
{ "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

POST/v1/rooms/:code/tokens

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.

BodyDescription
identityrequiredstring, 1–128Who this connection is. Unique per tab.
namestring, up to 120Display name.
role'moderator' | 'participant'Default participant.
publish{ microphone?, camera?, screenShare? }What a participant may publish. Omit for listen-only.
metadataobject | string, < 4 KBShown to other participants’ UIs. Never an access decision.
ttlSecondsnumberDefault 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.
passwordstringWhat the person typed, when the room has a password. Moderators need none.
bypassbooleanSkip the room’s password and waiting room — for someone your app already trusts, such as an enrolled student.
stagebooleanA webinar speaker: on stage from the start, with what publish grants, and let in while the webinar is backstage.
titlestring, up to 60Their title, under their name on streams and recordings as they come on — “Head of Research”. They can change it in the call.
200 OK
{
  "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

GET/v1/rooms/:code/lobby

First to arrive first. Someone whose browser stops polling drops off within half a minute. backstage counts those waiting for a webinar to start.

200 OK
{
  "waiting": [
    { "id": "66f1c3b2a9e4f1a2b3c4d5e6", "name": "Aisha Khan", "since": 1790000000000 }
  ],
  "backstage": 0
}

Errors: room_not_found, plus the authentication errors.

Let someone in

POST/v1/rooms/:code/lobby/admit

Their next poll returns their token.

BodyDescription
idstringWho, from the list. Omit to let everyone in.
200 OK
{ "admitted": 1 }

Errors: room_not_found, not_waiting, plus the authentication errors.

Turn someone away

POST/v1/rooms/:code/lobby/deny

Their next poll answers 403 denied.

BodyDescription
idrequiredstringWho, from the list.
200 OK
{ "ok": true }

Errors: room_not_found, not_waiting, plus the authentication errors.

Poll a ticket

GET/v1/rooms/:code/lobby/tickets/: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.

200 OK
{ "waiting": true, "pollAfterMs": 2000 }

Errors: room_not_found, ticket_not_found, denied, call_ended, plus the authentication errors.

Stop waiting

DELETE/v1/rooms/:code/lobby/tickets/:ticket

The ticket stops working and they leave the list.

200 OK
{ "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

GET/v1/rooms/:code/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.

200 OK
{
  "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

PATCH/v1/rooms/:code/whiteboard

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.

BodyDescription
boardsrequired[{ id, meta?, elements? }]The boards and elements that changed.
identitystringWho 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.
200 OK
{ "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

POST/v1/rooms/:code/whiteboard/snapshots

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.

BodyDescription
titlestringUp to 120 characters. Defaults to the room’s name.
boardsstring[]Only these boards, by id. Default: every board with something on it.
savedBystringYour id for who saved it.
savedByNamestringTheir display name.
labelsobjectUp to 20 string pairs of your own, to find it again by.
201 Created
{
  "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

GET/v1/rooms/:code/whiteboard/snapshots

Newest first, without their drawings. Works after the room is closed.

200 OK
{ "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

GET/v1/whiteboard/snapshots

Across every room, newest first, without their drawings. Filters combine.

QueryDescription
roomstring, repeatableOnly these rooms.
label.<key>stringLabel <key> equals this value.
limitnumberUp to 500, default 200.
200 OK
{ "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

GET/v1/whiteboard/snapshots/:id

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.

QueryDescription
boardstring, repeatableOnly these boards.
200 OK
{
  "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

PATCH/v1/whiteboard/snapshots/:id

Labels are merged into its own; an empty value removes that key. Returns the saved whiteboard, without drawings.

BodyDescription
titlestringA new title.
labelsobjecte.g. { "lessonId": "week-4", "draft": "" }
200 OK
{
  "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

DELETE/v1/whiteboard/snapshots/:id

For good. The room’s own whiteboard is untouched.

200 OK
{ "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

GET/v1/rooms/:code/breakouts

Whether they are open, and who is assigned to each. Someone not listed is in the main room.

200 OK
{
  "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

POST/v1/rooms/:code/breakouts

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.

BodyDescription
roomsrequirednumber | [{ name? }]1 to 20 rooms — a count, or a list naming them. Unnamed rooms are “Room 1”, “Room 2”, ….
assignmentsobjectWho goes where: identity → a room’s position in rooms (from 0) or its id.
autobooleanSpread everyone else in the call — moderators excepted — evenly across the rooms, at random.
allowChoosebooleanLet participants pick a room themselves.
durationSecnumber | nullClose the rooms after this long: 60 to 86400 seconds. Default: no limit.
200 OK
{ "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

POST/v1/rooms/:code/breakouts/move

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.

BodyDescription
identityrequiredstringWho to move.
torequiredstringA room’s id, or "main".
200 OK
{ "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

POST/v1/rooms/:code/breakouts/join

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.

BodyDescription
identityrequiredstringWho is moving: someone who has joined this room.
torequiredstringA room’s id, or "main".
200 OK
{
  "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

POST/v1/rooms/:code/breakouts/broadcast

A banner on every screen, in every room and the main one — “Two minutes left”.

BodyDescription
messagerequiredstringUp to 300 characters.
fromstringWhose name to show with it.
200 OK
{ "ok": true }

Errors: room_not_found, plan_required, invalid_message, breakouts_closed, plus the authentication errors.

Close the breakout rooms

DELETE/v1/rooms/:code/breakouts

Everyone comes back to the main room. Closing rooms that aren’t open does nothing. Rooms with a time limit close themselves.

200 OK
{ "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

POST/v1/rooms/:code/companions

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.

BodyDescription
identityrequiredstringThe moderator the tablet draws for. They must be in the call.
201 Created
{
  "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

POST/v1/rooms/:code/companions/resume

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.

BodyDescription
identityrequiredstringThe moderator, as they are now.
keyrequiredstringThe resumeKey from pairing.
200 OK
{ "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

POST/v1/rooms/:code/companions/disconnect

Takes the moderator’s tablet out of the call and ends its code. Ending the call does the same for every tablet.

BodyDescription
identityrequiredstringThe moderator whose tablet it is.
200 OK
{ "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

GET/v1/rooms/:code/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.

200 OK
{
  "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

POST/v1/rooms/:code/mute

Mutes one participant, or with no identity every regular participant. Returns the identities whose microphone was live.

BodyDescription
identitystringWho to mute. Omit to mute everyone but the moderators.
200 OK
{ "muted": ["student-981:7f3c"] }

Errors: room_not_found, participant_not_found, plus the authentication errors.

Allow one person to speak

POST/v1/rooms/:code/participants/:identity/microphone

While microphones are locked, lets one participant unmute — or, with false, takes it back and mutes them. Lasts for their current connection.

BodyDescription
allowedrequiredbooleantrue to let them speak, false to take it back.
200 OK
{ "ok": true, "allowed": true }

Errors: room_not_found, participant_not_found, is_moderator, plus the authentication errors.

Give one person permissions

POST/v1/rooms/:code/participants/:identity/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.

BodyDescription
screenSharebooleanShare their screen. Enforced by the media server.
pollsbooleanAsk polls, like a moderator. Paid plans.
qnabooleanAnswer, file and dismiss Q&A questions. Paid plans.
whiteboardbooleanDraw on the whiteboard while it is locked. Paid plans.
stagebooleanIn a webinar: come up on stage from the audience, publishing what their token granted — or go back, muted.
200 OK
{
  "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

POST/v1/rooms/:code/participants/:identity/remove

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.

200 OK
{ "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

POST/v1/rooms/:code/recordings

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.

BodyDescription
audioOnlybooleanSound only, as Ogg/Opus — a much smaller file. Default false: an MP4 with video.
startedBystringYour id for who started it.
startedByNamestringTheir display name.
labelsobjectUp to 20 string pairs of your own.
201 Created — or 202 Accepted with status "waiting" when every recorder is busy: it starts on its own once one has room
{ "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

GET/v1/rooms/:code/recordings

Newest first. Works after the room is closed.

200 OK
{ "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

POST/v1/rooms/:code/recordings/:id/stop

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.)

200 OK
{
  "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

POST/v1/rooms/:code/recordings/:id/abort

Stops a recording and throws it away — nothing is kept, and no webhook is sent. A no-op for one that already finished.

200 OK
{ "ok": true }

Errors: recording_not_found, plus the authentication errors.

Get a playback link

GET/v1/rooms/:code/recordings/:id/file

A link that plays the file in the browser, valid for 15 minutes.

QueryDescription
download1A link that saves the file instead of playing it.
200 OK
{
  "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

PATCH/v1/rooms/:code/recordings/:id

Merges labels into the recording’s own; an empty value removes that key. Returns the recording.

BodyDescription
labelsrequiredobjecte.g. { "lessonId": "week-3", "draft": "" }
200 OK
{
  "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

DELETE/v1/rooms/:code/recordings/:id

Deletes the file and its record, for good — stopping it first if it is still running.

200 OK
{ "ok": true }

Errors: recording_not_found, plus the authentication errors.

List all recordings

GET/v1/recordings

Across every room, newest first. Filters combine.

QueryDescription
roomstring, repeatableOnly these rooms.
label.<key>stringLabel <key> equals this value.
statusstringwaiting, recording, processing, ready, failed or aborted.
limitnumberUp to 500, default 200.
200 OK
{ "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

POST/v1/rooms/:code/streams

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.

BodyDescription
destinationsrequiredarray1–5 of { platform, key, server?, name?, save? } — server where the platform gives each stream its own — or { savedId }.
orientationstringlandscape (1280×720) or portrait (720×1280). Default: portrait only when every destination is Instagram or TikTok.
activityboolean | objectWhat shows over the picture, kept as the room’s streamActivity.
startedBystringYour id for who started it.
startedByNamestringTheir display name.
201 Created — or 202 Accepted with status "waiting" when every recorder is busy: it starts on its own once one has room
{
  "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

GET/v1/rooms/:code/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.

200 OK
{ "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

GET/v1/rooms/:code/streams/:id

Its status, and each destination’s: connecting, live, failed (with error), removed or ended.

200 OK
{
  "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

POST/v1/rooms/:code/streams/:id/destinations

They join the stream as it is. Returns the stream.

BodyDescription
destinationsrequiredarrayAs for going live.
200 OK
{
  "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

DELETE/v1/rooms/:code/streams/:id/destinations/:destinationId

The others carry on. Taking off the last one still on air stops the stream. Returns the stream.

200 OK
{
  "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

POST/v1/rooms/:code/streams/:id/stop

Takes it off every platform at once. Idempotent. Returns the stream, ended.

200 OK
{
  "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

GET/v1/stream-destinations

The workspace’s saved destinations — or a scope’s — most recently used first. Never their keys.

QueryDescription
scopestring, up to 64That 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.
200 OK
{ "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

POST/v1/stream-destinations

Checked as a stream’s destination is, then kept so a stream can name it by savedId.

BodyDescription
platformrequiredstringyoutube, facebook, instagram, linkedin, twitch, x, tiktok, kick or custom.
keyrequiredstringThe stream key.
serverstringThe stream URL, where the platform gives each stream its own.
namestringWhat to call it.
scopestring, up to 64Saves 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.
201 Created
{ "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

DELETE/v1/stream-destinations/:id

Key and all. A stream already using it carries on.

200 OK
{ "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

POST/v1/rooms/:code/webinar/start

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.

200 OK
{ "onAirAt": "2026-10-22T16:00:04.000Z", "admitted": 186 }

Errors: room_not_found, not_webinar, plus the authentication errors.

The call to action

GET/v1/rooms/:code/cta

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.

200 OK
{ "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

POST/v1/rooms/:code/cta

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.

BodyDescription
labelrequiredstring, up to 32The button: “Book a demo”.
urlrequiredstringWhat it opens. https:// is added when left off.
headlinestring, up to 90A line above it: “See it on your own data”.
200 OK
{ "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

DELETE/v1/rooms/:code/cta

From the call, the stream and the recording.

200 OK
{ "ok": true }

Errors: room_not_found, plus the authentication errors.

Count a press

POST/v1/rooms/:code/cta/click

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).

BodyDescription
idrequiredstringThe call to action’s id, as the call showed it.
identitystringWho pressed it, for the report.
200 OK
{ "counted": true }

Errors: room_not_found, plus the authentication errors.

Your brand

GET/v1/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.

QueryDescription
scopestring, up to 64That 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.
200 OK
{ "color": "#0ea5a4", "logoUrl": "https://inlinemeet.com/brand/66f0a1…/3f9a1c22b0e4d5c6", "theme": null }

Set your brand

PUT/v1/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.

BodyDescription
logostring | nullA 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 | nullFor 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 64Sets 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.
200 OK
{ "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

GET/v1/webhook

Your endpoint, its signing secret and the 20 latest deliveries. url and secret are null when webhooks are off.

200 OK
{
  "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

PUT/v1/webhook

A public https URL. An empty string turns webhooks off. The secret is made the first time and kept when the URL changes.

BodyDescription
urlrequiredstringe.g. https://example.com/webhooks/inlinemeet
200 OK
{ "url": "https://example.com/webhooks/inlinemeet", "secret": "whsec_…" }

Errors: invalid_request, invalid_webhook_url, plus the authentication errors.

Send a test event

POST/v1/webhook/test

Sends a signed webhook.test event now and reports how your endpoint answered. Never retried.

200 OK
{ "ok": true, "status": 200 }

Rotate the signing secret

POST/v1/webhook/secret

Replaces the secret and returns the new one. Everything is signed with it from now on, retries included.

200 OK
{ "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

GET/v1/members

Everyone who has joined the workspace, the owner first, then account managers and meeting bookers. Pending invitations aren’t listed.

200 OK
{
  "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

GET/v1/usage

Participant-minutes and sessions for a month, what you store now, and your host licences in use. See Usage and limits.

QueryDescription
periodYYYY-MMThe month. Defaults to the current one (UTC).
200 OK
{
  "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:

401 Unauthorized
{ "error": { "code": "unauthorized", "message": "Unknown or revoked API key." } }
StatusCodeMeans
400invalid_codeA room code isn’t 1–64 letters, digits, - or _.
400invalid_identityidentity is missing or longer than 128 characters.
400invalid_rolerole isn’t moderator or participant.
400invalid_settingA setting has the wrong type — waitingRoom, autoRecord and the like must be true or false; recordingActivity takes only its five parts.
400invalid_name, invalid_kindA template’s name is empty or over 80 characters, or its kind isn’t meeting or webinar.
404template_not_foundNo template with that id in your workspace — or in the scope asked for, or the room’s.
400invalid_scopeA scope isn’t 1–64 letters, digits, :, ., _ or -, starting with a letter or digit.
409scope_mismatchThe room already belongs to another scope.
409too_many_templatesA workspace keeps up to 100 templates, and each scope 100 of its own. Delete one first.
400invalid_defaultdefault isn’t true or false — or is true for a webinar template: only a meeting template can be the default.
409default_templateThe default can’t become a webinar template. Send default: false with it, or make another the default first.
400invalid_permissionA permission — screenShare, polls, qna, whiteboard — isn’t true or false.
400invalid_passwordA room password must be 4 to 64 characters.
400metadata_too_largeToken metadata is 4 KB or more.
400invalid_webhook_urlThe webhook URL isn’t a public https address. The message says why.
400invalid_labels, invalid_label, invalid_status, invalid_periodA label, filter or period is malformed.
400invalid_titleA saved whiteboard’s new title is empty.
400invalid_requestThe body is missing something it needs.
400invalid_rooms, invalid_assignments, invalid_durationBreakout 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.
400invalid_target, invalid_messageto isn’t a room’s id or "main"; or a message to every room is empty.
400invalid_keyA drawing tablet’s resume key is missing.
400invalid_destinations, invalid_destination, invalid_orientation, invalid_activityA 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.
400too_many_destinationsA stream goes to at most five places at once; a workspace saves up to 50.
400invalid_ctaA 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.
400invalid_logo, invalid_colorThe logo isn’t a PNG, JPEG or WebP data: URL under 300 KB; or the colour isn’t #rrggbb.
400invalid_themeThe 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.
401unauthorizedNo API key, or an unknown or revoked one.
401password_requiredThe room has a password and none was given.
402plan_requiredThe workspace’s plan doesn’t include the API — or, for whiteboard, breakout and permission routes, those features.
403tenant_suspendedThe workspace is suspended.
403recordings_disabledThis room can’t be recorded — or set to record automatically.
403streaming_disabled, webinar_disabledGoing live, or making the call a webinar, is turned off in this room (allowLiveStreaming, allowWebinar).
403snapshots_disabledThis room’s whiteboard can’t be saved through the API — a meeting hosted at /meet.
403wrong_passwordThat isn’t the room’s password.
403deniedA moderator turned this person away from the waiting room.
403whiteboard_lockedOnly moderators can change the whiteboard right now.
403not_your_roomThe host assigns the breakout rooms in this call; that isn’t this person’s.
403not_moderator, is_companionOnly a moderator can pair a drawing tablet — and a tablet can’t pair another.
404room_not_found, recording_not_found, participant_not_found, snapshot_not_found, stream_not_found, destination_not_foundNo such thing — or not in the call right now.
404breakout_not_foundNo breakout room with that id.
404pairing_not_foundThat drawing tablet isn’t paired any more: it was disconnected, paired again, or its call ended.
404ticket_not_found, not_waitingThe waiting-room ticket expired, or that person is no longer waiting.
409recording_in_progressA recording is already running in this room.
409stream_in_progress, stream_endedA live stream is already running in this room — add destinations to it; or the stream has ended.
409not_webinarOnly a webinar has a stage, or goes on air. Turn webinar mode on first.
502destination_failedThe media server wouldn’t stream to that address. The message says why.
409room_not_liveNobody is in the call, so there’s nothing to record yet.
409recording_not_readyThe recording isn’t ready yet.
409webhook_not_configuredSet a webhook URL first.
409is_moderatorModerators can always speak and share, and can’t be removed; there’s nothing to allow.
409too_many_boards, busyThe room has 20 boards already; or another change was being made at the same moment — retry.
409whiteboard_empty, too_many_snapshotsThere is nothing on the whiteboard to save; or the room keeps 500 saved copies already — delete some.
409breakouts_open, breakouts_closedBreakout rooms are already open (close them first), or aren’t open.
409not_in_callThe moderator isn’t in the call, so there’s nothing for a tablet to join.
409licence_limitEvery host licence is in use, so another call can’t start until one ends. Joining a running call never needs one.
410call_endedA moderator ended the call while this person was in the waiting room. Joining again asks afresh.
413board_full, too_largeThe board is at its limit, or the save is too big to take at once.
429rate_limitedToo many wrong passwords for this room. Wait and retry.
503lobby_fullToo many people are waiting to join this room.
503media_server_startingEvery 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.
503media_server_unavailable, storage_unavailable, recorder_unavailableA dependency is briefly unavailable or busy. Retry with backoff.

Formats and limits

ThingRule
Room code1–64 characters: letters, digits, - and _. Case-sensitive.
Identity1–128 characters. URL-encode it in paths.
Token lifetime6 hours by default, 24 hours at most.
Token metadataUnder 4 KB.
LabelsUp to 20 per recording; keys 1–40 of letters, digits, - and _; values up to 200 characters.
RecordingsOne at a time per room. MP4 (H.264/AAC, 720p) or, audio-only, Ogg/Opus.
Recording linksFrom …/file: 15 minutes. In a webhook: 24 hours.
Live streamsOne at a time per room, to up to 5 rtmp:// or rtmps:// destinations on the public internet. 720p at 30 fps, landscape or portrait.
WebhooksA 2xx within 10 seconds, or retried for about two days. Public https URLs only.