Skip to main content

Quickstart

This guide walks through the calls an integration needs to make against the /v1 API: authenticating, running a synchronous prediction, submitting and polling an asynchronous job, and supplying the correct strand. The examples use the REST API with curl and Python; the MCP server exposes the same tasks as tools.

BASE_URL = https://api.genomicintelligence.ai
KEY = gi_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

If you don't have a key, email contact@genomicintelligence.ai.

1. Authenticate

Every /v1/* request carries a bearer key. /health, /docs, /redoc, and /v1/openapi.json are public.

curl -sS "$BASE_URL/health"
curl -sS -H "Authorization: Bearer $KEY" \
"$BASE_URL/v1/tasks/promoter/models" | jq .
HTTPerror.codeCause
401unauthorizedHeader missing/malformed or key not registered. The JSON error.code is the same for both; the WWW-Authenticate header splits the cause per RFC 6750 §3.1error="invalid_request" for a missing/malformed header, error="invalid_token" for an unrecognised key. Use this when debugging from curl -i; typed clients should still switch on error.code.
403forbiddenKey disabled.
429too_many_requestsPer-key concurrency cap reached. Honour Retry-After.

Full catalogue: reference/errors.md.

2. Synchronous prediction

The promoter, splice, enhancer, and chromatin tasks return results inline by default.

SEQUENCE=$(python3 -c 'print("ACGT"*500)') # 2 kb
curl -sS -X POST "$BASE_URL/v1/tasks/promoter/predict" \
-H "Authorization: Bearer $KEY" \
-H "Content-Type: application/json" \
-d "$(jq -nc --arg s "$SEQUENCE" '{sequence:$s, sequence_name:"chr1:1000-3000"}')" \
| jq '{regions: .data.regions, meta}'

Response shape:

{
"data": { "task": "promoter", "model": "g0-promoter-2000bp", "regions": [] },
"meta": {
"job_id": "550e8400-…",
"task": "promoter",
"model": "g0-promoter-2000bp",
"cold_start": false,
"model_load_time_ms": 0,
"inference_time_ms": 410,
"sequence_length": 2000,
"task_specific_counts": { "task": "promoter", "windows_processed": 1, "regions_found": 0 }
}
}

job_id also appears in the X-Job-Id response header. cold_start and model_load_time_ms report whether the model had to be loaded into GPU memory for this request, which is useful for diagnosing first-request latency.

import os, requests

resp = requests.post(
f"{os.environ['BASE_URL']}/v1/tasks/promoter/predict",
headers={"Authorization": f"Bearer {os.environ['KEY']}"},
json={"sequence": "ACGT" * 500, "sequence_name": "chr1:1000-3000"},
timeout=60,
)
resp.raise_for_status()
print(resp.json()["meta"]["inference_time_ms"], "ms")

Synchronous requests are short. If one fails on a transient network error, retry the same POST.

3. Asynchronous job

Long inputs opt into asynchronous processing with Prefer: respond-async. The submit returns a 202 with a job_id; poll the result with GET /v1/tasks/jobs/{job_id}.

Every successful response — synchronous 200, async-submit 202, completed-job 200 — uses the same {data, meta} envelope, so one client code path parses all three. HTTP status discriminates progress from terminal state.

Once you hold a job_id, read the result with GET. Do not re-POST to poll: a repeated POST issues a new job.

SEQUENCE=$(python3 -c 'print("ACGT"*30000)')
JOB_ID=$(curl -sS -X POST "$BASE_URL/v1/tasks/annotation/predict" \
-H "Authorization: Bearer $KEY" \
-H "Content-Type: application/json" \
-H "Prefer: respond-async" \
-d "$(jq -nc --arg s "$SEQUENCE" '{sequence:$s, sequence_name:"chr8:1-120000"}')" \
| jq -r .data.job_id)

# HTTP status is the discriminator: 202 → still running, 200 → done,
# 4xx/5xx → terminal failure.
while true; do
STATUS=$(curl -sS -o /tmp/job.json -w "%{http_code}" \
-H "Authorization: Bearer $KEY" \
"$BASE_URL/v1/tasks/jobs/$JOB_ID")
case "$STATUS" in
200) jq '.meta' /tmp/job.json; break ;;
202) sleep 2 ;;
*) jq '.error' /tmp/job.json >&2; exit 1 ;;
esac
done
import time, requests

submit = requests.post(
f"{BASE_URL}/v1/tasks/annotation/predict",
headers={
"Authorization": f"Bearer {KEY}",
"Prefer": "respond-async",
},
json={"sequence": "ACGT" * 30000, "sequence_name": "chr8:1-120000"},
)
submit.raise_for_status()
job_id = submit.json()["data"]["job_id"] # 202 envelope: {data, meta}

while True:
r = requests.get(
f"{BASE_URL}/v1/tasks/jobs/{job_id}",
headers={"Authorization": f"Bearer {KEY}"},
)
if r.status_code == 202:
time.sleep(2); continue
if not r.ok:
raise RuntimeError(r.json().get("error"))
data, meta = r.json()["data"], r.json()["meta"]
break

While a job is running, the 202 body from GET /v1/tasks/jobs/{job_id} is also {data, meta}; data.progress carries current_percent, message, and elapsed_seconds. No separate progress endpoint is needed.

Async submit response shape (status 202):

{
"data": {
"job_id": "550e8400-e29b-41d4-a716-446655440000",
"status": "accepted",
"links": { "result": "/v1/tasks/jobs/550e8400-…" }
},
"meta": { "job_id": "550e8400-…", "task": "annotation", "mode": "async" }
}

Jobs are owner-scoped: a job created by your client_id returns 404 for any other caller.

On a 429, honour the Retry-After header. RateLimit-* response headers carry the per-request bucket state. Per-key caps are listed in reference/limits.md.

Each request emits one structured server log line with request_id, client_id, and the request path; the request body is never logged, so sequences and predictions stay out of logs.

4. Get the strand right

This is the most common silent failure. Submit DNA 5'→3' on the gene's coding (sense) strand. For a minus-strand gene, that means the reverse complement of the plus-strand reference — fetch with the gene's strand (/sequence/region/...:-1, or Ensembl /sequence/id?type=genomic, which already returns gene-sense), not the raw plus strand.

This matters for the strand-sensitive tasks: promoter and expression (and enhancer for the DeepSTARR dev/hk channels). Feeding the wrong strand silently returns near-empty or baseline results. For example, TP53 on the plus strand finds 0 promoters; on the coding strand it finds its real promoters.

The annotation (gene-finding) task is the exception: it is plus-strand-oriented, so submit the plus-strand genomic region.

The bundled examples in client/sequences/ carry the correct orientation in their FASTA headers (strand:-1|gene-sense, RC(gene-sense), and so on).

5. Next steps

  • reference/errors.md — every error.code.
  • reference/limits.md — per-task length caps, per-key rate quotas, async TTL.
  • /v1/openapi.json — generate a typed client. Each request schema carries an example you can copy.
  • The MCP server exposes the same six tasks as tools for agent and tool-based integrations; see the MCP reference for setup.