Workflow files
Path: apis/workflow/<name>.json, a reserved, project-scoped directory (no API may be
named workflow). Companion scripts live in apis/workflow/scripts/<name>/.
A workflow is an ordered list of blocks. Its request blocks may target multiple APIs; the
environment is bound per API at run time, not in this file. This page is the format
reference; see Writing workflows for the narrative guide.
Example
{
"version": 1,
"onFailure": "halt",
"validateContracts": false,
"inputs": {
"petName": { "type": "string", "required": true, "default": "Rufus" }
},
"blocks": [
{
"id": "create-pet",
"type": "request",
"request": {
"api": "petstore-api",
"resource": "Pet",
"operation": "create",
"overrides": { "body": { "name": "{{petName}}", "status": "available" } }
}
},
{
"id": "created",
"type": "assert",
"assertions": [
{ "source": "create-pet.response.status", "operator": "equals", "value": 201 },
{ "source": "create-pet.response.body.id", "operator": "exists" }
]
}
]
}
Top-level fields
| Field | Type | Required | Description |
|---|---|---|---|
version | 1 | yes | File format version. |
blocks | array | yes | Ordered list of blocks (see below). |
onFailure | "halt" | "continue" | no | halt (default) stops at the first failed block; continue runs the rest, still reporting failed. |
validateContracts | boolean | no | Default false. When true, each request response is validated against its operation schema. |
inputs | object | no | Named run-time parameters. |
inputs
Each entry is a name mapped to a declaration:
"inputs": {
"petName": { "type": "string", "required": true, "default": "Rufus" },
"userId": { "type": "number", "required": false, "default": 1 }
}
| Field | Type | Description |
|---|---|---|
type | "string" | "number" | "boolean" | Provided values are coerced to this type. |
required | boolean | If true and no value/default is given, the run errors. |
default | any | Used when the run doesn't supply the input. |
description | string | Optional label. |
Reference an input as {{name}}.
Blocks
Every block has a unique id and a type. Later blocks read a block's result by its id:
<id>.response.status, <id>.response.body.<path> for requests, or <id>.<key> for transform
outputs. Paths walk objects by key and arrays by numeric index. {{token}} interpolation works
in any string value.
Every block also accepts an optional description (string): free text describing the block.
JSON has no comments, so this is the format's only comment channel; the app displays it beside
the block.
| Field | Type | Required | Description |
|---|---|---|---|
id | string | yes | Unique within the workflow. Later blocks address results by it. |
type | string | yes | One of request, assert, transform, condition, loop, group, script. |
description | string | no | Free-text note about this block. |
request
{
"id": "create-pet",
"type": "request",
"request": {
"api": "petstore-api",
"resource": "Pet",
"operation": "create",
"overrides": {
"params": { "query": { "status": "available" }, "path": { "pet-id": "{{extract.petId}}" } },
"body": { "name": "{{petName}}" }
}
}
}
| Field | Type | Description |
|---|---|---|
request.api | string | Required. Slug of the API this block targets. The union of these across all request blocks is the set of APIs the workflow touches (each needs an env at run time). |
request.resource | string | Resource the operation/instance belongs to. |
request.operation | string | Schema operation to run. Use with resource. |
request.instance | string | Saved request instance to run instead of a bare operation. |
request.overrides | object | Per-run patches: params (path/query/header/cookie, each Record<string,string>) and body (Record<string,unknown>). Values interpolate {{tokens}}. |
Result: <id>.response with .status, .body, .headers.
assert
Fails if any assertion fails, or if the script throws. Provide one of assertions or script.
{
"id": "created",
"type": "assert",
"assertions": [
{ "source": "create-pet.response.status", "operator": "equals", "value": 201 }
]
}
| Field | Type | Description |
|---|---|---|
assertions | array | Each: source (path), operator, and usually value (may be a {{token}}). |
script | object | { "name", "exportName" }, a script that throws to fail. |
timeoutMs | number | Script timeout override (default 30000). |
Operators:
| Operator | Passes when |
|---|---|
equals / notEquals | Deep-equal, arrays and objects included. |
exists / notExists | The source path resolves / doesn't. A key present but null still exists. |
hasValue / hasNoValue | The path resolves and the value isn't null/undefined / it doesn't. |
greaterThan / lessThan | Both sides are numbers and the comparison holds. |
contains | source is a string containing value, or an array holding it. |
transform
Names values for reuse. Provide one of transforms or script.
{ "id": "extract", "type": "transform", "transforms": { "petId": "create-pet.response.body.id" } }
| Field | Type | Description |
|---|---|---|
transforms | object | outputName: sourcePath. Reference results as <id>.<outputName>. |
script | object | A script returning an object; its keys become the outputs. |
timeoutMs | number | Script timeout override. |
condition
Runs then if the check passes, else else. Provide one of if or script.
{
"id": "check-persisted",
"type": "condition",
"if": { "source": "get-pet.response.status", "operator": "equals", "value": 200 },
"then": [ { "id": "assert-name", "type": "assert", "assertions": [] } ],
"else": []
}
| Field | Type | Description |
|---|---|---|
if | object | A single assertion (source/operator/value). |
script | object | A script returning a boolean. |
then | array | Blocks run when the check passes. |
else | array | Blocks run when it doesn't. Optional. |
timeoutMs | number | Script timeout override. |
Blocks in the branch not taken are reported as skipped.
loop
Repeats nested blocks. {{iteratorVar}} expands in nested block ids so each iteration is
uniquely named.
{
"id": "check-each-pet",
"type": "loop",
"mode": "forEach",
"source": "list-pets.response.body",
"iteratorVar": "pet",
"execution": "parallel",
"blocks": []
}
| Field | Type | Description |
|---|---|---|
mode | "times" | "forEach" | "until" | times repeats count times; forEach walks the array at source; until polls until a check passes. |
count | number | Iteration count for times. The iterator is the index, 0…count-1. |
source | string | Array path for forEach. The iterator is each element. |
until | object | until mode: an assertion tested after each iteration; the loop stops when it passes. |
script | object | until mode: alternative to until, a script returning a boolean. |
maxIterations | number | until mode: cap on iterations. If the check never passes within it, the loop fails. |
delayMs | number | until mode: wait between iterations (polling interval). |
iteratorVar | string | Name nested blocks use for the current item/index. Optional. |
execution | "sequential" | "parallel" | Default sequential. times/forEach only. |
blocks | array | The nested blocks. |
group
Bundles nested blocks under one label. Purely organisational: no branching, no repetition, no scope of its own; nested blocks run as if they sat at the top level and their ids stay global.
{
"id": "setup",
"type": "group",
"description": "Fixtures the assertions below rely on.",
"blocks": []
}
| Field | Type | Description |
|---|---|---|
blocks | array | The nested blocks. |
script
A free-form TypeScript block (side effects only, e.g. a pause).
{ "id": "wait", "type": "script", "script": { "name": "wait-2s", "exportName": "run" } }
| Field | Type | Description |
|---|---|---|
script | object | { "name", "exportName" }, resolved under apis/workflow/scripts/<workflow>/. |
timeoutMs | number | Script timeout override (default 30000). |
Scripts
assert, condition, transform, and script blocks can each point at a TypeScript function
in apis/workflow/scripts/<workflow-name>/<name>.ts, exported under exportName. It receives a
context from @apicrafthq/script-sdk:
AssertContext, ConditionContext, TransformContext, or ScriptContext respectively. See
Writing workflows → Scripts.