08API basics
Tasks and statuses
An image is made in the background. Once a request is accepted, you have a task ID: use it to ask for the status until completed or failed arrives, or wait for a webhook.
Get the status
/creations/{id}Use the id from the generation response. A task is visible only to the account that created it: someone else’s or an unknown id returns 404 ERR_NOT_FOUND. Save the id on your side.
CREATION_ID="5b0214ae-9e39-460a-820a-f6ae3e24400b" curl --fail-with-body --max-time 20 \ "https://api.rendergrid.io/ api/ public/ v1/ creations/ $CREATION_ID" \ -H "Authorization: Bearer $RENDERGRID_API_KEY"
import os import requests API = "https://api.rendergrid.io/ api/ public/ v1" creation_id = "5b0214ae-9e39-460a-820a-f6ae3e24400b" key = os.environ["RENDERGRID_API_KEY"] response = requests.get( f"{API}/creations/{creation_id}", headers={"Authorization": f"Bearer {key}"}, timeout=(5, 20), ) response.raise_for_status() print(response.json())
// Node.js 22+, .mjs file const API = "https://api.rendergrid.io/ api/ public/ v1"; const id = "5b0214ae-9e39-460a-820a-f6ae3e24400b"; const key = process.env.RENDERGRID_API_KEY; const response = await fetch(`${API}/creations/${id}`, { headers: { Authorization: `Bearer ${key}` }, signal: AbortSignal.timeout(20_000), }); if (!response.ok) { const body = await response.text(); throw new Error(`HTTP ${response.status}: ${body}`); } console.log(await response.json());
Statuses
| Status | Meaning | What to do |
|---|---|---|
queued | The request is accepted and waiting its turn | Wait and ask again later |
processing | The image is being generated | Wait and ask again later |
completed | Done | Download the files from the links in result_urls |
failed | The image could not be produced | Read failure and decide whether to change the request |
completed and failed are final: you can stop polling. A 200 response can carry status: "failed": it is the outcome of the task, not an error of the status request itself.
A failed request is free: the amount goes straight back to your balance.
What the response looks like
Until there is a result, the response has only id and status. The other fields appear at the end.
{
"id": "5b0214ae-9e39-460a-820a-f6ae3e24400b",
"status": "queued"
}
{
"id": "5b0214ae-9e39-460a-820a-f6ae3e24400b",
"status": "processing"
}There are no other fields yet. Ask again in 5 seconds or more.
{
"id": "5b0214ae-9e39-460a-820a-f6ae3e24400b",
"status": "completed",
"result_urls": ["https:// cdn.rendergrid.io/ images/ example/ result.jpg"]
}result_urls are ready-made links to the files. Use them as they are: do not build them from the id and do not rely on the file extension.
{
"id": "5b0214ae-9e39-460a-820a-f6ae3e24400b",
"status": "failed",
"error": "The image or prompt was rejected by the safety filter. Try a different image or rephrase the prompt.",
"failure": {
"type": "declined",
"code": "content_policy",
"retryable": false
}
}error is text to display. Build your logic on failure.type and failure.code: what they mean.
Try it yourself
This is a teaching diagram; it makes no requests to the API. Choose an outcome and click the button.
queuedRequest accepted. The task is waiting in the queue.
processingThe image is being generated.
completedDone: the response has result_urls.
Click “Send request” and we will show what GET /creations/ returns at each step.
How often to poll
One task with one key: no more often than once every 5 seconds. More frequent polling gets 429 ERR_RATE_LIMIT: wait at least details.min_interval_seconds and at least Retry-After.
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 }
}
}- Stop polling on
completedandfailed. - On a dropped connection,
429, and5xx, retry with a pause and respectRetry-After. - Limit the total waiting time. If it runs out, save the
idand continue later: your client’s timeout does not cancel the task. - If several processes poll, agree on a shared interval between them.
An example waiting loop. The waiting time in it is a setting of the example, not how long the model takes to work.
import os import time import requests BASE = "https://api.rendergrid.io/ api/ public/ v1" KEY = os.environ["RENDERGRID_API_KEY"] SESSION = requests.Session() SESSION.headers["Authorization"] = f"Bearer {KEY}" TIMEOUT = (5, 20) # seconds: connect, read def wait_for(creation_id: str, wait_seconds: int = 180) -> dict: url = f"{BASE}/creations/{creation_id}" deadline = time.monotonic() + wait_seconds while time.monotonic() < deadline: delay = 5.0 try: resp = SESSION.get(url, timeout=TIMEOUT) except (requests.Timeout, requests.ConnectionError): resp = None if resp is not None: if resp.status_code == 429 or resp.status_code >= 500: wait = resp.headers.get("Retry-After", "5") if wait.isascii() and wait.isdigit(): delay = max(5.0, int(wait)) else: resp.raise_for_status() creation = resp.json() if creation["status"] in ("completed", "failed"): return creation if delay >= deadline - time.monotonic(): break time.sleep(delay) # time ran out: the ID is saved, polling can continue later raise TimeoutError(f"Still unresolved: {creation_id}")
const BASE = "https://api.rendergrid.io/ api/ public/ v1"; const KEY = process.env.RENDERGRID_API_KEY; const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms)); async function waitFor(creationId, waitMs = 180_000) { const url = `${BASE}/creations/${creationId}`; const deadline = Date.now() + waitMs; while (Date.now() < deadline) { let delay = 5_000; let resp; try { const left = Math.min(20_000, deadline - Date.now()); resp = await fetch(url, { headers: { Authorization: `Bearer ${KEY}` }, signal: AbortSignal.timeout(Math.max(1, left)), }); } catch (error) { const network = error instanceof TypeError; if (!network && error.name !== "TimeoutError") throw error; } if (resp) { if (resp.status === 429 || resp.status >= 500) { const wait = resp.headers.get("Retry-After"); if (wait && /^[0-9]+$/.test(wait)) { delay = Math.max(delay, Number(wait) * 1_000); } } else { if (!resp.ok) { const text = await resp.text(); throw new Error(`HTTP ${resp.status}: ${text}`); } const creation = await resp.json(); if (["completed", "failed"].includes(creation.status)) { return creation; } } } if (delay >= deadline - Date.now()) break; await sleep(delay); } // time ran out: the ID is saved, so polling can continue later throw new Error(`Still unresolved: ${creationId}`); }
Result files
Files are kept for a day. Download the image to your own storage as soon as you get the link, and retry if a download fails temporarily. Polling does not extend the storage time.
The task record and the link in the response do not guarantee that the file is still available. The permanent storage is yours.
A webhook instead of polling
Pass webhook_url in the generation request—we will tell you ourselves when the task becomes completed or failed. Keep polling as a fallback: a notification can be delayed or not arrive.
Frequently asked questions
How long do I wait for a result?
id and continue later.What should I do if the task became failed?
failure.type and failure.code. The amount for such a task has been returned to your balance automatically. For a new attempt, send a new request with a new idempotency key.Why does my ID return 404?
id and make sure the key belongs to the same account.