How to use this file: Drop it into your project's
CLAUDE.md,.claude/directory, or paste it into a Claude conversation when building apps that talk to mimoLive. Claude will then know every endpoint, gotcha, and pattern needed to write correct API calls.
mimoLive is a professional live video production app for macOS. It exposes a local HTTP API (JSON:API format) and a WebSocket for real-time events. There is also an optional companion app called mlController that provides a simplified proxy API.
# Check if mimoLive is running and has open documents
curl http://localhost:8989/api/v1/documents
# Start a show
curl http://localhost:8989/api/v1/documents/{DocID}/setLive
# Toggle a layer on/off
curl http://localhost:8989/api/v1/documents/{DocID}/layers/{LayerID}/toggleLiveBase URL: http://localhost:8989/api/v1
- Format: JSON:API — responses have
data,links,relationships, and optionallyincluded - Auth: None by default. If enabled in Preferences > Remote Control, use header
X-MimoLive-Password-SHA256: <hex>or query param?pwSHA256=<hex>(SHA-256 of the UTF-8 password) - Content-Type for writes:
application/vnd.api+jsonfor PUT/PATCH requests - Network access: Accessible on
localhostand on the local network via.localhostname
| Method | Endpoint | Description |
|---|---|---|
| GET | /documents |
List all open documents |
| GET | /documents/{DocID} |
Single document (includes sideloaded layers, sources, output-destinations) |
| GET | /documents/{DocID}/programOut |
Current program output as an image |
| GET|POST | /documents/{DocID}/setLive |
Start the show |
| GET|POST | /documents/{DocID}/setOff |
Stop the show |
| GET|POST | /documents/{DocID}/toggleLive |
Toggle show on/off |
Document attributes:
{
"name": "My Show.tvshow",
"live-state": "off",
"duration": 0,
"formatted-duration": "00:00:00",
"show-start": null,
"programOutputMasterVolume": 1.0,
"metadata": {
"title": "My Show",
"comments": "...",
"author": "...",
"show": "...",
"width": 1920,
"height": 1080,
"framerate": 30,
"samplerate": 48000,
"duration": 0
},
"outputs": [
{ "id": "record", "type": "record", "live-state": "off" },
{ "id": "stream", "type": "stream", "live-state": "off" },
{ "id": "playout", "type": "playout", "live-state": "off" },
{ "id": "fullscreen", "type": "fullscreen", "live-state": "off" }
]
}Document relationships: sources, layers, output-destinations, layer-sets
| Method | Endpoint | Description |
|---|---|---|
| GET | /documents/{DocID}/layers |
List all layers in a document |
| POST | /documents/{DocID}/layers |
Create a new layer |
| GET | /documents/{DocID}/layers/{LayerID} |
Single layer |
| PUT | /documents/{DocID}/layers/{LayerID} |
Modify layer attributes (omit unchanged values) |
| DELETE | /documents/{DocID}/layers/{LayerID} |
Delete a layer (returns 204) |
| GET|POST | .../layers/{LayerID}/setLive |
Switch layer live |
| GET|POST | .../layers/{LayerID}/setOff |
Switch layer off |
| GET|POST | .../layers/{LayerID}/toggleLive |
Toggle layer on/off |
| GET|POST | .../layers/{LayerID}/signals/{SignalID} |
Trigger a signal on a layer |
| GET|POST | .../layers/{LayerID}/cycleThroughVariants |
Cycle to next variant |
| GET|POST | .../layers/{LayerID}/cycleThroughVariantsBackwards |
Cycle to previous variant |
| GET|POST | .../layers/{LayerID}/setLiveFirstVariant |
Activate first variant |
| GET|POST | .../layers/{LayerID}/setLiveLastVariant |
Activate last variant |
| GET|POST | .../layers/{LayerID}/inputs/{SourceInputKey}/mediacontrol/{Command} |
Media playback control |
Creating a layer (POST): Provide the composition-id of the layer type and optional initial attributes:
curl -X POST \
-H "Content-Type: application/vnd.api+json" \
-d '{"data": {"attributes": {"composition-id": "com.boinx.layer.lowerThird", "input-values": {"tvIn_Title": "John Doe"}}}}' \
http://localhost:8989/api/v1/documents/{DocID}/layersReturns 201 Created with the new layer representation.
Layer attributes:
{
"name": "Lower Third",
"live-state": "off",
"volume": null,
"composition-id": "com.boinx.layer.lowerThird",
"index": 5,
"input-values": { "tvIn_Title": "John Doe", "tvIn_Subtitle": "CEO" },
"input-descriptions": { "tvIn_Title": { "uiName": "Title", "type": "string", "group-name": "Content", ... } },
"output-values": { "tvOut_SettingName": "Lower Third (◹ Dissolve)", "tvOut_Opaque": false }
}Layer relationships: variants, live-variant, active-variant, document
Modifying a layer (PUT): Only include the fields you want to change:
curl -X PUT \
-H "Content-Type: application/vnd.api+json" \
-d '{"input-values": {"tvIn_Title": "Jane Smith"}}' \
http://localhost:8989/api/v1/documents/{DocID}/layers/{LayerID}| Method | Endpoint | Description |
|---|---|---|
| GET | .../layers/{LayerID}/variants |
List all variants for a layer |
| GET | .../variants/{VariantID} |
Single variant |
| PUT | .../variants/{VariantID} |
Modify variant |
| GET|POST | .../variants/{VariantID}/setLive |
Activate variant (also makes layer live) |
| GET|POST | .../variants/{VariantID}/setOff |
Deactivate variant (also turns layer off) |
| GET|POST | .../variants/{VariantID}/toggleLive |
Toggle variant |
Variant attributes: Same as layer (live-state, input-descriptions, input-values). Relationships: layer.
| Method | Endpoint | Description |
|---|---|---|
| GET | /documents/{DocID}/sources |
List all sources in a document |
| POST | /documents/{DocID}/sources |
Create a new source |
| GET | .../sources/{SourceID} |
Single source (includes sideloaded filters) |
| PUT | .../sources/{SourceID} |
Modify source attributes |
| DELETE | .../sources/{SourceID} |
Delete a source (returns 204) |
| GET | .../sources/{SourceID}/preview |
Source preview image |
| GET|POST | .../sources/{SourceID}/mediacontrol/{Command} |
Media playback control |
| GET|POST | .../sources/{SourceID}/signals/{SignalID} |
Trigger signal on source |
Common source attributes (all types):
{
"name": "Camera 1",
"source-type": "com.boinx.mimoLive.sources.deviceVideoSource",
"summary": "MacBook Air Camera",
"audio": false,
"video": true,
"gain": 1.0,
"tally-state": "off",
"is-hidden": false,
"is-static": false
}Source ID format: {DocID}-{UUID} — the document ID is prefixed to the source UUID.
Source relationships: filters, document
Known source types:
| source-type | Description | Extra attributes |
|---|---|---|
com.boinx.mimoLive.sources.deviceVideoSource |
Camera/capture device | video-device-connected |
com.boinx.mimoLive.sources.imageSource |
Static image | filepath |
com.boinx.mimoLive.sources.zoomparticipant |
Zoom meeting participant | zoom-userid, zoom-username, zoom-userselectiontype, zoom-videoresolution |
com.boinx.mimoLive.sources.socialSource |
Social/streaming source | (none extra) |
com.boinx.boinxtv.source.placeholder |
Placeholder source | composition-id, input-values, input-descriptions, output-values |
com.boinx.boinxtv.source.sports.teamdata |
Sports team data | composition-id, input-values, input-descriptions, output-values |
Tally states: off, preview, program, in-use
Creating a source (POST): Provide the source-type identifier and optional settings for the source type:
curl -X POST \
-H "Content-Type: application/vnd.api+json" \
-d '{"data": {"attributes": {"source-type": "com.boinx.boinxtv.source.qrcode", "settings": {"tvGroup_Content__QR_Content": "https://example.com"}}}}' \
http://localhost:8989/api/v1/documents/{DocID}/sourcesReturns 201 Created with the new source representation.
| Method | Endpoint | Description |
|---|---|---|
| GET | .../sources/{SourceID}/filters |
List filters on a source |
| GET | .../filters/{FilterID} |
Single filter |
| PUT | .../filters/{FilterID} |
Modify filter |
| GET|POST | .../filters/{FilterID}/signals/{SignalID} |
Trigger signal on filter |
| Method | Endpoint | Description |
|---|---|---|
| GET | /documents/{DocID}/output-destinations |
List output destinations |
| POST | /documents/{DocID}/output-destinations |
Create a new output destination |
| GET | .../output-destinations/{OutputID} |
Single output destination |
| PUT|PATCH | .../output-destinations/{OutputID} |
Modify output destination settings |
| DELETE | .../output-destinations/{OutputID} |
Delete an output destination (returns 204) |
| GET|POST | .../output-destinations/{OutputID}/setLive |
Start output |
| GET|POST | .../output-destinations/{OutputID}/setOff |
Stop output |
| GET|POST | .../output-destinations/{OutputID}/toggleLive |
Toggle output |
Output destination attributes:
{
"title": "File Recording",
"output-destination-type": "com.boinx.mimoLive.outputDestination.fileRecording",
"type": "File Recording",
"live-state": "off",
"ready-to-go-live": true,
"starts-with-show": true,
"stops-with-show": true,
"summary": "~/Movies, Program Output (H.264), Program Mix (AAC)",
"settings": {
"location": "~/Movies",
"filename": "%show %year-%month-%day %hour-%minute-%second.%extension"
}
}Known output destination types:
| output-destination-type | Description |
|---|---|
com.boinx.mimoLive.outputDestination.fileRecording |
File Recording |
com.boinx.mimoLive.outputDestination.liveStreaming |
Live Streaming (RTMP) |
com.boinx.mimoLive.outputDestination.ndi |
NDI output |
com.boinx.mimoLive.outputDestination.fullscreen |
Fullscreen output |
com.boinx.mimoLive.outputDestination.virtualCamera |
Virtual Camera |
com.boinx.mimoLive.outputDestination.mimoCall |
mimoCall (WebRTC) |
com.boinx.mimoLive.outputDestination.blackmagicDesign |
Blackmagic Design playout |
com.boinx.mimoLive.outputDestination.imageSequence |
Image Sequence |
com.boinx.mimoLive.outputDestination.audioMonitor |
Audio Monitor |
com.boinx.mimoLive.outputDestination.fileUploader |
File Uploader |
Creating an output destination (POST):
curl -X POST \
-H "Content-Type: application/vnd.api+json" \
-d '{"data": {"attributes": {"output-destination-type": "com.boinx.mimoLive.outputDestination.liveStreaming", "title": "My Stream", "settings": {"rtmpurl": "rtmp://fb.live/1234567", "streamingkey": "abcdefghijk"}}}}' \
http://localhost:8989/api/v1/documents/{DocID}/output-destinationsReturns 201 Created with the new output destination representation.
Modifying RTMP streaming settings:
curl -X PATCH \
-H "Content-Type: application/vnd.api+json" \
-d '{"data": {"attributes": {"settings": {"rtmpurl": "rtmp://stream.example.com/live", "streamingkey": "your-key"}}}}' \
http://localhost:8989/api/v1/documents/{DocID}/output-destinations/{OutputID}| Method | Endpoint | Description |
|---|---|---|
| GET | /documents/{DocID}/layer-sets |
List layer sets |
| POST | /documents/{DocID}/layer-sets |
Create a new layer set |
| GET | .../layer-sets/{LayerSetID} |
Single layer set |
| PUT|PATCH | .../layer-sets/{LayerSetID} |
Update a layer set |
| DELETE | .../layer-sets/{LayerSetID} |
Delete a layer set (returns 204) |
| GET|POST | .../layer-sets/{LayerSetID}/recall |
Recall/activate a layer set |
Layer sets allow you to set the live state of multiple layers at once.
Layer set attributes:
{
"name": "Intro Scene",
"active": false,
"recall-on-show-start": true,
"recall-on-show-end": false,
"layers": [
{ "layer-id": "{LayerID}", "action": "live", "variant": "edit-variant" },
{ "layer-id": "{LayerID}", "action": "live", "variant": "{VariantID}" },
{ "layer-id": "{LayerID}", "action": "off" },
{ "layer-id": "{LayerID}", "action": "force-off" }
]
}Layer set member actions:
| action | variant required | Meaning |
|---|---|---|
live |
yes | Switch layer live with the given variant |
off |
no | Switch layer off (allows shutdown animation) |
force-off |
no | Force layer off immediately |
variant values: "edit-variant" (the layer's current edit variant) or a specific {VariantID}.
Creating a layer set (POST):
curl -X POST \
-H "Content-Type: application/vnd.api+json" \
-d '{"data": {"attributes": {"name": "Intro Scene", "recall-on-show-start": true, "layers": [{"layer-id": "{LayerID}", "action": "live", "variant": "edit-variant"}]}}}' \
http://localhost:8989/api/v1/documents/{DocID}/layer-setsReturns 201 Created. Omitting the body creates an empty layer set with a default name.
Updating a layer set (PATCH): All attributes are optional — omit fields you don't want to change. Providing layers replaces the full member list:
curl -X PATCH \
-H "Content-Type: application/vnd.api+json" \
-d '{"data": {"attributes": {"name": "Updated Name", "layers": [{"layer-id": "{LayerID}", "action": "off"}]}}}' \
http://localhost:8989/api/v1/documents/{DocID}/layer-sets/{LayerSetID}| Method | Endpoint | Description |
|---|---|---|
| GET | /documents/{DocID}/datastores |
List data stores |
| GET | .../datastores/{DataStoreID} |
Single data store (includes rows) |
| GET | .../datastores/{DataStoreID}/rows |
List rows |
| GET|POST | .../rows/{RowID}/focus |
Focus a row |
| GET|POST | .../rows/{RowID}/unfocus |
Unfocus a row |
| GET|POST | .../rows/{RowID}/toggleFocus |
Toggle row focus |
| GET|POST | .../rows/{RowID}/signal |
Trigger signal on a row |
| Method | Endpoint | Description |
|---|---|---|
| GET | /devices |
List all available video/audio devices |
| GET | /devices/{DeviceID} |
Single device |
Device attributes:
{
"name": "MacBook Air Camera",
"audio": false,
"video": true,
"connected": true,
"device-type": "com.boinx.devicetype.avfoundation",
"tally-state": "off"
}Device types: com.boinx.devicetype.avfoundation (local), com.boinx.devicetype.ndi (NDI network)
These endpoints control Zoom meetings integrated into mimoLive via the Zoom plugin. They are separate from the document/source endpoints.
| Method | Endpoint | Description |
|---|---|---|
| GET|POST | /zoom/join |
Join a meeting |
| GET|POST | /zoom/leave |
Leave the meeting |
| GET|POST | /zoom/end |
End/terminate the meeting (host only) |
| GET|POST | /zoom/participants |
List meeting participants |
| GET | /zoom/meetingaction?command={cmd} |
Execute a meeting action |
| GET | /zoom/meetingaction?command={cmd}&userid={id} |
Execute per-user action |
Query parameters (passed as URL params, not JSON body):
meetingid=123456789 # Required
passcode=abc123 # Optional
displayname=mimoLive # Optional
zoomaccountname=MyAccount # Optional
virtualcamera=true # Optional (boolean)
webinartoken=... # Optional
Example:
curl "http://localhost:8989/api/v1/zoom/join?meetingid=123456789&displayname=mimoLive&virtualcamera=true"{
"data": [
{
"id": 16786432,
"name": "John Doe",
"userRole": "Host",
"isHost": true,
"isCoHost": false,
"isVideoOn": true,
"isAudioOn": true,
"isTalking": false,
"isRaisingHand": false
}
]
}Note: When not in a meeting, data is an empty array [].
Zoom participant video feeds appear as sources in the document (type com.boinx.mimoLive.sources.zoomparticipant). To assign a participant to a source, PATCH the source:
# Assign specific participant (selectionType is inferred as 1)
curl -X PATCH \
-H "Content-Type: application/vnd.api+json" \
-d '{"zoom-userid": 16786432}' \
http://localhost:8989/api/v1/sources/{SourceID}
# Set to automatic (next active speaker)
curl -X PATCH \
-H "Content-Type: application/vnd.api+json" \
-d '{"zoom-userselectiontype": 2}' \
http://localhost:8989/api/v1/sources/{SourceID}
# Set to screen share
curl -X PATCH \
-H "Content-Type: application/vnd.api+json" \
-d '{"zoom-userselectiontype": 6}' \
http://localhost:8989/api/v1/sources/{SourceID}Zoom selection types:
| Value | Meaning |
|---|---|
| 0 | Unassigned |
| 1 | Specific participant (set via zoom-userid) |
| 2 | Automatic (next active speaker) |
| 6 | Screen share |
Per-user (require &userid={id}):
muteVideo/unmuteVideomuteAudio/unmuteAudio
Meeting-wide (host/co-host only):
requestRecordingPermissionmuteAll/unmuteAllenableUnmuteBySelf/disableUnmuteBySelflockMeeting/unlockMeetinglowerAllHandsallowParticipantsToChat/disallowParticipantsToChatallowParticipantsToShare/disallowParticipantsToShareallowParticipantsToStartVideo/disallowParticipantsToStartVideoallowParticipantsToShareWhiteBoard/disallowParticipantsToShareWhiteBoardenableAutoAllowLocalRecordingRequest/disableAutoAllowLocalRecordingRequestallowParticipantsToRename/disallowParticipantsToRenameshowParticipantProfilePictures/hideParticipantProfilePictures
Sharing/VoIP:
shareFitWindowMode/pauseShare/resumeSharejoinVoip/leaveVoip
Example:
# Mute a specific participant's audio
curl "http://localhost:8989/api/v1/zoom/meetingaction?command=muteAudio&userid=16786432"
# Mute all participants
curl "http://localhost:8989/api/v1/zoom/meetingaction?command=muteAll"Endpoint: ws://localhost:8989/api/v1/socket
The WebSocket pushes real-time state change events so you don't need to poll.
const ws = new WebSocket("ws://localhost:8989/api/v1/socket");
// MUST send ping every 5 seconds or connection times out at 15s
const pingInterval = setInterval(() => {
ws.send(JSON.stringify({ event: "ping" }));
}, 5000);
ws.onmessage = (event) => {
const msg = JSON.parse(event.data);
if (msg.event === "ping" || msg.event === "pong") return; // ignore keepalive
// Handle state changes
console.log(msg.event, msg.type, msg);
};
ws.onclose = () => {
clearInterval(pingInterval);
// Reconnect after 2 seconds
setTimeout(connect, 2000);
};{"event": "ping"}
{"event": "pong"}
{"event": "added", "type": "documents", ...}
{"event": "removed", "type": "documents", ...}
{"event": "changed", "type": "sources", ...}
{"event": "changed", "type": "layers", ...}Event types: added, removed, changed
Resource types: documents, sources, layers, variants, output-destinations, etc.
Best practice: On any added/removed/changed event, re-fetch the relevant resource to get the full updated state.
Reduce API response size with query parameters:
# Only include specific attributes for a type
curl "http://localhost:8989/api/v1/documents?fields[documents]=name,live-state"
# Control which relationships are included
curl "http://localhost:8989/api/v1/documents?include=layers,sources"Used across documents, layers, variants, and output destinations:
| Value | Meaning |
|---|---|
off |
Not active |
live |
Currently active/on air |
preview |
In preview mode (may go live) |
- PATCH uses short source path —
PATCH /api/v1/sources/{SourceID}works, NOT necessarily nested under/documents/{DocID}/sources/... - Content-Type matters — POST/PUT/PATCH requests need
Content-Type: application/vnd.api+json - POST returns 201, DELETE returns 204 — Creating resources returns 201 Created with the new object; deleting returns 204 No Content with an empty body
- Zoom endpoints use GET —
zoom/leave,zoom/join,zoom/endall accept GET (not just POST) - Meeting actions are GET —
zoom/meetingaction?command=...is a GET request - Zoom user IDs are numeric — e.g.,
16786432, not strings - 409 when not in a meeting — ALL
meetingactioncommands return 409 Conflict when not in a meeting - No meeting settings query — There's no endpoint to read current meeting-wide settings (mute state, lock state, etc.). Actions are fire-and-forget only. Per-participant state is available via
/zoom/participants - Source IDs include DocID — Source IDs are formatted as
{DocID}-{UUID} - Username resolution is async — After assigning a Zoom participant to a source, the
zoom-usernameupdates asynchronously (~500ms). Re-fetch to see the updated name - Layer/source IDs are stable — As long as you don't delete and recreate them, IDs persist across sessions. Reordering layers/sources does not change IDs
- Right-click for API URLs — In the mimoLive UI, right-click any element and select "Copy API Endpoint" to get its exact URL
- Enable HTTP Server — The API must be enabled in mimoLive's Preferences > Remote Control
mlController is an open-source macOS menu bar app that monitors and controls mimoLive. It provides a simplified proxy API on port 8990 with optional authentication.
Optional HTTP Basic Auth or custom header:
Authorization: Basic base64(username:password)x-mlcontroller-password: <password>- If enabled, returns
401 UnauthorizedwithWWW-Authenticateheader
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/status |
Get mimoLive status (running, documents, etc.) |
| POST | /api/start |
Launch mimoLive |
| POST | /api/stop |
Quit mimoLive |
| POST | /api/restart |
Restart mimoLive |
| POST | /api/open |
Open a document (body: {"path": "/path/to/file.tvshow"}) |
| POST | /api/select |
Select mimoLive version (body: {"path": "/Applications/mimoLive.app"}) |
| GET | /api/docicon |
Get .tvshow file icon as PNG (cached 1 hour) |
Status response:
{
"running": true,
"openDocuments": [
{ "id": "1536240957", "name": "My Show", "path": "/path/to/show.tvshow" }
],
"localDocuments": ["/Users/me/Documents/show1.tvshow", "/Users/me/Documents/show2.tvshow"],
"selectedMimoLive": "mimoLive (6.15)",
"selectedMimoLivePath": "/Applications/mimoLive.app",
"availableMimoLiveApps": [
{ "name": "mimoLive (6.15)", "path": "/Applications/mimoLive.app" }
]
}mlController wraps mimoLive's Zoom endpoints into a simpler JSON interface:
| Method | Endpoint | Body | Description |
|---|---|---|---|
| GET | /api/zoom/sources |
— | List Zoom video sources with attributes |
| GET | /api/zoom/participants |
— | List current Zoom participants |
| POST | /api/zoom/assign |
{"sourceId", "selectionType", "userId"} |
Assign participant to source |
| POST | /api/zoom/join |
{"meetingId", "displayName", "passcode?", "zoomAccountName?", "virtualCamera?"} |
Join a meeting |
| POST | /api/zoom/leave |
— | Leave current meeting |
| POST | /api/zoom/request-recording |
— | Request recording permission from host |
Endpoint: ws://localhost:8990/ws
- Sends current status snapshot immediately on connect
- Pushes updated status JSON whenever state changes
- Same format as
GET /api/status - Fallback: poll
/api/statusevery 2 seconds if WebSocket unavailable
curl -s http://localhost:8989/api/v1/documents/{DocID}/layers | \
python3 -c "
import json, sys
for l in json.load(sys.stdin)['data']:
a = l['attributes']
name = a.get('output-values', {}).get('tvOut_SettingName', a.get('name', '?'))
print(f\"{l['id']}: {name} [{a['live-state']}]\")
"# First find the layer ID
LAYER_ID=$(curl -s http://localhost:8989/api/v1/documents/{DocID}/layers | \
python3 -c "
import json, sys
for l in json.load(sys.stdin)['data']:
if 'Lower Third' in l['attributes'].get('output-values', {}).get('tvOut_SettingName', ''):
print(l['id']); break
")
# Then toggle it
curl http://localhost:8989/api/v1/documents/{DocID}/layers/$LAYER_ID/toggleLive# Update the text
curl -X PUT \
-H "Content-Type: application/vnd.api+json" \
-d '{"input-values": {"tvIn_Title": "Jane Smith", "tvIn_Subtitle": "CEO"}}' \
http://localhost:8989/api/v1/documents/{DocID}/layers/{LayerID}
# Go live
curl http://localhost:8989/api/v1/documents/{DocID}/layers/{LayerID}/setLivecurl http://localhost:8989/api/v1/documents/{DocID}/layers/{LayerID}/cycleThroughVariants# Find the File Recording output destination
OUTPUT_ID=$(curl -s http://localhost:8989/api/v1/documents/{DocID}/output-destinations | \
python3 -c "
import json, sys
for o in json.load(sys.stdin)['data']:
if o['attributes']['type'] == 'File Recording':
print(o['id']); break
")
# Start it
curl http://localhost:8989/api/v1/documents/{DocID}/output-destinations/$OUTPUT_ID/setLive# Configure stream URL and key
curl -X PUT \
-H "Content-Type: application/vnd.api+json" \
-d '{"data": {"attributes": {"settings": {"rtmpurl": "rtmp://stream.example.com/live", "streamingkey": "your-key"}}}}' \
http://localhost:8989/api/v1/documents/{DocID}/output-destinations/{OutputID}
# Start streaming
curl http://localhost:8989/api/v1/documents/{DocID}/output-destinations/{OutputID}/setLiveconst WebSocket = require("ws");
function connect() {
const ws = new WebSocket("ws://localhost:8989/api/v1/socket");
const ping = setInterval(() => ws.send(JSON.stringify({ event: "ping" })), 5000);
ws.on("message", (data) => {
const msg = JSON.parse(data);
if (msg.event === "ping" || msg.event === "pong") return;
console.log(`[${msg.event}] ${msg.type || "unknown"}`, msg);
});
ws.on("close", () => {
clearInterval(ping);
setTimeout(connect, 2000);
});
ws.on("error", (err) => {
console.error("WebSocket error:", err.message);
ws.close();
});
}
connect();# Join meeting
curl "http://localhost:8989/api/v1/zoom/join?meetingid=123456789&displayname=mimoLive&virtualcamera=true"
# Wait a moment for participants to appear, then list them
sleep 3
curl http://localhost:8989/api/v1/zoom/participants
# Find Zoom sources in the document
curl http://localhost:8989/api/v1/documents/{DocID}/sources | \
python3 -c "
import json, sys
for s in json.load(sys.stdin)['data']:
if s['attributes'].get('source-type') == 'com.boinx.mimoLive.sources.zoomparticipant':
a = s['attributes']
print(f\"{s['id']}: {a['name']} -> {a.get('zoom-username', 'unassigned')} (type={a.get('zoom-userselectiontype', '?')})\")
"
# Assign participant 16786432 to a Zoom source
curl -X PATCH \
-H "Content-Type: application/vnd.api+json" \
-d '{"zoom-userid": 16786432}' \
http://localhost:8989/api/v1/sources/{SourceID}