> ## Documentation Index
> Fetch the complete documentation index at: https://tetrafi.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Rate limits

> The limits on quotes, prices and every other request, the headers that report them, and how to wait before retrying a 429.

On a `429`, wait `Retry-After` seconds, then retry once; for an order, keep the same `Idempotency-Key`. Most refusals also carry `details.retryAfter`, the same wait as an ISO 8601 duration such as `PT3S`.

| Limit | What it counts | Over it |
| - | - | - |
| Quotes | One live `POST /api/v1/quotes` every 3 seconds per key. A requote counts; a request answered entirely from cached prices does not. | `429 QUOTE_RATE_LIMITED` |
| Requests per key | Every request, up to the key's `rateLimits` in `GET /api/v1/me` (`requestsPerMinute`, `burstSize`). Costlier operations spend more. | `429 RATE_LIMITED` |
| Requests per address | Requests from one network address, before the key is checked. | `429 RATE_LIMITED` |
| Price reads | Price reads per caller, separate from quotes. | `429 PRICES_RATE_LIMIT` |
| Price streams | At most 4 open price streams per caller. | `429 PRICES_STREAM_LIMIT`: close one first. |

> [accordion] RATE\_LIMITED when TetraFi is at capacity
>
> `RATE_LIMITED` also answers, before anything is sent, when TetraFi is briefly at capacity. Then no quota was counted.

## Why quotes are limited

A live quote request asks venues, and each venue limits how often it answers. Watch a market with [prices](https://tetrafi.mintlify.site/search/prices.md), and request a quote only when you intend to trade.

> [accordion] When a venue is asked too often
>
> | A venue asked too often | What you see |
> | - | - |
> | Stops quoting you for a while | Its source reads [`askLimitReached`](https://tetrafi.mintlify.site/reference/error-codes#askLimitReached), with a `fix` naming when it reopens, or [`firmQuoteLimited`](https://tetrafi.mintlify.site/reference/error-codes#firmQuoteLimited). |
> | Its own quote budget runs out on a requote | `429 VENUE_FIRM_QUOTE_LIMITED` or `BUDGET_EXHAUSTED`. |

> [accordion] Example: wait out a 429 and retry
>
> This example sends two quote requests at once, waits for the refused one's `details.retryAfter`, then sends it again. It places no order.
>
> ```bash cURL theme={null}
> # One live quote request every 3 seconds. Send two at once: one is answered, the other
> # answers 429 QUOTE_RATE_LIMITED with details.retryAfter (ISO 8601, such as PT3S) and the
> # same wait in whole seconds in the Retry-After header. Wait that long, then retry once.
> # Each signature is accepted once, so the two requests differ in amount.
> quote_body() {
>   jq -c -n --arg sell "$sell" --arg buy "$buy" --arg amount "$1" \
>     '{kind: "pair", sell: {asset: $sell}, buy: {asset: $buy}, sellAmount: $amount}'
> }
> tetrafi POST /api/v1/quotes "$(quote_body 10)" >"$tmp/first" 2>/dev/null & first=$!
> tetrafi POST /api/v1/quotes "$(quote_body 11)" >"$tmp/second" 2>/dev/null & second=$!
> wait "$first" && first_ok=true || first_ok=false
> wait "$second" && second_ok=true || second_ok=false
>
> if [[ $first_ok == true ]]; then limited=$(<"$tmp/second") amount=11; else limited=$(<"$tmp/first") amount=10; fi
> jq -c '{error, requestId, retryAfter: .details.retryAfter}' <<<"$limited" >&2
> if [[ $(jq -r '.error' <<<"$limited") == QUOTE_RATE_LIMITED ]]; then
>   # PT3S is 3 seconds; a fraction such as PT1.5S rounds up.
>   wait_seconds=$(jq -r '.details.retryAfter | capture("^PT(?<s>[0-9.]+)S$").s | tonumber | ceil' <<<"$limited")
>   sleep "$wait_seconds"
>   retried=$(tetrafi POST /api/v1/quotes "$(quote_body "$amount")")
>   echo "retried after ${wait_seconds}s: $(jq '.quotes | length' <<<"$retried") quotes" >&2
> fi
> ```

## Read the headers

Any answer a limit counted carries two headers in the [IETF RateLimit header fields](https://datatracker.ietf.org/doc/draft-ietf-httpapi-ratelimit-headers/) format:

| Header | Meaning | Example |
| - | - | - |
| `RateLimit-Policy` | The limit and its window in seconds. | `1;w=3`: one request every 3 seconds. |
| `RateLimit` | `limit`; `remaining`, the requests you may send now; `reset`, whole seconds until quota next frees up. | `limit=1, remaining=0, reset=3` |

`reset` is a wait, not a timestamp or a promise that the whole allowance refills.

> [accordion] Which limit the headers describe
>
> | Case | What the headers describe |
> | - | - |
> | Several limits counted the request | The one closest to running out: the lowest `remaining`, then the longest `reset`. |
> | A `429` | The limit that refused it, with `remaining=0` and `reset` equal to `Retry-After`. |
> | A refusal with no known quota, such as a venue's cooldown or TetraFi at capacity | No `RateLimit` headers; `Retry-After` only. |
> | An answer no limit counted | Neither header. Treat an absent header as "not counted", never as zero. |


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