> ## Documentation Index
> Fetch the complete documentation index at: https://docs.staging.questra.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Workflows

> How Program runs async work — domain starts, generic workflow read/stream, hooks, and tip reconciliation.

Program models long-running work as **workflows**. Each execution is a Workflow SDK **run** (`wrun_...`). You **start** (and cancel / product-resume) from domain endpoints; you **read status and subscribe** via the generic workflow routes.

```http theme={null}
GET /v1/workflows/{workflowId}
GET /v1/workflows/{workflowId}/stream
POST /v1/workflows/{workflowId}/hooks/{token}
```

<Note>
  Live subscribe is **run-scoped**. Stay on one `wrun_...` for the whole orchestration (including step retries). A new start (newest-wins) returns a new run — switch to that run’s `stream_url` from the start response.
</Note>

## Mental model

| Concept           | Meaning                                                                               |
| ----------------- | ------------------------------------------------------------------------------------- |
| **Run**           | One full execution of a workflow definition (`wrun_...`)                              |
| **Kind**          | Which definition ran (`survey.programming`, `survey.questionnaire_clarifications`, …) |
| **Live stream**   | In-flight World chunks for that run (NDJSON, or AI SDK SSE for chat)                  |
| **Tip reconcile** | Webhooks + REST after the fact (`content.updated`, `GET .../content`, etc.)           |

```mermaid theme={null}
flowchart LR
  start[Domain POST 202] --> runId["wrun_..."]
  runId --> stream["GET /workflows/id/stream"]
  runId --> rest["GET /workflows/id"]
  finalize[Terminal write] --> hooks[Account webhooks]
  finalize --> tip[Tip REST]
```

## Kinds at a glance

| Kind                                  | Started by                               | Live subscribe                                                    | Guide                                                                   |
| ------------------------------------- | ---------------------------------------- | ----------------------------------------------------------------- | ----------------------------------------------------------------------- |
| `survey.questionnaire_clarifications` | `POST .../questionnaire/clarifications`  | `stream_url` → generic NDJSON                                     | [Questionnaire clarifications](/workflows/questionnaire-clarifications) |
| `survey.programming`                  | `POST .../programming`                   | `stream_url` → generic NDJSON                                     | [Programming](/workflows/programming)                                   |
| `survey.audit`                        | `POST .../audits`                        | `GET /v1/workflows/{workflow_id}/stream` (no domain `stream_url`) | [Live updates](/workflows/live-updates)                                 |
| `survey.audit.remediate`              | `POST .../audits/{auditId}/remediations` | `GET /v1/workflows/{workflow_id}/stream`                          | [Live updates](/workflows/live-updates)                                 |
| `survey.content_edit`                 | `POST .../conversations/.../messages`    | AI SDK UIMessage SSE on the POST                                  | [Content edit](/workflows/content-edit)                                 |

Full chunk shapes, reconnect rules, and webhook pairing: [Live updates](/workflows/live-updates).

## What you get on create

Domain starts return **202** with the run already available. Example (programming):

```http theme={null}
POST /v1/surveys/{surveyId}/programming
```

```json theme={null}
{
  "data": {
    "workflow_id": "wrun_...",
    "stream_url": "/v1/workflows/wrun_.../stream",
    "platform": null,
    "programming": {
      "kind": "survey.programming",
      "survey_id": "...",
      "status": "pending",
      "phases": [],
      "workflow_id": "wrun_...",
      "stream_url": "/v1/workflows/wrun_.../stream"
    }
  }
}
```

Clarifications start responses include `workflow_id` + `stream_url`. Audit start responses include `workflow_id` on the audit resource (no `stream_url` — subscribe via `GET /v1/workflows/{workflow_id}/stream`). Content-edit returns an SSE body instead of NDJSON.

## Run detail

```http theme={null}
GET /v1/workflows/{workflowId}
```

Lean binding + World status (`id`, `kind`, `status`, `stream_url`, terminal `output` / `error`). Authz: binding must exist in the workspace (missing → **404**).

Domain views stay survey-scoped when the product needs them — e.g. `GET .../programming` (lean status + empty `phases` for greenfield; legacy in-flight runs may still include phased steps). Prefer `workflow_id` + generic GET/stream for new UIs.

| Field              | Meaning                                                          |
| ------------------ | ---------------------------------------------------------------- |
| `id`               | SDK run id (`wrun_...`)                                          |
| `status`           | `pending` \| `running` \| `completed` \| `failed` \| `cancelled` |
| `stream_url`       | `GET /v1/workflows/{id}/stream`                                  |
| `output` / `error` | Terminal result (null while active)                              |

## Statuses

| Status      | Meaning                                                    |
| ----------- | ---------------------------------------------------------- |
| `pending`   | Accepted; binding registered; execution may not have begun |
| `running`   | Durable work has started                                   |
| `completed` | Finished; see `output`                                     |
| `failed`    | Finished with sanitized `error`                            |
| `cancelled` | Cancelled by request or replaced by a newer run            |

## Resolving hooks

Greenfield configure gates and clarifications expose SDK hook tokens (often via anatomy `resumePath`). Resume with:

```http theme={null}
POST /v1/workflows/{workflowId}/hooks/{token}
```

The JSON body is **kind-specific** (validated server-side). Programming also keeps product resume paths: `PUT .../programming/target` and `PUT .../programming/template` when the run is waiting on those gates. See [Programming](/workflows/programming).

## Cancel

Domain cancel: `POST .../programming/cancel` (and similar domain surfaces). Cancelling a terminal workflow returns **409**.

## Execution World

* The Workflow SDK **World** is execution truth (steps, hooks, streams, cancellation).
* Program Postgres stores **bindings** and related receipts only.
* Hosted deployments use a durable serverless World (Upstash Redis + QStash into Nitro `/.well-known/workflow/v1/*`). Local development uses the Local World — no QStash required.

## Webhooks (not a live socket)

Account webhooks (`workflow.*`, `questionnaire.clarifications.updated`, `content.updated`, …) fan out tip / lifecycle changes at-least-once. Use them for backends and reconciliation — not as a substitute for the run stream while you are attached to a `wrun_...`.

See [Webhook event types](/webhooks/event-types).
