---
title: "Create an analysis (video × prompt × model)"
method: POST
path: "/v1/analyses"
tags: ["analyses"]
---

# Create an analysis (video × prompt × model)

`POST /v1/analyses`

## Request

- Base URL: `https://api.primateintelligence.ai`
- URL: `https://api.primateintelligence.ai/v1/analyses`
- Auth: HTTP bearer

Async by default: 202 + poll. `Prefer: wait=<s>` (≤120) blocks and returns the terminal resource when it finishes in time. Test-mode keys (pv_test_) return deterministic canned results with no GPU time and no credit burn. Set `validate_only: true` to dry-run a prompt — returns an AnalysisPreview with cost/assessability and creates nothing.

## Headers

- `Idempotency-Key` string
- `Prefer` string

## Request body

- CreateAnalysisRequest
  - `video_id` string, required: Video resource id (prefixed ULID). Immutable.
  - `prompt` string: Free text — compiled server-side. Exactly one of prompt/query.
  - `query` object: Structured CompiledQuery JSON (schema at /v1/schemas/compiled-query.json).
  - `model` string: Defaults to latest (currently Darwin-preview-1.3B, status preview); deprecated aliases (e.g. darwin-1.3) are accepted case-insensitively; response always echoes the canonical id.
  - `options` object: narrative: true — generate a narrative (timestamped event sentences) for the analysis. Populates the narrative object on the completed analysis; generation runs asynchronously after completion (poll the GET — status generating → ready|failed). Included in the analysis price.
    - `narrative` boolean
  - `metadata` object: Free-form key/value metadata. ≤16 keys, key ≤40 chars [a-zA-Z0-9_.-], value ≤500 chars.
  - `webhook` object: Per-request webhook override (delivery ships P7). Write-only — never echoed.
    - `url` string, uri, required
    - `events` string[], required
  - `validate_only` boolean: Dry run (PRI-480). Compiles the prompt, reports whether the query is assessable, and estimates duration/cost — WITHOUT creating an analysis, touching the GPU, or burning credits. Returns an AnalysisPreview (object: "analysis_preview") with 200, not an Analysis. Use it to check a prompt before paying for it.

## Response `200`

Terminal resource (Prefer: wait satisfied, or test mode) — OR an AnalysisPreview when validate_only:true was sent. Discriminate on the `object` field.

- union
  - Analysis: A video analysis (§13.2).
    - `id` string, required: Analysis resource id (prefixed ULID).
    - `object` 'analysis', required
    - `livemode` boolean, required: true when the request used a live key; false for test keys and sandbox. Stripe-style.
    - `origin` 'api' | 'console' | 'system', required: `api` — submitted via the public API; `console` — uploaded via the dashboard; `system` — generated internally (e.g. auto-probe, fixture seeding, run_prompt). System analyses are never billed. Agents should normally only process api/console-origin analyses. (Renamed 2026-07-27: former value `user` is now `api`.)
    - `status` 'queued' | 'preparing' | 'analyzing' | 'rendering' | 'completed' | 'failed' | 'canceled', required: Analysis lifecycle state (§14.2). Clients MUST tolerate unknown future non-terminal values.
    - `video_id` string, required: Video resource id (prefixed ULID). Immutable.
    - `prompt` string, nullable, required
    - `query` object, required: CompiledQuery (schema at /v1/schemas/compiled-query.json). Always populated in responses.
    - `parse_mode` 'llm' | 'heuristic' | 'client', nullable, required
    - `model` string, required
    - `options` object, required
      - `narrative` boolean
    - `progress` Progress, nullable, required: Non-terminal only. percent is monotonic non-decreasing per analysis.
      - `stage` string, required
      - `percent` number, required
    - `queue_position` integer, nullable, required
    - `result` Result, nullable, required: Analysis result — the product (§13.3). Reserved (always absent in v1): masks, bounding_boxes, events.
      - `answer` 'yes' | 'no' | 'indeterminate', required
      - `confidence` number, required
      - `clips` Clip[], required
        - `start_s` number, required
        - `end_s` number, required
        - `confidence` number, required
        - `terms` object, required
      - `term_confidences` object, required
      - `query_type` 'object' | 'action' | 'compound' | 'attribute' | 'open_ended', required
      - `video_duration_s` number, nullable, required
      - `detected_count` integer, nullable
      - `indeterminate_reason` 'low_confidence' | 'nothing_detected' | 'unsupported_query_form' | 'duration_mismatch' | 'unsupported_action_term', nullable
      - `unsupported_action` object: PRI-516: present only when the prompt requested something the model cannot detect/score. Absent otherwise — never null.
        - `reason` string, required: Why the request could not be scored.
        - `docs_url` string, required: The supported-actions capability list — what the model can and cannot detect.
    - `narrative` Narrative, nullable, required
      - `status` 'generating' | 'ready' | 'failed', required
      - `entries` object[], required
        - `t_s` number, required
        - `text` string, required
    - `artifacts` Artifacts, nullable, required: Signed URL TTL 1h; re-fetch GET analysis for a fresh URL.
      - `annotated_video_url` string, uri, required
      - `expires_at` string, date-time, required
    - `error` ErrorObject, nullable, required
      - `code` string, required
      - `message` string, required
    - `metering_rule` 'per_second' | 'per_frame': PRI-552: settlement rule pinned at admission. Resources created before the cutover report per_second.
    - `usage` AnalysisUsage, nullable, required: Terminal only. credit_balance_after is the balance immediately after THIS analysis settled — an immutable point-in-time snapshot; it does not change as later analyses run. Null when the account row is unavailable (e.g. free-grant exhausted before settlement). billed_seconds is the settled ledger amount, derived from the processed-frame count for per_frame rows (PRI-552); historical per_second rows carry their original clock-derived amount.
      - `billed_seconds` integer, required
      - `credit_balance_after` integer, nullable, required
      - `metering_rule` 'per_second' | 'per_frame': PRI-552: the rule this analysis settled under. Mirrors the top-level metering_rule for a self-contained terminal summary.
      - `frames` integer, nullable: PRI-552: processed-frame count reported by the inference callback (cost telemetry). Present for both metering rules when available; null when the callback did not carry it.
    - `rerun_eligible` boolean: PRI-493: present on failed analyses — true when this analysis qualifies for ONE free re-run (it failed during a platform incident, or was flagged by ops). POST /v1/analyses/{id}/rerun creates a fresh analysis at no charge. Absent on non-failed analyses.
    - `metadata` object: Free-form key/value metadata. ≤16 keys, key ≤40 chars [a-zA-Z0-9_.-], value ≤500 chars.
    - `created_at` string, date-time, required
    - `started_at` string, date-time, nullable, required
    - `completed_at` string, date-time, nullable, required
  - AnalysisPreview
    - `object` 'analysis_preview', required
    - `query` object, required: The compiled query the analysis would run.
    - `parse_mode` 'llm' | 'heuristic' | 'client', required: How the prompt was compiled. "client" means you supplied query directly.
    - `assessable` boolean, required: False for open_ended queries — the model cannot score them, so detections will be empty.
    - `video_duration_s` number, nullable, required: Null when duration is not yet known (probe pending).
    - `estimated_seconds` integer, nullable, required: Billable seconds this analysis would consume. Null when duration is unknown.
    - `estimated_cost_usd` number, nullable, required: Estimated cost at current pricing. Null when duration is unknown.

## Other responses

- `202`: Queued. Response echoes the compiled query and honest queue_position.
- `400`: validation_failed / url_forbidden / prompt errors (§15)
- `401`: invalid_api_key / key_revoked / key_expired
- `402`: insufficient_credits (details.meter = credit_seconds)
- `404`: resource_not_found (account-scoped; no existence oracle)
- `409`: resource_conflict / idempotency_key_reused / idempotency_unavailable
- `429`: rate_limit_exceeded / concurrency_limit_exceeded / quota_exceeded (always with Retry-After)
- `503`: inference_unavailable / capacity_exhausted (with Retry-After)

---

[API](https://skmtc.dev/primateintelligence/apis/primate-vision-api.md) · [All operations](https://skmtc.dev/primateintelligence/apis/primate-vision-api/llms.txt) · [OpenAPI document](https://skmtc.dev/primateintelligence/apis/primate-vision-api/revisions/aa51b01dc8ad?raw)
