# Soapbox API, complete reference Live audio rooms for your own app. This file is the whole surface in one fetch, so you do not have to crawl a docs site. Base URL: https://soapbox-server.fly.dev Auth: Authorization: Bearer sk_live_... or sk_test_... ## The rules the API enforces These are product rules, not rate limits. They have no per-account overrides. The first two are set by whoever runs the platform and default to ten and fifty, so read the numbers a response gives you rather than hardcoding these. - Ten live casts per account by default. The next one returns CAST_FULL. - Fifty people per cast by default, counting hosts and guests. Every response that names a cast carries its own maxParticipants. - Two hosts and eight guest seats. The stage does not grow. - Four hours, then the room ends itself. - Nothing is recorded. No audio, no chat, no transcripts, no clips. When a cast ends its data is deleted, not archived. - Chat messages exist for 180 seconds and are then gone for everyone. ## Keys Two kinds, distinguished by prefix. sk_test_... Creates real rooms. Never billed. Use while building. sk_live_... Creates real rooms. Billed. Use in production. Both are server credentials. A live key presented with a browser Origin header returns 403 and should be rotated: it has been exposed to anyone who opened devtools. ## Minutes and billing Minutes are participant-minutes, rounded up per person per session. One listener for ten seconds is one minute. Fifty people for an hour is 3,000. New accounts get 5 free participant-minutes, once. After that, packs of 1,000 minutes at $10 per month. Your balance refills to the full pack amount at the start of each billing period: it is set rather than added to, so minutes from a quiet month do not accumulate on top. Test key usage is recorded so you can see it, and never charged. ## POST /v1/casts Create a live room. Body: { "title": string (<=90), "tags": string[] (<=5, optional) } 201: { id, title, shareCode, maxParticipants, startedAt, endsAt, url } 402: INSUFFICIENT_MINUTES, on a live key with no balance 409: CAST_FULL, when every live slot is taken curl -X POST https://soapbox-server.fly.dev/v1/casts \ -H "authorization: Bearer $SOAPBOX_KEY" \ -H "content-type: application/json" \ -d '{"title":"Week 1 overreactions","tags":["NFL"]}' Tags are free-form strings, capped at five per cast. Soapbox ships a standard vocabulary that the mobile app and the feed filters use: Leagues NFL, NBA, WNBA, MLB, NHL, WWE, EPL Teams "LEAGUE:Team", for example "NFL:Chiefs" or "EPL:Arsenal" Categories BUSINESS, TECH, MUSIC, GENERAL Topics "HOT TAKE", "LIVE THREAD", POSTGAME, FANTASY, DRAFT, TRADES, ANALYSIS Teams are namespaced by league because several team names appear in more than one league: Cardinals, Rangers and Giants each name two different teams. Filtering by a league also matches that league's teams, so a cast tagged only "NFL:Chiefs" is returned when filtering by "NFL". Filtering is exact otherwise: a partial name matches nothing. Your own strings work too. They simply match themselves and take no part in the league behaviour above. Tags are trimmed and de-duplicated on the way in, case insensitively, so the stored list can be shorter than the one you sent. Read it back from the response rather than assuming it round-trips. ## GET /v1/casts 200: { casts: [ { id, title, shareCode, maxParticipants, endsAt } ] } ## GET /v1/casts/{id} 200: { id, title, shareCode, participantCount, maxParticipants, endsAt } 404: CAST_NOT_FOUND ## DELETE /v1/casts/{id} Ends the room and deletes it. Not reversible, and there is nothing to restore afterwards, by design. 200: { "ended": true } 404: CAST_NOT_FOUND ## POST /v1/casts/{id}/tokens The endpoint you will call most. Mint a join token for one of your users. Body: { "identity": string, "displayName": string, "role"?: "host" | "cohost" | "featured_guest" | "listener" } 200: { token, url, role, canPublish, expiresAt } 404: CAST_NOT_FOUND 410: CAST_ENDED `identity` is your id for your user. It never becomes a Soapbox account and never appears anywhere in the Soapbox app. Roles: listener hears, chats, reacts, votes. Never publishes audio. featured_guest speaks for the rest of the cast. cohost speaks and moderates. host speaks, moderates, and may promote one cohost. No role can publish video or share a screen. Soapbox is audio. curl -X POST https://soapbox-server.fly.dev/v1/casts/$CAST_ID/tokens \ -H "authorization: Bearer $SOAPBOX_KEY" \ -H "content-type: application/json" \ -d '{"identity":"user_8891","displayName":"Dana","role":"listener"}' ## GET /v1/usage 200: { mode, billable, balance: { freeMinutesRemaining, purchasedMinutesRemaining, totalRemaining, consumedThisPeriod }, last30Days: { participantMinutes, sessions } } ## Errors Every error is { "error": { "code", "message", "fix"? } }. Branch on `code`. It is stable across versions; `message` is written for a person to read and may change. UNAUTHENTICATED Missing, malformed or revoked key. FORBIDDEN Includes a live key sent from a browser. Rotate it. INSUFFICIENT_MINUTES No balance. Buy packs, or use a test key while building. CAST_FULL Every live slot is taken, or a room is at its people cap. CAST_NOT_FOUND No such cast on this account. CAST_ENDED The cast is over. Nothing from it was kept. RATE_LIMITED Slow down. ## Joining from a browser Install @soapbox/embed and hand it a token from your server. import { join } from '@soapbox/embed'; const session = await join({ token, // from your server, never an API key url, // returned alongside the token onState: (state) => render(state), }); // Browsers refuse audio without a user gesture, so call this from a click. await session.start(); The embed is subscribe only. It never opens a microphone, so there is no permission prompt and no way for an embedded room to put someone on a stage they did not choose. ## What does not exist Asking for these will not work, and the absence is deliberate: - Recording, transcripts, or any archive of a finished cast - Fetching chat history: messages last 180 seconds and are not stored - Raising the live cast or people caps for one account. They are platform wide - Video or screen sharing