# 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: wait` cap, `X-Refabric-Start-Timeout` range,
>   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):
>   its `inputSchema` at `GET /v1/tasks/{name}`.
> - **Page sizes** (`limit` default and maximum of every list): the OpenAPI spec (`openapi.json`).
> - **The request rate of your key:** the `X-RateLimit-*` headers ([Rate limits](https://docs.refabric.com/task-apis/limits#rate-limits)).
> - **Prices:** `GET /v1/pricing` ([Pricing](https://docs.refabric.com/task-apis/pricing)).

## Transport

`GET /v1/meta` answers, among the receiver facts, a `limits` object:

```json
{
  "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_bytes` answers `413 body_too_large` before the request is read.
- `Prefer: wait=N` is clipped to `prefer_wait.max_seconds`.
- `X-Refabric-Start-Timeout` outside `start_timeout` answers `422`.
- `idempotency_key.ttl_seconds: null` means 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_bytes` answers `413 file_too_large`; a file whose bytes are not one of
  `upload.media_types` answers `415 unsupported_media_type`. The type is read from the bytes.
- URLs are `https` only (`http://` answers `422 invalid_request` naming 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](https://docs.refabric.com/task-apis/files-and-media#reuse-pass-a-file-back)).
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](https://docs.refabric.com/task-apis/files-and-media#retention-and-deletion)).
- Download URL lifetime: none — urls are not signed and do not expire ([Files](https://docs.refabric.com/task-apis/files-and-media#download-urls)).

## 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:

```http
GET /v1/tasks/image.generate
```

```json
{ "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](https://docs.refabric.com/task-apis/limits#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](https://docs.refabric.com/task-apis/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](https://docs.refabric.com/task-apis/calling-tasks/asynchronous-jobs#start-deadline).

### Headers

Every response to a request made with an API key carries the state of the key's window:

```http
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

```json
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-After` seconds, then retry with the same `Idempotency-Key`.
- Prefer [webhooks](https://docs.refabric.com/task-apis/calling-tasks/webhooks) or `Prefer: wait=N` to tight polling loops. Poll a job no
  more than once every few seconds.
- Spread batch submissions instead of sending them in one burst.

```python
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")))
```
