# Limits

This page is the single owner of the API's numeric caps and rate quotas. For per-endpoint request/response schemas and the `error.code` list, see [ReDoc](https://api.genomicintelligence.ai/redoc); for how to handle each error, see [Errors](./errors.md).

## Input length per task

All six per-task `POST /v1/tasks/{task}/predict` endpoints accept **1–500,000 bp**. Sequences must contain only `A/C/G/T/N` (case-insensitive); oversized, undersized, or out-of-alphabet inputs are rejected with `422 validation_failed` before any GPU work. Request body cap: **16 MiB**.

The expression model uses a fixed **9,198 bp TSS-centered window (±4,599 bp)**. Off-length sequences are accepted (the tokenizer truncates or pads to this window), but predictions on non-TSS-centered or off-length input are not guaranteed to be biologically meaningful. If you have raw genomic input where the TSS isn't pre-known, [server-side annotation→expression chaining](../resources/support.md#server-side-task-chaining) can be enabled per account.

## Latency

Rough sync latency at the recommended input size, on a warm model.

| Task | Sync latency at recommended size | When to go async |
|---|---|---|
| Promoter / splice / enhancer / chromatin | 0.3-10 s | inputs above 100,000 bp |
| Annotation | 1-60 s | inputs above 30,000 bp |
| Expression (fixed 9,198 bp window) | 0.5-3 s | n/a (sync is always safe) |

### Cold start

If a task's model isn't already loaded into GPU memory, the first request
pays a model-load cost. The response carries `meta.cold_start: true` and
`meta.model_load_time_ms`. Cold start adds 5-15 s for the smaller models
and 30-90 s for `expression` and `annotation`. Subsequent calls are warm.

## Sync delivery: timeout and guidance

Sync delivery (the default, no `Prefer` header) is best-effort within the
upstream HTTP read timeout of 300 seconds. A request that takes longer than
that is terminated by the edge proxy and surfaces to your client as a
connection reset or `504 gateway_timeout`. The body in this case is the
proxy's, not the unified `{error: {...}}` envelope; this is the only place
where that happens. Pick async whenever you expect a request to push past
~60 s.

### Hard sync cap

The six per-task `POST /v1/tasks/{task}/predict` endpoints enforce no hard sync
cap: each accepts sync up to its per-task max (see "Input length per task"
above). The one exception is the composite **find-genes-and-predict-expression**
workflow (`POST /v1/workflows/find-genes-and-predict-expression`): submitted
synchronously above **50,000 bp** it returns [`413 sync_too_large`](./errors.md).
Submit it with `Prefer: respond-async` (as the MCP server's
`find_genes_and_predict_expression` tool always does) to lift that cap. No
other endpoint emits `sync_too_large`.

### Recommended async opt-in (client-side guidance, not enforced)

Use `Prefer: respond-async` when your input exceeds the threshold below.
These are calibrated against typical inference times. Sync still works
under them, but bursty traffic plus GPU contention can push individual
requests past the 300 s proxy window without warning.

| Task | Recommended async above |
|---|---:|
| Promoter | 100,000 bp |
| Splice | 250,000 bp |
| Enhancer | 100,000 bp |
| Chromatin | 100,000 bp |
| Annotation | 30,000 bp |
| Expression (TSS-centered) | n/a (input is the fixed 9,198 bp window; sync is always safe). |

If sync is critical for your workload and these guidelines push more traffic
to async than you'd like, contact us; we can profile your distribution and
tune the proxy timeout for your account.

## Per-key quotas

Three limiters run side by side. Each is configured per partner; your
account owner tells you the values issued for your key. Defaults for the
partner tier:

| Setting | Default for partner tier | Enforced? |
|---|---:|---|
| Concurrent in-flight requests | 2 | Yes. Exceeding the cap returns `429 too_many_requests` with `Retry-After: 1`. |
| Per-minute request rate | 60 | Yes. Token bucket; capacity = `rate / 6` (10-second burst, default 10). An empty bucket returns `429 too_many_requests` with `Retry-After` set to approximately the seconds until the next token. |
| Edge per-IP cap | 10 r/s burst 20 on `api.*` | Yes. At the edge, returns `429 too_many_requests` with the unified `{error: {...}}` envelope (see [errors](./errors.md)). |

### `RateLimit-*` headers (every authenticated response)

The application emits the IETF
[httpapi-ratelimit-headers](https://datatracker.ietf.org/doc/draft-ietf-httpapi-ratelimit-headers/)
draft set on every authenticated `2xx` and on every `429` from this
service, so you can pace from header state without inferring rate from
`429`s:

| Header | Meaning |
|---|---|
| `RateLimit-Limit` | Token-bucket capacity (= `rate_per_minute / 6`). |
| `RateLimit-Remaining` | Tokens left after this request, integer. |
| `RateLimit-Reset` | Seconds until the bucket refills to full. |
| `RateLimit-Policy` | `<capacity>;w=60`, capacity per 60-second window. |
| `Retry-After` | On `429` only, seconds until at least one token is available. |

### Pacing guidance

- Pace at ~80% of your issued `RateLimit-Limit` to leave headroom for
  bursts; serialize a small worker pool against your concurrency cap rather
  than firing N parallel requests at it.
- The token bucket and concurrency semaphore are independent: a 429 can
  come from either. Both carry the same `RateLimit-*` spine, but only the
  rate-bucket 429 has a `Retry-After` calibrated against refill time; the
  concurrency 429 uses `Retry-After: 1`.

## Async result store

Async job results are retained for **24 h** from last activity, then expire.
A fetch after the TTL (or after a service restart) returns `410 job_expired`,
so retrieve results promptly and re-submit if a job has expired.

---

Back to the workflow: [REST API guide](../getting-started.md) · [Tasks](../tasks.md).
