Rate limits

Limits apply at several layers: request rate per account, concurrent generations per account, a system-wide capacity valve, and optional per-key spend caps. Every 429 response carries a Retry-After header — honoring it is the only back-off logic you need.

Limits at a glance

LayerDefault limitScopeOn exceed
Request rate120 requests / minute (fixed 60-second window)Per account (shared across all your keys); higher limits per contract429 ERR_RATE_LIMIT
Concurrent generations100 in flight (queued or processing)Per account; higher limits per contract429 ERR_QUOTA_EXCEEDED (scope: account)
Capacity valveSystem-wide daily capacityAll accounts, image models429 ERR_QUOTA_EXCEEDED (reason: pool_capacity_exceeded)
Poll interval1 poll / 5 secondsPer API key per creation429 ERR_RATE_LIMIT
Monthly spend capOptional USD amount, set per keyPer API key402 ERR_INSUFFICIENT_BALANCE (reason: monthly_budget_exceeded)

Request rate

Your account may make 120 requests per minute by default, measured over a fixed 60-second window. This budget is shared across all of your API keys — adding more keys does not raise it. Exceeding it returns 429 ERR_RATE_LIMIT with details carrying the limit, the window, and seconds until it resets. Higher limits are available per contract.

Concurrent generations

An account may have up to 100 generations in flight (queued or processing) at once by default. Exceeding it returns 429 ERR_QUOTA_EXCEEDED with details: {"limit": N, "scope": "account"}. Higher concurrency is available per contract.

Capacity valve

A system-wide daily capacity valve protects the image models under peak load. When it trips, requests return 429 ERR_QUOTA_EXCEEDED with details.reason = "pool_capacity_exceeded" and a short retry-after — these are transient.

Monthly spend cap

Keys can carry an optional monthly spend cap — a USD amount (e.g. a $50.00 monthly budget). A request that would exceed it returns 402 ERR_INSUFFICIENT_BALANCE with details.reason = "monthly_budget_exceeded" and the cap value in USD.

Polling interval

Status polling has its own limit: at most one poll every 5 seconds per creation per key — see Job status.

The 429 responses

All 429s use the standard error envelope and carry a Retry-After header (seconds). The details object identifies which limit tripped:

429 responses
# Account request rate
HTTP/1.1 429 Too Many Requests
Retry-After: 17

{"error": {"code": "ERR_RATE_LIMIT", "message": "…",
  "details": {"limit": 120, "window": "1m", "retry_after": 17}}}

# Account concurrency
HTTP/1.1 429 Too Many Requests
Retry-After: 2

{"error": {"code": "ERR_QUOTA_EXCEEDED", "message": "…",
  "details": {"limit": 100, "scope": "account"}}}

# Capacity valve
HTTP/1.1 429 Too Many Requests
Retry-After: 5

{"error": {"code": "ERR_QUOTA_EXCEEDED", "message": "…",
  "details": {"reason": "pool_capacity_exceeded", "pool": "…", "retry_after": 5}}}

Handling 429s

Read the Retry-After header, wait at least that many seconds (plus a little jitter so parallel workers don't retry in lockstep), and retry:

import random
import time

import requests

BASE = "https://api.rendergrid.io/api/public/v1"
HEADERS = {"Authorization": "Bearer rg_live_xxx"}


def post_with_backoff(path: str, payload: dict, max_attempts: int = 5):
    for attempt in range(max_attempts):
        resp = requests.post(f"{BASE}{path}", headers=HEADERS, json=payload)
        if resp.status_code != 429:
            return resp
        retry_after = int(resp.headers.get("Retry-After", "2"))
        time.sleep(retry_after + random.uniform(0, 1))  # jitter
    return resp

Combine retries with an idempotency key so retried generation requests are never double-charged.