Skip to navigation

Quickstart

Use the Runtype API to make an authenticated request and run an inline flow.

Before you begin

  • Create a Runtype account.
  • Create an API key that can run flows. The key needs full access or one of the DISPATCH:*, FLOWS:*, or FLOWS:EXECUTE permissions. Test keys (rt_test_) have daily and lifetime execution limits.
  • Set the RUNTYPE_API_KEY environment variable to your API key.
  • Make sure your account can use the model in the examples, either through platform keys or your own provider key. For more information, see Platform keys vs. BYOK.

Test your API key

Verify your API key by requesting your profile. The following TypeScript example sends the request:

TypeScript
const response = await fetch('https://api.runtype.com/v1/users/profile', {
headers: {
Authorization: 'Bearer ' + process.env.RUNTYPE_API_KEY,
},
})
const profile = await response.json()
console.log(profile)

The following Python example sends the same request:

Python
import os
import requests
response = requests.get(
'https://api.runtype.com/v1/users/profile',
headers={'Authorization': 'Bearer ' + os.environ['RUNTYPE_API_KEY']},
)
print(response.json())

The following cURL command sends the same request:

cURL
curl https://api.runtype.com/v1/users/profile \
-H "Authorization: Bearer YOUR_API_KEY"

Replace YOUR_API_KEY with your Runtype API key before you run the cURL command.

A valid key returns 200 with your profile. The output is similar to the following, with some fields left out:

Response
{
"id": "user_abc123",
"orgId": "org_abc123",
"orgName": "Example Organization",
"email": "dana@example.com",
"createdAt": "2026-01-15T10:00:00.000Z",
"dashboardUrl": "https://use.runtype.com"
}

A 401 response means that the key isn’t valid. Check that you copied the full key and that it hasn’t been revoked. The profile request needs no permission, so a successful response doesn’t confirm that the key can run flows.

Define and run a flow

Send a POST request to /v1/dispatch with an inline flow definition. Set flowMode and recordMode to virtual to run without creating a persisted flow or record. The following TypeScript example sends the request, reads the Server-Sent Events (SSE) stream, and prints the generated text:

TypeScript
const response = await fetch('https://api.runtype.com/v1/dispatch', {
method: 'POST',
headers: {
Authorization: 'Bearer ' + process.env.RUNTYPE_API_KEY,
'Content-Type': 'application/json',
},
body: JSON.stringify({
inputs: {
customerName: 'Example Organization',
topic: 'sales report',
},
flow: {
name: 'Customer Greeting',
steps: [
{
id: 'greeting',
name: 'Greeting',
type: 'prompt',
order: 1,
config: {
model: 'claude-haiku-4-5',
userPrompt: 'Greet {{customerName}} about their {{topic}}.',
outputVariable: 'greeting',
},
},
],
},
options: {
flowMode: 'virtual',
recordMode: 'virtual',
streamResponse: true,
},
}),
})
const reader = response.body?.getReader()
if (!reader) throw new Error('Response body is empty')
const decoder = new TextDecoder()
let buffer = ''
for (;;) {
const { done, value } = await reader.read()
if (done) break
buffer += decoder.decode(value, { stream: true })
const lines = buffer.split('\n')
buffer = lines.pop() ?? ''
for (const line of lines) {
if (!line.startsWith('data: ')) continue
const event = JSON.parse(line.slice(6))
if (event.type === 'text_delta') process.stdout.write(event.delta)
}
}

The following Python example sends the same request:

Python
import json
import os
import requests
response = requests.post(
'https://api.runtype.com/v1/dispatch',
headers={'Authorization': 'Bearer ' + os.environ['RUNTYPE_API_KEY']},
json={
'inputs': {
'customerName': 'Example Organization',
'topic': 'sales report',
},
'flow': {
'name': 'Customer Greeting',
'steps': [
{
'id': 'greeting',
'name': 'Greeting',
'type': 'prompt',
'order': 1,
'config': {
'model': 'claude-haiku-4-5',
'userPrompt': (
'Greet {{customerName}} about their {{topic}}.'
),
'outputVariable': 'greeting',
},
},
],
},
'options': {
'flowMode': 'virtual',
'recordMode': 'virtual',
'streamResponse': True,
},
},
stream=True,
)
for line in response.iter_lines(decode_unicode=True):
if not line or not line.startswith('data: '):
continue
event = json.loads(line[len('data: '):])
if event['type'] == 'text_delta':
print(event['delta'], end='', flush=True)

The following cURL command sends the same request:

cURL
curl https://api.runtype.com/v1/dispatch \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"inputs": {
"customerName": "Example Organization",
"topic": "sales report"
},
"flow": {
"name": "Customer Greeting",
"steps": [
{
"id": "greeting",
"name": "Greeting",
"type": "prompt",
"order": 1,
"config": {
"model": "claude-haiku-4-5",
"userPrompt": "Greet {{customerName}} about their {{topic}}.",
"outputVariable": "greeting"
}
}
]
},
"options": {
"flowMode": "virtual",
"recordMode": "virtual",
"streamResponse": true
}
}'

Replace YOUR_API_KEY with your Runtype API key before you run the cURL command.

Use inputs to pass values that change between runs. Templates reference them as {{variableName}}.

Save the flow instead

The virtual mode runs the flow once and saves nothing. To save the flow so that you can run it again and see it in the dashboard, set flowMode to create or upsert. To save the run’s data on a record, set recordMode to create and pass a record object with a name and a type. Put data that must persist with the record in record.metadata. For more information, see Manage flows as code.

Read the response

Because streamResponse is true, the dispatch endpoint returns an SSE stream. The output is similar to the following, with some fields left out:

SSE stream
event: execution_start
data: {"type":"execution_start","executionId":"exec_abc123","seq":0,"kind":"flow","startedAt":"2026-01-15T10:00:00.000Z"}
event: step_start
data: {"type":"step_start","executionId":"exec_abc123","seq":1,"id":"greeting","name":"Greeting","stepType":"prompt"}
event: text_start
data: {"type":"text_start","executionId":"exec_abc123","seq":2,"id":"text_1"}
event: text_delta
data: {"type":"text_delta","executionId":"exec_abc123","seq":3,"id":"text_1","delta":"Hello!"}
event: text_complete
data: {"type":"text_complete","executionId":"exec_abc123","seq":4,"id":"text_1"}
event: step_complete
data: {"type":"step_complete","executionId":"exec_abc123","seq":5,"id":"greeting","name":"Greeting","success":true}
event: execution_complete
data: {"type":"execution_complete","executionId":"exec_abc123","seq":6,"kind":"flow","success":true,"outcome":"completed"}

Each text_delta event carries the id of the text block that it belongs to. The success field on execution_complete tells you whether the run succeeded. A run that fails can end with an execution_error event instead of execution_complete. Both terminal events carry an outcome field: completed, error, or aborted. The value aborted means that the caller or the host stopped the run, for example by a cancel, a client disconnect, or a newer turn that superseded it. Use outcome to settle a turn instead of parsing stopReason. A paused run is not terminal: it ends the stream on an approval_start or await event, and the resumed run ends on the terminal event. For all event types and fields, see the API reference.

To get a single JSON response instead of a stream, set streamResponse to false. The response includes success, status, and an events array with the same events. If the run fails, success is false and the error and code fields describe the failure.

Troubleshoot the request

The following table lists the most common error statuses from the dispatch endpoint:

StatusCause
401The API key isn’t valid.
402Your account reached a spending limit.
403The API key doesn’t have a permission that can run flows.
429You sent too many requests, or a test key reached its daily or lifetime limit.

When streamResponse is true, a spending or rate limit can arrive as an error event in the stream instead of as an HTTP status. After a rate-limit 429, wait for the time in the Retry-After header before you retry.

For more errors, see Common errors and solutions and Rate limits and usage.

Use the SDK

Use an SDK to build and run the same flow. The TypeScript SDK needs Node.js 22 or later, and the examples use top-level await, so run them as an ES module. The Python SDK needs Python 3.10 or later.

Install the SDK package:

Install the SDK
npm install @runtypelabs/sdk

The following TypeScript example builds a flow, passes input values, and runs it:

TypeScript
import { FlowBuilder, RuntypeClient } from '@runtypelabs/sdk'
const client = new RuntypeClient({
apiKey: process.env.RUNTYPE_API_KEY,
})
const result = await new FlowBuilder()
.createFlow({ name: 'Customer Greeting' })
.withInputs({
customerName: 'Alex',
accountType: 'premium',
})
.prompt({
name: 'Personalized Greeting',
model: 'claude-haiku-4-5',
userPrompt: 'Greet {{customerName}} as a {{accountType}} customer.',
})
.run(client, { streamResponse: true })
const greeting = await result.getResult('Personalized Greeting')
console.log(greeting)

The withInputs method (with_inputs in Python) adds top-level variables that prompts reference as {{variableName}}. Use it for values that change between runs.

Validate before running

Call the validate method with an authenticated client to check an inline flow before running it. The method reports structural issues, undeclared-variable warnings, and model recommendations without creating or running the flow. The following TypeScript example reports validation errors:

TypeScript
import { FlowBuilder, RuntypeClient } from '@runtypelabs/sdk'
const client = new RuntypeClient({
apiKey: process.env.RUNTYPE_API_KEY,
})
const builder = new FlowBuilder()
.createFlow({ name: 'Customer Greeting' })
.prompt({
name: 'Personalized Greeting',
model: 'claude-haiku-4-5',
userPrompt: 'Create a greeting for {{customerName}}.',
})
const validation = await builder.validate(client)
if (!validation.valid) {
for (const issue of validation.errors) {
console.error(issue.code, issue.message)
}
}

Validation doesn’t count as an execution. With an authenticated client, it also checks that the tools, flows, and agents that the flow references exist in your account. Check validation.context.accountChecksPerformed to confirm whether those checks ran.

Next steps