---
title: "Predict gene expression from the sequence around a TSS"
method: POST
path: "/v1/tasks/expression/predict"
tags: ["Tasks"]
---

# Predict gene expression from the sequence around a TSS

`POST /v1/tasks/expression/predict`

## Request

- Base URL: `https://api.genomicintelligence.ai`
- URL: `https://api.genomicintelligence.ai/v1/tasks/expression/predict`
- Auth: HTTP bearer

Predict expression for one gene from the sequence around its transcription start site (TSS), in ``log(TPM + 1)``.

Submit at least 9,198 bp with the TSS at least 4,599 bp from each end, and a ``tss_index`` (0-based, into the whitespace-stripped sequence) unless the submission is exactly 9,198 bp. Longer input is welcome. How far each model reads is its own: ``bio_spec.recommended_flank_bp`` (``GET /v1/tasks/expression/models``) is how many bp to fetch on each side of the TSS for that model. The response reports in ``meta.task_specific_counts.scored_window`` which part of the submission the model used (see that field). A TSS closer than 4,599 bp to either end is rejected with 422 rather than padded or truncated, and so is a window too N-rich, repeat-rich or low-complexity for the model to score.

**Strand-sensitive.** This endpoint does **not** discover the TSS for you and does **not** reverse-complement: submit the gene's coding (sense) strand, 5'→3'. A minus-strand window is accepted and scored like any other, so the wrong strand returns a confident number rather than an error. If you have raw genomic input where the TSS is not known, use ``POST /v1/workflows/find-genes-and-predict-expression`` instead — it finds the genes and centres each window itself.

``options.description`` is **required** — the model is conditioned on the experimental context (assay, biosample).

## Query parameters

- `format` string, nullable: Output format. ``expression`` renders JSON only; any other value is a ``415 unsupported_format`` (never a silent fallback).

## Headers

- `accept` string, nullable
- `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

- ExpressionPredictRequest: Envelope for ``POST /v1/tasks/expression/predict``. Expression is the one task whose models read the sequence around a TSS rather than the whole input, and how far each model reads is its own: ``bio_spec.recommended_flank_bp`` says how many bp to fetch on each side of the TSS for that model. Input used to be silently truncated or padded to fit, which produced confident but wrong numbers. The contract now makes the TSS explicit: the caller says where it is and the *server* cuts the model's slice around it. This is a literal route rather than the templated ``/{task}/predict`` one precisely so the published OpenAPI document can carry the expression-specific ``minLength``/``maxLength`` and the typed ``options`` object — an operation keyed on a path *parameter* cannot vary its request schema per value.
  - `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; **all lengths and ``tss_index`` are measured against the whitespace-stripped string**. At least 9,198 bp, with the TSS at least 4,599 bp from each end (see ``tss_index``). Longer input is welcome: fetch the selected model's ``bio_spec.recommended_flank_bp`` bp on each side of the TSS. The server cuts that model's slice around ``tss_index`` before inference.
  - `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` ExpressionOptions, required
    - `description` string, required: Experimental context (cell type, assay, conditions). **Required** — the model is conditioned on it, e.g. "assay term name is polyA plus RNA-seq. biosample summary is Homo sapiens K562."
  - `tss_index` integer, nullable: 0-based offset of the transcription start site **into the whitespace-stripped ``sequence``** (i.e. count nucleotides, not file characters — a line-wrapped FASTA would otherwise be off by its newline count). The response reports in ``meta.task_specific_counts.scored_window`` which part of the submission the selected model used around it (see that field). Required unless ``sequence`` is exactly 9,198 bp, in which case it defaults to 4599 (the only legal value). Must satisfy 4599 <= tss_index <= len(sequence) - 4599, i.e. at least 4,599 bp on each side of the TSS; out-of-range is a 422 rather than an N-padded guess.

## Response `200`

Successful inference. ``data`` is a discriminated union keyed on ``task`` — narrow on ``data.task`` (or ``meta.task``) to reach task-specific fields; here it is always ``"expression"``. ``meta.task_specific_counts`` is the matching counts variant.

- PredictResponse: Success envelope for ``POST /v1/tasks/{task}/predict``.
  - `data` union, required
    - PromoterData: ``data`` shape for ``POST /v1/tasks/promoter/predict``.
      - `model` string, required: Model id that produced this result.
      - `input` PromoterInput, required: ``data.input`` for ``POST /v1/tasks/promoter/predict``.
        - `sequence_name` string, required: The caller's label for the submitted sequence, echoed verbatim. Defaults to ``"sequence"`` when the request omitted it.
      - `summary` object, required: Task-specific summary statistics. **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.
      - `formats` object, nullable: Optional per-format pre-rendered text payloads (BED/GFF3/…). In every GFF3 this service generates, column 2 (``source``) is the id of the model that produced the feature — the same value as ``model`` above — so a saved track records its own provenance. The one exception is a structure-aware model whose upstream pipeline emits its own canonical GFF3 (annotation's ``g0-annotation``): that document is passed through verbatim and keeps its producer's source value, which names a multi-stage pipeline rather than one model.
      - `task` 'promoter', required
      - `regions` object[], required: Detected promoter regions (start/end/score).
      - `window_details` object[], required: Per-window scoring detail (one entry per scanned window).
    - SpliceData: ``data`` shape for ``POST /v1/tasks/splice/predict``.
      - `model` string, required: Model id that produced this result.
      - `input` SpliceInput, required: ``data.input`` for ``POST /v1/tasks/splice/predict``.
        - `sequence_name` string, required: The caller's label for the submitted sequence, echoed verbatim. Defaults to ``"sequence"`` when the request omitted it.
      - `summary` object, required: Task-specific summary statistics. **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.
      - `formats` object, nullable: Optional per-format pre-rendered text payloads (BED/GFF3/…). In every GFF3 this service generates, column 2 (``source``) is the id of the model that produced the feature — the same value as ``model`` above — so a saved track records its own provenance. The one exception is a structure-aware model whose upstream pipeline emits its own canonical GFF3 (annotation's ``g0-annotation``): that document is passed through verbatim and keeps its producer's source value, which names a multi-stage pipeline rather than one model.
      - `task` 'splice', required
      - `sites` object[], required: Detected splice sites (donor / acceptor). **``start``/``end`` bound the scored BPE token, not the junction.** The model reads the sequence as byte-pair-encoded tokens and scores whole tokens, so each site's span is that token's character range — typically 4–8 bp, and varying site to site because token lengths do — while a donor or acceptor junction is a single base. ``token_index`` names the token. Treating the span as the feature's extent, or intersecting it with a base-resolution reference annotation, will be quietly wrong at that scale. The same spans are what ``?format=gff3`` writes into columns 4–5, where they outlive the response that produced them. No base-resolution position is published: deriving one from a token score would be a modelling claim the service cannot currently defend, and an invented precision in a saved file is worse than an honest span (GH#58).
      - `tracks` object, required: Per-position score tracks for visualization.
      - `window_details` object[], required: Per-window scoring detail.
    - EnhancerData: ``data`` shape for ``POST /v1/tasks/enhancer/predict``.
      - `model` string, required: Model id that produced this result.
      - `input` EnhancerInput, required: ``data.input`` for ``POST /v1/tasks/enhancer/predict``.
        - `sequence_name` string, required: The caller's label for the submitted sequence, echoed verbatim. Defaults to ``"sequence"`` when the request omitted it.
      - `summary` object, required: Task-specific summary statistics. **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.
      - `formats` object, nullable: Optional per-format pre-rendered text payloads (BED/GFF3/…). In every GFF3 this service generates, column 2 (``source``) is the id of the model that produced the feature — the same value as ``model`` above — so a saved track records its own provenance. The one exception is a structure-aware model whose upstream pipeline emits its own canonical GFF3 (annotation's ``g0-annotation``): that document is passed through verbatim and keeps its producer's source value, which names a multi-stage pipeline rather than one model.
      - `task` 'enhancer', required
      - `windows` object[], required: Per-window enhancer scores.
      - `tracks` object, required: Per-position score tracks for visualization.
    - ChromatinData: ``data`` shape for ``POST /v1/tasks/chromatin/predict``.
      - `model` string, required: Model id that produced this result.
      - `input` ChromatinInput, required: ``data.input`` for ``POST /v1/tasks/chromatin/predict``.
        - `sequence_name` string, required: The caller's label for the submitted sequence, echoed verbatim. Defaults to ``"sequence"`` when the request omitted it.
      - `summary` object, required: Task-specific summary statistics. **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.
      - `formats` object, nullable: Optional per-format pre-rendered text payloads (BED/GFF3/…). In every GFF3 this service generates, column 2 (``source``) is the id of the model that produced the feature — the same value as ``model`` above — so a saved track records its own provenance. The one exception is a structure-aware model whose upstream pipeline emits its own canonical GFF3 (annotation's ``g0-annotation``): that document is passed through verbatim and keeps its producer's source value, which names a multi-stage pipeline rather than one model.
      - `task` 'chromatin', required
      - `windows` object[], required: Per-window chromatin annotations.
      - `tracks` object, required: Per-position score tracks for visualization.
    - AnnotationData: ``data`` shape for ``POST /v1/tasks/annotation/predict``.
      - `model` string, required: Model id that produced this result.
      - `input` AnnotationInput, required: ``data.input`` for ``POST /v1/tasks/annotation/predict``.
        - `sequence_name` string, required: The caller's label for the submitted sequence, echoed verbatim. Defaults to ``"sequence"`` when the request omitted it.
      - `summary` object, required: Task-specific summary statistics. **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.
      - `formats` object, nullable: Optional per-format pre-rendered text payloads (BED/GFF3/…). In every GFF3 this service generates, column 2 (``source``) is the id of the model that produced the feature — the same value as ``model`` above — so a saved track records its own provenance. The one exception is a structure-aware model whose upstream pipeline emits its own canonical GFF3 (annotation's ``g0-annotation``): that document is passed through verbatim and keeps its producer's source value, which names a multi-stage pipeline rather than one model.
      - `task` 'annotation', required
      - `transcripts` object[], required: Detected transcripts. Every transcript carries gene boundaries (``start``/``end``/``strand``/``score``) plus ``tss_position`` and ``polya_position``. Structure-aware models (g0-annotation) additionally populate ``transcript_type`` (mRNA/lnc_RNA), ``transcript_type_score`` and 0-based half-open ``exons``/``introns``/``cds`` arrays. The ``formats`` object exposes ``bed`` and, for structure-aware models, a full ``gff3`` track (also available via ``Accept: text/x-gff3``).
    - ExpressionData: ``data`` shape for ``POST /v1/tasks/expression/predict``. ``input`` echoes the request only: ``sequence_name``, ``description`` and the ``tss_index`` the server applied. The windowing provenance the server derived from that offset is in ``meta`` — which part of the submission the model used is ``meta.task_specific_counts.scored_window`` (see that field), and the submitted length is ``meta.sequence_length``.
      - `model` string, required: Model id that produced this result.
      - `input` ExpressionInput, required: ``data.input`` for ``POST /v1/tasks/expression/predict``. Three echoes, all of them things the caller sent or the server chose on the caller's behalf. ``tss_index`` is the second kind: it is optional on the request and defaults to 4599 when the submission is exactly 9,198 bp, so the echo is the only statement of which offset was actually applied. What is **not** here, deliberately: ``scored_window`` (read ``meta.task_specific_counts.scored_window``) and ``submitted_sequence_length`` (read ``meta.sequence_length``). Both were derived twins of a ``meta`` field.
        - `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 prediction was conditioned on (cell type / assay), echoed from ``options.description``.
        - `tss_index` integer, required: The 0-based TSS offset the server applied, into the whitespace-stripped ``sequence`` that was submitted. Echoes the request's ``tss_index``, or the 4599 implied by a submission of exactly 9198 bp. The window it produced is ``meta.task_specific_counts.scored_window``.
      - `summary` object, required: Task-specific summary statistics. **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.
      - `formats` object, nullable: Optional per-format pre-rendered text payloads (BED/GFF3/…). In every GFF3 this service generates, column 2 (``source``) is the id of the model that produced the feature — the same value as ``model`` above — so a saved track records its own provenance. The one exception is a structure-aware model whose upstream pipeline emits its own canonical GFF3 (annotation's ``g0-annotation``): that document is passed through verbatim and keeps its producer's source value, which names a multi-stage pipeline rather than one model.
      - `task` 'expression', required
      - `prediction` object, required: Expression prediction record: ``{expression, expression_log_tpm, expression_tpm, unit}``.
  - `meta` Meta, required: Metadata envelope shared by every successful predict response.
    - `job_id` string, required: Server-generated UUIDv4. Mirrored on the X-Job-Id response header.
    - `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. Quote it in support requests: it is the one handle that identifies a call whether it succeeded or failed.
    - `task` string, required: Task name (matches the {task} URL segment). Use this — or ``data.task`` — to narrow the typed unions.
    - `model` string, required: Model id that ran this inference.
    - `cold_start` boolean, required: True if the model had to be loaded into GPU memory for this request. Useful for diagnosing first-request latency.
    - `model_load_time_ms` integer, required: Milliseconds spent loading the model (0 when cold_start is False).
    - `inference_time_ms` integer, required: Milliseconds spent running inference.
    - `sequence_length` integer, required: Length of the submitted sequence (bp).
    - `task_specific_counts` union, required: Discriminated counts payload (see per-task ``*Counts`` models).
      - PromoterCounts
        - `task` 'promoter', required
        - `windows_processed` integer, required: Number of sliding windows scored.
        - `regions_found` integer, required: Detected promoter regions in this call.
      - SpliceCounts
        - `task` 'splice', required
        - `windows_processed` integer, required
        - `sites_found` integer, required: Detected splice sites (donor + acceptor).
      - EnhancerCounts
        - `task` 'enhancer', required
        - `windows_processed` integer, required
      - ChromatinCounts
        - `task` 'chromatin', required
        - `windows_processed` integer, required
        - `total_annotations` integer, required: Sum of per-track annotation counts across all windows.
      - AnnotationCounts
        - `task` 'annotation', required
        - `transcripts_found` integer, required
      - ExpressionCounts: Expression has no per-call counts — the prediction is a single value. What it carries instead is *windowing provenance*: an expression model reads a stretch of sequence around the TSS (how much depends on the model and on the sequence), while a caller may submit up to 500,000 bp. These fields report which part of the submission the model used (see ``scored_window``), so the contract is verifiable from the response alone rather than taken on trust. Always populated by ``POST /v1/tasks/expression/predict``.
        - `task` 'expression', required
        - `tss_index` integer, nullable: 0-based TSS offset the server applied, into the whitespace-stripped ``sequence`` that was submitted. Echoes the request's ``tss_index``, or the implied 4599 when the submission was exactly 9198 bp.
        - `scored_window` integer[], nullable: ``[start, end]`` — the half-open slice of the submitted, whitespace-stripped sequence the server cut for the model. For ``g0-expression`` it is the 9198 bp slice ``[tss_index-4599, tss_index+4599]``, of which the model reads the central part (roughly 3 kb on each side of the TSS, depending on sequence). For a model whose ``bio_spec.trained_window_bp`` is null it is the span actually read, so its width shows how much flank was used.

## Other responses

- `202`: Async submission accepted — returned only when ``Prefer: respond-async`` was sent. Poll ``GET /v1/tasks/jobs/{job_id}``; ``Content-Location`` carries the same path.
- `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)
