---
title: "Run one turn of a Cortex Agent conversation."
method: POST
path: "/v1/agent/run"
tags: ["agent"]
---

# Run one turn of a Cortex Agent conversation.

`POST /v1/agent/run`

## Request

- Base URL: `http://localhost:8080`
- URL: `http://localhost:8080/v1/agent/run`
- Auth: one of:
  - API key in cookie `dvt_session`
  - HTTP bearer

Streams the raw Cortex Agent SSE response back with **no retry** (ADR-0082 D6: a turn is never silently re-sent to the LLM) — exactly one upstream attempt. dvt does not parse frame content; the forwarded response is a pass-through (minus one internal SSE comment line, `: dvt-usage {...}`, the engine appends and this endpoint strips before forwarding, used only to record token-usage telemetry — `agent_turns`, never the transcript). In snowflake mode every frame is additionally persisted for up to ~48 hours for resume — see below. Subject to a per-org, per-`purpose` turn budget (30/hour); exceeding it returns 429.

DVT-4436: the underlying Cortex Agent call is detached from this HTTP request — it keeps running to completion (bounded by a 15-minute ceiling, or an explicit `POST /v1/agent/runs/{runId}/cancel`) regardless of whether this response stays open, in every edition. The first frame is always `event: dvt.run` (`AgentRunFrame`), written before any engine byte; every following frame (including the final `event: dvt.done`, `AgentRunDone`) carries an SSE `id: <seq>` line. The resume half of this contract — the response closing CLEANLY (normal EOF, no error) after at most ~75s with a final `event: dvt.resume` (`AgentRunResume`) frame naming a cursor to reconnect with via `GET /v1/agent/runs/{runId}/events?since=<cursor>` — is gated by `editionCapabilities.agentRunResume` (**true in snowflake mode only**): off editions stream to completion instead, exactly this route's pre-DVT-4436 shape. As a deliberate, bounded deviation from ADR-0082 D6 ("dvt holds no transcript"), SNOWFLAKE MODE ONLY additionally persists every frame of a run server-side SOLELY to make that resume possible — frames MAY include tool results (warehouse-derived content, e.g. a `response.tool_result` frame's query rows), organization-scoped, never logged, and purged by the daily retention sweep once older than 24 hours (at most ~48 hours at rest; requires `RETENTION_ENABLED`, default true, and not `RETENTION_DRY_RUN`) — see the `agent_turn_events` migration header and DVT-4544 for the tracked ADR-0082 amendment.

## Request body

- object
  - `threadId` string, required: The opaque Snowflake thread id (agent_threads.threadId).
  - `parentMessageId` string, nullable: Digits only. Omit/null on the first turn of a thread.
  - `message` string, required
  - `purpose` 'popup' | 'block', required: Independent turn budgets per purpose.
  - `context` object: Popup turns only (ignored for `purpose: block`); invalid values are dropped, never a 400. Rendered server-side into a delimited `<dvt_context>` prefix together with the caller's role and readable pushdown connection ids. Ids only — titles are not accepted (unknown fields such as `dashboardTitle`/`pageTitle` are ignored).
    - `dashboardId` string, uuid
    - `pageId` string

## Response `200`

The raw Cortex Agent SSE stream (`text/event-stream`), usage comment line stripped. The proxy also injects SSE comment lines `: keepalive` (no data, no trailing blank line) at a fixed interval while the upstream is silent; conformant clients ignore them.

## Other responses

- `400`: Malformed request.
- `401`: Distinguished by the Problem `type` (DVT-3518/DVT-3961, ADR-0082): - `https://docs.dvt.dev/errors/unauthorized` — missing, invalid, or expired session, same as every other route's `Unauthorized` response. - `https://docs.dvt.dev/errors/agent-caller-token-missing` — snowflake mode, `DVT_AGENT_IDENTITY=caller` only (DVT-3961, ADR-0082 D2d.4): this request carried no ingress caller token for the Cortex Agent call. Reconnect through the Snowflake-ingress-fronted session and retry.
- `403`: Several shapes, distinguished by the Problem `type` (DVT-3518/DVT-3961, ADR-0082): - `https://docs.dvt.dev/errors/agent-consent-required` — no active Snowflake Cortex Agent PAT is registered for the caller, or the stored one has expired or been revoked. Never emitted in snowflake mode under `DVT_AGENT_IDENTITY=caller` — there is no per-user PAT to re-consent for. Register a fresh PAT via `PUT /v1/agent/credential`, then retry. - `https://docs.dvt.dev/errors/agent-caller-not-authorized` — snowflake mode, `DVT_AGENT_IDENTITY=caller` only (DVT-3961, ADR-0082 D2d.4): Snowflake rejected the signed-in user's own combined ingress token for this Cortex Agent. Carries an additive `reason` (`usage_denied` | `token_rejected`) when the engine reported one. Ask a Snowflake administrator to grant USAGE on the agent to your role.
- `404`: Resource does not exist.
- `429`: Per-actor API rate limit exceeded (SEC-2). All authenticated `/v1` routes apply a per-actor fixed-window default tier; selected compute-heavy routes (connection tests, column describe, data export) apply an additional tighter tier. Auth endpoints apply a per-IP tier. Problem type: `https://docs.dvt.dev/errors/rate-limited`. Wait for the `Retry-After` response header value (seconds) before retrying.
- `502`: The analytics engine could not be reached (`https://docs.dvt.dev/errors/engine-unavailable`) or returned an error (`https://docs.dvt.dev/errors/engine-error`). One 502 on this shape is NOT an engine fault and says so: when Snowflake refuses the caller's-rights LOGIN, the Problem type is `https://docs.dvt.dev/errors/caller-connect-refused` — no session was established and the caller's SQL was never reached, so the remediation is a fresh dvt session rather than the engine logs (DVT-4345).
- `503`: The service or a required dependency is temporarily unavailable.

---

[API](https://skmtc.dev/dvt/apis/dvt-rest-api.md) · [All operations](https://skmtc.dev/dvt/apis/dvt-rest-api/llms.txt) · [OpenAPI document](https://skmtc.dev/dvt/apis/dvt-rest-api/revisions/00016f504165?raw)
