# Limits (/reference/limits)



Every number RiffAds enforces on the API, MCP and the CLI. All are fixed. No plan or header changes them unless the row says so.

## Rate limits [#rate-limits]

REST gets both brakes below. MCP has no key, so it gets only the per-action buckets. Over either one: `rate_limited` with `retry_after_seconds`.

### Per key [#per-key]

* **120 requests a minute** per API key, 60 second window.
* Copied onto the key when it is created.

### Per action [#per-action]

Fixed 60 second windows. Counted per API key on REST, per person on MCP.

| Bucket           | Per minute | Spent by                                                                                                           |
| ---------------- | ---------- | ------------------------------------------------------------------------------------------------------------------ |
| `agent_read`     | **120**    | Capability list and detail, actors, voices, generation reads, the wait, batch reads, templates, workflow run reads |
| `agent_estimate` | **60**     | `POST /estimates`, `estimate_generation`, `riffads estimate`                                                       |
| `agent_submit`   | **20**     | `POST /generations`, `submit_generation`, `riffads generate`                                                       |
| `agent_upload`   | **30**     | Reserving **and** finalizing an upload                                                                             |
| `workflow_run`   | **20**     | Both workflow invoke routes and `run_workflow`                                                                     |

* One wait call costs one `agent_read` token, however long it blocks.
* No `X-RateLimit-*` headers. Only `Retry-After`, and only on a 429 that has `retry_after_seconds`.
* `quota_exceeded` (the key's request allowance is spent) is also 429, but not retryable. [Error codes](/reference/errors).

### MCP host brake [#mcp-host-brake]

Per IP, before any token check, on `https://mcp.riffads.com` only.

| Rule                        | Limit | Window     |
| --------------------------- | ----- | ---------- |
| Dynamic client registration | 10    | 10 minutes |
| Every other MCP path        | 120   | 60 seconds |

This 429 is OAuth shaped (`{"error": "too_many_requests", ...}`), not the error envelope.

## Concurrency [#concurrency]

| Rule                                     | Value             |
| ---------------------------------------- | ----------------- |
| Agent submissions running, REST          | **1 per API key** |
| Agent submissions running, MCP           | **1 per person**  |
| A stuck job stops holding the slot after | **600 seconds**   |
| Renders at once, per workspace           | Set by plan       |

* Slot busy: `submission_in_flight` with `in_flight.generation_id`. Wait on that job.
* Render cap hit: `rate_limited` with **no** `retry_after_seconds`. Wait for a job to finish.
* One submit with several variants takes one slot.

## Variants [#variants]

| Rule                | Value                                     |
| ------------------- | ----------------------------------------- |
| Variants per submit | max **4**                                 |
| Real ceiling        | the smaller of the capability's max and 4 |
| Over the ceiling    | **refused, never clamped**                |

Every variant is a full generation and is charged.

## Uploads [#uploads]

| Kind  | Accepted types                                                                   | Max size   |
| ----- | -------------------------------------------------------------------------------- | ---------- |
| Image | `image/jpeg`, `image/png`, `image/webp`, `image/gif`                             | **20 MB**  |
| Video | `video/mp4`, `video/webm`, `video/quicktime`                                     | **100 MB** |
| Audio | `audio/mpeg`, `audio/mp3`, `audio/wav`, `audio/x-wav`, `audio/webm`, `audio/ogg` | **25 MB**  |

| Rule                                  | Value                                      |
| ------------------------------------- | ------------------------------------------ |
| Pixels per image                      | max **40,000,000**                         |
| Longest image side                    | max **16,384** px                          |
| `filename`                            | **1 to 255** characters                    |
| Signed PUT link                       | **300 seconds**, one use, exact byte count |
| Unfinished reservations per workspace | max **50**                                 |
| Abandoned reservation clears after    | **15 minutes**                             |
| Reservations per workspace            | **30 a minute**                            |

* Other file types are refused at reserve, before any bytes move.
* A capability can set stricter file rules. The refusal names its number.

[Uploads](/api/uploads).

## Page sizes [#page-sizes]

| List                | Default | Max |
| ------------------- | ------- | --- |
| `GET /capabilities` | 25      | 50  |
| `GET /actors`       | 25      | 50  |
| `GET /voices`       | 25      | 50  |

* `limit` above the max is clamped, not refused. Junk values (`abc`, `0`, `-1`, `2.5`) read as omitted.
* Cursors are opaque. `next_cursor` is `null` on the last page. A stale cursor restarts from the top.

## Waits and timeouts [#waits-and-timeouts]

| Thing                        | Value          |
| ---------------------------- | -------------- |
| One wait call blocks at most | **20 seconds** |
| Wait route wall clock        | **60 seconds** |
| MCP route wall clock         | **60 seconds** |
| Image inspection on finalize | **5 seconds**  |

* Wait budget used up: **200** with `still_running: true`. Never 408 or 504. Call again.
* No `timeout` parameter.

<Callout type="warn" title="This is not a render time">
  There is no published render time. Loop the wait until `still_running` is `false`. Don't build a deadline from the wait budget.
</Callout>

### CLI [#cli]

| Thing                                                                           | Value                              |
| ------------------------------------------------------------------------------- | ---------------------------------- |
| Request timeout                                                                 | 30 seconds                         |
| Wait call timeout                                                               | 90 seconds                         |
| `riffads generate`, `riffads status --wait` give up after                       | 30 minutes (`--timeout <seconds>`) |
| `riffads workflows invoke --wait`, `riffads workflows run --wait` give up after | 60 minutes (`--timeout <seconds>`) |
| Retryable refusals in a row before it stops                                     | 5                                  |

Giving up is not cancelling. The job keeps running. [CLI commands](/cli/commands).

## Link lifetimes [#link-lifetimes]

| Link                         | Lifetime                                               |
| ---------------------------- | ------------------------------------------------------ |
| Output link (REST, MCP, CLI) | **600 seconds** (`output_urls_expire_in_seconds: 600`) |
| Signed upload PUT link       | **300 seconds**                                        |

<Callout type="warn" title="Never store a link">
  Download right away. If it fails, read the generation again for a fresh link. Don't retry the old URL. [Results](/guides/results).
</Callout>

## Blocks [#blocks]

| Window                                 | Value            |
| -------------------------------------- | ---------------- |
| Failures in a row that pause a request | **3**            |
| Pause lasts                            | about **1 hour** |
| Content refusals that block a request  | **2**            |
| Block lasts                            | **30 days**      |

Only `moderation_blocked`, `provider_unavailable` and `internal_error` count. [Error codes](/reference/errors).

## Request bodies [#request-bodies]

* Every write body is strict. **Unknown keys are refused**, including inside `config`. `maxCredits` is a 400.
* Workflow invoke: max **60** input entries.
* Validation refusals list max **5** issues, then `Plus N more.`
* No published body size limit. Send files through uploads.

## Spend caps [#spend-caps]

Agent spend is capped by `max_credits` on each request, plus caps a workspace owner or admin sets. Over one: `max_credits_exceeded` or `spend_limit_exceeded`.
