Worked Example (Node.js)
A complete, runnable integration: list actions, stream live mixer state, and mute a channel by name. This is the same shape a Stream Deck plugin or Bitfocus Companion module would use — both run as ordinary local Node.js processes, which is exactly what this example is.
Setup
mkdir aura-open-api-demo && cd aura-open-api-demo
npm init -y
npm install @grpc/grpc-js @grpc/proto-loader
mkdir -p proto/google/protobuf
Copy mixer.proto and actions.proto (see Overview & Setup) into proto/. They both import "google/protobuf/empty.proto" — most protobuf toolchains resolve this automatically, but @grpc/proto-loader doesn't bundle it, so create proto/google/protobuf/empty.proto yourself:
syntax = "proto3";
package google.protobuf;
message Empty {}
Your proto/ folder should now have mixer.proto, actions.proto, and google/protobuf/empty.proto.
index.js
const path = require('path');
const grpc = require('@grpc/grpc-js');
const protoLoader = require('@grpc/proto-loader');
const PROTO_DIR = path.join(__dirname, 'proto');
const packageDefinition = protoLoader.loadSync(
path.join(PROTO_DIR, 'actions.proto'),
{
keepCase: true, // keep snake_case field names (action_id, target_id, ...) as declared
longs: String,
enums: String,
defaults: true,
oneofs: true,
includeDirs: [PROTO_DIR],
}
);
const proto = grpc.loadPackageDefinition(packageDefinition).auramixer.actions;
const PORT = process.argv[2] || '51477';
const TOKEN = process.argv[3];
if (!TOKEN) {
console.error('usage: node index.js <port> <token>');
process.exit(1);
}
// grpc.credentials.createInsecure() talks plain HTTP/2 (h2c) by default in @grpc/grpc-js —
// no extra flag needed here (the .NET client in this repo's own test tooling needs an explicit
// AppContext switch for the same thing; Node doesn't).
const client = new proto.ActionsService(`127.0.0.1:${PORT}`, grpc.credentials.createInsecure());
function authMetadata() {
const md = new grpc.Metadata();
md.set('authorization', `Bearer ${TOKEN}`);
return md;
}
// 1. List every action once at startup — build your picker from this, don't hardcode it.
client.ListActions({}, authMetadata(), (err, response) => {
if (err) return console.error('ListActions failed:', err.message);
console.log(`${response.actions.length} actions available:`);
for (const a of response.actions) {
console.log(` ${a.id.padEnd(28)} ${a.display_name}`);
}
});
// 2. Stream live mixer state — this is how you resolve a name to a target_id, and how you'd
// keep a button icon's mute/solo/fader indicator current.
let channels = [];
const stateStream = client.StreamState({}, authMetadata());
stateStream.on('data', (snapshot) => {
channels = snapshot.entities;
});
stateStream.on('error', (err) => console.error('StreamState error:', err.message));
// 3. Invoke an action once we've resolved a real target_id from the state stream.
function muteByName(name) {
const target = channels.find((e) => e.name === name);
if (!target) {
console.error(`"${name}" not found — is StreamState connected yet, and does that name exist?`);
return;
}
client.InvokeAction(
{ action_id: 'mixer.setMuted', target_id: target.id },
authMetadata(),
(err, response) => {
if (err) return console.error('InvokeAction transport error:', err.message);
// InvokeAction's real success signal is response.ok, not the absence of a gRPC error —
// see the Actions API reference.
if (!response.ok) return console.error('InvokeAction failed:', response.error);
console.log(`Muted "${name}".`);
}
);
}
// Give the first StreamState snapshot a moment to arrive, then fire once as a demo.
// Replace "Mic Ext" with a channel name that actually exists in your project.
setTimeout(() => muteByName('Mic Ext'), 1000);
Running it
- In Aura Mixer: Settings → Integrations, enable Open API, copy the port and token.
node index.js <port> <token>
You should see the action catalog printed, and — if a channel named Mic Ext exists in the project — it mutes, visible live in the app.
Where to go from here
- Swap the hardcoded
muteByName('Mic Ext')for whatever your integration actually triggers on (a Stream Deck button press, a Companion action, a hotkey from your own tool). - Use the
StreamStatedata to drive a button icon's visual state (mute/solo indicator) instead of just logging it. - If the Actions API doesn't cover what you need, the same connection/auth pattern here works identically against any service in the Mixer API — just load
mixer.protoinstead of (or alongside)actions.proto, and construct the relevant*ServiceClient(e.g.proto.auramixer.ChannelsService) the same way.