> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.runtype.com/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.runtype.com/_mcp/server.

# Run flows and agents with dispatch

> Choose a target, flow and record modes, and a response type for POST /v1/dispatch

`POST /v1/dispatch` runs a flow or an agent in one request. The request body
names the target, says whether to save the flow definition and the record, and
chooses how the result comes back. The following table lists the choices and
their defaults:

| Choice          | Field                    | Values                                         | Default    |
| --------------- | ------------------------ | ---------------------------------------------- | ---------- |
| Target          | `flow` or `agent`        | A saved ID or an inline definition             | None       |
| Flow definition | `options.flowMode`       | `existing`, `virtual`, `create`, `upsert`      | `existing` |
| Record          | `options.recordMode`     | `existing`, `virtual`, `create`                | `existing` |
| Response        | `options.streamResponse` | `true` (SSE stream) or `false` (one JSON body) | `true`     |
| Background run  | `Prefer` header          | `respond-async`                                | Not set    |

## Before you begin

* [Create an API key](/developer-guides/getting-started/authentication) that
  has full access or one of the `DISPATCH:*`, `FLOWS:*`, or `FLOWS:EXECUTE`
  permissions. The same permissions run agents through dispatch.
* Set the `RUNTYPE_API_KEY` environment variable to your API key.

## Run a saved flow

To run a flow that you saved in the dashboard, pass its ID in `flow.id`. Values
in `inputs` become variables that the flow's templates reference as
`{{variableName}}`. The following TypeScript example runs a saved flow and
waits for one JSON response:

**`TypeScript`**

```typescript title="TypeScript"
import { RuntypeClient } from '@runtypelabs/sdk'

const client = new RuntypeClient({ apiKey: process.env.RUNTYPE_API_KEY })

const result = await client.dispatch.execute({
  flow: { id: 'YOUR_FLOW_ID' },
  inputs: { topic: 'quarterly revenue' },
})
console.log(result.status, result.events.length)
```

The following Python example runs the same flow:

**`Python`**

```python title="Python"
import os

from runtype import RuntypeClient

client = RuntypeClient(api_key=os.environ['RUNTYPE_API_KEY'])

result = client.dispatch(
    {
        'flow': {'id': 'YOUR_FLOW_ID'},
        'inputs': {'topic': 'quarterly revenue'},
    },
    stream=False,
)
print(result['status'], len(result['events']))
```

The following cURL command runs the same flow:

**`cURL`**

```bash title="cURL"
curl https://api.runtype.com/v1/dispatch \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "flow": { "id": "YOUR_FLOW_ID" },
    "inputs": { "topic": "quarterly revenue" },
    "options": { "streamResponse": false }
  }'
```

Replace the following:

* `YOUR_API_KEY`: your Runtype API key
* `YOUR_FLOW_ID`: the ID of the saved flow, which starts with `flow_`

To run an inline flow definition instead, send `flow.name` and `flow.steps`
and set `flowMode`. For a complete inline example, see
[Quickstart](/developer-guides/getting-started/quickstart).

## Choose a flow mode

The `options.flowMode` field controls what dispatch does with the flow
definition. The following table describes each mode:

| Mode       | Required fields              | Behavior                                                                                   |
| ---------- | ---------------------------- | ------------------------------------------------------------------------------------------ |
| `existing` | `flow.id`                    | Runs the saved flow. Returns `404` with `FLOW_NOT_FOUND` if no saved flow has that ID.     |
| `virtual`  | `flow.name` and `flow.steps` | Runs the inline definition once and saves no flow.                                         |
| `create`   | `flow.name` and `flow.steps` | Saves the definition as a new flow, then runs it. Each request creates another flow.       |
| `upsert`   | `flow.name` and `flow.steps` | Updates the saved flow that has the same name, or creates it if none exists, then runs it. |

Use `upsert` when you send the same flow definition from code on every run.
When `upsert` changes a saved flow, it saves the previous steps as a version
first. It refuses to overwrite a flow that was last changed outside code, such as in
the dashboard, and returns `422` with `FLOW_DEFINITION_WRITE_REJECTED`. To overwrite
it anyway, set `options.upsertOptions.allowOverwriteExternalChanges` to `true`.
For more information, see
[Manage flows as code](/user-guide/flows/manage-flows-as-code).

## Bind the run to a record

A flow run can read a record's fields through `{{_record.name}}` and
`{{_record.metadata.industry}}` template variables. Without a `record` object, the
flow runs with no record. The `options.recordMode` field controls how dispatch
resolves the `record` object. The following table describes each mode:

| Mode       | Required fields                                 | Behavior                                                                                                  |
| ---------- | ----------------------------------------------- | --------------------------------------------------------------------------------------------------------- |
| `existing` | `record.id`                                     | Loads the saved record.                                                                                   |
| `virtual`  | `record.name` and `record.type`                 | Uses `record.name`, `record.type`, and `record.metadata` for this run and saves no record.                |
| `create`   | `record.id`, or `record.name` and `record.type` | Saves a record, then runs. If a record with the same `type` and `name` exists, merges `metadata` into it. |

The following request body creates a `company` record and runs a saved flow
against it:

**`Request body`**

```json title="Request body"
{
  "record": {
    "name": "Example Organization",
    "type": "company",
    "metadata": { "industry": "Logistics" }
  },
  "flow": { "id": "YOUR_FLOW_ID" },
  "options": { "recordMode": "create" }
}
```

A record ID that doesn't exist, or that your API key can't read, returns `422`
with `FLOW_RECORD_UNRESOLVED`. For more information about records, see
[What are Records?](/user-guide/records/what-are-records).

## Run an agent

To run an agent, send an `agent` object instead of a `flow` object and pass
the conversation in `messages`. To run a saved agent, set `agent.agentId`. To
run an agent without saving it, set `agent.name` and `agent.model`, and
optionally `agent.systemPrompt`. The following TypeScript example runs an
inline agent:

**`TypeScript`**

```typescript title="TypeScript"
const result = await client.dispatch.execute({
  agent: {
    name: 'Support Assistant',
    model: 'claude-sonnet-5',
    systemPrompt: 'Answer questions about shipping in two sentences.',
  },
  messages: [{ role: 'user', content: 'How long does delivery take?' }],
})
console.log(result.result)
```

The following Python example runs a saved agent:

**`Python`**

```python title="Python"
result = client.dispatch(
    {
        'agent': {'agentId': 'YOUR_AGENT_ID'},
        'messages': [
            {'role': 'user', 'content': 'How long does delivery take?'},
        ],
    },
    stream=False,
)
print(result['result'])
```

The following cURL command runs the same saved agent:

**`cURL`**

```bash title="cURL"
curl https://api.runtype.com/v1/dispatch \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "agent": { "agentId": "YOUR_AGENT_ID" },
    "messages": [
      { "role": "user", "content": "How long does delivery take?" }
    ],
    "options": { "streamResponse": false }
  }'
```

Replace `YOUR_AGENT_ID` with the ID of the saved agent, which starts with
`agent_`.

A buffered agent response carries the final text in `result`, plus `success`,
`iterations`, `totalCost`, and `stopReason`. To run a specific saved version,
set `agent.alias` or `agent.versionId`.
For more information, see
[Agent versioning and publishing](/user-guide/agents/agent-versioning-and-publishing).
To decide which target fits your task, see
[Choosing between a flow and an agent](/user-guide/flows/choosing-between-a-flow-and-an-agent).

## Choose a response type

Dispatch streams Server-Sent Events (SSE) unless you set
`options.streamResponse` to `false`. Flows and agents share one event
vocabulary. The `kind` field on `execution_start` and `execution_complete` is `flow`
or `agent`. Read `text_delta` events for generated text, and read the
`outcome` field (`completed`, `error`, or `aborted`) on the terminal
`execution_complete` or `execution_error` event for how the run ended. For a stream-reading example and sample
output, see [Quickstart](/developer-guides/getting-started/quickstart).

In the TypeScript SDK, `client.dispatch.executeStream(request)` returns the
streaming `Response`, and `client.dispatch.execute(request)` returns the JSON
body. In the Python SDK, `client.dispatch(request)` streams by default, and
`stream=False` returns the JSON body.

With `streamResponse` set to `false`, a flow response carries `success`,
`status`, and an `events` array that holds the same events as the stream. The
`status` is `completed`, `paused`, or `failed`. A `paused` run waits for an
approval or a tool result and carries a `pausedReason`. For how to continue a
paused run, see
[Pause and resume on an agent dispatch](/developer-guides/guides/runtime-tools#pause-and-resume-on-an-agent-dispatch).

An agent stream can end with an `await` event whose `awaitReason` is
`detached` while the agent keeps running. The TypeScript SDK reconnects for
you. For other clients, see
[Detached streams](/developer-guides/guides/runtime-tools#detached-streams).

## Run in the background and poll for the result

To start a run without holding the HTTP connection open, send the
`Prefer: respond-async` header. Dispatch returns `202 Accepted` with an
execution handle, and the run continues without the connection. The following
TypeScript example starts a run and polls until it finishes:

**`TypeScript`**

```typescript title="TypeScript"
const handle = await client.dispatch.executeAsync({
  flow: { id: 'YOUR_FLOW_ID' },
  inputs: { topic: 'quarterly revenue' },
})

let execution = await client.executions.getStatus(handle.executionId)
while (execution.status === 'queued' || execution.status === 'running') {
  await new Promise((resolve) => setTimeout(resolve, 2_000))
  execution = await client.executions.getStatus(handle.executionId)
}
console.log(execution.status, execution.result ?? execution.error)
```

The following Python example sends the header on a dedicated client and polls
the status endpoint:

**`Python`**

```python title="Python"
import os
import time

from runtype import RuntypeClient

with RuntypeClient(
    api_key=os.environ['RUNTYPE_API_KEY'],
    headers={'Prefer': 'respond-async'},
) as client:
    handle = client.dispatch(
        {
            'flow': {'id': 'YOUR_FLOW_ID'},
            'inputs': {'topic': 'quarterly revenue'},
        },
        stream=False,
    )
    status_path = '/executions/' + handle['executionId'] + '/status'
    execution = client.get(status_path)
    while execution['status'] in ('queued', 'running'):
        time.sleep(2)
        execution = client.get(status_path)
    print(execution['status'], execution.get('result'))
```

The following cURL command starts the same run:

**`cURL`**

```bash title="cURL"
curl https://api.runtype.com/v1/dispatch \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Prefer: respond-async" \
  -d '{
    "flow": { "id": "YOUR_FLOW_ID" },
    "inputs": { "topic": "quarterly revenue" }
  }'
```

The response is similar to the following:

**`Response`**

```json title="Response"
{
  "executionId": "exec_abc123",
  "status": "queued",
  "statusUrl": "/v1/executions/exec_abc123/status",
  "targetKind": "flow",
  "targetId": "flow_abc123"
}
```

To check progress, send a `GET` request to `https://api.runtype.com` followed
by the `statusUrl` path, with the same `Authorization` header:

**`cURL`**

```bash title="cURL"
curl https://api.runtype.com/v1/executions/YOUR_EXECUTION_ID/status \
  -H "Authorization: Bearer YOUR_API_KEY"
```

Replace `YOUR_EXECUTION_ID` with the `executionId` from the handle.

The `status` field is `queued` or `running` while the run is in progress. The
run ends as `completed`, `failed`, or `cancelled`, with the output in `result`
or the failure in `error`. A `paused` run waits for an approval or a tool
result, and polling doesn't resume it. Poll the status endpoint instead of
repeating the dispatch request, because a repeated request starts a second
run.

A background run doesn't accept inline `secrets` or an `identity_proof`, and
it allows longer flow timeouts than an attached run. For those limits and the
error codes, see
[Timeouts and long-running work](/user-guide/flows/timeouts-and-long-running-work#detach-an-interactive-execution-from-the-client-request).

## Troubleshoot dispatch errors

The following table lists the dispatch-specific errors and how to fix them:

| Status | Code                             | Fix                                                                               |
| ------ | -------------------------------- | --------------------------------------------------------------------------------- |
| `400`  | None                             | Send a `flow` object or an `agent` object, with the fields that the target needs. |
| `404`  | `FLOW_NOT_FOUND`                 | Check `flow.id`, or send the steps with `flowMode` set to `virtual` or `upsert`.  |
| `422`  | `FLOW_RECORD_UNRESOLVED`         | Check `record.id`, or set `recordMode` to `create` and send `name` and `type`.    |
| `422`  | `FLOW_DEFINITION_WRITE_REJECTED` | Send `flow.name` and `flow.steps`, or allow the overwrite with `upsertOptions`.   |

For authentication, spending-limit, and rate-limit errors, see
[Quickstart](/developer-guides/getting-started/quickstart#troubleshoot-the-request).

## Next steps

* [Runtime tools](/developer-guides/guides/runtime-tools): give a dispatched agent tools for one request
* [Steer an active agent execution](/developer-guides/guides/live-message-steering): send more input to a running agent
* [Flow variables and templates](/user-guide/flows/flow-variables-and-templates): reference `inputs` and record fields in steps
* [Dispatch API reference](/api-reference/runtype-api/dispatch/execute-a-flow-or-agent): every request field and response schema