Skip to main content
Nineninesix enforces two limits, both per org: a request rate (requests per minute) and a concurrency cap (simultaneous streams). They scale by your tier.
Limits are shared across your whole org. All of your API keys draw on one rate-limit bucket, one concurrency pool, and one tier — extra keys are for rotation and security, not more capacity. Creating five keys does not give you five times the limits.

Tiers

These are current limits, not hard caps — we raise them per customer as you scale. Contact us if you need more headroom.

What counts as a request

The requests-per-minute limit counts new requests started per minute:
  • POST /tts/bytes and POST /tts/sse — one request per HTTP call.
  • GET /tts/websocket — one request per connection, spent at handshake, not per message. A long-lived socket streaming many turns costs a single request token when it opens; what bounds its ongoing work is the concurrency cap.

How concurrency is measured

This is the important, non-obvious part. A stream counts against your concurrency limit only while it is actively generating audio — not for the socket’s lifetime, and not for the whole call.
  • A slot is held only while a turn is generating, then freed as soon as the turn finishes.
  • Idle time between turns (the user talking, silence, thinking) costs nothing.
So “concurrent” counts simultaneous active-speech turns, not simultaneous calls. Because only ~10–30% of a typical conversation is active TTS, one slot comfortably serves many live calls — you only approach the limit when that many turns are generating at the same moment. In practice Tier 1’s 5 concurrent supports far more than 5 live conversations. Hold a socket open per call for free; you only pay concurrency for overlapping speech.

Handling 429 Responses

When you exceed a limit you’ll receive 429 Too Many Requests with a Retry-After header:
Concurrency rejections look like:
If a turn exceeds the concurrency limit, only that one context is rejected — the socket and your other contexts keep running. Back off and retry that turn on a fresh context_id; there’s no “push through,” since continuing to stream a rejected context produces no audio. Rejected turns aren’t charged. Separately from your tier limits, overloaded (429) means the synthesis servers are at capacity. Back off and retry; don’t rely on a Retry-After header for it.

Credits

Billing is separate from rate limits and is keyed to your org, shared across all its keys. Each character of transcript costs 1 credit; $15 buys 1,000,000 characters and credits never expire. Billing is postpaid: requests start only while your balance is above zero, and once it reaches zero you’ll get a 402 payment_required — top up on the Billing page.

Best Practices

  1. Cache generated audio — don’t regenerate the same text repeatedly
  2. Respect concurrency — pool and reuse streams rather than opening unbounded connections
  3. Back off on 429 — use the Retry-After header when present, and retry rejected turns on a fresh context_id
  4. Monitor your dashboard — watch usage and balance before hitting limits