TwinToneAPI Docs

API reference

Errors & rate limits

Error format

Every error response has the same shape:

{ "error": { "code": "rate_limited", "message": "Too many requests. Limit: 100/min." } }
HTTPCodeMeaningFix
400missing_fieldsRequired body/query params absent.Check the endpoint reference.
400invalid_fieldsField validation failed (e.g. bad platform, script too long, duration out of range).Check the endpoint reference for constraints.
400invalid_creatorThe creator_id doesn't match any creator.Use GET /api/v1/creators to list available creators.
400invalid_urlWebhook url failed validation (must be an https:// endpoint).Check the URL and re-register.
400invalid_eventsWebhook events must be a non-empty subset of stream.started, stream.ended, stream.error.Fix the event list.
401unauthorizedKey missing, malformed, or revoked. GET /creators needs no key, but if you send one that doesn't validate it also returns 401.Verify the Bearer tt_… header; create a new key if revoked.
402plan_requiredCreating a live key, or managing streams and webhooks with one, requires the Growth plan or above. Test keys are free.Upgrade your plan or use a test key.
402no_minutesStarting a live stream when the minutes you've used this billing period, plus time on streams running now, have reached your plan's allowance.Wait for your next billing period or upgrade under Settings → Billing.
403insufficient_scopeKey lacks the scope for this endpoint.Create a key with the needed scope.
403vertical_mismatchvertical in the request ≠ your brand's vertical, or platform is not permitted for your brand's vertical.Drop the vertical field (it defaults to your brand).
403vertical_lockedThe key was created when your brand had a different vertical than it has now, so it no longer works.Create a new key.
404not_foundResource doesn't exist or belongs to another brand. Ending a stream that isn't live also returns this, and so does a test key reading or ending a stream that wasn't started with a test key.Check the id, or wait for the stream to be live before ending it.
409webhook_limitYour brand already has 10 active webhooks.Delete one you no longer use (DELETE /api/v1/webhooks?id=…), then register again.
429rate_limitedOver the per-minute request limit.Back off before retrying.
429concurrency_limitBrand already has 3 active (preparing+live) streams of the same kind. Test-key and live-key streams are counted separately.End one before starting another.
500db_errorSomething failed on our side.Retry with backoff; contact support if persistent.
502launch_failedStream dispatch to the agent worker failed.Retry — this is usually transient. If persistent, contact support.
502stop_failedDELETE /streams/{id} couldn't confirm the broadcast stopped. The stream stays live.Retry the DELETE. If persistent, contact support.

Warnings

A successful response can carry a warnings array: the request worked, but something needs your attention.

CodeReturned byMeaningFix
no_broadcast_destinationPOST /streams (live keys)The stream started, but your brand has no connected channel with a stream key, so it reaches no platform.Connect a channel, then start a new stream.

Rate limits

LimitApplies to
100 requests/minAll v1 endpoints except public GET /creators
10 active webhooksPer brand. The 11th returns 409 webhook_limit.
1,000 waiting webhook deliveriesPer brand, including retries. Above it, new events are not queued until the backlog drains.

The limit is per brand, per minute, fixed window. When you hit it you'll get a 429 rate_limited. GET /api/v1/creators is public and not rate-limited. 429 concurrency_limit is separate — retrying won't clear it, end a stream first.

Limits are applied in-memory and reset on deploy. If you're polling stream status, switch to webhooks — it's what they're for.

Idempotency & retries

  • GET/DELETE are safe to retry freely.
  • POST /streams supports an Idempotency-Key header (recommended). Send a unique key per logical request (e.g. a UUID). If the request times out or the connection drops, retry with the same key and the same body:
    • same key + same body ⇒ the exact original response is replayed — never a second stream or a second charge
    • same key + different body ⇒ 409 idempotency_conflict
    • same key while the first request is still executing ⇒ 409 idempotency_in_progress; keep retrying with the same key
    • if the idempotency ledger is temporarily unavailable, the POST fails closed with 503 idempotency_unavailable (no stream is started); retry with the same key
    • successful responses replay for 24 hours; failed launches (502) replay for 1 hour, then the key is free again
  • Without the header, a retry can create a duplicate stream — if you don't send one, check GET /streams before retrying.
  • Webhook deliveries are at-least-once; dedupe on the event id.

Abuse prevention

Keys used for probing, scraping, or exceeding rate limits without prior arrangement may be revoked. If you need higher limits, email us — we're happy to accommodate legitimate traffic.

Was this page helpful?