New: Control Quadraviz From a Stream Deck, a Script, or Anything Else
A public HTTP API and signed webhooks. Play a scene from a hardware panel, set a score from a script, and have your own systems find out the moment something changes on air.
Team Quadraviz
August 23, 2026 · 6 min read
The browser controller is good at what it's for: one operator, one screen, full view of the show. But it's a browser tab, and some productions don't want a browser tab in the loop.
They want a physical button. Or a scoreboard that updates itself from the timing system nobody wants to retype from. Or a Companion module so the graphics live on the same panel as the camera cuts.
All of that is now possible. Quadraviz has a public API, and it can call you back.
The API
Base URL https://api.quadraviz.com/v1, authenticated with a bearer key. Read what's on air, and drive it.
# Take what's cued in preview to air
curl -X POST https://api.quadraviz.com/v1/channels/$CHANNEL/play \
-H "Authorization: Bearer $KEY"
# Set a value on a live graphic
curl -X POST https://api.quadraviz.com/v1/channels/$CHANNEL/bind \
-H "Authorization: Bearer $KEY" \
-H "Content-Type: application/json" \
-d '{"bind": "lowerThird/name", "text": "Jordan Vale", "target": "live"}'
Ten endpoints: list channels, scenes and animations; read exactly what's on air including resolved Smart Input values; play, load a scene, fire a trigger, set a value.
Two details worth knowing, because they're the ones that make integrations pleasant:
GET /channels reports viewers — how many outputs are actually connected. It's the quickest possible answer to is anything showing this?, and it makes a very good tally light.
GET /channels/:id/state resolves values the same way an overlay does. Not the raw stored data — the same values the graphic itself is displaying. That's what makes feedbacks honest: a button that lights when a lower third is up is reading what the lower third is reading.
Keys that can't do more than they should
Every key carries a role, a set of permissions, and optionally a channel lock.
That last one is the one to use. A key locked to one channel can only see and drive that channel. It can't list your other channels, read their state, or play anything on them.
Which matters, because API keys end up in places browser sessions never do — a config file on a machine in a venue you don't control, a stream deck at a client's studio, a Raspberry Pi taped behind a monitor. If that hardware walks out of the building, a locked key means the blast radius is one channel instead of your whole production.
Keys are shown once. We store a hash, not the key, so a lost key gets replaced rather than recovered. They also start with qv_ — deliberately, because secret-scanning tools recognise prefixes like that, and a key accidentally committed to a repository gets flagged instead of sitting there for a year.
They belong to the organisation, not to whoever made them. A panel bolted into a rack keeps working after that person has moved on.
Webhooks: the other direction
Polling works. For a control surface refreshing its own buttons, it's usually fine.
Webhooks are for when something outside Quadraviz needs to react: logging every graphic that went on air, driving a tally system, posting to a production channel when the scoreboard changes.
Add an https endpoint in settings and we POST to it when a scene goes live, a trigger fires, or a value changes — whether the change came from the controller or the API, so your integration sees what a human operator did as well as what a script did.
{
"id": "7c2f…",
"type": "channel.played",
"orgId": "org_7f3a…",
"channelId": "chan_91b2…",
"createdAt": "2026-08-23T18:04:11Z",
"data": { "sceneId": "scn_88fa…" }
}
Every delivery is signed. Your endpoint is a public URL — anyone who finds it can POST to it — so the signature is what separates a real delivery from someone pretending. The docs have verification code you can copy, and the three things people get wrong: verify before parsing, check the timestamp, compare in constant time.
We retry three times, seconds apart, then move on. That's short on purpose. These are show events. By the time a long backoff delivers "the scoreboard went live", the scoreboard has been live for ten minutes and gone again. A stale event is worse than a missing one.
After 20 consecutive failures we switch the endpoint off and show you the last error. One success resets the count, so an endpoint that blips and recovers is never at risk.
One thing we won't let you do
Your webhook URL has to be https and resolve to a public address. We refuse loopback, private ranges, .local names, and cloud metadata addresses — and we check again when we deliver, not only when you save it.
That's worth explaining rather than just enforcing. A webhook URL is something we connect to on your behalf. Without those checks, the webhook form would be a way to make our servers reach into networks they have no business reaching — including our own. It's a well-known class of bug, and it's much easier to not have than to clean up after.
If you're testing locally, use a tunnel that gives you a public https URL.
Where to start
- Create a key — Settings → API & webhooks. Lock it to a channel.
- Call
GET /whoami— it tells you what the key can do. Make it the first call in any integration, and a misconfigured panel says this key can't control on startup instead of failing on a button press mid-show. - Read the API docs — every endpoint, every error code.
If you're building a Companion module or something similar, we'd like to hear about it. Tell us what's missing and it goes on the list.