Getting started

What is Rundown Studio?Create an account

Rundown

Rundown basicsColumnsTemplatesSettingsTrashMentionsText variablesRunning a showImport CSV rundownAPICompanion Module

Event

Event basicsSharing events

Sharing and outputs

Read-only rundownEditable rundownOutputPrompterPDF exportCSV export

Account

Your teamSubscription and invoices

Updates

Changelog
Docs Rundown

API

API

With our v0 API, we have exposed some super handy rundown-specific API calls in order to run your show from your favorite third-party client.

Contents

  1. Getting started
  2. HTTP API
  3. Websocket API
  4. Using with Companion

Getting started

The API uses HTTP GET requests in other to start, pause or advance a rundown. These can be used with tools like Bitfocus’ Companion - where you can use buttons on a Stream Deck to take control of your rundown and other pieces of equipment during a video production.

The API also requires an API token as a security step to ensure only you - or your team mates - have control of any given rundown.

Get the API token

API tokens can be generated from within the Rundown Studio dashboard.

Only team admins can generate and regenerate these tokens, however anyone on your team can read and use the token.

Generating the token can be done in the API section of the dashboard.

Generating an API token in the dashboard
Generating an API token in the dashboard

Copy this API token to your clipboard as we will need it a little later.

URL breakdown

An example URL for interacting with our API is as follows…

GET
https://app.rundownstudio.app/api-v0/rundown/<rundown_id>/<start|pause|next>?token=ABCD1234
  • app.rundownstudio.app/api-v0/rundown/ ← This is the base URL
  • rundown_id ← This is the rundown that you are trying to control
  • start|pause|next ← This is the action you want to take
  • ?token=ABCD1234 ← This is the token that you have copy/pasted from the dashboard

HTTP API

A detailed breakdown of the available HTTP API endpoints can be found on the swagger documentation page:

Swagger Logo https://app.rundownstudio.app/api-v0/docs/

Rate limits: the API is limited to 60 requests per minute, scoped per team (all rundowns and API tokens belonging to the same team share this budget). Limits are tracked in a sliding window. Every response includes standard RateLimit-Limit, RateLimit-Remaining, and RateLimit-Reset headers so your client can pace itself. Requests over the limit are rejected with HTTP 429 Too Many Requests; the headers are included on 429 responses too. Prefer the websocket over polling for real-time updates.

Websocket API

The API also provides a websocket endpoint to receive push notifications from our server. It uses the socket.io library which is available in various programming languages:

The socket.io endpoint receives real-time push messages from the server whenever there is a timing-related change in the rundown. Note that the socket.io endpoint does not listen for any incoming messages.

No initial state on connect: the server only emits events when something changes. A freshly connected client will not receive the current rundown, currentCue, nextCue, or timesnap until the next change occurs. To populate your initial UI, fetch the current state once via the HTTP API on startup, then rely on the websocket for live updates. (The serverTime event is the exception — it broadcasts on a 30s interval, so you will receive one within 30 seconds of connecting.)

Enter your API credentials and try it here:

Rundown ID:
API Token:
# Click connect ...

Events

The server will send the following events:

serverTime – The server time sent on a 30s interval, used to sync clocks
"2024-01-09T11:45:20.035Z"
rundown – When the rundown itself changes
{
  "id": "4LdiPByHcbVZHxjovBQB",
  "name": "Example Rundown",
  "startTime": "2023-11-23T09:45:00.000Z",
  "endTime": "2023-04-01T09:45:00.000Z",
  "status": "approved",
  "createdAt": "2023-10-23T18:51:22.653Z",
  "updatedAt": "2024-01-06T12:55:26.021Z"
}
currentCue – When the current cue changes
{
  "id": "ArWdCNok6nS9DjhrQaoD",
  "type": "cue",
  "title": "Welcome",
  "subtitle": "",
  "duration": 180000,
  "backgroundColor": "#450a0a",
  "locked": false,
  "createdAt": "2023-10-23T18:51:23.326Z",
  "updatedAt": "2023-10-23T18:51:23.326Z"
}
nextCue – When the next cue changes
{
  "id": "dkSnrutcDgp5Tq5WGz6P",
  "type": "cue",
  "title": "Keynote",
  "subtitle": "",
  "duration": 300000,
  "backgroundColor": "#450a0a",
  "locked": false,
  "createdAt": "2023-10-23T18:51:23.326Z",
  "updatedAt": "2023-10-23T18:51:23.326Z"
}
timesnap – For all timing-related changes like start, stop, next, and previous
{
  "lastStop": 1704546261727,
  "kickoff": 1704546261727,
  "running": true,
  "cueId": "EF3Rgj4A0IwXV42VP0Ed",
  "deadline": 1704547171727
}

Note: The timesnap event contains timestamps in milliseconds since UNIX epoch. The total duration of the active cue is deadline - kickoff. The remaining countdown is computed locally — the server does not push a ticking value.

Computing a countdown

The timesnap event is emitted only on state changes (start, pause, next, previous), not on every tick. Your code computes the remaining time locally from the timestamps. When running is true, the countdown ticks against wall-clock time. When paused, it freezes at the moment of lastStop.

JavaScript — derive remaining time from a timesnap event
// Returns remaining milliseconds for the active cue
function getRemainingMs (timesnap) {
  if (!timesnap || !timesnap.deadline) return 0
  if (timesnap.running) return timesnap.deadline - Date.now()
  return timesnap.deadline - timesnap.lastStop // paused: freeze at the stop point
}

// Total duration of the active cue
const totalMs = timesnap.deadline - timesnap.kickoff

// Update your display in a loop
setInterval(() => {
  const remaining = getRemainingMs(timesnap)
  console.log(`${Math.ceil(remaining / 1000)}s remaining`)
}, 100)

Code Examples

JavaScript (socket.io-client)
import { io } from "socket.io-client";

const socket = io('https://socket.rundownstudio.app', {
  path: '/api-v0/socket.io',
  auth: { rundownId: '[YOUR_RUNDOWN_ID]', apiToken: '[YOUR_API_TOKEN]' },
});

socket.on('connect', () => { console.info('Connection Success') });
socket.on('connect_error', (error) => { console.error('Connection Error:', error); });
socket.onAny((event, payload) => { console.info(event, payload) });
Python (python-socketio)
import socketio

sio = socketio.Client()

@sio.event
def connect():
    print('Connected to Socket.io')

@sio.on('*')
def catch_all(event, data):
    print('Message received', event, data)

auth = { 'rundownId': '[YOUR_RUNDOWN_ID]', 'apiToken': '[YOUR_API_TOKEN]' }
sio.connect('https://socket.rundownstudio.app', {}, auth, None, None, 'api-v0/socket.io')