Docs API reference
API overview
Authentication with API keys, scopes, refs and idempotent upserts, errors, paging and times.
The Wunderframe API lets your own systems do what an editor does in the app: import presenters, plan sessions, keep the words on your screens up to date and upload media. It is a JSON REST API with one kind of key, one error shape, and an OpenAPI 3.1 document you can load into Postman, Insomnia or a client generator.
Base URL
Every endpoint lives under one versioned base URL:
https://www.wunderframe.com/api/v1The version is part of the path. Additions — new endpoints, new optional fields — arrive in v1 without notice, so
ignore fields you do not know. A breaking change would become /api/v2, and v1 keeps working.
Authentication
Every call carries an API key in the Authorization header. A key belongs to one workspace, so there is no
workspace parameter: the key decides where your calls act.
Create a key
As the workspace owner, open Settings → Integrations, name the key after the system that will use it (“Speaker portal”), tick the scopes it needs and create it.
Copy it now
The key is shown once and looks like
wfi.<id>.<secret>. Wunderframe does not keep the secret, so store it in your secret manager or an environment variable right away.Make your first call
Send it as a bearer token. This lists the first five presenters:
curl "https://www.wunderframe.com/api/v1/presenters?limit=5" \
-H "Authorization: Bearer $WUNDERFRAME_API_KEY"Scopes
A key can only do what its scopes allow. A call outside them answers 403 forbidden and names the missing
scope. Give each system its own key with the fewest scopes it needs.
| Scope | Allows |
|---|---|
data | Create, read, update and delete records, collections and their feeds. |
presenters | Import, update and delete presenters and their photos. |
sessions:read | Read sessions and their schedule. |
sessions | Plan sessions, create invite links and rehearsals. |
assets | Upload and list images, video and audio. |
rules | Read and write the named conditions rules refer to. |
authoring | Read and write stories and their chapters, scenes and their shot timelines. |
channels | Read the channel line-up and switch a stage’s channel. |
publishing | Publish releases, read the history and roll back. |
stages | Make venues, choose what a stage plays, book stories and read the setup checklist. |
engagements:read | List live engagements, their rooms and whether they take posts. |
engagements:write | Post photos, videos and notes into a live engagement like a guest (a photo booth, a kiosk), get the take-home link, and prepare and run live polls. |
shows | Build an event’s shows and their cues from its agenda (POST /events/apply). |
brand | Set the workspace brand: the accent color and the logo guest pages and screens share. |
Keys created before scopes existed have data.
Refs and idempotent upserts
Presenters, sessions and records can carry a ref: your own id for them, like speakerportal/1234. A ref is 1 to 200 letters, digits and . _ : / -, starts with a letter or
digit, and is unique in the workspace. Slashes are fine, also in URLs: /presenters/by-ref/speakerportal/1234.
With refs you never need to store Wunderframe’s ids. PUT …/by-ref/{ref} creates the item when the ref
is new (201) and updates it otherwise (200). Sending the same request twice gives the same
result, so a sync that fails halfway can simply run again.
curl -X PUT "https://www.wunderframe.com/api/v1/presenters/by-ref/speakerportal/1234" \
-H "Authorization: Bearer $WUNDERFRAME_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Ada Lovelace",
"title": "Head of Research"
}'Bulk endpoints such as importing presenters and updating many records take up to 500 items per call.
Errors
Successful calls answer 200, 201 for something created, or 204 with no body.
Every error has the same shape, with a message that is safe to show to people:
{
"error": {
"code": "invalid",
"message": "\"name\" is longer than 200 characters.",
"details": { "field": "name" }
}
}| Status | Code | What to do |
|---|---|---|
| 400 | invalid | Fix the request. details.field names the field when there is one. |
| 401 | unauthorized | The key is missing, mistyped or revoked. |
| 403 | forbidden | The key lacks a scope (details.required), or the role is read-only. |
| 404 | not_found | Nothing with that id or ref in this workspace. |
| 409 | conflict | The change clashes with the current state — for example deleting a presenter who is in an unfinished session. |
| 413 | too_large | The body or file is too big for this endpoint. |
| 415 | unsupported | The Content-Type is not accepted here. |
| 423 | closed | The live engagement takes no posts right now. |
| 429 | rate_limited | Slow down and retry after a pause. |
| 500 | internal | Something failed on our side. Retry later. |
| 503 | unavailable | A service the call needs is briefly unavailable. Retry later. |
Paging
List endpoints return a page of items and a next_cursor. Pass limit (1–200,
default 50) and, for the next page, cursor set to the previous next_cursor. The last page has next_cursor: null. Treat cursors as opaque strings.
{
"items": [ { "id": "wkFRBzMq6POZ_qYJHvgQf", "name": "Ada Lovelace" } ],
"next_cursor": "eyJzIjo1MH0"
}let cursor = null;
do {
const url = new URL('https://www.wunderframe.com/api/v1/presenters');
url.searchParams.set('limit', '200');
if (cursor) url.searchParams.set('cursor', cursor);
const res = await fetch(url, {
headers: { Authorization: `Bearer ${process.env.WUNDERFRAME_API_KEY}` }
});
const page = await res.json();
for (const presenter of page.items) console.log(presenter.name);
cursor = page.next_cursor;
} while (cursor);Times
Send a time as epoch milliseconds (1791468000000) or ISO 8601 ("2026-10-08T14:00:00Z") —
both are accepted. Responses give epoch milliseconds, and session times come with an ISO twin in UTC: starts_at and starts_at_iso. Include a time zone in ISO strings; the start of a session is an
exact moment, not a wall-clock time.
Changes show up live
Every write through the API notifies open apps and screens the same way an edit in the app does. A new speaker title reaches the lower third on screen within seconds — there is nothing to refresh.
Limits and good citizenship
- Bulk endpoints take up to 500 items; split larger syncs into several calls.
- A platform-wide rate limit protects the service. On
429, wait a few seconds and retry with backoff. - Use refs and, where a list offers it,
updated_since, rather than re-sending everything on every run. - Going live and finishing a session stay with operators in the app and presenters on their page: the API plans sessions, it does not run the desk.
OpenAPI document
The complete reference is available as an OpenAPI 3.1 document at /api/v1/openapi.json. It is public and contains no data. The reference pages of these docs are generated from the same document, so the two always agree.