Getting started
What is Rundown Studio?Create an accountRundown
Rundown basicsColumnsTemplatesSettingsTrashCell historyMentionsText variablesRunning a showImport CSV rundownEvent
Event basicsSharing eventsAPI
Getting startedAPI reference ↗Build a rundown from CSVWorking with cell contentLive updates over SSEAdvanced usageError referenceMigrating from v0API v0 (deprecated)Integrations
Companion ModuleQLabSharing and outputs
Read-only rundownEditable rundownOutputPrompterPDF exportCSV exportAccount
Your teamSubscription and invoicesUpdates
ChangelogBuild a rundown from CSV
A worked example — turn a spreadsheet into a fully populated rundown with one API call.
A very common integration is “I have my show in a spreadsheet, get it into Rundown Studio.” The API doesn’t parse CSV for you — instead, POST /rundowns accepts one JSON document describing the whole rundown: cues, columns, and cell contents, all nested. You (or your script, or your AI agent) transform the spreadsheet into that JSON shape and send it in a single request.
This page walks through the whole flow with a real example.
How it worked in v0
The v0 API had no import surface at all — it was a control-and-read API. CSV imports happened in the dashboard UI, or people scripted them against endpoints that were never meant for it. If that’s you, this page is your migration path; see also Migrating from v0.
The v1 shape
POST /rundowns is one endpoint with two modes: send { "title": "..." } and you get an empty rundown; send a fully nested document and you get a populated one. The same body shape, just more or less of it.
The mapping from a spreadsheet is:
- Spreadsheet columns that are cue properties (title, duration) map to fields on each cue.
- The other spreadsheet columns become entries in
columns— each cue then carries acellsmap keyed by column name (IDs don’t exist yet at import time). - Section-header rows become cues of
type: "heading"— with nocells.
Worked example
Here’s a small show CSV — three data rows, one section header, four columns:
Title,Duration,AUDIO,NOTES
ACT 1,,,
Opening monologue,0:01:00,track-01,Host walks on stage
Guest interview,0:08:00,lav-2,Confirm name pronunciation
Outro,0:00:30,track-02,
Transformed into the POST /rundowns body:
{
"title": "Brave Show — June 14",
"timezone": "America/New_York",
"start_time": 1750024800000,
"columns": [
{ "name": "AUDIO" },
{ "name": "NOTES" }
],
"cues": [
{ "type": "heading", "title": "ACT 1" },
{
"type": "cue",
"title": "Opening monologue",
"duration_ms": 60000,
"cells": {
"AUDIO": "track-01",
"NOTES": "Host walks on stage"
}
},
{
"type": "cue",
"title": "Guest interview",
"duration_ms": 480000,
"cells": {
"AUDIO": "lav-2",
"NOTES": "Confirm name pronunciation"
}
},
{
"type": "cue",
"title": "Outro",
"duration_ms": 30000,
"cells": {
"AUDIO": "track-02"
}
}
]
}
Things to notice:
TitleandDurationbecame cue fields (title,duration_msin milliseconds), not columns.- The
ACT 1row became aheading— noduration_ms, nocells. - In
cells, values are plain strings keyed by column name. Empty cells are simply omitted (theOutrocue has noNOTESentry).
Send it:
curl -X POST \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 1f8e4a2c-7b3d-4e9f-a1c5-2d6b8e0f4a7c" \
-d @rundown.json \
https://api-v1.rundownstudio.app/rundowns
The response
You get 201 Created with the full rundown echoed back — every cue, column, and cell now carries its server-assigned ID, ready for follow-up edits:
{
"ok": true,
"message": "Rundown created",
"data": {
"id": "YOmm4UG2SGSomuOci44c",
"title": "Brave Show — June 14",
"timezone": "America/New_York",
"start_time": 1750024800000,
"status": "draft",
"cue_order": [
{ "id": "Ii6WeSEQmEqyKY6YAu8c" },
{ "id": "G0M48GugUIem6YOSIOyO" },
{ "id": "UEieqCc0q4wKsME0QSSm" },
{ "id": "Ec0mmeOaGq68oiASGoSG" }
],
"column_order": ["EoeiemGEmeMOY8GwUK6s", "Sw0kqCqSuM8gKeICwsIa"],
"created_at": "2026-06-14T10:00:00.000Z",
"updated_at": "2026-06-14T10:00:00.000Z"
},
"included": {
"cues": [
{ "id": "Ii6WeSEQmEqyKY6YAu8c", "type": "heading", "title": "ACT 1" },
{ "id": "G0M48GugUIem6YOSIOyO", "type": "cue", "title": "Opening monologue", "duration_ms": 60000 }
],
"columns": [
{ "id": "EoeiemGEmeMOY8GwUK6s", "name": "AUDIO" },
{ "id": "Sw0kqCqSuM8gKeICwsIa", "name": "NOTES" }
],
"cells": [
{
"cue_id": "G0M48GugUIem6YOSIOyO",
"column_id": "EoeiemGEmeMOY8GwUK6s",
"content": "track-01",
"content_html": "<p>track-01</p>"
}
]
},
"meta": {
"request_id": "req_8f4c2b6d9e1a7c3b5d8e2f01",
"server_time": 1750024801000,
"side_effects": []
}
}
(Abbreviated — the real response carries every cue, column, and cell, each with full audit fields.)
datais the shallow rundown;cue_orderandcolumn_ordergive you the running order.includedcarries the created cues, columns, and cells with their IDs — store these if you plan follow-up edits; cells are addressed by(cue_id, column_id)and have no ID of their own.meta.side_effectsis[]on creates; it gets populated on later mutations (e.g. editing a live cue’s duration re-times the runner).
The create is best-effort atomic: if any part of the document fails validation, nothing is created and the error’s details.issues[] points at the offending field (e.g. cues[2].cells.AUDIO). Fix one thing, retry — with the same Idempotency-Key this is always safe.
Common gotchas
- No cells on
headingorgroupcues. Section-header rows must map totype: "heading"(or"group"withchildren) and carry nocellsfield — getting this wrong is rejected withrundowns.invalid_payload. - Column names must be unique within
columns[]. Spreadsheets happily allow duplicate column headers; the API rejects them. textis the only writable column type. Existing dropdown / image / attachment columns can be read but not written through the API yet.- Durations are milliseconds.
0:01:00in the spreadsheet is60000on the wire. - Always send an
Idempotency-Keyon the create. APOST /rundownsrepeated with the same key within 24 hours is replayed from cache instead of creating a duplicate — re-running your import script after a network error is safe. - Caps: at most 500 cues per create request and 1,000 per rundown (group children count), 50 columns, 1 MB body. A bigger show is created shell-first, then padded with
POST /rundowns/:id/cues:bulkbatches.
One more call: start the show
The created rundown is immediately controllable. Start it:
curl -X POST \
-H "Authorization: Bearer $TOKEN" \
https://api-v1.rundownstudio.app/rundowns/YOmm4UG2SGSomuOci44c/action:start
The response is a status snapshot — the same shape GET /rundowns/:id/status returns — with the first cue running.
Where to go next
- API reference — the full
POST /rundownsschema with every optional field (settings, cue numbering, groups). - Working with cell content — when plain strings aren’t enough and you want formatting in cells.
- Error reference — what every validation error means.