> ## Documentation Index
> Fetch the complete documentation index at: https://docs.finesse.flamapp.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Rate limits

> Per-plan concurrency and request-rate limits, the 429 responses they produce, and how to back off.

Every workspace has two limits, set by its plan:

* **Concurrent streams**: how many synthesis requests can be in flight at once.
* **Requests per second (rps)**: how many public API requests the workspace can make in each
  one-second window.

Both are counted per **workspace**, across every API key and every server, so the limit is exact
however many keys you use or wherever your requests land.

## Limits by plan

| Plan | Concurrent streams | Requests per second |
| - | - | - |
| Free | 2 | 2 |
| Starter | 5 | No limit |
| Pro | 10 | No limit |
| Enterprise | 50 | No limit |

Enterprise contracts can set different limits. The `RateLimit-Policy` header (below) always shows
the limits that apply to your workspace.

## What counts as a stream

A concurrent-stream slot is held for the whole life of one synthesis:

* [`POST /voices/{id}/speak`](/voice-api/synthesize-speech): until the audio has been returned.
* [`POST /voices/{id}/stream`](/voice-api/stream-speech): until the event stream ends.
* The OpenAI-compatible `POST /openai/audio/speech`: until the audio has been returned.
* [Realtime WebSocket](/voice-api/realtime-websocket): from the handshake until the socket closes,
  however many contexts it runs. Run several contexts on one socket rather than opening more sockets.

The **dashboard and the API share the same slots**. Generating speech on the dashboard's TTS page
uses one of your workspace's slots, just like an API call does.

Every public API request counts toward requests per second, including WebSocket handshakes and
OpenAI-compatible calls. Other
endpoints (list, get, create voices, jobs, tokens) do not hold a stream slot.

## When you hit a limit

The request is refused with `429 Too Many Requests`, a `Retry-After` header (seconds), and the
standard [error envelope](/voice-api/errors) with a `code`:

```json theme={null}
{
  "status": 429,
  "error": true,
  "code": "concurrency_limited",
  "message": "Too many concurrent synthesis requests for this workspace's plan. Retry when one finishes.",
  "request_id": "req_4f1c9a0b7e2d4c6a8b1f3e5d"
}
```

| Code | Meaning |
| - | - |
| `rate_limited` | Over the plan's requests per second. The next one-second window starts within `Retry-After` seconds. |
| `concurrency_limited` | Every stream slot is in use. A slot frees as soon as one of your in-flight requests or sockets finishes. |

A refused request is not billed. On the WebSocket, a refused handshake gets the same `429` and body
before the upgrade, so the socket never opens.

<Note>
  `503` with code `upstream_busy` is different: it means the synthesis service itself is at
  capacity, not that you are over your plan. Back off and retry it the same way.
</Note>

## Rate limit headers

Responses from the public API carry the IETF
[RateLimit header fields](https://datatracker.ietf.org/doc/draft-ietf-httpapi-ratelimit-headers/):

```http theme={null}
RateLimit-Policy: "concurrency";q=2;qu="concurrent-requests", "rps";q=2;w=1
RateLimit: "rps";r=1;t=1, "concurrency";r=1
```

* `RateLimit-Policy` lists your limits: `q` is the quota, `qu="concurrent-requests"` marks the
  concurrency limit, and `w=1` is the one-second rps window. A policy appears only when your plan
  has that limit.
* `RateLimit` reports what is left: `r` is the remaining quota and `t` the seconds until the rps
  window resets. The `concurrency` item appears on synthesis requests and WebSocket handshakes,
  and counts the slots still free after this request took one.

Browsers can read these headers, along with `Retry-After` and `X-Request-Id`.

## Backing off

1. On `429`, wait at least the `Retry-After` seconds before retrying.
2. Retry with exponential backoff plus jitter (for example about 1s, 2s, 4s) and stop after a few
   attempts.
3. Watch `RateLimit` and slow down when `r` reaches `0`, instead of waiting for a `429`.
4. Cap your own concurrency at your plan's concurrent-streams limit, for example with a small
   worker pool or semaphore, and queue the rest.
5. For realtime use, keep one WebSocket open and run several contexts on it.

Retrying a refused request is safe: it was not processed and not billed.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.