For AI agents: this page is also available as Markdown at https://docs.refabric.com/task-apis/limits.md, and the index of every page is https://docs.refabric.com/llms.txt.
Task APIs
Limits
Where to read every limit the API enforces — request and upload sizes, a task's field limits and your key's request rate — live from the API.
Every limit below is read live from the API, so this page always shows the values in force.
- Transport limits (request body size,
Prefer: waitcap,X-Refabric-Start-Timeoutrange, upload size and file types, idempotency-key retention, url fetch timeouts / size / redirects):GET /v1/meta→limits. - A task's own field limits (
maxItems,maxLength,minimum/maximum, defaults): itsinputSchemaatGET /v1/tasks/{name}. - Page sizes (
limitdefault and maximum of every list): the OpenAPI spec (openapi.json). - The request rate of your key: the
X-RateLimit-*headers (Rate limits). - Prices:
GET /v1/pricing(Pricing).
Transport
GET /v1/meta answers, among the receiver facts, a limits object:
{
"limits": {
"request": { "max_body_bytes": <n> },
"prefer_wait": { "max_seconds": <n> },
"start_timeout": { "min_seconds": <n>, "max_seconds": <n> },
"upload": { "max_bytes": <n>, "media_types": ["<media type>", "…"] },
"idempotency_key": { "ttl_seconds": <n or null> },
"url_fetch": { "connect_timeout_seconds": <n>, "read_timeout_seconds": <n>, "max_bytes": <n>, "max_redirects": <n> }
}
}- A body over
request.max_body_bytesanswers413 body_too_largebefore the request is read. Prefer: wait=Nis clipped toprefer_wait.max_seconds.X-Refabric-Start-Timeoutoutsidestart_timeoutanswers422.idempotency_key.ttl_seconds: nullmeans a key is kept as long as the job it started.
A submit answers at once and the work runs as a job.
Uploads (POST /v1/files)
- A file over
upload.max_bytesanswers413 file_too_large; a file whose bytes are not one ofupload.media_typesanswers415 unsupported_media_type. The type is read from the bytes. - URLs are
httpsonly (http://answers422 invalid_requestnaming the field); an address that resolves to a private, loopback, link-local or metadata range is refused (image_not_public).
Images given by URL
An image url you send to a task is fetched from our servers when the job runs (a url of another
site with its own query string is fetched when you submit, and kept as your file: — Files).
Media urls are https only. The fetch's timeouts, size cap and redirect count are
limits.url_fetch; each redirect hop is checked to be a public address.
Files
- Retention: files are kept until you delete them (Files).
- Download URL lifetime: none — urls are not signed and do not expire (Files).
Per task
Every count, length and range a task accepts is in its inputSchema (GET /v1/tasks/{name}).
For example, to read how many references image.generate takes:
GET /v1/tasks/image.generate{ "inputSchema": { "properties": { "references": { "type": "array", "maxItems": <n> } } } }A request over a bound answers 422 naming the field; a shoot that would make more images than
its task allows answers 422 too_many_outputs.
Account
The per-key request rate: Rate limits. There is no per-account concurrency limit.
Rate limits
One limit applies: a request rate per API key.
| Limit | Counts | Scope |
|---|---|---|
| request rate | requests to the API in a fixed window — the allowance is X-RateLimit-Limit | per API key |
Every request made with the key counts, reads and submits alike. The window is fixed: it starts
again when X-RateLimit-Reset says. Read the allowance from the headers, never from this page.
There is no per-account concurrency limit: no request is refused for how many of your jobs
are running; jobs wait in the queue. What bounds a key besides its rate is its credit balance
— every submit holds its credits before the job starts (Pricing), and a submit the
balance does not cover answers 402 insufficient_credits. A moodboard.create job that cannot get
capacity in time fails with capacity_busy (retryable).
If you set X-Refabric-Start-Timeout, a job that cannot start in time fails with start_timeout
instead of starting late — see Jobs.
Headers
Every response to a request made with an API key carries the state of the key's window:
X-RateLimit-Limit: <limit>
X-RateLimit-Remaining: <remaining>
X-RateLimit-Reset: <seconds>| Header | Meaning |
|---|---|
X-RateLimit-Limit | requests allowed in the current window |
X-RateLimit-Remaining | requests left in the window |
X-RateLimit-Reset | seconds until the window resets |
Retry-After | on 429: seconds until the window resets |
When you hit a limit
HTTP 429
Retry-After: <seconds>
{ "error": { "code": "rate_limited", "type": "rate_limited",
"message": "rate limit exceeded for this API key",
"field": null, "retryable": true, "request_id": "req_…" } }- Wait
Retry-Afterseconds, then retry with the sameIdempotency-Key. - Prefer webhooks or
Prefer: wait=Nto tight polling loops. Poll a job no more than once every few seconds. - Spread batch submissions instead of sending them in one burst.
import time
def submit(session, url, body, key):
while True:
r = session.post(url, json=body, headers={"Idempotency-Key": key})
if r.status_code != 429:
return r
time.sleep(float(r.headers.get("Retry-After", "5")))