Rate limits

Rate limits cap how many requests and how many input tokens your organization can send per minute. This page explains how both are counted, how your tier raises them, how to read your remaining capacity from response headers, and how to handle a 429.

In short

The two limits

LimitWhat countsWhat does not count
Requests per minute (RPM)Each request to POST /v1/decisions or POST /v1/systemone, however many questions it carries. A request that passes the key and body checks counts even if it is then refused for tokens per minute, spend limit or credit, or the model fails.GET /v1/models. Requests refused while the key and body are checked (400, 401, 404, 413, 422), which happens before the limit is checked. A request refused because requests per minute is already reached.
Tokens per minute (TPM)Billed input tokens: the same number the response reports in usage.input_tokens.Any request that fails or is refused, for example 402, 429 or 502. Output: only input tokens count, and Clara's output price is $0.

A request must fit under both limits to run. Before it runs, the platform estimates its input tokens from the size of the request and checks that the estimate fits in what is left of your tokens for the window. The estimate is deliberately on the high side. Once the request finishes, the window holds the tokens it was actually billed for instead.

Which limit will you reach first. On every tier, tokens per minute is 1,000 times requests per minute. If your average request bills more than 1,000 input tokens, tokens per minute is the limit you reach first. Below that, requests per minute is.

How the sliding window works

There is no minute boundary that resets your limits at :00. Every request stays in the window for 60 seconds after it was made, then drops out and frees its request slot and its tokens.

For example, Tier 1 allows 300 requests per minute:

Bursts up to the limit are allowed, but then you wait for the window to clear. Spreading the same traffic evenly avoids the wait: at 300 requests per minute, spacing requests a little more than 0.2 seconds apart keeps you under the limit.

Tokens work the same way: the tokens a request was billed for leave the window 60 seconds after that request. For tokens, Retry-After is the time until the oldest request in the window drops out. If that frees fewer tokens than your next request needs, you get another 429 with a new Retry-After, so retry in a loop rather than once.

How tokens per minute are counted

Tokens per minute count the input tokens you are billed for, so the number you see in usage.input_tokens is exactly what each request took from the window. Requests that fail or are refused bill nothing and take nothing from the window.

Fast and consistent mode

The mode field changes how many tokens a request bills, and so how many requests fit in your tokens per minute:

Take a 1,500-token record with 5 questions of about 40 tokens each:

ModeHow it is billedBilled tokens (about)
fast1,500 + 5 × 401,700
consistent5 × (1,500 + 40)7,700

Consistent mode bills about 4.5 times as many tokens here, so each request costs about 4.5 times as much and takes that much more of your tokens per minute. About the most of these requests each tier can sustain, whichever limit is reached first:

TierRPMTPMFast (1,700 tokens)Consistent (7,700 tokens)
Tier 1300300K176 / min38 / min
Tier 2700700K411 / min90 / min
Tier 31,5001.5M882 / min194 / min
Tier 42,5002.5M1,470 / min324 / min
Tier 55,0005M2,941 / min649 / min

Use consistent mode where answers must not shift with the question set (JevBench-mini: 83.6 consistent, 81.8 fast), and fast mode everywhere else. The response has the same shape in both modes.

Requests per minute count requests, not questions, and fast mode reads the record only once. With the example above, five questions in one request use one request slot and bill about 1,700 tokens. The same five questions as five separate requests use five slots and bill about 7,700 tokens, because the record is read five times. Put every question about one record into one request, up to your tier's questions per request.

Usage tiers

Your tier sets your limits. Tiers apply to the whole organization, across all of its projects, keys and models.

TierTotal paidSince first paymentRPMTPMQuestions / requestEach top-up
Tier 0Sign up—NoneNoneNone$5–$25
Tier 1$5right away300300K32$5–$25
Tier 2$507+ days700700K64$5–$100
Tier 3$25014+ days1,5001.5M64$5–$300
Tier 4$1,00030+ days2,5002.5M64$5–$500
Tier 5$2,50060+ days5,0005M64$5–$1,000

How upgrades happen

You never apply for a higher tier. The moment your organization meets both conditions for a tier, it moves up automatically, with nothing to request. The API picks up the new limits within 60 seconds.

For example, say you buy $5 on your first day and $45 more on day 3. You have now paid $50, but Tier 2 also needs 7 days since your first payment, so it applies 7 days after that first purchase, automatically.

When you move up a tier, the console shows a one-time notice. Limits in the console shows your current tier and limits, your progress toward the next tier on both conditions, and a tier history listing every move and what caused it: a credit purchase, time since your first payment or a refund. A move caused by time can take up to an hour to appear in the history and the notice; the new limits apply before that.

Refunds can lower your tier. A refund removes the refunded credit from your lifetime purchases and recalculates your tier, so it can move your organization down. See Billing and credit.

Rate limit headers

Responses from POST /v1/decisions and POST /v1/systemone tell you where you stand, so you can slow down before you reach a limit.

HeaderMeaningExample
x-ratelimit-limit-requestsYour organization's requests per minute.300
x-ratelimit-remaining-requestsRequests left in the current window, after this one.287
x-ratelimit-reset-requestsTime until the oldest request leaves the window and frees a slot, in whole seconds.12s
x-ratelimit-limit-tokensYour organization's input tokens per minute.300000
x-ratelimit-remaining-tokensInput tokens left in the current window, counting this request at its estimated size.291604
Retry-AfterOn 429 rate_limit_exceeded: seconds to wait before retrying.9

Two more headers are useful when debugging. x-request-id identifies the request (it is on errors too, and matches request_id in an error body), and saanora-processing-ms is the total time the request spent in the platform, in milliseconds.

To see the headers, send any request with curl -i, or read them from your HTTP client:

curl -i https://api.saanora.com/v1/decisions \
  -H "Authorization: Bearer $SAANORA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"model": "clara-1", "state": "Customer writes: I was charged twice for my March invoice.", "questions": {"refund": {"type": "noul", "instructions": "Is the customer asking for money back?"}}}'
response headers
HTTP/2 200
content-type: application/json
x-request-id: req_8f2c41d09a7e4b6c1f3a5e70
x-ratelimit-limit-requests: 300
x-ratelimit-remaining-requests: 287
x-ratelimit-reset-requests: 12s
x-ratelimit-limit-tokens: 300000
x-ratelimit-remaining-tokens: 291604
saanora-processing-ms: 241

Read the headers defensively: not every error response carries all of them.

Handling 429 responses

Three different situations return 429. Branch on error.code, not on the message text:

codeCauseRetry?What to do
rate_limit_exceededOver your requests per minute. The message says requests per minute.YesWait Retry-After seconds, then retry.
rate_limit_exceededOver your tokens per minute. The message says input tokens per minute.YesWait Retry-After seconds, then retry. A request larger than your whole minute runs once the window is empty and then fills it; make it smaller for steady throughput.
project_spend_limitThe project reached its monthly spend limit.NoRaise the limit in Projects, or wait for the next UTC month.

All three have "type": "rate_limit_error". A rate limit response looks like this:

429
HTTP/2 429
content-type: application/json
retry-after: 9
x-request-id: req_3c9e1a7f5b2d4e8c0a6f1b94
x-ratelimit-limit-requests: 300
x-ratelimit-remaining-requests: 0
x-ratelimit-reset-requests: 9s

{
  "error": {
    "type": "rate_limit_error",
    "code": "rate_limit_exceeded",
    "message": "300 requests per minute on tier 1; lifetime top-ups raise it",
    "param": null
  },
  "request_id": "req_3c9e1a7f5b2d4e8c0a6f1b94"
}

A good client waits for Retry-After, adds a little random jitter so many workers do not retry at the same instant, caps the number of attempts, and stops at once on project_spend_limit. The same loop can retry 502 model_unavailable and 503 with exponential backoff. Retrying costs nothing: failed and refused requests are never charged.

import os
import random
import time

import requests

API_URL = "https://api.saanora.com"
API_KEY = os.environ["SAANORA_API_KEY"]


class SpendLimitReached(Exception):
    """The project reached its monthly spend limit. Retrying will not help until the limit is raised or the month ends."""


def decide(body: dict, max_attempts: int = 6) -> dict:
    """POST /v1/decisions, waiting out rate limits and retrying temporary errors."""
    request_id = None
    for attempt in range(max_attempts):
        r = requests.post(
            f"{API_URL}/v1/decisions",
            headers={"Authorization": f"Bearer {API_KEY}"},
            json=body,
            timeout=120,  # the first request after an idle period can take a while
        )
        if r.status_code == 200:
            return r.json()

        try:
            err = r.json().get("error") or {}
        except ValueError:
            err = {}
        code = err.get("code")
        request_id = r.headers.get("x-request-id")

        if r.status_code == 429 and code == "project_spend_limit":
            raise SpendLimitReached(err.get("message"))
        if r.status_code == 429:
            wait = float(r.headers.get("Retry-After", "1"))  # seconds until capacity frees up
        elif r.status_code in (502, 503):
            wait = min(2 ** attempt, 30)                     # 1, 2, 4, 8, 16 seconds
        else:
            # 400, 401, 402, 404, 413, 422: the same request will fail again
            raise RuntimeError(f"{r.status_code} {code}: {err.get('message')} ({request_id})")

        if attempt + 1 < max_attempts:
            time.sleep(wait + random.uniform(0, 1))         # jitter keeps clients from retrying in lockstep

    raise RuntimeError(f"gave up after {max_attempts} attempts ({request_id})")


if __name__ == "__main__":
    result = decide({
        "model": "clara-1",
        "state": "Customer writes: I was charged twice for my March invoice.",
        "questions": {
            "refund": {"type": "noul", "instructions": "Is the customer asking for money back?"},
        },
    })
    print(result["answers"]["refund"]["noul"], result["usage"])

Retrying a 429 immediately, without waiting, only gets another 429. Other errors (400, 401, 402, 404, 413, 422) will fail again until you change the request or your account; see Errors.

Stay under the limits

Per-request limits

These apply to each request on its own, whatever your remaining rate limit:

LimitValueWhen exceeded
Questions per request32 on Tier 1, 64 from Tier 2422 too_many_questions
Request body1 MB413 request_too_large
Record (state)About 16K tokens, less when a question has a long option listNot an error: longer records are shortened in the middle and billed as read
Options in a choice question2 to 255422 invalid_criteria

Project spend limits

Rate limits cap speed. Spend limits cap money. Each project can have its own monthly spend limit, so a runaway job in one project cannot use up the credit every project shares.

A spend limit does not change the rate limit: a project with a limit still shares the organization's requests and tokens per minute. It is also separate from 402 insufficient_credit, which means the organization's prepaid credit cannot cover a request. See Organizations and projects and Billing and credit.

Beyond Tier 5

If you need more than 5,000 requests or 5M tokens per minute, or dedicated capacity, write to support@saanora.com. Tell us the peak requests and tokens per minute you expect.

We can set dedicated limits for your organization: requests and tokens per minute, questions per request and the size of a single top-up, each above what your tier allows. They apply to every key in the organization within a minute, replace the tier's values only where set, and show in the console under Limits with a “Dedicated limits” label.