Integrations
API reference
Control rooms programmatically with the REST API, or subscribe to live updates over WebSocket. API access is available on Yearly and Single Event plans.
Authentication
Create an API key from the dashboard under API Keys. Keys are shown once at creation and use the format ptp_….
Send the key on every REST request:
Authorization: Bearer ptp_your_api_key_hereAPI keys are scoped to your user account. Room access is limited to rooms in your organization. Keys are tied to the user who created them — any active organization membership for that user is used.
REST API
Base URL: https://stagetoolkit.com
/api/v1/rooms/[slug]Read the current room configuration — timers, messages, and active timer selection.
Response
{
"slug": "abc123",
"activeTimerId": "timer-id-or-null",
"timers": [
{
"id": "…",
"title": "Opening",
"speaker": "Host",
"durationMs": 300000,
"mode": "countdown",
"sortOrder": 0,
"…": "…"
}
],
"messages": [
{
"id": "…",
"text": "Wrap in 2 minutes",
"color": "yellow",
"status": "queued",
"sortOrder": 0,
"durationMs": 10000,
"flash": false
}
]
}Example
curl -s "https://stagetoolkit.com/api/v1/rooms/abc123" \
-H "Authorization: Bearer ptp_your_api_key_here"/api/v1/rooms/[slug]Send a control action to the room. Returns { "ok": true } on success.
Request body
{
"action": "start",
"timerId": "optional-timer-id",
"messageId": "optional-message-id"
}Actions
| Action | Description | Extra fields |
|---|---|---|
start | Start the active or specified timer | timerId (optional) |
pause | Pause the running timer | — |
reset | Reset the active or specified timer | timerId (optional) |
timer.select | Select which timer is active | timerId (required) |
message.show | Push a queued message to air | messageId (required) |
prompter.play | Start prompter scroll | — |
prompter.pause | Pause prompter scroll | — |
Example
curl -s -X POST "https://stagetoolkit.com/api/v1/rooms/abc123" \
-H "Authorization: Bearer ptp_your_api_key_here" \
-H "Content-Type: application/json" \
-d '{"action":"start"}'/api/rooms/[slug]/access-check?role=viewerCheck whether a shared link role requires a passcode before joining. No authentication required. Valid roles: viewer, agenda, qna, qna-screen.
{ "passcodeRequired": true }WebSocket
Connect to the realtime server for live room state and bidirectional control. URL: wss://timer-app-realtime.fly.dev (set via NEXT_PUBLIC_WS_URL in production).
Join a room
Send room.join as the first message after connecting. Controller and moderator roles require the token from the room's share links.
{
"type": "room.join",
"payload": {
"slug": "abc123",
"role": "viewer",
"token": "optional-share-token",
"passcode": "optional-link-passcode"
}
}Server events
| Event | Description |
|---|---|
room.state | Full live room snapshot (timers, runtime, messages, script, settings, connections) |
error | Join or action failed — includes message and optional code |
pong | Response to ping keepalive |
Client events
After joining, send JSON messages with a type field. Available events depend on your connection role. Common examples:
| Event | Role | Description |
|---|---|---|
timer.start | controller | Start timer — optional timerId in payload |
timer.pause | controller | Pause the running timer |
timer.reset | controller | Reset timer — optional timerId |
timer.select | controller | Set active timer |
timer.addTime | controller | Add milliseconds — payload.ms |
timer.subtractTime | controller | Subtract milliseconds — payload.ms |
message.show | controller, moderator | Show message — payload.messageId |
message.create | controller, moderator | Create and queue a message |
prompter.play | controller | Start prompter scroll |
prompter.pause | controller | Pause prompter scroll |
question.create | qna, ask | Submit audience question |
question.upvote | qna, ask | Upvote a question |
settings.update | controller | Update room theme, chimes, Q&A settings, etc. |
layout.set | controller | Switch viewer layout (timer, prompter, split) |
room.resync | controller | Restore live state after reconnect |
ping | any | Keepalive — server replies with pong |
The server broadcasts room.state whenever something changes. Controllers receive the full state including runtime timer progress and connection list.
Connection roles
Use these values in room.join. Token requirements match the share links in the room control panel.
| Role | Access | Token |
|---|---|---|
controller | Full room control — timers, messages, prompter, settings | Required |
viewer | Read-only output display | Optional passcode |
moderator | Messages and Q&A moderation | Required |
agenda | Rundown / agenda view | Optional passcode |
qna | Audience Q&A lobby — submit and vote on questions | Optional passcode |
qna-screen | Venue screen for displayed questions | Optional passcode |
ask | Legacy alias for audience Q&A (same as qna) | Optional passcode |
Errors
REST endpoints return JSON error bodies with an HTTP status code.
| Status | Meaning |
|---|---|
401 | Missing or invalid API key |
403 | Plan does not include this feature — code: PLAN |
404 | Room not found or not in your organization |
400 | Invalid action or request body |
Example plan error:
{
"error": "API access requires Pro or Premium plan",
"code": "PLAN",
"action": "api.room.read"
}