Skip to content

API guides

Build and run workflows with the API

Discover blocks, edit a draft with dry runs and lint, test it, deploy it, run it, and debug or resume runs.

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 command, and the same operations are available to AI agents through Sim MCP.

Prerequisites

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.

StepRequired access
Discover blocks and toolsWorkspace read access
Create a workflowWorkspace write access
Edit the graph and run the draftWorkspace write access with a personal API key or an OAuth token with api:write
DeployWorkspace Admin access with a personal API key or an OAuth token with api:write
Run the deploymentWorkspace read access with a full-access API key or an OAuth token with api:write

Workspace API keys cannot edit graphs, deploy, or run the draft; those requests return 403. See Authentication for key types and scopes. The CLI uses your sim login session or profile; set the workspace with --workspace or SIM_WORKSPACE.

1. Discover blocks

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.

Code
curl -sS --fail-with-body -H "X-API-Key: $SIM_API_KEY" \
  "$BASE_URL/api/v2/blocks?workspaceId=$WORKSPACE_ID&search=slack"

curl -sS --fail-with-body -H "X-API-Key: $SIM_API_KEY" \
  "$BASE_URL/api/v2/blocks/slack?workspaceId=$WORKSPACE_ID"

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.

2. Create a workflow

Code
curl -sS --fail-with-body -X POST "$BASE_URL/api/v2/workflows" \
  -H "X-API-Key: $SIM_API_KEY" -H 'Content-Type: application/json' \
  -d "{\"workspaceId\": \"$WORKSPACE_ID\", \"name\": \"Ticket triage\"}"

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.

3. Define inputs

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.

4. Preview edits with a dry run

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:

Code
{
  "operations": [
    {
      "operation_type": "add",
      "block_id": "lookup",
      "params": {
        "type": "function",
        "name": "Lookup",
        "inputs": { "code": "return { ticketId: <start.ticketId>, priority: 'high' }" }
      }
    },
    {
      "operation_type": "edit",
      "block_id": "START_ID",
      "params": {
        "inputs": { "inputFormat": [{ "name": "ticketId", "type": "string" }] },
        "connections": { "source": "lookup" }
      }
    }
  ]
}

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.

Add ?dryRun=true to validate and lint without saving:

Code
curl -sS --fail-with-body -X POST \
  "$BASE_URL/api/v2/workflows/$WORKFLOW_ID/operations?dryRun=true" \
  -H "X-API-Key: $SIM_API_KEY" -H 'Content-Type: application/json' \
  -d @ops.json

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.

5. Read the result and lint

Check these fields before applying, and again after:

FieldMeans
skippedOperations the engine declined, each with type, blockId, and reason
inputValidationErrorsBlock inputs that were dropped instead of saved
deferredEdges waiting for a target block. They apply when the target exists; do not resubmit them
lint.fieldIssuesBlocks with missingRequiredFields, which fail at run time, or values stranded on an inactive field mode
lint.unresolvedReferencesCredential, resource, tool, skill, or <block.output> references that will not resolve. They are saved anyway
lint.orphanBlocks, lint.sources, lint.emptyOutgoingPortsDisconnected blocks, unreachable blocks, and branches that lead nowhere
lint.manualRunIssueWhy 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
warningsNormalizations, plus a line counting lint findings by category

Lint is advisory. It never blocks a write or a deploy, so fix findings before you rely on the workflow.

6. Apply the batch

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.

Code
jq '. + {atomic: true}' ops.json | curl -sS --fail-with-body -X POST \
  "$BASE_URL/api/v2/workflows/$WORKFLOW_ID/operations" \
  -H "X-API-Key: $SIM_API_KEY" -H 'Content-Type: application/json' \
  -d @-

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.

7. Test the draft

Run the saved draft with run.source: "manual". Manual runs are synchronous or streamed, never async.

Code
curl -sS --fail-with-body -X POST \
  "$BASE_URL/api/v2/workflows/$WORKFLOW_ID/execute" \
  -H "X-API-Key: $SIM_API_KEY" -H 'Content-Type: application/json' \
  -d '{"run": {"source": "manual"}, "input": {"ticketId": "T-123"}}'

Narrow a test run with these options:

GoalRequestCLI
Enter through a specific triggerrun.entry: {type: "trigger", blockId}--trigger <blockId>
Use the trigger's mock payload instead of inputrun.entry: {type: "trigger", useMockPayload: true}--mock-payload
Stop after one blockrun.stopAfterBlockId--stop-after <blockId>
Rerun from a block, reusing an earlier run's upstream outputsrun.entry: {type: "block", blockId, sourceRunId}--from-block <blockId> --source-run <runId>
Rerun one block onlyBlock entry plus stopAfterBlockId naming the same block--from-block and --stop-after with the same block

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.

8. Deploy

Deploying creates a new version and activates it in the background. The response returns while the attempt may still be preparing.

Code
curl -sS --fail-with-body -X POST \
  "$BASE_URL/api/v2/workflows/$WORKFLOW_ID/deploy" \
  -H "X-API-Key: $SIM_API_KEY" -H 'Content-Type: application/json' \
  -d '{"name": "First release"}'

curl -sS --fail-with-body -H "X-API-Key: $SIM_API_KEY" \
  "$BASE_URL/api/v2/workflows/$WORKFLOW_ID/deployment" \
  | jq '.data.latestDeploymentAttempt.status'

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.

9. Run the deployment

Omit run to execute the active deployment. Choose how to wait:

ModeRequestResponse
SynchronousDefault200 with status, output, and error
Bounded waitwaitTimeoutSecondsThe result, or a 202 receipt when the run is still going; the run continues in the background
Asynchronousasync: true202 with runId and statusUrl
Code
RUN_ID="run_$(uuidgen | tr 'A-Z' 'a-z')"
curl -sS --fail-with-body -X POST \
  "$BASE_URL/api/v2/workflows/$WORKFLOW_ID/execute" \
  -H "X-API-Key: $SIM_API_KEY" -H 'Content-Type: application/json' \
  -H "X-Run-Id: $RUN_ID" \
  -d '{"input": {"ticketId": "T-123"}, "async": true}'

curl -sS --fail-with-body -H "X-API-Key: $SIM_API_KEY" \
  "$BASE_URL/api/v2/workflows/$WORKFLOW_ID/runs/$RUN_ID?includeOutput=true"

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.

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.

10. Debug a failed run

Both the execute response and the polled run resource name the failing block in error.blockId, error.blockName, and error.blockType. The run resource reads these from the run's trace, so they are absent when the trace is unavailable or hidden by your organization's permission groups, when no failed block in the trace recorded the run error, and for a timeout, cancellation, usage-limit, or invalid-input failure. Read the trace spans for the full picture:

Code
curl -sS --fail-with-body -H "X-API-Key: $SIM_API_KEY" \
  "$BASE_URL/api/v2/logs/$RUN_ID?includeWorkflowState=false" \
  | jq '.data.traceSpans'

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.

11. Resume a paused run

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.

Code
curl -sS --fail-with-body -X POST \
  "$BASE_URL/api/v2/workflows/$WORKFLOW_ID/runs/$RUN_ID/resume" \
  -H "X-API-Key: $SIM_API_KEY" -H 'Content-Type: application/json' \
  -d '{"contextId": "CONTEXT_ID", "input": {"approved": true}}'

Resume returns the resumed run's result (200) or a queue receipt (202). Poll the run resource as in step 9.

Use Sim MCP

AI agents can follow the same loop through Sim MCP (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}.

MCP results are limited to 1 MiB and do not stream; see MCP tools.

Coding agents that use the CLI can install the Sim skills, which teach this loop step by step (bunx skills add simstudioai/skills --global).

Was this helpful?
Suggest an edit