# Agent integration guide

The Genomic Intelligence API runs six DNA sequence analysis tasks built on transformer language models. Each task is a single HTTP endpoint — `POST /v1/tasks/<task>/predict` — and one composite workflow chains two of them. The same tasks are also reachable over [MCP](mcp.md).

This page orients you to the REST API and points to the canonical guides and reference. For the step-by-step walkthrough, jump to the [REST API guide](rest-api.md).

Base URL: `https://api.genomicintelligence.ai`

## Overview

Every endpoint shares one contract:

- **Six tasks**, one published operation each: `POST /v1/tasks/{promoter,splice,enhancer,chromatin,annotation,expression}/predict`, plus the composite `POST /v1/workflows/find-genes-and-predict-expression`.
- **Per-task request bodies.** Each task has its own request schema, its own minimum `sequence` length, and its own **closed** `options` object — there is no shared `PredictRequest`. The URLs are unchanged; a typed client generated from an older document is not.
- **Bearer authentication** on every `/v1/*` route. Public routes (no key): `/health`, `/docs`, `/redoc`, `/v1/openapi.json`.
- **One success envelope**: `{data, meta}` on every 2xx and 202. `data` carries task-specific fields; `meta` is uniform across tasks. `GET /v1/tasks/{task}/models` is the exception — a flat `{task, default_model, models}` object.
- **One error envelope**: `{error: {code, message, request_id, details?}}`. `code` is a closed 21-value enum; treat an unlisted value as a generic failure rather than a parse error.
- **Synchronous by default, asynchronous on request.** `Prefer: respond-async` is a declared header parameter on all six predict operations and the composite: the call returns `202` with `{data: {job_id, status, links}, meta}`, and you poll `GET /v1/tasks/jobs/{job_id}` until it reaches a terminal state. Async is JSON-only.

### Length bounds per task

Minimums are per task, published as `minLength` on that task's request schema and enforced before any model loads; the maximum is 500,000 bp everywhere. Lengths are measured **after whitespace is stripped**.

| Task | `sequence` | Model window (`context_window_bp`) |
|---|---:|---:|
| `promoter` | 300–500,000 bp | 2,000 (default) · 300 on the `*-300bp` models |
| `splice` | 100–500,000 bp | 15,000 |
| `enhancer` | 50–500,000 bp | 249 |
| `chromatin` | 200–500,000 bp | 1,000 |
| `annotation` | 1,000–500,000 bp | — |
| `expression` | 9,198–500,000 bp, TSS ≥ 4,599 bp from each end | — (see `recommended_flank_bp`) |
| `find-genes-and-predict-expression` | 1,000–500,000 bp | — |

Being above the floor is **not** the same as being in regime: a sequence shorter than the selected model's `context_window_bp` (from `GET /v1/tasks/{task}/models`) is accepted and scored against a window padded out to that size, with nothing in the response flagging it. Longer than the context window is fine — the scanner steps a window at a time. Out-of-range lengths are `422 validation_failed`, **not** `413`; `413` means either the 16 MiB body cap (`payload_too_large`) or a synchronous-delivery cap (`sync_too_large`) — the composite's 50,000 bp or annotation's 200,000 bp, the only two in the contract. Annotation's applies to a **JSON** response only: `?format=bed` and `?format=gff3` are uncapped and stay synchronous, since async is JSON-only and a text format with `Prefer: respond-async` is a `400`.

The live OpenAPI document at `https://api.genomicintelligence.ai/v1/openapi.json` is the machine-readable source of truth. It carries a `oneOf` discriminator on `data.task`, so generated typed clients (Python pydantic, TypeScript zod) narrow the success payload correctly. `error.details` is an **undiscriminated** `anyOf` — narrow it on `error.code`, not by shape. A rendered view is at `https://api.genomicintelligence.ai/redoc`.

## Authentication

All `/v1/*` routes require an API key:

```http
Authorization: Bearer gi_...
```

Verify connectivity before integrating:

```bash
curl -sS https://api.genomicintelligence.ai/health
# {"status":"healthy","version":"YYYY.MM.DD.iter (commit)"}

curl -sS -H "Authorization: Bearer $GI_API_KEY" \
     https://api.genomicintelligence.ai/v1/tasks/promoter/models
# 200 with {task, default_model, models: [...]}
```

Model-listing endpoints return a flat object, not the `{data, meta}` envelope used by predict endpoints. Per-key rate caps, the `RateLimit-*` headers, and the concurrency limiter are documented in [Limits](reference/limits.md).

## Get started

The [REST API guide](rest-api.md) has the full walkthrough: the contract, the downloadable integration kit (a drop-in single-file `gi_client.py` depending only on `requests`), simple sync and async examples in Python, and self-contained recipes — promoters across a gene list, async annotation polling, rate-limit-aware retry, typed error handling.

### Using with an AI coding agent

If you are integrating from inside an AI coding agent, point it at this page and the [llms.txt](https://docs.genomicintelligence.ai/llms.txt) index so it works from the same contract. To have an agent inherit these rules across sessions, save this guide into your repo as `AGENTS.md` (or `CLAUDE.md`) — the raw Markdown is served at [docs.genomicintelligence.ai/AGENTS.md](https://docs.genomicintelligence.ai/AGENTS.md). If your agent runs in an MCP client such as Claude Desktop, Cursor, or Claude Code, the [MCP server](mcp.md) reaches the same six tasks with typed tools.

## The six tasks

Each task accepts a DNA `sequence` (and a `sequence_name` label) and returns task-specific fields under `data`. See the [Tasks](tasks.md) page for biology, task-specific options, and output fields; per-task length caps are in [Limits](reference/limits.md).

| Task | Endpoint | Output |
|---|---|---|
| [`promoter`](tasks.md#promoter) | `POST /v1/tasks/promoter/predict` | Promoter-region probabilities (binary classification). |
| [`splice`](tasks.md#splice) | `POST /v1/tasks/splice/predict` | Per-token acceptor and donor splice-site predictions. |
| [`enhancer`](tasks.md#enhancer) | `POST /v1/tasks/enhancer/predict` | Developmental and housekeeping enhancer-activity scores (regression). |
| [`chromatin`](tasks.md#chromatin) | `POST /v1/tasks/chromatin/predict` | Multi-label chromatin features grouped into tracks. |
| [`annotation`](tasks.md#annotation) | `POST /v1/tasks/annotation/predict` | Gene-finding transcript intervals (TSS/PolyA detection). |
| [`expression`](tasks.md#expression) | `POST /v1/tasks/expression/predict` | Predicted gene expression in log(TPM+1), from sequence plus an experimental description. |
| [composite](tasks.md#find-genes-and-predict-expression) | `POST /v1/workflows/find-genes-and-predict-expression` | Genes found in a region, each with a predicted expression — annotation and expression chained server-side. |

### `options` is typed and closed per task

Unknown keys are rejected with `422 validation_failed` (`type: "extra_forbidden"`, `loc: ["body","options","<key>"]`), never ignored. A model is selected with the **top-level `model` field**, not `options.model`.

| Task | `options` |
|---|---|
| `promoter` | `threshold` (0–1, default 0.5) |
| `splice` | `threshold` (0–1, default 0.5 — see the response-size cap under [Error handling](#error-handling)), `site_types` (subset of `["donor","acceptor"]`, default both) |
| `enhancer` | *no fields* |
| `chromatin` | `threshold` (0–1, default 0.5) |
| `annotation` | `shift_coordinates`, `reverse_complement` (default `true`) |
| `expression` | `description` — **required**, as is `options` itself |
| composite | `description` (required at runtime), `annotation_model`, `expression_model`, `shift_coordinates`; `options` itself is required |

**`expression` is the task with the most extra rules.** The rule is the same for every expression model: its `sequence` must be **at least 9,198 bp** (up to 500,000) with the TSS **at least 4,599 bp from each end** (shorter is a hard `422` — no padding, no truncation), and any submission that is not exactly 9,198 bp must also carry **`tss_index`**: the 0-based TSS offset into the **whitespace-stripped** sequence, bounded by `4599 <= tss_index <= len(sequence) - 4599`. There is no `tes_index`. Longer TSS-centred sequence is better, and the model list says how much; never hard-code a window, flank or model id. Read `GET /v1/tasks/expression/models` (public): use the model your user named, else `default_model`, and pass it as `model` on every call of a run; take `F` = the largest `bio_spec.recommended_flank_bp` among the listed models (one fetch serves any of them), never below 4,599 and with `2F` within `request_max_bp`; put the TSS at offset `F` of the gene-sense sequence and send `tss_index = F`. With `tss` the 0-based genomic TSS position, fetch `[tss − F, tss + F)` on the plus strand, or `(tss − F, tss + F]` and reverse-complement it on the minus strand; reverse-complementing the plus-strand interval leaves the TSS at `F − 1`, a legal index for the wrong window. The server cuts what the model reads around `tss_index` and echoes it at `meta.task_specific_counts.scored_window` (its width depends on the model) — check that it sits around your TSS, because a wrong-but-in-range index returns a confident `200` for the wrong region. A window dominated by `N` or low-complexity repeats can be refused with `422` (roughly 2 kb of real sequence upstream and 2.6 kb downstream for `g0-expression`; 3.3 kb and 2.6 kb for `g0-expression-8192`). The `tss_index` checks report at `loc: ["body"]`, never `body.tss_index`, so match on `error.code`. Details: [Tasks](tasks.md#expression) and [Limits](reference/limits.md#expression-sequence-length-and-tss_index).

**The composite** takes plus-strand genomic input, finds the genes itself and windows each TSS, so it needs no `tss_index` and its floor is 1,000 bp. It scores each gene with the API's default expression model, using as much flank around each TSS as the submitted region holds, so longer regions give better predictions. Above 50,000 bp it refuses synchronous delivery with `413 sync_too_large` — resend with `Prefer: respond-async`. (`POST /v1/tasks/annotation/predict` has the same behaviour above 200,000 bp; no other endpoint is capped.) `meta.task_specific_counts` is `{genes_found, genes_predicted, genes_skipped}` (they sum), with per-gene causes in `data.expression_predictions[].skip_reason`.

## Model selection

Each task has a default model and may offer alternatives. List them with `GET /v1/tasks/{task}/models` (public, no key needed; an unknown task is `404 not_found`); the response is a flat object with `default_model` and a `models` array, each carrying a `bio_spec` with `request_max_bp` (the enforced ceiling), `context_window_bp`, `trained_window_bp` and, for expression, `recommended_flank_bp`. Use `request_max_bp` as the cap. Older responses may also carry `max_seq_length_bp`; it is ambiguous — the request cap for every model except `g0-expression`, where it is instead the 9,198 bp trained window — so do not read it, and do not depend on it being present. If you do not name a model the task uses `default_model`; to pick one, pass its id in the **top-level `model` field** of the request body — `options.model` is a `422`. Beyond the default human models, `promoter` offers species-specific variants (Drosophila, yeast, Arabidopsis) and DNABERT k-mer variants exist for `promoter`, `enhancer`, and `chromatin`; `expression` offers more than one model, and `default_model` names the current default. The model-list endpoint is authoritative for what each task currently serves.

## Error handling

Every error uses the envelope `{error: {code, message, request_id, details?}}`. Switch on `error.code`, not on `error.message` — the code is the authoritative discriminator; the message is for human-readable logs and can change without notice. Map every code to one of three actions:

1. **Retryable transient** (`too_many_requests`, `rate_limited`, `model_loading`, `service_unavailable`, `timeout`, `insufficient_memory`): honor `Retry-After`; if absent, back off exponentially, capped at 30 s.
2. **Permanent client error** (other `4xx`): surface `error.message`; do not retry. `422` messages are deterministic and safe to echo.
3. **Server error** (`5xx` other than `503`): capture `request_id`, retry once at most, then escalate.

Read `error.details` defensively and **never branch on `loc`**: `validation_failed` details are *usually* the declared object (`{errors: [...]}`, each entry the FastAPI `{loc, msg, type}` shape), but the `loc` values themselves are not a stable contract — expression's `tss_index` checks report at `loc: ["body"]` rather than `body.tss_index`. One `validation_failed` has a different shape entirely: splice's response-size cap sends `{task, record_count, maximum_records, sequence_length, threshold}` with no `errors` key. Switch on `code`, and read `details.get("errors", [])`.

**The splice response-size cap is not in OpenAPI.** `POST /v1/tasks/splice/predict` returns `422 validation_failed` if the response would carry more than **20,000 sites** — reachable only by setting `options.threshold` very low on a long sequence, where the site count tracks length rather than biology. The remedy is a higher threshold, never a shorter sequence. If you are lowering the threshold to capture everything, use `1e-3` rather than `0`: equivalent within 4.6e-4, ~4x faster (serialisation, not inference, is the bottleneck), and under the cap. Details: [Limits](reference/limits.md#response-size-the-splice-site-cap).

`504 gateway_timeout` is the one response with no envelope (it comes from the edge proxy) — treat it as a signal to switch to async. The full code catalog, typed `details` shapes, and handling notes are in [Errors](reference/errors.md).

## Next steps

- [REST API guide](rest-api.md): the REST walkthrough, the downloadable `gi_client.py`, and runnable recipes.
- [Errors](reference/errors.md): the `error.code` catalog and handling strategy.
- [Limits](reference/limits.md): per-task caps, rate quotas, and async job TTL.
- [Support](resources/support.md): contact, bug reports, and production access.
- [MCP server](mcp.md): the six tasks over Model Context Protocol.
- [API reference (ReDoc)](https://api.genomicintelligence.ai/redoc): the full rendered OpenAPI.
- [OpenAPI schema](https://api.genomicintelligence.ai/v1/openapi.json): the live machine-readable contract.
