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 for roughly 0.5–4 seconds per spoken turn — just the generation window — then freed the instant the turn finishes (~1 s after its last audio).
  • 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 happen to be mid-utterance in the same ~1–2 s window. 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. Failed generations are never charged.

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; $5 buys 1,000,000 characters and credits never expire. When you run out 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, and retry rejected turns on a fresh context_id
  4. Monitor your dashboard — watch usage and balance before hitting limits