---
title: "Annotate a raw DNA sequence then predict per-gene expression"
method: POST
path: "/v1/workflows/find-genes-and-predict-expression"
tags: ["Workflows"]
---

# Annotate a raw DNA sequence then predict per-gene expression

`POST /v1/workflows/find-genes-and-predict-expression`

## Request

- Base URL: `https://api.genomicintelligence.ai`
- URL: `https://api.genomicintelligence.ai/v1/workflows/find-genes-and-predict-expression`
- Auth: HTTP bearer

Composite workflow: annotation → TSS centring → expression prediction.

Use this instead of ``POST /v1/tasks/expression/predict`` when the input
is raw genomic sequence and the TSS positions are unknown:

1. Run gene finding to get transcript intervals with TSS positions.
2. Cut a window around each gene's TSS, in the gene's own orientation.
   The workflow takes as much flank on each side as the expression stage
   can use, limited by what the submitted sequence holds, so longer
   regions around genes give better expression predictions. Near an edge
   the window is shorter on that side (padded with ``N`` where the
   expression stage needs a fixed width) rather than dropping the gene;
   the direct expression route, by contrast, refuses to pad at all.
3. Score each window with the expression stage.

``options.description`` is required and is applied to every gene.

**Above 50,000 bp this endpoint requires ``Prefer: respond-async``.** A
synchronous request over that size is rejected with ``413
sync_too_large`` (``error.details`` carries ``{sequence_length,
threshold}``) because sync delivery would outlast the proxy timeout.
The threshold is published as a number in ``x-sync-limit-bp`` on this
operation — read it from there; this sentence is prose and a client
cannot pin it.

## Headers

- `Prefer` string, nullable: Set to ``respond-async`` to dispatch asynchronously: the server returns ``202`` with ``{data: {job_id, status, links}, meta}`` immediately and the result is polled from ``GET /v1/tasks/jobs/{job_id}``. Anything else (or omitting the header) delivers the result synchronously with ``200``. Async is JSON-only — combining it with a text ``format`` is a ``400``. Some endpoints *require* it above a size threshold and reject sync delivery with ``413 sync_too_large``.

## Request body

- FindGenesAndPredictExpressionRequest: Body for ``POST /v1/workflows/find-genes-and-predict-expression``. The floor is the annotation stage's — the workflow cannot find a gene in less sequence than the gene finder will look at.
  - `sequence` string, required: DNA sequence (A/C/G/T/N, case-insensitive). Whitespace (newlines, spaces, tabs) is stripped first, so a line-wrapped FASTA sequence body may be pasted verbatim; **length is measured against the whitespace-stripped string**, not the raw character count — so a line-wrapped body can satisfy the published ``minLength`` as a raw string and still be rejected once stripped. Count bases, not characters. 1,000–500,000 bp. The 1,000 bp floor applies to every annotation model — the annotation stage sets it — the workflow cannot find a gene in less sequence than the gene finder looks at. Requests outside the range are rejected with ``422 validation_failed`` before any model is loaded.
  - `sequence_name` string, nullable: Display name for the sequence (e.g., 'chr1:1000-5000').
  - `model` string, nullable: Model ID. If omitted, the task's default model is used. The valid ids and the default for this operation are published structurally as ``x-models`` on the operation itself — deliberately **not** as an ``enum`` here, so that adding a model cannot make a previously-generated client reject a valid id. ``GET /v1/tasks/{task}/models`` carries the same roster with full per-model specs.
  - `options` FindGenesAndPredictExpressionOptions, required
    - `description` string, required: Experimental context (cell type, assay, conditions) applied to every found gene. **Required** — the expression stage is conditioned on it.
    - `annotation_model` string, nullable
    - `expression_model` string, nullable
    - `shift_coordinates` string, nullable

## Response `200`

Both stages completed. ``data.expression_predictions`` has one record per gene the annotation stage found; a gene whose TSS-centred window would run past the end of the submitted sequence comes back with ``skipped: true`` and a ``skip_reason`` rather than failing the whole request, so ``genes_predicted + genes_skipped == genes_found``.

- FindGenesAndPredictExpressionResponse: Success envelope for ``POST /v1/workflows/find-genes-and-predict-expression``. Published in the partner OpenAPI document. Also returned by ``GET /v1/tasks/jobs/{job_id}`` when the polled job is a composite run, so that operation's ``200`` is a union of this and :class:`PredictResponse`.
  - `data` FindGenesAndPredictExpressionData, required: ``data`` shape for ``POST /v1/workflows/find-genes-and-predict-expression``.
    - `task` 'find_genes_and_predict_expression', required
    - `annotation_model` string, required
    - `expression_model` string, required
    - `input` FindGenesAndPredictExpressionInput, required: ``data.input`` for the composite workflow. No ``tss_index``: the composite windows each gene itself and reports the result per gene in ``data.expression_predictions[].tss_position``.
      - `sequence_name` string, required: The caller's label for the submitted sequence, echoed verbatim. Defaults to ``"sequence"`` when the request omitted it.
      - `description` string, required: The experimental context the expression stage was conditioned on, echoed from ``options.description``.
    - `summary` object, required: Aggregate stats: gene counts plus avg/min/max expression across non-skipped predictions. **Its keys are deliberately not part of this contract**: they are per-task statistics that may be added, renamed or removed without a contract revision, so consumers must not depend on them — branch on the typed fields (``meta`` and this payload's own declared keys) instead.
    - `annotation` object, required: Annotation stage's full ``data`` payload (same shape as ``AnnotationData``).
    - `expression_predictions` GeneExpressionPredictionData[], required: One record per gene found by annotation.
      - `gene_index` integer, required
      - `gene_name` string, required
      - `strand` '+' | '-', required
      - `tss_position` integer, required: TSS coordinate within the input sequence (bp).
      - `centered_sequence_length` integer, required
      - `expression` number, required: log(TPM + 1).
      - `expression_tpm` number, required
      - `skipped` boolean, required
      - `skip_reason` string, nullable: Present when skipped=True; missing otherwise.
  - `meta` FindGenesAndPredictExpressionMeta, required: ``meta`` shape for ``POST /v1/workflows/find-genes-and-predict-expression``. Inherits the spine fields from the simple-task ``Meta`` (job_id, cold_start, *_time_ms, sequence_length, task_specific_counts) but adds the composite-specific keys: per-stage model ids and per-stage timing blocks. ``model`` is the canonical concatenation ``"<annotation_model>+<expression_model>"`` — the same string the Web UI displays as the run identifier.
    - `job_id` string, required
    - `request_id` string, required: Correlation id for this request, mirrored on the X-Request-Id response header and identical to ``error.request_id`` on the failure envelope.
    - `task` 'find_genes_and_predict_expression', required: Workflow name. Present so ``meta.task`` is required on every success envelope, composite included, and always agrees with ``data.task``.
    - `model` string, required: Composite identifier: "<annotation_model>+<expression_model>". Use ``annotation_model`` / ``expression_model`` for the parts.
    - `cold_start` boolean, required: True if EITHER stage had to load its model for this request.
    - `model_load_time_ms` integer, required: Sum of per-stage load times (annotation + expression).
    - `inference_time_ms` integer, required: Sum of per-stage inference times.
    - `sequence_length` integer, required
    - `task_specific_counts` FindGenesAndPredictExpressionCounts, required: ``meta.task_specific_counts`` shape for the composite workflow. Invariant: ``genes_predicted + genes_skipped == genes_found``.
      - `genes_found` integer, required: Genes detected by the annotation stage on the input sequence.
      - `genes_predicted` integer, required: Genes whose expression was successfully predicted (TSS-centered window resolved + expression model returned a value).
      - `genes_skipped` integer, required: Genes annotation found but the expression stage skipped — usually because the TSS-centered window would extend past the input sequence. ``data.expression_predictions[].skip_reason`` carries the per-gene cause.
    - `annotation_model` string, required
    - `expression_model` string, required
    - `annotation` StageTimings, required: Per-stage timing sub-block on the composite ``meta``.
      - `cold_start` boolean, required: True if the stage's model had to be loaded for this request.
      - `model_load_time_ms` integer, required: Milliseconds spent loading.
      - `inference_time_ms` integer, required: Milliseconds spent running inference.
    - `expression` StageTimings, required: Per-stage timing sub-block on the composite ``meta``.
      - `cold_start` boolean, required: True if the stage's model had to be loaded for this request.
      - `model_load_time_ms` integer, required: Milliseconds spent loading.
      - `inference_time_ms` integer, required: Milliseconds spent running inference.

## Other responses

- `202`: Async submission accepted (``Prefer: respond-async``). **Required above this operation's ``x-sync-limit-bp``** (50,000 bp) — see the 413 response. Poll ``GET /v1/tasks/jobs/{job_id}``.
- `400`: Bad request — e.g. `Prefer: respond-async` combined with a text format.
- `401`: Missing or invalid bearer key (`unauthorized`).
- `403`: Key disabled or insufficient tier (`forbidden`).
- `404`: Unknown task, unknown job, or a job owned by another key (`not_found`).
- `409`: Conflict (`conflict`).
- `410`: Async job result expired out of the job store (`job_expired`).
- `413`: Either the 16 MiB request-body hard cap was exceeded (`payload_too_large`, enforced before parsing), or the body is over the endpoint's synchronous-delivery cap (`sync_too_large`; `error.details` carries `{sequence_length, threshold}` — retry the same request with `Prefer: respond-async`).
- `415`: The requested output format is not one this task can render (`unsupported_format`). The message lists the supported formats; the server never silently falls back to JSON.
- `422`: Validation failed (`validation_failed`) — sequence length outside the task's bounds, a non-ACGTN character, an unknown key under `options` (every options object is closed), or a sequence below the *selected task's published `minLength`/`maxLength` bounds.
- `429`: Rate or concurrency cap reached (`too_many_requests`). `Retry-After` and the `RateLimit-*` headers say when to retry.
- `500`: Server error (`internal_error`).
- `503`: Model still loading (`model_loading`), GPU memory exhausted (`insufficient_memory`), or service unavailable. `insufficient_memory` means the card OOM-ed on this request and its cache has been released — it is transient GPU pressure, not a server bug, so `Retry-After` is honest and a bounded retry is the right response.

---

[API](https://skmtc.dev/genomicintelligence/apis/genomic-intelligence-api.md) · [All operations](https://skmtc.dev/genomicintelligence/apis/genomic-intelligence-api/llms.txt) · [OpenAPI document](https://skmtc.dev/genomicintelligence/apis/genomic-intelligence-api/revisions/72436b86d4fe?raw)
