The control API: examples
JavaScript (Node 22 or later)
Connect on the Mac running InterDirector, follow the state, and cut to whatever is in Preview:
const socket = new WebSocket('ws://127.0.0.1:49721');
socket.addEventListener('open', () => {
socket.send(JSON.stringify({ type: 'hello', client: 'My Panel', protocol: 2 }));
});
socket.addEventListener('message', (event) => {
const message = JSON.parse(event.data);
if (message.type === 'state') {
const name = (id) => message.inputs.find((input) => input.id === id)?.name ?? 'nothing';
console.log(`Program: ${name(message.program)}, Preview: ${name(message.preview)}`);
} else if (message.type === 'done') {
console.log(`Done: ${message.requestID}`);
} else if (message.type === 'refused') {
console.log(`Refused (${message.reason}): ${message.requestID ?? ''}`);
}
});
// Cut whatever is in Preview to Program, five seconds in.
setTimeout(() => {
socket.send(JSON.stringify({ type: 'action', verb: 'cutPreview', requestID: 'take-1' }));
}, 5000);
// Keep the connection alive.
setInterval(() => socket.send(JSON.stringify({ type: 'ping' })), 10000);
Python (no packages needed)
Read the state and mute an input by name, over HTTP:
import json
import urllib.request
BASE = 'http://127.0.0.1:49721'
def call(path, body=None):
request = urllib.request.Request(
BASE + path,
data=None if body is None else json.dumps(body).encode(),
headers={'X-Client': 'My Script', 'Content-Type': 'application/json'},
method='GET' if body is None else 'POST')
try:
with urllib.request.urlopen(request, timeout=5) as response:
return json.load(response)
except urllib.error.HTTPError as error:
return json.load(error)
state = call('/v2/state')
print('Show:', state['show'])
microphone = next(i for i in state['inputs'] if i['name'] == 'Microphone')
print(call('/v2/action', {'verb': 'mute', 'inputID': microphone['id'], 'requestID': 'mute-1'}))
curl
# The state
curl http://127.0.0.1:49721/v2/state
# Start a fade of Preview to Program over two seconds
curl -X POST http://127.0.0.1:49721/v2/action -H 'X-Client: curl' \
-d '{"verb": "fadePreview", "parameters": {"milliseconds": 2000}}'
# Press a hotkey, from another computer, with the web remote's password
curl -k -X POST https://studio-mac.local:49722/v2/press \
-H 'Authorization: Bearer <the web remote password>' \
-d '{"mappingID": "8A1B2C3D-4E5F-4061-9273-8495A6B7C8DA"}'
From another computer, over a WebSocket
Sign in before anything else. InterDirector sends a challenge; answer it with a proof made from the password, then say
hello as above. In Node, with the ws package, since the Mac’s certificate is
its own and Node’s built-in WebSocket will not accept it:
import { createHmac, pbkdf2Sync } from 'node:crypto';
import WebSocket from 'ws';
const socket = new WebSocket('wss://studio-mac.local:49722/socket', { rejectUnauthorized: false });
const password = '<the web remote password>';
socket.addEventListener('message', (event) => {
const message = JSON.parse(event.data);
if (message.type === 'challenge') {
const key = pbkdf2Sync(password, Buffer.from(message.salt, 'base64'), message.iterations, 32, 'sha256');
const proof = createHmac('sha256', key)
.update(Buffer.concat([Buffer.from('interdirector-web-remote-sign-in-v1\n'), Buffer.from(message.nonce, 'base64')]))
.digest('base64');
socket.send(JSON.stringify({ type: 'signIn', proof }));
} else if (message.type === 'signedIn') {
socket.send(JSON.stringify({ type: 'hello', client: 'My Panel', protocol: 2 }));
}
});
Keep the session from signedIn to resume with {"type": "resume", "session": …} after a dropped connection,
within ten minutes.