Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
1f7728b
fix(execution): honor pinned deployment version
waleedlatif1 Nov 10, 2026
ff29edc
feat(api): pin a deployment version on v2 execute
waleedlatif1 Nov 10, 2026
8d7aa4f
feat(api): expose paused run pause points
waleedlatif1 Nov 10, 2026
ad2187b
fix(api): type the deployed-run helper's principal and keep the statu…
waleedlatif1 Nov 10, 2026
b025760
test(execution): prove pinned deployed runs against real Postgres
waleedlatif1 Nov 10, 2026
b10ab69
fix(execution): use deployed variables for async deployed runs
waleedlatif1 Nov 10, 2026
2911901
refactor(execution): share the deployment version load and pause disp…
waleedlatif1 Nov 10, 2026
c589406
chore(api): document how to run the pinned execute e2e script
waleedlatif1 Nov 10, 2026
98f6bd8
fix(execution): use deployed variables for scheduled and webhook runs
waleedlatif1 Nov 10, 2026
7a96b17
fix(execution): run a queued API run on the version it loaded
waleedlatif1 Nov 10, 2026
ceba330
fix(api): withhold pause display on the run output's terms
waleedlatif1 Nov 10, 2026
bccb528
docs(api): document running a deployment version and paused run points
waleedlatif1 Nov 10, 2026
91a1505
test(mothership): record the run detail's additive version field in t…
waleedlatif1 Nov 10, 2026
4b311c5
fix(execution): report a resuming run's version and list pause displa…
waleedlatif1 Nov 10, 2026
57a2bf0
test(execution): pin the webhook case to a superseded version and tig…
waleedlatif1 Nov 10, 2026
6a8400d
docs(api): record the new run-a-version example in the content baseline
waleedlatif1 Nov 10, 2026
2b7478e
test(mothership): include the run version in the v2 run fixture
waleedlatif1 Nov 10, 2026
5a1790c
Merge remote-tracking branch 'origin/staging' into feat/v2-execute-ve…
waleedlatif1 Nov 10, 2026
d3943d9
test(execution): remove run snapshots in the new integration fixtures
waleedlatif1 Nov 10, 2026
123a182
Merge remote-tracking branch 'origin/staging' into feat/v2-execute-ve…
waleedlatif1 Nov 10, 2026
9c0e642
fix(api): withhold resolved pause input fields from viewers whose gro…
waleedlatif1 Nov 10, 2026
a6f07f0
fix(api): list a paused run's points when read by a resume attempt id
waleedlatif1 Nov 10, 2026
7cc22a7
Merge remote-tracking branch 'origin/staging' into feat/v2-execute-ve…
waleedlatif1 Nov 10, 2026
36c7cc9
fix(api): compute pause queue positions over the whole pending queue
waleedlatif1 Nov 10, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions apps/docs/content/docs/workflows/deployment/api.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,17 @@ You need workspace **Admin** access and the applicable deployment permission. Te

Ordinary API calls use the active snapshot. Editing the draft does not change that version. Deploy an update before expecting a normal API call to use your changes.

### Run an earlier version [#run-a-version]

To run a specific deployment version instead of the active one, name its number in `run.version`. The workflow must still be deployed, and the caller needs workspace **Write** access with an API key or OAuth access token; anonymous public-API callers cannot pin a version. An unknown version returns `404`. The run's status reports the version it ran in `version`.

```bash
curl -X POST 'https://www.sim.ai/api/v2/workflows/{workflow-id}/execute' \
-H "X-API-Key: $SIM_API_KEY" \
-H 'Content-Type: application/json' \
-d '{"input":{"message":"Summarize this article"},"run":{"source":"deployment","version":3}}'
```

## Authentication

Send a valid Sim API key in `X-API-Key`, or use a supported OAuth access token. Deployed workflow execution requires access to the workflow's workspace; OAuth callers also need the operation's `api:write` scope. See [API authentication](/api-reference/authentication).
Expand Down Expand Up @@ -249,6 +260,8 @@ A completed response includes the result when requested:

Stop polling on `completed`, `failed`, or `cancelled`. A paused Human run requires submitted input; a time pause can resume automatically. See [Human](/workflows/blocks/human-in-the-loop) and [Wait](/workflows/blocks/wait). Resuming an originally streaming run through the v2 resume endpoint returns an async receipt, not an SSE continuation.

A paused run lists every point it waits at in `paused.points`: the block, whether it is a `human` or `time` pause, its resume state, any parallel branch or loop iteration, its place in the resume queue, and the `inputFields` a Human pause asks for. With `includeOutput=true`, each point also carries the reviewer `display` content with resolved secrets redacted; organization permission groups that hide execution data withhold it, along with `inputFields`, whose labels and descriptions are resolved from run data. Resume a point by sending its `contextId` to `POST /api/v2/workflows/{workflow-id}/runs/{runId}/resume`.

## Select outputs and handle large values [#oversized-outputs]

`selectedOutputs` can project smaller fields. For a synchronous v2 call, selected values appear in `data.blockOutputs`, separately from `data.output`.
Expand Down
258 changes: 252 additions & 6 deletions apps/docs/openapi-v2-workflows.json

Large diffs are not rendered by default.

7 changes: 7 additions & 0 deletions apps/docs/scripts/fixtures/content-baseline.json
Original file line number Diff line number Diff line change
Expand Up @@ -181727,6 +181727,13 @@
],
"expressions": [],
"codeFences": [
{
"lang": "bash",
"meta": null,
"sha256": "32c20ca5dbc0941290361d3ddba31d3585a80493e65c486960e2660fae4c03d0",
"bytes": 243,
"line": 21
},
{
"lang": "bash",
"meta": null,
Expand Down
22 changes: 21 additions & 1 deletion apps/sim/app/api/v2/workflows/[workflowId]/execute/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,10 @@ import {
executeManualWorkflowFromBlockOperation,
executeManualWorkflowOperation,
} from '@/lib/workflows/application/execute-manual-workflow'
import { executeWorkflowOperation } from '@/lib/workflows/application/execute-workflow'
import {
executeWorkflowOperation,
executeWorkflowVersionOperation,
} from '@/lib/workflows/application/execute-workflow'
import { workflowOperations } from '@/lib/workflows/application/operations'
import {
type ExecuteWorkflowServiceFailure,
Expand Down Expand Up @@ -316,6 +319,8 @@ type V2ExecuteWorkflowRouteContext = {
*
* - Auth: `X-API-Key` (personal/workspace) or the anonymous public-API path for
* workflows deployed with `isPublicApi` (actor = owner; sync/stream only).
* - `run: { source: 'deployment', version }` runs that deployment version under
* the write-role `workflows.execute_version` operation; keyed callers only.
* - `async: true` (body flag — v2 has no mode headers) → 202
* `{ data: { runId, statusUrl } }`; poll the v2 runs resource.
* - `stream: true` → SSE passthrough (no `{data}` envelope on event frames).
Expand Down Expand Up @@ -422,6 +427,7 @@ export const POST = withRouteHandler(
if (!parsed.success) return parsed.response
const body = parsed.data.body
const manualRun = body.run?.source === 'manual' ? body.run : undefined
const pinnedVersion = body.run?.source === 'deployment' ? body.run.version : undefined

if (manualRun && !apiKeyPrincipal) {
return v2Error(
Expand All @@ -439,6 +445,9 @@ export const POST = withRouteHandler(
) {
return v2Error('BAD_REQUEST', 'input and run.entry.useMockPayload cannot be combined')
}
if (pinnedVersion !== undefined && isPublicApiAccess) {
return v2Error('BAD_REQUEST', 'run.version requires an OAuth access token or API key')
}

const detachable = body.waitTimeoutSeconds !== undefined
if (detachable && (body.async || body.stream)) {
Expand Down Expand Up @@ -544,6 +553,17 @@ export const POST = withRouteHandler(
},
request: req,
})
} else if (pinnedVersion !== undefined) {
result = await executeWorkflowVersionOperation.execute({
principal: apiKeyPrincipal,
input: {
...commonInput,
input: body.input ?? {},
mode: body.async ? 'async' : body.stream ? 'stream' : syncMode,
version: pinnedVersion,
},
request: req,
})
} else {
result = await executeWorkflowOperation.execute({
principal: apiKeyPrincipal,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ const baseStatus = {
startedAt: '2026-07-31T00:00:00.000Z',
endedAt: '2026-07-31T00:00:05.000Z',
totalDurationMs: 5000,
deploymentVersion: null,
paused: null,
cost: { total: 0.02 },
error: 'Send Email: Invalid credentials',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,13 @@ export const GET = defineV2JsonRoute({
startedAt: status.startedAt,
endedAt: status.endedAt,
durationMs: status.totalDurationMs,
paused: status.paused ? v2WorkflowRunStatusSchema.shape.paused.parse(status.paused) : null,
version: status.deploymentVersion ?? null,
paused: status.paused
? v2WorkflowRunStatusSchema.shape.paused.parse({
...status.paused,
points: status.pausePoints ?? [],
})
: null,
cost: status.cost,
error: status.error ? classifyExecutionError(new Error(status.error)) : null,
output: status.finalOutput,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ describe('internal execution status route projection subject', () => {
startedAt: '2026-08-05T12:00:00.000Z',
endedAt: null,
totalDurationMs: null,
deploymentVersion: null,
paused: null,
cost: null,
error: null,
Expand Down
Loading
Loading