15Reference
Limits
The request rate and the number of unfinished tasks are limited separately. Account limits are shared by all of its keys; task polling and the monthly budget have their own rules.
At a glance
These are the default values: they are the same for any account, including a newly registered account. Your account’s limit may be different: if the limit is not enough for you, contact support.
All limits
| Limit | Default | Response when exceeded |
|---|---|---|
| Request rate Account, shared by all keys | 120 requests per minute, in 60-second windows | 429 ERR_RATE_LIMIT |
| Concurrent generations Account | 100 tasks queued or in progress | 429 ERR_QUOTA_EXCEEDED, details.scope = "account" |
| Polling interval Key and task | No more often than once every 5 seconds | 429 ERR_RATE_LIMIT, details.min_interval_seconds |
| Reference image uploads A self-registered account that has not topped up yet; per UTC day | 100 reference images per day; after the first top-up there is no limit | 429 ERR_RATE_LIMIT, details.window = "1d" |
| Account monthly limit All keys together, calendar month in UTC | An optional amount in dollars; you set it in the portal, in the “Balance and top-ups” section | 402 ERR_INSUFFICIENT_BALANCE, details.reason = monthly_budget_exceeded |
| Key monthly limit One key, calendar month in UTC | An optional amount in dollars; you set it in the “API keys” section | 402 ERR_INSUFFICIENT_BALANCE, details.reason = monthly_budget_exceeded |
| Block on access errors The IP address the requests came from | 30 401 refusals or 30 403 refusals per minute (counted separately); a block of up to 120 seconds | 429 ERR_RATE_LIMIT, details.reason = burst_block |
Request rate
The request rate counts starting generations, uploading files, reading the model catalog, and reading the balance. Task polling is counted separately, under the rule below. Additional keys do not raise this count.
A response to an exceeded limit contains details.limit, details.window = "1m", and details.retry_after, and Retry-After in the header.
Concurrent generations
Tasks that are queued or in progress take a place in the account’s limit until they finish. If the response contains details.scope = "account", wait for the tasks to finish and send new ones less often.
For bulk submission, keep your own queue: start new tasks as places free up, not all at once.
Polling interval
Poll one task with one key no more often than once every 5 seconds. A poll that comes too early gets 429 with details.min_interval_seconds = 5. Wait at least the larger of that number and Retry-After: the header sometimes indicates a shorter interval.
Stop polling when the status becomes completed or failed. To avoid polling altogether, use webhooks; for how to get the result, see “Tasks and statuses.”
Reference image uploads
An account that signed up on its own and has never topped up its balance can use no more than 100 reference images per day, counted by the UTC calendar day. Both files uploaded through POST /uploads and addresses in image_urls count. The limit is checked before uploading, downloading, and charging: on a refusal the balance does not change.
After the first top-up there is no limit. Other accounts are not restricted by it.
When it is exceeded, 429 ERR_RATE_LIMIT arrives with details.limit, details.window = "1d", and details.retry_after, and Retry-After in the header. Top up your balance and the limit goes away; otherwise wait for the next day.
Monthly spending limits
A monthly limit is an optional cap on spending. By default there is none. You can set it in two ways, and they work together:
- Account limit—for all keys together. You set it in the portal, in the “Balance and top-ups” section.
- Key limit—for one key only. You set it in the “API keys” section.
Charges for generations within the UTC calendar month are counted; the refund for a failed image frees that amount. When either of the two limits is used up, the next generation request gets 402 ERR_INSUFFICIENT_BALANCE with details.reason = monthly_budget_exceeded and a numeric details.cap field in dollars. No money is charged.
It is a hard cap: until the month changes or you raise the limit, new generations are not accepted.
Block on access errors
Stop retrying on 401 and 403 and fix the cause: a wrong key or a disabled account. Repeated 401 responses can block API traffic from your IP address for a while; repeated 403 responses block the public API from that address, while the portal remains available.
Integrations behind a shared NAT share one IP address, and so share this block. On burst_block, wait for Retry-After and fix the cause of the refusals first.
Example responses
The header values here are examples: read them in each response. The format is the standard one, as in the “Error codes” section.
HTTP/1.1 429 Too Many Requests Retry-After: 17 { "error": { "code": "ERR_RATE_LIMIT", "message": "rate limit exceeded", "details": { "limit": 120, "window": "1m", "retry_after": 17 } } }
HTTP/1.1 429 Too Many Requests Retry-After: 2 { "error": { "code": "ERR_QUOTA_EXCEEDED", "message": "concurrency limit reached", "details": { "limit": 100, "scope": "account" } } }
HTTP/1.1 429 Too Many Requests Retry-After: 2 { "error": { "code": "ERR_RATE_LIMIT", "message": "polled too soon — retry after the minimum interval", "details": { "min_interval_seconds": 5 } } }
HTTP/1.1 429 Too Many Requests Retry-After: 41230 { "error": { "code": "ERR_RATE_LIMIT", "message": "Daily upload limit reached. Add funds to your account to lift it.", "details": { "limit": 100, "window": "1d", "retry_after": 41230 } } }
HTTP/1.1 429 Too Many Requests Retry-After: 120 { "error": { "code": "ERR_RATE_LIMIT", "message": "Too many errors. Please wait before retrying.", "details": { "reason": "burst_block", "retry_after": 120 } } }
Retries on 429
Wait at least Retry-After seconds and add a small random delay so that your processes do not repeat the request at the same moment. Limit the number of attempts and keep the request body, the API key, and the X-Idempotency-Key: repeating the same submission then will not create a second task.
While the first submission is still being processed, a retry with the same idempotency key can return details.reason = "idempotency_in_flight"—wait and ask again (learn more).
import os import random import time import requests API = "https://api.rendergrid.io/ api/ public/ v1" def submit_with_backoff(payload, idempotency_key, max_attempts=5): key = os.environ["RENDERGRID_API_KEY"] headers = { "Authorization": f"Bearer {key}", # the same one for all attempts "X-Idempotency-Key": idempotency_key, } for attempt in range(max_attempts): response = requests.post( f"{API}/images/generate", headers=headers, json=payload, timeout=(10, 120), ) if response.status_code != 429: return response try: body = response.json() except ValueError: body = {} details = body.get("error", {}).get("details", {}) last = attempt + 1 == max_attempts if details.get("reason") == "burst_block" or last: # a block and the last attempt go up to the caller return response wait = int(response.headers.get("Retry-After", "2")) # wait at least Retry-After time.sleep(max(0, wait) + random.uniform(0, 1))
// Node.js 22+, .mjs file const API = "https://api.rendergrid.io/ api/ public/ v1"; const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms)); async function submitWithBackoff(payload, idempotencyKey, maxAttempts = 5) { for (let attempt = 0; attempt < maxAttempts; attempt++) { const response = await fetch(`${API}/images/generate`, { method: "POST", headers: { Authorization: `Bearer ${process.env.RENDERGRID_API_KEY}`, "Content-Type": "application/json", // the same on every attempt "X-Idempotency-Key": idempotencyKey, }, body: JSON.stringify(payload), signal: AbortSignal.timeout(120_000), }); if (response.status !== 429) return response; const body = await response .clone() .json() .catch(() => ({})); const last = attempt + 1 === maxAttempts; if (body.error?.details?.reason === "burst_block" || last) { // a block and the last attempt are passed up return response; } const wait = Number(response.headers.get("Retry-After") ?? 2); const delay = Math.max(0, wait); // wait at least Retry-After await sleep((delay + Math.random()) * 1000); } }
The example retries only 429, hands every other response to your code, and stops on burst_block. If the connection dropped or the response was lost, keep the same key and check the known task before submitting again.
Frequently asked questions
Can the limit be raised?
Will additional keys raise the limit?
How should I poll a task to avoid a 429?
completed or failed. Better still, subscribe to a webhook: then you do not need to poll.What should I do if I get a 429 burst_block?
Retry-After.