Skip to content

Commit f32dc83

Browse files
improvement(api): replace workflow jobs with execution resources (#6294)
* improvement(api): replace workflow jobs with execution resources * fix(api): preserve legacy jobs while preferring v2 executions * fix(api): make execution polling resume-aware * fix(ui): hide async examples for public workflows * fix(api): bridge resume queue visibility lag * feat(api): add v2 workflow resume endpoint * fix(api): project pending resume attempts * fix(api): prefer terminal logs over stale resumes
1 parent 701400e commit f32dc83

33 files changed

Lines changed: 2269 additions & 920 deletions

File tree

apps/docs/content/docs/en/api-reference/getting-started.mdx

Lines changed: 32 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -49,28 +49,28 @@ A workflow must be deployed before it can be executed via the API. Click the **D
4949
<Tabs items={['curl', 'TypeScript', 'Python']}>
5050
<Tab value="curl">
5151
```bash
52-
curl -X POST https://www.sim.ai/api/workflows/{workflowId}/execute \
52+
curl -X POST https://www.sim.ai/api/v2/workflows/{workflowId}/execute \
5353
-H "Content-Type: application/json" \
5454
-H "X-API-Key: YOUR_API_KEY" \
55-
-d '{"inputs": {}}'
55+
-d '{"input": {}}'
5656
```
5757
</Tab>
5858
<Tab value="TypeScript">
5959
```typescript
6060
const response = await fetch(
61-
`https://www.sim.ai/api/workflows/${workflowId}/execute`,
61+
`https://www.sim.ai/api/v2/workflows/${workflowId}/execute`,
6262
{
6363
method: 'POST',
6464
headers: {
6565
'Content-Type': 'application/json',
6666
'X-API-Key': process.env.SIM_API_KEY!,
6767
},
68-
body: JSON.stringify({ inputs: {} }),
68+
body: JSON.stringify({ input: {} }),
6969
}
7070
)
7171

7272
const data = await response.json()
73-
console.log(data.output)
73+
console.log(data.data.output)
7474
```
7575
</Tab>
7676
<Tab value="Python">
@@ -79,16 +79,16 @@ A workflow must be deployed before it can be executed via the API. Click the **D
7979
import os
8080

8181
response = requests.post(
82-
f"https://www.sim.ai/api/workflows/{workflow_id}/execute",
82+
f"https://www.sim.ai/api/v2/workflows/{workflow_id}/execute",
8383
headers={
8484
"Content-Type": "application/json",
8585
"X-API-Key": os.environ["SIM_API_KEY"],
8686
},
87-
json={"inputs": {}},
87+
json={"input": {}},
8888
)
8989

9090
data = response.json()
91-
print(data["output"])
91+
print(data["data"]["output"])
9292
```
9393
</Tab>
9494
</Tabs>
@@ -103,77 +103,61 @@ By default, workflow executions are **synchronous** — the API blocks until the
103103
For long-running workflows, use **asynchronous execution** by passing `async: true`:
104104

105105
```bash
106-
curl -X POST https://www.sim.ai/api/workflows/{workflowId}/execute \
106+
curl -X POST https://www.sim.ai/api/v2/workflows/{workflowId}/execute \
107107
-H "Content-Type: application/json" \
108108
-H "X-API-Key: YOUR_API_KEY" \
109-
-d '{"inputs": {}, "async": true}'
109+
-d '{"input": {}, "async": true}'
110110
```
111111

112-
This returns immediately with a `jobId` and `statusUrl`:
112+
This returns immediately with an `executionId` and `statusUrl`:
113113

114114
```json
115115
{
116-
"success": true,
117-
"jobId": "job_abc123",
118-
"statusUrl": "https://www.sim.ai/api/jobs/job_abc123",
119-
"message": "Workflow execution started",
120-
"async": true
116+
"data": {
117+
"executionId": "c7a92e15-3f4b-4d8c-a1e6-9b0d5f2c8e74",
118+
"statusUrl": "https://www.sim.ai/api/v2/workflows/{workflowId}/executions/c7a92e15-3f4b-4d8c-a1e6-9b0d5f2c8e74"
119+
}
121120
}
122121
```
123122

124-
Poll the [Get Job Status](/api-reference/execution/getJobStatus) endpoint until the status is `completed` or `failed`:
123+
Poll the [Get Execution Status](/api-reference/execution/getWorkflowExecution) endpoint until the status is terminal:
125124

126125
```bash
127-
curl https://www.sim.ai/api/jobs/{jobId} \
126+
curl https://www.sim.ai/api/v2/workflows/{workflowId}/executions/{executionId}?includeOutput=true \
128127
-H "X-API-Key: YOUR_API_KEY"
129128
```
130129

131130
<Callout type="info">
132-
Job status transitions follow: `queued``processing``completed` or `failed`. The `output` field is only present when status is `completed`.
131+
Execution status transitions follow: `queued``running``completed`, `failed`, `cancelled`, or `paused`. The `data.output` field is populated for completed executions when `includeOutput=true`.
133132
</Callout>
134133

135134
## Response Format
136135

137-
Successful responses include an `output` object with your workflow results and a `limits` object with your current rate limit and usage status:
136+
Successful v2 responses wrap the execution resource in `data`:
138137

139138
```json
140139
{
141-
"success": true,
142-
"output": {
143-
"result": "Hello, world!"
144-
},
145-
"limits": {
146-
"workflowExecutionRateLimit": {
147-
"sync": {
148-
"requestsPerMinute": 60,
149-
"maxBurst": 10,
150-
"remaining": 59,
151-
"resetAt": "2025-01-01T00:01:00Z"
152-
},
153-
"async": {
154-
"requestsPerMinute": 30,
155-
"maxBurst": 5,
156-
"remaining": 30,
157-
"resetAt": "2025-01-01T00:01:00Z"
158-
}
159-
},
160-
"usage": {
161-
"currentPeriodCost": 1.25,
162-
"limit": 50.00,
163-
"plan": "pro",
164-
"isExceeded": false
165-
}
140+
"data": {
141+
"executionId": "c7a92e15-3f4b-4d8c-a1e6-9b0d5f2c8e74",
142+
"workflowId": "{workflowId}",
143+
"status": "completed",
144+
"output": { "result": "Hello, world!" },
145+
"error": null,
146+
"durationMs": 842
166147
}
167148
}
168149
```
169150

170151
## Error Handling
171152

172-
The API uses standard HTTP status codes. Error responses include a human-readable `error` message:
153+
The API uses standard HTTP status codes. v2 errors include a stable code and human-readable message:
173154

174155
```json
175156
{
176-
"error": "Workflow not found"
157+
"error": {
158+
"code": "NOT_FOUND",
159+
"message": "Workflow not found"
160+
}
177161
}
178162
```
179163

@@ -191,7 +175,7 @@ The API uses standard HTTP status codes. Error responses include a human-readabl
191175

192176
## Rate Limits
193177

194-
Rate limits depend on your subscription plan and apply separately to synchronous and asynchronous executions. Every execution response includes a `limits` object showing your current rate limit status.
178+
Rate limits depend on your subscription plan and apply separately to synchronous and asynchronous executions.
195179

196180
When rate limited, the API returns a `429` response with a `Retry-After` header indicating how many seconds to wait before retrying.
197181

apps/docs/content/docs/en/api-reference/python.mdx

Lines changed: 40 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ result = client.execute_workflow(
8080

8181
**Returns:** `WorkflowExecutionResult | AsyncExecutionResult`
8282

83-
When `async_execution=True`, returns immediately with a `job_id` and `status_url` for polling. Otherwise, waits for completion.
83+
When `async_execution=True`, returns immediately with an `execution_id` and `status_url` for polling. Otherwise, waits for completion.
8484

8585
##### get_workflow_status()
8686

@@ -112,30 +112,42 @@ if is_ready:
112112

113113
**Returns:** `bool`
114114

115-
##### get_job_status()
115+
##### get_workflow_execution()
116116

117-
Get the status of an async job execution.
117+
Get the status and optional outputs of a workflow execution.
118118

119119
```python
120-
status = client.get_job_status("job-id-from-async-execution")
121-
print("Status:", status["status"]) # 'queued', 'processing', 'completed', 'failed'
120+
status = client.get_workflow_execution("workflow-id", "execution-id", include_output=True)
121+
print("Status:", status["status"]) # 'queued', 'running', 'completed', 'failed'
122122
if status["status"] == "completed":
123123
print("Output:", status["output"])
124124
```
125125

126126
**Parameters:**
127-
- `task_id` (str): The job ID returned from async execution
127+
- `workflow_id` (str): The workflow ID
128+
- `execution_id` (str): The execution ID returned from async execution
129+
- `include_output` (bool, optional): Include the final output for completed executions
130+
- `selected_outputs` (list[str], optional): Block output selectors to include
128131

129132
**Returns:** `Dict[str, Any]`
130133

131134
**Response fields:**
132-
- `success` (bool): Whether the request was successful
133-
- `taskId` (str): The job ID
134-
- `status` (str): One of `'queued'`, `'processing'`, `'completed'`, `'failed'`, `'cancelled'`
135-
- `metadata` (dict): Contains `startedAt`, `completedAt`, and `duration`
136-
- `output` (any, optional): The workflow output (when completed)
137-
- `error` (any, optional): Error details (when failed)
138-
- `estimatedDuration` (int, optional): Estimated duration in milliseconds (when processing/queued)
135+
- `executionId` (str): The execution ID
136+
- `workflowId` (str): The workflow ID
137+
- `status` (str): One of `'queued'`, `'pending'`, `'running'`, `'paused'`, `'completed'`, `'failed'`, `'cancelled'`
138+
- `startedAt` / `endedAt` (str): Execution timestamps
139+
- `durationMs` (int, optional): Duration in milliseconds
140+
- `output` (any, optional): The workflow output when requested for a completed execution
141+
- `blockOutputs` (dict, optional): Requested block outputs
142+
- `error` (dict, optional): Structured failure details with `code`, `message`, and optional `details`
143+
144+
##### get_job_status()
145+
146+
Get the status of a job created through the legacy async execution endpoint. New integrations should use `get_workflow_execution()` with the execution ID instead.
147+
148+
```python
149+
status = client.get_job_status("legacy-job-id")
150+
```
139151

140152
##### execute_with_retry()
141153

@@ -270,9 +282,8 @@ class WorkflowExecutionResult:
270282
@dataclass
271283
class AsyncExecutionResult:
272284
success: bool
273-
job_id: str
285+
execution_id: str
274286
status_url: str
275-
execution_id: Optional[str] = None
276287
message: str = ""
277288
async_execution: bool = True
278289
```
@@ -494,22 +505,26 @@ def execute_async():
494505
)
495506

496507
# Check if result is an async execution
497-
if hasattr(result, 'job_id'):
498-
print(f"Job ID: {result.job_id}")
508+
if hasattr(result, 'async_execution') and result.async_execution:
509+
print(f"Execution ID: {result.execution_id}")
499510
print(f"Status endpoint: {result.status_url}")
500511

501512
# Poll for completion
502-
status = client.get_job_status(result.job_id)
513+
status = client.get_workflow_execution(
514+
"workflow-id", result.execution_id, include_output=True
515+
)
503516

504-
while status["status"] in ["queued", "processing"]:
517+
while status["status"] in ["queued", "pending", "running"]:
505518
print(f"Current status: {status['status']}")
506519
time.sleep(2) # Wait 2 seconds
507-
status = client.get_job_status(result.job_id)
520+
status = client.get_workflow_execution(
521+
"workflow-id", result.execution_id, include_output=True
522+
)
508523

509524
if status["status"] == "completed":
510525
print("Workflow completed!")
511526
print(f"Output: {status['output']}")
512-
print(f"Duration: {status['metadata']['duration']}")
527+
print(f"Duration: {status['durationMs']}")
513528
else:
514529
print(f"Workflow failed: {status['error']}")
515530

@@ -656,13 +671,13 @@ def stream_workflow():
656671

657672
def generate():
658673
response = requests.post(
659-
'https://sim.ai/api/workflows/WORKFLOW_ID/execute',
674+
'https://sim.ai/api/v2/workflows/WORKFLOW_ID/execute',
660675
headers={
661676
'Content-Type': 'application/json',
662677
'X-API-Key': os.getenv('SIM_API_KEY')
663678
},
664679
json={
665-
'message': 'Generate a story',
680+
'input': {'message': 'Generate a story'},
666681
'stream': True,
667682
'selectedOutputs': ['agent1.content']
668683
},
@@ -765,9 +780,9 @@ import { FAQ } from '@/components/ui/faq'
765780

766781
<FAQ items={[
767782
{ question: "Do I need to deploy a workflow before I can execute it via the SDK?", answer: "Yes. Workflows must be deployed before they can be executed through the SDK. You can use the validate_workflow() method to check whether a workflow is deployed and ready. If it returns False, deploy the workflow from the Sim UI first and create or select an API key during deployment." },
768-
{ question: "What is the difference between sync and async execution?", answer: "Sync execution (the default) blocks until the workflow completes and returns the full result. Async execution (async_execution=True) returns immediately with a job ID and status URL that you can poll using get_job_status(). Use async mode for long-running workflows to avoid request timeouts. Async job statuses include queued, processing, completed, failed, and cancelled." },
783+
{ question: "What is the difference between sync and async execution?", answer: "Sync execution (the default) blocks until the workflow completes and returns the full result. Async execution (async_execution=True) returns immediately with an execution ID and status URL that you can poll using get_workflow_execution(). Use async mode for long-running workflows to avoid request timeouts. Execution statuses include queued, pending, running, paused, completed, failed, and cancelled." },
769784
{ question: "How does the SDK handle rate limiting?", answer: "The SDK provides built-in rate limiting support through the execute_with_retry() method. It uses exponential backoff (1s, 2s, 4s, 8s...) with 25% jitter to avoid thundering herd problems. If the API returns a retry-after header, that value is used instead. You can configure max_retries, initial_delay, max_delay, and backoff_multiplier. Use get_rate_limit_info() to check your current rate limit status." },
770785
{ question: "Can I use the Python SDK as a context manager?", answer: "Yes. The SimStudioClient supports Python's context manager protocol. Use it with the 'with' statement to automatically close the underlying HTTP session when you are done, which is especially useful for scripts that create and discard client instances." },
771786
{ question: "How do I handle different types of errors from the SDK?", answer: "The SDK raises SimStudioError with a code property for API-specific errors. Common error codes are UNAUTHORIZED (invalid API key), TIMEOUT (request timed out), RATE_LIMIT_EXCEEDED (too many requests), USAGE_LIMIT_EXCEEDED (billing limit reached), and EXECUTION_ERROR (workflow failed). Use the error code to implement targeted error handling and recovery logic." },
772787
{ question: "How do I monitor my API usage and remaining quota?", answer: "Use the get_usage_limits() method to check your current usage. It returns sync and async rate limit details (limit, remaining, reset time, whether you are currently limited), plus your current period cost, usage limit, and plan tier. This lets you monitor consumption and alert before hitting limits." },
773-
]} />
788+
]} />

0 commit comments

Comments
 (0)