Skip to navigation

Run flows and agents with 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:

ChoiceFieldValuesDefault
Targetflow or agentA saved ID or an inline definitionNone
Flow definitionoptions.flowModeexisting, virtual, create, upsertexisting
Recordoptions.recordModeexisting, virtual, createexisting
Responseoptions.streamResponsetrue (SSE stream) or false (one JSON body)true
Background runPrefer headerrespond-asyncNot set

Before you begin

  • Create an API key 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
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
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
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.

Choose a flow mode

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

ModeRequired fieldsBehavior
existingflow.idRuns the saved flow. Returns 404 with FLOW_NOT_FOUND if no saved flow has that ID.
virtualflow.name and flow.stepsRuns the inline definition once and saves no flow.
createflow.name and flow.stepsSaves the definition as a new flow, then runs it. Each request creates another flow.
upsertflow.name and flow.stepsUpdates 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.

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:

ModeRequired fieldsBehavior
existingrecord.idLoads the saved record.
virtualrecord.name and record.typeUses record.name, record.type, and record.metadata for this run and saves no record.
createrecord.id, or record.name and record.typeSaves 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
{
"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?.

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
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
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
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. To decide which target fits your task, see 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.

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.

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.

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
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
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
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
{
"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
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.

Troubleshoot dispatch errors

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

StatusCodeFix
400NoneSend a flow object or an agent object, with the fields that the target needs.
404FLOW_NOT_FOUNDCheck flow.id, or send the steps with flowMode set to virtual or upsert.
422FLOW_RECORD_UNRESOLVEDCheck record.id, or set recordMode to create and send name and type.
422FLOW_DEFINITION_WRITE_REJECTEDSend flow.name and flow.steps, or allow the overwrite with upsertOptions.

For authentication, spending-limit, and rate-limit errors, see Quickstart.

Next steps