Skip to content

Commit c844be8

Browse files
authored
docs(api): guide for building and running workflows with the API (#9901)
* docs(api): add a guide for building and running workflows with the API * docs(api): point the workflow guide at the Sim skills * docs(api): pass only the operations array to the CLI in the workflow guide * docs(api): keep polling while a run is redacting
1 parent 70ec98a commit c844be8

4 files changed

Lines changed: 326 additions & 0 deletions

File tree

Lines changed: 323 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,323 @@
1+
---
2+
title: Build and run workflows with the API
3+
description: Discover blocks, edit a draft with dry runs and lint, test it, deploy it, run it, and debug or resume runs.
4+
---
5+
6+
import { Callout } from '@/components/ui/callout'
7+
import { Tab, Tabs } from '@/components/ui/tabs'
8+
9+
Build a workflow without the workflow builder: find the blocks you need, write the draft graph in batches, check each batch before committing it, test the draft, deploy it, and run it. Every step has an HTTP request and an equivalent [`sim` CLI](/cli) command, and the same operations are available to AI agents through [Sim MCP](#use-sim-mcp).
10+
11+
## Prerequisites
12+
13+
Set `BASE_URL` to your deployment origin (`https://www.sim.ai` for Sim Cloud), `SIM_API_KEY` to a personal API key, and `WORKSPACE_ID` to the target workspace. Examples use `jq` to read responses.
14+
15+
| Step | Required access |
16+
| --- | --- |
17+
| Discover blocks and tools | Workspace read access |
18+
| Create a workflow | Workspace write access |
19+
| Edit the graph and run the draft | Workspace write access with a personal API key or an OAuth token with `api:write` |
20+
| Deploy | Workspace **Admin** access with a personal API key or an OAuth token with `api:write` |
21+
| Run the deployment | Workspace read access with a full-access API key or an OAuth token with `api:write` |
22+
23+
Workspace API keys cannot edit graphs, deploy, or run the draft; those requests return `403`. See [Authentication](/api-reference/authentication) for key types and scopes. The CLI uses your `sim login` session or profile; set the workspace with `--workspace` or `SIM_WORKSPACE`.
24+
25+
## 1. Discover blocks
26+
27+
The block catalog is filtered per workspace, so every request takes `workspaceId`. List candidates, then read one block to get its configuration fields, operations, and outputs.
28+
29+
<Tabs items={['curl', 'CLI']}>
30+
<Tab value="curl">
31+
```bash
32+
curl -sS --fail-with-body -H "X-API-Key: $SIM_API_KEY" \
33+
"$BASE_URL/api/v2/blocks?workspaceId=$WORKSPACE_ID&search=slack"
34+
35+
curl -sS --fail-with-body -H "X-API-Key: $SIM_API_KEY" \
36+
"$BASE_URL/api/v2/blocks/slack?workspaceId=$WORKSPACE_ID"
37+
```
38+
</Tab>
39+
<Tab value="CLI">
40+
```bash
41+
sim blocks list --search slack
42+
sim blocks get slack
43+
```
44+
</Tab>
45+
</Tabs>
46+
47+
In a block detail, `inputSchema` lists fields that always apply, `operationInputSchema` lists the fields each operation reveals, and `outputs` lists what downstream blocks can reference. A block's `id` is the `type` you use when adding it. `GET /api/v2/tools` and `GET /api/v2/tools/{toolId}` (`sim tools list`, `sim tools get`) describe the underlying tools and their parameters. Use `capability=trigger` to find blocks that can start a workflow.
48+
49+
## 2. Create a workflow
50+
51+
<Tabs items={['curl', 'CLI']}>
52+
<Tab value="curl">
53+
```bash
54+
curl -sS --fail-with-body -X POST "$BASE_URL/api/v2/workflows" \
55+
-H "X-API-Key: $SIM_API_KEY" -H 'Content-Type: application/json' \
56+
-d "{\"workspaceId\": \"$WORKSPACE_ID\", \"name\": \"Ticket triage\"}"
57+
```
58+
</Tab>
59+
<Tab value="CLI">
60+
```bash
61+
sim workflows create --name "Ticket triage" --output json
62+
```
63+
</Tab>
64+
</Tabs>
65+
66+
The response includes the workflow `id` and `blocks`, the blocks the workflow was seeded with. It contains a Start block; connect edges to its `id`. Set `WORKFLOW_ID` and `START_ID` from the response.
67+
68+
## 3. Define inputs [#inputs]
69+
70+
The Start block's `inputFormat` field declares the input fields a run accepts. A run passes values for them in `input`, keyed by field name, and blocks reference them as `<start.fieldName>`. Set the field with an `edit` operation in the next step.
71+
72+
## 4. Preview edits with a dry run [#dry-run]
73+
74+
`POST /api/v2/workflows/{workflowId}/operations` applies a batch of semantic edits: `add`, `edit`, `delete`, `insert_into_subflow`, and `extract_from_subflow`. Save this batch as `ops.json`. It adds a Function block, declares a `ticketId` input, and connects Start to the new block:
75+
76+
```json
77+
{
78+
"operations": [
79+
{
80+
"operation_type": "add",
81+
"block_id": "lookup",
82+
"params": {
83+
"type": "function",
84+
"name": "Lookup",
85+
"inputs": { "code": "return { ticketId: <start.ticketId>, priority: 'high' }" }
86+
}
87+
},
88+
{
89+
"operation_type": "edit",
90+
"block_id": "START_ID",
91+
"params": {
92+
"inputs": { "inputFormat": [{ "name": "ticketId", "type": "string" }] },
93+
"connections": { "source": "lookup" }
94+
}
95+
}
96+
]
97+
}
98+
```
99+
100+
Replace `START_ID` with the Start block ID. The CLI's `--operations` takes just the array, so the CLI examples pass `.operations` from this file. `inputs` maps field IDs from the block catalog to values. `connections` maps a source handle to a target block, and replaces that block's outgoing edges. Within one batch, refer to new blocks by the `block_id` you gave them.
101+
102+
Add `?dryRun=true` to validate and lint without saving:
103+
104+
<Tabs items={['curl', 'CLI']}>
105+
<Tab value="curl">
106+
```bash
107+
curl -sS --fail-with-body -X POST \
108+
"$BASE_URL/api/v2/workflows/$WORKFLOW_ID/operations?dryRun=true" \
109+
-H "X-API-Key: $SIM_API_KEY" -H 'Content-Type: application/json' \
110+
-d @ops.json
111+
```
112+
</Tab>
113+
<Tab value="CLI">
114+
```bash
115+
jq .operations ops.json |
116+
sim workflows operations apply "$WORKFLOW_ID" --operations @- --dry-run
117+
```
118+
</Tab>
119+
</Tabs>
120+
121+
A dry run returns the same validation, warnings, lint, and ID conflicts as a committed write, with `dryRun: true`. Its `previewBlockIds` are provisional; never use them in a later request.
122+
123+
## 5. Read the result and lint [#lint]
124+
125+
Check these fields before applying, and again after:
126+
127+
| Field | Means |
128+
| --- | --- |
129+
| `skipped` | Operations the engine declined, each with `type`, `blockId`, and `reason` |
130+
| `inputValidationErrors` | Block inputs that were dropped instead of saved |
131+
| `deferred` | Edges waiting for a target block. They apply when the target exists; do not resubmit them |
132+
| `lint.fieldIssues` | Blocks with `missingRequiredFields`, which fail at run time, or values stranded on an inactive field mode |
133+
| `lint.unresolvedReferences` | Credential, resource, tool, skill, or `<block.output>` references that will not resolve. They are saved anyway |
134+
| `lint.orphanBlocks`, `lint.sources`, `lint.emptyOutgoingPorts` | Disconnected blocks, unreachable blocks, and branches that lead nowhere |
135+
| `lint.manualRunIssue` | Why a manual run from the workflow builder's **Run** button would be refused: `no-trigger`, `multiple-api-triggers`, or `trigger-not-connected`. API runs do not apply this check |
136+
| `warnings` | Normalizations, plus a line counting lint findings by category |
137+
138+
Lint is advisory. It never blocks a write or a deploy, so fix findings before you rely on the workflow.
139+
140+
## 6. Apply the batch [#apply]
141+
142+
Apply without `dryRun`. Set `atomic: true` in the body to write nothing when any operation is declined or any input would be dropped; the request then returns `409` with `OPERATIONS_NOT_APPLIED`. Without it, the batch applies what it can and reports the rest in `skipped` and `inputValidationErrors`.
143+
144+
<Tabs items={['curl', 'CLI']}>
145+
<Tab value="curl">
146+
```bash
147+
jq '. + {atomic: true}' ops.json | curl -sS --fail-with-body -X POST \
148+
"$BASE_URL/api/v2/workflows/$WORKFLOW_ID/operations" \
149+
-H "X-API-Key: $SIM_API_KEY" -H 'Content-Type: application/json' \
150+
-d @-
151+
```
152+
</Tab>
153+
<Tab value="CLI">
154+
```bash
155+
jq .operations ops.json |
156+
sim workflows operations apply "$WORKFLOW_ID" --operations @- --atomic --yes
157+
```
158+
</Tab>
159+
</Tabs>
160+
161+
`mintedBlockIds` maps each non-UUID `block_id` to the ID the block was given. Use the minted IDs in later requests. Read the full draft with `GET /api/v2/workflows/{workflowId}/state` (`sim workflows state get`). Edits change the draft only; the deployed workflow keeps running its version until you deploy.
162+
163+
## 7. Test the draft [#test-run]
164+
165+
Run the saved draft with `run.source: "manual"`. Manual runs are synchronous or streamed, never `async`.
166+
167+
<Tabs items={['curl', 'CLI']}>
168+
<Tab value="curl">
169+
```bash
170+
curl -sS --fail-with-body -X POST \
171+
"$BASE_URL/api/v2/workflows/$WORKFLOW_ID/execute" \
172+
-H "X-API-Key: $SIM_API_KEY" -H 'Content-Type: application/json' \
173+
-d '{"run": {"source": "manual"}, "input": {"ticketId": "T-123"}}'
174+
```
175+
</Tab>
176+
<Tab value="CLI">
177+
```bash
178+
sim workflows run "$WORKFLOW_ID" --manual --input '{"ticketId": "T-123"}'
179+
```
180+
</Tab>
181+
</Tabs>
182+
183+
Narrow a test run with these options:
184+
185+
| Goal | Request | CLI |
186+
| --- | --- | --- |
187+
| Enter through a specific trigger | `run.entry: {type: "trigger", blockId}` | `--trigger <blockId>` |
188+
| Use the trigger's mock payload instead of `input` | `run.entry: {type: "trigger", useMockPayload: true}` | `--mock-payload` |
189+
| Stop after one block | `run.stopAfterBlockId` | `--stop-after <blockId>` |
190+
| Rerun from a block, reusing an earlier run's upstream outputs | `run.entry: {type: "block", blockId, sourceRunId}` | `--from-block <blockId> --source-run <runId>` |
191+
| Rerun one block only | Block entry plus `stopAfterBlockId` naming the same block | `--from-block` and `--stop-after` with the same block |
192+
193+
`stopAfterBlockId` cannot name a block inside a loop or parallel. A run that routes away from the stop block fails as soon as that is decided.
194+
195+
## 8. Deploy [#deploy]
196+
197+
Deploying creates a new version and activates it in the background. The response returns while the attempt may still be preparing.
198+
199+
<Tabs items={['curl', 'CLI']}>
200+
<Tab value="curl">
201+
```bash
202+
curl -sS --fail-with-body -X POST \
203+
"$BASE_URL/api/v2/workflows/$WORKFLOW_ID/deploy" \
204+
-H "X-API-Key: $SIM_API_KEY" -H 'Content-Type: application/json' \
205+
-d '{"name": "First release"}'
206+
207+
curl -sS --fail-with-body -H "X-API-Key: $SIM_API_KEY" \
208+
"$BASE_URL/api/v2/workflows/$WORKFLOW_ID/deployment" \
209+
| jq '.data.latestDeploymentAttempt.status'
210+
```
211+
</Tab>
212+
<Tab value="CLI">
213+
```bash
214+
sim workflows deploy "$WORKFLOW_ID" --name "First release"
215+
sim workflows deployment status "$WORKFLOW_ID"
216+
```
217+
</Tab>
218+
</Tabs>
219+
220+
Poll `GET /api/v2/workflows/{workflowId}/deployment` until `latestDeploymentAttempt.status` is `active`, or `failed` with an `error`. The deploy response also carries advisory `lint` for the deployed version. Each accepted deploy creates a version, so retrying after a timeout can create a duplicate; check the deployment first. The deployment read also reports `needsRedeployment` when the draft has changed, and `webhooks` with the public URL for each webhook trigger.
221+
222+
## 9. Run the deployment [#execute]
223+
224+
Omit `run` to execute the active deployment. Choose how to wait:
225+
226+
| Mode | Request | Response |
227+
| --- | --- | --- |
228+
| Synchronous | Default | `200` with `status`, `output`, and `error` |
229+
| Bounded wait | `waitTimeoutSeconds` | The result, or a `202` receipt when the run is still going; the run continues in the background |
230+
| Asynchronous | `async: true` | `202` with `runId` and `statusUrl` |
231+
232+
<Tabs items={['curl', 'CLI']}>
233+
<Tab value="curl">
234+
```bash
235+
RUN_ID="run_$(uuidgen | tr 'A-Z' 'a-z')"
236+
curl -sS --fail-with-body -X POST \
237+
"$BASE_URL/api/v2/workflows/$WORKFLOW_ID/execute" \
238+
-H "X-API-Key: $SIM_API_KEY" -H 'Content-Type: application/json' \
239+
-H "X-Run-Id: $RUN_ID" \
240+
-d '{"input": {"ticketId": "T-123"}, "async": true}'
241+
242+
curl -sS --fail-with-body -H "X-API-Key: $SIM_API_KEY" \
243+
"$BASE_URL/api/v2/workflows/$WORKFLOW_ID/runs/$RUN_ID?includeOutput=true"
244+
```
245+
</Tab>
246+
<Tab value="CLI">
247+
```bash
248+
RUN_ID="run_$(uuidgen | tr 'A-Z' 'a-z')"
249+
sim workflows run "$WORKFLOW_ID" --input '{"ticketId": "T-123"}' --async --run-id "$RUN_ID"
250+
sim workflows runs wait "$RUN_ID" --workflow "$WORKFLOW_ID"
251+
sim workflows runs get "$RUN_ID" --workflow "$WORKFLOW_ID" --include-output
252+
```
253+
</Tab>
254+
</Tabs>
255+
256+
Run failures are reported in the result, not as HTTP errors: branch on `status` (`completed`, `failed`, `paused`, `cancelled`). A run that exceeds its timeout returns `200` with `status: "failed"` and `error.code: "TIMEOUT"`. Poll the run resource until `status` is no longer `queued`, `pending`, `running`, or `redacting` (a finished run whose output is still being scrubbed); `sim workflows runs wait` does this and exits `0` completed, `1` failed, `2` cancelled, `3` needs input, or `4` timed out.
257+
258+
`X-Run-Id` claims a run ID so a retry cannot start a second run. After an uncertain response, retry with the same ID: if the ID was already claimed, the request returns `409` with `RUN_ID_CONFLICT` and does not replay the result. Read the run instead. Do not retry with a new or omitted ID, which can start another run.
259+
260+
## 10. Debug a failed run [#debugging]
261+
262+
A synchronous execute response names the failing block in `error.blockId`, `error.blockName`, and `error.blockType`. The polled run resource cannot attribute a block; read the run's trace spans instead:
263+
264+
<Tabs items={['curl', 'CLI']}>
265+
<Tab value="curl">
266+
```bash
267+
curl -sS --fail-with-body -H "X-API-Key: $SIM_API_KEY" \
268+
"$BASE_URL/api/v2/logs/$RUN_ID?includeWorkflowState=false" \
269+
| jq '.data.traceSpans'
270+
```
271+
</Tab>
272+
<Tab value="CLI">
273+
```bash
274+
sim logs get "$RUN_ID" --trace
275+
```
276+
</Tab>
277+
</Tabs>
278+
279+
Each span has `blockId`, `name`, `type`, `status`, `errorMessage`, `input`, `output`, timing, and nested `children`. Find the span with an error, fix that block with an `edit` operation, then rerun from it with `run.entry: {type: "block", blockId, sourceRunId}` to reuse the upstream outputs. Spans marked `contentWithheld` omit content that could not be shown safely. Trace spans expire on the workspace's retention schedule, so an empty `traceSpans` array does not prove none were recorded.
280+
281+
## 11. Resume a paused run [#approvals]
282+
283+
A Human in the Loop block pauses the run until someone responds. The run resource reports `status: "paused"` and lists each pause under `paused.points`, with its `contextId`, `kind`, and, for a human pause, the `inputFields` to fill. A `time` pause resumes on its own at `resumeAt`.
284+
285+
<Tabs items={['curl', 'CLI']}>
286+
<Tab value="curl">
287+
```bash
288+
curl -sS --fail-with-body -X POST \
289+
"$BASE_URL/api/v2/workflows/$WORKFLOW_ID/runs/$RUN_ID/resume" \
290+
-H "X-API-Key: $SIM_API_KEY" -H 'Content-Type: application/json' \
291+
-d '{"contextId": "CONTEXT_ID", "input": {"approved": true}}'
292+
```
293+
</Tab>
294+
<Tab value="CLI">
295+
```bash
296+
sim workflows runs resume "$RUN_ID" --workflow "$WORKFLOW_ID" \
297+
--context CONTEXT_ID --input '{"approved": true}'
298+
```
299+
</Tab>
300+
</Tabs>
301+
302+
Resume returns the resumed run's result (`200`) or a queue receipt (`202`). Poll the run resource as in step 9.
303+
304+
## Use Sim MCP [#use-sim-mcp]
305+
306+
AI agents can follow the same loop through [Sim MCP](/mcp/overview) (`https://mcp.sim.ai/mcp` on Sim Cloud, `/api/mcp` on a self-hosted deployment). Its tools are `search_operations`, `describe_operation`, `call_read_operation`, and `call_write_operation`. Operation names match the API reference, such as `listBlocks`, `getBlock`, `createWorkflow`, `applyWorkflowOperations`, `executeWorkflow`, `deployWorkflow`, `getWorkflowDeployment`, `getWorkflowRun`, `getLog`, and `resumeWorkflow`. Pass the dry run as `query: {"dryRun": true}`.
307+
308+
<Callout type="info">
309+
Use `describe_operation` for the current request schema instead of copying one into a prompt. Schemas change as Sim adds fields.
310+
</Callout>
311+
312+
MCP results are limited to 1 MiB and do not stream; see [MCP tools](/mcp/tools).
313+
314+
Coding agents that use the CLI can install the [Sim skills](https://github.com/simstudioai/skills), which teach this loop step by step (`bunx skills add simstudioai/skills --global`).
315+
316+
## Related docs
317+
318+
- [Apply workflow operations](/api-reference/workflows/applyWorkflowOperations)
319+
- [Execute workflow](/api-reference/workflows/executeWorkflowV2)
320+
- [Get workflow run](/api-reference/workflow-runs/getWorkflowRunV2)
321+
- [Get log](/api-reference/logs/getLog)
322+
- [CLI workflow commands](/cli/workflows)
323+
- [Sim MCP tools](/mcp/tools)

‎apps/docs/content/docs/api-reference/meta.json‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
"getting-started",
77
"authentication",
88
"---API guides---",
9+
"build-and-run-workflows",
910
"workflow-sync",
1011
"---Endpoints---",
1112
"(generated)/workflows",

‎apps/docs/content/docs/cli/workflows.mdx‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1247,5 +1247,6 @@ API request and response schemas: [createWorkflowFolder](/api-reference/workflow
12471247
## Related docs
12481248

12491249
- [Workflow builder](/workflows)
1250+
- [Build and run workflows with the API](/api-reference/build-and-run-workflows)
12501251
- [Import and workspace sync](/cli/workflow-sync)
12511252
- [Scripting and exit codes](/cli/scripting#exit-codes)

‎scripts/generate-cli-docs.ts‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,7 @@ const GROUP_DOCUMENTATION: Record<string, GroupDocumentation> = {
230230
description: 'Build, deploy, run, and inspect workflows from the terminal.',
231231
related: [
232232
['Workflow builder', '/workflows'],
233+
['Build and run workflows with the API', '/api-reference/build-and-run-workflows'],
233234
['Import and workspace sync', '/cli/workflow-sync'],
234235
['Scripting and exit codes', '/cli/scripting#exit-codes'],
235236
],

0 commit comments

Comments
 (0)