Advanced
API & Stream Deck
A plain-English, step-by-step guide to the control API: keys, every action, set_values, scoreboard actions, errors and Companion setup.
Anything that can send an HTTP request — an Elgato Stream Deck, Bitfocus Companion, an ATEM macro, a Python script, even a browser bookmark — can cue, take, clear and update your graphics. This page assumes you have never used an API before, so it starts from zero.
What an "API call" actually is
A request is four things: a method (almost always POST here), a URL (which channel you are talking to), headers (your key, so we know it is you), and a body (a small piece of JSON that says what to do). Press a button, a request goes out, the graphic moves. That is the whole idea.
POST https://gfx.miri.tech/api/public/control/channel/<channel-id>/command
Header Authorization: Bearer mgfx_live_xxxxxxxx
Header Content-Type: application/json
Body {"action":"take_next"}JSON is just {"key":"value"}. Text goes in double quotes, numbers do not, and every pair is separated by a comma. A missing comma or a curly quote is the single most common reason a button "does nothing".
1. Create your key
- 1Go to Settings → API & Stream Deck → Create key.
- 2Name it after the machine that will use it ("Booth Stream Deck"), and scope it to one channel if that controller only ever runs one show.
- 3Copy the key immediately. It looks like
mgfx_live_9f2c…and is shown only once — we store a hash, not the key, so it cannot be recovered. - 4Paste it into your controller as the header
Authorization: Bearer mgfx_live_…. If your tool cannot set that header, it can sendx-api-key: mgfx_live_…instead.
2. Find your channel ID
Every URL below needs the channel's ID. Ask the API for it rather than hunting through the app — this one request is safe to run any time:
curl -H "Authorization: Bearer $KEY" \
https://gfx.miri.tech/api/public/control/channels
{"channels":[{"id":"3f8c…","name":"Main","width":1920,"height":1080}]}Then list the rundown, which gives you the item IDs, names and 1-based positions you will use in buttons:
curl -H "Authorization: Bearer $KEY" \
https://gfx.miri.tech/api/public/control/channel/$CH/rundown
{"playlist_id":"…","items":[
{"position":1,"item_id":"a1…","name":"Lower Third — Host"},
{"position":2,"item_id":"b2…","name":"Score Bug"}
]}If a channel has more than one rundown, add "playlist_id" to any command to target a different one. Otherwise the first rundown is used.
3. The commands, one by one
All of these are a POST to /api/public/control/channel/<channel-id>/command with a JSON body.
| Action | What it does | Body |
|---|---|---|
overlay.set | Puts a graphic on overlay layer 1–4 (default 1). Replaces what was on that layer with a fade. | {"action":"overlay.set","layer":2,"name":"Lower Third"} |
overlay.toggle | Same as clicking a 1–4 button: on if off, off if already on that layer. | {"action":"overlay.toggle","layer":4,"position":1} |
overlay.clear | Takes whatever is on one layer off air. | {"action":"overlay.clear","layer":2} |
overlay.next | Moves a layer to the next rundown item (overlay.prev goes back). | {"action":"overlay.next","layer":2} |
program.clear | Takes every layer off air. | {"action":"program.clear"} |
preview.toggle | Adds/removes a graphic from Preview. | {"action":"preview.toggle","position":3} |
preview.clear | Empties Preview. | {"action":"preview.clear"} |
cue_next | Moves the cue one item down the rundown. | {"action":"cue_next"} |
cue_previous | Moves the cue one item up. | {"action":"cue_previous"} |
cue | Cues one specific graphic. Pick it by item_id, position (1 = first) or exact name. | {"action":"cue","position":3} |
take (alias cue_in) | Takes whatever is On Cue to air with the 500 ms fade. | {"action":"take"} |
take_next | Cues the next item and takes it — one press. | {"action":"take_next"} |
take_previous | Cues the previous item and takes it. | {"action":"take_previous"} |
take_item | Takes a named graphic straight to air, wherever the cue was. | {"action":"take_item","name":"Score Bug"} |
clear_preview | Empties On Cue. Nothing on air changes. | {"action":"clear_preview"} |
clear_program (alias off) | Fades the live graphic off air over 500 ms. | {"action":"clear_program"} |
set_values | Changes text/data fields — see below. | {"action":"set_values","values":{…}} |
scoreboard | Runs scoreboard actions — see below. | {"action":"scoreboard","actions":[…]} |
"layer", and "cue" loads Preview. The state response now also includes a layers object with on_air, item_name and position for layers 1–4 — handy for lighting a button per layer.Three ways to point at a graphic, usable on cue, take_item and the overlay actions: "item_id" (exact, never ambiguous — best for saved buttons), "position" (1-based, matches the rundown order, but shifts if someone reorders the show), and "name" (case-insensitive but must match the whole name exactly).
4. set_values — changing text live
Every template has named fields — the same names you see in the Live Data panel of the Control Room. A lower third might have title and subtitle; a countdown might have finishedText. set_values writes new text into those fields.
{
"action": "set_values",
"target": "cue",
"values": { "title": "Ryan Brenneman", "subtitle": "Miri Technologies" }
}target: "cue"(the default) edits the graphic sitting On Cue, so you can prepare text before taking it.target: "air"edits the graphic that is already live. The change appears immediately on the output with no re-take and no fade.- Values merge. Sending only
subtitleleavestitlealone. To blank a field, send it as an empty string:"subtitle": "". - Every value must be text in quotes. A score of 7 is
"7", not7.
Don't know the field names? Cue the graphic once, then read the state endpoint — the preview.values object lists every field name and its current text. Those are exactly the keys set_values expects.
curl -H "Authorization: Bearer $KEY" \
https://gfx.miri.tech/api/public/control/channel/$CH/state
{"ok":true,"on_air":true,"revision":412,
"program":{"item_id":"a1…","item_name":"Lower Third — Host",
"values":{"title":"Ryan Brenneman","subtitle":"Miri Technologies"}},
"preview":{"item_id":"b2…","item_name":"Score Bug","position":2,"values":{"home":"3","away":"1"}}}A typical "insert a guest name" button is therefore two presses, or two actions on one press: cue the lower third, set_values the name, then take.
5. scoreboard — running the game
If the channel has a scoreboard, one command can run several changes at once. actions is a list, applied in order, so "add a run and stop the clock" is a single button.
{
"action": "scoreboard",
"actions": [
{ "type": "score", "team": "home", "delta": 1 },
{ "type": "clock", "op": "stop" }
]
}team is always "home" or "away". delta is a change (use -1 to undo a mis-press), while value sets an exact number. Clock times are in milliseconds — 12 minutes is 720000.
| Action type | Meaning | Example |
|---|---|---|
score | Add to or subtract from a score. | {"type":"score","team":"away","delta":2} |
set-score | Set a score exactly. | {"type":"set-score","team":"home","value":14} |
period | Next/previous period, quarter or inning. Resets the count or clock. | {"type":"period","delta":1} |
set-period | Jump to a period. | {"type":"set-period","value":3} |
half | Baseball: top or bottom of the inning. | {"type":"half","value":"bottom"} |
clock | Game clock. op is start, stop, toggle, reset, set (needs ms) or adjust (adds ms, may be negative). | {"type":"clock","op":"set","ms":720000} |
shot-clock | Shot clock. Same op list minus adjust. | {"type":"shot-clock","op":"reset"} |
count | Baseball count. field is balls, strikes or outs. | {"type":"count","field":"strikes","delta":1} |
clear-count | Balls, strikes and outs back to zero. | {"type":"clear-count"} |
base | Toggle a runner. index 0 = first, 1 = second, 2 = third. | {"type":"base","index":1} |
clear-bases | Empty the diamond. | {"type":"clear-bases"} |
timeout | Timeouts left for a team. | {"type":"timeout","team":"home","delta":-1} |
foul | Team fouls. | {"type":"foul","team":"away","delta":1} |
shots | Shots on goal. | {"type":"shots","team":"home","delta":1} |
sets | Sets won (volleyball). | {"type":"sets","team":"away","delta":1} |
down | Football down. | {"type":"down","delta":1} |
distance | Yards to go. | {"type":"distance","value":7} |
possession | Possession arrow. null clears it. | {"type":"possession","team":"home"} |
status | Free text banner (HALFTIME, FINAL). | {"type":"status","value":"FINAL"} |
reset | Back to a fresh game. | {"type":"reset"} |
6. What comes back
Every successful command replies with the resulting state — the same shape as the state endpoint, plus "ok": true and the action you sent. That is what makes button feedback possible: poll /state once a second and read on_air.
| Code | What went wrong | Usual fix |
|---|---|---|
| 400 | Unknown action, or the body wasn't valid JSON. | Check spelling and quotes. |
| 401 | Key missing, mistyped or revoked. | Re-check the Authorization header; create a new key. |
| 403 | The key is scoped to a different channel. | Use that channel's ID, or an org-wide key. |
| 404 | Channel, rundown, item or scoreboard not found. | Re-list the rundown; positions shift. |
| 409 | Nothing cued, empty rundown, end of list, feed-driven board. | Cue something first. |
| 429 | More than 600 requests a minute from one key. | Slow your polling down. |
| 500 | Something failed on our side. | Retry; the state endpoint shows the truth. |
7. Bitfocus Companion, step by step
- 1Connections → add Generic: HTTP Requests.
- 2Make a button → Press actions → POST.
- 3URL:
https://gfx.miri.tech/api/public/control/channel/<channel-id>/command - 4Body type JSON, body
{"action":"take_next"}. - 5Custom header:
Authorization=Bearer mgfx_live_… - 6Duplicate the button and change only the body for cue_next, take, clear_program, and so on.
- 7For tally, add a second HTTP instance polling
/stateevery second into a custom variable, then add a Boolean feedback onon_airto turn the TAKE button red while a graphic is live.
On a Stream Deck without Companion, use any HTTP-request plugin with the same method, URL, headers and body. A practical layout:
[ CUE PREV ] [ CUE NEXT ] [ TAKE ] [ CLEAR PGM ]
[ LT HOST ] [ SCORE BUG ] [ TICKER ] [ COUNTDOWN ]The bottom row each posts {"action":"cue","name":"…"}, so an operator can run the show without touching the browser.
8. Troubleshooting
- Nothing happens, no error — your tool probably sent the body as plain text. Set the content type to JSON.
- 401 with a key you just made — a trailing space, or the word
Beareris missing before the key. - Wrong graphic fires — you used
positionand the rundown was reordered. Switch those buttons toitem_id. - set_values does nothing visible — you targeted
cuewhile the graphic is on air. Use"target":"air". - Text appears but looks wrong — the field name is misspelled; unknown names are stored and ignored. Confirm names from
preview.values.
The same reference in copy-paste form lives in docs/control-api.md in your project.