Skip to content

Commit 272de32

Browse files
feat(ui): deploy modal + copilot advertise the v2 execute surface
All 20 API-tab snippets move to POST /api/v2/workflows/{id}/execute with the nested {"input": ...} body, async as the "async": true body flag (X-Execution-Mode gone), status polling against the v2 executions resource, the third tab renamed Usage and pointed at /api/v2/billing/usage, and {data} envelope unwraps in the printed responses. Fixes the latent baseUrl derivation (endpoint.split('/api/workflows/')) that would have silently built garbage URLs under a v2 endpoint, and deletes dead code (exampleCommand across 3 sites, getAsyncExampleTitle). Copilot deploy/manage/serializer endpoint builders and the api_trigger bestPractices example follow (the latter also drops its hardcoded staging host). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CiHhAk2R1NryaS3R8n2yFz
1 parent 63fcfe2 commit 272de32

6 files changed

Lines changed: 51 additions & 67 deletions

File tree

apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/deploy/components/deploy-modal/components/api/api.tsx

Lines changed: 42 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import {
1212
Tooltip,
1313
} from '@sim/emcn'
1414
import { Check, Clipboard } from 'lucide-react'
15+
import { getBaseUrl } from '@/lib/core/utils/urls'
1516
import {
1617
AGENT_STREAM_PROTOCOL_HEADER_LABEL,
1718
AGENT_STREAM_PROTOCOL_V1,
@@ -24,7 +25,6 @@ interface WorkflowDeploymentInfo {
2425
deployedAt?: string
2526
apiKey: string
2627
endpoint: string
27-
exampleCommand: string
2828
needsRedeployment: boolean
2929
isPublicApi?: boolean
3030
}
@@ -39,7 +39,7 @@ interface ApiDeployProps {
3939
onSelectedStreamingOutputsChange: (outputs: string[]) => void
4040
}
4141

42-
type AsyncExampleType = 'execute' | 'status' | 'rate-limits'
42+
type AsyncExampleType = 'execute' | 'status' | 'usage'
4343
type CodeLanguage = 'curl' | 'python' | 'javascript' | 'typescript'
4444

4545
type CopiedState = {
@@ -97,19 +97,23 @@ export function ApiDeploy({
9797
return info.endpoint.replace(info.apiKey, '$SIM_API_KEY')
9898
}
9999

100-
const getPayloadObject = (): Record<string, unknown> => {
100+
/** The workflow's example input fields, parsed from the shared example command. */
101+
const getInputObject = (): Record<string, unknown> => {
101102
const inputExample = getInputFormatExample ? getInputFormatExample(false) : ''
102103
const match = inputExample.match(/-d\s*'([\s\S]*)'/)
103104
if (match) {
104105
try {
105106
return JSON.parse(match[1]) as Record<string, unknown>
106107
} catch {
107-
return { input: 'your data here' }
108+
return { key: 'value' }
108109
}
109110
}
110-
return { input: 'your data here' }
111+
return { key: 'value' }
111112
}
112113

114+
/** v2 body: the input nests under `input`; control fields are siblings. */
115+
const getPayloadObject = (): Record<string, unknown> => ({ input: getInputObject() })
116+
113117
const getStreamPayloadObject = (): Record<string, unknown> => {
114118
const payload: Record<string, unknown> = { ...getPayloadObject(), stream: true }
115119
if (selectedStreamingOutputs && selectedStreamingOutputs.length > 0) {
@@ -148,7 +152,7 @@ ${isPublic ? '' : ' "X-API-Key": os.environ.get("SIM_API_KEY"),\n'}
148152
json=${JSON.stringify(payload, null, 4).replace(/\n/g, '\n ')}
149153
)
150154
151-
print(response.json())`
155+
print(response.json()["data"])`
152156

153157
case 'javascript':
154158
return `const response = await fetch("${endpoint}", {
@@ -159,7 +163,7 @@ ${isPublic ? '' : ' "X-API-Key": process.env.SIM_API_KEY,\n'} "Content-Typ
159163
body: JSON.stringify(${JSON.stringify(payload)})
160164
});
161165
162-
const data = await response.json();
166+
const { data } = await response.json();
163167
console.log(data);`
164168

165169
case 'typescript':
@@ -171,7 +175,7 @@ ${isPublic ? '' : ' "X-API-Key": process.env.SIM_API_KEY,\n'} "Content-Typ
171175
body: JSON.stringify(${JSON.stringify(payload)})
172176
});
173177
174-
const data: Record<string, unknown> = await response.json();
178+
const { data }: { data: Record<string, unknown> } = await response.json();
175179
console.log(data);`
176180

177181
default:
@@ -261,8 +265,8 @@ while (true) {
261265
const getAsyncCommand = (): string => {
262266
if (!info) return ''
263267
const endpoint = getBaseEndpoint()
264-
const baseUrl = endpoint.split('/api/workflows/')[0]
265-
const payload = getPayloadObject()
268+
const baseUrl = getBaseUrl()
269+
const payload = { ...getPayloadObject(), async: true }
266270
const isPublic = info.isPublicApi
267271

268272
switch (asyncExampleType) {
@@ -271,7 +275,6 @@ while (true) {
271275
case 'curl':
272276
return `curl -X POST \\
273277
${isPublic ? '' : ' -H "X-API-Key: $SIM_API_KEY" \\\n'} -H "Content-Type: application/json" \\
274-
-H "X-Execution-Mode: async" \\
275278
-d '${JSON.stringify(payload)}' \\
276279
${endpoint}`
277280

@@ -282,40 +285,38 @@ import requests
282285
response = requests.post(
283286
"${endpoint}",
284287
headers={
285-
${isPublic ? '' : ' "X-API-Key": os.environ.get("SIM_API_KEY"),\n'} "Content-Type": "application/json",
286-
"X-Execution-Mode": "async"
288+
${isPublic ? '' : ' "X-API-Key": os.environ.get("SIM_API_KEY"),\n'} "Content-Type": "application/json"
287289
},
288290
json=${JSON.stringify(payload, null, 4).replace(/\n/g, '\n ')}
289291
)
290292
291-
job = response.json()
292-
print(job) # Contains jobId and executionId`
293+
job = response.json()["data"]
294+
print(job) # Contains executionId and statusUrl`
293295

294296
case 'javascript':
295297
return `const response = await fetch("${endpoint}", {
296298
method: "POST",
297299
headers: {
298-
${isPublic ? '' : ' "X-API-Key": process.env.SIM_API_KEY,\n'} "Content-Type": "application/json",
299-
"X-Execution-Mode": "async"
300+
${isPublic ? '' : ' "X-API-Key": process.env.SIM_API_KEY,\n'} "Content-Type": "application/json"
300301
},
301302
body: JSON.stringify(${JSON.stringify(payload)})
302303
});
303304
304-
const job = await response.json();
305-
console.log(job); // Contains jobId and executionId`
305+
const { data: job } = await response.json();
306+
console.log(job); // Contains executionId and statusUrl`
306307

307308
case 'typescript':
308309
return `const response = await fetch("${endpoint}", {
309310
method: "POST",
310311
headers: {
311-
${isPublic ? '' : ' "X-API-Key": process.env.SIM_API_KEY,\n'} "Content-Type": "application/json",
312-
"X-Execution-Mode": "async"
312+
${isPublic ? '' : ' "X-API-Key": process.env.SIM_API_KEY,\n'} "Content-Type": "application/json"
313313
},
314314
body: JSON.stringify(${JSON.stringify(payload)})
315315
});
316316
317-
const job: { jobId: string; executionId: string } = await response.json();
318-
console.log(job); // Contains jobId and executionId`
317+
const { data: job }: { data: { executionId: string; statusUrl: string } } =
318+
await response.json();
319+
console.log(job); // Poll statusUrl until status is terminal`
319320

320321
default:
321322
return ''
@@ -325,84 +326,84 @@ console.log(job); // Contains jobId and executionId`
325326
switch (language) {
326327
case 'curl':
327328
return `curl -H "X-API-Key: $SIM_API_KEY" \\
328-
${baseUrl}/api/jobs/JOB_ID_FROM_EXECUTION`
329+
${baseUrl}/api/v2/workflows/${workflowId}/executions/EXECUTION_ID`
329330

330331
case 'python':
331332
return `import os
332333
import requests
333334
334335
response = requests.get(
335-
"${baseUrl}/api/jobs/JOB_ID_FROM_EXECUTION",
336+
"${baseUrl}/api/v2/workflows/${workflowId}/executions/EXECUTION_ID",
336337
headers={"X-API-Key": os.environ.get("SIM_API_KEY")}
337338
)
338339
339-
status = response.json()
340-
print(status)`
340+
status = response.json()["data"]
341+
print(status) # status: queued | running | completed | failed | cancelled | paused`
341342

342343
case 'javascript':
343344
return `const response = await fetch(
344-
"${baseUrl}/api/jobs/JOB_ID_FROM_EXECUTION",
345+
"${baseUrl}/api/v2/workflows/${workflowId}/executions/EXECUTION_ID",
345346
{
346347
headers: { "X-API-Key": process.env.SIM_API_KEY }
347348
}
348349
);
349350
350-
const status = await response.json();
351+
const { data: status } = await response.json();
351352
console.log(status);`
352353

353354
case 'typescript':
354355
return `const response = await fetch(
355-
"${baseUrl}/api/jobs/JOB_ID_FROM_EXECUTION",
356+
"${baseUrl}/api/v2/workflows/${workflowId}/executions/EXECUTION_ID",
356357
{
357358
headers: { "X-API-Key": process.env.SIM_API_KEY }
358359
}
359360
);
360361
361-
const status: Record<string, unknown> = await response.json();
362+
const { data: status }: { data: Record<string, unknown> } = await response.json();
362363
console.log(status);`
363364

364365
default:
365366
return ''
366367
}
367368

368-
case 'rate-limits':
369+
case 'usage':
369370
switch (language) {
370371
case 'curl':
371372
return `curl -H "X-API-Key: $SIM_API_KEY" \\
372-
${baseUrl}/api/users/me/usage-limits`
373+
${baseUrl}/api/v2/billing/usage`
373374

374375
case 'python':
375376
return `import os
376377
import requests
377378
378379
response = requests.get(
379-
"${baseUrl}/api/users/me/usage-limits",
380+
"${baseUrl}/api/v2/billing/usage",
380381
headers={"X-API-Key": os.environ.get("SIM_API_KEY")}
381382
)
382383
383-
limits = response.json()
384-
print(limits)`
384+
limits = response.json()["data"]
385+
print(limits) # totalCredits + bySourceCredits breakdown`
385386

386387
case 'javascript':
387388
return `const response = await fetch(
388-
"${baseUrl}/api/users/me/usage-limits",
389+
"${baseUrl}/api/v2/billing/usage",
389390
{
390391
headers: { "X-API-Key": process.env.SIM_API_KEY }
391392
}
392393
);
393394
394-
const limits = await response.json();
395+
const { data: limits } = await response.json();
395396
console.log(limits);`
396397

397398
case 'typescript':
398399
return `const response = await fetch(
399-
"${baseUrl}/api/users/me/usage-limits",
400+
"${baseUrl}/api/v2/billing/usage",
400401
{
401402
headers: { "X-API-Key": process.env.SIM_API_KEY }
402403
}
403404
);
404405
405-
const limits: Record<string, unknown> = await response.json();
406+
const { data: limits }: { data: Record<string, unknown> } = await response.json();
406407
console.log(limits);`
407408

408409
default:
@@ -414,19 +415,6 @@ console.log(limits);`
414415
}
415416
}
416417

417-
const getAsyncExampleTitle = () => {
418-
switch (asyncExampleType) {
419-
case 'execute':
420-
return 'Execute Job'
421-
case 'status':
422-
return 'Check Status'
423-
case 'rate-limits':
424-
return 'Usage Limits'
425-
default:
426-
return 'Execute Job'
427-
}
428-
}
429-
430418
const handleCopy = (key: keyof CopiedState, value: string) => {
431419
navigator.clipboard.writeText(value)
432420
setCopied((prev) => ({ ...prev, [key]: true }))
@@ -564,7 +552,7 @@ console.log(limits);`
564552
options={[
565553
{ label: 'Execute Job', value: 'execute' },
566554
{ label: 'Check Status', value: 'status' },
567-
{ label: 'Usage Limits', value: 'rate-limits' },
555+
{ label: 'Usage', value: 'usage' },
568556
]}
569557
value={asyncExampleType}
570558
onChange={(value) => setAsyncExampleType(value as AsyncExampleType)}

apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/deploy/components/deploy-modal/deploy-modal.tsx

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,6 @@ interface WorkflowDeploymentInfoUI {
7777
deployedAt?: string
7878
apiKey: string
7979
endpoint: string
80-
exampleCommand: string
8180
needsRedeployment: boolean
8281
isPublicApi: boolean
8382
}
@@ -234,7 +233,7 @@ export function DeployModal({
234233
return null
235234
}
236235

237-
const endpoint = `${getBaseUrl()}/api/workflows/${workflowId}/execute`
236+
const endpoint = `${getBaseUrl()}/api/v2/workflows/${workflowId}/execute`
238237
const inputFormatExample = getInputFormatExample(selectedStreamingOutputs.length > 0)
239238
const placeholderKey = getApiHeaderPlaceholder()
240239

@@ -243,7 +242,6 @@ export function DeployModal({
243242
deployedAt: deploymentInfoData.deployedAt ?? undefined,
244243
apiKey: getApiKeyLabel(deploymentInfoData.apiKey),
245244
endpoint,
246-
exampleCommand: `curl -X POST -H "X-API-Key: ${placeholderKey}" -H "Content-Type: application/json"${inputFormatExample} ${endpoint}`,
247245
needsRedeployment: deploymentInfoData.needsRedeployment,
248246
isPublicApi: isPublicApiDisabled ? false : (deploymentInfoData.isPublicApi ?? false),
249247
}

apps/sim/blocks/blocks/api_trigger.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ export const ApiTriggerBlock: BlockConfig = {
1111
bestPractices: `
1212
- Can run the workflow manually to test implementation when this is the trigger point.
1313
- The input format determines variables accesssible in the following blocks. E.g. <api1.paramName>. You can set the value in the input format to test the workflow manually.
14-
- In production, the curl would come in as e.g. curl -X POST -H "X-API-Key: $SIM_API_KEY" -H "Content-Type: application/json" -d '{"paramName":"example"}' https://www.staging.sim.ai/api/workflows/9e7e4f26-fc5e-4659-b270-7ea474b14f4a/execute -- If user asks to test via API, you might need to clarify the API key.
14+
- In production, the curl would come in as e.g. curl -X POST -H "X-API-Key: $SIM_API_KEY" -H "Content-Type: application/json" -d '{"input":{"paramName":"example"}}' https://www.sim.ai/api/v2/workflows/9e7e4f26-fc5e-4659-b270-7ea474b14f4a/execute -- If user asks to test via API, you might need to clarify the API key.
1515
`,
1616
category: 'triggers',
1717
hideFromToolbar: true,

apps/sim/lib/copilot/tools/handlers/deployment/deploy.ts

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ import { ensureWorkflowAccess } from '../access'
3030
import type { DeployApiParams, DeployChatParams, DeployMcpParams } from '../param-types'
3131

3232
function buildWorkflowApiEndpoint(baseUrl: string, workflowId: string): string {
33-
return `${baseUrl}/api/workflows/${workflowId}/execute`
33+
return `${baseUrl}/api/v2/workflows/${workflowId}/execute`
3434
}
3535

3636
function buildWorkflowApiConfig(baseUrl: string, apiEndpoint: string) {
@@ -57,9 +57,8 @@ function buildWorkflowApiConfig(baseUrl: string, apiEndpoint: string) {
5757
method: 'POST',
5858
transport: 'json',
5959
stream: false,
60-
headers: { 'X-Execution-Mode': 'async' },
61-
body: { input: { key: 'value' } },
62-
jobStatusEndpointTemplate: `${baseUrl}/api/jobs/{jobId}`,
60+
body: { async: true, input: { key: 'value' } },
61+
jobStatusEndpointTemplate: `${baseUrl}/api/v2/workflows/{workflowId}/executions/{executionId}`,
6362
},
6463
},
6564
}
@@ -78,9 +77,8 @@ function buildWorkflowApiExamples(baseUrl: string, apiEndpoint: string) {
7877
async: `curl -X POST "${apiEndpoint}" \\
7978
-H "Content-Type: application/json" \\
8079
-H "X-API-Key: YOUR_API_KEY" \\
81-
-H "X-Execution-Mode: async" \\
82-
-d '{"input":{"key":"value"}}'`,
83-
poll: `curl "${baseUrl}/api/jobs/JOB_ID" \\
80+
-d '{"async":true,"input":{"key":"value"}}'`,
81+
poll: `curl "${baseUrl}/api/v2/workflows/WORKFLOW_ID/executions/EXECUTION_ID" \\
8482
-H "X-API-Key: YOUR_API_KEY"`,
8583
}
8684
}

apps/sim/lib/copilot/tools/handlers/deployment/manage.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ export async function executeCheckDeploymentStatus(
8282
const apiDetails = {
8383
isDeployed: isApiDeployed,
8484
deployedAt: apiDeploy[0]?.deployedAt || null,
85-
endpoint: isApiDeployed ? `/api/workflows/${workflowId}/execute` : null,
85+
endpoint: isApiDeployed ? `/api/v2/workflows/${workflowId}/execute` : null,
8686
apiKey: workflowRecord.workspaceId ? 'Workspace API keys' : 'Personal API keys',
8787
needsRedeployment,
8888
activeDeployment: deploymentSummary.activeDeployment,

apps/sim/lib/copilot/vfs/serializers.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -808,7 +808,7 @@ export function serializeDeployments(data: DeploymentData): string {
808808
result.api = {
809809
isDeployed: true,
810810
deployedAt: data.deployedAt?.toISOString(),
811-
apiEndpoint: `/api/workflows/${data.workflowId}/execute`,
811+
apiEndpoint: `/api/v2/workflows/${data.workflowId}/execute`,
812812
...(data.api ? { version: data.api.version } : {}),
813813
}
814814
}

0 commit comments

Comments
 (0)