Rate Limits
The Prezent API enforces three independent kinds of limits. They are evaluated independently, and for any given request the most restrictive one binds first. All three are configurable per company — contact your CSM to adjust them.
This page is self-contained: every number you need is in the tables below. For the broader error catalog and async patterns, see the Developer Guide.
The three tiers at a glance
| Tier | Enforced at | Scope | Window | 429 error.code |
|---|---|---|---|---|
| 1. Gateway throttle | AWS API Gateway edge | Per API key, all endpoints | Per-second + per-day | TOO_MANY_REQUESTS |
| 2. Per-minute rate limit | Application (Lambda) | Per company × API category | 60-second sliding | RATE_LIMIT_EXCEEDED |
| 3. Annual usage quota | Application (Lambda) | Per API key, per billable operation | Rolling year | USAGE_LIMIT_EXCEEDED |
A fourth code, MAX_RETRIES_EXCEEDED, is returned when an internal
retry budget for a downstream operation is exhausted (also 429).
Tier 1 — Gateway throttle
A per-API-key request-rate and daily-request cap enforced at the AWS API Gateway edge. It applies to every endpoint, regardless of category.
| Setting | Default |
|---|---|
| Steady request rate | 10 requests / second per API key |
| Burst capacity | 5 requests |
| Daily request quota | 1,000 requests / day per API key, across all endpoints |
Custom usage plans can raise these caps per customer — the values above
are the standard defaults provisioned for a new key. When a key exceeds
the throttle, API Gateway returns HTTP 429 with the canonical error
envelope and error.code: "TOO_MANY_REQUESTS".
The gateway throttle is enforced before your request reaches the application, so a gateway
429does not carry theX-RateLimit-*headers described below — those are computed by the application for the per-minute limit (Tier 2). Treat a gateway429as a signal to back off across all of your traffic on that key.
Tier 2 — Per-minute rate limits
Application-level limits, enforced per company × API category over a 60-second sliding window. Three categories exist in production today:
| Category | Endpoints in this category |
|---|---|
auto_generator | All /api/v1/autogenerator/* (and the /api/v1/autogenerations/* REST aliases), plus /api/v1/audiences, /api/v1/themes, /api/v1/upload, /api/v1/validate, and /api/v1/file-access |
template_converter | All /api/v1/template-converter/* and /api/v2/template-converter/* (and the /api/v1/template-conversions/* REST aliases) |
scim | All /api/v1/scim/* |
| Setting | Default |
|---|---|
| Requests per window | Configured per company — contact your CSM |
| Window length | 60 seconds, sliding |
The per-window request count is configured per company × category;
there is no platform-wide default value. The limiter only engages once a
limit has been configured for your company and category — until then,
only Tier 1 (the gateway throttle) and Tier 3 (annual quotas) apply.
In practice the gateway throttle's 1,000 requests / day default is the
most restrictive limit for most workloads and will bind first.
Endpoints not classified under one of the three categories above (for example, health checks) are not throttled by the per-minute limiter — only the gateway throttle applies to them.
When this limit is exceeded the application returns HTTP 429 with
error.code: "RATE_LIMIT_EXCEEDED".
Tier 3 — Annual usage quotas
Per-API-key caps on specific billable operations, tracked across a rolling year. Default quotas for new customers (configurable per company):
| Operation | Default quota (per year) | Counted by |
|---|---|---|
Slide generation — POST /api/v1/autogenerator, POST /api/v1/autogenerator/regenerate | 50,000 / year | successful slides generated |
Presentation download — POST /api/v1/autogenerator/download | 1,000,000 / year | successful downloads |
Template-Converter start — POST /api/v1/template-converter/start | Negotiated per customer | successful TC starts |
Template-Converter download — GET /api/v1/template-converter/download/{callback_id} | Negotiated per customer | successful TC downloads |
Quotas are tracked on the API key. All other endpoints (audiences,
themes, upload, file-access, SCIM, status polls, etc.) do not
consume any annual quota. When a key exceeds its quota the API returns
HTTP 429 with error.code: "USAGE_LIMIT_EXCEEDED".
The four 429 error codes
Every limit surfaces as HTTP 429 with the standard error envelope.
The error.code tells you which tier you hit and how to react:
error.code | Tier | Triggered when | What to do |
|---|---|---|---|
TOO_MANY_REQUESTS | 1 — Gateway throttle | You exceed 10 req/s, the 5-request burst, or 1,000 req/day on the key | Back off all traffic on the key; retry after a delay |
RATE_LIMIT_EXCEEDED | 2 — Per-minute rate limit | Your company exceeds its configured per-category limit in the 60-second window | Slow this category; retry after the window resets |
USAGE_LIMIT_EXCEEDED | 3 — Annual usage quota | The key reaches its yearly cap for a billable operation | Contact your CSM to raise the quota |
MAX_RETRIES_EXCEEDED | Internal retry budget | An internal retry budget for a downstream operation is exhausted | Retry the request after a delay; if it persists, contact support |
error.code values are stable strings drawn from the
error catalog — they never change
meaning or HTTP status once published.
Error envelope
{
"success": false,
"data": null,
"error": {
"code": "RATE_LIMIT_EXCEEDED",
"message": "Company rate limit exceeded. Please try again later.",
"details": {
"category": "auto_generator"
}
}
}
Response headers
The headers below describe the per-minute, per-category limit (Tier 2) — they are the values the application computes for your company and category.
| Header | Description |
|---|---|
X-RateLimit-Limit | Configured requests-per-window for the category. |
X-RateLimit-Remaining | Approximate requests remaining in the current 60-second window. |
X-RateLimit-Reset | Unix timestamp (seconds) at which the window resets. |
Retry-After | Seconds the client should wait before retrying. |
X-RateLimit-Limit, X-RateLimit-Remaining, and X-RateLimit-Reset are
returned on successful (2xx) responses from rate-limited endpoints, and
alongside Retry-After on a per-category RATE_LIMIT_EXCEEDED 429 — so
you can pace yourself before hitting the wall and know how long to wait when
you do. They always describe the Tier-2 per-category limit.
The gateway throttle (Tier 1) is enforced at the API Gateway edge, and the
annual quota (Tier 3, USAGE_LIMIT_EXCEEDED) is not a per-minute window —
neither response carries X-RateLimit-* / Retry-After. For those, use
error.code in the body as the signal and apply the jittered backoff below.
Handling 429: Retry-After + jittered exponential backoff
The robust pattern is: honour Retry-After when it is present, and
otherwise fall back to jittered exponential backoff (so a fleet of
clients doesn't retry in lockstep and re-trigger the limit).
Bash / curl
#!/usr/bin/env bash
# Retry a Prezent API call on 429 with Retry-After + jittered backoff.
set -euo pipefail
URL="https://api.prezent.ai/api/v1/autogenerator"
MAX_ATTEMPTS=6
BASE=1 # base backoff in seconds
CAP=60 # max backoff in seconds
attempt=0
while :; do
attempt=$((attempt + 1))
# -s body to stdout, write the HTTP code + Retry-After to a side channel
resp="$(curl -s -w '\n%{http_code} %{header_json}' \
-X POST "$URL" \
-H "Authorization: Bearer $PREZENT_API_KEY" \
-H "Content-Type: application/json" \
-d '{"prompt":"A 5-slide deck on renewable energy","template_id":"tmpl-1"}')"
code="$(printf '%s' "$resp" | tail -n1 | awk '{print $1}')"
if [ "$code" != "429" ]; then
printf '%s\n' "$resp" | sed '$d' # print the body, drop the status line
exit 0
fi
if [ "$attempt" -ge "$MAX_ATTEMPTS" ]; then
echo "giving up after $attempt attempts" >&2
exit 1
fi
# Prefer the server's Retry-After if it sent one.
retry_after="$(printf '%s' "$resp" | tail -n1 \
| grep -oiE '"retry-after":[^,}]*[0-9]+' | grep -oE '[0-9]+' | head -n1 || true)"
if [ -n "${retry_after:-}" ]; then
sleep_for="$retry_after"
else
# Exponential backoff capped at $CAP, plus full jitter in [0, backoff].
backoff=$(( BASE * (2 ** (attempt - 1)) ))
[ "$backoff" -gt "$CAP" ] && backoff="$CAP"
sleep_for=$(( RANDOM % (backoff + 1) ))
fi
echo "429 received; sleeping ${sleep_for}s (attempt ${attempt})" >&2
sleep "$sleep_for"
done
Python
import random
import time
import requests
URL = "https://api.prezent.ai/api/v1/autogenerator"
MAX_ATTEMPTS = 6
BASE = 1.0 # base backoff in seconds
CAP = 60.0 # max backoff in seconds
def call_with_retry(api_key: str, payload: dict) -> dict:
headers = {
"Authorization": f"Bearer {api_key}",
"Content-Type": "application/json",
}
for attempt in range(1, MAX_ATTEMPTS + 1):
resp = requests.post(URL, headers=headers, json=payload)
if resp.status_code != 429:
resp.raise_for_status()
return resp.json()
if attempt == MAX_ATTEMPTS:
resp.raise_for_status() # surface the final 429
# Honour Retry-After if the server sent one...
retry_after = resp.headers.get("Retry-After")
if retry_after is not None:
delay = float(retry_after)
else:
# ...otherwise full-jitter exponential backoff.
backoff = min(CAP, BASE * (2 ** (attempt - 1)))
delay = random.uniform(0, backoff)
code = resp.json().get("error", {}).get("code", "RATE_LIMIT_EXCEEDED")
print(f"429 ({code}); sleeping {delay:.1f}s (attempt {attempt})")
time.sleep(delay)
raise RuntimeError("unreachable")
Best practices
- Honour
Retry-Afterfirst. When the header is present, wait at least that long. Only fall back to computed backoff when it is absent. - Use jitter. Full jitter (a random delay in
[0, backoff]) prevents a thundering herd of clients from retrying in lockstep. - Cap your backoff and your attempts. Unbounded retries turn a
transient
429into a stuck client. - Branch on
error.code, not just the status. AUSAGE_LIMIT_EXCEEDED(annual quota) will not clear by retrying — it needs a quota increase, not backoff. - Spread bulk work out. The gateway's
1,000 requests / daydefault is usually the first wall you hit; batch and pace large jobs.
See also
- Developer Guide → Usage quotas and rate limits — the same model in the context of the full cross-cutting guide.
- Developer Guide → Error codes — the complete stable error catalog.
- Getting Started — auth, base URL, and your first request.