diff --git a/.agents/skills/v2-api-conventions/SKILL.md b/.agents/skills/v2-api-conventions/SKILL.md index f947af3d68a..9438c8434cd 100644 --- a/.agents/skills/v2-api-conventions/SKILL.md +++ b/.agents/skills/v2-api-conventions/SKILL.md @@ -11,7 +11,7 @@ The v2 surface makes one promise: **every response is the same two shapes, and a ``` success (single) { "data": {...} } success (collection) { "data": [...], "nextCursor": "..." | null } -failure (always) { "error": { "code": "...", "message": "...", "details"?: ... } } +failure (always) { "error": { "code": "...", "message": "...", "details"?: ..., "requestId"?: "..." } } ``` Nothing else at the top level. No `success: true`, no bare `{ "error": "string" }`, no HTML. @@ -214,7 +214,7 @@ The consequence to keep true: **never put a resource id, filter, or scope into a Run this against any new or changed v2 endpoint. -- [ ] Success body is exactly `{data}` or `{data, nextCursor}`; failures are exactly `{error:{code,message,details?}}`. +- [ ] Success body is exactly `{data}` or `{data, nextCursor}`; failures are exactly `{error:{code,message,details?,requestId?}}`, where `v2Error` alone sets `requestId` (the `x-request-id` value). - [ ] Route uses a shared builder; no hand-built `NextResponse.json`. - [ ] Query and body schemas are `.strict()`. - [ ] The contract declares a `query` — `noInputSchema` when the endpoint takes no query params, never omission. diff --git a/.github/CI.md b/.github/CI.md index 314b375df05..bf04c91cf9d 100644 --- a/.github/CI.md +++ b/.github/CI.md @@ -1,6 +1,6 @@ # CI selection and acceptance ownership -The `changes` job runs Turborepo 2.9.14's `affectedTasks` query before installing application dependencies or provisioning services. `turbo.json` records package dependency propagation; `apps/sim/turbo.json` partitions the app into shared runtime, server, workspace UI, and acceptance tasks. `.github/ci-suites.json` turns those tasks into job matrices. The uploaded `ci-plan` artifact names changed files, selected tasks, attribution reasons, and any fallback. +The `changes` job runs Turborepo 2.11.10's `affectedTasks` query before installing application dependencies or provisioning services. `turbo.json` records package dependency propagation; `apps/sim/turbo.json` partitions the app into shared runtime, server, workspace UI, and acceptance tasks. `.github/ci-suites.json` turns those tasks into job matrices. The uploaded `ci-plan` artifact names changed files, selected tasks, attribution reasons, and any fallback. A PR compares the checked-out GitHub merge commit with its first parent: the actual target revision tested by that merge. The PR event's base SHA can become stale when the target advances. Scoping requires a matching GitHub merge ref and SHA, exactly two merge parents, and a usable target parent; otherwise every check runs. This works with the existing depth-two checkout without fetching older ancestry. Main/staging pushes execute all checks; the parent `ci` workflow skips checks on `dev` pushes, while PRs targeting `dev` still run checks. A manual `checks` run executes all checks unless supplied a full base commit SHA to reproduce a scoped plan. Missing history, an empty diff, unknown files outside the workspace graph, root script/CI changes, or a planner error run every check. Missing selection outputs fail the aggregate gate. The gate also rejects an omitted dependency, any failed/cancelled job, and any skip that its explicit selection flag did not authorize. @@ -24,7 +24,7 @@ Integration also depends on workspace UI because the desktop executor integratio SEO depends on workspace UI and server code: landing pages reuse workspace pricing and preview components, provider models/pricing, and the block registry. These directory boundaries are not isolated application dependency graphs. -`affectedTasks` already uses task inputs. Do not enable `futureFlags.affectedUsingTaskInputs` just for this planner: that flag also changes pruning, which can pull CI-only cross-app task dependencies into production Docker contexts. +`affectedTasks` matches task inputs only under a task-level future flag; without one it reports every task of an affected package. The root `turbo.json` enables `futureFlags.filterUsingTasks`, which CI's package-name `--filter` calls are unaffected by. Do not use `futureFlags.affectedUsingTaskInputs` instead: that flag also changes pruning, which pulls CI-only cross-app task dependencies into production Docker contexts. `affectedTasks.items` also lists the prerequisites of affected tasks, so the planner selects `docs#build` through its own task filter, which applies before prerequisites are added. Affected type-checks follow the package graph too; repository audits and lint run whenever the checks workflow runs. Workspace-only unit jobs use a free GitHub runner and do not start the app test shards. diff --git a/.github/scripts/ci-plan.gql b/.github/scripts/ci-plan.gql index 78dcbf3eaab..6ccefc87483 100644 --- a/.github/scripts/ci-plan.gql +++ b/.github/scripts/ci-plan.gql @@ -22,4 +22,10 @@ query($base: String!, $head: String!) { } } } + # `items` also lists the prerequisites of affected tasks, and docs#build is one for + # ci:e2e-docs-platform. The task filter applies before prerequisites are added, so this + # selection holds docs#build only when the docs site itself changed. + docsSite: affectedTasks(base: $base, head: $head, tasks: ["docs#build"]) { + items { fullName } + } } diff --git a/apps/desktop/package.json b/apps/desktop/package.json index 3f8e6924c29..fe5088f6726 100644 --- a/apps/desktop/package.json +++ b/apps/desktop/package.json @@ -58,7 +58,7 @@ "@types/react": "^19", "@types/react-dom": "^19", "electron": "43.8.1", - "electron-builder": "26.15.3", + "electron-builder": "26.18.0", "esbuild": "0.28.2", "jsdom": "^26.0.0", "postcss": "^8.5.30", diff --git a/apps/docs/components/ui/icon-mapping.ts b/apps/docs/components/ui/icon-mapping.ts index 39e82ed79c6..b4347637bd8 100644 --- a/apps/docs/components/ui/icon-mapping.ts +++ b/apps/docs/components/ui/icon-mapping.ts @@ -409,6 +409,7 @@ export const blockTypeToIconMap: Record = { credential: CredentialIcon, credential_group: GridOffset, crowdstrike: CrowdStrikeIcon, + crowdstrike_v2: CrowdStrikeIcon, crunchbase: CrunchbaseIcon, crunchbase_v2: CrunchbaseIcon, cursor: CursorIcon, @@ -418,6 +419,7 @@ export const blockTypeToIconMap: Record = { databricks: DatabricksIcon, databricks_v2: DatabricksIcon, datadog: DatadogIcon, + datadog_v2: DatadogIcon, datagma: DatagmaIcon, daytona: DaytonaIcon, daytona_v2: DaytonaIcon, @@ -487,6 +489,7 @@ export const blockTypeToIconMap: Record = { gmail: GmailIcon, gmail_v2: GmailIcon, gong: GongIcon, + gong_v2: GongIcon, google_ads: GoogleAdsIcon, google_appsheet: GoogleAppsheetIcon, google_bigquery: GoogleBigQueryIcon, @@ -658,6 +661,7 @@ export const blockTypeToIconMap: Record = { plane: PlaneIcon, plane_v2: PlaneIcon, planetscale: PlanetScaleIcon, + planetscale_v2: PlanetScaleIcon, polymarket: PolymarketIcon, postgresql: PostgresIcon, posthog: PosthogIcon, @@ -703,6 +707,7 @@ export const blockTypeToIconMap: Record = { s3: S3Icon, s3_v2: S3Icon, sailpoint: SailPointIcon, + sailpoint_v2: SailPointIcon, salesforce: SalesforceIcon, sap_concur: SapConcurIcon, sap_s4hana: SapS4HanaIcon, @@ -719,6 +724,7 @@ export const blockTypeToIconMap: Record = { serper: SerperIcon, servicenow: ServiceNowIcon, servicenow_v2: ServiceNowIcon, + servicenow_v3: ServiceNowIcon, ses: SESIcon, ses_v2: SESIcon, sftp: SftpIcon, @@ -823,12 +829,14 @@ export const blockTypeToIconMap: Record = { youtube: YouTubeIcon, youtube_v2: YouTubeIcon, zendesk: ZendeskIcon, + zendesk_v2: ZendeskIcon, zep: ZepIcon, zep_v2: ZepIcon, zerobounce: ZeroBounceIcon, zoho_desk: ZohoDeskIcon, zoom: ZoomIcon, zoominfo: ZoomInfoIcon, + zoominfo_v2: ZoomInfoIcon, } export const coreBlockTypes = new Set([ diff --git a/apps/docs/content/docs/api-reference/(generated)/workflow-runs/meta.json b/apps/docs/content/docs/api-reference/(generated)/workflow-runs/meta.json index 66d2d156e60..f719fdfe6f5 100644 --- a/apps/docs/content/docs/api-reference/(generated)/workflow-runs/meta.json +++ b/apps/docs/content/docs/api-reference/(generated)/workflow-runs/meta.json @@ -1,6 +1,7 @@ { "pages": [ "listWorkflowRunsV2", + "listPausedWorkflowRunsV2", "getWorkflowRunV2", "downloadWorkflowRunFileV2", "resumeWorkflowRunV2", diff --git a/apps/docs/content/docs/api-reference/(generated)/workflows/meta.json b/apps/docs/content/docs/api-reference/(generated)/workflows/meta.json index 4c6c32150a3..f4b795acb97 100644 --- a/apps/docs/content/docs/api-reference/(generated)/workflows/meta.json +++ b/apps/docs/content/docs/api-reference/(generated)/workflows/meta.json @@ -9,6 +9,7 @@ "duplicateWorkflow", "moveWorkflows", "getWorkflowState", + "getWorkflowOutline", "replaceWorkflowState", "applyWorkflowOperations", "applyWorkflowVariables", @@ -24,6 +25,7 @@ "updateWorkflowPublicApi", "listWorkflowTriggers", "updateWorkflowTrigger", + "listWorkspaceSchedules", "deployWorkflow", "undeployWorkflow", "rollbackWorkflow", diff --git a/apps/docs/content/docs/api-reference/authentication.mdx b/apps/docs/content/docs/api-reference/authentication.mdx index 58b962765fe..7a178c8e462 100644 --- a/apps/docs/content/docs/api-reference/authentication.mdx +++ b/apps/docs/content/docs/api-reference/authentication.mdx @@ -42,9 +42,9 @@ Delete a key from the same settings page to revoke it. Update applications that A read-only key cannot change workspace resources or run, resume, or cancel workflows. Reads can still cost credits: searches use embedding credits, and search chat uses model credits and keeps a private conversation history. Selector lookups call third-party services with your connected accounts. -A refused request fails with `403` and the message `This API key is read-only and cannot perform this operation`. Where the surface carries a code, it is `READ_ONLY_API_KEY`: in `error.details.code` on the v2 API and the Sim MCP server, in `details.code` on the v1 API and workflow execution, and in `error.data.code` on a workflow MCP server. +A refused request fails with `403` and the message `This API key is read-only and cannot perform this operation. Use a full-access API key.` Where the surface carries a code, it is `READ_ONLY_API_KEY`: in `error.details.code` on the v2 API and the Sim MCP server, in `details.code` on the v1 API and workflow execution, and in `error.data.code` on a workflow MCP server. -Access narrows a key; it never grants more than the key's identity already has. Access is fixed at creation, so create a new key to change it. Keys created before access levels existed have full access. Keys created from the deploy modal or for a workflow MCP server always have full access, because they exist to run workflows. If **Access** isn't shown when you create a key, read-only keys aren't available on your deployment yet. +Access narrows a key; it never grants more than the key's identity already has. Access is fixed at creation, so create a new key to change it. Keys created before access levels existed have full access. Keys created from the deploy modal or for a workflow MCP server always have full access, because they exist to run workflows. ### Expiration [#key-expiration] diff --git a/apps/docs/content/docs/api-reference/build-and-run-workflows.mdx b/apps/docs/content/docs/api-reference/build-and-run-workflows.mdx new file mode 100644 index 00000000000..efdd8e87316 --- /dev/null +++ b/apps/docs/content/docs/api-reference/build-and-run-workflows.mdx @@ -0,0 +1,323 @@ +--- +title: Build and run workflows with the API +description: Discover blocks, edit a draft with dry runs and lint, test it, deploy it, run it, and debug or resume runs. +--- + +import { Callout } from '@/components/ui/callout' +import { Tab, Tabs } from '@/components/ui/tabs' + +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). + +## 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. + +| Step | Required access | +| --- | --- | +| Discover blocks and tools | Workspace read access | +| Create a workflow | Workspace write access | +| Edit the graph and run the draft | Workspace write access with a personal API key or an OAuth token with `api:write` | +| Deploy | Workspace **Admin** access with a personal API key or an OAuth token with `api:write` | +| Run the deployment | Workspace 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](/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`. + +## 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. + + + + ```bash + 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" + ``` + + + ```bash + sim blocks list --search slack + sim blocks get slack + ``` + + + +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 + + + + ```bash + 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\"}" + ``` + + + ```bash + sim workflows create --name "Ticket triage" --output json + ``` + + + +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 [#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 ``. Set the field with an `edit` operation in the next step. + +## 4. Preview edits with a dry run [#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: + +```json +{ + "operations": [ + { + "operation_type": "add", + "block_id": "lookup", + "params": { + "type": "function", + "name": "Lookup", + "inputs": { "code": "return { 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: + + + + ```bash + 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 + ``` + + + ```bash + jq .operations ops.json | + sim workflows operations apply "$WORKFLOW_ID" --operations @- --dry-run + ``` + + + +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 [#lint] + +Check these fields before applying, and again after: + +| Field | Means | +| --- | --- | +| `skipped` | Operations the engine declined, each with `type`, `blockId`, and `reason` | +| `inputValidationErrors` | Block inputs that were dropped instead of saved | +| `deferred` | Edges waiting for a target block. They apply when the target exists; do not resubmit them | +| `lint.fieldIssues` | Blocks with `missingRequiredFields`, which fail at run time, or values stranded on an inactive field mode | +| `lint.unresolvedReferences` | Credential, resource, tool, skill, or `` references that will not resolve. They are saved anyway | +| `lint.orphanBlocks`, `lint.sources`, `lint.emptyOutgoingPorts` | Disconnected blocks, unreachable blocks, and branches that lead nowhere | +| `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 | +| `warnings` | Normalizations, 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] + +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`. + + + + ```bash + 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 @- + ``` + + + ```bash + jq .operations ops.json | + sim workflows operations apply "$WORKFLOW_ID" --operations @- --atomic --yes + ``` + + + +`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 [#test-run] + +Run the saved draft with `run.source: "manual"`. Manual runs are synchronous or streamed, never `async`. + + + + ```bash + 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"}}' + ``` + + + ```bash + sim workflows run "$WORKFLOW_ID" --manual --input '{"ticketId": "T-123"}' + ``` + + + +Narrow a test run with these options: + +| Goal | Request | CLI | +| --- | --- | --- | +| Enter through a specific trigger | `run.entry: {type: "trigger", blockId}` | `--trigger ` | +| Use the trigger's mock payload instead of `input` | `run.entry: {type: "trigger", useMockPayload: true}` | `--mock-payload` | +| Stop after one block | `run.stopAfterBlockId` | `--stop-after ` | +| Rerun from a block, reusing an earlier run's upstream outputs | `run.entry: {type: "block", blockId, sourceRunId}` | `--from-block --source-run ` | +| Rerun one block only | Block 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 [#deploy] + +Deploying creates a new version and activates it in the background. The response returns while the attempt may still be preparing. + + + + ```bash + 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' + ``` + + + ```bash + sim workflows deploy "$WORKFLOW_ID" --name "First release" + sim workflows deployment status "$WORKFLOW_ID" + ``` + + + +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 [#execute] + +Omit `run` to execute the active deployment. Choose how to wait: + +| Mode | Request | Response | +| --- | --- | --- | +| Synchronous | Default | `200` with `status`, `output`, and `error` | +| Bounded wait | `waitTimeoutSeconds` | The result, or a `202` receipt when the run is still going; the run continues in the background | +| Asynchronous | `async: true` | `202` with `runId` and `statusUrl` | + + + + ```bash + 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" + ``` + + + ```bash + RUN_ID="run_$(uuidgen | tr 'A-Z' 'a-z')" + sim workflows run "$WORKFLOW_ID" --input '{"ticketId": "T-123"}' --async --run-id "$RUN_ID" + sim workflows runs wait "$RUN_ID" --workflow "$WORKFLOW_ID" + sim workflows runs get "$RUN_ID" --workflow "$WORKFLOW_ID" --include-output + ``` + + + +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 [#debugging] + +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: + + + + ```bash + curl -sS --fail-with-body -H "X-API-Key: $SIM_API_KEY" \ + "$BASE_URL/api/v2/logs/$RUN_ID?includeWorkflowState=false" \ + | jq '.data.traceSpans' + ``` + + + ```bash + sim logs get "$RUN_ID" --trace + ``` + + + +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 [#approvals] + +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`. + + + + ```bash + 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}}' + ``` + + + ```bash + sim workflows runs resume "$RUN_ID" --workflow "$WORKFLOW_ID" \ + --context 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 [#use-sim-mcp] + +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}`. + + +Use `describe_operation` for the current request schema instead of copying one into a prompt. Schemas change as Sim adds fields. + + +MCP results are limited to 1 MiB and do not stream; see [MCP tools](/mcp/tools). + +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`). + +## Related docs + +- [Apply workflow operations](/api-reference/workflows/applyWorkflowOperations) +- [Execute workflow](/api-reference/workflows/executeWorkflowV2) +- [Get workflow run](/api-reference/workflow-runs/getWorkflowRunV2) +- [Get log](/api-reference/logs/getLog) +- [CLI workflow commands](/cli/workflows) +- [Sim MCP tools](/mcp/tools) diff --git a/apps/docs/content/docs/api-reference/getting-started.mdx b/apps/docs/content/docs/api-reference/getting-started.mdx index 9d0b352682e..71099990c3c 100644 --- a/apps/docs/content/docs/api-reference/getting-started.mdx +++ b/apps/docs/content/docs/api-reference/getting-started.mdx @@ -174,11 +174,14 @@ Rejected v2 requests use HTTP error status codes and a stable code/message envel { "error": { "code": "NOT_FOUND", - "message": "Workflow not found" + "message": "Workflow not found", + "requestId": "3f9a1c7e" } } ``` +Every response carries an `x-request-id` header, and an error repeats the same id as `error.requestId`. Quote it when you contact support about a failed request. + | Status | Meaning | Action | | --- | --- | --- | | `400` | Invalid request | Check the published schema and `error.details`, when present | @@ -195,13 +198,13 @@ Other statuses and endpoint-specific recovery are documented in each reference. V2 validates path parameters, query strings, and bodies against the published schema. Unknown fields return `400`; for example, `?limt=20` is an error. The rule also applies to endpoints with no query parameters: omit tracking tags, cache busters, and other undeclared fields. -A validation error can look like: +The message names the first rejected field, prefixed with where it was sent (`path`, `query`, `header`, or `body`); `error.details` lists every issue. A validation error can look like: ```json { "error": { "code": "BAD_REQUEST", - "message": "Invalid request", + "message": "query: Unrecognized key: \"limt\"", "details": [ { "code": "unrecognized_keys", "keys": ["limt"], "path": [], "message": "Unrecognized key: \"limt\"" } ] diff --git a/apps/docs/content/docs/api-reference/meta.json b/apps/docs/content/docs/api-reference/meta.json index 413272d21ab..2b87e7cd55d 100644 --- a/apps/docs/content/docs/api-reference/meta.json +++ b/apps/docs/content/docs/api-reference/meta.json @@ -6,6 +6,7 @@ "getting-started", "authentication", "---API guides---", + "build-and-run-workflows", "workflow-sync", "---Endpoints---", "(generated)/workflows", diff --git a/apps/docs/content/docs/cli/commands.mdx b/apps/docs/content/docs/cli/commands.mdx index e998b13dc7b..777595c9765 100644 --- a/apps/docs/content/docs/cli/commands.mdx +++ b/apps/docs/content/docs/cli/commands.mdx @@ -58,6 +58,7 @@ See [output formats](/cli/output) for the JSON default and display formats. | [`sim organizations`](/cli/organizations) | Manage organization membership, invitations, usage, access requests, and SSO. | | [`sim permission-groups`](/cli/permission-groups) | Manage organization permission groups, their configuration, and member assignments. | | [`sim sandboxes`](/cli/sandboxes) | Manage reusable dependency sets for Function blocks. | +| [`sim schedules`](/cli/schedules) | List the schedules deployed workflows run across a workspace. | | [`sim secrets`](/cli/secrets) | Set and manage workspace or personal secrets. | | [`sim selectors`](/cli/selectors) | Discover destination options for registered fields, such as calendars, channels, and tables. | | [`sim skills`](/cli/skills) | Create reusable agent instructions and manage their editors. | @@ -79,9 +80,10 @@ sim login [options] | Option | Required | Description | | --- | --- | --- | -| `--method ` | No | Credential to obtain: oauth requires OAuth support; api-key creates a permanent key through pairing (auto-selects when omitted). Accepted values: `oauth`, `api-key`. | +| `--method ` | No | Credential to obtain: oauth requires OAuth support; api-key creates an API key through pairing (auto-selects when omitted). Accepted values: `oauth`, `api-key`. | | `--no-browser` | No | Print the approval URL without opening it (either login method). | | `--read-only` | No | Ask only for permission to read, never to change anything. | +| `--expires-in ` | No | Log in with an API key that expires after this many days (1-365); without it an API key never expires. | | `--callback-port ` | No | Pin the local port the browser returns to. | | `-y, --yes` | No | Overwrite an existing API-key profile without prompting. | diff --git a/apps/docs/content/docs/cli/meta.json b/apps/docs/content/docs/cli/meta.json index 14ef23a60dd..71c788d6f2d 100644 --- a/apps/docs/content/docs/cli/meta.json +++ b/apps/docs/content/docs/cli/meta.json @@ -31,6 +31,7 @@ "organizations", "permission-groups", "sandboxes", + "schedules", "secrets", "selectors", "skills", diff --git a/apps/docs/content/docs/cli/reference.mdx b/apps/docs/content/docs/cli/reference.mdx index 981fdcef182..0bacce6d88f 100644 --- a/apps/docs/content/docs/cli/reference.mdx +++ b/apps/docs/content/docs/cli/reference.mdx @@ -47,9 +47,10 @@ sim login [options] | Option | Required | Description | | --- | --- | --- | -| `--method ` | No | Credential to obtain: oauth requires OAuth support; api-key creates a permanent key through pairing (auto-selects when omitted). Accepted values: `oauth`, `api-key`. | +| `--method ` | No | Credential to obtain: oauth requires OAuth support; api-key creates an API key through pairing (auto-selects when omitted). Accepted values: `oauth`, `api-key`. | | `--no-browser` | No | Print the approval URL without opening it (either login method). | | `--read-only` | No | Ask only for permission to read, never to change anything. | +| `--expires-in ` | No | Log in with an API key that expires after this many days (1-365); without it an API key never expires. | | `--callback-port ` | No | Pin the local port the browser returns to. | | `-y, --yes` | No | Overwrite an existing API-key profile without prompting. | @@ -5101,6 +5102,37 @@ sim sandboxes update [options] API request and response schemas: [updateSandbox](/api-reference/sandboxes/updateSandbox). +## sim schedules + +List the schedules deployed workflows run across a workspace. + +A schedule’s timing comes from its schedule block. Edit the block with `sim workflows operations apply`, then deploy; pause or resume one with `sim workflows triggers update`. + +Also spelled `sim schedule`. + +### sim schedules list + +Show the timing and health of every schedule the workspace’s deployments run + +```bash +sim schedules list [options] +``` + +**Options** + + + +| Option | Required | Description | +| --- | --- | --- | +| `--limit ` | No | Maximum items to return (0 for everything). Defaults to `100`. | +| `--cursor ` | No | Continue from nextCursor returned by a previous result. | +| `--sort-by ` | No | Field used to sort the result. `createdAt` is when the schedule was created for the deployment version that runs it: each deploy creates new schedule rows, so a redeployed workflow's schedules move to the newest end. Defaults to "createdAt". Accepted values: `createdAt`. | +| `--sort-order ` | No | Sort direction. Defaults to "desc". Accepted values: `asc`, `desc`. | + + + +API request and response schemas: [listWorkspaceSchedules](/api-reference/workflows/listWorkspaceSchedules). + ## sim secrets Set and manage workspace or personal secrets. @@ -5222,7 +5254,7 @@ sim selectors get [options] | Option | Required | Description | | --- | --- | --- | -| `--selector-key ` | Yes | Registered selector key from an import or sync preview, such as `gmail.labels`. Accepted values: `airtable.bases`, `airtable.tables`, `asana.workspaces`, `attio.lists`, `attio.objects`, `bigquery.datasets`, `bigquery.tables`, `bitbucket.workspaces`, `bitbucket.repositories`, `calcom.eventTypes`, `calcom.schedules`, `clickup.workspaces`, `clickup.spaces`, `clickup.folders`, `clickup.lists`, `coda.docs`, `coda.pages`, `coda.tables`, `coda.columns`, `coda.rows`, `coda.formulas`, `coda.controls`, `coda.folders`, `coda.permissions`, `confluence.spaces`, `confluence.spacesById`, `confluence.pages`, `google.tasks.lists`, `gauge.organizations`, `gauge.categories`, `gauge.topics`, `gauge.prompts`, `gauge.tags`, `gauge.answers`, `fivetran.groups`, `fivetran.connections`, `gmail.labels`, `google.calendar`, `google.drive`, `google.sheets`, `harmonic.savedSearches`, `jama.projects`, `jama.itemTypes`, `flow.projects`, `flow.categories`, `flow.branches`, `helpscout.mailboxes`, `helpscout.assignees`, `hubspot.lists`, `hubspot.owners`, `hubspot.pipelines`, `hubspot.pipelineStages`, `hubspot.properties`, `jsm.requestTypes`, `jsm.serviceDesks`, `microsoft.planner.plans`, `notion.databases`, `notion.pages`, `netsuite.recordTypes`, `netsuite.asyncTasks`, `pipedrive.pipelines`, `sharepoint.lists`, `trello.boards`, `zoho_desk.organizations`, `zoho_desk.departments`, `zoho_desk.agents`, `zoom.meetings`, `slack.channels`, `snowflake.databases`, `snowflake.schemas`, `snowflake.tables`, `snowflake.warehouses`, `snowflake.roles`, `snowflake.fileFormats`, `snowflake.procedures`, `slack.users`, `outlook.folders`, `outlook.calendars`, `microsoft.teams`, `microsoft.chats`, `microsoft.channels`, `microsoft.planner`, `onedrive.files`, `onedrive.folders`, `sharepoint.sites`, `microsoft.excel`, `microsoft.excel.drives`, `microsoft.excel.sheets`, `microsoft.word`, `wealthbox.contacts`, `jira.issues`, `jira.projects`, `jira.projectKeys`, `linear.projects`, `linear.teams`, `monday.boards`, `monday.groups`, `webflow.sites`, `webflow.collections`, `webflow.items`, `planetscale.databases`, `powerbi.workspaces`, `powerbi.datasets`, `powerbi.reports`, `planetscale.branches`, `planetscale.backups`, `planetscale.deployRequests`, `cloudwatch.logGroups`, `cloudwatch.logStreams`, `cloudwatch.savedLogGroups`, `cloudwatch.savedLogStreams`, `imap.mailboxes`, `mcp.tools`, `managedAgent.agents`, `managedAgent.environments`, `managedAgent.vaults`, `managedAgent.memoryStores`, `knowledge.documents`, `sim.workflows`, `table.columns`, `table.outputColumns`, `workspace.secretNames`, `workspace.sandboxes`, `providers.ollamaEmbeddingModels`, `providers.openrouterEmbeddingModels`. | +| `--selector-key ` | Yes | Registered selector key from an import or sync preview, such as `gmail.labels`. Accepted values: `airtable.bases`, `airtable.tables`, `asana.workspaces`, `attio.lists`, `attio.objects`, `bigquery.datasets`, `bigquery.tables`, `bitbucket.workspaces`, `bitbucket.repositories`, `calcom.eventTypes`, `calcom.schedules`, `clickup.workspaces`, `clickup.spaces`, `clickup.folders`, `clickup.lists`, `coda.docs`, `coda.pages`, `coda.tables`, `coda.columns`, `coda.rows`, `coda.formulas`, `coda.controls`, `coda.folders`, `coda.permissions`, `confluence.spaces`, `confluence.spacesById`, `confluence.pages`, `google.tasks.lists`, `gauge.organizations`, `gauge.categories`, `gauge.topics`, `gauge.prompts`, `gauge.tags`, `gauge.answers`, `fivetran.groups`, `fivetran.connections`, `gmail.labels`, `google.calendar`, `google.drive`, `google.sheets`, `harmonic.savedSearches`, `jama.projects`, `jama.itemTypes`, `flow.projects`, `flow.categories`, `flow.branches`, `helpscout.mailboxes`, `helpscout.assignees`, `hubspot.lists`, `hubspot.owners`, `hubspot.pipelines`, `hubspot.pipelineStages`, `hubspot.properties`, `jsm.requestTypes`, `jsm.serviceDesks`, `microsoft.planner.plans`, `notion.databases`, `notion.pages`, `netsuite.recordTypes`, `netsuite.asyncTasks`, `pipedrive.pipelines`, `sharepoint.lists`, `trello.boards`, `zoho_desk.organizations`, `zoho_desk.departments`, `zoho_desk.agents`, `zoom.meetings`, `slack.channels`, `snowflake.databases`, `snowflake.schemas`, `snowflake.tables`, `snowflake.warehouses`, `snowflake.roles`, `snowflake.fileFormats`, `snowflake.procedures`, `slack.users`, `outlook.folders`, `outlook.calendars`, `microsoft.teams`, `microsoft.chats`, `microsoft.channels`, `microsoft.planner`, `onedrive.files`, `onedrive.folders`, `sharepoint.sites`, `microsoft.excel`, `microsoft.excel.drives`, `microsoft.excel.sheets`, `microsoft.word`, `wealthbox.contacts`, `jira.issues`, `jira.projects`, `jira.projectKeys`, `linear.projects`, `linear.teams`, `monday.boards`, `monday.groups`, `webflow.sites`, `webflow.collections`, `webflow.items`, `planetscale.databases`, `powerbi.workspaces`, `powerbi.datasets`, `powerbi.reports`, `planetscale.branches`, `planetscale.backups`, `planetscale.deployRequests`, `planetscale.savedDatabases`, `planetscale.savedBranches`, `planetscale.savedBackups`, `planetscale.savedDeployRequests`, `cloudwatch.logGroups`, `cloudwatch.logStreams`, `cloudwatch.savedLogGroups`, `cloudwatch.savedLogStreams`, `imap.mailboxes`, `mcp.tools`, `managedAgent.agents`, `managedAgent.environments`, `managedAgent.vaults`, `managedAgent.memoryStores`, `knowledge.documents`, `sim.workflows`, `table.columns`, `table.outputColumns`, `workspace.secretNames`, `workspace.sandboxes`, `providers.ollamaEmbeddingModels`, `providers.openrouterEmbeddingModels`. | | `--context ` | No | Only the dependencies declared by the selector, such as oauthCredential and channelId. Missing OAuth connections require human authorization. Defaults to {}. (JSON, or @path / @- to read a file or stdin). | | `--id ` | Yes | Provider option ID to resolve with the same selector and declared context. | @@ -5244,7 +5276,7 @@ sim selectors list [options] | Option | Required | Description | | --- | --- | --- | -| `--selector-key ` | Yes | Registered selector key from an import or sync preview, such as `gmail.labels`. Accepted values: `airtable.bases`, `airtable.tables`, `asana.workspaces`, `attio.lists`, `attio.objects`, `bigquery.datasets`, `bigquery.tables`, `bitbucket.workspaces`, `bitbucket.repositories`, `calcom.eventTypes`, `calcom.schedules`, `clickup.workspaces`, `clickup.spaces`, `clickup.folders`, `clickup.lists`, `coda.docs`, `coda.pages`, `coda.tables`, `coda.columns`, `coda.rows`, `coda.formulas`, `coda.controls`, `coda.folders`, `coda.permissions`, `confluence.spaces`, `confluence.spacesById`, `confluence.pages`, `google.tasks.lists`, `gauge.organizations`, `gauge.categories`, `gauge.topics`, `gauge.prompts`, `gauge.tags`, `gauge.answers`, `fivetran.groups`, `fivetran.connections`, `gmail.labels`, `google.calendar`, `google.drive`, `google.sheets`, `harmonic.savedSearches`, `jama.projects`, `jama.itemTypes`, `flow.projects`, `flow.categories`, `flow.branches`, `helpscout.mailboxes`, `helpscout.assignees`, `hubspot.lists`, `hubspot.owners`, `hubspot.pipelines`, `hubspot.pipelineStages`, `hubspot.properties`, `jsm.requestTypes`, `jsm.serviceDesks`, `microsoft.planner.plans`, `notion.databases`, `notion.pages`, `netsuite.recordTypes`, `netsuite.asyncTasks`, `pipedrive.pipelines`, `sharepoint.lists`, `trello.boards`, `zoho_desk.organizations`, `zoho_desk.departments`, `zoho_desk.agents`, `zoom.meetings`, `slack.channels`, `snowflake.databases`, `snowflake.schemas`, `snowflake.tables`, `snowflake.warehouses`, `snowflake.roles`, `snowflake.fileFormats`, `snowflake.procedures`, `slack.users`, `outlook.folders`, `outlook.calendars`, `microsoft.teams`, `microsoft.chats`, `microsoft.channels`, `microsoft.planner`, `onedrive.files`, `onedrive.folders`, `sharepoint.sites`, `microsoft.excel`, `microsoft.excel.drives`, `microsoft.excel.sheets`, `microsoft.word`, `wealthbox.contacts`, `jira.issues`, `jira.projects`, `jira.projectKeys`, `linear.projects`, `linear.teams`, `monday.boards`, `monday.groups`, `webflow.sites`, `webflow.collections`, `webflow.items`, `planetscale.databases`, `powerbi.workspaces`, `powerbi.datasets`, `powerbi.reports`, `planetscale.branches`, `planetscale.backups`, `planetscale.deployRequests`, `cloudwatch.logGroups`, `cloudwatch.logStreams`, `cloudwatch.savedLogGroups`, `cloudwatch.savedLogStreams`, `imap.mailboxes`, `mcp.tools`, `managedAgent.agents`, `managedAgent.environments`, `managedAgent.vaults`, `managedAgent.memoryStores`, `knowledge.documents`, `sim.workflows`, `table.columns`, `table.outputColumns`, `workspace.secretNames`, `workspace.sandboxes`, `providers.ollamaEmbeddingModels`, `providers.openrouterEmbeddingModels`. | +| `--selector-key ` | Yes | Registered selector key from an import or sync preview, such as `gmail.labels`. Accepted values: `airtable.bases`, `airtable.tables`, `asana.workspaces`, `attio.lists`, `attio.objects`, `bigquery.datasets`, `bigquery.tables`, `bitbucket.workspaces`, `bitbucket.repositories`, `calcom.eventTypes`, `calcom.schedules`, `clickup.workspaces`, `clickup.spaces`, `clickup.folders`, `clickup.lists`, `coda.docs`, `coda.pages`, `coda.tables`, `coda.columns`, `coda.rows`, `coda.formulas`, `coda.controls`, `coda.folders`, `coda.permissions`, `confluence.spaces`, `confluence.spacesById`, `confluence.pages`, `google.tasks.lists`, `gauge.organizations`, `gauge.categories`, `gauge.topics`, `gauge.prompts`, `gauge.tags`, `gauge.answers`, `fivetran.groups`, `fivetran.connections`, `gmail.labels`, `google.calendar`, `google.drive`, `google.sheets`, `harmonic.savedSearches`, `jama.projects`, `jama.itemTypes`, `flow.projects`, `flow.categories`, `flow.branches`, `helpscout.mailboxes`, `helpscout.assignees`, `hubspot.lists`, `hubspot.owners`, `hubspot.pipelines`, `hubspot.pipelineStages`, `hubspot.properties`, `jsm.requestTypes`, `jsm.serviceDesks`, `microsoft.planner.plans`, `notion.databases`, `notion.pages`, `netsuite.recordTypes`, `netsuite.asyncTasks`, `pipedrive.pipelines`, `sharepoint.lists`, `trello.boards`, `zoho_desk.organizations`, `zoho_desk.departments`, `zoho_desk.agents`, `zoom.meetings`, `slack.channels`, `snowflake.databases`, `snowflake.schemas`, `snowflake.tables`, `snowflake.warehouses`, `snowflake.roles`, `snowflake.fileFormats`, `snowflake.procedures`, `slack.users`, `outlook.folders`, `outlook.calendars`, `microsoft.teams`, `microsoft.chats`, `microsoft.channels`, `microsoft.planner`, `onedrive.files`, `onedrive.folders`, `sharepoint.sites`, `microsoft.excel`, `microsoft.excel.drives`, `microsoft.excel.sheets`, `microsoft.word`, `wealthbox.contacts`, `jira.issues`, `jira.projects`, `jira.projectKeys`, `linear.projects`, `linear.teams`, `monday.boards`, `monday.groups`, `webflow.sites`, `webflow.collections`, `webflow.items`, `planetscale.databases`, `powerbi.workspaces`, `powerbi.datasets`, `powerbi.reports`, `planetscale.branches`, `planetscale.backups`, `planetscale.deployRequests`, `planetscale.savedDatabases`, `planetscale.savedBranches`, `planetscale.savedBackups`, `planetscale.savedDeployRequests`, `cloudwatch.logGroups`, `cloudwatch.logStreams`, `cloudwatch.savedLogGroups`, `cloudwatch.savedLogStreams`, `imap.mailboxes`, `mcp.tools`, `managedAgent.agents`, `managedAgent.environments`, `managedAgent.vaults`, `managedAgent.memoryStores`, `knowledge.documents`, `sim.workflows`, `table.columns`, `table.outputColumns`, `workspace.secretNames`, `workspace.sandboxes`, `providers.ollamaEmbeddingModels`, `providers.openrouterEmbeddingModels`. | | `--context ` | No | Only the dependencies declared by the selector, such as oauthCredential and channelId. Missing OAuth connections require human authorization. Defaults to {}. (JSON, or @path / @- to read a file or stdin). | | `--search ` | No | Provider option search text. | | `--cursor ` | No | Continue from nextCursor returned by a previous result. | @@ -7493,6 +7525,31 @@ sim workflows runs get [options] API request and response schemas: [getWorkflowRun](/api-reference/workflow-runs/getWorkflowRunV2). +### sim workflows runs paused + +List runs waiting on human input across the workspace + +```bash +sim workflows runs paused [options] +``` + +**Options** + + + +| Option | Required | Description | +| --- | --- | --- | +| `--include-output` | No | Include each pause point's reviewer `display` content when true. | +| `--no-include-output` | No | Send --include-output as false. | +| `--limit ` | No | Maximum items to return (0 for everything). Defaults to `100`. | +| `--cursor ` | No | Continue from nextCursor returned by a previous result. | +| `--sort-by ` | No | Field used to sort the result. Defaults to "pausedAt". Accepted values: `pausedAt`. | +| `--sort-order ` | No | Sort direction. Defaults to "desc". Accepted values: `asc`, `desc`. | + + + +API request and response schemas: [listPausedRuns](/api-reference/workflow-runs/listPausedWorkflowRunsV2). + ### sim workflows runs list List runs for a workflow @@ -8166,6 +8223,26 @@ sim workflows deployment update [options] API request and response schemas: [updateWorkflowPublicApi](/api-reference/workflows/updateWorkflowPublicApi). +### sim workflows outline + +Show a workflow’s draft as a compact read-only outline + +```bash +sim workflows outline +``` + +**Arguments** + + + +| Argument | Required | Description | +| --- | --- | --- | +| `workflowId` | Yes | Unique workflow identifier. | + + + +API request and response schemas: [getWorkflowOutline](/api-reference/workflows/getWorkflowOutline). + ### sim workflows state get Get workflow state @@ -9239,6 +9316,27 @@ sim workspaces unlink [options] API request and response schemas: [unlinkWorkspaceFork](/api-reference/workspace-sync/unlinkWorkspaceFork). +### sim workspaces update + +Update workspace (OAuth login or personal API key required) + +```bash +sim workspaces update [options] +``` + +**Options** + + + +| Option | Required | Description | +| --- | --- | --- | +| `--name ` | No | New workspace display name. | +| `--logo-url ` | No | New workspace logo: a root-relative path (starting with /) or an https:// URL. To remove the logo, send logoUrl: null through the API or MCP (--logo-url null sends the word, not JSON null). | + + + +API request and response schemas: [updateWorkspace](/api-reference/workspaces/updateWorkspace). + ### sim workspaces sync-exclusions Update workspace fork exclusions (OAuth login or personal API key required) diff --git a/apps/docs/content/docs/cli/schedules.mdx b/apps/docs/content/docs/cli/schedules.mdx new file mode 100644 index 00000000000..b056eb04771 --- /dev/null +++ b/apps/docs/content/docs/cli/schedules.mdx @@ -0,0 +1,35 @@ +--- +title: Schedules +description: List the schedules deployed workflows run across a workspace. +--- + +import { CommandTable } from '@/components/ui/command-table' + +List the schedules deployed workflows run across a workspace. + +A schedule’s timing comes from its schedule block. Edit the block with `sim workflows operations apply`, then deploy; pause or resume one with `sim workflows triggers update`. + +`sim schedules` is also spelled `sim schedule`. + +These commands accept the [global options](/cli/commands#global-options). + +## Show the timing and health of every schedule the workspace’s deployments run + +```bash +sim schedules list [options] +``` + +**Options** + + + +| Option | Required | Description | +| --- | --- | --- | +| `--limit ` | No | Maximum items to return (0 for everything). Defaults to `100`. | +| `--cursor ` | No | Continue from nextCursor returned by a previous result. | +| `--sort-by ` | No | Field used to sort the result. `createdAt` is when the schedule was created for the deployment version that runs it: each deploy creates new schedule rows, so a redeployed workflow's schedules move to the newest end. Defaults to "createdAt". Accepted values: `createdAt`. | +| `--sort-order ` | No | Sort direction. Defaults to "desc". Accepted values: `asc`, `desc`. | + + + +API request and response schemas: [listWorkspaceSchedules](/api-reference/workflows/listWorkspaceSchedules). diff --git a/apps/docs/content/docs/cli/selectors.mdx b/apps/docs/content/docs/cli/selectors.mdx index 368e237b9d9..0ffd129f4e6 100644 --- a/apps/docs/content/docs/cli/selectors.mdx +++ b/apps/docs/content/docs/cli/selectors.mdx @@ -25,7 +25,7 @@ sim selectors list [options] | Option | Required | Description | | --- | --- | --- | -| `--selector-key ` | Yes | Registered selector key from an import or sync preview, such as `gmail.labels`. Accepted values: `airtable.bases`, `airtable.tables`, `asana.workspaces`, `attio.lists`, `attio.objects`, `bigquery.datasets`, `bigquery.tables`, `bitbucket.workspaces`, `bitbucket.repositories`, `calcom.eventTypes`, `calcom.schedules`, `clickup.workspaces`, `clickup.spaces`, `clickup.folders`, `clickup.lists`, `coda.docs`, `coda.pages`, `coda.tables`, `coda.columns`, `coda.rows`, `coda.formulas`, `coda.controls`, `coda.folders`, `coda.permissions`, `confluence.spaces`, `confluence.spacesById`, `confluence.pages`, `google.tasks.lists`, `gauge.organizations`, `gauge.categories`, `gauge.topics`, `gauge.prompts`, `gauge.tags`, `gauge.answers`, `fivetran.groups`, `fivetran.connections`, `gmail.labels`, `google.calendar`, `google.drive`, `google.sheets`, `harmonic.savedSearches`, `jama.projects`, `jama.itemTypes`, `flow.projects`, `flow.categories`, `flow.branches`, `helpscout.mailboxes`, `helpscout.assignees`, `hubspot.lists`, `hubspot.owners`, `hubspot.pipelines`, `hubspot.pipelineStages`, `hubspot.properties`, `jsm.requestTypes`, `jsm.serviceDesks`, `microsoft.planner.plans`, `notion.databases`, `notion.pages`, `netsuite.recordTypes`, `netsuite.asyncTasks`, `pipedrive.pipelines`, `sharepoint.lists`, `trello.boards`, `zoho_desk.organizations`, `zoho_desk.departments`, `zoho_desk.agents`, `zoom.meetings`, `slack.channels`, `snowflake.databases`, `snowflake.schemas`, `snowflake.tables`, `snowflake.warehouses`, `snowflake.roles`, `snowflake.fileFormats`, `snowflake.procedures`, `slack.users`, `outlook.folders`, `outlook.calendars`, `microsoft.teams`, `microsoft.chats`, `microsoft.channels`, `microsoft.planner`, `onedrive.files`, `onedrive.folders`, `sharepoint.sites`, `microsoft.excel`, `microsoft.excel.drives`, `microsoft.excel.sheets`, `microsoft.word`, `wealthbox.contacts`, `jira.issues`, `jira.projects`, `jira.projectKeys`, `linear.projects`, `linear.teams`, `monday.boards`, `monday.groups`, `webflow.sites`, `webflow.collections`, `webflow.items`, `planetscale.databases`, `powerbi.workspaces`, `powerbi.datasets`, `powerbi.reports`, `planetscale.branches`, `planetscale.backups`, `planetscale.deployRequests`, `cloudwatch.logGroups`, `cloudwatch.logStreams`, `cloudwatch.savedLogGroups`, `cloudwatch.savedLogStreams`, `imap.mailboxes`, `mcp.tools`, `managedAgent.agents`, `managedAgent.environments`, `managedAgent.vaults`, `managedAgent.memoryStores`, `knowledge.documents`, `sim.workflows`, `table.columns`, `table.outputColumns`, `workspace.secretNames`, `workspace.sandboxes`, `providers.ollamaEmbeddingModels`, `providers.openrouterEmbeddingModels`. | +| `--selector-key ` | Yes | Registered selector key from an import or sync preview, such as `gmail.labels`. Accepted values: `airtable.bases`, `airtable.tables`, `asana.workspaces`, `attio.lists`, `attio.objects`, `bigquery.datasets`, `bigquery.tables`, `bitbucket.workspaces`, `bitbucket.repositories`, `calcom.eventTypes`, `calcom.schedules`, `clickup.workspaces`, `clickup.spaces`, `clickup.folders`, `clickup.lists`, `coda.docs`, `coda.pages`, `coda.tables`, `coda.columns`, `coda.rows`, `coda.formulas`, `coda.controls`, `coda.folders`, `coda.permissions`, `confluence.spaces`, `confluence.spacesById`, `confluence.pages`, `google.tasks.lists`, `gauge.organizations`, `gauge.categories`, `gauge.topics`, `gauge.prompts`, `gauge.tags`, `gauge.answers`, `fivetran.groups`, `fivetran.connections`, `gmail.labels`, `google.calendar`, `google.drive`, `google.sheets`, `harmonic.savedSearches`, `jama.projects`, `jama.itemTypes`, `flow.projects`, `flow.categories`, `flow.branches`, `helpscout.mailboxes`, `helpscout.assignees`, `hubspot.lists`, `hubspot.owners`, `hubspot.pipelines`, `hubspot.pipelineStages`, `hubspot.properties`, `jsm.requestTypes`, `jsm.serviceDesks`, `microsoft.planner.plans`, `notion.databases`, `notion.pages`, `netsuite.recordTypes`, `netsuite.asyncTasks`, `pipedrive.pipelines`, `sharepoint.lists`, `trello.boards`, `zoho_desk.organizations`, `zoho_desk.departments`, `zoho_desk.agents`, `zoom.meetings`, `slack.channels`, `snowflake.databases`, `snowflake.schemas`, `snowflake.tables`, `snowflake.warehouses`, `snowflake.roles`, `snowflake.fileFormats`, `snowflake.procedures`, `slack.users`, `outlook.folders`, `outlook.calendars`, `microsoft.teams`, `microsoft.chats`, `microsoft.channels`, `microsoft.planner`, `onedrive.files`, `onedrive.folders`, `sharepoint.sites`, `microsoft.excel`, `microsoft.excel.drives`, `microsoft.excel.sheets`, `microsoft.word`, `wealthbox.contacts`, `jira.issues`, `jira.projects`, `jira.projectKeys`, `linear.projects`, `linear.teams`, `monday.boards`, `monday.groups`, `webflow.sites`, `webflow.collections`, `webflow.items`, `planetscale.databases`, `powerbi.workspaces`, `powerbi.datasets`, `powerbi.reports`, `planetscale.branches`, `planetscale.backups`, `planetscale.deployRequests`, `planetscale.savedDatabases`, `planetscale.savedBranches`, `planetscale.savedBackups`, `planetscale.savedDeployRequests`, `cloudwatch.logGroups`, `cloudwatch.logStreams`, `cloudwatch.savedLogGroups`, `cloudwatch.savedLogStreams`, `imap.mailboxes`, `mcp.tools`, `managedAgent.agents`, `managedAgent.environments`, `managedAgent.vaults`, `managedAgent.memoryStores`, `knowledge.documents`, `sim.workflows`, `table.columns`, `table.outputColumns`, `workspace.secretNames`, `workspace.sandboxes`, `providers.ollamaEmbeddingModels`, `providers.openrouterEmbeddingModels`. | | `--context ` | No | Only the dependencies declared by the selector, such as oauthCredential and channelId. Missing OAuth connections require human authorization. Defaults to {}. (JSON, or @path / @- to read a file or stdin). | | `--search ` | No | Provider option search text. | | `--cursor ` | No | Continue from nextCursor returned by a previous result. | @@ -49,7 +49,7 @@ sim selectors get [options] | Option | Required | Description | | --- | --- | --- | -| `--selector-key ` | Yes | Registered selector key from an import or sync preview, such as `gmail.labels`. Accepted values: `airtable.bases`, `airtable.tables`, `asana.workspaces`, `attio.lists`, `attio.objects`, `bigquery.datasets`, `bigquery.tables`, `bitbucket.workspaces`, `bitbucket.repositories`, `calcom.eventTypes`, `calcom.schedules`, `clickup.workspaces`, `clickup.spaces`, `clickup.folders`, `clickup.lists`, `coda.docs`, `coda.pages`, `coda.tables`, `coda.columns`, `coda.rows`, `coda.formulas`, `coda.controls`, `coda.folders`, `coda.permissions`, `confluence.spaces`, `confluence.spacesById`, `confluence.pages`, `google.tasks.lists`, `gauge.organizations`, `gauge.categories`, `gauge.topics`, `gauge.prompts`, `gauge.tags`, `gauge.answers`, `fivetran.groups`, `fivetran.connections`, `gmail.labels`, `google.calendar`, `google.drive`, `google.sheets`, `harmonic.savedSearches`, `jama.projects`, `jama.itemTypes`, `flow.projects`, `flow.categories`, `flow.branches`, `helpscout.mailboxes`, `helpscout.assignees`, `hubspot.lists`, `hubspot.owners`, `hubspot.pipelines`, `hubspot.pipelineStages`, `hubspot.properties`, `jsm.requestTypes`, `jsm.serviceDesks`, `microsoft.planner.plans`, `notion.databases`, `notion.pages`, `netsuite.recordTypes`, `netsuite.asyncTasks`, `pipedrive.pipelines`, `sharepoint.lists`, `trello.boards`, `zoho_desk.organizations`, `zoho_desk.departments`, `zoho_desk.agents`, `zoom.meetings`, `slack.channels`, `snowflake.databases`, `snowflake.schemas`, `snowflake.tables`, `snowflake.warehouses`, `snowflake.roles`, `snowflake.fileFormats`, `snowflake.procedures`, `slack.users`, `outlook.folders`, `outlook.calendars`, `microsoft.teams`, `microsoft.chats`, `microsoft.channels`, `microsoft.planner`, `onedrive.files`, `onedrive.folders`, `sharepoint.sites`, `microsoft.excel`, `microsoft.excel.drives`, `microsoft.excel.sheets`, `microsoft.word`, `wealthbox.contacts`, `jira.issues`, `jira.projects`, `jira.projectKeys`, `linear.projects`, `linear.teams`, `monday.boards`, `monday.groups`, `webflow.sites`, `webflow.collections`, `webflow.items`, `planetscale.databases`, `powerbi.workspaces`, `powerbi.datasets`, `powerbi.reports`, `planetscale.branches`, `planetscale.backups`, `planetscale.deployRequests`, `cloudwatch.logGroups`, `cloudwatch.logStreams`, `cloudwatch.savedLogGroups`, `cloudwatch.savedLogStreams`, `imap.mailboxes`, `mcp.tools`, `managedAgent.agents`, `managedAgent.environments`, `managedAgent.vaults`, `managedAgent.memoryStores`, `knowledge.documents`, `sim.workflows`, `table.columns`, `table.outputColumns`, `workspace.secretNames`, `workspace.sandboxes`, `providers.ollamaEmbeddingModels`, `providers.openrouterEmbeddingModels`. | +| `--selector-key ` | Yes | Registered selector key from an import or sync preview, such as `gmail.labels`. Accepted values: `airtable.bases`, `airtable.tables`, `asana.workspaces`, `attio.lists`, `attio.objects`, `bigquery.datasets`, `bigquery.tables`, `bitbucket.workspaces`, `bitbucket.repositories`, `calcom.eventTypes`, `calcom.schedules`, `clickup.workspaces`, `clickup.spaces`, `clickup.folders`, `clickup.lists`, `coda.docs`, `coda.pages`, `coda.tables`, `coda.columns`, `coda.rows`, `coda.formulas`, `coda.controls`, `coda.folders`, `coda.permissions`, `confluence.spaces`, `confluence.spacesById`, `confluence.pages`, `google.tasks.lists`, `gauge.organizations`, `gauge.categories`, `gauge.topics`, `gauge.prompts`, `gauge.tags`, `gauge.answers`, `fivetran.groups`, `fivetran.connections`, `gmail.labels`, `google.calendar`, `google.drive`, `google.sheets`, `harmonic.savedSearches`, `jama.projects`, `jama.itemTypes`, `flow.projects`, `flow.categories`, `flow.branches`, `helpscout.mailboxes`, `helpscout.assignees`, `hubspot.lists`, `hubspot.owners`, `hubspot.pipelines`, `hubspot.pipelineStages`, `hubspot.properties`, `jsm.requestTypes`, `jsm.serviceDesks`, `microsoft.planner.plans`, `notion.databases`, `notion.pages`, `netsuite.recordTypes`, `netsuite.asyncTasks`, `pipedrive.pipelines`, `sharepoint.lists`, `trello.boards`, `zoho_desk.organizations`, `zoho_desk.departments`, `zoho_desk.agents`, `zoom.meetings`, `slack.channels`, `snowflake.databases`, `snowflake.schemas`, `snowflake.tables`, `snowflake.warehouses`, `snowflake.roles`, `snowflake.fileFormats`, `snowflake.procedures`, `slack.users`, `outlook.folders`, `outlook.calendars`, `microsoft.teams`, `microsoft.chats`, `microsoft.channels`, `microsoft.planner`, `onedrive.files`, `onedrive.folders`, `sharepoint.sites`, `microsoft.excel`, `microsoft.excel.drives`, `microsoft.excel.sheets`, `microsoft.word`, `wealthbox.contacts`, `jira.issues`, `jira.projects`, `jira.projectKeys`, `linear.projects`, `linear.teams`, `monday.boards`, `monday.groups`, `webflow.sites`, `webflow.collections`, `webflow.items`, `planetscale.databases`, `powerbi.workspaces`, `powerbi.datasets`, `powerbi.reports`, `planetscale.branches`, `planetscale.backups`, `planetscale.deployRequests`, `planetscale.savedDatabases`, `planetscale.savedBranches`, `planetscale.savedBackups`, `planetscale.savedDeployRequests`, `cloudwatch.logGroups`, `cloudwatch.logStreams`, `cloudwatch.savedLogGroups`, `cloudwatch.savedLogStreams`, `imap.mailboxes`, `mcp.tools`, `managedAgent.agents`, `managedAgent.environments`, `managedAgent.vaults`, `managedAgent.memoryStores`, `knowledge.documents`, `sim.workflows`, `table.columns`, `table.outputColumns`, `workspace.secretNames`, `workspace.sandboxes`, `providers.ollamaEmbeddingModels`, `providers.openrouterEmbeddingModels`. | | `--context ` | No | Only the dependencies declared by the selector, such as oauthCredential and channelId. Missing OAuth connections require human authorization. Defaults to {}. (JSON, or @path / @- to read a file or stdin). | | `--id ` | Yes | Provider option ID to resolve with the same selector and declared context. | diff --git a/apps/docs/content/docs/cli/workflows.mdx b/apps/docs/content/docs/cli/workflows.mdx index d0d274cc03c..2bb27df712c 100644 --- a/apps/docs/content/docs/cli/workflows.mdx +++ b/apps/docs/content/docs/cli/workflows.mdx @@ -280,6 +280,24 @@ sim workflows state replace [options] API request and response schemas: [replaceWorkflowState](/api-reference/workflows/replaceWorkflowState). +### Show a workflow’s draft as a compact read-only outline + +```bash +sim workflows outline +``` + +**Arguments** + + + +| Argument | Required | Description | +| --- | --- | --- | +| `workflowId` | Yes | Unique workflow identifier. | + + + +API request and response schemas: [getWorkflowOutline](/api-reference/workflows/getWorkflowOutline). + ### Edit a workflow’s blocks, connections and settings with a batch of operations OAuth login or personal API key required. @@ -788,6 +806,29 @@ sim workflows runs list [options] API request and response schemas: [listWorkflowRuns](/api-reference/workflow-runs/listWorkflowRunsV2). +### List runs waiting on human input across the workspace + +```bash +sim workflows runs paused [options] +``` + +**Options** + + + +| Option | Required | Description | +| --- | --- | --- | +| `--include-output` | No | Include each pause point's reviewer `display` content when true. | +| `--no-include-output` | No | Send --include-output as false. | +| `--limit ` | No | Maximum items to return (0 for everything). Defaults to `100`. | +| `--cursor ` | No | Continue from nextCursor returned by a previous result. | +| `--sort-by ` | No | Field used to sort the result. Defaults to "pausedAt". Accepted values: `pausedAt`. | +| `--sort-order ` | No | Sort direction. Defaults to "desc". Accepted values: `asc`, `desc`. | + + + +API request and response schemas: [listPausedRuns](/api-reference/workflow-runs/listPausedWorkflowRunsV2). + ### Show run status Requested outputs are included in JSON or YAML output. @@ -1247,5 +1288,6 @@ API request and response schemas: [createWorkflowFolder](/api-reference/workflow ## Related docs - [Workflow builder](/workflows) +- [Build and run workflows with the API](/api-reference/build-and-run-workflows) - [Import and workspace sync](/cli/workflow-sync) - [Scripting and exit codes](/cli/scripting#exit-codes) diff --git a/apps/docs/content/docs/cli/workspaces.mdx b/apps/docs/content/docs/cli/workspaces.mdx index cf195f61761..b3d5f9b4306 100644 --- a/apps/docs/content/docs/cli/workspaces.mdx +++ b/apps/docs/content/docs/cli/workspaces.mdx @@ -41,6 +41,27 @@ sim workspaces get API request and response schemas: [getWorkspace](/api-reference/workspaces/getWorkspace). +### Update workspace + +OAuth login or personal API key required. + +```bash +sim workspaces update [options] +``` + +**Options** + + + +| Option | Required | Description | +| --- | --- | --- | +| `--name ` | No | New workspace display name. | +| `--logo-url ` | No | New workspace logo: a root-relative path (starting with /) or an https:// URL. To remove the logo, send logoUrl: null through the API or MCP (--logo-url null sends the word, not JSON null). | + + + +API request and response schemas: [updateWorkspace](/api-reference/workspaces/updateWorkspace). + ### List workspace members ```bash diff --git a/apps/docs/content/docs/integrations/atlassian-service-account.mdx b/apps/docs/content/docs/integrations/atlassian-service-account.mdx index 76f7eea35de..ba1769ef676 100644 --- a/apps/docs/content/docs/integrations/atlassian-service-account.mdx +++ b/apps/docs/content/docs/integrations/atlassian-service-account.mdx @@ -97,12 +97,21 @@ Use the **App** and **Scope type** filters to find both classic and granular sco height={458} /> +## Use an OAuth 2.0 credential instead of a token [#oauth-2-0-credential] + +A service account can also have an [OAuth 2.0 credential](https://support.atlassian.com/user-management/docs/create-oauth-2-0-credential-for-service-accounts/): a client ID and secret with the scopes you select when you create it. Sim requests a 60-minute access token from `auth.atlassian.com` with the client credentials grant whenever a workflow needs one and calls `api.atlassian.com/ex/{jira|confluence}/{cloudId}` with it, so nothing expires in Sim the way a pasted token does. + +- Atlassian supports these credentials for the **Jira** and **Confluence** REST APIs. It does not list Jira Service Management, so use a scoped API token for Service Management operations. +- In **Directory → Service accounts**, select the service account, then **Create credentials → OAuth 2.0**, choose the scopes listed above, and copy the client ID and secret. Atlassian shows the secret only once. +- When you add the credential in Sim, set **Authentication** to **OAuth 2.0 credential (client credentials)** and paste the client ID and secret instead of an API token. +- Once the OAuth 2.0 credential is revoked in Atlassian, Sim can no longer request tokens with it. To rotate, create a new credential and **Reconnect** the Sim credential with its client ID and secret. + ## Add the credential to Sim 1. Open **Integrations** in your workspace sidebar, then **Jira**, **Jira Service Management**, or **Confluence**. 2. Select **Add to Sim → Add service account**. If only service accounts are available, select **Add service account** directly. -3. Paste the **API token** and enter **Site domain**, such as `your-team.atlassian.net`. Omit `https://` and any path. Optionally add a display name and description. -4. Select **Add service account**. Sim checks the token against the selected product's current-user endpoint; resolve any error before continuing. +3. Keep **Authentication** on **Scoped API token** and paste the **API token**, or choose [**OAuth 2.0 credential**](#oauth-2-0-credential) and paste its client ID and secret. Enter **Site domain**, such as `your-team.atlassian.net`. Omit `https://` and any path. Optionally add a display name and description. +4. Select **Add service account**. Sim checks the token (or a token minted from the OAuth 2.0 credential) against the selected product's current-user endpoint; resolve any error before continuing. 5. In your workflow's Jira, Jira Service Management, or Confluence block, select the credential and configure the operation. The credential appears on all three integration pages, but adding it only validates the selected product. Check account access and scopes before using another product. diff --git a/apps/docs/content/docs/integrations/aws-credentials.mdx b/apps/docs/content/docs/integrations/aws-credentials.mdx index 65308e11f36..ede81c9503d 100644 --- a/apps/docs/content/docs/integrations/aws-credentials.mdx +++ b/apps/docs/content/docs/integrations/aws-credentials.mdx @@ -65,7 +65,7 @@ To rotate a key, create the new key in AWS, reconnect the credential with it, co Keyless roles need two things on a self-hosted deployment: - `WORKLOAD_IDENTITY_SIGNING_KEY`: a PKCS#8 PEM RSA private key that signs Sim's tokens. -- An issuer AWS can reach over the public internet: an `https` URL on the default port, served by a certificate from a public CA. It defaults to `/oidc/workload`; set `WORKLOAD_IDENTITY_ISSUER` to pin it. Trust policies reference this URL, so never change it once roles trust it. Sim keeps keyless off when the issuer isn't `https` or names a port, query, or fragment, because AWS can't use it. +- An issuer AWS can reach over the public internet: an `https` URL on the default port, served by a certificate from a public CA. It defaults to `/oidc/workload`; set `WORKLOAD_IDENTITY_ISSUER` to pin it. A pinned issuer may use another host that reaches Sim. Its path should end in `/oidc/workload`, because Sim serves `/.well-known/openid-configuration` and `/jwks` under that path; any other path works only if a proxy routes it there, and Sim logs a warning for it. Trust policies reference this URL, so never change it once roles trust it. Sim keeps keyless off when the issuer isn't `https` or names a port, query, or fragment, because AWS can't use it. Generate a key with `openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:2048`. diff --git a/apps/docs/content/docs/integrations/bitbucket-service-account.mdx b/apps/docs/content/docs/integrations/bitbucket-service-account.mdx index afa099a3e2a..48afdbbf106 100644 --- a/apps/docs/content/docs/integrations/bitbucket-service-account.mdx +++ b/apps/docs/content/docs/integrations/bitbucket-service-account.mdx @@ -35,6 +35,10 @@ Admin access to the workspace, project, or repository the token is for. Workspac A repository access token can only reach its repository, and a project access token only the repositories in its project. Treat the token like a password; Sim encrypts it at rest. + +Sim also accepts an Atlassian API token created for Bitbucket (**Create API token with scopes**, then **Bitbucket**) with at least **Repositories: Read** (`read:repository:bitbucket`). That scope does not cover pull requests, so pull request operations also need **Pull requests: Read** (`read:pullrequest:bitbucket`), plus **Pull requests: Write** (`write:pullrequest:bitbucket`) to change them. An API token belongs to the person who created it and acts as them. Bitbucket app passwords no longer work: Atlassian stopped new app passwords on September 9, 2025 and set June 9, 2026 as the date existing ones stop working. See [Using API tokens](https://support.atlassian.com/bitbucket-cloud/docs/using-api-tokens/). + + ## Add the access token to Sim [#adding-the-access-token-to-sim] diff --git a/apps/docs/content/docs/integrations/crowdstrike.mdx b/apps/docs/content/docs/integrations/crowdstrike.mdx index d009fb81689..a459ba8f206 100644 --- a/apps/docs/content/docs/integrations/crowdstrike.mdx +++ b/apps/docs/content/docs/integrations/crowdstrike.mdx @@ -2,7 +2,7 @@ title: CrowdStrike description: Investigate and respond to CrowdStrike Falcon alerts, hosts, IOCs, and vulnerabilities integration: - type: "crowdstrike" + type: "crowdstrike_v2" color: "#E01F3D" --- @@ -10,7 +10,7 @@ import { ReferenceDetails } from "@/components/ui/reference-details" {/* MANUAL-CONTENT-START:intro */} -[CrowdStrike](https://www.crowdstrike.com/) is a cybersecurity platform providing endpoint protection, threat intelligence, and identity security through its Falcon suite. This integration authenticates with a Falcon API client ID and secret against a chosen cloud region and covers the Alerts, Hosts, Host Groups, IOC Management, Spotlight, Real Time Response, Case Management, and Identity Protection APIs. +[CrowdStrike](https://www.crowdstrike.com/) is a cybersecurity platform providing endpoint protection, threat intelligence, and identity security through its Falcon suite. This integration covers the Alerts, Hosts, Host Groups, IOC Management, Spotlight, Real Time Response, Case Management, and Identity Protection APIs. With this integration, you can: @@ -23,7 +23,9 @@ With this integration, you can: - **Read cases**: Search Case Management cases and read case details - **Query identity sensors**: Search Identity Protection sensors, fetch sensor details, and run aggregate queries -Each operation maps to a specific Falcon API scope — for example `Alerts: Read` and `Alerts: Write`, `Hosts: Write` for containment, `Host groups: Read`/`Write`, `IOC Management: Read`/`Write`, `Vulnerabilities: Read`, `Real time response: Read`, and `Cases: Read`. Containment and indicator deletion change live protection behavior, so scope the credential to only the operations your workflows need. +Connect a Falcon API client once as a CrowdStrike credential: create the API client in Falcon under **Support and resources > API clients and keys**, then add its client ID and secret and choose the cloud your console signs in to. Sim checks the credential by requesting a token when you save it, and requests a new one before each 30-minute Falcon token expires. An MSSP parent CID can enter a member CID so every call acts on that one child CID. Workflows built with the earlier CrowdStrike block keep the client ID, secret, and cloud they entered in the block. + +Each operation maps to a specific Falcon API scope — for example `Alerts: Read` and `Alerts: Write`, `Hosts: Write` for containment, `Host groups: Read`/`Write`, `IOC Management: Read`/`Write`, `Vulnerabilities: Read`, `Real time response: Read`, and `Cases: Read`. Containment and indicator deletion change live protection behavior, so scope the API client to only the operations your workflows need. Note that CrowdStrike decommissioned the legacy Detects API (September 30, 2025) and the CrowdScore Incidents API (March 9, 2026). This integration uses the current Alerts API and Case Management API in their place. {/* MANUAL-CONTENT-END */} @@ -44,9 +46,6 @@ Create custom CrowdStrike Falcon indicators of compromise (POST /iocs/entities/i | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `clientId` | string | Yes | CrowdStrike Falcon API client ID | -| `clientSecret` | string | Yes | CrowdStrike Falcon API client secret | -| `cloud` | string | Yes | CrowdStrike Falcon cloud region | | `indicators` | json | Yes | JSON array of indicators to create. Each entry requires type, value, and applied_globally \(boolean\). type is one of sha256, md5, domain, ipv4, ipv6; action is one of no_action, allow, prevent, detect \(prevent_no_ui is widely reported and appears in the Falcon console, but CrowdStrike does not enumerate it in the IOC API docs - call GET /iocs/queries/actions/v1 to read the actions your tenant actually accepts\); severity is one of informational, low, medium, high, critical; platforms entries are windows, mac, or linux. Other documented fields: host_groups \(array\), description, source, tags \(array\), expiration \(ISO 8601\), mobile_action, metadata \(\{ filename \}\). Either applied_globally must be true or host_groups must be supplied. Tenants can extend these value sets, so treat them as the documented defaults rather than a closed list. | | `comment` | string | No | Audit comment explaining why these indicators were created | | `retrodetects` | boolean | No | Whether to generate retroactive detections for the new indicators | @@ -106,9 +105,6 @@ Permanently delete custom CrowdStrike Falcon indicators of compromise (DELETE /i | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `clientId` | string | Yes | CrowdStrike Falcon API client ID | -| `clientSecret` | string | Yes | CrowdStrike Falcon API client secret | -| `cloud` | string | Yes | CrowdStrike Falcon cloud region | | `indicatorIds` | json | No | JSON array of CrowdStrike IOC IDs to delete. Cannot be combined with a filter. | | `filter` | string | No | Falcon Query Language filter selecting indicators to delete in bulk. Cannot be combined with an ID list. | | `comment` | string | No | Audit comment explaining why these indicators were deleted | @@ -136,9 +132,6 @@ Close an open CrowdStrike Falcon Real Time Response session (DELETE /real-time-r | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `clientId` | string | Yes | CrowdStrike Falcon API client ID | -| `clientSecret` | string | Yes | CrowdStrike Falcon API client secret | -| `cloud` | string | Yes | CrowdStrike Falcon cloud region | | `sessionId` | string | Yes | RTR session ID to close | #### Output @@ -164,9 +157,6 @@ Run a read-only Real Time Response command in an open CrowdStrike Falcon session | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `clientId` | string | Yes | CrowdStrike Falcon API client ID | -| `clientSecret` | string | Yes | CrowdStrike Falcon API client secret | -| `cloud` | string | Yes | CrowdStrike Falcon cloud region | | `sessionId` | string | Yes | RTR session ID returned by Init RTR Session | | `baseCommand` | string | Yes | Read-only RTR base command family, one of: cat, cd, clear, csrutil, env, eventlog, filehash, getsid, help, history, ifconfig, ipconfig, ls, mount, netstat, ps, reg, users. Subcommands belong in commandString, not here — and only reg query is read-tier, since reg set and reg delete are Active Responder commands. | | `commandString` | string | Yes | Full command line to run, such as "ls C:\Windows" or "reg query HKLM\Software" | @@ -195,9 +185,6 @@ Get full CrowdStrike Falcon alert records for one or more composite alert IDs (P | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `clientId` | string | Yes | CrowdStrike Falcon API client ID | -| `clientSecret` | string | Yes | CrowdStrike Falcon API client secret | -| `cloud` | string | Yes | CrowdStrike Falcon cloud region | | `compositeIds` | json | Yes | JSON array of CrowdStrike composite alert IDs | | `includeHidden` | boolean | No | Include previously hidden alerts \(CrowdStrike defaults this to true\) | @@ -277,9 +264,6 @@ Get CrowdStrike Falcon Case Management case records for one or more case IDs (PO | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `clientId` | string | Yes | CrowdStrike Falcon API client ID | -| `clientSecret` | string | Yes | CrowdStrike Falcon API client secret | -| `cloud` | string | Yes | CrowdStrike Falcon cloud region | | `caseIds` | json | Yes | JSON array of CrowdStrike case IDs | #### Output @@ -337,9 +321,6 @@ Get CrowdStrike Falcon host group records for one or more group IDs (GET /device | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `clientId` | string | Yes | CrowdStrike Falcon API client ID | -| `clientSecret` | string | Yes | CrowdStrike Falcon API client secret | -| `cloud` | string | Yes | CrowdStrike Falcon cloud region | | `hostGroupIds` | json | Yes | JSON array of CrowdStrike host group IDs | #### Output @@ -374,9 +355,6 @@ Get custom CrowdStrike Falcon indicator of compromise (IOC) records for one or m | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `clientId` | string | Yes | CrowdStrike Falcon API client ID | -| `clientSecret` | string | Yes | CrowdStrike Falcon API client secret | -| `cloud` | string | Yes | CrowdStrike Falcon cloud region | | `indicatorIds` | json | Yes | JSON array of CrowdStrike IOC IDs | #### Output @@ -433,9 +411,6 @@ Get the status and output of a Real Time Response command by cloud request ID (G | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `clientId` | string | Yes | CrowdStrike Falcon API client ID | -| `clientSecret` | string | Yes | CrowdStrike Falcon API client secret | -| `cloud` | string | Yes | CrowdStrike Falcon cloud region | | `cloudRequestId` | string | Yes | Cloud request ID returned by Execute RTR Command | | `sequenceId` | number | No | Output chunk to retrieve, starting at 0 | @@ -467,9 +442,6 @@ Aggregate CrowdStrike Identity Protection sensors from a JSON aggregate query bo | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `clientId` | string | Yes | CrowdStrike Falcon API client ID | -| `clientSecret` | string | Yes | CrowdStrike Falcon API client secret | -| `cloud` | string | Yes | CrowdStrike Falcon cloud region | | `aggregateQuery` | json | Yes | JSON aggregate query body documented by CrowdStrike for sensor aggregates | #### Output @@ -509,9 +481,6 @@ Get CrowdStrike Identity Protection sensor details for one or more device IDs (P | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `clientId` | string | Yes | CrowdStrike Falcon API client ID | -| `clientSecret` | string | Yes | CrowdStrike Falcon API client secret | -| `cloud` | string | Yes | CrowdStrike Falcon cloud region | | `ids` | json | Yes | JSON array of CrowdStrike sensor device IDs | #### Output @@ -560,9 +529,6 @@ Get CrowdStrike Falcon Spotlight vulnerability records for one or more vulnerabi | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `clientId` | string | Yes | CrowdStrike Falcon API client ID | -| `clientSecret` | string | Yes | CrowdStrike Falcon API client secret | -| `cloud` | string | Yes | CrowdStrike Falcon cloud region | | `vulnerabilityIds` | json | Yes | JSON array of Spotlight vulnerability IDs \(maximum 400 per request\) | #### Output @@ -639,9 +605,6 @@ Open a CrowdStrike Falcon Real Time Response session against a host so read-only | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `clientId` | string | Yes | CrowdStrike Falcon API client ID | -| `clientSecret` | string | Yes | CrowdStrike Falcon API client secret | -| `cloud` | string | Yes | CrowdStrike Falcon cloud region | | `deviceId` | string | Yes | CrowdStrike host agent ID \(AID\) to open the session against | | `queueOffline` | boolean | No | Queue the session so it runs when an offline host comes back online | | `origin` | string | No | Optional session origin string recorded by CrowdStrike | @@ -674,9 +637,6 @@ Act on CrowdStrike Falcon hosts (POST /devices/entities/devices-actions/v2). Act | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `clientId` | string | Yes | CrowdStrike Falcon API client ID | -| `clientSecret` | string | Yes | CrowdStrike Falcon API client secret | -| `cloud` | string | Yes | CrowdStrike Falcon cloud region | | `actionName` | string | Yes | Action to take: contain, lift_containment, hide_host, unhide_host, detection_suppress, or detection_unsuppress. "contain" network-isolates the host; "hide_host" removes it from the Falcon console. | | `deviceIds` | json | Yes | JSON array of up to 100 CrowdStrike host agent IDs \(AIDs\) to act on | @@ -705,9 +665,6 @@ Add hosts to or remove hosts from a CrowdStrike Falcon static host group (POST / | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `clientId` | string | Yes | CrowdStrike Falcon API client ID | -| `clientSecret` | string | Yes | CrowdStrike Falcon API client secret | -| `cloud` | string | Yes | CrowdStrike Falcon cloud region | | `actionName` | string | Yes | Action to take: add-hosts or remove-hosts | | `hostGroupId` | string | Yes | CrowdStrike host group ID to modify \(static groups only\) | | `deviceIds` | json | Yes | JSON array of CrowdStrike host agent IDs \(AIDs\) to add to or remove from the group | @@ -744,9 +701,6 @@ Search CrowdStrike Falcon alerts with a Falcon Query Language filter and return | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `clientId` | string | Yes | CrowdStrike Falcon API client ID | -| `clientSecret` | string | Yes | CrowdStrike Falcon API client secret | -| `cloud` | string | Yes | CrowdStrike Falcon cloud region | | `filter` | string | No | Falcon Query Language filter over alert fields | | `q` | string | No | Free-text search across all alert metadata | | `limit` | number | No | Maximum number of alert IDs to return \(max 10000\) | @@ -777,9 +731,6 @@ Search CrowdStrike Falcon Case Management cases with a Falcon Query Language fil | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `clientId` | string | Yes | CrowdStrike Falcon API client ID | -| `clientSecret` | string | Yes | CrowdStrike Falcon API client secret | -| `cloud` | string | Yes | CrowdStrike Falcon cloud region | | `filter` | string | No | Falcon Query Language filter. Exact-match fields include cid and id; wildcard fields include assigned_to_name and assigned_to_uuid; range fields include created_timestamp and updated_timestamp. | | `q` | string | No | Free-text search across all case metadata | | `limit` | number | No | Maximum number of case IDs to return \(max 10000, default 100\) | @@ -809,9 +760,6 @@ Search CrowdStrike Falcon host groups with a Falcon Query Language filter and re | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `clientId` | string | Yes | CrowdStrike Falcon API client ID | -| `clientSecret` | string | Yes | CrowdStrike Falcon API client secret | -| `cloud` | string | Yes | CrowdStrike Falcon cloud region | | `filter` | string | No | Falcon Query Language filter over host group fields | | `limit` | number | No | Maximum number of host group IDs to return \(1-5000\) | | `offset` | number | No | Pagination offset for the host group query | @@ -840,9 +788,6 @@ Search custom CrowdStrike Falcon indicators of compromise (IOCs) with a Falcon Q | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `clientId` | string | Yes | CrowdStrike Falcon API client ID | -| `clientSecret` | string | Yes | CrowdStrike Falcon API client secret | -| `cloud` | string | Yes | CrowdStrike Falcon cloud region | | `filter` | string | No | Falcon Query Language filter over IOC fields | | `limit` | number | No | Maximum number of IOC IDs to return \(default 100\). CrowdStrike publishes no maximum for this endpoint; Sim caps it at 500 to keep a single request bounded | | `offset` | number | No | Pagination offset. Mutually exclusive with the after cursor; use after beyond 10,000 IOCs. | @@ -873,9 +818,6 @@ Search CrowdStrike Identity Protection sensors -- the domain controllers Falcon | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `clientId` | string | Yes | CrowdStrike Falcon API client ID | -| `clientSecret` | string | Yes | CrowdStrike Falcon API client secret | -| `cloud` | string | Yes | CrowdStrike Falcon cloud region | | `filter` | string | No | Falcon Query Language filter for identity sensor search | | `limit` | number | No | Maximum number of sensor records to return | | `offset` | number | No | Pagination offset for the identity sensor query | @@ -927,9 +869,6 @@ Search CrowdStrike Falcon Spotlight vulnerabilities with a required Falcon Query | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `clientId` | string | Yes | CrowdStrike Falcon API client ID | -| `clientSecret` | string | Yes | CrowdStrike Falcon API client secret | -| `cloud` | string | Yes | CrowdStrike Falcon cloud region | | `filter` | string | Yes | Falcon Query Language filter \(required by Spotlight\). Filterable fields include status, aid, cid, last_seen_within, cve.id, cve.severity, cve.exprt_rating, cve.is_cisa_kev, cve.base_score, host_info.platform_name, host_info.groups, host_info.tags, host_info.internet_exposure, and suppression_info.is_suppressed. | | `limit` | number | No | Maximum number of vulnerability IDs to return \(1-400, default 100\) | | `after` | string | No | Pagination cursor from a previous response. Spotlight does not support offset. | @@ -958,9 +897,6 @@ Update CrowdStrike Falcon alerts by composite ID: change status, assign or unass | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `clientId` | string | Yes | CrowdStrike Falcon API client ID | -| `clientSecret` | string | Yes | CrowdStrike Falcon API client secret | -| `cloud` | string | Yes | CrowdStrike Falcon cloud region | | `compositeIds` | json | Yes | JSON array of CrowdStrike composite alert IDs to update | | `updateStatus` | string | No | New alert status: new, in_progress, reopened, or closed | | `assignToUuid` | string | No | Assign the alert to this Falcon user UUID | @@ -998,9 +934,6 @@ Update custom CrowdStrike Falcon indicators of compromise by ID (PATCH /iocs/ent | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `clientId` | string | Yes | CrowdStrike Falcon API client ID | -| `clientSecret` | string | Yes | CrowdStrike Falcon API client secret | -| `cloud` | string | Yes | CrowdStrike Falcon cloud region | | `indicators` | json | Yes | JSON array of indicators to update. Each entry requires id, and should also repeat every field it wants to keep: an updatable field the entry omits may be cleared. Updatable fields: action, severity, description, source, tags \(array\), platforms \(array\), applied_globally \(boolean\), host_groups \(array\), expiration \(ISO 8601\), mobile_action, metadata \(\{ filename \}\). type and value cannot be changed. | | `comment` | string | No | Audit comment explaining why these indicators were updated | | `retrodetects` | boolean | No | Whether to generate retroactive detections for the updated indicators | diff --git a/apps/docs/content/docs/integrations/datadog.mdx b/apps/docs/content/docs/integrations/datadog.mdx index 93b13968ef8..b143fadca99 100644 --- a/apps/docs/content/docs/integrations/datadog.mdx +++ b/apps/docs/content/docs/integrations/datadog.mdx @@ -2,7 +2,7 @@ title: Datadog description: Monitor infrastructure, applications, and logs with Datadog integration: - type: "datadog" + type: "datadog_v2" color: "#632CA6" --- @@ -11,6 +11,8 @@ import { ReferenceDetails } from "@/components/ui/reference-details" {/* MANUAL-CONTENT-START:intro */} Use [Datadog](https://datadoghq.com/) to send or query telemetry, manage monitors and incidents, and inspect observability data from a workflow. The actions below also cover dashboards, SLOs, synthetic tests, and security signals. + +Connect Datadog once as a Datadog credential: choose your site, then add an API key and an application key from **Organization Settings > API Keys and Application Keys**. Sim checks the API key when you save the credential. The application key is checked by the first action that needs it, so a key scoped to only the permissions your workflows use works. Workflows built with the earlier Datadog block keep the keys and site they entered in the block. {/* MANUAL-CONTENT-END */} ## Use the block [#usage-instructions] @@ -30,8 +32,6 @@ Submit custom metrics to Datadog. Use for tracking application performance, busi | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | | `series` | string | Yes | JSON array of metric series to submit. Each entry needs "metric" and "points" \(objects with "timestamp" in POSIX seconds and a numeric "value"\); timestamps cannot be more than 10 minutes in the future or 1 hour in the past. Optional per entry: "type" \("count", "rate", or "gauge"; omit to let Datadog infer\), "interval" in seconds \(required by Datadog for count and rate\), "tags", "unit", "sourceTypeName", and "resources". | -| `apiKey` | string | Yes | Datadog API key | -| `site` | string | No | Datadog site/region \(default: datadoghq.com\) | #### Output @@ -55,9 +55,6 @@ Query metric timeseries data from Datadog. Use for analyzing trends, creating re | `query` | string | Yes | Datadog metrics query \(e.g., "avg:system.cpu.user\{*\}", "sum:nginx.requests\{env:prod\}.as_count\(\)"\) | | `from` | number | Yes | Start time as Unix timestamp in seconds \(e.g., 1705320000\) | | `to` | number | Yes | End time as Unix timestamp in seconds \(e.g., 1705323600\) | -| `apiKey` | string | Yes | Datadog API key | -| `applicationKey` | string | Yes | Datadog Application key | -| `site` | string | No | Datadog site/region \(default: datadoghq.com\) | #### Output @@ -92,8 +89,6 @@ Post an event to the Datadog event stream. Use for deployment notifications, ale | `aggregationKey` | string | No | Key to aggregate events together | | `sourceTypeName` | string | No | Source type name for the event | | `dateHappened` | number | No | Unix timestamp in seconds when the event occurred \(e.g., 1705320000, defaults to now\). Datadog limits this to events no older than 18 hours. | -| `apiKey` | string | Yes | Datadog API key | -| `site` | string | No | Datadog site/region \(default: datadoghq.com\) | #### Output @@ -129,9 +124,6 @@ Create a new monitor/alert in Datadog. Monitors can track metrics, service check | `tags` | string | No | Comma-separated list of tags | | `priority` | number | No | Monitor priority \(1-5, where 1 is highest\) | | `options` | string | No | JSON string of monitor options \(thresholds, notify_no_data, renotify_interval, etc.\) | -| `apiKey` | string | Yes | Datadog API key | -| `applicationKey` | string | Yes | Datadog Application key | -| `site` | string | No | Datadog site/region \(default: datadoghq.com\) | #### Output @@ -166,9 +158,6 @@ Retrieve details of a specific monitor by ID. | `monitorId` | string | Yes | The ID of the monitor to retrieve \(e.g., "12345678"\) | | `groupStates` | string | No | Comma-separated group states to include. Valid values are "all", "alert", "warn", and "no data" \(e.g., "alert,warn"\). | | `withDowntimes` | boolean | No | Include downtime data with the monitor | -| `apiKey` | string | Yes | Datadog API key | -| `applicationKey` | string | Yes | Datadog Application key | -| `site` | string | No | Datadog site/region \(default: datadoghq.com\) | #### Output @@ -207,9 +196,6 @@ List all monitors in Datadog with optional filtering by name, tags, or state. | `withDowntimes` | boolean | No | Include downtime data with monitors | | `page` | number | No | Page to start paginating from \(0-indexed, e.g., 0, 1, 2\). Datadog returns every monitor in the org without pagination when this is not specified, so set it to bound the response. Setting Page Size alone implies page 0. | | `pageSize` | number | No | Number of monitors per page \(e.g., 50, max: 1000\). Datadog only applies this when a page is specified — otherwise it returns all monitors with no page size limit — so setting this alone sends page 0. With a page but no page size, Datadog defaults to 100. | -| `apiKey` | string | Yes | Datadog API key | -| `applicationKey` | string | Yes | Datadog Application key | -| `site` | string | No | Datadog site/region \(default: datadoghq.com\) | #### Output @@ -244,9 +230,6 @@ Mute a monitor to temporarily suppress its notifications. Use Unmute Monitor to | `monitorId` | string | Yes | The ID of the monitor to mute \(e.g., "12345678"\) | | `scope` | string | No | Scope to mute \(e.g., "host:myhost", "env:prod"\). If not specified, mutes all scopes. | | `end` | number | No | Unix timestamp in seconds when the mute should end \(e.g., 1705323600\). If not specified, the monitor stays muted until it is unmuted. | -| `apiKey` | string | Yes | Datadog API key | -| `applicationKey` | string | Yes | Datadog Application key | -| `site` | string | No | Datadog site/region \(default: datadoghq.com\) | #### Output @@ -272,9 +255,6 @@ Unmute a monitor so it resumes sending notifications. Reverses Mute Monitor, eit | `monitorId` | string | Yes | The ID of the monitor to unmute \(e.g., "12345678"\) | | `scope` | string | No | Scope to unmute \(e.g., "host:myhost"\). Leave blank to unmute the monitor itself rather than a single scope. | | `allScopes` | boolean | No | Clear the mute settings for every scope on this monitor | -| `apiKey` | string | Yes | Datadog API key | -| `applicationKey` | string | Yes | Datadog Application key | -| `site` | string | No | Datadog site/region \(default: datadoghq.com\) | #### Output @@ -304,9 +284,6 @@ Search and retrieve logs from Datadog. Use for troubleshooting, analysis, or mon | `cursor` | string | No | Pagination cursor from a previous call, taken from its nextLogId output. Omit for the first page. | | `sort` | string | No | Sort order: "timestamp" for oldest first, "-timestamp" for newest first | | `indexes` | string | No | Comma-separated list of log indexes to search | -| `apiKey` | string | Yes | Datadog API key | -| `applicationKey` | string | Yes | Datadog Application key | -| `site` | string | No | Datadog site/region \(default: datadoghq.com\) | #### Output @@ -337,8 +314,6 @@ Send log entries to Datadog for centralized logging and analysis. | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | | `logs` | string | Yes | JSON array of log entries. Each entry should have message and optionally ddsource, ddtags, hostname, service. Sim fills in ddsource="custom" when an entry omits it — that is a Sim default, not a Datadog one; set ddsource yourself to have Datadog apply the matching integration log pipeline. | -| `apiKey` | string | Yes | Datadog API key | -| `site` | string | No | Datadog site/region \(default: datadoghq.com\) | #### Output @@ -366,9 +341,6 @@ Schedule a downtime to suppress monitor notifications during maintenance windows | `monitorId` | string | No | Specific monitor ID to mute \(e.g., "12345678"\) | | `monitorTags` | string | No | Comma-separated monitor tags to match \(e.g., "team:backend,priority:high"\) | | `muteFirstRecoveryNotification` | boolean | No | Mute the first recovery notification | -| `apiKey` | string | Yes | Datadog API key | -| `applicationKey` | string | Yes | Datadog Application key | -| `site` | string | No | Datadog site/region \(default: datadoghq.com\) | #### Output @@ -400,9 +372,6 @@ List all scheduled downtimes in Datadog. | `currentOnly` | boolean | No | Only return currently active downtimes | | `limit` | number | No | Number of downtimes to return per page. Datadog defaults to 30 and declares no maximum; keep this at 100 or below to stay within the bound Sim recommends. | | `offset` | number | No | Index of the first downtime to return \(e.g., 0, 30, 60\) | -| `apiKey` | string | Yes | Datadog API key | -| `applicationKey` | string | Yes | Datadog Application key | -| `site` | string | No | Datadog site/region \(default: datadoghq.com\) | #### Output @@ -433,9 +402,6 @@ Cancel a scheduled downtime. | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | | `downtimeId` | string | Yes | The ID of the downtime to cancel \(e.g., "abc123def456"\) | -| `apiKey` | string | Yes | Datadog API key | -| `applicationKey` | string | Yes | Datadog Application key | -| `site` | string | No | Datadog site/region \(default: datadoghq.com\) | #### Output @@ -458,9 +424,6 @@ List incidents for the organization. Requires the Incident Management `incident_ | `include` | string | No | Comma-separated related resources to include: "users" and/or "attachments" | | `pageSize` | number | No | Number of incidents to return per page \(default: 10, max: 100\) | | `pageOffset` | number | No | Index of the first incident to return \(e.g., 0, 10, 20\) | -| `apiKey` | string | Yes | Datadog API key | -| `applicationKey` | string | Yes | Datadog Application key | -| `site` | string | No | Datadog site/region \(default: datadoghq.com\) | #### Output @@ -494,9 +457,6 @@ Get the details of a single incident by ID. Requires the Incident Management `in | --------- | ---- | -------- | ----------- | | `incidentId` | string | Yes | The UUID of the incident \(e.g., "00000000-0000-0000-1234-000000000000"\) | | `include` | string | No | Comma-separated related resources to include \(e.g., "users", "attachments"\) | -| `apiKey` | string | Yes | Datadog API key | -| `applicationKey` | string | Yes | Datadog Application key | -| `site` | string | No | Datadog site/region \(default: datadoghq.com\) | #### Output @@ -537,9 +497,6 @@ Declare a new incident. Requires the Incident Management `incident_write` permis | `isTest` | boolean | No | Whether this is a test incident | | `fields` | string | No | JSON object of user-defined incident fields, e.g. \{"severity": \{"type": "dropdown", "value": "SEV-2"\}\} | | `notificationHandles` | string | No | Comma-separated handles to notify on creation \(e.g., "@slack-incidents,@user@example.com"\) | -| `apiKey` | string | Yes | Datadog API key | -| `applicationKey` | string | Yes | Datadog Application key | -| `site` | string | No | Datadog site/region \(default: datadoghq.com\) | #### Output @@ -577,9 +534,6 @@ Partially update an existing incident. Requires the Incident Management `inciden | `detected` | string | No | ISO-8601 timestamp when the incident was detected | | `fields` | string | No | JSON object of user-defined incident fields to update, e.g. \{"state": \{"type": "dropdown", "value": "resolved"\}\} | | `notificationHandles` | string | No | Comma-separated handles to notify about the update | -| `apiKey` | string | Yes | Datadog API key | -| `applicationKey` | string | Yes | Datadog Application key | -| `site` | string | No | Datadog site/region \(default: datadoghq.com\) | #### Output @@ -611,9 +565,6 @@ Add a follow-up task (todo) to an incident. Requires the Incident Management `in | `content` | string | Yes | The follow-up task content \(e.g., "Restore lost data"\) | | `assignees` | string | Yes | Comma-separated assignee handles \(e.g., "@jane@example.com,@on-call"\). Datadog requires at least one assignee | | `dueDate` | string | No | ISO-8601 timestamp for when the todo should be completed | -| `apiKey` | string | Yes | Datadog API key | -| `applicationKey` | string | Yes | Datadog Application key | -| `site` | string | No | Datadog site/region \(default: datadoghq.com\) | #### Output @@ -648,9 +599,6 @@ List service level objectives, optionally filtered by IDs, name, tags, or underl | `limit` | number | No | Number of SLOs to return \(default: 1000\) | | `offset` | number | No | Offset of the first SLO returned \(e.g., 0, 50\) | | `isDeleted` | boolean | No | Return only deleted SLOs | -| `apiKey` | string | Yes | Datadog API key | -| `applicationKey` | string | Yes | Datadog Application key | -| `site` | string | No | Datadog site/region \(default: datadoghq.com\) | #### Output @@ -684,9 +632,6 @@ Get the configuration of a single service level objective by ID. | --------- | ---- | -------- | ----------- | | `sloId` | string | Yes | The ID of the service level objective | | `withConfiguredAlertIds` | boolean | No | Include the IDs of SLO monitors that reference this SLO | -| `apiKey` | string | Yes | Datadog API key | -| `applicationKey` | string | Yes | Datadog Application key | -| `site` | string | No | Datadog site/region \(default: datadoghq.com\) | #### Output @@ -731,9 +676,6 @@ Create a service level objective from a metric query, monitors, or a time-slice | `targetThreshold` | number | No | Primary target threshold \(e.g., 99.9\) | | `warningThreshold` | number | No | Primary warning threshold, must be greater than the target \(e.g., 99.95\) | | `timeframe` | string | No | Primary timeframe: "7d", "30d", or "90d" | -| `apiKey` | string | Yes | Datadog API key | -| `applicationKey` | string | Yes | Datadog Application key | -| `site` | string | No | Datadog site/region \(default: datadoghq.com\) | #### Output @@ -773,9 +715,6 @@ Update a service level objective. Reads the current SLO first and applies only t | `targetThreshold` | number | No | Primary target threshold \(e.g., 99.9\) | | `warningThreshold` | number | No | Primary warning threshold, must be greater than the target \(e.g., 99.95\) | | `timeframe` | string | No | Primary timeframe: "7d", "30d", or "90d" | -| `apiKey` | string | Yes | Datadog API key | -| `applicationKey` | string | Yes | Datadog Application key | -| `site` | string | No | Datadog site/region \(default: datadoghq.com\) | #### Output @@ -804,9 +743,6 @@ Permanently delete a service level objective. Datadog returns a conflict when th | --------- | ---- | -------- | ----------- | | `sloId` | string | Yes | The ID of the service level objective to delete | | `force` | boolean | No | Delete even when the SLO is referenced by other resources | -| `apiKey` | string | Yes | Datadog API key | -| `applicationKey` | string | Yes | Datadog Application key | -| `site` | string | No | Datadog site/region \(default: datadoghq.com\) | #### Output @@ -832,9 +768,6 @@ Get an SLO’s history over a time window, including the overall SLI value and r | `toTs` | number | Yes | End of the query window as a Unix timestamp in seconds | | `target` | number | No | SLO target between 0 and 100. When supplied, the response includes the remaining error budget for a custom timeframe | | `applyCorrection` | boolean | No | Whether to apply SLO corrections \(defaults to true\) | -| `apiKey` | string | Yes | Datadog API key | -| `applicationKey` | string | Yes | Datadog Application key | -| `site` | string | No | Datadog site/region \(default: datadoghq.com\) | #### Output @@ -869,9 +802,6 @@ List custom created or cloned dashboards. Datadog preset dashboards are not retu | `filterDeleted` | boolean | No | Return only deleted dashboards. Incompatible with filterShared | | `count` | number | No | Maximum number of dashboards to return \(default: 100\) | | `start` | number | No | Offset of the first dashboard returned \(e.g., 0, 100\) | -| `apiKey` | string | Yes | Datadog API key | -| `applicationKey` | string | Yes | Datadog Application key | -| `site` | string | No | Datadog site/region \(default: datadoghq.com\) | #### Output @@ -901,9 +831,6 @@ Get the full definition of a dashboard, including its widgets. | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | | `dashboardId` | string | Yes | The ID of the dashboard \(e.g., "abc-def-ghi"\) | -| `apiKey` | string | Yes | Datadog API key | -| `applicationKey` | string | Yes | Datadog Application key | -| `site` | string | No | Datadog site/region \(default: datadoghq.com\) | #### Output @@ -944,9 +871,6 @@ Create a dashboard from a title, layout type, and widget definitions. | `templateVariables` | string | No | JSON array of template variable definitions, e.g. \[\{"name": "env", "prefix": "env", "available_values": \["prod"\]\}\] | | `tags` | string | No | Comma-separated dashboard tags in the form "team:<name>" \(max 5\) | | `reflowType` | string | No | Reflow type for ordered layouts: "auto" or "fixed" | -| `apiKey` | string | Yes | Datadog API key | -| `applicationKey` | string | Yes | Datadog Application key | -| `site` | string | No | Datadog site/region \(default: datadoghq.com\) | #### Output @@ -975,9 +899,6 @@ Delete a dashboard by ID. | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | | `dashboardId` | string | Yes | The ID of the dashboard to delete | -| `apiKey` | string | Yes | Datadog API key | -| `applicationKey` | string | Yes | Datadog Application key | -| `site` | string | No | Datadog site/region \(default: datadoghq.com\) | #### Output @@ -1000,9 +921,6 @@ List all Synthetic tests (API, browser, and mobile) with their current status. | --------- | ---- | -------- | ----------- | | `pageSize` | number | No | Number of tests returned per page \(default: 100\) | | `pageNumber` | number | No | Page to retrieve, starting at zero | -| `apiKey` | string | Yes | Datadog API key | -| `applicationKey` | string | Yes | Datadog Application key | -| `site` | string | No | Datadog site/region \(default: datadoghq.com\) | #### Output @@ -1032,9 +950,6 @@ Get the configuration of a Synthetic test by public ID. Browser test steps are n | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | | `publicId` | string | Yes | The public ID of the Synthetic test \(e.g., "abc-def-ghi"\) | -| `apiKey` | string | Yes | Datadog API key | -| `applicationKey` | string | Yes | Datadog Application key | -| `site` | string | No | Datadog site/region \(default: datadoghq.com\) | #### Output @@ -1070,9 +985,6 @@ Get the latest result summaries (up to the last 150 runs) for a Synthetic API te | `fromTs` | number | No | Timestamp in milliseconds from which to start querying results | | `toTs` | number | No | Timestamp in milliseconds up to which to query results | | `probeDc` | string | No | Comma-separated locations to query results for \(e.g., "aws:eu-west-3"\) | -| `apiKey` | string | Yes | Datadog API key | -| `applicationKey` | string | Yes | Datadog Application key | -| `site` | string | No | Datadog site/region \(default: datadoghq.com\) | #### Output @@ -1104,9 +1016,6 @@ Get the latest result summaries (up to the last 150 runs) for a Synthetic browse | `fromTs` | number | No | Timestamp in milliseconds from which to start querying results | | `toTs` | number | No | Timestamp in milliseconds up to which to query results | | `probeDc` | string | No | Comma-separated locations to query results for \(e.g., "aws:eu-west-3"\) | -| `apiKey` | string | Yes | Datadog API key | -| `applicationKey` | string | Yes | Datadog Application key | -| `site` | string | No | Datadog site/region \(default: datadoghq.com\) | #### Output @@ -1138,9 +1047,6 @@ Trigger an immediate run of one or more Synthetic tests by public ID. | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | | `publicIds` | string | Yes | Comma-separated public IDs of the Synthetic tests to trigger | -| `apiKey` | string | Yes | Datadog API key | -| `applicationKey` | string | Yes | Datadog Application key | -| `site` | string | No | Datadog site/region \(default: datadoghq.com\) | #### Output @@ -1171,9 +1077,6 @@ Pause or resume a Synthetic test by setting its status to "paused" or "live". | --------- | ---- | -------- | ----------- | | `publicId` | string | Yes | The public ID of the Synthetic test to update | | `newStatus` | string | Yes | New status: "live" to start the test or "paused" to pause it | -| `apiKey` | string | Yes | Datadog API key | -| `applicationKey` | string | Yes | Datadog Application key | -| `site` | string | No | Datadog site/region \(default: datadoghq.com\) | #### Output @@ -1200,9 +1103,6 @@ Search Cloud SIEM security signals by query and time range. Requires the `securi | `sort` | string | No | Sort order: "timestamp" for oldest first, "-timestamp" for newest first | | `cursor` | string | No | Pagination cursor returned as nextCursor by a previous call | | `limit` | number | No | Maximum number of signals to return \(default: 10, max: 1000\) | -| `apiKey` | string | Yes | Datadog API key | -| `applicationKey` | string | Yes | Datadog Application key | -| `site` | string | No | Datadog site/region \(default: datadoghq.com\) | #### Output @@ -1231,9 +1131,6 @@ Get the details of a single Cloud SIEM security signal. Requires the `security_m | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | | `signalId` | string | Yes | The ID of the security signal | -| `apiKey` | string | Yes | Datadog API key | -| `applicationKey` | string | Yes | Datadog Application key | -| `site` | string | No | Datadog site/region \(default: datadoghq.com\) | #### Output @@ -1264,9 +1161,6 @@ Change the triage state of a Cloud SIEM security signal to open, under_review, o | `state` | string | Yes | New triage state: "open", "under_review", or "archived" | | `archiveReason` | string | No | Reason when archiving: none, false_positive, testing_or_maintenance, remediated, investigated_case_opened, true_positive_benign, true_positive_malicious, or other | | `archiveComment` | string | No | Comment explaining why the signal was archived | -| `apiKey` | string | Yes | Datadog API key | -| `applicationKey` | string | Yes | Datadog Application key | -| `site` | string | No | Datadog site/region \(default: datadoghq.com\) | #### Output @@ -1296,9 +1190,6 @@ Assign a Cloud SIEM security signal to a Datadog user by UUID. Requires the `sec | --------- | ---- | -------- | ----------- | | `signalId` | string | Yes | The ID of the security signal | | `assigneeUuid` | string | Yes | UUID of the Datadog user to assign the signal to \(e.g., "773b045d-ccf8-4808-bd3b-955ef6a8c940"\) | -| `apiKey` | string | Yes | Datadog API key | -| `applicationKey` | string | Yes | Datadog Application key | -| `site` | string | No | Datadog site/region \(default: datadoghq.com\) | #### Output @@ -1330,9 +1221,6 @@ List Cloud SIEM detection rules. Requires the `security_monitoring_rules_read` p | `sort` | string | No | Sort attribute, prefix with "-" for descending: name, creation_date, update_date, enabled, type, highest_severity, or source | | `pageSize` | number | No | Number of rules per page \(default: 10, max: 100\) | | `pageNumber` | number | No | Page to retrieve, starting at zero | -| `apiKey` | string | Yes | Datadog API key | -| `applicationKey` | string | Yes | Datadog Application key | -| `site` | string | No | Datadog site/region \(default: datadoghq.com\) | #### Output @@ -1367,9 +1255,6 @@ Search indexed APM spans using the span query syntax, with cursor pagination. | `sort` | string | No | Sort order: "timestamp" for oldest first, "-timestamp" for newest first | | `cursor` | string | No | Pagination cursor returned as nextCursor by a previous call | | `limit` | number | No | Maximum number of spans to return \(default: 10, max: 1000\) | -| `apiKey` | string | Yes | Datadog API key | -| `applicationKey` | string | Yes | Datadog Application key | -| `site` | string | No | Datadog site/region \(default: datadoghq.com\) | #### Output @@ -1409,9 +1294,6 @@ List service definitions from the Datadog Service Catalog. Requires the `apm_ser | `pageSize` | number | No | Number of service definitions per page \(default: 10, max: 100\) | | `pageNumber` | number | No | Page to retrieve, starting at zero | | `schemaVersion` | string | No | Schema version to return \(e.g., "v2", "v2.1", "v2.2"\) | -| `apiKey` | string | Yes | Datadog API key | -| `applicationKey` | string | Yes | Datadog Application key | -| `site` | string | No | Datadog site/region \(default: datadoghq.com\) | #### Output diff --git a/apps/docs/content/docs/integrations/gong.mdx b/apps/docs/content/docs/integrations/gong.mdx index 78c99697c27..b28db606ad6 100644 --- a/apps/docs/content/docs/integrations/gong.mdx +++ b/apps/docs/content/docs/integrations/gong.mdx @@ -2,7 +2,7 @@ title: Gong description: Read Gong data, upload calls, assign or unassign flow prospects, and request data erasure integration: - type: "gong" + type: "gong_v2" color: "#8039DF" --- @@ -12,7 +12,9 @@ import { ReferenceDetails } from "@/components/ui/reference-details" {/* MANUAL-CONTENT-START:intro */} Use [Gong](https://www.gong.io/) to list calls, read call details and transcripts, and retrieve topics, trackers, interaction statistics, and other extensive call data. The actions also read users, activity and coaching metrics, answered scorecards, scorecard definitions, tracker configuration, call-library folders and content, and Engage flows. -Look up an email address or phone number to find related calls, emails, meetings, CRM records, and engagement events. Connect your Gong API credentials and inspect the returned data when preparing coaching reports or syncing conversations to another system. +Look up an email address or phone number to find related calls, emails, meetings, CRM records, and engagement events, and inspect the returned data when preparing coaching reports or syncing conversations to another system. + +Connect Gong once as a Gong credential: a Gong technical administrator creates an access key and secret on the **API** page of the Gong company settings, then adds both. Sim calls `https://api.gong.io`; an account whose API page shows a different base URL is not supported by the credential yet. Workflows built with the earlier Gong block keep the access key and secret they entered in the block. **Create Call** uploads metadata and asks Gong to fetch the recording from a media URL. **Assign Flow Prospects** and **Unassign Flow Prospects** change a prospect’s Engage flow assignments. **Purge Email Address** and **Purge Phone Number** request deletion of associated Gong data. [Gong processes these deletions in the background](https://help.gong.io/docs/delete-personal-data); a returned request ID does not confirm completion. {/* MANUAL-CONTENT-END */} @@ -33,8 +35,6 @@ Retrieve call data by date range from Gong. | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `accessKey` | string | Yes | Gong API Access Key | -| `accessKeySecret` | string | Yes | Gong API Access Key Secret | | `fromDateTime` | string | Yes | Start date/time in ISO-8601 format \(e.g., 2024-01-01T00:00:00Z\) | | `toDateTime` | string | No | End date/time in ISO-8601 format \(e.g., 2024-01-31T23:59:59Z\). Defaults to the current execution time when omitted. | | `cursor` | string | No | Pagination cursor from a previous response | @@ -83,8 +83,6 @@ Upload call metadata to Gong and let Gong pull the media from a URL. | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `accessKey` | string | Yes | Gong API Access Key | -| `accessKeySecret` | string | Yes | Gong API Access Key Secret | | `clientUniqueId` | string | Yes | Unique call ID from the source telephony or recording system | | `actualStart` | string | Yes | Actual call start time in ISO-8601 format | | `primaryUser` | string | Yes | Gong user ID for the call's host or owner | @@ -117,8 +115,6 @@ Retrieve detailed data for a specific call from Gong. | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `accessKey` | string | Yes | Gong API Access Key | -| `accessKeySecret` | string | Yes | Gong API Access Key Secret | | `callId` | string | Yes | The Gong call ID to retrieve | #### Output @@ -159,8 +155,6 @@ Retrieve transcripts of calls from Gong by call IDs or date range. | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `accessKey` | string | Yes | Gong API Access Key | -| `accessKeySecret` | string | Yes | Gong API Access Key Secret | | `callIds` | string | No | Comma-separated list of call IDs to retrieve transcripts for | | `fromDateTime` | string | No | Start date/time filter in ISO-8601 format | | `toDateTime` | string | No | End date/time filter in ISO-8601 format | @@ -195,8 +189,6 @@ Retrieve detailed call data including trackers, topics, highlights, and AI spotl | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `accessKey` | string | Yes | Gong API Access Key | -| `accessKeySecret` | string | Yes | Gong API Access Key Secret | | `callIds` | string | No | Comma-separated list of call IDs to retrieve detailed data for | | `fromDateTime` | string | No | Start date/time filter in ISO-8601 format | | `toDateTime` | string | No | End date/time filter in ISO-8601 format | @@ -322,8 +314,6 @@ List all users in your Gong account. | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `accessKey` | string | Yes | Gong API Access Key | -| `accessKeySecret` | string | Yes | Gong API Access Key Secret | | `cursor` | string | No | Pagination cursor from a previous response | | `includeAvatars` | string | No | Whether to include avatar URLs \(true/false\) | @@ -375,8 +365,6 @@ Retrieve details for a specific user from Gong. | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `accessKey` | string | Yes | Gong API Access Key | -| `accessKeySecret` | string | Yes | Gong API Access Key Secret | | `userId` | string | Yes | The Gong user ID to retrieve | #### Output @@ -422,8 +410,6 @@ Retrieve aggregated activity statistics for users by date range from Gong. | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `accessKey` | string | Yes | Gong API Access Key | -| `accessKeySecret` | string | Yes | Gong API Access Key Secret | | `userIds` | string | No | Comma-separated list of Gong user IDs \(up to 20 digits each\) | | `fromDate` | string | Yes | Start date in YYYY-MM-DD format \(inclusive, in company timezone\) | | `toDate` | string | Yes | End date in YYYY-MM-DD format \(exclusive, in company timezone, cannot exceed current day\) | @@ -469,8 +455,6 @@ Retrieve detailed day-by-day activity (call IDs per activity type) for users by | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `accessKey` | string | Yes | Gong API Access Key | -| `accessKeySecret` | string | Yes | Gong API Access Key Secret | | `userIds` | string | No | Comma-separated list of Gong user IDs \(up to 20 digits each\) | | `fromDate` | string | Yes | Start date in YYYY-MM-DD format \(inclusive, in company timezone\) | | `toDate` | string | Yes | End date in YYYY-MM-DD format \(exclusive, in company timezone, cannot exceed current day\) | @@ -516,8 +500,6 @@ Retrieve aggregated user activity grouped into time periods (day, week, month, q | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `accessKey` | string | Yes | Gong API Access Key | -| `accessKeySecret` | string | Yes | Gong API Access Key Secret | | `aggregationPeriod` | string | Yes | Calendar period to group activity by: DAY, WEEK, MONTH, QUARTER, or YEAR \(week starts Monday\) | | `userIds` | string | No | Comma-separated list of Gong user IDs \(up to 20 digits each\) | | `fromDate` | string | Yes | Start date in YYYY-MM-DD format \(inclusive, in company timezone\) | @@ -564,8 +546,6 @@ Retrieve interaction statistics for users by date range from Gong. Only includes | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `accessKey` | string | Yes | Gong API Access Key | -| `accessKeySecret` | string | Yes | Gong API Access Key Secret | | `userIds` | string | No | Comma-separated list of Gong user IDs \(up to 20 digits each\) | | `fromDate` | string | Yes | Start date in YYYY-MM-DD format \(inclusive, in company timezone\) | | `toDate` | string | Yes | End date in YYYY-MM-DD format \(exclusive, in company timezone, cannot exceed current day\) | @@ -599,8 +579,6 @@ Retrieve answered scorecards for reviewed users or by date range from Gong. | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `accessKey` | string | Yes | Gong API Access Key | -| `accessKeySecret` | string | Yes | Gong API Access Key Secret | | `callFromDate` | string | No | Start date for calls in YYYY-MM-DD format \(inclusive, in company timezone\). Defaults to earliest recorded call. | | `callToDate` | string | No | End date for calls in YYYY-MM-DD format \(exclusive, in company timezone\). Defaults to latest recorded call. | | `reviewFromDate` | string | No | Start date for reviews in YYYY-MM-DD format \(inclusive, in company timezone\). Defaults to earliest reviewed call. | @@ -646,8 +624,6 @@ Retrieve library folders from Gong. | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `accessKey` | string | Yes | Gong API Access Key | -| `accessKeySecret` | string | Yes | Gong API Access Key Secret | | `workspaceId` | string | No | Gong workspace ID to filter folders | #### Output @@ -674,8 +650,6 @@ Retrieve the list of calls in a specific library folder from Gong. | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `accessKey` | string | Yes | Gong API Access Key | -| `accessKeySecret` | string | Yes | Gong API Access Key Secret | | `folderId` | string | No | The library folder ID to retrieve content for \(up to 20 digits\) | #### Output @@ -710,8 +684,6 @@ Retrieve scorecard definitions from Gong settings. | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `accessKey` | string | Yes | Gong API Access Key | -| `accessKeySecret` | string | Yes | Gong API Access Key Secret | #### Output @@ -752,8 +724,6 @@ Retrieve smart tracker and keyword tracker definitions from Gong settings. | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `accessKey` | string | Yes | Gong API Access Key | -| `accessKeySecret` | string | Yes | Gong API Access Key Secret | | `workspaceId` | string | No | The ID of the workspace the keyword trackers are in. When empty, all trackers in all workspaces are returned. | #### Output @@ -793,8 +763,6 @@ List all company workspaces in Gong. | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `accessKey` | string | Yes | Gong API Access Key | -| `accessKeySecret` | string | Yes | Gong API Access Key Secret | #### Output @@ -818,8 +786,6 @@ List Gong Engage flows (sales engagement sequences). | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `accessKey` | string | Yes | Gong API Access Key | -| `accessKeySecret` | string | Yes | Gong API Access Key Secret | | `flowOwnerEmail` | string | Yes | Email of a Gong user. The API will return 'PERSONAL' flows belonging to this user in addition to 'COMPANY' flows. | | `workspaceId` | string | No | Optional workspace ID to filter flows to a specific workspace | | `cursor` | string | No | Pagination cursor from a previous API call to retrieve the next page of records | @@ -854,8 +820,6 @@ Assign up to 200 CRM prospects (contacts or leads) to a Gong Engage flow. | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `accessKey` | string | Yes | Gong API Access Key | -| `accessKeySecret` | string | Yes | Gong API Access Key Secret | | `flowId` | string | Yes | The Gong Engage flow ID to assign the prospects to | | `crmProspectsIds` | string | Yes | Comma-separated list of CRM prospect IDs \(contacts or leads\) to assign | | `flowInstanceOwnerEmail` | string | Yes | Email of the Gong user who owns the flow instance and its to-dos | @@ -894,8 +858,6 @@ Remove a prospect from Gong Engage flows. Omit the flow ID to remove the prospec | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `accessKey` | string | Yes | Gong API Access Key | -| `accessKeySecret` | string | Yes | Gong API Access Key Secret | | `crmProspectId` | string | Yes | The CRM ID of the prospect to unassign | | `flowId` | string | No | The ID of the flow to unassign the prospect from. If omitted, the prospect is removed from all flows they are assigned to. | | `unassignedByUserEmail` | string | No | Email address of the Gong user requesting to remove the prospect from the flow | @@ -919,8 +881,6 @@ Get the Gong Engage flows currently assigned to the given CRM prospects. | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `accessKey` | string | Yes | Gong API Access Key | -| `accessKeySecret` | string | Yes | Gong API Access Key Secret | | `crmProspectsIds` | string | Yes | Comma-separated list of CRM prospect IDs \(contacts or leads\) to look up | #### Output @@ -952,8 +912,6 @@ Retrieve coaching metrics for a manager from Gong. | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `accessKey` | string | Yes | Gong API Access Key | -| `accessKeySecret` | string | Yes | Gong API Access Key Secret | | `managerId` | string | Yes | Gong user ID of the manager | | `workspaceId` | string | Yes | Gong workspace ID | | `fromDate` | string | Yes | Start date in ISO-8601 format | @@ -992,8 +950,6 @@ Ask a natural-language question about a CRM account, deal, contact, or lead. Gon | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `accessKey` | string | Yes | Gong API Access Key | -| `accessKeySecret` | string | Yes | Gong API Access Key Secret | | `workspaceId` | string | Yes | Gong workspace ID the entity belongs to | | `crmEntityType` | string | Yes | Type of the CRM entity: ACCOUNT, CONTACT, DEAL, or LEAD | | `crmEntityId` | string | Yes | The CRM ID of the entity the question is asked about | @@ -1026,8 +982,6 @@ Generate an AI brief (configured in Gong Agent Studio) for a CRM account, deal, | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `accessKey` | string | Yes | Gong API Access Key | -| `accessKeySecret` | string | Yes | Gong API Access Key Secret | | `workspaceId` | string | Yes | Gong workspace ID the entity belongs to | | `briefName` | string | Yes | Name of the brief to generate, as configured in Gong Agent Studio > AI Briefer | | `crmEntityType` | string | Yes | Type of the CRM entity: ACCOUNT, CONTACT, DEAL, or LEAD | @@ -1063,8 +1017,6 @@ Retrieve Gong log entries of a specific type within a time range. | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `accessKey` | string | Yes | Gong API Access Key | -| `accessKeySecret` | string | Yes | Gong API Access Key Secret | | `logType` | string | Yes | Type of logs requested: AccessLog, UserActivityLog, UserCallPlay, ExternallySharedCallAccess, or ExternallySharedCallPlay | | `fromDateTime` | string | Yes | Time from which to retrieve log records, in ISO-8601 format \(e.g., '2024-01-01T00:00:00Z'\) | | `toDateTime` | string | No | Time until which to retrieve log records, in ISO-8601 format. Defaults to the latest available logs when omitted. | @@ -1102,8 +1054,6 @@ Find all references to an email address in Gong (calls, email messages, meetings | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `accessKey` | string | Yes | Gong API Access Key | -| `accessKeySecret` | string | Yes | Gong API Access Key Secret | | `emailAddress` | string | Yes | Email address to look up | #### Output @@ -1158,8 +1108,6 @@ Find all references to a phone number in Gong (calls, email messages, meetings, | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `accessKey` | string | Yes | Gong API Access Key | -| `accessKeySecret` | string | Yes | Gong API Access Key Secret | | `phoneNumber` | string | Yes | Phone number to look up \(must start with + followed by country code\) | #### Output @@ -1209,8 +1157,6 @@ Erase all Gong data (calls, email messages, leads, contacts) referencing an emai | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `accessKey` | string | Yes | Gong API Access Key | -| `accessKeySecret` | string | Yes | Gong API Access Key Secret | | `emailAddress` | string | Yes | Email address whose associated data should be permanently erased from Gong | #### Output @@ -1231,8 +1177,6 @@ Erase all Gong data (calls, leads, contacts) referencing a phone number. Asynchr | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `accessKey` | string | Yes | Gong API Access Key | -| `accessKeySecret` | string | Yes | Gong API Access Key Secret | | `phoneNumber` | string | Yes | Phone number whose associated data should be permanently erased from Gong. Must include a leading "+" and country code \(e.g., +14255552671\) | #### Output diff --git a/apps/docs/content/docs/integrations/google-service-account.mdx b/apps/docs/content/docs/integrations/google-service-account.mdx index 5a2e8d86514..4fcb62b7ba2 100644 --- a/apps/docs/content/docs/integrations/google-service-account.mdx +++ b/apps/docs/content/docs/integrations/google-service-account.mdx @@ -180,7 +180,7 @@ A credential can serve multiple supported Google services when their APIs and sc A Google service account credential can also be **keyless**: Sim stores no key. Instead, your Google Cloud project trusts Sim through [Workload Identity Federation](https://cloud.google.com/iam/docs/workload-identity-federation-with-other-providers). On each run, Sim signs a short-lived token for this credential's own subject, exchanges it at Google's Security Token Service for a federated token, and uses that to impersonate your service account. The service account's IAM roles decide what workflows can do, exactly as with a JSON key. -Keyless credentials work with Google Cloud APIs that the service account calls as itself: **BigQuery**, and **Vertex AI** wherever a Google service account credential is accepted. They can't use domain-wide delegation, so Gmail, Drive, Sheets, Calendar, and other Google Workspace blocks don't offer them, and a keyless credential refuses an **Impersonated Account**. Use a JSON key for those. +Keyless credentials work with Google Cloud APIs that the service account calls as itself: **BigQuery**, and **Vertex AI** in every block that runs a Vertex model (pick the credential in its **Google Cloud Account** field). They can't use domain-wide delegation, so Gmail, Drive, Sheets, Calendar, and other Google Workspace blocks don't offer them, and a keyless credential refuses an **Impersonated Account**. Use a JSON key for those. diff --git a/apps/docs/content/docs/integrations/hubspot-service-account.mdx b/apps/docs/content/docs/integrations/hubspot-service-account.mdx index a142b0a62bb..9c4e66301d6 100644 --- a/apps/docs/content/docs/integrations/hubspot-service-account.mdx +++ b/apps/docs/content/docs/integrations/hubspot-service-account.mdx @@ -1,22 +1,43 @@ --- -title: HubSpot private app tokens -description: Set up a HubSpot private app with a scoped access token to use HubSpot in Sim workflows +title: HubSpot service keys and private app tokens +description: Connect HubSpot to Sim with a scoped service key or an existing private app access token --- import { Callout } from '@/components/ui/callout' import { Step, Steps } from '@/components/ui/steps' -Connect HubSpot with a private app access token created by a super admin. Select the CRM scopes your workflows need and keep the creating admin's account permissions in mind when managing the app. +Connect HubSpot with a service key, or with the access token of an existing legacy private app. Both are scoped Bearer credentials for your HubSpot account: select the CRM scopes your workflows need when you create one. ## Legacy app availability -HubSpot [ended legacy private-app creation](https://developers.hubspot.com/changelog/legacy-private-app-creation-sunset) on September 28, 2026 for accounts created on or after that date. Creation ends on October 26, 2026 for older accounts. Existing legacy private apps continue to work; the creation steps below apply only while the option remains available in your account. +HubSpot [ended legacy private-app creation](https://developers.hubspot.com/changelog/legacy-private-app-creation-sunset) on September 28, 2026 for accounts created on or after that date, and on October 26, 2026 for older accounts. Existing legacy private apps continue to work, so the private app steps below are for finding and managing an app you already have. -For a new Sim connection when that option is unavailable, use [HubSpot OAuth](/integrations/hubspot-setup). HubSpot recommends Service Keys for new direct integrations, but this guide does not establish that those keys are compatible with Sim’s private-app-token connection. +For a new connection, create a [service key](https://developers.hubspot.com/docs/apps/developer-platform/build-apps/authentication/account-service-keys), which HubSpot recommends for new direct integrations, or use [HubSpot OAuth](/integrations/hubspot-setup). Sim calls HubSpot's REST APIs with a service key as a Bearer token, exactly as it does with a private app token. -## Prerequisites +## Create a service key [#creating-a-service-key] + +Super admins and users with the **Developer tools access** permission can create service keys. + + + + In HubSpot, navigate to **Development**, then **Keys** → **Service keys**. In some accounts the page is under **Settings** → **Integrations** → **Service keys** instead. + + + Click **Create service key** and give it a name (e.g. `sim-hubspot`) + + + Click **Add new scope** and select the scopes your workflows need (see [Choose scopes](#2-choose-scopes)), then click **Create** and confirm + + + Open the key, click **Show**, then **Copy**. Continue at [Add the key to Sim](#adding-the-private-app-token-to-sim) + + + +A service key works only for REST API calls. If you turn on automatic rotation, HubSpot replaces the key every 3, 6, or 12 months and emails admins before the old key expires; paste the new key into the credential in Sim each time. + +## Prerequisites for a private app [#prerequisites] You need a HubSpot **super admin** to create the private app. Private apps can only be created and managed by super admins in the portal. @@ -47,7 +68,7 @@ The token is tied to the super admin who created the private app. If that user i ### 2. Choose scopes [#2-choose-scopes] -Private apps use the same scope catalog as HubSpot OAuth apps. Grant only what your workflows use. The full set Sim's HubSpot tools can draw on is: +Service keys and private apps use the same scope catalog as HubSpot OAuth apps. Grant only what your workflows use. The full set Sim's HubSpot tools can draw on is: **CRM objects (read/write as needed):** ``` @@ -76,7 +97,7 @@ crm.lists.read crm.lists.write ``` -Ticket tools need `tickets`, email tools need `sales-email-read`, list tools need `crm.lists.*`, and each object type's tools need its `crm.objects.*` scope. Notes and association tools need the CRM object scopes of the records involved. A missing scope surfaces at run time as a `403` error with category `MISSING_SCOPES` naming the scopes required — widen the app's scopes from the same Scopes tab, commit the change in HubSpot, and re-run the workflow. If the `403` persists, rotate the token and update the credential in Sim. +Ticket tools need `tickets`, email tools need `sales-email-read`, list tools need `crm.lists.*`, and each object type's tools need its `crm.objects.*` scope. Notes and association tools need the CRM object scopes of the records involved. A missing scope surfaces at run time as a `403` error with category `MISSING_SCOPES` naming the scopes required — widen the key's or app's scopes in HubSpot, save the change, and re-run the workflow. If the `403` persists, rotate the token and update the credential in Sim. Sim's OAuth flow also requests an `oauth` scope — that one is OAuth-app-only and doesn't exist for private apps. You don't need it and can't grant it. @@ -88,37 +109,37 @@ The token looks like `pat-na1-…` (North America) or `pat-eu1-…` (EU data res The access token is bearer credentials for your entire portal, limited only by its scopes. Treat it like a password — do not commit it to source control or share it publicly. Sim encrypts the token at rest. -## Add the private app token to Sim [#adding-the-private-app-token-to-sim] +## Add the key to Sim [#adding-the-private-app-token-to-sim] Open **Integrations** from your workspace sidebar - Search for "HubSpot" and open it, then click **Add to Sim** and choose **Add private app token** + Search for "HubSpot" and open it, then click **Add to Sim** and choose **Add service key** - Paste the **Private app access token**, and optionally set a display name and description + Paste the service key or private app token into **Service key or private app token**, and optionally set a display name and description - Click **Add private app token**. Sim checks HubSpot’s token-info endpoint and, for ambiguous responses, its Account Information API — if it fails, you'll see a specific error explaining what went wrong. + Click **Add service key**. Sim checks HubSpot’s token-info endpoint and, when that endpoint doesn't recognize the key, its Account Information API — if it fails, you'll see a specific error explaining what went wrong. ## Use the credential in workflows [#using-the-credential-in-workflows] -Add a HubSpot block or the HubSpot CRM Trigger to your workflow. In the credential dropdown, select the saved HubSpot private app token. Configure the operation and its required fields. Private app tokens also work with the polling trigger. If the creating admin leaves or loses permissions, follow the account-management guidance above. +Add a HubSpot block or the HubSpot CRM Trigger to your workflow. In the credential dropdown, select the saved HubSpot credential. Configure the operation and its required fields. Service keys and private app tokens also work with the polling trigger. If the creating admin leaves or loses permissions, follow the account-management guidance above. -The block calls `api.hubapi.com` using the private app token — the same API requests used by the OAuth connection. Availability depends on the app’s scopes and account access. +The block calls `api.hubapi.com` using the key — the same API requests used by the OAuth connection. Availability depends on the key's scopes and account access. ## Rotate the token [#rotating-the-token] -Private app tokens don't auto-expire, but HubSpot recommends rotating them every six months and emails super admins once a token hasn't rotated in about 180 days. From the app's Auth tab you can: +Service keys and private app tokens don't expire unless rotated, but HubSpot recommends rotating them every six months. From the service key's page, or the private app's Auth tab, you can: -- **Rotate and expire now** — the old token dies immediately -- **Rotate and expire later** — the old token keeps working for 7 days while you migrate +- **Rotate and expire now**: the old key stops working immediately +- **Rotate and expire later**: the old key keeps working for 7 days while you migrate -Prefer the 7-day option: rotate in HubSpot, paste the new token into the credential in Sim, confirm your workflows run, and let the old token lapse. +Prefer the 7-day option: rotate in HubSpot, paste the new key into the credential in Sim, confirm your workflows run, and let the old key lapse. ## Handle permission failures diff --git a/apps/docs/content/docs/integrations/hubspot-setup.mdx b/apps/docs/content/docs/integrations/hubspot-setup.mdx index 0277b318055..ae371682814 100644 --- a/apps/docs/content/docs/integrations/hubspot-setup.mdx +++ b/apps/docs/content/docs/integrations/hubspot-setup.mdx @@ -24,7 +24,7 @@ You need: 1. Log in to Sim, open your workspace, and click **Integrations** in the sidebar. 2. Search for **HubSpot** and open it. -3. Click **Add to Sim** and choose **Connect with OAuth**. For a private app token, follow the [Private App Tokens guide](/integrations/hubspot-service-account). +3. Click **Add to Sim** and choose **Connect with OAuth**. For a service key or an existing private app token, follow the [service keys and private app tokens guide](/integrations/hubspot-service-account). + + In Linear, create or edit the OAuth app under **Settings** → **API** → **OAuth applications** and turn on **Client credentials tokens** + + + In Sim, add the Linear credential, set **Authentication** to **OAuth app (client credentials)**, and paste the app's client ID and client secret + + + Save. Sim requests a `read,write` token from Linear and checks it with a `viewer` and `organization` query before saving. Credentials whose token has no viewer are named after the workspace + + + + +Use a dedicated OAuth app for Sim. Requesting a client credentials token **replaces an existing authorization-code installation of the same app** in that workspace; revoke that authorization first if you rely on it. A client credentials request for the app with **different scopes** replaces its existing tokens, so another service using the same app revokes Sim's tokens and Sim's requests (always `read,write`) revoke theirs. Rotating the client secret immediately invalidates every client credentials token, so reconnect the Sim credential with the new secret right away. Tokens last 30 days and cannot be refreshed; Sim shares one token across all of its credentials for the app, replaces it daily, and requests a new one as soon as Linear rejects it. + + ## Use the credential in workflows [#using-the-credential-in-workflows] Add a Linear block to your workflow. In the credential dropdown, select the saved Linear API key. Configure the operation and its required fields. diff --git a/apps/docs/content/docs/integrations/microsoft-app-credentials.mdx b/apps/docs/content/docs/integrations/microsoft-app-credentials.mdx index 4e91652bc1d..40895257e5d 100644 --- a/apps/docs/content/docs/integrations/microsoft-app-credentials.mdx +++ b/apps/docs/content/docs/integrations/microsoft-app-credentials.mdx @@ -1,12 +1,12 @@ --- title: Microsoft app credentials -description: Run Microsoft Entra ID, SharePoint, Dataverse, and Dynamics 365 blocks as an app registration with a client secret, a certificate, or a keyless federated credential that trusts Sim +description: Run Microsoft Entra ID, SharePoint, Intune, Dataverse, and Dynamics 365 blocks as an app registration with a client secret, a certificate, or a keyless federated credential that trusts Sim --- import { Callout } from '@/components/ui/callout' import { Step, Steps } from '@/components/ui/steps' -A Microsoft app credential signs in as a Microsoft Entra app registration instead of a person (the OAuth 2.0 client credentials flow). One credential works for the **Azure AD** (Microsoft Entra ID), **SharePoint**, **Microsoft Dataverse**, and **Dynamics 365** blocks. Connecting a Microsoft account through OAuth keeps working exactly as before; the app credential is an additional option in the same account picker. +A Microsoft app credential signs in as a Microsoft Entra app registration instead of a person (the OAuth 2.0 client credentials flow). One credential works for the **Azure AD** (Microsoft Entra ID), **SharePoint**, **Microsoft Intune**, **Microsoft Dataverse**, and **Dynamics 365** blocks. Connecting a Microsoft account through OAuth keeps working exactly as before; the app credential is an additional option in the same account picker. Outlook, OneDrive, Excel, Word, Planner, and Teams act on behalf of a signed-in person and don't accept an app credential. The Azure AD **Reset password** operation also needs a signed-in admin, because Microsoft Graph doesn't support application permissions for it. @@ -20,7 +20,7 @@ Outlook, OneDrive, Excel, Word, Planner, and Teams act on behalf of a signed-in | **Client secret** | The tenant ID, client ID, and client secret | Quick setup. Secrets expire, so plan to rotate them. | | **Certificate** | The tenant ID, client ID, the certificate, and its private key | When your organization requires certificate credentials. Sim signs each token request with the key. | -Every option resolves on Sim's servers at run time, and tokens never appear in block outputs, logs, or anything a model sees. Azure AD and SharePoint blocks get a Microsoft Graph token; Dataverse and Dynamics 365 blocks get a token for the credential's own environment. +Every option resolves on Sim's servers at run time, and tokens never appear in block outputs, logs, or anything a model sees. Azure AD, SharePoint, and Intune blocks get a Microsoft Graph token; Dataverse and Dynamics 365 blocks get a token for the credential's own environment. ## Register the app and grant permissions @@ -32,6 +32,7 @@ Every option resolves on Sim's servers at run time, and tokens never appear in b Under **API permissions** → **Add a permission** → **Microsoft Graph** → **Application permissions**, add what your workflows need, then select **Grant admin consent**. Application permissions always need admin consent. For example: - Azure AD: `User.Read.All` or `User.ReadWrite.All`, `Group.ReadWrite.All`, `GroupMember.ReadWrite.All`, `AuditLog.Read.All` for sign-in and audit logs, `Policy.Read.All` for conditional access, `UserAuthenticationMethod.Read.All` for authentication methods, `User.RevokeSessions.All` to revoke sessions. Setting a password needs `User-PasswordProfile.ReadWrite.All` **and** the app assigned at least the User Administrator role. - SharePoint: `Sites.Read.All` or `Sites.ReadWrite.All`. Creating and editing pages needs `Sites.ReadWrite.All`. `Sites.Selected` limits the app to sites you grant it, but searching for sites doesn't work with it, so enter site IDs directly. + - Intune: `DeviceManagementManagedDevices.Read.All` for devices and detected apps, `DeviceManagementConfiguration.Read.All` for compliance policies, their device statuses, and device configurations, and `DeviceManagementManagedDevices.PrivilegedOperations.All` to sync, restart, remotely lock, or retire a device. The tenant needs an active Intune license. Dataverse and Dynamics 365 don't use API permissions. Instead, in the [Power Platform admin center](https://admin.powerplatform.microsoft.com), open the environment → **Settings** → **Users + permissions** → **Application users** → **New app user**, add this app, pick a business unit, and assign a security role. Then enter the environment URL (like `https://contoso.crm.dynamics.com`) on the credential. diff --git a/apps/docs/content/docs/integrations/planetscale.mdx b/apps/docs/content/docs/integrations/planetscale.mdx index f6589c52722..6476bdd4f59 100644 --- a/apps/docs/content/docs/integrations/planetscale.mdx +++ b/apps/docs/content/docs/integrations/planetscale.mdx @@ -2,7 +2,7 @@ title: PlanetScale description: Manage PlanetScale databases, branches, backups, and deploy requests integration: - type: "planetscale" + type: "planetscale_v2" color: "#111111" --- @@ -12,7 +12,7 @@ import { ReferenceDetails } from "@/components/ui/reference-details" {/* MANUAL-CONTENT-START:intro */} Use [PlanetScale](https://planetscale.com/) to inspect databases and branches, create or delete branches, restore backups into a new branch, and create or inspect backups. Vitess deploy-request actions create, review, approve, queue, or close schema-change requests. -Create an organization service token with access to the databases and actions your workflow needs. Enter the service token ID, token, and organization slug in the block. Deploy requests are specific to Vitess; use the MySQL or PostgreSQL block to execute SQL. +Connect PlanetScale once as a PlanetScale credential: create a service token on your organization's **Service tokens** page, grant it access to the databases and actions your workflows need, then add its ID and token. The organization slug stays in the block. Workflows built with the earlier PlanetScale block keep the service token ID and token they entered in the block. Deploy requests are specific to Vitess; use the MySQL or PostgreSQL block to execute SQL. {/* MANUAL-CONTENT-END */} ## Use the block [#usage-instructions] @@ -31,8 +31,6 @@ List databases | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `serviceTokenId` | string | Yes | PlanetScale service token ID | -| `serviceToken` | string | Yes | PlanetScale service token secret | | `organization` | string | Yes | PlanetScale organization slug | | `q` | string | No | Search term to filter databases by name | | `page` | number | No | If provided, specifies the page offset of returned results | @@ -74,8 +72,6 @@ Get a database | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `serviceTokenId` | string | Yes | PlanetScale service token ID | -| `serviceToken` | string | Yes | PlanetScale service token secret | | `organization` | string | Yes | PlanetScale organization slug | | `database` | string | Yes | Database name slug from `list_databases`. Example: `app-db`. | @@ -109,8 +105,6 @@ List branches | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `serviceTokenId` | string | Yes | PlanetScale service token ID | -| `serviceToken` | string | Yes | PlanetScale service token secret | | `organization` | string | Yes | PlanetScale organization slug | | `database` | string | Yes | Database name slug from `list_databases`. Example: `app-db`. | | `q` | string | No | Search branches by name | @@ -156,8 +150,6 @@ Get a branch | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `serviceTokenId` | string | Yes | PlanetScale service token ID | -| `serviceToken` | string | Yes | PlanetScale service token secret | | `organization` | string | Yes | PlanetScale organization slug | | `database` | string | Yes | Database name slug from `list_databases`. Example: `app-db`. | | `branch` | string | Yes | Branch name from `list_branches`. Example: `main`. | @@ -192,8 +184,6 @@ Create a branch | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `serviceTokenId` | string | Yes | PlanetScale service token ID | -| `serviceToken` | string | Yes | PlanetScale service token secret | | `organization` | string | Yes | PlanetScale organization slug | | `database` | string | Yes | The name of the database the branch belongs to | | `name` | string | Yes | The name of the branch to create | @@ -237,8 +227,6 @@ Delete a branch | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `serviceTokenId` | string | Yes | PlanetScale service token ID | -| `serviceToken` | string | Yes | PlanetScale service token secret | | `organization` | string | Yes | PlanetScale organization slug | | `database` | string | Yes | The name of the database the branch belongs to | | `branch` | string | Yes | The name of the branch | @@ -262,8 +250,6 @@ List backups | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `serviceTokenId` | string | Yes | PlanetScale service token ID | -| `serviceToken` | string | Yes | PlanetScale service token secret | | `organization` | string | Yes | PlanetScale organization slug | | `database` | string | Yes | The name of the database the branch belongs to | | `branch` | string | Yes | The name of the branch | @@ -312,8 +298,6 @@ Get a backup | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `serviceTokenId` | string | Yes | PlanetScale service token ID | -| `serviceToken` | string | Yes | PlanetScale service token secret | | `organization` | string | Yes | PlanetScale organization slug | | `backupId` | string | Yes | The ID for the backup | | `database` | string | Yes | The name of the database the branch belongs to | @@ -348,8 +332,6 @@ Create a backup | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `serviceTokenId` | string | Yes | PlanetScale service token ID | -| `serviceToken` | string | Yes | PlanetScale service token secret | | `organization` | string | Yes | PlanetScale organization slug | | `database` | string | Yes | The name of the database the branch belongs to | | `branch` | string | Yes | The name of the branch | @@ -387,8 +369,6 @@ List deploy requests (Vitess only) | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `serviceTokenId` | string | Yes | PlanetScale service token ID | -| `serviceToken` | string | Yes | PlanetScale service token secret | | `organization` | string | Yes | PlanetScale organization slug | | `database` | string | Yes | The name of the deploy request's database | | `state` | string | No | Filter by state of the deploy request \(open, closed, deployed\) | @@ -437,8 +417,6 @@ Get a deploy request (Vitess only) | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `serviceTokenId` | string | Yes | PlanetScale service token ID | -| `serviceToken` | string | Yes | PlanetScale service token secret | | `organization` | string | Yes | PlanetScale organization slug | | `database` | string | Yes | The name of the deploy request's database | | `deployRequestNumber` | number | Yes | The number of the deploy request | @@ -475,8 +453,6 @@ Create a deploy request (Vitess only) | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `serviceTokenId` | string | Yes | PlanetScale service token ID | -| `serviceToken` | string | Yes | PlanetScale service token secret | | `organization` | string | Yes | PlanetScale organization slug | | `database` | string | Yes | The name of the deploy request's database | | `branch` | string | Yes | The name of the branch the deploy request is created from | @@ -517,8 +493,6 @@ Review a deploy request (Vitess only) | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `serviceTokenId` | string | Yes | PlanetScale service token ID | -| `serviceToken` | string | Yes | PlanetScale service token secret | | `organization` | string | Yes | PlanetScale organization slug | | `database` | string | Yes | The name of the database the deploy request belongs to | | `deployRequestNumber` | number | Yes | The number of the deploy request | @@ -548,8 +522,6 @@ Queue a deploy request (Vitess only) | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `serviceTokenId` | string | Yes | PlanetScale service token ID | -| `serviceToken` | string | Yes | PlanetScale service token secret | | `organization` | string | Yes | PlanetScale organization slug | | `database` | string | Yes | The name of the deploy request's database | | `deployRequestNumber` | number | Yes | The number of the deploy request | @@ -587,8 +559,6 @@ Close a deploy request (Vitess only) | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `serviceTokenId` | string | Yes | PlanetScale service token ID | -| `serviceToken` | string | Yes | PlanetScale service token secret | | `organization` | string | Yes | PlanetScale organization slug | | `database` | string | Yes | The name of the deploy request's database | | `deployRequestNumber` | number | Yes | The number of the deploy request | diff --git a/apps/docs/content/docs/integrations/sailpoint.mdx b/apps/docs/content/docs/integrations/sailpoint.mdx index fdafe235639..a98b62309b8 100644 --- a/apps/docs/content/docs/integrations/sailpoint.mdx +++ b/apps/docs/content/docs/integrations/sailpoint.mdx @@ -2,7 +2,7 @@ title: SailPoint description: Govern identities and access in SailPoint Identity Security Cloud integration: - type: "sailpoint" + type: "sailpoint_v2" color: "#FFFFFF" --- @@ -10,7 +10,9 @@ import { ReferenceDetails } from "@/components/ui/reference-details" {/* MANUAL-CONTENT-START:intro */} -The SailPoint integration connects Sim workflows to SailPoint Identity Security Cloud (ISC) with a Personal Access Token (PAT). Enter the tenant name from your ISC URL—or the full `*.api.identitynow.com` / `*.api.identitynowgov.com` host—plus the PAT client ID and client secret. Sim exchanges those credentials at the tenant's `/oauth/token` endpoint and calls SailPoint's current service-versioned endpoints, such as `/identities/v1`, `/access-requests/v1`, and `/certifications/v1`. There is no global API-version setting. +The SailPoint integration connects Sim workflows to SailPoint Identity Security Cloud (ISC) with a Personal Access Token (PAT) or an API client that has the client credentials grant. Connect it once as a SailPoint credential: enter the tenant name from your ISC URL (or its `*.api.identitynow.com` host) plus the client ID and client secret. Sim checks the credential by exchanging it at the tenant's `/oauth/token` endpoint when you save it, then calls SailPoint's current service-versioned endpoints, such as `/identities/v1`, `/access-requests/v1`, and `/certifications/v1`. There is no global API-version setting. + +Government tenants enter their `*.api.identitynowgov.com` host. Workflows built with the earlier SailPoint block keep the tenant, client ID, and client secret they entered in the block. ## Permissions @@ -37,7 +39,7 @@ The 40 actions cover six connected workflows: search and entity lookup; account, ## Use the block [#usage-instructions] -Connect SailPoint Identity Security Cloud with a Personal Access Token to search identity-governance data, manage access requests and approvals, review certifications, and import source data. The token owner and scopes determine the operations available to the integration. +Connect SailPoint Identity Security Cloud with a Personal Access Token or a client-credentials API client to search identity-governance data, manage access requests and approvals, review certifications, and import source data. The token owner and scopes determine the operations available to the integration. ## Actions @@ -51,9 +53,6 @@ Approve one pending access-request approval. | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `clientId` | string | Yes | SailPoint Personal Access Token client ID | -| `clientSecret` | string | Yes | SailPoint Personal Access Token client secret | -| `tenant` | string | Yes | SailPoint tenant name or full *.api.identitynow.com / *.api.identitynowgov.com host | | `approvalId` | string | Yes | Approval ID | | `comment` | string | No | Optional reviewer comment | @@ -76,9 +75,6 @@ Cancel an access request that has not passed approval. | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `clientId` | string | Yes | SailPoint Personal Access Token client ID | -| `clientSecret` | string | Yes | SailPoint Personal Access Token client secret | -| `tenant` | string | Yes | SailPoint tenant name or full *.api.identitynow.com / *.api.identitynowgov.com host | | `accountActivityId` | string | Yes | Account activity / identity request ID | | `comment` | string | Yes | Cancellation reason | @@ -101,9 +97,6 @@ Approve or revoke 1-250 review items in an identity certification. | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `clientId` | string | Yes | SailPoint Personal Access Token client ID | -| `clientSecret` | string | Yes | SailPoint Personal Access Token client secret | -| `tenant` | string | Yes | SailPoint tenant name or full *.api.identitynow.com / *.api.identitynowgov.com host | | `id` | string | Yes | Certification ID | | `decisions` | array | Yes | Array of \{id, decision: APPROVE\|REVOKE, bulk, proposedEndDate?, recommendation?, comments?\} | @@ -142,9 +135,6 @@ Get an access profile by ID. | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `clientId` | string | Yes | SailPoint Personal Access Token client ID | -| `clientSecret` | string | Yes | SailPoint Personal Access Token client secret | -| `tenant` | string | Yes | SailPoint tenant name or full *.api.identitynow.com / *.api.identitynowgov.com host | | `id` | string | Yes | Access profile ID | #### Output @@ -181,9 +171,6 @@ List entitlements in one access profile. | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `clientId` | string | Yes | SailPoint Personal Access Token client ID | -| `clientSecret` | string | Yes | SailPoint Personal Access Token client secret | -| `tenant` | string | Yes | SailPoint tenant name or full *.api.identitynow.com / *.api.identitynowgov.com host | | `id` | string | Yes | Access profile ID | | `filters` | string | No | SailPoint standard collection filter expression for this operation | | `sorters` | string | No | Comma-separated supported sort fields, prefixed with - for descending order | @@ -253,9 +240,6 @@ Get tenant access-request, request-on-behalf-of, and machine-identity configurat | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `clientId` | string | Yes | SailPoint Personal Access Token client ID | -| `clientSecret` | string | Yes | SailPoint Personal Access Token client secret | -| `tenant` | string | Yes | SailPoint tenant name or full *.api.identitynow.com / *.api.identitynowgov.com host | #### Output @@ -301,9 +285,6 @@ List requested-item status records for access requests. | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `clientId` | string | Yes | SailPoint Personal Access Token client ID | -| `clientSecret` | string | Yes | SailPoint Personal Access Token client secret | -| `tenant` | string | Yes | SailPoint tenant name or full *.api.identitynow.com / *.api.identitynowgov.com host | | `requestedFor` | string | No | Identity ID for whom the access was requested | | `requestedBy` | string | No | Identity ID that submitted the access request | | `regardingIdentity` | string | No | Identity ID that is either the requester or the request target | @@ -366,9 +347,6 @@ Get an account from the current /accounts/v1 service by ID. | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `clientId` | string | Yes | SailPoint Personal Access Token client ID | -| `clientSecret` | string | Yes | SailPoint Personal Access Token client secret | -| `tenant` | string | Yes | SailPoint tenant name or full *.api.identitynow.com / *.api.identitynowgov.com host | | `id` | string | Yes | Account ID | #### Output @@ -417,9 +395,6 @@ Get an account activity by ID. | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `clientId` | string | Yes | SailPoint Personal Access Token client ID | -| `clientSecret` | string | Yes | SailPoint Personal Access Token client secret | -| `tenant` | string | Yes | SailPoint tenant name or full *.api.identitynow.com / *.api.identitynowgov.com host | | `id` | string | Yes | Account activity ID | #### Output @@ -454,9 +429,6 @@ List entitlements granted to one account. | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `clientId` | string | Yes | SailPoint Personal Access Token client ID | -| `clientSecret` | string | Yes | SailPoint Personal Access Token client secret | -| `tenant` | string | Yes | SailPoint tenant name or full *.api.identitynow.com / *.api.identitynowgov.com host | | `id` | string | Yes | Account ID | | `limit` | number | No | Maximum records for this page \(0-250; default 250\) | | `offset` | number | No | Zero-based record offset \(default 0\) | @@ -524,9 +496,6 @@ Resolve eligible source accounts before submitting a machine or multi-account ac | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `clientId` | string | Yes | SailPoint Personal Access Token client ID | -| `clientSecret` | string | Yes | SailPoint Personal Access Token client secret | -| `tenant` | string | Yes | SailPoint tenant name or full *.api.identitynow.com / *.api.identitynowgov.com host | | `requestType` | string | No | GRANT_ACCESS \(default\), REVOKE_ACCESS, or MODIFY_ACCESS | | `requestedFor` | array | No | Human identity IDs for the flat request shape | | `requestedItems` | array | No | Flat human request items | @@ -573,9 +542,6 @@ Get a certification campaign by ID. | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `clientId` | string | Yes | SailPoint Personal Access Token client ID | -| `clientSecret` | string | Yes | SailPoint Personal Access Token client secret | -| `tenant` | string | Yes | SailPoint tenant name or full *.api.identitynow.com / *.api.identitynowgov.com host | | `id` | string | Yes | Campaign ID | | `detail` | string | No | SLIM or FULL | @@ -620,9 +586,6 @@ Get an identity certification by ID. | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `clientId` | string | Yes | SailPoint Personal Access Token client ID | -| `clientSecret` | string | Yes | SailPoint Personal Access Token client secret | -| `tenant` | string | Yes | SailPoint tenant name or full *.api.identitynow.com / *.api.identitynowgov.com host | | `id` | string | Yes | Certification ID | #### Output @@ -660,9 +623,6 @@ Get an entitlement by ID. | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `clientId` | string | Yes | SailPoint Personal Access Token client ID | -| `clientSecret` | string | Yes | SailPoint Personal Access Token client secret | -| `tenant` | string | Yes | SailPoint tenant name or full *.api.identitynow.com / *.api.identitynowgov.com host | | `id` | string | Yes | Entitlement ID | #### Output @@ -727,9 +687,6 @@ Get grant, revocation, duration, approval, and form settings for an entitlement. | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `clientId` | string | Yes | SailPoint Personal Access Token client ID | -| `clientSecret` | string | Yes | SailPoint Personal Access Token client secret | -| `tenant` | string | Yes | SailPoint tenant name or full *.api.identitynow.com / *.api.identitynowgov.com host | | `id` | string | Yes | Entitlement ID | #### Output @@ -766,9 +723,6 @@ Get an identity from the current /identities/v1 service by ID. | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `clientId` | string | Yes | SailPoint Personal Access Token client ID | -| `clientSecret` | string | Yes | SailPoint Personal Access Token client secret | -| `tenant` | string | Yes | SailPoint tenant name or full *.api.identitynow.com / *.api.identitynowgov.com host | | `id` | string | Yes | Identity ID | #### Output @@ -802,9 +756,6 @@ Get a role by ID. | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `clientId` | string | Yes | SailPoint Personal Access Token client ID | -| `clientSecret` | string | Yes | SailPoint Personal Access Token client secret | -| `tenant` | string | Yes | SailPoint tenant name or full *.api.identitynow.com / *.api.identitynowgov.com host | | `id` | string | Yes | Role ID | #### Output @@ -845,9 +796,6 @@ List entitlements in one role using the current non-experimental roles service. | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `clientId` | string | Yes | SailPoint Personal Access Token client ID | -| `clientSecret` | string | Yes | SailPoint Personal Access Token client secret | -| `tenant` | string | Yes | SailPoint tenant name or full *.api.identitynow.com / *.api.identitynowgov.com host | | `id` | string | Yes | Role ID | | `filters` | string | No | SailPoint standard collection filter expression for this operation | | `sorters` | string | No | Comma-separated supported sort fields, prefixed with - for descending order | @@ -917,9 +865,6 @@ Get an identity source by ID. | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `clientId` | string | Yes | SailPoint Personal Access Token client ID | -| `clientSecret` | string | Yes | SailPoint Personal Access Token client secret | -| `tenant` | string | Yes | SailPoint tenant name or full *.api.identitynow.com / *.api.identitynowgov.com host | | `id` | string | Yes | Source ID | #### Output @@ -971,9 +916,6 @@ Get the current status of a SailPoint background task by ID. | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `clientId` | string | Yes | SailPoint Personal Access Token client ID | -| `clientSecret` | string | Yes | SailPoint Personal Access Token client secret | -| `tenant` | string | Yes | SailPoint tenant name or full *.api.identitynow.com / *.api.identitynowgov.com host | | `id` | string | Yes | Task ID | #### Output @@ -1029,9 +971,6 @@ List access profiles with current visibility and segmentation controls. | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `clientId` | string | Yes | SailPoint Personal Access Token client ID | -| `clientSecret` | string | Yes | SailPoint Personal Access Token client secret | -| `tenant` | string | Yes | SailPoint tenant name or full *.api.identitynow.com / *.api.identitynowgov.com host | | `forSubadmin` | string | No | Subadmin identity ID or 'me' whose visible resources should be returned | | `forSegmentIds` | string | No | Comma-separated segment IDs used to restrict the returned resources | | `includeUnsegmented` | boolean | No | Include resources not assigned to a segment \(default true\) | @@ -1077,9 +1016,6 @@ List provisioning activities with identity, filter, sort, and page controls. | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `clientId` | string | Yes | SailPoint Personal Access Token client ID | -| `clientSecret` | string | Yes | SailPoint Personal Access Token client secret | -| `tenant` | string | Yes | SailPoint tenant name or full *.api.identitynow.com / *.api.identitynowgov.com host | | `requestedFor` | string | No | Target identity ID or 'me'; mutually exclusive with regardingIdentity | | `requestedBy` | string | No | Requester identity ID or 'me'; mutually exclusive with regardingIdentity | | `regardingIdentity` | string | No | Requester-or-target identity ID or 'me'; excludes requestedFor/requestedBy | @@ -1123,9 +1059,6 @@ List accounts with documented filtering, sorting, detail, and pagination. | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `clientId` | string | Yes | SailPoint Personal Access Token client ID | -| `clientSecret` | string | Yes | SailPoint Personal Access Token client secret | -| `tenant` | string | Yes | SailPoint tenant name or full *.api.identitynow.com / *.api.identitynowgov.com host | | `filters` | string | No | SailPoint standard collection filter expression for this operation | | `sorters` | string | No | Comma-separated supported sort fields, prefixed with - for descending order | | `detailLevel` | string | No | SLIM or FULL \(default FULL\) | @@ -1181,9 +1114,6 @@ List certification campaigns with detail, filtering, sorting, and pagination. | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `clientId` | string | Yes | SailPoint Personal Access Token client ID | -| `clientSecret` | string | Yes | SailPoint Personal Access Token client secret | -| `tenant` | string | Yes | SailPoint tenant name or full *.api.identitynow.com / *.api.identitynowgov.com host | | `detail` | string | No | SLIM \(default\) or FULL | | `filters` | string | No | SailPoint standard collection filter expression for this operation | | `sorters` | string | No | Comma-separated supported sort fields, prefixed with - for descending order | @@ -1234,9 +1164,6 @@ List access-review items in one identity certification. | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `clientId` | string | Yes | SailPoint Personal Access Token client ID | -| `clientSecret` | string | Yes | SailPoint Personal Access Token client secret | -| `tenant` | string | Yes | SailPoint tenant name or full *.api.identitynow.com / *.api.identitynowgov.com host | | `id` | string | Yes | Certification ID | | `filters` | string | No | SailPoint standard collection filter expression for this operation | | `sorters` | string | No | Comma-separated supported sort fields, prefixed with - for descending order | @@ -1274,9 +1201,6 @@ List identity certifications assigned to a reviewer. | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `clientId` | string | Yes | SailPoint Personal Access Token client ID | -| `clientSecret` | string | Yes | SailPoint Personal Access Token client secret | -| `tenant` | string | Yes | SailPoint tenant name or full *.api.identitynow.com / *.api.identitynowgov.com host | | `reviewerIdentity` | string | No | Reviewer identity ID or 'me' | | `filters` | string | No | SailPoint standard collection filter expression for this operation | | `sorters` | string | No | Comma-separated supported sort fields, prefixed with - for descending order | @@ -1321,9 +1245,6 @@ List entitlements with current segmentation, cursor, filter, and page controls. | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `clientId` | string | Yes | SailPoint Personal Access Token client ID | -| `clientSecret` | string | Yes | SailPoint Personal Access Token client secret | -| `tenant` | string | Yes | SailPoint tenant name or full *.api.identitynow.com / *.api.identitynowgov.com host | | `segmentedForIdentity` | string | No | Identity ID whose visible segments restrict the results | | `forSegmentIds` | string | No | Comma-separated segment IDs used to restrict the returned resources | | `includeUnsegmented` | boolean | No | Include resources not assigned to a segment \(default true\) | @@ -1398,9 +1319,6 @@ List identities with documented filtering, sorting, and pagination. | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `clientId` | string | Yes | SailPoint Personal Access Token client ID | -| `clientSecret` | string | Yes | SailPoint Personal Access Token client secret | -| `tenant` | string | Yes | SailPoint tenant name or full *.api.identitynow.com / *.api.identitynowgov.com host | | `filters` | string | No | SailPoint standard collection filter expression for this operation | | `sorters` | string | No | Comma-separated supported sort fields, prefixed with - for descending order | | `defaultFilter` | string | No | CORRELATED_ONLY \(default\) or NONE | @@ -1441,9 +1359,6 @@ List tagged entitlement references held by one identity. | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `clientId` | string | Yes | SailPoint Personal Access Token client ID | -| `clientSecret` | string | Yes | SailPoint Personal Access Token client secret | -| `tenant` | string | Yes | SailPoint tenant name or full *.api.identitynow.com / *.api.identitynowgov.com host | | `id` | string | Yes | Identity ID | | `limit` | number | No | Maximum records for this page \(0-250; default 250\) | | `offset` | number | No | Zero-based record offset \(default 0\) | @@ -1471,9 +1386,6 @@ List pending access-request approvals visible to the caller. | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `clientId` | string | Yes | SailPoint Personal Access Token client ID | -| `clientSecret` | string | Yes | SailPoint Personal Access Token client secret | -| `tenant` | string | Yes | SailPoint tenant name or full *.api.identitynow.com / *.api.identitynowgov.com host | | `ownerId` | string | No | Approval owner identity ID or 'me'; admins may omit it for all approvals | | `filters` | string | No | SailPoint standard collection filter expression for this operation | | `sorters` | string | No | Comma-separated supported sort fields, prefixed with - for descending order | @@ -1531,9 +1443,6 @@ List roles with current visibility, segmentation, filtering, and pagination cont | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `clientId` | string | Yes | SailPoint Personal Access Token client ID | -| `clientSecret` | string | Yes | SailPoint Personal Access Token client secret | -| `tenant` | string | Yes | SailPoint tenant name or full *.api.identitynow.com / *.api.identitynowgov.com host | | `forSubadmin` | string | No | Subadmin identity ID or 'me' whose visible resources should be returned | | `forSegmentIds` | string | No | Comma-separated segment IDs used to restrict the returned resources | | `includeUnsegmented` | boolean | No | Include resources not assigned to a segment \(default true\) | @@ -1583,9 +1492,6 @@ List identity sources with visibility, filtering, sorting, and pagination contro | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `clientId` | string | Yes | SailPoint Personal Access Token client ID | -| `clientSecret` | string | Yes | SailPoint Personal Access Token client secret | -| `tenant` | string | Yes | SailPoint tenant name or full *.api.identitynow.com / *.api.identitynowgov.com host | | `filters` | string | No | SailPoint standard collection filter expression for this operation | | `sorters` | string | No | Comma-separated supported sort fields, prefixed with - for descending order | | `forSubadmin` | string | No | Subadmin identity ID or 'me' whose visible resources should be returned | @@ -1645,9 +1551,6 @@ Start account aggregation for a source, optionally using a CSV file. | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `clientId` | string | Yes | SailPoint Personal Access Token client ID | -| `clientSecret` | string | Yes | SailPoint Personal Access Token client secret | -| `tenant` | string | Yes | SailPoint tenant name or full *.api.identitynow.com / *.api.identitynowgov.com host | | `sourceId` | string | Yes | Source ID | | `file` | file | No | Delimited-file source account CSV | | `disableOptimization` | boolean | No | Reprocess every account instead of using optimized aggregation | @@ -1692,9 +1595,6 @@ Start entitlement aggregation for a source, optionally using a CSV file. | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `clientId` | string | Yes | SailPoint Personal Access Token client ID | -| `clientSecret` | string | Yes | SailPoint Personal Access Token client secret | -| `tenant` | string | Yes | SailPoint tenant name or full *.api.identitynow.com / *.api.identitynowgov.com host | | `sourceId` | string | Yes | Source ID | | `file` | file | No | Delimited-file source entitlement CSV | @@ -1725,9 +1625,6 @@ Reject one pending access-request approval with a reviewer comment. | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `clientId` | string | Yes | SailPoint Personal Access Token client ID | -| `clientSecret` | string | Yes | SailPoint Personal Access Token client secret | -| `tenant` | string | Yes | SailPoint tenant name or full *.api.identitynow.com / *.api.identitynowgov.com host | | `approvalId` | string | Yes | Approval ID | | `comment` | string | Yes | Reviewer rejection comment | @@ -1750,9 +1647,6 @@ Submit a current human or machine identity access request. | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `clientId` | string | Yes | SailPoint Personal Access Token client ID | -| `clientSecret` | string | Yes | SailPoint Personal Access Token client secret | -| `tenant` | string | Yes | SailPoint tenant name or full *.api.identitynow.com / *.api.identitynowgov.com host | | `requestType` | string | No | GRANT_ACCESS \(default\), REVOKE_ACCESS, or MODIFY_ACCESS | | `requestedFor` | array | No | Human identity IDs for the flat request shape | | `requestedItems` | array | No | Flat human request items | @@ -1792,9 +1686,6 @@ Search current SailPoint indices with every documented search query mode. | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `clientId` | string | Yes | SailPoint Personal Access Token client ID | -| `clientSecret` | string | Yes | SailPoint Personal Access Token client secret | -| `tenant` | string | Yes | SailPoint tenant name or full *.api.identitynow.com / *.api.identitynowgov.com host | | `indices` | array | No | Indices to search: accessprofiles, accountactivities, entitlements, events, identities, roles, or *. Omit to search all. | | `queryType` | string | No | SAILPOINT \(default\), DSL, TEXT, or TYPEAHEAD | | `queryVersion` | string | No | Elasticsearch query language version \(default 5.2\) | @@ -1835,9 +1726,6 @@ Run an Elasticsearch DSL or SailPoint aggregation over current search indices. | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `clientId` | string | Yes | SailPoint Personal Access Token client ID | -| `clientSecret` | string | Yes | SailPoint Personal Access Token client secret | -| `tenant` | string | Yes | SailPoint tenant name or full *.api.identitynow.com / *.api.identitynowgov.com host | | `indices` | array | No | Indices to search: accessprofiles, accountactivities, entitlements, events, identities, roles, or *. Omit to search all. | | `queryType` | string | No | SAILPOINT \(default\), DSL, TEXT, or TYPEAHEAD | | `queryVersion` | string | No | Elasticsearch query language version \(default 5.2\) | @@ -1878,9 +1766,6 @@ Count documents matching a complete SailPoint search body. | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `clientId` | string | Yes | SailPoint Personal Access Token client ID | -| `clientSecret` | string | Yes | SailPoint Personal Access Token client secret | -| `tenant` | string | Yes | SailPoint tenant name or full *.api.identitynow.com / *.api.identitynowgov.com host | | `indices` | array | No | Indices to search: accessprofiles, accountactivities, entitlements, events, identities, roles, or *. Omit to search all. | | `queryType` | string | No | SAILPOINT \(default\), DSL, TEXT, or TYPEAHEAD | | `queryVersion` | string | No | Elasticsearch query language version \(default 5.2\) | @@ -1916,9 +1801,6 @@ Sign off a completed identity certification. | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `clientId` | string | Yes | SailPoint Personal Access Token client ID | -| `clientSecret` | string | Yes | SailPoint Personal Access Token client secret | -| `tenant` | string | Yes | SailPoint tenant name or full *.api.identitynow.com / *.api.identitynowgov.com host | | `id` | string | Yes | Certification ID | #### Output diff --git a/apps/docs/content/docs/integrations/servicenow.mdx b/apps/docs/content/docs/integrations/servicenow.mdx index 91f21ff398e..8dc4d79cf78 100644 --- a/apps/docs/content/docs/integrations/servicenow.mdx +++ b/apps/docs/content/docs/integrations/servicenow.mdx @@ -2,7 +2,7 @@ title: ServiceNow description: Create, read, update, and delete ServiceNow records integration: - type: "servicenow_v2" + type: "servicenow_v3" color: "#032D42" --- @@ -10,7 +10,9 @@ import { ReferenceDetails } from "@/components/ui/reference-details" {/* MANUAL-CONTENT-START:intro */} -Use [ServiceNow](https://www.servicenow.com/) in Sim to create or update incidents and change requests, order catalog items, and process approvals. Read configuration items, knowledge articles, users, and groups, and transfer attachments. Generic record and aggregate operations accept a table name, such as incident, task, change_request, or sys_user, and the fields required by the operation. List Attachments and Upload Attachment also require a table name. Connect with your ServiceNow instance URL, username, and password. +Use [ServiceNow](https://www.servicenow.com/) in Sim to create or update incidents and change requests, order catalog items, and process approvals. Read configuration items, knowledge articles, users, and groups, and transfer attachments. Generic record and aggregate operations accept a table name, such as incident, task, change_request, or sys_user, and the fields required by the operation. List Attachments and Upload Attachment also require a table name. + +Connect ServiceNow once as a ServiceNow credential: enter the instance URL plus the username and password of a dedicated integration user that has the roles your workflows need and does not use multi-factor authentication. Enter the instance's https URL, such as `acme.service-now.com` or a custom instance URL; the instance must be reachable from the internet. Workflows built with the earlier ServiceNow block keep the instance URL, username, and password they entered in the block. {/* MANUAL-CONTENT-END */} ## Use the block [#usage-instructions] @@ -29,9 +31,6 @@ Create a new record in a ServiceNow table | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `instanceUrl` | string | Yes | ServiceNow instance URL \(e.g., https://instance.service-now.com) | -| `username` | string | Yes | ServiceNow username | -| `password` | string | Yes | ServiceNow password | | `tableName` | string | Yes | Table name \(e.g., incident, task, sys_user\) | | `fields` | json | Yes | Fields to set on the record as JSON object \(e.g., \{"short_description": "Issue title", "priority": "1"\}\) | @@ -54,9 +53,6 @@ Read records from a ServiceNow table | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `instanceUrl` | string | Yes | ServiceNow instance URL \(e.g., https://instance.service-now.com) | -| `username` | string | Yes | ServiceNow username | -| `password` | string | Yes | ServiceNow password | | `tableName` | string | Yes | Table name \(e.g., incident, task, sys_user, change_request\) | | `sysId` | string | No | Specific record sys_id \(e.g., 6816f79cc0a8016401c5a33be04be441\) | | `number` | string | No | Record number \(e.g., INC0010001\) | @@ -85,9 +81,6 @@ Update an existing record in a ServiceNow table | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `instanceUrl` | string | Yes | ServiceNow instance URL \(e.g., https://instance.service-now.com) | -| `username` | string | Yes | ServiceNow username | -| `password` | string | Yes | ServiceNow password | | `tableName` | string | Yes | Table name \(e.g., incident, task, sys_user, change_request\) | | `sysId` | string | Yes | Record sys_id to update \(e.g., 6816f79cc0a8016401c5a33be04be441\) | | `fields` | json | Yes | Fields to update as JSON object \(e.g., \{"state": "2", "priority": "1"\}\) | @@ -111,9 +104,6 @@ Delete a record from a ServiceNow table | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `instanceUrl` | string | Yes | ServiceNow instance URL \(e.g., https://instance.service-now.com) | -| `username` | string | Yes | ServiceNow username | -| `password` | string | Yes | ServiceNow password | | `tableName` | string | Yes | Table name \(e.g., incident, task, sys_user, change_request\) | | `sysId` | string | Yes | Record sys_id to delete \(e.g., 6816f79cc0a8016401c5a33be04be441\) | @@ -136,9 +126,6 @@ Compute aggregate statistics (count, sum, average, min, max, group by) over a Se | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `instanceUrl` | string | Yes | ServiceNow instance URL \(e.g., https://instance.service-now.com) | -| `username` | string | Yes | ServiceNow username | -| `password` | string | Yes | ServiceNow password | | `tableName` | string | Yes | Table name \(e.g., incident, change_request, task\) | | `query` | string | No | Encoded query string to filter records before aggregating \(e.g., "active=true"\) | | `count` | boolean | No | Return the count of matching records | @@ -170,9 +157,6 @@ List the attachments on a ServiceNow record | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `instanceUrl` | string | Yes | ServiceNow instance URL \(e.g., https://instance.service-now.com) | -| `username` | string | Yes | ServiceNow username | -| `password` | string | Yes | ServiceNow password | | `tableName` | string | Yes | Table that owns the record \(e.g., incident, change_request\) | | `recordSysId` | string | Yes | sys_id of the record whose attachments should be listed | | `limit` | number | No | Maximum number of attachments to return | @@ -201,9 +185,6 @@ Download an attachment file from ServiceNow by its sys_id | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `instanceUrl` | string | Yes | ServiceNow instance URL \(e.g., https://instance.service-now.com) | -| `username` | string | Yes | ServiceNow username | -| `password` | string | Yes | ServiceNow password | | `attachmentSysId` | string | Yes | sys_id of the attachment to download \(from List Attachments\) | #### Output @@ -224,9 +205,6 @@ Attach a file to a ServiceNow record | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `instanceUrl` | string | Yes | ServiceNow instance URL \(e.g., https://instance.service-now.com) | -| `username` | string | Yes | ServiceNow username | -| `password` | string | Yes | ServiceNow password | | `tableName` | string | Yes | Table that owns the record \(e.g., incident, change_request\) | | `recordSysId` | string | Yes | sys_id of the record to attach the file to | | `fileName` | string | Yes | Name to give the uploaded file \(e.g., logs.txt\) | @@ -251,9 +229,6 @@ Create an incident in ServiceNow. Reference fields (caller, assignment group, as | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `instanceUrl` | string | Yes | ServiceNow instance URL \(e.g., https://instance.service-now.com) | -| `username` | string | Yes | ServiceNow username | -| `password` | string | Yes | ServiceNow password | | `shortDescription` | string | Yes | Short description — the one-line summary of the incident. | | `description` | string | No | Detailed description of the incident. | | `callerId` | string | No | Caller \(caller_id\) — sys_id of the sys_user who reported the incident. Use Find ServiceNow User to resolve an email address to a sys_id. | @@ -319,9 +294,6 @@ Retrieve a single ServiceNow incident by number (e.g., INC0010001) or sys_id. Re | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `instanceUrl` | string | Yes | ServiceNow instance URL \(e.g., https://instance.service-now.com) | -| `username` | string | Yes | ServiceNow username | -| `password` | string | Yes | ServiceNow password | | `sysId` | string | No | Record sys_id. Provide either this or the record number. | | `number` | string | No | Record number \(e.g., INC0010001\). Provide either this or the sys_id. | | `fields` | string | No | Comma-separated list of fields to return \(e.g., number,short_description,state\). Returns all fields when omitted. | @@ -371,9 +343,6 @@ Search ServiceNow incidents by state, priority, assignment, caller, or text. All | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `instanceUrl` | string | Yes | ServiceNow instance URL \(e.g., https://instance.service-now.com) | -| `username` | string | Yes | ServiceNow username | -| `password` | string | Yes | ServiceNow password | | `searchText` | string | No | Text to match against the incident short description using the ServiceNow LIKE operator, which matches anywhere in the field. | | `state` | string | No | Incident state coded value. Base system: 1=New, 2=In Progress, 3=On Hold, 6=Resolved, 7=Closed, 8=Canceled. | | `priority` | string | No | Priority coded value 1-5 \(1 Critical … 5 Planning\). | @@ -431,9 +400,6 @@ Update fields on an existing ServiceNow incident. Only the fields you supply are | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `instanceUrl` | string | Yes | ServiceNow instance URL \(e.g., https://instance.service-now.com) | -| `username` | string | Yes | ServiceNow username | -| `password` | string | Yes | ServiceNow password | | `sysId` | string | Yes | sys_id of the record to update. If you only have the record number, look it up first \(for example with Get ServiceNow Incident\). | | `shortDescription` | string | No | New short description. | | `description` | string | No | New detailed description. | @@ -497,9 +463,6 @@ Move a ServiceNow incident to Resolved (state 6) with a resolution code and reso | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `instanceUrl` | string | Yes | ServiceNow instance URL \(e.g., https://instance.service-now.com) | -| `username` | string | Yes | ServiceNow username | -| `password` | string | Yes | ServiceNow password | | `sysId` | string | Yes | sys_id of the record to update. If you only have the record number, look it up first \(for example with Get ServiceNow Incident\). | | `closeCode` | string | Yes | Resolution code \(close_code\). This is a choice field whose values are configured per instance — read the choice list on your incident table and pass one of its values. | | `closeNotes` | string | Yes | Resolution notes \(close_notes\) documenting how the incident was resolved. | @@ -553,9 +516,6 @@ Move a ServiceNow incident to Closed (state 7) with a resolution code and resolu | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `instanceUrl` | string | Yes | ServiceNow instance URL \(e.g., https://instance.service-now.com) | -| `username` | string | Yes | ServiceNow username | -| `password` | string | Yes | ServiceNow password | | `sysId` | string | Yes | sys_id of the record to update. If you only have the record number, look it up first \(for example with Get ServiceNow Incident\). | | `closeCode` | string | Yes | Resolution code \(close_code\). This is a choice field whose values are configured per instance — read the choice list on your incident table and pass one of its values. | | `closeNotes` | string | Yes | Resolution notes \(close_notes\) documenting how the incident was resolved. | @@ -609,9 +569,6 @@ Append an internal work note or a customer-visible additional comment to a Servi | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `instanceUrl` | string | Yes | ServiceNow instance URL \(e.g., https://instance.service-now.com) | -| `username` | string | Yes | ServiceNow username | -| `password` | string | Yes | ServiceNow password | | `sysId` | string | Yes | sys_id of the record to update. If you only have the record number, look it up first \(for example with Get ServiceNow Incident\). | | `comment` | string | Yes | Text to append to the journal field. | | `commentField` | string | No | Which journal field to write to: "work_notes" for an internal note \(default\) or "comments" for a customer-visible additional comment. | @@ -663,9 +620,6 @@ Create a change request in ServiceNow. Reference fields (assignment group, assig | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `instanceUrl` | string | Yes | ServiceNow instance URL \(e.g., https://instance.service-now.com) | -| `username` | string | Yes | ServiceNow username | -| `password` | string | Yes | ServiceNow password | | `shortDescription` | string | Yes | Short description — the one-line summary of the change. | | `description` | string | No | Detailed description of the change. | | `type` | string | No | Change type: "normal", "standard", or "emergency". | @@ -732,9 +686,6 @@ Retrieve a single ServiceNow change request by number (e.g., CHG0030001) or sys_ | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `instanceUrl` | string | Yes | ServiceNow instance URL \(e.g., https://instance.service-now.com) | -| `username` | string | Yes | ServiceNow username | -| `password` | string | Yes | ServiceNow password | | `sysId` | string | No | Record sys_id. Provide either this or the record number. | | `number` | string | No | Record number \(e.g., INC0010001\). Provide either this or the sys_id. | | `fields` | string | No | Comma-separated list of fields to return \(e.g., number,short_description,state\). Returns all fields when omitted. | @@ -784,9 +735,6 @@ Search ServiceNow change requests by state, type, risk, assignment, or text. All | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `instanceUrl` | string | Yes | ServiceNow instance URL \(e.g., https://instance.service-now.com) | -| `username` | string | Yes | ServiceNow username | -| `password` | string | Yes | ServiceNow password | | `searchText` | string | No | Text to match against the change short description using the ServiceNow LIKE operator, which matches anywhere in the field. | | `state` | string | No | Change state coded value. Base system: -5=New, -4=Assess, -3=Authorize, -2=Scheduled, -1=Implement, 0=Review, 3=Closed, 4=Canceled. | | `type` | string | No | Change type: "normal", "standard", or "emergency". | @@ -844,9 +792,6 @@ Update fields on an existing ServiceNow change request. Only the fields you supp | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `instanceUrl` | string | Yes | ServiceNow instance URL \(e.g., https://instance.service-now.com) | -| `username` | string | Yes | ServiceNow username | -| `password` | string | Yes | ServiceNow password | | `sysId` | string | Yes | sys_id of the record to update. If you only have the record number, look it up first \(for example with Get ServiceNow Incident\). | | `shortDescription` | string | No | New short description. | | `description` | string | No | New detailed description. | @@ -910,9 +855,6 @@ Move a ServiceNow change request to another state. Base-system change model stat | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `instanceUrl` | string | Yes | ServiceNow instance URL \(e.g., https://instance.service-now.com) | -| `username` | string | Yes | ServiceNow username | -| `password` | string | Yes | ServiceNow password | | `sysId` | string | Yes | sys_id of the record to update. If you only have the record number, look it up first \(for example with Get ServiceNow Incident\). | | `state` | string | Yes | Target state coded value: -5 \(New\), -4 \(Assess\), -3 \(Authorize\), -2 \(Scheduled\), -1 \(Implement\), 0 \(Review\), 3 \(Closed\), or 4 \(Canceled\). | | `closeCode` | string | No | Close code, required when moving to Closed \(3\): "successful", "successful_issues", or "unsuccessful". | @@ -967,9 +909,6 @@ List the change tasks belonging to a ServiceNow change request, via the Change M | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `instanceUrl` | string | Yes | ServiceNow instance URL \(e.g., https://instance.service-now.com) | -| `username` | string | Yes | ServiceNow username | -| `password` | string | Yes | ServiceNow password | | `changeSysId` | string | Yes | sys_id of the change request whose tasks should be listed. | | `query` | string | No | ServiceNow encoded query used to filter the tasks \(e.g., "active=true^ORDERBYnumber"\). | | `order` | string | No | Field to sort the returned tasks by. | @@ -996,9 +935,6 @@ Read the states a ServiceNow change request can actually move to next, with the | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `instanceUrl` | string | Yes | ServiceNow instance URL \(e.g., https://instance.service-now.com) | -| `username` | string | Yes | ServiceNow username | -| `password` | string | Yes | ServiceNow password | | `changeSysId` | string | Yes | sys_id of the change request. Use Get ServiceNow Change Request to resolve a CHG number to its sys_id. | #### Output @@ -1033,9 +969,6 @@ Browse or search the ServiceNow service catalog. Returns each item with its sys_ | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `instanceUrl` | string | Yes | ServiceNow instance URL \(e.g., https://instance.service-now.com) | -| `username` | string | Yes | ServiceNow username | -| `password` | string | Yes | ServiceNow password | | `searchText` | string | No | Text to search for in the catalog items \(e.g., "iPhone"\). | | `catalogSysId` | string | No | Restrict results to a specific catalog, by catalog sys_id. | | `categorySysId` | string | No | Restrict results to a specific category, by category sys_id. | @@ -1079,9 +1012,6 @@ Submit a service catalog request for a catalog item using the Service Catalog AP | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `instanceUrl` | string | Yes | ServiceNow instance URL \(e.g., https://instance.service-now.com) | -| `username` | string | Yes | ServiceNow username | -| `password` | string | Yes | ServiceNow password | | `catalogItemSysId` | string | Yes | sys_id of the catalog item to order. Use List ServiceNow Catalog Items to find it. | | `quantity` | number | No | Quantity to order. Must not be negative. Defaults to 1. | | `requestedFor` | string | No | sys_id of the sys_user the item is ordered for. Ordering on behalf of another user is governed by the glide.sc.req_for.roles instance properties. | @@ -1109,9 +1039,6 @@ List requested items (RITMs) from the ServiceNow Requested Item [sc_req_item] ta | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `instanceUrl` | string | Yes | ServiceNow instance URL \(e.g., https://instance.service-now.com) | -| `username` | string | Yes | ServiceNow username | -| `password` | string | Yes | ServiceNow password | | `requestSysId` | string | No | sys_id of the parent request \(sc_request\) whose items should be listed. | | `catalogItemSysId` | string | No | sys_id of the catalog item \(cat_item\) to filter by. | | `active` | string | No | Restrict to active \("true"\) or inactive \("false"\) requested items. | @@ -1165,9 +1092,6 @@ Retrieve a single ServiceNow requested item (RITM) by number (e.g., RITM0010001) | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `instanceUrl` | string | Yes | ServiceNow instance URL \(e.g., https://instance.service-now.com) | -| `username` | string | Yes | ServiceNow username | -| `password` | string | Yes | ServiceNow password | | `sysId` | string | No | Record sys_id. Provide either this or the record number. | | `number` | string | No | Record number \(e.g., INC0010001\). Provide either this or the sys_id. | | `fields` | string | No | Comma-separated list of fields to return \(e.g., number,short_description,state\). Returns all fields when omitted. | @@ -1217,9 +1141,6 @@ List approval records from the ServiceNow Approval [sysapproval_approver] table. | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `instanceUrl` | string | Yes | ServiceNow instance URL \(e.g., https://instance.service-now.com) | -| `username` | string | Yes | ServiceNow username | -| `password` | string | Yes | ServiceNow password | | `approverSysId` | string | No | sys_id of the approver \(sys_user\) whose approvals should be listed. Use Find ServiceNow User to resolve an email address to a sys_id. | | `state` | string | No | Approval state: "requested" \(pending, the default\), "approved", or "rejected". Pass an empty string with a custom query to list every state. | | `approvalFor` | string | No | sys_id of the record being approved, matched against the sysapproval reference field. | @@ -1273,9 +1194,6 @@ Approve or reject a ServiceNow approval record by setting its state on the Appro | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `instanceUrl` | string | Yes | ServiceNow instance URL \(e.g., https://instance.service-now.com) | -| `username` | string | Yes | ServiceNow username | -| `password` | string | Yes | ServiceNow password | | `approvalSysId` | string | Yes | sys_id of the approval record on the sysapproval_approver table. Use List ServiceNow Approvals to find it. | | `decision` | string | Yes | Decision to record: "approved" or "rejected". | | `comments` | string | No | Comment to record alongside the decision. This is a journal field. | @@ -1327,9 +1245,6 @@ Search the ServiceNow CMDB for configuration items. Defaults to the base cmdb_ci | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `instanceUrl` | string | Yes | ServiceNow instance URL \(e.g., https://instance.service-now.com) | -| `username` | string | Yes | ServiceNow username | -| `password` | string | Yes | ServiceNow password | | `ciClass` | string | No | CMDB class \(table\) to search, e.g., cmdb_ci_linux_server or cmdb_ci_app_server. Defaults to cmdb_ci, which covers every CI class. | | `name` | string | No | Text to match against the CI name using the ServiceNow LIKE operator, which matches anywhere in the field. | | `operationalStatus` | string | No | Operational status coded value \(operational_status\). The choice list is configured per instance. | @@ -1383,9 +1298,6 @@ Retrieve a configuration item and its CMDB relationships through the CMDB Instan | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `instanceUrl` | string | Yes | ServiceNow instance URL \(e.g., https://instance.service-now.com) | -| `username` | string | Yes | ServiceNow username | -| `password` | string | Yes | ServiceNow password | | `ciClass` | string | Yes | CMDB class \(table\) the CI belongs to, e.g., cmdb_ci_linux_server. Read it from the sys_class_name field returned by Search ServiceNow Configuration Items. | | `sysId` | string | Yes | sys_id of the configuration item to retrieve. | @@ -1418,9 +1330,6 @@ List rows from the CI Relationship [cmdb_rel_ci] table for a configuration item. | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `instanceUrl` | string | Yes | ServiceNow instance URL \(e.g., https://instance.service-now.com) | -| `username` | string | Yes | ServiceNow username | -| `password` | string | Yes | ServiceNow password | | `ciSysId` | string | Yes | sys_id of the configuration item whose relationships should be listed. | | `direction` | string | No | Which side of the relationship the CI sits on: "parent", "child", or "both" \(default\). "both" matches rows where the CI is either the parent or the child. | | `query` | string | No | Additional ServiceNow encoded query, ANDed with the other filters \(e.g., "opened_at>=javascript:gs.beginningOfLastMonth\(\)"\). | @@ -1473,9 +1382,6 @@ Search ServiceNow knowledge base articles through the Knowledge Management API. | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `instanceUrl` | string | Yes | ServiceNow instance URL \(e.g., https://instance.service-now.com) | -| `username` | string | Yes | ServiceNow username | -| `password` | string | Yes | ServiceNow password | | `query` | string | No | Text to search for across knowledge articles. | | `filter` | string | No | Encoded query used to filter the results, against the Knowledge \[kb_knowledge\] table \(e.g., "workflow_state=published"\). | | `knowledgeBaseSysIds` | string | No | Comma-separated knowledge base sys_ids \(from kb_knowledge_base\) to restrict results to. | @@ -1513,9 +1419,6 @@ Retrieve the full content of a ServiceNow knowledge article by sys_id or KB numb | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `instanceUrl` | string | Yes | ServiceNow instance URL \(e.g., https://instance.service-now.com) | -| `username` | string | Yes | ServiceNow username | -| `password` | string | Yes | ServiceNow password | | `articleId` | string | Yes | sys_id or KB number of the knowledge article \(e.g., KB0000011\). | | `fields` | string | No | Comma-separated kb_knowledge fields to include alongside the article content \(e.g., workflow_state,author\). | | `language` | string | No | Two-letter ISO 639-1 language code. Only applies when the article is addressed by KB number and a translation exists. | @@ -1548,9 +1451,6 @@ Look up ServiceNow users by email, user name, or display name. Use this to resol | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `instanceUrl` | string | Yes | ServiceNow instance URL \(e.g., https://instance.service-now.com) | -| `username` | string | Yes | ServiceNow username | -| `password` | string | Yes | ServiceNow password | | `email` | string | No | Exact email address to match. | | `userName` | string | No | Exact user name \(user_name\) to match. | | `name` | string | No | Text to match against the display name using the ServiceNow LIKE operator, which matches anywhere in the field. | @@ -1605,9 +1505,6 @@ List the members of a ServiceNow group from the Group Member [sys_user_grmember] | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `instanceUrl` | string | Yes | ServiceNow instance URL \(e.g., https://instance.service-now.com) | -| `username` | string | Yes | ServiceNow username | -| `password` | string | Yes | ServiceNow password | | `groupSysId` | string | No | sys_id of the sys_user_group whose members should be listed. | | `groupName` | string | No | Exact group name, resolved against the referenced group record. Provide this or the group sys_id. | | `query` | string | No | Additional ServiceNow encoded query, ANDed with the other filters \(e.g., "opened_at>=javascript:gs.beginningOfLastMonth\(\)"\). | diff --git a/apps/docs/content/docs/integrations/shopify-service-account.mdx b/apps/docs/content/docs/integrations/shopify-service-account.mdx index 93660d04247..0dbacb38bbd 100644 --- a/apps/docs/content/docs/integrations/shopify-service-account.mdx +++ b/apps/docs/content/docs/integrations/shopify-service-account.mdx @@ -1,12 +1,15 @@ --- title: Shopify Admin API tokens -description: Set up a Shopify custom app with an Admin API access token to use Shopify in Sim workflows +description: Connect a Shopify Dev Dashboard app with client credentials, or a custom app with an Admin API access token, to use Shopify in Sim workflows --- import { Callout } from '@/components/ui/callout' import { Step, Steps } from '@/components/ui/steps' -Connect a Shopify store with a **non-expiring offline Admin API access token** and its `.myshopify.com` domain. Sim stores the pasted token; this connection does not renew expiring tokens. +Connect a Shopify store in one of two ways, both with the store's `.myshopify.com` domain: + +- **Client credentials (Dev Dashboard app)**: the app's client ID and secret. Sim requests a 24-hour Admin API token from Shopify when a workflow needs one and renews it before it expires. Use this for apps created in the Dev Dashboard, which never show an Admin API token. +- **Admin API access token**: a **non-expiring offline** `shpat_` token pasted once. Sim stores the pasted token and does not renew expiring tokens. @@ -14,7 +17,28 @@ Connect a Shopify store with a **non-expiring offline Admin API access token** a You need store owner or staff permissions that allow managing apps on the store. You'll also need your store's permanent domain (`your-store.myshopify.com`) — Sim asks for it alongside the token. -Shopify no longer allows creating new custom apps from the store admin — that path is legacy. New custom apps are created from the **Dev Dashboard** ([dev.shopify.com](https://dev.shopify.com/)) or the Shopify CLI. If you already have an admin-created custom app, its `shpat_` token still works and can be pasted into Sim directly. +Shopify no longer allows creating new custom apps from the store admin — that path is legacy. New custom apps are created from the **Dev Dashboard** ([dev.shopify.com](https://dev.shopify.com/)) or the Shopify CLI; connect those with client credentials. If you already have an admin-created custom app, its `shpat_` token still works and can be pasted into Sim directly. + +## Connect a Dev Dashboard app with client credentials [#client-credentials] + +Shopify's [client credentials grant](https://shopify.dev/docs/apps/build/authentication-authorization/access-tokens/client-credentials-grant) only works when the app and the store belong to the **same Shopify organization**. If they don't, Shopify refuses the grant (`shop_not_permitted`) and Sim tells you so when you save the connection. + + + + In the [Dev Dashboard](https://dev.shopify.com/), create an app, configure the Admin API access scopes listed below, and install it on your store + + + Copy the app's **Client ID** and **Client secret** from its settings + + + In Sim, open **Integrations**, search for "Shopify", click **Add to Sim**, and choose **Add admin API token**. Leave **Authentication** on **Client credentials (Dev Dashboard app)**, then paste the client ID, the client secret, and the store domain + + + Save. Sim requests a token from Shopify and runs a scope-free `shop` query with it before saving. The client ID, client secret, and store domain are encrypted. Sim keeps one encrypted 24-hour token per app and store, separate from the credential, requests a new one before it expires, and replaces it early if Shopify rejects it + + + +Tokens carry every scope the store granted the app; Sim never requests a narrower set. Rotating the client secret in the Dev Dashboard means reconnecting the credential in Sim with the new secret. ## Set up the custom app [#setting-up-the-custom-app] @@ -48,10 +72,10 @@ Shopify no longer allows creating new custom apps from the store admin — that How you obtain the `shpat_` token depends on how the app was created: - **Legacy admin-created custom apps** (created from the store admin before Shopify removed that path) show the **Admin API access token** — it starts with `shpat_` — once on the app's API credentials page. Reveal it, copy it immediately, and paste it into Sim. For legacy admin-created custom apps, Shopify shows the token only once, at creation — if you lose it, you have to reinstall or recreate the app to get a new one. -- **New Dev Dashboard apps** do **not** display a permanent `shpat_` token in any UI. Complete an [authorization code grant](https://shopify.dev/docs/apps/build/authentication-authorization/authenticate-standalone-apps) install and obtain a **non-expiring offline** access token. Check the token response and access mode: the `shpat_` prefix is also used by expiring tokens. The Dev Dashboard's client-credentials tokens are different — they expire after 24 hours and cannot be stored in Sim. +- **New Dev Dashboard apps** do **not** display a permanent `shpat_` token in any UI. Connect them with [client credentials](#client-credentials) instead. Don't paste a 24-hour client-credentials token here: it stops working after a day. -Don't confuse the token types: `shpat_` is the Admin API access token Sim needs. `shpss_` is the app's client secret and `shpca_` identifies an older custom-app token format. Use the documented Admin API token for your app and confirm its lifetime; a client secret is not an access token. If your Dev Dashboard app only shows a client ID and secret, that is expected: Dev Dashboard apps issue tokens via OAuth, not a UI reveal (see the setup steps above). +Don't confuse the token types: `shpat_` is the Admin API access token Sim needs. `shpss_` is the app's client secret and `shpca_` identifies an older custom-app token format. Use the documented Admin API token for your app and confirm its lifetime; a client secret is not an access token. If your Dev Dashboard app only shows a client ID and secret, that is expected: choose **Client credentials** and paste those instead. ### 3. Find your store domain [#3-find-your-store-domain] @@ -68,7 +92,7 @@ Use the permanent `.myshopify.com` domain — for example, `your-store.myshopify Search for "Shopify" and open it, then click **Add to Sim** and choose **Add admin API token** - Paste the Admin API access token, enter the store domain (e.g. `your-store.myshopify.com`), and optionally set a display name and description + Set **Authentication** to **Admin API access token (shpat_)**, paste the token, enter the store domain (e.g. `your-store.myshopify.com`), and optionally set a display name and description Click **Add admin API token**. Sim verifies the token with a scope-free `shop` query on the store’s GraphQL Admin API — if it fails, you'll see a specific error explaining what went wrong. @@ -85,7 +109,7 @@ The block calls your store's Admin API (`your-store.myshopify.com/admin/api/...` ## Token lifecycle and recovery -A pasted credential must use a non-expiring offline token; Sim does not renew online, expiring offline, or 24-hour client-credentials tokens. A `shpat_` prefix alone does not establish the token’s lifetime. See [Shopify’s access-token reference](https://shopify.dev/docs/apps/build/authentication-authorization/access-tokens). +A pasted credential must use a non-expiring offline token; Sim does not renew a pasted online, expiring offline, or 24-hour client-credentials token. Connect a Dev Dashboard app with client credentials instead, and Sim renews its tokens itself. A `shpat_` prefix alone does not establish the token’s lifetime. See [Shopify’s access-token reference](https://shopify.dev/docs/apps/build/authentication-authorization/access-tokens). Tokens are scoped to the app’s installation on a store. Add a separate credential for each store. If an operation fails with access denied, review the app’s scopes and the store’s approval for protected data. Apply the scope change and re-authorize as required; update Sim if the procedure issues a replacement token. diff --git a/apps/docs/content/docs/integrations/table.mdx b/apps/docs/content/docs/integrations/table.mdx index 0e62bcd719f..79a60a6957c 100644 --- a/apps/docs/content/docs/integrations/table.mdx +++ b/apps/docs/content/docs/integrations/table.mdx @@ -217,7 +217,7 @@ Query rows with a typed predicate filter and cursor pagination. A single filter | --------- | ---- | -------- | ----------- | | `tableId` | string | Yes | Table ID in the execution workspace. In a workflow, the table must belong to the same workspace as the workflow. | | `filter` | json | No | Predicate condition, e.g. `\{"field":"wins","op":"gte","value":10\}`. Use `all` or `any` for multiple conditions; omit to match all rows. | -| `columns` | array | No | Stable column IDs or table column names to include in each row data object. Omit or pass an empty array to return all columns. A reference that matches no column is ignored. | +| `columns` | array | No | Stable column IDs or table column names to include in each row data object. Omit or pass an empty array to return all columns. A reference that matches no column is ignored; if none match, each row's data is empty. | | `order` | json | No | Sort spec, e.g. `\[\{"field":"wins","direction":"desc"\}\]`. | | `limit` | number | No | Maximum rows per page. Omit to return the entire matching result — fails if it exceeds the fixed 5 MiB row-data budget. With a limit, pages may byte-cut early and set nextCursor when more remain. | | `cursor` | string | No | Opaque pagination cursor returned by a prior query. Omit for the first page. | diff --git a/apps/docs/content/docs/integrations/twilio_sms.mdx b/apps/docs/content/docs/integrations/twilio_sms.mdx index 39bdd4f6cfb..ee940ea75b2 100644 --- a/apps/docs/content/docs/integrations/twilio_sms.mdx +++ b/apps/docs/content/docs/integrations/twilio_sms.mdx @@ -11,6 +11,8 @@ import { ReferenceDetails } from "@/components/ui/reference-details" {/* MANUAL-CONTENT-START:intro */} Use [Twilio SMS](https://www.twilio.com/en-us/sms) to send a text message from a workflow. Configure the Twilio credentials, sending number, recipient, and message body documented below. + +Connect Twilio once as a Twilio credential with your Account SID and either the account's Auth Token or an [API key](https://www.twilio.com/docs/iam/api-keys). Enter an API key as its SID and secret joined by a colon (`SKxxx:secret`). A Standard or Main key works; a restricted key needs read access to phone numbers. Workflows built with the earlier Twilio block keep the Account SID and Auth Token they entered in the block. {/* MANUAL-CONTENT-END */} ## Use the block [#usage-instructions] diff --git a/apps/docs/content/docs/integrations/twilio_voice.mdx b/apps/docs/content/docs/integrations/twilio_voice.mdx index 8e7527ab8ae..cc8d55d5ef8 100644 --- a/apps/docs/content/docs/integrations/twilio_voice.mdx +++ b/apps/docs/content/docs/integrations/twilio_voice.mdx @@ -11,6 +11,8 @@ import { ReferenceDetails } from "@/components/ui/reference-details" {/* MANUAL-CONTENT-START:intro */} [Twilio Voice](https://www.twilio.com/en-us/voice) connects phone calls to workflows. Use this integration to place outbound calls, list calls, retrieve recordings, and receive call events through a webhook. Supply the call instructions and phone numbers required by the selected operation. + +Connect Twilio once as a Twilio credential with your Account SID and either the account's Auth Token or an [API key](https://www.twilio.com/docs/iam/api-keys). Enter an API key as its SID and secret joined by a colon (`SKxxx:secret`). A Standard or Main key works; a restricted key needs read access to phone numbers. Workflows built with the earlier Twilio block keep the Account SID and Auth Token they entered in the block. {/* MANUAL-CONTENT-END */} ## Use the block [#usage-instructions] diff --git a/apps/docs/content/docs/integrations/zendesk.mdx b/apps/docs/content/docs/integrations/zendesk.mdx index d8e6e39f8ce..883b298cc3e 100644 --- a/apps/docs/content/docs/integrations/zendesk.mdx +++ b/apps/docs/content/docs/integrations/zendesk.mdx @@ -2,7 +2,7 @@ title: Zendesk description: Manage support tickets, users, and organizations in Zendesk integration: - type: "zendesk" + type: "zendesk_v2" color: "#03363D" --- @@ -11,6 +11,15 @@ import { ReferenceDetails } from "@/components/ui/reference-details" {/* MANUAL-CONTENT-START:intro */} Use [Zendesk](https://www.zendesk.com/) in Sim to manage tickets, users, and organizations and search support records. The reference includes single-record and bulk operations, ticket merging, organization autocomplete, and search counts. + +### Connect Zendesk + +Add a Zendesk credential under **Integrations** and pick it in the block. A credential holds your subdomain (`your-company` from `your-company.zendesk.com`) and one of two sign-ins: + +- **OAuth client (client credentials)**: the unique identifier and secret of a **confidential** OAuth client from Admin Center (**Apps and integrations** > **APIs** > **OAuth Clients**), plus the permissions to request (read only, or read and write). Sim requests a one-hour token with [Zendesk's client credentials grant](https://developer.zendesk.com/api-reference/ticketing/oauth/grant_type_tokens/) when a workflow needs one. Every call runs as the user the OAuth client belongs to. +- **API token**: an agent or admin email and an [API token](https://developer.zendesk.com/api-reference/introduction/security-and-auth/). Every call runs as that agent. Zendesk has deprecated API tokens and asks customers to move to OAuth, so prefer the OAuth client. + +When you save, Sim checks that the credential signs in as a real agent or admin of that subdomain. Workflows that still type an email and API token into a Zendesk block keep working unchanged. {/* MANUAL-CONTENT-END */} ## Use the block [#usage-instructions] @@ -29,9 +38,6 @@ Retrieve a list of tickets from Zendesk with optional filtering | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `email` | string | Yes | Your Zendesk email address | -| `apiToken` | string | Yes | Zendesk API token | -| `subdomain` | string | Yes | Your Zendesk subdomain \(e.g., "mycompany" for mycompany.zendesk.com\) | | `status` | string | No | Filter by status: "new", "open", "pending", "hold", "solved", or "closed" | | `priority` | string | No | Filter by priority: "low", "normal", "high", or "urgent" | | `type` | string | No | Filter by type: "problem", "incident", "question", or "task" | @@ -118,9 +124,6 @@ Get a single ticket by ID from Zendesk | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `email` | string | Yes | Your Zendesk email address | -| `apiToken` | string | Yes | Zendesk API token | -| `subdomain` | string | Yes | Your Zendesk subdomain | | `ticketId` | string | Yes | Ticket ID to retrieve as a numeric string \(e.g., "12345"\) | #### Output @@ -194,9 +197,6 @@ Create a new ticket in Zendesk with support for custom fields | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `email` | string | Yes | Your Zendesk email address | -| `apiToken` | string | Yes | Zendesk API token | -| `subdomain` | string | Yes | Your Zendesk subdomain | | `subject` | string | No | Ticket subject \(optional - will be auto-generated if not provided\) | | `description` | string | Yes | Ticket description text \(first comment\) | | `priority` | string | No | Priority: "low", "normal", "high", or "urgent" | @@ -279,9 +279,6 @@ Create multiple tickets in Zendesk at once (max 100) | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `email` | string | Yes | Your Zendesk email address | -| `apiToken` | string | Yes | Zendesk API token | -| `subdomain` | string | Yes | Your Zendesk subdomain | | `tickets` | string | Yes | JSON array of ticket objects to create \(max 100\). Each ticket should have subject and comment properties \(e.g., \[\{"subject": "Issue 1", "comment": \{"body": "Description"\}\}\]\) | #### Output @@ -317,9 +314,6 @@ Update an existing ticket in Zendesk with support for custom fields | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `email` | string | Yes | Your Zendesk email address | -| `apiToken` | string | Yes | Zendesk API token | -| `subdomain` | string | Yes | Your Zendesk subdomain | | `ticketId` | string | Yes | Ticket ID to update as a numeric string \(e.g., "12345"\) | | `subject` | string | No | New ticket subject text | | `comment` | string | No | Comment text to add to the ticket | @@ -402,9 +396,6 @@ Update multiple tickets in Zendesk at once (max 100) | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `email` | string | Yes | Your Zendesk email address | -| `apiToken` | string | Yes | Zendesk API token | -| `subdomain` | string | Yes | Your Zendesk subdomain | | `ticketIds` | string | Yes | Comma-separated ticket IDs to update \(max 100, e.g., "111, 222, 333"\) | | `status` | string | No | New status for all tickets: "new", "open", "pending", "hold", "solved", or "closed" | | `priority` | string | No | New priority for all tickets: "low", "normal", "high", or "urgent" | @@ -445,9 +436,6 @@ Delete a ticket from Zendesk | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `email` | string | Yes | Your Zendesk email address | -| `apiToken` | string | Yes | Zendesk API token | -| `subdomain` | string | Yes | Your Zendesk subdomain | | `ticketId` | string | Yes | Ticket ID to delete as a numeric string \(e.g., "12345"\) | #### Output @@ -469,9 +457,6 @@ Merge multiple tickets into a target ticket | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `email` | string | Yes | Your Zendesk email address | -| `apiToken` | string | Yes | Zendesk API token | -| `subdomain` | string | Yes | Your Zendesk subdomain | | `targetTicketId` | string | Yes | Target ticket ID as a numeric string \(tickets will be merged into this one, e.g., "12345"\) | | `sourceTicketIds` | string | Yes | Comma-separated source ticket IDs to merge \(e.g., "111, 222, 333"\) | | `targetComment` | string | No | Comment text to add to target ticket after merge | @@ -510,9 +495,6 @@ Retrieve a list of users from Zendesk with optional filtering | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `email` | string | Yes | Your Zendesk email address | -| `apiToken` | string | Yes | Zendesk API token | -| `subdomain` | string | Yes | Your Zendesk subdomain \(e.g., "mycompany" for mycompany.zendesk.com\) | | `role` | string | No | Filter by role: "end-user", "agent", or "admin" | | `permissionSet` | string | No | Filter by permission set ID as a numeric string \(e.g., "12345"\) | | `perPage` | string | No | Results per page as a number string \(default: "100", max: "100"\) | @@ -584,9 +566,6 @@ Get a single user by ID from Zendesk | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `email` | string | Yes | Your Zendesk email address | -| `apiToken` | string | Yes | Zendesk API token | -| `subdomain` | string | Yes | Your Zendesk subdomain | | `userId` | string | Yes | User ID to retrieve as a numeric string \(e.g., "12345"\) | #### Output @@ -649,9 +628,6 @@ Get the currently authenticated user from Zendesk | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `email` | string | Yes | Your Zendesk email address | -| `apiToken` | string | Yes | Zendesk API token | -| `subdomain` | string | Yes | Your Zendesk subdomain | #### Output @@ -713,9 +689,6 @@ Search for users in Zendesk using a query string | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `email` | string | Yes | Your Zendesk email address | -| `apiToken` | string | Yes | Zendesk API token | -| `subdomain` | string | Yes | Your Zendesk subdomain | | `query` | string | No | Search query string \(e.g., user name or email\) | | `externalId` | string | No | External ID to search by \(your system identifier\) | | `perPage` | string | No | Results per page as a number string \(default: "100", max: "100"\) | @@ -787,9 +760,6 @@ Create a new user in Zendesk | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `email` | string | Yes | Your Zendesk email address | -| `apiToken` | string | Yes | Zendesk API token | -| `subdomain` | string | Yes | Your Zendesk subdomain | | `name` | string | Yes | User full name \(e.g., "John Smith"\) | | `userEmail` | string | No | User email address \(e.g., "john@example.com"\) | | `role` | string | No | User role: "end-user", "agent", or "admin" | @@ -859,9 +829,6 @@ Create multiple users in Zendesk using bulk import | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `email` | string | Yes | Your Zendesk email address | -| `apiToken` | string | Yes | Zendesk API token | -| `subdomain` | string | Yes | Your Zendesk subdomain | | `users` | string | Yes | JSON array of user objects to create \(e.g., \[\{"name": "User1", "email": "user1@example.com"\}\]\) | #### Output @@ -897,9 +864,6 @@ Update an existing user in Zendesk | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `email` | string | Yes | Your Zendesk email address | -| `apiToken` | string | Yes | Zendesk API token | -| `subdomain` | string | Yes | Your Zendesk subdomain | | `userId` | string | Yes | User ID to update as a numeric string \(e.g., "12345"\) | | `name` | string | No | New user full name \(e.g., "John Smith"\) | | `userEmail` | string | No | New user email address \(e.g., "john@example.com"\) | @@ -970,9 +934,6 @@ Update multiple users in Zendesk using bulk update | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `email` | string | Yes | Your Zendesk email address | -| `apiToken` | string | Yes | Zendesk API token | -| `subdomain` | string | Yes | Your Zendesk subdomain | | `users` | string | Yes | JSON array of user objects to update, each must include id field \(e.g., \[\{"id": "123", "name": "New Name"\}\]\) | #### Output @@ -1008,9 +969,6 @@ Delete a user from Zendesk | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `email` | string | Yes | Your Zendesk email address | -| `apiToken` | string | Yes | Zendesk API token | -| `subdomain` | string | Yes | Your Zendesk subdomain | | `userId` | string | Yes | User ID to delete as a numeric string \(e.g., "12345"\) | #### Output @@ -1032,9 +990,6 @@ Retrieve a list of organizations from Zendesk | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `email` | string | Yes | Your Zendesk email address | -| `apiToken` | string | Yes | Zendesk API token | -| `subdomain` | string | Yes | Your Zendesk subdomain \(e.g., "mycompany" for mycompany.zendesk.com\) | | `perPage` | string | No | Results per page as a number string \(default: "100", max: "100"\) | | `pageAfter` | string | No | Cursor from a previous response to fetch the next page of results | @@ -1077,9 +1032,6 @@ Get a single organization by ID from Zendesk | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `email` | string | Yes | Your Zendesk email address | -| `apiToken` | string | Yes | Zendesk API token | -| `subdomain` | string | Yes | Your Zendesk subdomain | | `organizationId` | string | Yes | Organization ID to retrieve as a numeric string \(e.g., "12345"\) | #### Output @@ -1115,9 +1067,6 @@ Autocomplete organizations in Zendesk by name prefix (for name matching/autocomp | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `email` | string | Yes | Your Zendesk email address | -| `apiToken` | string | Yes | Zendesk API token | -| `subdomain` | string | Yes | Your Zendesk subdomain | | `name` | string | Yes | Organization name prefix to search for \(e.g., "Acme"\) | | `perPage` | string | No | Results per page as a number string \(default: "100", max: "100"\) | | `page` | string | No | Page number for pagination \(1-based\) | @@ -1161,9 +1110,6 @@ Create a new organization in Zendesk | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `email` | string | Yes | Your Zendesk email address | -| `apiToken` | string | Yes | Zendesk API token | -| `subdomain` | string | Yes | Your Zendesk subdomain | | `name` | string | Yes | Organization name \(e.g., "Acme Corporation"\) | | `domainNames` | string | No | Comma-separated domain names \(e.g., "acme.com, acme.org"\) | | `details` | string | No | Organization details text | @@ -1204,9 +1150,6 @@ Create multiple organizations in Zendesk using bulk import | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `email` | string | Yes | Your Zendesk email address | -| `apiToken` | string | Yes | Zendesk API token | -| `subdomain` | string | Yes | Your Zendesk subdomain | | `organizations` | string | Yes | JSON array of organization objects to create \(e.g., \[\{"name": "Org1"\}, \{"name": "Org2"\}\]\) | #### Output @@ -1242,9 +1185,6 @@ Update an existing organization in Zendesk | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `email` | string | Yes | Your Zendesk email address | -| `apiToken` | string | Yes | Zendesk API token | -| `subdomain` | string | Yes | Your Zendesk subdomain | | `organizationId` | string | Yes | Organization ID to update as a numeric string \(e.g., "12345"\) | | `name` | string | No | New organization name \(e.g., "Acme Corporation"\) | | `domainNames` | string | No | Comma-separated domain names \(e.g., "acme.com, acme.org"\) | @@ -1286,9 +1226,6 @@ Delete an organization from Zendesk | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `email` | string | Yes | Your Zendesk email address | -| `apiToken` | string | Yes | Zendesk API token | -| `subdomain` | string | Yes | Your Zendesk subdomain | | `organizationId` | string | Yes | Organization ID to delete as a numeric string \(e.g., "12345"\) | #### Output @@ -1310,9 +1247,6 @@ Unified search across tickets, users, and organizations in Zendesk | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `email` | string | Yes | Your Zendesk email address | -| `apiToken` | string | Yes | Zendesk API token | -| `subdomain` | string | Yes | Your Zendesk subdomain | | `query` | string | Yes | Search query string using Zendesk search syntax \(e.g., "type:ticket status:open"\) | | `filterType` | string | Yes | Resource type to search for: "ticket", "user", "organization", or "group" | | `perPage` | string | No | Results per page as a number string \(default: "100", max: "100"\) | @@ -1343,9 +1277,6 @@ Count the number of search results matching a query in Zendesk | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `email` | string | Yes | Your Zendesk email address | -| `apiToken` | string | Yes | Zendesk API token | -| `subdomain` | string | Yes | Your Zendesk subdomain | | `query` | string | Yes | Search query string using Zendesk search syntax \(e.g., "type:ticket status:open"\) | #### Output diff --git a/apps/docs/content/docs/integrations/zoominfo.mdx b/apps/docs/content/docs/integrations/zoominfo.mdx index aedf441c1ba..39481c9a471 100644 --- a/apps/docs/content/docs/integrations/zoominfo.mdx +++ b/apps/docs/content/docs/integrations/zoominfo.mdx @@ -2,7 +2,7 @@ title: ZoomInfo description: Search and enrich B2B company and contact data with ZoomInfo. integration: - type: "zoominfo" + type: "zoominfo_v2" color: "#EA1B15" --- @@ -16,7 +16,7 @@ Use [ZoomInfo](https://www.zoominfo.com/) to search company and contact data, en **Search Intent** returns companies researching selected topics, signal scores, and recommended contacts. **Search News** filters by category, source URL, or publication date. -Supply a client ID and secret. Sim exchanges them for a short-lived OAuth bearer token on each call; you do not supply the access token yourself. +Connect ZoomInfo once as a ZoomInfo credential: create a client credentials app in the ZoomInfo Developer Portal, choose the integration user it acts as, and add its client ID and client secret. Sim checks the credential by requesting a token, without calling a data endpoint, and requests a new token before each one expires. Every call acts as the app's integration user with the scopes configured on the app, and data calls consume credits as usual. Workflows built with the earlier ZoomInfo block keep the client ID and secret they entered in the block. {/* MANUAL-CONTENT-END */} ## Use the block [#usage-instructions] @@ -35,8 +35,6 @@ Search the ZoomInfo company database by name, industry, location, and size. | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `clientId` | string | Yes | ZoomInfo OAuth client ID | -| `clientSecret` | string | Yes | ZoomInfo OAuth client secret | | `companyName` | string | No | Company name to search for | | `companyWebsite` | string | No | Company website \(comma-separated for multiple\) | | `companyTicker` | string | No | Stock ticker symbols — JSON array, comma-separated list, or single ticker. Sent to the API as an array. | @@ -75,8 +73,6 @@ Search ZoomInfo for contacts (people) by name, job title, company, and other fil | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `clientId` | string | Yes | ZoomInfo OAuth client ID | -| `clientSecret` | string | Yes | ZoomInfo OAuth client secret | | `firstName` | string | No | First name | | `lastName` | string | No | Last name | | `fullName` | string | No | Full name | @@ -115,8 +111,6 @@ Enrich up to 25 companies in one request with detailed firmographics, industry, | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `clientId` | string | Yes | ZoomInfo OAuth client ID | -| `clientSecret` | string | Yes | ZoomInfo OAuth client secret | | `matchCompanyInput` | string | Yes | JSON array \(1-25 items\) of company matching criteria, e.g. \[\{"companyName":"Acme","companyWebsite":"acme.com"\}\] | | `outputFields` | string | No | JSON array or comma-separated list of fields to return \(e.g. \["id","name","website","revenue","employeeCount"\]\). Defaults to a standard firmographic set if omitted. | @@ -138,8 +132,6 @@ Enrich up to 25 contacts in one request with verified emails, phone numbers, job | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `clientId` | string | Yes | ZoomInfo OAuth client ID | -| `clientSecret` | string | Yes | ZoomInfo OAuth client secret | | `matchPersonInput` | string | Yes | JSON array \(1-25 items\) of contact matching criteria, e.g. \[\{"firstName":"Jane","lastName":"Doe","companyName":"Acme"\}\] | | `outputFields` | string | No | JSON array or comma-separated list of fields to return \(e.g. \["id","firstName","email","phone","jobTitle"\]\). Defaults to a standard contact set if omitted. | | `requiredFields` | string | No | JSON array or comma-separated list of fields that must exist in results \(e.g. \["email"\]\) | @@ -162,8 +154,6 @@ Search for companies showing intent signals on specific topics. | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `clientId` | string | Yes | ZoomInfo OAuth client ID | -| `clientSecret` | string | Yes | ZoomInfo OAuth client secret | | `topics` | string | Yes | Up to 50 intent topics as JSON array or comma-separated list \(e.g. \["CRM Software","Marketing Automation"\]\) | | `signalStartDate` | string | No | Earliest signal date \(YYYY-MM-DD\) | | `signalEndDate` | string | No | Latest signal date \(YYYY-MM-DD\) | @@ -199,8 +189,6 @@ Search ZoomInfo news articles by category, URL, or date range. | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `clientId` | string | Yes | ZoomInfo OAuth client ID | -| `clientSecret` | string | Yes | ZoomInfo OAuth client secret | | `categories` | string | No | News categories as JSON array or comma-separated list | | `url` | string | No | News source URLs as JSON array or comma-separated list | | `pageDateMin` | string | No | Earliest publish date \(YYYY-MM-DD\) | diff --git a/apps/docs/content/docs/platform/self-hosting/environment-variables.mdx b/apps/docs/content/docs/platform/self-hosting/environment-variables.mdx index 9261787a85d..11412054792 100644 --- a/apps/docs/content/docs/platform/self-hosting/environment-variables.mdx +++ b/apps/docs/content/docs/platform/self-hosting/environment-variables.mdx @@ -167,7 +167,7 @@ Saved AWS, Databricks, Snowflake, Microsoft app, and Google Cloud credentials ca | Variable | Description | | --- | --- | | `WORKLOAD_IDENTITY_SIGNING_KEY` | PKCS#8 PEM RSA private key that signs Sim's workload-identity tokens: `openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:2048` | -| `WORKLOAD_IDENTITY_ISSUER` | Issuer URL clouds trust. Defaults to `/oidc/workload`; must be `https` on the default port, with no query or fragment, and reachable from the public internet, or keyless stays off. Never change it once trust policies reference it. | +| `WORKLOAD_IDENTITY_ISSUER` | Issuer URL clouds trust. Defaults to `/oidc/workload`; must be `https` on the default port, with no query or fragment, and reachable from the public internet, or keyless stays off. A pinned issuer may use another host that reaches Sim; its path should end in `/oidc/workload`, where Sim serves discovery and keys, and any other path works only if a proxy routes it there (Sim logs a warning). Never change it once trust policies reference it. | | `WORKLOAD_IDENTITY_PREVIOUS_KEY` | A second published key (private or public PEM) that never signs: the incoming key before a rotation, or the retiring key after it | See [AWS credentials](/integrations/aws-credentials#self-hosted-deployments) for setup and the rotation steps. diff --git a/apps/docs/content/integration-navigation.json b/apps/docs/content/integration-navigation.json index 17a8067e6a1..1f757240e19 100644 --- a/apps/docs/content/integration-navigation.json +++ b/apps/docs/content/integration-navigation.json @@ -47,7 +47,7 @@ }, "hubspot-service-account": { "integration": "hubspot", - "title": "Private app tokens" + "title": "Service keys and private app tokens" }, "hubspot-setup": { "integration": "hubspot", diff --git a/apps/docs/openapi-v2-billing.json b/apps/docs/openapi-v2-billing.json index fabcf04f983..e56e81b0ced 100644 --- a/apps/docs/openapi-v2-billing.json +++ b/apps/docs/openapi-v2-billing.json @@ -39,7 +39,7 @@ "get": { "operationId": "getBillingStatus", "summary": "Get billing status", - "description": "Get the current plan, billing standing, credit allowance, and storage quota. Pooled `credits` and `storage` are visible only to callers who can manage the payer's billing; workspace API keys receive null for both. Use List billing logs for credit history.\n\nOAuth scope: `api:read`.", + "description": "Get the current plan, billing standing, credit allowance, storage quota, and plan limits on runs. Pooled `credits` and `storage` are visible only to callers who can manage the payer's billing; workspace API keys receive null for both, but every caller reads `limits`. Use List billing logs for credit history.\n\nOAuth scope: `api:read`.", "x-sim-operation": "billing.status.read", "x-oauth-scope": "api:read", "tags": ["Billing"], @@ -105,6 +105,11 @@ "usedBytes": 5242880, "limitBytes": 1073741824, "percentUsed": 0.48828125 + }, + "limits": { + "maxSyncExecutionTimeoutSeconds": 3000, + "maxAsyncExecutionTimeoutSeconds": 5400, + "maxConcurrentExecutions": 50 } } } @@ -596,6 +601,10 @@ "description": "Other structured context defined by the specific error." } ] + }, + "requestId": { + "description": "Identifier of this request, the same id as the `x-request-id` response header. Quote it when contacting support.", + "type": "string" } }, "required": ["code", "message"], @@ -713,12 +722,61 @@ } ], "description": "The payer's storage consumption and quota, or null when the caller cannot manage that payer's billing. Always null for a workspace API key." + }, + "limits": { + "type": "object", + "properties": { + "maxSyncExecutionTimeoutSeconds": { + "anyOf": [ + { + "type": "number", + "exclusiveMinimum": 0 + }, + { + "type": "null" + } + ], + "description": "Longest a synchronous run may execute, in seconds, or null when synchronous runs are not time-limited." + }, + "maxAsyncExecutionTimeoutSeconds": { + "anyOf": [ + { + "type": "number", + "exclusiveMinimum": 0 + }, + { + "type": "null" + } + ], + "description": "Longest an asynchronous run may execute, in seconds, or null when asynchronous runs are not time-limited. `executionTimeoutSeconds` on Execute workflow can lower it, never raise it." + }, + "maxConcurrentExecutions": { + "anyOf": [ + { + "type": "integer", + "exclusiveMinimum": 0, + "maximum": 9007199254740991 + }, + { + "type": "null" + } + ], + "description": "Most runs the payer can have executing at once, across every workspace and member it funds, or null when concurrency is not limited." + } + }, + "required": [ + "maxSyncExecutionTimeoutSeconds", + "maxAsyncExecutionTimeoutSeconds", + "maxConcurrentExecutions" + ], + "additionalProperties": false, + "description": "The payer's plan caps on workflow runs. Visible to every caller that can read the plan, including workspace API keys." } }, - "required": ["workspaceId", "period", "plan", "status", "credits", "storage"], + "required": ["workspaceId", "period", "plan", "status", "credits", "storage", "limits"], "additionalProperties": false, "title": "Billing status", - "description": "Current billing standing, credit allowance, and storage quota." + "description": "Current billing standing, credit allowance, storage quota, and plan limits." }, "V2BillingStatusResponse": { "type": "object", @@ -751,6 +809,11 @@ "usedBytes": 5242880, "limitBytes": 1073741824, "percentUsed": 0.48828125 + }, + "limits": { + "maxSyncExecutionTimeoutSeconds": 3000, + "maxAsyncExecutionTimeoutSeconds": 5400, + "maxConcurrentExecutions": 50 } } } diff --git a/apps/docs/openapi-v2-files-audit.json b/apps/docs/openapi-v2-files-audit.json index d28f2df00a7..154b5ae207e 100644 --- a/apps/docs/openapi-v2-files-audit.json +++ b/apps/docs/openapi-v2-files-audit.json @@ -4743,6 +4743,10 @@ "description": "Other structured context defined by the specific error." } ] + }, + "requestId": { + "description": "Identifier of this request, the same id as the `x-request-id` response header. Quote it when contacting support.", + "type": "string" } }, "required": ["code", "message"], diff --git a/apps/docs/openapi-v2-knowledge.json b/apps/docs/openapi-v2-knowledge.json index 75f473cb683..ae3f904261c 100644 --- a/apps/docs/openapi-v2-knowledge.json +++ b/apps/docs/openapi-v2-knowledge.json @@ -6048,6 +6048,10 @@ "description": "Other structured context defined by the specific error." } ] + }, + "requestId": { + "description": "Identifier of this request, the same id as the `x-request-id` response header. Quote it when contacting support.", + "type": "string" } }, "required": ["code", "message"], diff --git a/apps/docs/openapi-v2-logs.json b/apps/docs/openapi-v2-logs.json index 58c1c1e947f..ab1cac9efb9 100644 --- a/apps/docs/openapi-v2-logs.json +++ b/apps/docs/openapi-v2-logs.json @@ -1095,6 +1095,10 @@ "description": "Other structured context defined by the specific error." } ] + }, + "requestId": { + "description": "Identifier of this request, the same id as the `x-request-id` response header. Quote it when contacting support.", + "type": "string" } }, "required": ["code", "message"], diff --git a/apps/docs/openapi-v2-resources.json b/apps/docs/openapi-v2-resources.json index e5a56c40eb0..86126938320 100644 --- a/apps/docs/openapi-v2-resources.json +++ b/apps/docs/openapi-v2-resources.json @@ -300,6 +300,121 @@ "$ref": "#/components/responses/ServiceUnavailable" } } + }, + "patch": { + "operationId": "updateWorkspace", + "summary": "Update workspace", + "description": "Rename a workspace or change its logo. Omitted fields remain unchanged; send `logoUrl: null` to remove the logo. Requires workspace Admin access. Workspace API keys return `403`; use a personal API key or scoped OAuth token.\n\nOAuth scope: `api:write`.", + "x-sim-operation": "workspaces.identity.update", + "x-oauth-scope": "api:write", + "tags": ["Workspaces"], + "security": [ + { + "apiKey": [] + }, + { + "oauthBearer": ["api:write"] + } + ], + "parameters": [ + { + "name": "workspaceId", + "in": "path", + "required": true, + "description": "Workspace to update.", + "schema": { + "type": "string", + "minLength": 1, + "maxLength": 128, + "description": "Workspace to update." + } + } + ], + "requestBody": { + "required": true, + "description": "The workspace fields to change.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdateWorkspaceBody" + }, + "examples": { + "example1": { + "summary": "Request example 1", + "value": { + "name": "Platform Engineering" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "The updated workspace.", + "headers": { + "X-RateLimit-Limit": { + "$ref": "#/components/headers/X-RateLimit-Limit" + }, + "X-RateLimit-Remaining": { + "$ref": "#/components/headers/X-RateLimit-Remaining" + }, + "X-RateLimit-Reset": { + "$ref": "#/components/headers/X-RateLimit-Reset" + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdateWorkspaceResponse" + }, + "examples": { + "example1": { + "summary": "Response example 1", + "value": { + "data": { + "id": "a91c4b2e-6d3f-4e8a-b5c7-0d9e2f1a8c64", + "name": "Platform Engineering", + "color": "#33C482", + "logoUrl": null, + "memberCount": 14, + "createdAt": "2026-01-15T10:30:00.000Z", + "updatedAt": "2026-06-20T14:02:11.000Z" + } + } + } + } + } + } + }, + "400": { + "$ref": "#/components/responses/BadRequest" + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "403": { + "$ref": "#/components/responses/Forbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "413": { + "$ref": "#/components/responses/PayloadTooLarge" + }, + "415": { + "$ref": "#/components/responses/UnsupportedMediaType" + }, + "429": { + "$ref": "#/components/responses/RateLimited" + }, + "500": { + "$ref": "#/components/responses/InternalError" + }, + "503": { + "$ref": "#/components/responses/ServiceUnavailable" + } + } } }, "/api/v2/workspaces/{workspaceId}/members": { @@ -4221,6 +4336,7 @@ "value": { "data": { "authorizationUrl": "https://www.sim.ai/api/auth/oauth2/authorize?draftId=draft-123", + "draftId": "draft-123", "expiresAt": "2026-06-20T14:17:11.000Z" } } @@ -5066,7 +5182,7 @@ "get": { "operationId": "getApiMeta", "summary": "Get API status", - "description": "Get the calling credential’s type, expiry, and API scopes. Requires a valid API key or OAuth access token. `v2Enabled` is true whenever this endpoint is served; the response does not enumerate resource permissions.\n\nOAuth scope: `api:read`.", + "description": "Get the calling credential’s type, identity, expiry, and API scopes, plus links to the OpenAPI document, API docs, and Sim MCP server. Requires a valid API key or OAuth access token. `v2Enabled` is true whenever this endpoint is served; the response does not enumerate resource permissions.\n\nOAuth scope: `api:read`.", "x-sim-operation": "meta.capabilities.read", "x-oauth-scope": "api:read", "tags": ["Meta"], @@ -5080,7 +5196,7 @@ ], "responses": { "200": { - "description": "Availability and lifecycle facts about the calling credential.", + "description": "Identity, availability, and lifecycle facts about the calling credential.", "headers": { "X-RateLimit-Limit": { "$ref": "#/components/headers/X-RateLimit-Limit" @@ -5105,7 +5221,15 @@ "v2Enabled": true, "keyType": "personal", "expiresAt": null, - "scopes": ["api:read", "api:write", "search:read"] + "scopes": ["api:read", "api:write", "search:read"], + "keyId": "key_7f3a9c2e", + "userId": "user-123", + "workspaceId": null, + "links": { + "openapi": "https://docs.sim.ai/openapi.json", + "docs": "https://docs.sim.ai/api-reference/getting-started", + "mcp": "https://mcp.sim.ai/mcp" + } } } } @@ -14957,6 +15081,10 @@ "description": "Other structured context defined by the specific error." } ] + }, + "requestId": { + "description": "Identifier of this request, the same id as the `x-request-id` response header. Quote it when contacting support.", + "type": "string" } }, "required": ["code", "message"], @@ -15098,6 +15226,64 @@ } ] }, + "UpdateWorkspaceResponse": { + "type": "object", + "properties": { + "data": { + "description": "Response data.", + "$ref": "#/components/schemas/V2Workspace" + } + }, + "required": ["data"], + "additionalProperties": false, + "title": "Update workspace response", + "description": "The workspace after the update.", + "examples": [ + { + "data": { + "id": "a91c4b2e-6d3f-4e8a-b5c7-0d9e2f1a8c64", + "name": "Platform Engineering", + "color": "#33C482", + "logoUrl": null, + "memberCount": 14, + "createdAt": "2026-01-15T10:30:00.000Z", + "updatedAt": "2026-06-20T14:02:11.000Z" + } + } + ] + }, + "UpdateWorkspaceBody": { + "type": "object", + "properties": { + "name": { + "description": "New workspace display name.", + "type": "string", + "minLength": 1, + "maxLength": 255 + }, + "logoUrl": { + "description": "New workspace logo URL: a root-relative path (starting with /) or an https:// URL. Send null to remove the logo.", + "anyOf": [ + { + "type": "string", + "pattern": "^(\\/$|\\/[^/]|https:\\/\\/)" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false, + "minProperties": 1, + "title": "Update workspace body", + "description": "The workspace fields to change.", + "examples": [ + { + "name": "Platform Engineering" + } + ] + }, "V2WorkspaceMember": { "type": "object", "properties": { @@ -18155,6 +18341,10 @@ "format": "uri", "description": "Short-lived Sim browser URL that starts the OAuth authorization flow." }, + "draftId": { + "type": "string", + "description": "ID of the pending connection behind authorizationUrl. Only the user who created it can complete it." + }, "expiresAt": { "type": "string", "format": "date-time", @@ -18162,7 +18352,7 @@ "description": "ISO 8601 timestamp when the connection link expires." } }, - "required": ["authorizationUrl", "expiresAt"], + "required": ["authorizationUrl", "draftId", "expiresAt"], "additionalProperties": false, "title": "Credential connection authorization", "description": "A short-lived browser entrypoint for an OAuth connection flow." @@ -18183,6 +18373,7 @@ { "data": { "authorizationUrl": "https://www.sim.ai/api/auth/oauth2/authorize?draftId=draft-123", + "draftId": "draft-123", "expiresAt": "2026-06-20T14:17:11.000Z" } } @@ -18933,12 +19124,74 @@ "enum": ["api:read", "api:write", "search:read"] }, "description": "API scopes the calling credential can use. api:write implies api:read, and either implies search:read. A read-only API key holds api:read and search:read: it is refused every operation that changes state and every operation that declares no API scope, so api:read alone does not authorize those. A full-access key holds all three; an OAuth access token holds what it was granted and what that implies." + }, + "keyId": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "ID of the calling API key, or null for an OAuth access token." + }, + "userId": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "ID of the user the credential acts for, or null for a workspace API key, which acts as its workspace rather than as a user." + }, + "workspaceId": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "The only workspace a workspace API key can reach, or null for personal API keys and OAuth access tokens, which reach every workspace their user can." + }, + "links": { + "type": "object", + "properties": { + "openapi": { + "type": "string", + "description": "URL of the OpenAPI document for this API." + }, + "docs": { + "type": "string", + "description": "URL of the API reference documentation." + }, + "mcp": { + "type": "string", + "description": "URL of the Sim MCP server." + } + }, + "required": ["openapi", "docs", "mcp"], + "additionalProperties": false, + "description": "Where to find the API description, its documentation, and the Sim MCP server." } }, - "required": ["v2Enabled", "keyType", "expiresAt", "scopes"], + "required": [ + "v2Enabled", + "keyType", + "expiresAt", + "scopes", + "keyId", + "userId", + "workspaceId", + "links" + ], "additionalProperties": false, "title": "API status", - "description": "API availability and lifecycle facts about the calling credential." + "description": "Identity, API availability, and lifecycle facts about the calling credential." }, "GetApiMetaResponse": { "type": "object", @@ -18951,14 +19204,22 @@ "required": ["data"], "additionalProperties": false, "title": "API status response", - "description": "API availability, credential type, expiry, and scopes for the caller.", + "description": "API availability, credential type and identity, expiry, scopes, and discovery links for the caller.", "examples": [ { "data": { "v2Enabled": true, "keyType": "personal", "expiresAt": null, - "scopes": ["api:read", "api:write", "search:read"] + "scopes": ["api:read", "api:write", "search:read"], + "keyId": "key_7f3a9c2e", + "userId": "user-123", + "workspaceId": null, + "links": { + "openapi": "https://docs.sim.ai/openapi.json", + "docs": "https://docs.sim.ai/api-reference/getting-started", + "mcp": "https://mcp.sim.ai/mcp" + } } } ] @@ -20031,7 +20292,12 @@ }, "type": { "type": "string", - "description": "Editor control the field renders as, e.g. `short-input`." + "description": "Editor control the field renders as, e.g. `short-input`. Not a value type; see `valueType`." + }, + "valueType": { + "description": "Type of value the field holds, from the block’s declared input for the field (the same type `inputDefinitions` and operation `inputs` report). Absent when the block declares no input for the field.", + "type": "string", + "enum": ["string", "number", "boolean", "json", "array", "file"] }, "title": { "description": "Human-readable label.", diff --git a/apps/docs/openapi-v2-tables.json b/apps/docs/openapi-v2-tables.json index 509b1ef430d..44dfcb42fc4 100644 --- a/apps/docs/openapi-v2-tables.json +++ b/apps/docs/openapi-v2-tables.json @@ -6007,6 +6007,10 @@ "description": "Other structured context defined by the specific error." } ] + }, + "requestId": { + "description": "Identifier of this request, the same id as the `x-request-id` response header. Quote it when contacting support.", + "type": "string" } }, "required": ["code", "message"], diff --git a/apps/docs/openapi-v2-workflows.json b/apps/docs/openapi-v2-workflows.json index d18c6fee64d..27407f0f794 100644 --- a/apps/docs/openapi-v2-workflows.json +++ b/apps/docs/openapi-v2-workflows.json @@ -595,11 +595,114 @@ } } }, + "/api/v2/workflows/{workflowId}/outline": { + "get": { + "operationId": "getWorkflowOutline", + "summary": "Get workflow outline", + "description": "Read the saved draft as a compact outline: connections embedded in their source blocks, loop and parallel children nested under their container, and null values omitted. Fields hidden from agents are omitted too, some of them real configuration, as are blocks whose parent container no longer exists; compare with Get workflow state for the full configuration. Requires workspace read access, and reveals nothing Get workflow state does not: its derived fields, such as `triggerWebhookUrl`, are computed from values that state already returns. The outline is read-only: it adds those derived fields and omits positions and edge handles, so never send it to Replace workflow state; edit with Apply workflow operations or a full state from Get workflow state. This read records no audit event; `HEAD` mirrors `GET`.\n\nOAuth scope: `api:read`.", + "x-sim-operation": "workflows.read", + "x-oauth-scope": "api:read", + "tags": ["Workflows"], + "security": [ + { + "apiKey": [] + }, + { + "oauthBearer": ["api:read"] + } + ], + "parameters": [ + { + "name": "workflowId", + "in": "path", + "required": true, + "description": "Unique workflow identifier.", + "schema": { + "type": "string", + "minLength": 1, + "description": "Unique workflow identifier.", + "examples": ["3b1f7c92-8d4e-4a6b-9c0d-5e2f8a714b36"] + } + } + ], + "responses": { + "200": { + "description": "The workflow draft outline.", + "headers": { + "X-RateLimit-Limit": { + "$ref": "#/components/headers/X-RateLimit-Limit" + }, + "X-RateLimit-Remaining": { + "$ref": "#/components/headers/X-RateLimit-Remaining" + }, + "X-RateLimit-Reset": { + "$ref": "#/components/headers/X-RateLimit-Reset" + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/WorkflowOutlineResponse" + }, + "examples": { + "example1": { + "summary": "Response example 1", + "value": { + "data": { + "blocks": { + "start-1": { + "type": "starter", + "name": "Start", + "enabled": true, + "connections": { + "source": "agent-1" + } + }, + "agent-1": { + "type": "agent", + "name": "Agent 1", + "enabled": true, + "inputs": { + "systemPrompt": "Route the ticket to the right team." + } + } + } + } + } + } + } + } + } + }, + "400": { + "$ref": "#/components/responses/BadRequest" + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "403": { + "$ref": "#/components/responses/Forbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "429": { + "$ref": "#/components/responses/RateLimited" + }, + "500": { + "$ref": "#/components/responses/InternalError" + }, + "503": { + "$ref": "#/components/responses/ServiceUnavailable" + } + } + } + }, "/api/v2/workflows/{workflowId}/operations": { "post": { "operationId": "applyWorkflowOperations", "summary": "Apply workflow operations", - "description": "Apply draft edits and block enablement changes in one write. Requires workspace write access. The live deployment is unchanged; lint is advisory.\n\nInspect `skipped` and `inputValidationErrors` for edits that did not apply. Deferred edges apply when their targets exist; do not resubmit them. With `atomic=true`, declined operations or dropped inputs return `409` (`OPERATIONS_NOT_APPLIED`) without saving. Use returned `mintedBlockIds` in later requests. `dryRun=true` evaluates without saving, auditing, or notifying. Workspace API keys return `403`; use a personal API key or scoped OAuth token.\n\nOAuth scope: `api:write`.", + "description": "Apply draft edits and block enablement changes in one write. Requires workspace write access. The live deployment is unchanged; lint is advisory.\n\nInspect `skipped` and `inputValidationErrors` for edits that did not apply. Deferred edges apply when their targets exist; do not resubmit them. With `atomic=true`, declined operations or dropped inputs return `409` (`OPERATIONS_NOT_APPLIED`) without saving. A `409` with code `CONFLICT` means the workflow changed after the batch read it and nothing was saved; re-read the workflow and retry. Use returned `mintedBlockIds` in later requests. `dryRun=true` evaluates without saving, auditing, or notifying. Workspace API keys return `403`; use a personal API key or scoped OAuth token.\n\nOAuth scope: `api:write`.", "x-sim-operation": "workflows.operations.apply", "x-oauth-scope": "api:write", "tags": ["Workflows"], @@ -1237,7 +1340,7 @@ "get": { "operationId": "getWorkflow", "summary": "Get workflow", - "description": "Get workflow metadata, variables, and trigger input fields from the saved draft. Requires workspace read access. Input fields may differ from the live deployment. Workspace folder trees exceeding 10,000 folders return `413`.\n\nOAuth scope: `api:read`.", + "description": "Get workflow metadata, variables, and trigger input fields from the saved draft. Requires workspace read access. Input fields may differ from the live deployment; `inputSchema` describes the `input` an API run of the active deployment accepts. Workspace folder trees exceeding 10,000 folders return `413`.\n\nOAuth scope: `api:read`.", "x-sim-operation": "workflows.read", "x-oauth-scope": "api:read", "tags": ["Workflows"], @@ -1300,7 +1403,21 @@ "createdAt": "2026-05-01T09:00:00.000Z", "updatedAt": "2026-08-09T18:04:11.000Z", "variables": {}, - "inputs": [] + "inputs": [ + { + "name": "ticket", + "type": "string" + } + ], + "inputSchema": { + "type": "object", + "properties": { + "ticket": { + "type": "string" + } + }, + "required": ["ticket"] + } } } } @@ -2669,7 +2786,7 @@ "patch": { "operationId": "updateWorkflowTrigger", "summary": "Update workflow trigger", - "description": "Enable or disable one trigger of the live deployment without redeploying. Requires workspace write access. Enabling resets `failedCount`, schedules the next run, and clears any polling backoff; a schedule whose cron expression is invalid or has no future runs returns `400`. A permission group that blocks webhook triggers returns `403` when a member it governs enables a polling, internal-event, or webhook trigger; a workspace API key is not governed by permission groups, as with every other capability. Disabling a completed schedule marks it disabled. A block with several live rows switches them together. Requesting the current state changes nothing.\n\nDisabling only stops Sim from accepting the trigger; nothing is removed at the provider. A disable lasts until the next deploy or version activation, which re-enables every trigger, the same way it clears a trigger that disabled itself after repeated failures. Deliveries to a disabled webhook receive `404`, and some providers disable their subscription after repeated failures, which requires a redeploy to restore.\n\nOAuth scope: `api:write`.", + "description": "Enable or disable one trigger of the live deployment without redeploying. Requires workspace write access. Enabling resets `failedCount`, schedules the next run, and clears any polling backoff; a schedule whose cron expression is invalid or has no future runs returns `400`. A permission group that blocks webhook triggers returns `403` when a member it governs enables a polling, internal-event, or webhook trigger; a workspace API key is not governed by permission groups, as with every other capability. Disabling a completed schedule marks it disabled. A block with several live rows switches them together. Requesting the current state changes nothing.\n\nDisabling only stops Sim from accepting the trigger; nothing is removed at the provider. A disable lasts until the next deploy or version activation, which re-enables every trigger, the same way it clears a trigger that disabled itself after repeated failures. Deliveries to a disabled webhook receive `404`, and some providers disable their subscription after repeated failures, which requires a redeploy to restore.\n\nA schedule's cron expression and timezone cannot be changed here: deploy computes them from the schedule block, and a deployed version is never edited. Change the block's schedule fields in the draft with Apply workflow operations, then deploy.\n\nOAuth scope: `api:write`.", "x-sim-operation": "workflows.triggers.set_enabled", "x-oauth-scope": "api:write", "tags": ["Workflows"], @@ -2709,7 +2826,7 @@ ], "requestBody": { "required": true, - "description": "Enable or disable one trigger of the live deployment.", + "description": "Enable or disable one trigger of the live deployment. A schedule's timing is not set here: it comes from the deployed schedule block, so edit that block in the draft and deploy.", "content": { "application/json": { "schema": { @@ -2805,6 +2922,153 @@ } } }, + "/api/v2/schedules": { + "get": { + "operationId": "listWorkspaceSchedules", + "summary": "List workspace schedules", + "description": "List the schedules every deployed workflow in a workspace runs, including disabled ones, with their timing and failure counts. Requires workspace read access. These are the same schedules List workflow triggers reports for each workflow; a workflow that is not deployed has none. To change a schedule's timing, edit its schedule block in the draft and deploy; to pause or resume one, use Update workflow trigger.\n\nOAuth scope: `api:read`.", + "x-sim-operation": "workflows.triggers.read", + "x-oauth-scope": "api:read", + "tags": ["Workflows"], + "security": [ + { + "apiKey": [] + }, + { + "oauthBearer": ["api:read"] + } + ], + "parameters": [ + { + "name": "workspaceId", + "in": "query", + "required": true, + "description": "Workspace whose schedules should be listed.", + "schema": { + "type": "string", + "minLength": 1, + "maxLength": 128, + "description": "Workspace whose schedules should be listed." + } + }, + { + "name": "limit", + "in": "query", + "required": false, + "description": "Maximum schedules to return per page. Must be a whole number from 1 to 100. Defaults to 50.", + "schema": { + "default": 50, + "description": "Maximum schedules to return per page. Must be a whole number from 1 to 100. Defaults to 50.", + "type": "integer", + "minimum": 1, + "maximum": 100 + } + }, + { + "name": "cursor", + "in": "query", + "required": false, + "description": "Opaque cursor from the previous page. Send it back with the same sort and filters; only `limit` may change. Change anything else and pagination must restart without a cursor.", + "schema": { + "description": "Opaque cursor from the previous page. Send it back with the same sort and filters; only `limit` may change. Change anything else and pagination must restart without a cursor.", + "type": "string", + "minLength": 1 + } + }, + { + "name": "sortBy", + "in": "query", + "required": false, + "description": "Field used to sort the result. `createdAt` is when the schedule was created for the deployment version that runs it: each deploy creates new schedule rows, so a redeployed workflow's schedules move to the newest end.", + "schema": { + "default": "createdAt", + "description": "Field used to sort the result. `createdAt` is when the schedule was created for the deployment version that runs it: each deploy creates new schedule rows, so a redeployed workflow's schedules move to the newest end.", + "type": "string", + "enum": ["createdAt"] + } + }, + { + "name": "sortOrder", + "in": "query", + "required": false, + "description": "Sort direction.", + "schema": { + "default": "desc", + "description": "Sort direction.", + "type": "string", + "enum": ["asc", "desc"] + } + } + ], + "responses": { + "200": { + "description": "A page of schedules.", + "headers": { + "X-RateLimit-Limit": { + "$ref": "#/components/headers/X-RateLimit-Limit" + }, + "X-RateLimit-Remaining": { + "$ref": "#/components/headers/X-RateLimit-Remaining" + }, + "X-RateLimit-Reset": { + "$ref": "#/components/headers/X-RateLimit-Reset" + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/WorkspaceScheduleListResponse" + }, + "examples": { + "example1": { + "summary": "Response example 1", + "value": { + "data": [ + { + "workflowId": "3b1f7c92-8d4e-4a6b-9c0d-5e2f8a714b36", + "workflowName": "Customer support triage", + "blockId": "blk_01J8ZK3QW4M6X2R9T7B5C0V4", + "enabled": true, + "status": "active", + "failedCount": 0, + "lastFailedAt": null, + "nextRunAt": "2026-08-10T09:00:00.000Z", + "lastRanAt": "2026-08-10T08:00:00.000Z", + "cronExpression": "0 * * * *", + "timezone": "UTC" + } + ], + "nextCursor": null + } + } + } + } + } + }, + "400": { + "$ref": "#/components/responses/BadRequest" + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "403": { + "$ref": "#/components/responses/Forbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "429": { + "$ref": "#/components/responses/RateLimited" + }, + "500": { + "$ref": "#/components/responses/InternalError" + }, + "503": { + "$ref": "#/components/responses/ServiceUnavailable" + } + } + } + }, "/api/v2/workflows/{workflowId}/deploy": { "post": { "operationId": "deployWorkflow", @@ -4129,7 +4393,8 @@ "error": null, "startedAt": "2026-08-09T18:04:10.000Z", "endedAt": "2026-08-09T18:04:11.000Z", - "durationMs": 1000 + "durationMs": 1000, + "executionTimeoutSeconds": 3000 } } } @@ -4174,7 +4439,8 @@ "value": { "data": { "runId": "run_8f14e45f-ceea-467f-a", - "statusUrl": "https://www.sim.ai/api/v2/workflows/3b1f7c92-8d4e-4a6b-9c0d-5e2f8a714b36/runs/run_8f14e45f-ceea-467f-a" + "statusUrl": "https://www.sim.ai/api/v2/workflows/3b1f7c92-8d4e-4a6b-9c0d-5e2f8a714b36/runs/run_8f14e45f-ceea-467f-a", + "executionTimeoutSeconds": 5400 } } } @@ -4401,12 +4667,12 @@ } } }, - "/api/v2/workflows/{workflowId}/runs/{runId}": { + "/api/v2/workflows/runs/paused": { "get": { - "operationId": "getWorkflowRunV2", - "summary": "Get workflow run", - "description": "Read current run state with workspace read access. Request final output and files with `includeOutput`, or block outputs with `selectedOutputs`. Organization permission groups can hide output, files, or cost. A paused run lists every pause point in `paused.points`, with the fields a human pause asks for and its content with resolved secrets redacted; resume one with Resume workflow run.\n\n`includeFileBase64` inlines file bytes. A file or the total above 16 MiB returns `413` naming a download path; download large files individually. `HEAD` checks access with the same authorization as `GET` but skips side effects, returning an empty `200` without payload headers on success.\n\nOAuth scope: `api:read`.", - "x-sim-operation": "workflows.runs.read", + "operationId": "listPausedWorkflowRunsV2", + "summary": "List paused workflow runs", + "description": "List a workspace's runs waiting on human input, with workspace read access. Each run lists the human pause points it still waits at, exactly as Get workflow run shows them; resume one with Resume workflow run. Request each point's reviewer content with `includeOutput`. Organization permission groups can hide pause fields. A page ends early, with a cursor, once it holds 100 pause points.\n\nOAuth scope: `api:read`.", + "x-sim-operation": "workflows.runs.list_paused", "x-oauth-scope": "api:read", "tags": ["Workflow Runs"], "security": [ @@ -4419,45 +4685,218 @@ ], "parameters": [ { - "name": "workflowId", - "in": "path", - "required": true, - "description": "Unique workflow identifier.", - "schema": { - "type": "string", - "minLength": 1, - "description": "Unique workflow identifier." - } - }, - { - "name": "runId", - "in": "path", + "name": "workspaceId", + "in": "query", "required": true, - "description": "Unique workflow run identifier.", + "description": "Workspace whose paused runs should be listed.", "schema": { "type": "string", "minLength": 1, "maxLength": 128, - "pattern": "^[A-Za-z0-9._:-]+$", - "description": "Unique workflow run identifier.", - "examples": ["run_8f14e45f-ceea-467f-a"] + "description": "Workspace whose paused runs should be listed." } }, { "name": "includeOutput", "in": "query", "required": false, - "description": "Include the final workflow output when true. It does not gate `blockOutputs`, which `selectedOutputs` selects on its own.", + "description": "Include each pause point's reviewer `display` content when true.", "schema": { - "description": "Include the final workflow output when true. It does not gate `blockOutputs`, which `selectedOutputs` selects on its own.", + "description": "Include each pause point's reviewer `display` content when true.", "type": "boolean" } }, { - "name": "selectedOutputs", + "name": "limit", "in": "query", "required": false, - "description": "Comma-separated block output references to include, as `blockId` or `blockId.path`. Block *names* are not resolved here — unlike the execute request, this resource reads a recorded run and matches ids only, so a selector that is not headed by a block id answers `400` instead of an empty `blockOutputs`.", + "description": "Maximum runs to return per page. A page also ends early once it holds 100 pause points, so it can return fewer runs while `nextCursor` is set. Must be a whole number from 1 to 25. Defaults to 10.", + "schema": { + "default": 10, + "description": "Maximum runs to return per page. A page also ends early once it holds 100 pause points, so it can return fewer runs while `nextCursor` is set. Must be a whole number from 1 to 25. Defaults to 10.", + "type": "integer", + "minimum": 1, + "maximum": 25 + } + }, + { + "name": "cursor", + "in": "query", + "required": false, + "description": "Opaque cursor from the previous page. Send it back with the same sort and filters; only `limit` may change. Change anything else and pagination must restart without a cursor.", + "schema": { + "description": "Opaque cursor from the previous page. Send it back with the same sort and filters; only `limit` may change. Change anything else and pagination must restart without a cursor.", + "type": "string", + "minLength": 1 + } + }, + { + "name": "sortBy", + "in": "query", + "required": false, + "description": "Field used to sort the result.", + "schema": { + "default": "pausedAt", + "description": "Field used to sort the result.", + "type": "string", + "enum": ["pausedAt"] + } + }, + { + "name": "sortOrder", + "in": "query", + "required": false, + "description": "Sort direction.", + "schema": { + "default": "desc", + "description": "Sort direction.", + "type": "string", + "enum": ["asc", "desc"] + } + } + ], + "responses": { + "200": { + "description": "A page of paused workflow runs.", + "headers": { + "X-RateLimit-Limit": { + "$ref": "#/components/headers/X-RateLimit-Limit" + }, + "X-RateLimit-Remaining": { + "$ref": "#/components/headers/X-RateLimit-Remaining" + }, + "X-RateLimit-Reset": { + "$ref": "#/components/headers/X-RateLimit-Reset" + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PausedRunListResponse" + }, + "examples": { + "example1": { + "summary": "Response example 1", + "value": { + "data": [ + { + "runId": "run_8f14e45f-ceea-467f-a", + "workflowId": "3b1f7c92-8d4e-4a6b-9c0d-5e2f8a714b36", + "workflowName": "Customer support triage", + "pausedAt": "2026-08-09T18:04:11.000Z", + "points": [ + { + "contextId": "blk_approval", + "blockId": "blk_approval", + "blockName": "Manager approval", + "kind": "human", + "status": "paused", + "registeredAt": "2026-08-09T18:04:11.000Z", + "resumeAt": null, + "parallel": null, + "loop": null, + "inputFields": [ + { + "name": "approved", + "label": "Approved", + "type": "boolean", + "required": true + } + ], + "display": null, + "queuePosition": null + } + ] + } + ], + "nextCursor": null + } + } + } + } + } + }, + "400": { + "$ref": "#/components/responses/BadRequest" + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "403": { + "$ref": "#/components/responses/Forbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "429": { + "$ref": "#/components/responses/RateLimited" + }, + "500": { + "$ref": "#/components/responses/InternalError" + }, + "503": { + "$ref": "#/components/responses/ServiceUnavailable" + } + } + } + }, + "/api/v2/workflows/{workflowId}/runs/{runId}": { + "get": { + "operationId": "getWorkflowRunV2", + "summary": "Get workflow run", + "description": "Read current run state with workspace read access. Request final output and files with `includeOutput`, or block outputs with `selectedOutputs`. Organization permission groups can hide output, files, or cost. A paused run lists every pause point in `paused.points`, with the fields a human pause asks for and its content with resolved secrets redacted; resume one with Resume workflow run.\n\n`includeFileBase64` inlines file bytes. A file or the total above 16 MiB returns `413` naming a download path; download large files individually. `HEAD` checks access with the same authorization as `GET` but skips side effects, returning an empty `200` without payload headers on success.\n\nOAuth scope: `api:read`.", + "x-sim-operation": "workflows.runs.read", + "x-oauth-scope": "api:read", + "tags": ["Workflow Runs"], + "security": [ + { + "apiKey": [] + }, + { + "oauthBearer": ["api:read"] + } + ], + "parameters": [ + { + "name": "workflowId", + "in": "path", + "required": true, + "description": "Unique workflow identifier.", + "schema": { + "type": "string", + "minLength": 1, + "description": "Unique workflow identifier." + } + }, + { + "name": "runId", + "in": "path", + "required": true, + "description": "Unique workflow run identifier.", + "schema": { + "type": "string", + "minLength": 1, + "maxLength": 128, + "pattern": "^[A-Za-z0-9._:-]+$", + "description": "Unique workflow run identifier.", + "examples": ["run_8f14e45f-ceea-467f-a"] + } + }, + { + "name": "includeOutput", + "in": "query", + "required": false, + "description": "Include the final workflow output when true. It does not gate `blockOutputs`, which `selectedOutputs` selects on its own.", + "schema": { + "description": "Include the final workflow output when true. It does not gate `blockOutputs`, which `selectedOutputs` selects on its own.", + "type": "boolean" + } + }, + { + "name": "selectedOutputs", + "in": "query", + "required": false, + "description": "Comma-separated block output references to include, as `blockId` or `blockId.path`. Block *names* are not resolved here — unlike the execute request, this resource reads a recorded run and matches ids only, so a selector that is not headed by a block id answers `400` instead of an empty `blockOutputs`.", "schema": { "description": "Comma-separated block output references to include, as `blockId` or `blockId.path`. Block *names* are not resolved here — unlike the execute request, this resource reads a recorded run and matches ids only, so a selector that is not headed by a block id answers `400` instead of an empty `blockOutputs`.", "type": "string" @@ -7815,6 +8254,10 @@ "description": "Other structured context defined by the specific error." } ] + }, + "requestId": { + "description": "Identifier of this request, the same id as the `x-request-id` response header. Quote it when contacting support.", + "type": "string" } }, "required": ["code", "message"], @@ -8684,45 +9127,195 @@ } ] }, - "WorkflowLintReport": { + "WorkflowOutlineBlock": { "type": "object", "properties": { - "sources": { - "type": "array", - "items": { - "type": "object", - "properties": { - "blockId": { - "type": "string", - "description": "Block the finding is about." - }, - "blockName": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "description": "Display name of the block, when it has one." + "type": { + "type": "string", + "description": "Block type, e.g. `agent` or `slack`." + }, + "name": { + "type": "string", + "description": "Display name; references to this block's outputs use it." + }, + "enabled": { + "type": "boolean", + "description": "Whether the block runs." + }, + "inputs": { + "description": "Configured values keyed by field id, with null values omitted. Fields hidden from agents are omitted as well, and some of them are real configuration, so read Get workflow state for the full set. Loop and parallel containers report their iteration settings here. A webhook trigger also carries a derived `triggerWebhookUrl`, and a credential-routed trigger a derived `triggerRouting`; both are read-only.", + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": { + "description": "Configured value; shape depends on the field." + } + }, + "connections": { + "description": "Outgoing connections keyed by source handle: `source`, `error`, `if` / `else-if-0` / `else` for a Condition, or `route-0`, `route-1`, ... for a Router.", + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": { + "anyOf": [ + { + "type": "string" }, - "blockType": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "description": "Registered type of the block, when it has one." + { + "type": "array", + "items": { + "type": "string" + } } + ], + "description": "Target block id, or ids when the handle fans out." + } + }, + "nestedNodes": { + "description": "Blocks inside this loop or parallel container, keyed by block id.", + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": { + "description": "Child block in this same outline shape." + } + }, + "advancedMode": { + "description": "Whether the block shows its advanced fields.", + "type": "boolean" + }, + "errorEnabled": { + "description": "Whether the block has an error output path.", + "type": "boolean" + }, + "retry": { + "description": "Per-block retry configuration.", + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "Whether the block retries on failure." }, - "required": ["blockId", "blockName", "blockType"], - "additionalProperties": false + "maxTries": { + "type": "number", + "description": "Total attempts, including the first." + }, + "waitBetweenTriesMs": { + "type": "number", + "description": "Delay between attempts, in milliseconds." + } }, - "description": "Blocks with no incoming edge. A trigger block is naturally a source; anything else here is unreachable." + "required": ["enabled", "maxTries", "waitBetweenTriesMs"], + "additionalProperties": false + }, + "triggerMode": { + "description": "Whether the block acts as the workflow trigger.", + "type": "boolean" + } + }, + "required": ["type", "name", "enabled"], + "additionalProperties": false, + "title": "Workflow outline block", + "description": "One block of a workflow outline, with its connections embedded." + }, + "WorkflowOutline": { + "type": "object", + "properties": { + "blocks": { + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": { + "$ref": "#/components/schemas/WorkflowOutlineBlock" + }, + "description": "Top-level blocks keyed by block id. Blocks inside a loop or parallel appear under their container's `nestedNodes`." + } + }, + "required": ["blocks"], + "additionalProperties": false, + "title": "Workflow outline", + "description": "Compact read-only view of a workflow draft. Not a valid Replace workflow state body." + }, + "WorkflowOutlineResponse": { + "type": "object", + "properties": { + "data": { + "description": "Response data.", + "$ref": "#/components/schemas/WorkflowOutline" + } + }, + "required": ["data"], + "additionalProperties": false, + "title": "Workflow outline response", + "description": "The compact read-only outline of a workflow draft.", + "examples": [ + { + "data": { + "blocks": { + "start-1": { + "type": "starter", + "name": "Start", + "enabled": true, + "connections": { + "source": "agent-1" + } + }, + "agent-1": { + "type": "agent", + "name": "Agent 1", + "enabled": true, + "inputs": { + "systemPrompt": "Route the ticket to the right team." + } + } + } + } + } + ] + }, + "WorkflowLintReport": { + "type": "object", + "properties": { + "sources": { + "type": "array", + "items": { + "type": "object", + "properties": { + "blockId": { + "type": "string", + "description": "Block the finding is about." + }, + "blockName": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "Display name of the block, when it has one." + }, + "blockType": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "Registered type of the block, when it has one." + } + }, + "required": ["blockId", "blockName", "blockType"], + "additionalProperties": false + }, + "description": "Blocks with no incoming edge. A trigger block is naturally a source; anything else here is unreachable." }, "sinks": { "type": "array", @@ -11010,6 +11603,39 @@ "title": "Workflow input field", "description": "An input field from an input-definition trigger in the saved draft." }, + "WorkflowApiInputSchema": { + "type": "object", + "properties": { + "type": { + "type": "string", + "description": "JSON Schema type of the `input` object; always `object`." + }, + "properties": { + "description": "Input field schemas keyed by field name.", + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": { + "description": "JSON Schema for one input field." + } + }, + "required": { + "description": "Names of the input fields an API run requires.", + "type": "array", + "items": { + "type": "string", + "description": "Name of a required input field." + } + } + }, + "required": ["type"], + "additionalProperties": { + "description": "Additional JSON Schema keyword." + }, + "title": "Workflow API input schema", + "description": "JSON Schema for the `input` an API run of the active deployment accepts." + }, "WorkflowDetail": { "type": "object", "properties": { @@ -11109,7 +11735,18 @@ "items": { "$ref": "#/components/schemas/WorkflowInputField" }, - "description": "Input fields from the saved draft's input-definition trigger; they may differ from the live deployment." + "description": "Input fields from the saved draft's input-definition trigger; they may differ from the live deployment. Use `inputSchema` for what an API run accepts." + }, + "inputSchema": { + "anyOf": [ + { + "$ref": "#/components/schemas/WorkflowApiInputSchema" + }, + { + "type": "null" + } + ], + "description": "JSON Schema for the `input` an API run of the active deployment accepts, including `required` fields. Null when the workflow is not deployed or its active deployment has no API entry point." } }, "required": [ @@ -11126,11 +11763,12 @@ "createdAt", "updatedAt", "variables", - "inputs" + "inputs", + "inputSchema" ], "additionalProperties": false, "title": "Workflow detail", - "description": "Workflow metadata, variables, and saved draft trigger input fields." + "description": "Workflow metadata, variables, saved draft trigger input fields, and the input schema of the active deployment." }, "WorkflowDetailResponse": { "type": "object", @@ -11160,7 +11798,21 @@ "createdAt": "2026-05-01T09:00:00.000Z", "updatedAt": "2026-08-09T18:04:11.000Z", "variables": {}, - "inputs": [] + "inputs": [ + { + "name": "ticket", + "type": "string" + } + ], + "inputSchema": { + "type": "object", + "properties": { + "ticket": { + "type": "string" + } + }, + "required": ["ticket"] + } } } ] @@ -12906,7 +13558,7 @@ "type": "null" } ], - "description": "Cron expression of a schedule; null for non-schedules." + "description": "Cron expression the deployed schedule runs on; null for non-schedules. Read-only: it is computed at deploy from the schedule block, so change it by editing the block in the draft and deploying." }, "timezone": { "anyOf": [ @@ -12917,7 +13569,7 @@ "type": "null" } ], - "description": "IANA timezone the cron expression runs in; null for non-schedules." + "description": "IANA timezone the cron expression runs in; null for non-schedules. Read-only, like `cronExpression`." }, "url": { "anyOf": [ @@ -13040,13 +13692,159 @@ "required": ["enabled"], "additionalProperties": false, "title": "Update workflow trigger request", - "description": "Enable or disable one trigger of the live deployment.", + "description": "Enable or disable one trigger of the live deployment. A schedule's timing is not set here: it comes from the deployed schedule block, so edit that block in the draft and deploy.", "examples": [ { "enabled": true } ] }, + "WorkspaceSchedule": { + "type": "object", + "properties": { + "blockId": { + "type": "string", + "description": "Trigger block in the deployed workflow." + }, + "enabled": { + "type": "boolean", + "description": "Whether the trigger currently fires." + }, + "status": { + "type": "string", + "enum": ["active", "disabled", "completed"], + "description": "`disabled` after a manual disable or an automatic one following repeated failures. `completed` applies only to schedules that have no runs left." + }, + "failedCount": { + "type": "integer", + "minimum": 0, + "maximum": 9007199254740991, + "description": "Consecutive failures since the last success; the trigger disables itself at 100. Always 0 for `webhook` and `event` triggers." + }, + "lastFailedAt": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "ISO 8601 timestamp of the most recent counted failure, or null.", + "format": "date-time" + }, + "nextRunAt": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "ISO 8601 timestamp of the next scheduled run; null when disabled and for non-schedules.", + "format": "date-time" + }, + "lastRanAt": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "ISO 8601 timestamp of the last scheduled run; null for non-schedules.", + "format": "date-time" + }, + "cronExpression": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "Cron expression the deployed schedule runs on; null for non-schedules. Read-only: it is computed at deploy from the schedule block, so change it by editing the block in the draft and deploying." + }, + "workflowId": { + "type": "string", + "description": "Workflow whose live deployment runs the schedule." + }, + "workflowName": { + "type": "string", + "description": "Name of that workflow." + }, + "timezone": { + "type": "string", + "description": "IANA timezone the cron expression runs in. Read-only, like `cronExpression`." + } + }, + "required": [ + "blockId", + "enabled", + "status", + "failedCount", + "lastFailedAt", + "nextRunAt", + "lastRanAt", + "cronExpression", + "workflowId", + "workflowName", + "timezone" + ], + "additionalProperties": false, + "title": "Workspace schedule", + "description": "Health and timing of one schedule a deployed workflow in the workspace runs." + }, + "WorkspaceScheduleListResponse": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/WorkspaceSchedule" + }, + "description": "Items in the current page." + }, + "nextCursor": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "Opaque cursor for the next page. Send it back as `cursor`; `null` means there are no more pages. Do not construct a cursor yourself." + } + }, + "required": ["data", "nextCursor"], + "additionalProperties": false, + "title": "Workspace schedule list response", + "description": "A cursor-paginated page of the schedules deployed workflows run.", + "examples": [ + { + "data": [ + { + "workflowId": "3b1f7c92-8d4e-4a6b-9c0d-5e2f8a714b36", + "workflowName": "Customer support triage", + "blockId": "blk_01J8ZK3QW4M6X2R9T7B5C0V4", + "enabled": true, + "status": "active", + "failedCount": 0, + "lastFailedAt": null, + "nextRunAt": "2026-08-10T09:00:00.000Z", + "lastRanAt": "2026-08-10T08:00:00.000Z", + "cronExpression": "0 * * * *", + "timezone": "UTC" + } + ], + "nextCursor": null + } + ] + }, "DeployResult": { "type": "object", "properties": { @@ -14741,15 +15539,15 @@ "description": "Stable machine-readable execution failure code. `BLOCK_EXECUTION_FAILED` and `CHILD_WORKFLOW_FAILED` are reported only where block attribution is available; elsewhere a block-level failure is reported as `EXECUTION_FAILED`." }, "blockId": { - "description": "Identifier of the failing block. Present on the synchronous execute response only; the polled run resource and the resume response cannot attribute a block.", + "description": "Identifier of the failing block, when attributable. Get workflow run reads it from the run trace, so it is absent there when the trace is unavailable or hidden by organization permission groups, when no failed block in the trace recorded the run error, and for a timeout, cancellation, usage-limit, or invalid-input failure.", "type": "string" }, "blockName": { - "description": "Display name of the failing block. Present on the synchronous execute response only.", + "description": "Display name of the failing block, when attributable. Present whenever `blockId` is.", "type": "string" }, "blockType": { - "description": "Integration or block type that failed. Present on the synchronous execute response only.", + "description": "Integration or block type that failed, when attributable. Present whenever `blockId` is.", "type": "string" } }, @@ -14823,9 +15621,29 @@ "description": "Execution duration in milliseconds.", "type": "number", "minimum": 0 + }, + "executionTimeoutSeconds": { + "anyOf": [ + { + "type": "number", + "exclusiveMinimum": 0 + }, + { + "type": "null" + } + ], + "description": "Execution timeout applied to this run, in seconds: the plan's timeout, lowered by `executionTimeoutSeconds` when one was requested. Null when the run has no execution timeout." } }, - "required": ["runId", "workflowId", "status", "output", "blockOutputs", "error"], + "required": [ + "runId", + "workflowId", + "status", + "output", + "blockOutputs", + "error", + "executionTimeoutSeconds" + ], "additionalProperties": false, "title": "Workflow run result", "description": "Synchronous workflow run output and in-band execution status. Run failures are reported in band, not as HTTP errors — a run that exceeds its execution timeout returns HTTP 200 with `status: \"failed\"` and `error.code: \"TIMEOUT\"`, so branch on `status`." @@ -14855,7 +15673,8 @@ "error": null, "startedAt": "2026-08-09T18:04:10.000Z", "endedAt": "2026-08-09T18:04:11.000Z", - "durationMs": 1000 + "durationMs": 1000, + "executionTimeoutSeconds": 3000 } } ] @@ -14875,9 +15694,21 @@ "type": "string", "format": "uri", "description": "Absolute URL of the workflow run resource." + }, + "executionTimeoutSeconds": { + "anyOf": [ + { + "type": "number", + "exclusiveMinimum": 0 + }, + { + "type": "null" + } + ], + "description": "Execution timeout applied to this run, in seconds: the plan's timeout, lowered by `executionTimeoutSeconds` when one was requested. Null when the run has no execution timeout." } }, - "required": ["runId", "statusUrl"], + "required": ["runId", "statusUrl", "executionTimeoutSeconds"], "additionalProperties": false, "title": "Queued workflow run", "description": "Receipt returned when a workflow run is queued, or is still running when `waitTimeoutSeconds` elapsed." @@ -14898,7 +15729,8 @@ { "data": { "runId": "run_8f14e45f-ceea-467f-a", - "statusUrl": "https://www.sim.ai/api/v2/workflows/3b1f7c92-8d4e-4a6b-9c0d-5e2f8a714b36/runs/run_8f14e45f-ceea-467f-a" + "statusUrl": "https://www.sim.ai/api/v2/workflows/3b1f7c92-8d4e-4a6b-9c0d-5e2f8a714b36/runs/run_8f14e45f-ceea-467f-a", + "executionTimeoutSeconds": 5400 } } ] @@ -15251,6 +16083,43 @@ } ] }, + "PausedRun": { + "type": "object", + "properties": { + "runId": { + "type": "string", + "minLength": 1, + "maxLength": 128, + "pattern": "^[A-Za-z0-9._:-]+$", + "description": "Unique workflow run identifier.", + "examples": ["run_8f14e45f-ceea-467f-a"] + }, + "workflowId": { + "type": "string", + "description": "Workflow that produced the run." + }, + "workflowName": { + "type": "string", + "description": "Name of the workflow that produced the run." + }, + "pausedAt": { + "type": "string", + "description": "ISO 8601 timestamp when the run entered the paused state.", + "format": "date-time" + }, + "points": { + "type": "array", + "items": { + "$ref": "#/components/schemas/WorkflowRunPausePoint" + }, + "description": "Human pause points still waiting for a resume, earliest first, at most 100. Resume one with Resume workflow run, naming its `contextId` and supplying `input`. Get workflow run lists every point of the run, including time pauses and points already resuming." + } + }, + "required": ["runId", "workflowId", "workflowName", "pausedAt", "points"], + "additionalProperties": false, + "title": "Paused run", + "description": "A run waiting on human input, with the points it waits at." + }, "WorkflowRunPausePoint": { "type": "object", "properties": { @@ -15467,6 +16336,69 @@ "title": "Pause input field", "description": "One field a human pause asks the resumer to fill." }, + "PausedRunListResponse": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/PausedRun" + }, + "description": "Items in the current page." + }, + "nextCursor": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "Opaque cursor for the next page. Send it back as `cursor`; `null` means there are no more pages. Do not construct a cursor yourself." + } + }, + "required": ["data", "nextCursor"], + "additionalProperties": false, + "title": "Paused run list response", + "description": "A cursor-paginated page of runs waiting on human input.", + "examples": [ + { + "data": [ + { + "runId": "run_8f14e45f-ceea-467f-a", + "workflowId": "3b1f7c92-8d4e-4a6b-9c0d-5e2f8a714b36", + "workflowName": "Customer support triage", + "pausedAt": "2026-08-09T18:04:11.000Z", + "points": [ + { + "contextId": "blk_approval", + "blockId": "blk_approval", + "blockName": "Manager approval", + "kind": "human", + "status": "paused", + "registeredAt": "2026-08-09T18:04:11.000Z", + "resumeAt": null, + "parallel": null, + "loop": null, + "inputFields": [ + { + "name": "approved", + "label": "Approved", + "type": "boolean", + "required": true + } + ], + "display": null, + "queuePosition": null + } + ] + } + ], + "nextCursor": null + } + ] + }, "V2RunFile": { "type": "object", "properties": { @@ -15679,7 +16611,7 @@ "items": { "$ref": "#/components/schemas/WorkflowRunPausePoint" }, - "description": "Every point the run is paused at, earliest first, at most 100. Resume one with Resume workflow run, naming its `contextId` and supplying `input`." + "description": "Every point the run is paused at, earliest first, at most 100; past 100, points not yet resumed are kept in preference to resumed ones. Resume one with Resume workflow run, naming its `contextId` and supplying `input`." } }, "required": [ @@ -15729,7 +16661,7 @@ "type": "null" } ], - "description": "Structured execution failure, or null when none occurred. Reclassified from the persisted error message, so `blockId`/`blockName`/`blockType` are absent and a block-level failure reports `EXECUTION_FAILED` here even when the same run reported `BLOCK_EXECUTION_FAILED` on its synchronous execute response." + "description": "Structured execution failure, or null when none occurred. The failing block is read from the run trace, so `blockId`/`blockName`/`blockType` are absent, and a block-level failure reports `EXECUTION_FAILED`, when the trace is unavailable or hidden by organization permission groups." }, "output": { "anyOf": [ @@ -15840,12 +16772,84 @@ } ] }, + "WorkflowResumeResult": { + "type": "object", + "properties": { + "runId": { + "type": "string", + "minLength": 1, + "maxLength": 128, + "pattern": "^[A-Za-z0-9._:-]+$", + "description": "Unique workflow run identifier.", + "examples": ["run_8f14e45f-ceea-467f-a"] + }, + "workflowId": { + "type": "string", + "description": "Workflow that produced the run." + }, + "status": { + "type": "string", + "enum": ["completed", "failed", "paused", "cancelled"], + "description": "Terminal or paused run status." + }, + "output": { + "description": "Workflow output, including partial output on failure." + }, + "blockOutputs": { + "anyOf": [ + { + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": { + "description": "Output value produced by one workflow block." + } + }, + { + "type": "null" + } + ], + "description": "Outputs of the blocks named by `selectedOutputs`, keyed by those selector strings exactly as sent, or null when none were requested. `output` stays the final workflow output regardless. Selectors whose block did not run or whose path is absent are omitted (an unknown block is a `400` instead); failed runs include the outputs of the blocks that did run." + }, + "error": { + "anyOf": [ + { + "$ref": "#/components/schemas/ExecutionError" + }, + { + "type": "null" + } + ], + "description": "Structured execution failure, or null when none occurred." + }, + "startedAt": { + "description": "ISO 8601 timestamp when execution started.", + "format": "date-time", + "type": "string" + }, + "endedAt": { + "description": "ISO 8601 timestamp when execution ended.", + "format": "date-time", + "type": "string" + }, + "durationMs": { + "description": "Execution duration in milliseconds.", + "type": "number", + "minimum": 0 + } + }, + "required": ["runId", "workflowId", "status", "output", "blockOutputs", "error"], + "additionalProperties": false, + "title": "Workflow resume result", + "description": "Synchronous resumed run output and in-band execution status. Run failures are reported in band, not as HTTP errors, so branch on `status`." + }, "ResumeWorkflowSyncResponse": { "type": "object", "properties": { "data": { "description": "Response data.", - "$ref": "#/components/schemas/WorkflowRunResult" + "$ref": "#/components/schemas/WorkflowResumeResult" } }, "required": ["data"], @@ -19739,6 +20743,10 @@ "planetscale.branches", "planetscale.backups", "planetscale.deployRequests", + "planetscale.savedDatabases", + "planetscale.savedBranches", + "planetscale.savedBackups", + "planetscale.savedDeployRequests", "cloudwatch.logGroups", "cloudwatch.logStreams", "cloudwatch.savedLogGroups", @@ -19938,6 +20946,10 @@ "planetscale.branches", "planetscale.backups", "planetscale.deployRequests", + "planetscale.savedDatabases", + "planetscale.savedBranches", + "planetscale.savedBackups", + "planetscale.savedDeployRequests", "cloudwatch.logGroups", "cloudwatch.logStreams", "cloudwatch.savedLogGroups", diff --git a/apps/docs/package.json b/apps/docs/package.json index 013cdd66b7d..47d6aa4edd2 100644 --- a/apps/docs/package.json +++ b/apps/docs/package.json @@ -50,7 +50,7 @@ "@tanstack/react-query": "^5.90.21", "github-slugger": "2.0.0", "mdast-util-to-string": "4.0.0", - "mdast-util-to-markdown": "2.1.2", + "mdast-util-to-markdown": "2.2.2", "remark-mdx": "3.1.1", "remark-parse": "11.0.0", "unified": "11.0.5", @@ -69,13 +69,13 @@ "devDependencies": { "@playwright/test": "1.64.1", "@sim/tsconfig": "workspace:*", - "@tailwindcss/postcss": "^4.3.3", + "@tailwindcss/postcss": "^4.3.4", "@types/node": "24.19.2", "@types/react": "^19", "@types/react-dom": "^19", "gray-matter": "4.0.3", "postcss": "^8.5.30", - "tailwindcss": "^4.3.3", + "tailwindcss": "^4.3.4", "typescript": "^7.0.2", "vitest": "^5.1.0", "@modelcontextprotocol/client": "2.4.0", diff --git a/apps/docs/scripts/fixtures/content-baseline.json b/apps/docs/scripts/fixtures/content-baseline.json index 6694ecde8c8..0b72b79d282 100644 --- a/apps/docs/scripts/fixtures/content-baseline.json +++ b/apps/docs/scripts/fixtures/content-baseline.json @@ -879,16 +879,16 @@ { "lang": "json", "meta": null, - "sha256": "573578c2c3add71d0cbed2cca9574f0a44f6761971a6feeb8dd19757480b2272", - "bytes": 81, - "line": 169 + "sha256": "90d01678c8b5767e06bfe5a5e0b3e79d034de9f6c73bd6eb242df064e94c561b", + "bytes": 110, + "line": 173 }, { "lang": "json", "meta": null, - "sha256": "6340f4239f2a4c453c52b0ee9bdf52cbb24fe1208e4cbe23a7081d7fd8e6bc20", - "bytes": 213, - "line": 196 + "sha256": "b631aa240e7bf162bc7ee08642c5c32eb2e7d8ddcd0f8b07233726510add28ad", + "bytes": 231, + "line": 203 }, { "lang": "bash", @@ -12258,6 +12258,13 @@ "bytes": 42, "line": 5074 }, + { + "lang": "bash", + "meta": null, + "sha256": "d0f3737b421a98903f809a0b9e6dea5cd3bd96377b7dcc30fc5239e3aecf311d", + "bytes": 28, + "line": 5116 + }, { "lang": "bash", "meta": null, @@ -12839,481 +12846,502 @@ "bytes": 40, "line": 7465 }, + { + "lang": "bash", + "meta": null, + "sha256": "11658c406271a4f22a346aab51f697bc949342fbaaaabb2a786de824561bc4c3", + "bytes": 35, + "line": 7500 + }, { "lang": "bash", "meta": null, "sha256": "d683e8c6e5a1d063ea9f6c74fd9020d48aede0d5af7031f6f48bd5a173ad2d3d", "bytes": 33, - "line": 7500 + "line": 7525 }, { "lang": "bash", "meta": null, "sha256": "96abc89ee23cb4a7e2fc3caca2afbd2dd57d2734ca9e73816c412a71c5c5eb9d", "bytes": 43, - "line": 7527 + "line": 7552 }, { "lang": "bash", "meta": null, "sha256": "d63bf37625acc32ec1ee6e59a56f57469b9cb4dab03e23067a54d2f7d8abd3a3", "bytes": 41, - "line": 7559 + "line": 7584 }, { "lang": "bash", "meta": null, "sha256": "eb79c561c688b305a39f834bb39d74f65095db957e337d8e6081bbc00545bb08", "bytes": 53, - "line": 7592 + "line": 7617 }, { "lang": "bash", "meta": null, "sha256": "f30977b7b952e6a369b72b828256fac3f9ede78ce6e7c45560a8d09989bd4db5", "bytes": 49, - "line": 7623 + "line": 7648 }, { "lang": "bash", "meta": null, "sha256": "7d00d2a238c8794c5479e28ef14e6e104bcaff1aaaef70a094a2a51c55b6420c", "bytes": 50, - "line": 7644 + "line": 7669 }, { "lang": "bash", "meta": null, "sha256": "f34921d79bc4b608cb3e3c3f882729808afe1d8cf95ca45c63cbdf00ab494a3e", "bytes": 62, - "line": 7675 + "line": 7700 }, { "lang": "bash", "meta": null, "sha256": "ebfb60766394d803a01326b040ac37c5f122134555f1b968aeb6a79b10d74fc7", "bytes": 30, - "line": 7707 + "line": 7732 }, { "lang": "bash", "meta": null, "sha256": "432348582be57741628ca2b6a762c385ed57bbbfd4e4794b914409b7a89db582", "bytes": 35, - "line": 7729 + "line": 7754 }, { "lang": "bash", "meta": null, "sha256": "25d75743717b8e1c96f6b78e334e3e3cbc6dac5e395068308a4e358e06a0bac6", "bytes": 45, - "line": 7751 + "line": 7776 }, { "lang": "bash", "meta": null, "sha256": "2fc4fd97c2a62f635b42d646e481b18b0402ec0d051e7532988c026d838f0534", "bytes": 36, - "line": 7782 + "line": 7807 }, { "lang": "bash", "meta": null, "sha256": "3da31453cf86b97920f8d013f00122dee830af847c064e30ed3e158bd8f642cb", "bytes": 47, - "line": 7807 + "line": 7832 }, { "lang": "bash", "meta": null, "sha256": "d4de05e9142f532574133568e3fe2965c7fd82338e5b54ba0d8ae5713639eaca", "bytes": 43, - "line": 7832 + "line": 7857 }, { "lang": "bash", "meta": null, "sha256": "e63db5a4bab57e3229127bb086d0c5429dfe20de83eaf5097e8943f90520b9b3", "bytes": 51, - "line": 7864 + "line": 7889 }, { "lang": "bash", "meta": null, "sha256": "b130d4ed01c735074275b1505e7959e6dd3ae89c6b7ac159d17ccd495bec8f38", "bytes": 38, - "line": 7894 + "line": 7919 }, { "lang": "bash", "meta": null, "sha256": "32709306f0caabb4378fce9efba6b9c60a3985bb3500402d9a7f9e89fe35e00e", "bytes": 49, - "line": 7918 + "line": 7943 }, { "lang": "bash", "meta": null, "sha256": "a09f283134986b066a49a41c5ef250ff1502ae2adbc3316baacf92712112cc2d", "bytes": 43, - "line": 7960 + "line": 7985 }, { "lang": "bash", "meta": null, "sha256": "d941e4f91a379820ba5d640e7ceffd42c6fdd70b5b19da9c508c259b40ce14f7", "bytes": 53, - "line": 7991 + "line": 8016 }, { "lang": "bash", "meta": null, "sha256": "fb10d8958f9f1b03ab76713459abf552755dae3334103a451c387c9d1c7b3605", "bytes": 40, - "line": 8022 + "line": 8047 }, { "lang": "bash", "meta": null, "sha256": "b5dc1e771889fb1bf5f4d81d340cfd35d3a2263c87a725341d1d5c70405eb78d", "bytes": 43, - "line": 8071 + "line": 8096 }, { "lang": "bash", "meta": null, "sha256": "736889b5b5a59bc4fe7965922999b5cbec295acad8313c1e486d5668c7da961e", "bytes": 30, - "line": 8103 + "line": 8128 }, { "lang": "bash", "meta": null, "sha256": "4d6c0bc0130fd4a83c7b2fcac6f5b4b7bc2f275c05fa6101551ac61ce1405aca", "bytes": 44, - "line": 8123 + "line": 8148 }, { "lang": "bash", "meta": null, "sha256": "62d28b9be74c32c4ba00fefe878c492a6a5c671cc4a500373d7f2bdba9f7ef79", "bytes": 54, - "line": 8143 + "line": 8168 + }, + { + "lang": "bash", + "meta": null, + "sha256": "185227ff19342ab461c3e652c4158d8ece85c079b2542b9243b7c7cf0b2dac2c", + "bytes": 34, + "line": 8204 }, { "lang": "bash", "meta": null, "sha256": "7115696baaba8ca1ecd8f7cb22f215b7a53bf13d5757bd6932267644a0377807", "bytes": 36, - "line": 8173 + "line": 8198 }, { "lang": "bash", "meta": null, "sha256": "10330e610f59c2fb169fcff11cb8147563efbcc109f875222ecbfaaa48c05f31", "bytes": 50, - "line": 8195 + "line": 8220 }, { "lang": "bash", "meta": null, "sha256": "e88ab81833a4d529271c80e2cdf0e7ff5037a60066a918a9702a28ceca73a846", "bytes": 30, - "line": 8232 + "line": 8257 }, { "lang": "bash", "meta": null, "sha256": "f094df6732656b52792e5e766a69079c3e8bd929326bda7284e0bcb760bedd79", "bytes": 28, - "line": 8262 + "line": 8287 }, { "lang": "bash", "meta": null, "sha256": "7579bd6ac197afa0afa5f62d9b412a7e265725bd507c1c63b438a60fd72ce1d0", "bytes": 40, - "line": 8289 + "line": 8314 }, { "lang": "bash", "meta": null, "sha256": "63aac11606dc0eeb6cc452c20c405806840960425372cf2cf7c8b4b90a7de878", "bytes": 62, - "line": 8309 + "line": 8334 }, { "lang": "bash", "meta": null, "sha256": "52be4758f4289e9f75875d8401cf57fda54283b2df00e413921d07303e728638", "bytes": 28, - "line": 8340 + "line": 8365 }, { "lang": "bash", "meta": null, "sha256": "4c51312bac2ca9ea2dba0e7cf27f06d5e99502bab35ab569064057a85514cc38", "bytes": 38, - "line": 8361 + "line": 8386 }, { "lang": "bash", "meta": null, "sha256": "2b26aa6f404ab19bda2d846871eb04b732f6397d1899fc28d89a29706fc0a670", "bytes": 34, - "line": 8387 + "line": 8412 }, { "lang": "bash", "meta": null, "sha256": "47af2a240e0fa4bca92d786d1fea2d4b6cd7ee8810c4540b619271457739f829", "bytes": 60, - "line": 8409 + "line": 8434 }, { "lang": "bash", "meta": null, "sha256": "f4a5d9b54707e03cc845a3ffc99493f9990d7c0a0aa96a48a492b303ef1959b4", "bytes": 45, - "line": 8442 + "line": 8467 }, { "lang": "bash", "meta": null, "sha256": "4a4ff033e75c32373422638e4f8079487d9cc37fa2b250cc2907584e690e2699", "bytes": 45, - "line": 8475 + "line": 8500 }, { "lang": "bash", "meta": null, "sha256": "66d1f7b4fc772fcb1ac84bcba2c1aa0e1a26105dda64b564c40dec35fc5fdaf2", "bytes": 43, - "line": 8505 + "line": 8530 }, { "lang": "bash", "meta": null, "sha256": "5fc8d60c19e8e3fc1c640ea87e091d0b95642a210a7e1a7d9d19e527b6f11207", "bytes": 38, - "line": 8537 + "line": 8562 }, { "lang": "bash", "meta": null, "sha256": "a7acb92552eae9b29dd7b973751bb7f3c2f7eb1dc00f36e84c594cb88dd9fc1c", "bytes": 33, - "line": 8558 + "line": 8583 }, { "lang": "bash", "meta": null, "sha256": "bb84857c237d2bfae0c6f63653597fe55263147428a5e8d36ceacfcda6bd6246", "bytes": 26, - "line": 8589 + "line": 8614 }, { "lang": "bash", "meta": null, "sha256": "3e634421662d08fe73882dd6a0d428545c33597790333f83b4c5086c8d494a1e", "bytes": 49, - "line": 8615 + "line": 8640 }, { "lang": "bash", "meta": null, "sha256": "c48dfec9df0cebb16aa976f859770a8b5beb67e2cf01dc5472dd8b3727ea164d", "bytes": 47, - "line": 8635 + "line": 8660 }, { "lang": "bash", "meta": null, "sha256": "8fd3a890425cf05e630bcd969b2aff57b3993d6632d905cab5b23cfce60e425c", "bytes": 49, - "line": 8656 + "line": 8681 }, { "lang": "bash", "meta": null, "sha256": "bd32d826b077176044eabbf175d64a747ba1a826bb36c95efb46925525f75ba9", "bytes": 45, - "line": 8682 + "line": 8707 }, { "lang": "bash", "meta": null, "sha256": "ee94be8e7fcb3c1bdb0abd6991992563371ba113b80b433cce0c71ac499f1d48", "bytes": 43, - "line": 8706 + "line": 8731 }, { "lang": "bash", "meta": null, "sha256": "bbaecaea783d60d4e8cbaad1c6951d2aa5fde5772f050166a24415162d9de3bc", "bytes": 29, - "line": 8728 + "line": 8753 }, { "lang": "bash", "meta": null, "sha256": "627ab83bbee7d7a608e8771c98556a00cb9df200a40fe9b68d79f93c322de59d", "bytes": 18, - "line": 8753 + "line": 8778 }, { "lang": "bash", "meta": null, "sha256": "9396c5d2aba2c0ff0d484a0a778dc38368154eb19edca9372cf576f8abedac90", "bytes": 32, - "line": 8763 + "line": 8788 }, { "lang": "bash", "meta": null, "sha256": "456a3638df3f1208cc5620fe2f7744e60215930e90cdada8c6d3f3041844b81c", "bytes": 22, - "line": 8773 + "line": 8798 }, { "lang": "bash", "meta": null, "sha256": "80c6f38f04b6c653c8de67e79ba7b6ca1a5ec208e8765a036048fd0adc4b4522", "bytes": 37, - "line": 8783 + "line": 8808 }, { "lang": "bash", "meta": null, "sha256": "3be0a5bf2f996779b39f57c35c08a046709ad91ffbb1ecd700c2d0f8a1c9b554", "bytes": 40, - "line": 8808 + "line": 8833 }, { "lang": "bash", "meta": null, "sha256": "94cdaecf523029bf20f25cf278f76142041e4ad9ce6980a0de225f6dd0b93a8f", "bytes": 43, - "line": 8830 + "line": 8855 }, { "lang": "bash", "meta": null, "sha256": "1731734821194e9c9ab5d0e866031dff102eab3c67b81c83780dce706461c802", "bytes": 40, - "line": 8850 + "line": 8875 }, { "lang": "bash", "meta": null, "sha256": "466187fc1a5788ab09d7700cf7cd19c9c541a571d0bc86d70fe52a1eb831b851", "bytes": 54, - "line": 8872 + "line": 8897 }, { "lang": "bash", "meta": null, "sha256": "e10bc7e084ad3b9231262208e0ac216aee47dc0ec7b1507214690ea9c098d5c9", "bytes": 32, - "line": 8904 + "line": 8929 }, { "lang": "bash", "meta": null, "sha256": "6bb0cf09f6bd8ba02e1f24a5eed89b35c0226ec7eefce07502015a455520fd0f", "bytes": 33, - "line": 8914 + "line": 8939 }, { "lang": "bash", "meta": null, "sha256": "23acce8016e243ab651bcc860cd8f1b1d236e6a5b7951abc76282cd674b2a25e", "bytes": 39, - "line": 8937 + "line": 8962 }, { "lang": "bash", "meta": null, "sha256": "210e8ab3d8af173ce648501ca50c828325ac1f4452006ad4485cb7a24d760ae4", "bytes": 37, - "line": 8961 + "line": 8986 }, { "lang": "bash", "meta": null, "sha256": "cdff2d4272d5bc3a18729e63e5ece9053d33ac0f8faf35cfaa3a289baa2dccc1", "bytes": 48, - "line": 8983 + "line": 9008 }, { "lang": "bash", "meta": null, "sha256": "c307203dd609208b90cbc0372e3401a75e943421bdf77e4e10779ed2c89ca4ff", "bytes": 48, - "line": 9013 + "line": 9038 }, { "lang": "bash", "meta": null, "sha256": "f6340c3d5dc4eedb98435df593ff277d0e44fbcb4c61e71a05123267b5352e78", "bytes": 29, - "line": 9043 + "line": 9068 }, { "lang": "bash", "meta": null, "sha256": "ca6e405dc70365db67ec66e6c2499fedf89ae3d7a90f4b5d044a2aab13bb8f37", "bytes": 37, - "line": 9065 + "line": 9090 }, { "lang": "bash", "meta": null, "sha256": "52918df5b4d4003d22322d5785d8a0202a2b66d39a3c71171f92a65545e28788", "bytes": 37, - "line": 9086 + "line": 9111 }, { "lang": "bash", "meta": null, "sha256": "28e77f2a35930390cc7de2fc5abdce7809311c33b10eb749941d5c44b9de0cfa", "bytes": 37, - "line": 9111 + "line": 9136 }, { "lang": "bash", "meta": null, "sha256": "088f9018d5639eac73c710bf1ef4fd134b0cd4c290587a161af1322b415a013f", "bytes": 29, - "line": 9138 + "line": 9163 }, { "lang": "bash", "meta": null, "sha256": "67384a12d7642ea8a82e807a58e42f921ea37cb496e2315ac87368ae0e297e4d", "bytes": 29, - "line": 9170 + "line": 9195 }, { "lang": "bash", "meta": null, "sha256": "4c9e46c0b1c48cb348bfb37e4c705e4dfe1cd5f221d667ebd02dd7325fce7e29", "bytes": 38, - "line": 9202 + "line": 9227 }, { "lang": "bash", "meta": null, "sha256": "20fbb381c0b4ef786f755f485789ecb9e76538ec6394956bb6153ee4a79547ca", "bytes": 31, - "line": 9225 + "line": 9250 + }, + { + "lang": "bash", + "meta": null, + "sha256": "ec9baafc46bf3e7425a387e9d92083a610028b2f7bf477ccc203fee669796cf1", + "bytes": 31, + "line": 9247 }, { "lang": "bash", "meta": null, "sha256": "54ffdfd98a1fc3aad38e602c1d02336a81fe97d48e6732402a7980d0f55cb20d", "bytes": 40, - "line": 9246 + "line": 9271 } ], "links": [ @@ -17194,6 +17222,13 @@ "bytes": 50, "line": 246 }, + { + "lang": "bash", + "meta": null, + "sha256": "185227ff19342ab461c3e652c4158d8ece85c079b2542b9243b7c7cf0b2dac2c", + "bytes": 34, + "line": 285 + }, { "lang": "bash", "meta": null, @@ -17234,196 +17269,203 @@ "meta": null, "sha256": "7579bd6ac197afa0afa5f62d9b412a7e265725bd507c1c63b438a60fd72ce1d0", "bytes": 40, - "line": 436 + "line": 432 }, { "lang": "bash", "meta": null, "sha256": "63aac11606dc0eeb6cc452c20c405806840960425372cf2cf7c8b4b90a7de878", "bytes": 62, - "line": 454 + "line": 450 }, { "lang": "bash", "meta": null, "sha256": "7d00d2a238c8794c5479e28ef14e6e104bcaff1aaaef70a094a2a51c55b6420c", "bytes": 50, - "line": 432 + "line": 479 }, { "lang": "bash", "meta": null, "sha256": "f30977b7b952e6a369b72b828256fac3f9ede78ce6e7c45560a8d09989bd4db5", "bytes": 49, - "line": 461 + "line": 508 }, { "lang": "bash", "meta": null, "sha256": "f34921d79bc4b608cb3e3c3f882729808afe1d8cf95ca45c63cbdf00ab494a3e", "bytes": 62, - "line": 480 + "line": 527 }, { "lang": "bash", "meta": null, "sha256": "eb79c561c688b305a39f834bb39d74f65095db957e337d8e6081bbc00545bb08", "bytes": 53, - "line": 510 + "line": 557 }, { "lang": "bash", "meta": null, "sha256": "5ed096b4dcf0efad18c54d322d528b7bb0e80336d061ee7eaa5d39d3bd505a4d", "bytes": 62, - "line": 543 + "line": 590 }, { "lang": "bash", "meta": null, "sha256": "47af2a240e0fa4bca92d786d1fea2d4b6cd7ee8810c4540b619271457739f829", "bytes": 60, - "line": 576 + "line": 623 }, { "lang": "bash", "meta": null, "sha256": "f4a5d9b54707e03cc845a3ffc99493f9990d7c0a0aa96a48a492b303ef1959b4", "bytes": 45, - "line": 609 + "line": 656 }, { "lang": "bash", "meta": null, "sha256": "4a4ff033e75c32373422638e4f8079487d9cc37fa2b250cc2907584e690e2699", "bytes": 45, - "line": 642 + "line": 689 }, { "lang": "bash", "meta": null, "sha256": "fb10d8958f9f1b03ab76713459abf552755dae3334103a451c387c9d1c7b3605", "bytes": 40, - "line": 672 + "line": 719 }, { "lang": "bash", "meta": null, "sha256": "d683e8c6e5a1d063ea9f6c74fd9020d48aede0d5af7031f6f48bd5a173ad2d3d", "bytes": 33, - "line": 717 + "line": 764 + }, + { + "lang": "bash", + "meta": null, + "sha256": "11658c406271a4f22a346aab51f697bc949342fbaaaabb2a786de824561bc4c3", + "bytes": 35, + "line": 789 }, { "lang": "bash", "meta": null, "sha256": "bfb90a10455a59ef26068d176221816b9c374c4001d386ffdbb0140c4cfe40ca", "bytes": 40, - "line": 744 + "line": 814 }, { "lang": "bash", "meta": null, "sha256": "d63bf37625acc32ec1ee6e59a56f57469b9cb4dab03e23067a54d2f7d8abd3a3", "bytes": 41, - "line": 777 + "line": 847 }, { "lang": "bash", "meta": null, "sha256": "96abc89ee23cb4a7e2fc3caca2afbd2dd57d2734ca9e73816c412a71c5c5eb9d", "bytes": 43, - "line": 810 + "line": 880 }, { "lang": "bash", "meta": null, "sha256": "653a412a8eeaffac8afbbbb2533375f02df67fe7ef882ca53fc7ca2755d0bcec", "bytes": 43, - "line": 842 + "line": 912 }, { "lang": "bash", "meta": null, "sha256": "b130d4ed01c735074275b1505e7959e6dd3ae89c6b7ac159d17ccd495bec8f38", "bytes": 38, - "line": 874 + "line": 944 }, { "lang": "bash", "meta": null, "sha256": "32709306f0caabb4378fce9efba6b9c60a3985bb3500402d9a7f9e89fe35e00e", "bytes": 49, - "line": 898 + "line": 968 }, { "lang": "bash", "meta": null, "sha256": "e63db5a4bab57e3229127bb086d0c5429dfe20de83eaf5097e8943f90520b9b3", "bytes": 51, - "line": 942 + "line": 1012 }, { "lang": "bash", "meta": null, "sha256": "b5dc1e771889fb1bf5f4d81d340cfd35d3a2263c87a725341d1d5c70405eb78d", "bytes": 43, - "line": 974 + "line": 1044 }, { "lang": "bash", "meta": null, "sha256": "4c51312bac2ca9ea2dba0e7cf27f06d5e99502bab35ab569064057a85514cc38", "bytes": 38, - "line": 1004 + "line": 1074 }, { "lang": "bash", "meta": null, "sha256": "e88ab81833a4d529271c80e2cdf0e7ff5037a60066a918a9702a28ceca73a846", "bytes": 30, - "line": 1028 + "line": 1098 }, { "lang": "bash", "meta": null, "sha256": "a7acb92552eae9b29dd7b973751bb7f3c2f7eb1dc00f36e84c594cb88dd9fc1c", "bytes": 33, - "line": 1058 + "line": 1128 }, { "lang": "bash", "meta": null, "sha256": "2fc4fd97c2a62f635b42d646e481b18b0402ec0d051e7532988c026d838f0534", "bytes": 36, - "line": 1087 + "line": 1157 }, { "lang": "bash", "meta": null, "sha256": "432348582be57741628ca2b6a762c385ed57bbbfd4e4794b914409b7a89db582", "bytes": 35, - "line": 1110 + "line": 1180 }, { "lang": "bash", "meta": null, "sha256": "25d75743717b8e1c96f6b78e334e3e3cbc6dac5e395068308a4e358e06a0bac6", "bytes": 45, - "line": 1130 + "line": 1200 }, { "lang": "bash", "meta": null, "sha256": "3da31453cf86b97920f8d013f00122dee830af847c064e30ed3e158bd8f642cb", "bytes": 47, - "line": 1159 + "line": 1229 }, { "lang": "bash", "meta": null, "sha256": "bb84857c237d2bfae0c6f63653597fe55263147428a5e8d36ceacfcda6bd6246", "bytes": 26, - "line": 1180 + "line": 1250 } ], "links": [ @@ -17813,6 +17855,13 @@ "bytes": 18, "line": 34 }, + { + "lang": "bash", + "meta": null, + "sha256": "ec9baafc46bf3e7425a387e9d92083a610028b2f7bf477ccc203fee669796cf1", + "bytes": 31, + "line": 48 + }, { "lang": "bash", "meta": null, diff --git a/apps/sim/app/.well-known/api-catalog/route.ts b/apps/sim/app/.well-known/api-catalog/route.ts index f82be29d6b1..07c4b655dd3 100644 --- a/apps/sim/app/.well-known/api-catalog/route.ts +++ b/apps/sim/app/.well-known/api-catalog/route.ts @@ -1,7 +1,7 @@ import { NextResponse } from 'next/server' +import { getV2ApiLinks } from '@/lib/api/links' import { getBaseUrl } from '@/lib/core/utils/urls' import { withRouteHandler } from '@/lib/core/utils/with-route-handler' -import { DOCS_URL } from '@/lib/help-links' /** RFC 9727 §4.2: the catalog is a Linkset carrying the RFC's profile. */ const API_CATALOG_CONTENT_TYPE = @@ -20,6 +20,7 @@ export const GET = withRouteHandler(() => { const baseUrl = getBaseUrl() const catalogUrl = `${baseUrl}/.well-known/api-catalog` const apiUrl = `${baseUrl}/api/v2` + const links = getV2ApiLinks() return NextResponse.json( { @@ -27,10 +28,8 @@ export const GET = withRouteHandler(() => { { anchor: catalogUrl, item: [{ href: apiUrl }] }, { anchor: apiUrl, - 'service-desc': [ - { href: `${DOCS_URL}/openapi.json`, type: 'application/vnd.oai.openapi+json' }, - ], - 'service-doc': [{ href: `${DOCS_URL}/api-reference/getting-started`, type: 'text/html' }], + 'service-desc': [{ href: links.openapi, type: 'application/vnd.oai.openapi+json' }], + 'service-doc': [{ href: links.docs, type: 'text/html' }], status: [{ href: `${baseUrl}/api/health`, type: 'application/json' }], }, ], diff --git a/apps/sim/app/account/settings/layout.test.tsx b/apps/sim/app/account/settings/layout.test.tsx deleted file mode 100644 index a3214f53af2..00000000000 --- a/apps/sim/app/account/settings/layout.test.tsx +++ /dev/null @@ -1,47 +0,0 @@ -import type { ReactNode } from 'react' -import { authMockFns } from '@sim/testing' -import { featureFlagsMock, featureFlagsMockFns } from '@sim/testing/mocks/feature-flags.mock' -import { nextNavigationMock } from '@sim/testing/mocks/next-navigation.mock' -import { renderToStaticMarkup } from 'react-dom/server' -import { beforeEach, describe, expect, it, vi } from 'vitest' - -vi.mock('next/navigation', () => nextNavigationMock) -vi.mock('@/lib/core/config/feature-flags', () => featureFlagsMock) -vi.mock('@/lib/permissions/super-user', () => ({ isPlatformAdmin: vi.fn(async () => false) })) -vi.mock('@/components/settings/standalone-settings-shell', () => ({ - StandaloneSettingsShell: ({ children }: { children: ReactNode }) => children, -})) - -import AccountSettingsLayout from '@/app/account/settings/layout' -import { useFeatureFlag } from '@/app/workspace/[workspaceId]/providers/feature-flags-provider' - -/** - * Renders the account layout around a section that reads the read-only key flag the way the - * personal API keys page's create modal does on every render, and returns what it read. - */ -async function readFlagInAccountSettings(): Promise { - let observed: boolean | undefined - function ReadOnlyKeysSection() { - observed = useFeatureFlag('api-key-read-only') - return null - } - renderToStaticMarkup(await AccountSettingsLayout({ children: })) - if (observed === undefined) throw new Error('The account section never rendered') - return observed -} - -describe('AccountSettingsLayout', () => { - beforeEach(() => { - authMockFns.mockGetSession.mockResolvedValue({ - user: { id: 'viewer-1' }, - session: { id: 'session-1' }, - }) - }) - - it.each([true, false])('gives account sections the read-only key flag (%s)', async (enabled) => { - featureFlagsMockFns.mockIsFeatureEnabled.mockImplementation( - async (flag) => flag === 'api-key-read-only' && enabled - ) - expect(await readFlagInAccountSettings()).toBe(enabled) - }) -}) diff --git a/apps/sim/app/account/settings/layout.tsx b/apps/sim/app/account/settings/layout.tsx index 2cc46a3d1f8..43427e014f6 100644 --- a/apps/sim/app/account/settings/layout.tsx +++ b/apps/sim/app/account/settings/layout.tsx @@ -1,6 +1,5 @@ import { redirect } from 'next/navigation' import { StandaloneSettingsShell } from '@/components/settings/standalone-settings-shell' -import { isReadOnlyApiKeyCreationEnabled } from '@/lib/api-key/feature-flag' import { getSession } from '@/lib/auth' import { resolveDeploymentShape } from '@/lib/core/config/deployment-shape' import { isPlatformAdmin } from '@/lib/permissions/super-user' @@ -9,10 +8,7 @@ import { FeatureFlagsProvider } from '@/app/workspace/[workspaceId]/providers/fe export default async function AccountSettingsLayout({ children }: { children: React.ReactNode }) { const session = await getSession() if (!session?.user) redirect('/login') - const [isSuperUser, readOnlyApiKeysEnabled] = await Promise.all([ - isPlatformAdmin(session.user.id), - isReadOnlyApiKeyCreationEnabled(), - ]) + const isSuperUser = await isPlatformAdmin(session.user.id) /** Account settings host no workspace surface, so only the flags its sections read are on. */ return ( @@ -23,7 +19,6 @@ export default async function AccountSettingsLayout({ children }: { children: Re changelog: false, 'mothership-model-selector': false, 'mothership-plan-mode': false, - 'api-key-read-only': readOnlyApiKeysEnabled, }} > { ) }) + describe('Vertex AI account listing', () => { + const workspaceId = '3f1c8a54-1c2e-4a1b-9d6e-2b7c5a9f0e11' + + beforeEach(() => { + hybridAuthMockFns.mockCheckSessionOrInternalAuth.mockReset().mockResolvedValue({ + success: true, + userId: 'user-123', + userName: 'Test User', + userEmail: 'test@example.com', + authType: 'session', + }) + permissionsMockFns.mockCheckWorkspaceAccess.mockResolvedValue({ + exists: true, + hasAccess: true, + canWrite: true, + canAdmin: true, + }) + permissionGroupScopeMockFns.mockResolvePermissionGroupConfig.mockResolvedValue( + DEFAULT_PERMISSION_GROUP_CONFIG + ) + }) + + it('lists the workspace’s Google service accounts beside its Vertex AI OAuth accounts', async () => { + const updatedAt = new Date('2026-01-01T00:00:00Z') + dbChainMockFns.where + .mockResolvedValueOnce([ + { + id: 'vertex-oauth-1', + displayName: 'Vertex account', + providerId: 'vertex-ai', + scope: getCanonicalScopesForProvider('vertex-ai').join(' '), + updatedAt, + }, + ]) + .mockResolvedValueOnce([ + { + id: 'google-sa-1', + displayName: 'Keyless Vertex', + providerId: 'google-service-account', + updatedAt, + }, + ]) + + const response = await GET( + createMockRequestWithQuery('GET', `?provider=vertex-ai&workspaceId=${workspaceId}`), + { params: Promise.resolve({}) } + ) + + expect(response.status).toBe(200) + const { credentials } = await response.json() + expect( + credentials.map((credential: { id: string; type: string; provider: string }) => ({ + id: credential.id, + type: credential.type, + provider: credential.provider, + })) + ).toEqual([ + { id: 'vertex-oauth-1', type: 'oauth', provider: 'vertex-ai' }, + { id: 'google-sa-1', type: 'service_account', provider: 'google-service-account' }, + ]) + }) + }) + /** The session/executor split documented on {@link integrationsWithheldFromSession} in the route. */ describe('integrations.manage', () => { const INTEGRATIONS_WITHHELD = { diff --git a/apps/sim/app/api/auth/oauth2/authorize/route.test.ts b/apps/sim/app/api/auth/oauth2/authorize/route.test.ts index bd6c5c44124..12b13cc6080 100644 --- a/apps/sim/app/api/auth/oauth2/authorize/route.test.ts +++ b/apps/sim/app/api/auth/oauth2/authorize/route.test.ts @@ -53,6 +53,8 @@ vi.mock('@/lib/oauth/quickbooks-state', () => ({ createQuickBooksOAuthState: mocks.createQuickBooksState, })) +import { OrchestrationError } from '@/lib/core/orchestration/types' +import { OAUTH_LINK_UNAVAILABLE_ERROR } from '@/lib/credentials/draft-constants' import { GET as routeGET } from '@/app/api/auth/oauth2/authorize/route' const GET = (req: NextRequest) => routeGET(req, { params: Promise.resolve({}) }) @@ -458,6 +460,20 @@ describe('OAuth2 authorize route', () => { expect(response.headers.get('location')).toBe('https://provider.example/authorize') }) + it('tells a user who cannot open a draft to sign in as its creator, without saying whether it exists', async () => { + mocks.launchConnection.mockRejectedValueOnce( + new OrchestrationError('not_found', 'OAuth connection link is invalid or expired') + ) + + const response = await GET(request({ draftId: 'someone-elses-draft' })) + + const location = new URL(response.headers.get('location') ?? '') + expect(`${location.origin}${location.pathname}`).toBe(`${BASE_URL}/oauth/credential-connected`) + expect(Object.fromEntries(location.searchParams)).toEqual({ + error: OAUTH_LINK_UNAVAILABLE_ERROR, + }) + }) + it.each([ { callbackURL: undefined, diff --git a/apps/sim/app/api/auth/oauth2/authorize/route.ts b/apps/sim/app/api/auth/oauth2/authorize/route.ts index f241cf4cb10..af8a786077e 100644 --- a/apps/sim/app/api/auth/oauth2/authorize/route.ts +++ b/apps/sim/app/api/auth/oauth2/authorize/route.ts @@ -25,7 +25,10 @@ import { getBaseUrl } from '@/lib/core/utils/urls' import { isSameOrigin } from '@/lib/core/utils/validation' import { withRouteHandler } from '@/lib/core/utils/with-route-handler' import { launchScopedCredentialConnection } from '@/lib/credentials/application/launch-scoped-credential-connection' -import { OAUTH_CREDENTIAL_DRAFT_CALLBACK_PARAM } from '@/lib/credentials/draft-constants' +import { + OAUTH_CREDENTIAL_DRAFT_CALLBACK_PARAM, + OAUTH_LINK_UNAVAILABLE_ERROR, +} from '@/lib/credentials/draft-constants' import { APP_ENTRY_PATH } from '@/lib/navigation/paths' import { decryptQuickBooksOAuthClientConfig } from '@/lib/oauth/quickbooks-client-config' import { QUICKBOOKS_AUTHORIZATION_URL } from '@/lib/oauth/quickbooks-constants' @@ -229,9 +232,17 @@ export const GET = withRouteHandler(async (request: NextRequest) => { }).catch((error: unknown) => { if (!(error instanceof OrchestrationError)) throw error logger.warn('Rejected OAuth connection draft', { userId, draftId, code: error.code }) - return null + return error }) - if (!launched) { + if (launched instanceof OrchestrationError) { + // A draft is looked up by its id and the signed-in user together, so one + // that is unknown, expired, or another account's all read as not found. + // The reason names every case and does not say which one applied. + if (launched.code === 'not_found') { + const unavailableUrl = new URL('/oauth/credential-connected', baseUrl) + unavailableUrl.searchParams.set('error', OAUTH_LINK_UNAVAILABLE_ERROR) + return NextResponse.redirect(unavailableUrl) + } return NextResponse.redirect(`${baseUrl}${APP_ENTRY_PATH}?error=oauth_link_invalid`) } const { draft } = launched diff --git a/apps/sim/app/api/cli/auth/approve/route.test.ts b/apps/sim/app/api/cli/auth/approve/route.test.ts index 7118eee73f3..737efc11b43 100644 --- a/apps/sim/app/api/cli/auth/approve/route.test.ts +++ b/apps/sim/app/api/cli/auth/approve/route.test.ts @@ -79,6 +79,7 @@ describe('POST /api/cli/auth/approve', () => { scope: 'platform', workspaceId: 'ws-1', workspaceBound: false, + access: 'write', }) }) @@ -110,6 +111,20 @@ describe('POST /api/cli/auth/approve', () => { expect(mockCreateApproval).not.toHaveBeenCalled() }) + it('refuses a read-only or expiring key on the copilot scope', async () => { + const response = await POST( + createMockRequest('POST', { + request: REQUEST, + challenge: CHALLENGE, + scope: 'copilot', + access: 'read', + }), + { params: Promise.resolve({}) } + ) + expect(response.status).toBe(400) + expect(mockCreateApproval).not.toHaveBeenCalled() + }) + /** * The workspace-key pass-through in the authorization funnel resolves no * group, so minting one is the only place the regime can be enforced. The diff --git a/apps/sim/app/api/cli/auth/approve/route.ts b/apps/sim/app/api/cli/auth/approve/route.ts index fa463ab9238..81941671e56 100644 --- a/apps/sim/app/api/cli/auth/approve/route.ts +++ b/apps/sim/app/api/cli/auth/approve/route.ts @@ -28,8 +28,9 @@ const logger = createLogger('CliAuthApproveAPI') * * That makes the approval record the sole carrier of the decision. The poll body * is a request id and a secret and nothing else, so it cannot assert a scope, a - * workspace, or a binding of its own — a refusal here writes no record, and a - * poll driven directly against the same request id answers `pending` forever. + * workspace, a binding, an access level, or a lifetime of its own — a refusal + * here writes no record, and a poll driven directly against the same request id + * answers `pending` forever. */ export const POST = withRouteHandler(async (request: NextRequest) => { const session = await getSession() @@ -43,7 +44,15 @@ export const POST = withRouteHandler(async (request: NextRequest) => { const parsed = await parseRequest(approveCliAuthContract, request, {}) if (!parsed.success) return parsed.response - const { request: requestId, challenge, scope, workspaceId, bindKeyToWorkspace } = parsed.data.body + const { + request: requestId, + challenge, + scope, + workspaceId, + bindKeyToWorkspace, + access, + expiresInDays, + } = parsed.data.body if ((workspaceId || bindKeyToWorkspace) && scope !== 'platform') { return NextResponse.json( @@ -52,6 +61,15 @@ export const POST = withRouteHandler(async (request: NextRequest) => { ) } + // A copilot key has no access level or expiry to set; accepting either would + // record a consent the mint cannot honor. + if ((access !== 'write' || expiresInDays !== undefined) && scope !== 'platform') { + return NextResponse.json( + { error: 'access and expiresInDays are only valid for the platform scope' }, + { status: 400 } + ) + } + if (bindKeyToWorkspace && !workspaceId) { return NextResponse.json( { error: 'bindKeyToWorkspace requires a workspaceId' }, @@ -135,12 +153,16 @@ export const POST = withRouteHandler(async (request: NextRequest) => { scope, workspaceId, workspaceBound: bindKeyToWorkspace, + access, + expiresInDays, }) logger.info('Recorded CLI authorization approval', { userId: session.user.id, scope, workspaceId: workspaceId ?? null, workspaceBound: bindKeyToWorkspace, + access, + expiresInDays: expiresInDays ?? null, }) return NextResponse.json({ ok: true }) diff --git a/apps/sim/app/api/cli/auth/poll/route.test.ts b/apps/sim/app/api/cli/auth/poll/route.test.ts index 113e391c070..3082bd1812f 100644 --- a/apps/sim/app/api/cli/auth/poll/route.test.ts +++ b/apps/sim/app/api/cli/auth/poll/route.test.ts @@ -55,10 +55,15 @@ function approved(overrides: Record = {}) { scope: 'copilot', workspaceId: null, workspaceBound: false, + access: 'write', + expiresInDays: null, ...overrides, } } +/** The name the terminal shows when it asks the caller to revoke a key it refused. */ +const CLI_KEY_NAME = expect.stringMatching(/^CLI \(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}Z\)$/) + describe('POST /api/cli/auth/poll', () => { beforeEach(() => { mockEnforceIpRateLimit.mockResolvedValue(null) @@ -89,17 +94,14 @@ describe('POST /api/cli/auth/poll', () => { expect(response.status).toBe(200) await expect(response.json()).resolves.toEqual({ status: 'complete', - key: { id: 'key-1', apiKey: 'sk-test' }, + key: { id: 'key-1', apiKey: 'sk-test', name: CLI_KEY_NAME }, scope: 'copilot', workspaceId: null, workspaceBound: false, }) // Second precision, not day: a date-only name made the second login of the // day fail after the user had already approved in the browser. - expect(mockGenerateCopilotApiKey).toHaveBeenCalledWith( - 'user-1', - expect.stringMatching(/^CLI \(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}Z\)$/) - ) + expect(mockGenerateCopilotApiKey).toHaveBeenCalledWith('user-1', CLI_KEY_NAME) expect(mockCompleteApproval).toHaveBeenCalledWith(REQUEST) expect(mockReleaseMint).not.toHaveBeenCalled() }) @@ -113,6 +115,22 @@ describe('POST /api/cli/auth/poll', () => { expect(mockCreatePersonalApiKey).not.toHaveBeenCalled() }) + it('mints with the access and lifetime the approval recorded', async () => { + mockPollApproval.mockResolvedValue( + approved({ scope: 'platform', access: 'read', expiresInDays: 30 }) + ) + const before = Date.now() + const response = await POST(pollRequest({ request: REQUEST, verifier: VERIFIER }), { + params: Promise.resolve({}), + }) + expect(response.status).toBe(200) + const [params] = mockCreatePersonalApiKey.mock.calls[0] + expect(params.access).toBe('read') + const lifetimeDays = (new Date(params.expiresAt).getTime() - before) / (24 * 60 * 60 * 1000) + expect(lifetimeDays).toBeGreaterThanOrEqual(30) + expect(lifetimeDays).toBeLessThan(30.01) + }) + /** * `/api/cli/auth/approve` is where the session exists to check workspace-admin * permission and the `api_keys.manage` capability, so it must be impossible to @@ -173,7 +191,7 @@ describe('POST /api/cli/auth/poll', () => { expect(response.status).toBe(200) await expect(response.json()).resolves.toEqual({ status: 'complete', - key: { id: 'key-1', apiKey: 'sk-test' }, + key: { id: 'key-1', apiKey: 'sk-test', name: CLI_KEY_NAME }, scope: 'copilot', workspaceId: null, workspaceBound: false, diff --git a/apps/sim/app/api/cli/auth/poll/route.ts b/apps/sim/app/api/cli/auth/poll/route.ts index 50517528091..da7beea3b27 100644 --- a/apps/sim/app/api/cli/auth/poll/route.ts +++ b/apps/sim/app/api/cli/auth/poll/route.ts @@ -1,6 +1,8 @@ +import type { ApiKeyAccess } from '@sim/auth/principal' import { createLogger } from '@sim/logger' import { type NextRequest, NextResponse } from 'next/server' import { pollCliAuthContract } from '@/lib/api/contracts' +import { DAY_MS } from '@/lib/api/contracts/api-keys' import { parseRequest } from '@/lib/api/server' import { apiKeyCreationErrorStatus, @@ -42,6 +44,19 @@ function cliKeyName(): string { return `CLI (${new Date().toISOString().slice(0, 19).replace('T', ' ')}Z)` } +/** + * Slack added to an approved lifetime. Key creation refuses an expiry under one + * day from its own clock reading, which comes a moment after this one, so a + * one-day approval would otherwise always be refused. + */ +const EXPIRY_SLACK_MS = 60_000 + +/** The expiry the approval asked for, counted from now; null for a key that never expires. */ +function approvedExpiresAt(grant: ApprovalGrant): string | null { + if (grant.expiresInDays === null) return null + return new Date(Date.now() + grant.expiresInDays * DAY_MS + EXPIRY_SLACK_MS).toISOString() +} + /** * Mints from the key space the approval recorded. * @@ -49,17 +64,20 @@ function cliKeyName(): string { * with second precision it means something genuinely unexpected, and silently * accumulating near-identical rows would hide it. */ -async function mintForGrant( - grant: ApprovalGrant -): Promise< - { ok: true; key: { id: string; apiKey: string } } | { ok: false; status: number; message: string } +async function mintForGrant(grant: ApprovalGrant): Promise< + | { + ok: true + key: { id: string; apiKey: string; name: string } + settings?: { access: ApiKeyAccess; expiresAt: string | null } + } + | { ok: false; status: number; message: string } > { const name = cliKeyName() if (grant.scope === 'copilot') { try { const key = await generateCopilotApiKey(grant.userId, name) - return { ok: true, key } + return { ok: true, key: { id: key.id, apiKey: key.apiKey, name } } } catch (error) { const status = error instanceof CopilotApiKeyError ? error.upstreamStatus : undefined return { ok: false, status: status ?? 500, message: 'Failed to generate copilot API key' } @@ -70,7 +88,9 @@ async function mintForGrant( // key to it is a separate decision made at approval, where the session exists // to check workspace-admin permission and the `api_keys.manage` capability. // Nothing in the poll body can set it, so this branch can only be reached by - // an approval that already passed both. + // an approval that already passed both. Access and lifetime come from the + // same approval, for the same reason. + const settings = { access: grant.access, expiresAt: approvedExpiresAt(grant) } const result = grant.workspaceBound && grant.workspaceId ? await performCreateWorkspaceApiKey({ @@ -78,8 +98,14 @@ async function mintForGrant( userId: grant.userId, name, source: 'cli', + ...settings, + }) + : await performCreatePersonalApiKey({ + userId: grant.userId, + name, + source: 'cli', + ...settings, }) - : await performCreatePersonalApiKey({ userId: grant.userId, name, source: 'cli' }) if (!result.success || !result.key) { return { @@ -89,7 +115,14 @@ async function mintForGrant( } } - return { ok: true, key: { id: result.key.id, apiKey: result.key.key } } + return { + ok: true, + key: { id: result.key.id, apiKey: result.key.key, name }, + settings: { + access: result.key.access, + expiresAt: result.key.expiresAt?.toISOString() ?? null, + }, + } } /** @@ -134,6 +167,8 @@ export const POST = withRouteHandler(async (request: NextRequest) => { scope: result.scope, workspaceId: result.workspaceId, workspaceBound: result.workspaceBound, + access: minted.settings?.access, + expiresAt: minted.settings?.expiresAt, }) return NextResponse.json({ status: 'complete', @@ -141,5 +176,6 @@ export const POST = withRouteHandler(async (request: NextRequest) => { scope: result.scope, workspaceId: result.workspaceId, workspaceBound: result.workspaceBound, + ...minted.settings, }) }) diff --git a/apps/sim/app/api/credentials/route.test.ts b/apps/sim/app/api/credentials/route.test.ts index 2491d6af8c0..73321238d2d 100644 --- a/apps/sim/app/api/credentials/route.test.ts +++ b/apps/sim/app/api/credentials/route.test.ts @@ -409,4 +409,44 @@ describe('POST /api/credentials', () => { expect(body).not.toContain('exact passphrase') expect(body).not.toContain('encrypted-oci-blob') }) + + it('forwards a password with surrounding spaces unchanged', async () => { + mockVerifyAndBuildServiceAccountSecret.mockRejectedValueOnce( + new TokenServiceAccountValidationError('invalid_credentials', 400, { step: 'verify' }) + ) + + await POST( + createMockRequest('POST', { + workspaceId: WORKSPACE_ID, + type: 'service_account', + providerId: 'servicenow-service-account', + apiToken: ' pass word ', + providerWorkspaceId: 'integration.user', + domain: 'acme.service-now.com', + }) + ) + + expect(mockVerifyAndBuildServiceAccountSecret).toHaveBeenCalledWith( + 'servicenow-service-account', + expect.objectContaining({ apiToken: ' pass word ' }) + ) + }) + + it('refuses a blank token before verifying it', async () => { + mockVerifyAndBuildServiceAccountSecret.mockClear() + + const response = await POST( + createMockRequest('POST', { + workspaceId: WORKSPACE_ID, + type: 'service_account', + providerId: 'servicenow-service-account', + apiToken: ' ', + providerWorkspaceId: 'integration.user', + domain: 'acme.service-now.com', + }) + ) + + expect(response.status).toBe(400) + expect(mockVerifyAndBuildServiceAccountSecret).not.toHaveBeenCalled() + }) }) diff --git a/apps/sim/app/api/table/[tableId]/rows/route.ts b/apps/sim/app/api/table/[tableId]/rows/route.ts index 9e4207bb70f..37b95bc65c8 100644 --- a/apps/sim/app/api/table/[tableId]/rows/route.ts +++ b/apps/sim/app/api/table/[tableId]/rows/route.ts @@ -124,6 +124,7 @@ export const GET = defineInternalJsonRoute({ limit: query.limit, offset: query.offset, after: query.after, + columns: query.columns, includeTotal: query.includeTotal, includeRunState: query.limit !== undefined && query.limit <= TABLE_LIMITS.MAX_QUERY_LIMIT, allowExpandedLimit: true, diff --git a/apps/sim/app/api/tools/custom/route.test.ts b/apps/sim/app/api/tools/custom/route.test.ts index 7a2b85a2218..748635d7c1e 100644 --- a/apps/sim/app/api/tools/custom/route.test.ts +++ b/apps/sim/app/api/tools/custom/route.test.ts @@ -3,6 +3,7 @@ */ import { authMockFns, + createRouteContext, hybridAuthMockFns, permissionsMock, permissionsMockFns, @@ -15,8 +16,9 @@ import { import { NextRequest } from 'next/server' import { afterAll, beforeEach, describe, expect, it, vi } from 'vitest' -const { mockUpsertCustomTools } = vi.hoisted(() => ({ +const { mockUpsertCustomTools, MockCustomToolTitleConflictError } = vi.hoisted(() => ({ mockUpsertCustomTools: vi.fn(), + MockCustomToolTitleConflictError: class extends Error {}, })) const mockGetUserEntityPermissions = permissionsMockFns.mockGetUserEntityPermissions @@ -82,11 +84,12 @@ vi.mock('@/lib/workspaces/permissions/utils', () => permissionsMock) vi.mock('@/lib/workflows/custom-tools/operations', () => ({ upsertCustomTools: (...args: unknown[]) => mockUpsertCustomTools(...args), + CustomToolTitleConflictError: MockCustomToolTitleConflictError, })) vi.mock('@/lib/workflows/utils', () => workflowsUtilsMock) -import { DELETE } from '@/app/api/tools/custom/route' +import { DELETE, POST } from '@/app/api/tools/custom/route' describe('Custom Tools API Routes', () => { const mockSession = { user: { id: 'user-123' } } @@ -113,6 +116,41 @@ describe('Custom Tools API Routes', () => { resetDbChainMock() }) + describe('POST /api/tools/custom', () => { + function saveRequest() { + return new NextRequest('http://localhost:3000/api/tools/custom', { + method: 'POST', + body: JSON.stringify({ + workspaceId: 'workspace-123', + tools: [{ title: sampleTools[0].title, schema: sampleTools[0].schema, code: 'return 1' }], + }), + }) + } + + it('answers a title the workspace already holds with 409, not 500', async () => { + mockUpsertCustomTools.mockRejectedValueOnce( + new MockCustomToolTitleConflictError('A tool with that title already exists') + ) + + const response = await POST(saveRequest(), createRouteContext({})) + + expect(response.status).toBe(409) + expect(await response.json()).toEqual({ error: 'A tool with that title already exists' }) + }) + + it('answers a unique violation from a concurrent save with 409', async () => { + mockUpsertCustomTools.mockRejectedValueOnce( + Object.assign(new Error('duplicate key value violates unique constraint'), { + code: '23505', + }) + ) + + const response = await POST(saveRequest(), createRouteContext({})) + + expect(response.status).toBe(409) + }) + }) + /** * Test DELETE endpoint */ diff --git a/apps/sim/app/api/tools/custom/route.ts b/apps/sim/app/api/tools/custom/route.ts index baf4b307621..cea8e2f0626 100644 --- a/apps/sim/app/api/tools/custom/route.ts +++ b/apps/sim/app/api/tools/custom/route.ts @@ -3,7 +3,7 @@ import { db } from '@sim/db' import { customTools } from '@sim/db/schema' import { createLogger } from '@sim/logger' import { authorizeWorkflowByWorkspacePermission } from '@sim/platform-authz/workflow' -import { getErrorMessage } from '@sim/utils/errors' +import { getErrorMessage, getPostgresErrorCode } from '@sim/utils/errors' import { and, desc, eq, isNull, or } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' import { @@ -16,7 +16,10 @@ import { checkSessionOrInternalAuth } from '@/lib/auth/hybrid' import { generateRequestId } from '@/lib/core/utils/request' import { withRouteHandler } from '@/lib/core/utils/with-route-handler' import { captureServerEvent } from '@/lib/posthog/server' -import { upsertCustomTools } from '@/lib/workflows/custom-tools/operations' +import { + CustomToolTitleConflictError, + upsertCustomTools, +} from '@/lib/workflows/custom-tools/operations' import { getUserEntityPermissions } from '@/lib/workspaces/permissions/utils' const logger = createLogger('CustomToolsAPI') @@ -176,6 +179,15 @@ export const POST = withRouteHandler(async (req: NextRequest) => { return NextResponse.json({ success: true, data: resultTools }) } catch (error) { + if (error instanceof CustomToolTitleConflictError) { + return NextResponse.json({ error: error.message }, { status: 409 }) + } + if (getPostgresErrorCode(error) === '23505') { + return NextResponse.json( + { error: 'A custom tool with that title already exists in this workspace' }, + { status: 409 } + ) + } logger.error('Error updating custom tools', error) const errorMessage = getErrorMessage(error, 'Failed to update custom tools') return NextResponse.json({ error: errorMessage }, { status: 500 }) diff --git a/apps/sim/app/api/v2/credentials/connections/route.ts b/apps/sim/app/api/v2/credentials/connections/route.ts index 7fef7f6ce2d..33fc0a9cb22 100644 --- a/apps/sim/app/api/v2/credentials/connections/route.ts +++ b/apps/sim/app/api/v2/credentials/connections/route.ts @@ -23,9 +23,10 @@ export const POST = defineV2JsonRoute({ errorPolicy: credentialConnectionErrorPolicy, mapInput: ({ body }) => body, useCase: createCredentialConnection, - present: ({ authorizationUrl, expiresAt }) => ({ + present: ({ authorizationUrl, draftId, expiresAt }) => ({ data: { authorizationUrl, + draftId, expiresAt: expiresAt.toISOString(), }, }), diff --git a/apps/sim/app/api/v2/lib/response.test.ts b/apps/sim/app/api/v2/lib/response.test.ts index 33b7f6e1ef5..4940b9da41f 100644 --- a/apps/sim/app/api/v2/lib/response.test.ts +++ b/apps/sim/app/api/v2/lib/response.test.ts @@ -1,4 +1,5 @@ import { describe, expect, it } from 'vitest' +import { z } from 'zod' import { InsufficientScopeError } from '@/lib/core/application' import { HttpError } from '@/lib/core/utils/http-error' import { @@ -6,6 +7,7 @@ import { v2Error, v2HttpError, v2RateLimitError, + v2ValidationError, } from '@/app/api/v2/lib/response' describe('v2 403 insufficient_scope challenge', () => { @@ -146,3 +148,21 @@ describe('v2 401 authentication challenge', () => { expect(response.headers.get('WWW-Authenticate')).toBe('Bearer realm="mcp"') }) }) + +describe('v2 validation error message', () => { + async function messageFor(schema: z.ZodType, value: unknown): Promise { + const parsed = schema.safeParse(value) + if (parsed.success) throw new Error('expected a validation failure') + return (await v2ValidationError(parsed.error, 'query').json()).error.message + } + + it('names the field once when the issue message already leads with its label', async () => { + const schema = z.object({ + enabled: z.string().refine((value) => value === 'true', 'enabled: expected one of "true"'), + }) + + await expect(messageFor(schema, { enabled: 'maybe' })).resolves.toBe( + 'query.enabled: expected one of "true"' + ) + }) +}) diff --git a/apps/sim/app/api/v2/lib/response.ts b/apps/sim/app/api/v2/lib/response.ts index 6b0db3d312e..82459661bec 100644 --- a/apps/sim/app/api/v2/lib/response.ts +++ b/apps/sim/app/api/v2/lib/response.ts @@ -1,3 +1,4 @@ +import { getRequestContext } from '@sim/logger' import { NextResponse } from 'next/server' import type { ZodError } from 'zod' import { @@ -7,7 +8,7 @@ import { } from '@/lib/api/contracts/v2/error-codes' import { REFILTERED_CURSOR_MESSAGE, UNREADABLE_CURSOR_MESSAGE } from '@/lib/api/cursor-binding' import { type CursorKey, INVALID_CURSOR_MESSAGE } from '@/lib/api/list-query' -import { getValidationErrorMessage, serializeZodIssues } from '@/lib/api/server' +import { getValidationErrorMessage, type RequestSlice, serializeZodIssues } from '@/lib/api/server' import { ADMISSION_RETRY_AFTER_SECONDS } from '@/lib/core/admission/transient-failure' import { ContentDeliveryUnobservedError, @@ -192,14 +193,26 @@ interface V2ErrorOptions { omitRetryAfter?: boolean } -/** `{ error: { code, message, details? } }`. */ +/** + * `{ error: { code, message, details?, requestId? } }`. + * + * `requestId` is the id the route handler stamps on the `x-request-id` response + * header (and logs as `requestId`), repeated in the body so a caller that only + * logs the body can still quote it to support. Absent outside a route + * handler's request context. + */ export function v2Error( code: V2ErrorCode, message: string, options: V2ErrorOptions = {} ): NextResponse { - const error: { code: V2ErrorCode; message: string; details?: unknown } = { code, message } + const error: { code: V2ErrorCode; message: string; details?: unknown; requestId?: string } = { + code, + message, + } if (options.details !== undefined) error.details = options.details + const requestId = getRequestContext()?.requestId + if (requestId) error.requestId = requestId const status = options.status ?? V2_ERROR_STATUS_BY_CODE[code] const retryAfterSeconds = options.omitRetryAfter ? undefined @@ -237,9 +250,24 @@ export function v2UploadDataPlaneError(): NextResponse { return v2Error('INTERNAL_ERROR', 'Internal server error') } -/** Render a contract `ZodError` as the v2 error envelope. */ -export function v2ValidationError(error: ZodError): NextResponse { - return v2Error('BAD_REQUEST', getValidationErrorMessage(error, 'Invalid request'), { +/** + * Render a contract `ZodError` as the v2 error envelope. The message is the + * first issue's, prefixed with where it failed (`body.limit: ...`) so a caller + * reading only the message knows which field to fix; `details` keeps every + * issue with its slice-relative path. A message that already leads with its + * field label (`enabled: expected one of ...`) drops it, so the field is named + * once. The CLI parses this prefix to restate it as a flag (`retypeApiError`). + */ +export function v2ValidationError(error: ZodError, slice?: RequestSlice): NextResponse { + const path = (error.issues[0]?.path ?? []).map(String) + const fieldLabel = path.length > 0 ? `${path.at(-1)}: ` : null + const issueMessage = getValidationErrorMessage(error, 'Invalid request') + const message = + fieldLabel && issueMessage.startsWith(fieldLabel) + ? issueMessage.slice(fieldLabel.length) + : issueMessage + const location = [slice, ...path].filter((part) => part !== undefined && part !== '').join('.') + return v2Error('BAD_REQUEST', location ? `${location}: ${message}` : message, { details: serializeZodIssues(error), }) } diff --git a/apps/sim/app/api/v2/meta/route.ts b/apps/sim/app/api/v2/meta/route.ts index 39231e06382..a46f4d65d9f 100644 --- a/apps/sim/app/api/v2/meta/route.ts +++ b/apps/sim/app/api/v2/meta/route.ts @@ -11,7 +11,7 @@ import { export const dynamic = 'force-dynamic' export const revalidate = 0 -/** GET /api/v2/meta — Report the calling credential's API availability and lifecycle. */ +/** GET /api/v2/meta — Report the calling credential's identity, API availability, and lifecycle. */ export const GET = defineV2JsonRoute({ contract: v2GetMetaContract, auth: v2ApiKeyAuth, @@ -24,7 +24,7 @@ export const GET = defineV2JsonRoute({ return { keyType: credential.keyType, expiresAt: credential.keyExpiresAt } }, useCase: readV2ApiCapabilities, - present: ({ v2Enabled, keyType, expiresAt, scopes }) => ({ - data: { v2Enabled, keyType, expiresAt: expiresAt?.toISOString() ?? null, scopes }, + present: ({ expiresAt, ...identity }) => ({ + data: { ...identity, expiresAt: expiresAt?.toISOString() ?? null }, }), }) diff --git a/apps/sim/app/api/v2/schedules/route.ts b/apps/sim/app/api/v2/schedules/route.ts new file mode 100644 index 00000000000..8839814a610 --- /dev/null +++ b/apps/sim/app/api/v2/schedules/route.ts @@ -0,0 +1,56 @@ +import { v2ListWorkspaceSchedulesContract } from '@/lib/api/contracts/v2/workflows' +import { cursorRoute, cursorScopeKey } from '@/lib/api/cursor-binding' +import { + defineV2JsonRoute, + v2ApiKeyAuth, + v2OrchestrationErrorPolicy, + v2RateLimits, +} from '@/lib/api/server/routes' +import { presentV2WorkspaceSchedule } from '@/lib/workflows/api' +import { workflowOperations } from '@/lib/workflows/application/operations' +import { listWorkspaceSchedules } from '@/lib/workflows/application/triggers' +import { readSortedCursor, writeSortedCursor } from '@/app/api/v2/lib/response' + +export const dynamic = 'force-dynamic' +export const revalidate = 0 + +/** Every param that changes which schedules this list returns. */ +function scheduleCursorFilters(query: { workspaceId: string }) { + return cursorScopeKey(cursorRoute(v2ListWorkspaceSchedulesContract), { + workspaceId: query.workspaceId, + }) +} + +/** + * GET /api/v2/schedules — health and timing of every schedule the workspace's + * deployed workflows run, disabled ones included. + */ +export const GET = defineV2JsonRoute({ + contract: v2ListWorkspaceSchedulesContract, + auth: v2ApiKeyAuth, + operation: workflowOperations.readTriggers, + rateLimit: v2RateLimits.publicApi, + errorPolicy: v2OrchestrationErrorPolicy, + mapInput: ({ query }) => ({ + workspaceId: query.workspaceId, + sortBy: query.sortBy, + sortOrder: query.sortOrder, + limit: query.limit, + cursorKeys: readSortedCursor( + query.cursor, + query.sortBy, + query.sortOrder, + scheduleCursorFilters(query) + ), + }), + useCase: listWorkspaceSchedules, + present: ({ schedules, nextCursorKeys }, { query }) => ({ + data: schedules.map(presentV2WorkspaceSchedule), + nextCursor: writeSortedCursor( + nextCursorKeys, + query.sortBy, + query.sortOrder, + scheduleCursorFilters(query) + ), + }), +}) diff --git a/apps/sim/app/api/v2/workflows/[workflowId]/deployments/chat/route.test.ts b/apps/sim/app/api/v2/workflows/[workflowId]/deployments/chat/route.test.ts index e5ce031b2bf..eb006d0d878 100644 --- a/apps/sim/app/api/v2/workflows/[workflowId]/deployments/chat/route.test.ts +++ b/apps/sim/app/api/v2/workflows/[workflowId]/deployments/chat/route.test.ts @@ -307,7 +307,7 @@ describe('/api/v2/workflows/[workflowId]/deployments/chat', () => { expect(response.status).toBe(400) expect((await response.json()).error.message).toBe( - 'password is required when authType is "password"' + 'body.password: password is required when authType is "password"' ) expect(mockPerformChatDeploy).not.toHaveBeenCalled() }) diff --git a/apps/sim/app/api/v2/workflows/[workflowId]/execute/route.test.ts b/apps/sim/app/api/v2/workflows/[workflowId]/execute/route.test.ts index 45e9ce4748f..f2cb5c0acd4 100644 --- a/apps/sim/app/api/v2/workflows/[workflowId]/execute/route.test.ts +++ b/apps/sim/app/api/v2/workflows/[workflowId]/execute/route.test.ts @@ -386,6 +386,7 @@ describe('POST /api/v2/workflows/[workflowId]/execute', () => { startedAt: '2026-07-31T00:00:00.000Z', endedAt: '2026-07-31T00:00:01.000Z', durationMs: 42, + executionTimeoutSeconds: 60, }, }) expect((await reader.read()).done).toBe(true) @@ -491,6 +492,7 @@ describe('POST /api/v2/workflows/[workflowId]/execute', () => { expect(body.data).toEqual({ runId: 'execution-123', statusUrl: 'http://localhost:3000/api/v2/workflows/workflow-1/runs/execution-123', + executionTimeoutSeconds: 300, }) expect(mockPreprocessExecution).toHaveBeenCalledWith( expect.objectContaining({ rateLimitCounter: 'async' }) @@ -1135,9 +1137,11 @@ describe('POST /api/v2/workflows/[workflowId]/execute', () => { endTime: '2026-07-31T00:00:01.000Z', }, } + /** A detached run is admitted under asynchronous limits, so it reports the async timeout. */ const receipt = { runId: 'execution-123', statusUrl: 'http://localhost:3000/api/v2/workflows/workflow-1/runs/execution-123', + executionTimeoutSeconds: 300, } /** A run the test settles by hand, recording the signal the executor was handed. */ diff --git a/apps/sim/app/api/v2/workflows/[workflowId]/execute/route.ts b/apps/sim/app/api/v2/workflows/[workflowId]/execute/route.ts index a97fe7c4d0a..39ec3bc305a 100644 --- a/apps/sim/app/api/v2/workflows/[workflowId]/execute/route.ts +++ b/apps/sim/app/api/v2/workflows/[workflowId]/execute/route.ts @@ -25,6 +25,7 @@ import { getWorkspaceBilledAccountUserId } from '@/lib/billing/core/attribution' import { tryAdmit } from '@/lib/core/admission/gate' import { ADMISSION_ERROR_DESCRIPTOR } from '@/lib/core/admission/transient-failure' import type { ForbiddenDetailCode } from '@/lib/core/application' +import { executionTimeoutSeconds } from '@/lib/core/execution-limits/types' import { acceptsMediaType } from '@/lib/core/utils/media-types' import { generateRequestId } from '@/lib/core/utils/request' import { getBaseUrl } from '@/lib/core/utils/urls' @@ -133,12 +134,17 @@ function presentRun(result: ExecuteWorkflowServiceRun) { startedAt: result.startedAt, endedAt: result.endedAt, durationMs: result.durationMs, + executionTimeoutSeconds: executionTimeoutSeconds(result.executionTimeoutMs), } } /** Where a run that continues past its response is followed: the v2 runs resource. */ -function runReceipt(workflowId: string, runId: string) { - return { runId, statusUrl: `${getBaseUrl()}/api/v2/workflows/${workflowId}/runs/${runId}` } +function runReceipt(workflowId: string, runId: string, executionTimeoutMs: number) { + return { + runId, + statusUrl: `${getBaseUrl()}/api/v2/workflows/${workflowId}/runs/${runId}`, + executionTimeoutSeconds: executionTimeoutSeconds(executionTimeoutMs), + } } /** A run that may outlive its response: past `afterMs` (never, when null) only its receipt is sent. */ @@ -633,7 +639,10 @@ export const POST = withRouteHandler( if ('pending' in result) { const detachment: RunDetachment | undefined = detachable - ? { afterMs: detachAfterMs, receipt: runReceipt(workflowId, result.executionId) } + ? { + afterMs: detachAfterMs, + receipt: runReceipt(workflowId, result.executionId, result.executionTimeoutMs), + } : undefined if (resultStream) { const response = streamPendingRun(result, requestId, ticket.release, detachment) @@ -665,7 +674,7 @@ export const POST = withRouteHandler( } if ('queued' in result) { - return v2Data(runReceipt(workflowId, result.executionId), { + return v2Data(runReceipt(workflowId, result.executionId, result.executionTimeoutMs), { status: 202, headers: { [V2_WORKFLOW_RUN_ID_HEADER]: result.executionId }, }) diff --git a/apps/sim/app/api/v2/workflows/[workflowId]/outline/route.ts b/apps/sim/app/api/v2/workflows/[workflowId]/outline/route.ts new file mode 100644 index 00000000000..0a73d8bd9f4 --- /dev/null +++ b/apps/sim/app/api/v2/workflows/[workflowId]/outline/route.ts @@ -0,0 +1,24 @@ +import { v2GetWorkflowOutlineContract } from '@/lib/api/contracts/v2/workflows' +import { defineV2JsonRoute, v2ApiKeyAuth, v2RateLimits } from '@/lib/api/server/routes' +import { v2WorkflowErrorPolicies } from '@/lib/workflows/api' +import { workflowOperations } from '@/lib/workflows/application/operations' +import { readWorkflowOutline } from '@/lib/workflows/application/read-workflow-graph' + +export const dynamic = 'force-dynamic' +export const revalidate = 0 + +/** + * GET /api/v2/workflows/[workflowId]/outline — the draft as a compact, + * read-only outline. Head-safe and unaudited for the same reasons as + * `GET /api/v2/workflows/{workflowId}/state`, which it projects. + */ +export const GET = defineV2JsonRoute({ + contract: v2GetWorkflowOutlineContract, + auth: v2ApiKeyAuth, + operation: workflowOperations.read, + rateLimit: v2RateLimits.publicApi, + errorPolicy: v2WorkflowErrorPolicies.concealWorkflowAuthorization, + mapInput: ({ params }) => ({ workflowId: params.workflowId }), + useCase: readWorkflowOutline, + present: ({ blocks }) => ({ data: { blocks } }), +}) diff --git a/apps/sim/app/api/v2/workflows/[workflowId]/route.ts b/apps/sim/app/api/v2/workflows/[workflowId]/route.ts index ea42f70f681..1a0240d34b1 100644 --- a/apps/sim/app/api/v2/workflows/[workflowId]/route.ts +++ b/apps/sim/app/api/v2/workflows/[workflowId]/route.ts @@ -34,7 +34,7 @@ export const GET = defineV2JsonRoute({ errorPolicy: v2WorkflowErrorPolicies.concealWorkflowAuthorization, mapInput: ({ params }) => ({ workflowId: params.workflowId }), useCase: readWorkflow, - present: ({ workflow, workspaceId, folderPath, inputs }) => ({ + present: ({ workflow, workspaceId, folderPath, inputs, inputSchema }) => ({ data: { id: workflow.id, webUrl: workspaceResourceWebUrl(getBaseUrl(), workspaceId, 'workflow', workflow.id), @@ -48,6 +48,7 @@ export const GET = defineV2JsonRoute({ lastRunAt: workflow.lastRunAt?.toISOString() ?? null, variables: (workflow.variables as Record | null) ?? {}, inputs, + inputSchema, createdAt: workflow.createdAt.toISOString(), updatedAt: workflow.updatedAt.toISOString(), }, diff --git a/apps/sim/app/api/v2/workflows/[workflowId]/runs/[runId]/route.ts b/apps/sim/app/api/v2/workflows/[workflowId]/runs/[runId]/route.ts index 9bbf5c353a6..dffdb4e639a 100644 --- a/apps/sim/app/api/v2/workflows/[workflowId]/runs/[runId]/route.ts +++ b/apps/sim/app/api/v2/workflows/[workflowId]/runs/[runId]/route.ts @@ -6,7 +6,7 @@ import { defineV2JsonRoute, v2ApiKeyAuth, v2RateLimits } from '@/lib/api/server/ import { v2WorkflowErrorPolicies } from '@/lib/workflows/api' import { workflowOperations } from '@/lib/workflows/application/operations' import { readWorkflowRun } from '@/lib/workflows/application/read-workflow-run' -import { classifyExecutionError } from '@/executor/utils/errors' +import { classifyRecordedExecutionError } from '@/executor/utils/errors' export const dynamic = 'force-dynamic' @@ -53,7 +53,7 @@ export const GET = defineV2JsonRoute({ }) : null, cost: status.cost, - error: status.error ? classifyExecutionError(new Error(status.error)) : null, + error: status.error ? classifyRecordedExecutionError(status.error, status.failedBlock) : null, output: status.finalOutput, blockOutputs: status.blockOutputs, files: status.files, diff --git a/apps/sim/app/api/v2/workflows/runs/paused/route.ts b/apps/sim/app/api/v2/workflows/runs/paused/route.ts new file mode 100644 index 00000000000..12ec6a64e53 --- /dev/null +++ b/apps/sim/app/api/v2/workflows/runs/paused/route.ts @@ -0,0 +1,60 @@ +import { type V2PausedRun, v2ListPausedRunsContract } from '@/lib/api/contracts/v2/workflows' +import { cursorRoute, cursorScopeKey } from '@/lib/api/cursor-binding' +import { + defineV2JsonRoute, + v2ApiKeyAuth, + v2OrchestrationErrorPolicy, + v2RateLimits, +} from '@/lib/api/server/routes' +import { listPausedRuns } from '@/lib/workflows/application/list-paused-runs' +import { workflowOperations } from '@/lib/workflows/application/operations' +import { readSortedCursor, writeSortedCursor } from '@/app/api/v2/lib/response' + +export const dynamic = 'force-dynamic' +export const revalidate = 0 + +/** Every param that changes which paused runs, in which order, this list returns. */ +function pausedRunCursorFilters(query: { workspaceId: string }) { + return cursorScopeKey(cursorRoute(v2ListPausedRunsContract), { + workspaceId: query.workspaceId, + }) +} + +/** List a workspace's runs waiting on human input, with the points they wait at. */ +export const GET = defineV2JsonRoute({ + contract: v2ListPausedRunsContract, + auth: v2ApiKeyAuth, + operation: workflowOperations.listPausedRuns, + rateLimit: v2RateLimits.publicApi, + errorPolicy: v2OrchestrationErrorPolicy, + mapInput: ({ query }) => ({ + workspaceId: query.workspaceId, + includeOutput: query.includeOutput, + sortOrder: query.sortOrder, + cursorKeys: readSortedCursor( + query.cursor, + query.sortBy, + query.sortOrder, + pausedRunCursorFilters(query) + ), + limit: query.limit, + }), + useCase: listPausedRuns, + present: ({ runs, nextCursorKeys }, { query }) => ({ + data: runs.map( + (run): V2PausedRun => ({ + runId: run.runId, + workflowId: run.workflowId, + workflowName: run.workflowName, + pausedAt: run.pausedAt.toISOString(), + points: run.points, + }) + ), + nextCursor: writeSortedCursor( + nextCursorKeys, + query.sortBy, + query.sortOrder, + pausedRunCursorFilters(query) + ), + }), +}) diff --git a/apps/sim/app/api/v2/workspaces/[workspaceId]/route.ts b/apps/sim/app/api/v2/workspaces/[workspaceId]/route.ts index bdcd9092d8b..8db098a1ac3 100644 --- a/apps/sim/app/api/v2/workspaces/[workspaceId]/route.ts +++ b/apps/sim/app/api/v2/workspaces/[workspaceId]/route.ts @@ -1,8 +1,26 @@ -import { v2GetWorkspaceContract } from '@/lib/api/contracts/v2/workspaces' +import { + v2GetWorkspaceContract, + v2UpdateWorkspaceContract, +} from '@/lib/api/contracts/v2/workspaces' import { defineV2JsonRoute, v2ApiKeyAuth, v2RateLimits } from '@/lib/api/server/routes' import { v2WorkspaceErrorPolicies } from '@/lib/workspaces/api/route-policies' import { getPublicWorkspace } from '@/lib/workspaces/application/get-public-workspace' -import { workspaceOperations } from '@/lib/workspaces/application/operations' +import { + workspaceOperations, + workspaceUpdateOperations, +} from '@/lib/workspaces/application/operations' +import { updateWorkspaceIdentity } from '@/lib/workspaces/application/update-workspace' +import type { PublicWorkspaceDetail } from '@/lib/workspaces/public-queries' + +function presentWorkspace(workspace: PublicWorkspaceDetail) { + return { + data: { + ...workspace, + createdAt: workspace.createdAt.toISOString(), + updatedAt: workspace.updatedAt.toISOString(), + }, + } +} /** GET /api/v2/workspaces/[workspaceId] — Public workspace metadata. */ export const GET = defineV2JsonRoute({ @@ -13,11 +31,17 @@ export const GET = defineV2JsonRoute({ errorPolicy: v2WorkspaceErrorPolicies.concealWorkspaceAuthorization, mapInput: ({ params }) => ({ workspaceId: params.workspaceId }), useCase: getPublicWorkspace, - present: ({ workspace }) => ({ - data: { - ...workspace, - createdAt: workspace.createdAt.toISOString(), - updatedAt: workspace.updatedAt.toISOString(), - }, - }), + present: ({ workspace }) => presentWorkspace(workspace), +}) + +/** PATCH /api/v2/workspaces/[workspaceId] — Rename a workspace or change its logo. */ +export const PATCH = defineV2JsonRoute({ + contract: v2UpdateWorkspaceContract, + auth: v2ApiKeyAuth, + operation: workspaceUpdateOperations.updateIdentity, + rateLimit: v2RateLimits.publicApi, + errorPolicy: v2WorkspaceErrorPolicies.concealWorkspaceAuthorization, + mapInput: ({ params, body }) => ({ workspaceId: params.workspaceId, patch: body }), + useCase: updateWorkspaceIdentity, + present: ({ workspace }) => presentWorkspace(workspace), }) diff --git a/apps/sim/app/api/workspaces/[id]/route.ts b/apps/sim/app/api/workspaces/[id]/route.ts index db049b18fe5..8febfed45cc 100644 --- a/apps/sim/app/api/workspaces/[id]/route.ts +++ b/apps/sim/app/api/workspaces/[id]/route.ts @@ -24,10 +24,8 @@ import { NoWorkspaceAccessError, } from '@/lib/core/application' import { captureServerEvent } from '@/lib/posthog/server' -import { - updateWorkspace, - workspaceUpdateOperations, -} from '@/lib/workspaces/application/update-workspace' +import { workspaceUpdateOperations } from '@/lib/workspaces/application/operations' +import { updateWorkspace } from '@/lib/workspaces/application/update-workspace' import { archiveWorkspace } from '@/lib/workspaces/lifecycle' import { presentWorkspace } from '@/lib/workspaces/response' diff --git a/apps/sim/app/cli/auth/cli-auth-request.ts b/apps/sim/app/cli/auth/cli-auth-request.ts index 57a849b97b0..8331cc47775 100644 --- a/apps/sim/app/cli/auth/cli-auth-request.ts +++ b/apps/sim/app/cli/auth/cli-auth-request.ts @@ -1,4 +1,9 @@ -import type { CliAuthScope } from '@/lib/api/contracts/cli-auth' +import type { ApiKeyAccess } from '@sim/auth/principal' +import { + CLI_AUTH_MAX_EXPIRY_DAYS, + CLI_AUTH_MIN_EXPIRY_DAYS, + type CliAuthScope, +} from '@/lib/api/contracts/cli-auth' /** BASE64URL, 43 chars (request id or SHA-256 challenge), no padding. */ const BASE64URL_43 = /^[A-Za-z0-9\-_]{43}$/ @@ -18,6 +23,10 @@ export interface CliAuthRequest { scope: CliAuthScope /** Workspace the terminal suggests preselecting. A hint only — never authority. */ suggestedWorkspaceId: string | null + /** Platform scope only: the access the terminal asked the key to have. */ + access: ApiKeyAccess + /** Platform scope only: days until the key expires, or null for a key that never does. */ + expiresInDays: number | null } export type CliAuthRequestResolution = @@ -30,6 +39,8 @@ interface RawCliAuthParams { pairing: string | null scope: CliAuthScope workspace: string | null + access: string | null + expires: string | null } /** @@ -42,6 +53,8 @@ export function resolveCliAuthRequest({ pairing, scope, workspace, + access, + expires, }: RawCliAuthParams): CliAuthRequestResolution { if (!request || !challenge || !pairing) { return { valid: false, reason: 'This link is missing the parameters the Sim CLI sends.' } @@ -55,8 +68,39 @@ export function resolveCliAuthRequest({ return { valid: false, reason: 'The pairing code is malformed.' } } + if ((access !== null || expires !== null) && scope !== 'platform') { + return { valid: false, reason: 'Key access and expiry only apply to Sim API keys.' } + } + + const keyAccess = access ?? 'write' + if (keyAccess !== 'read' && keyAccess !== 'write') { + return { valid: false, reason: 'The requested key access is malformed.' } + } + + const expiresInDays = expires === null ? null : Number(expires) + if ( + expires !== null && + (!/^\d+$/.test(expires) || + expiresInDays === null || + expiresInDays < CLI_AUTH_MIN_EXPIRY_DAYS || + expiresInDays > CLI_AUTH_MAX_EXPIRY_DAYS) + ) { + return { + valid: false, + reason: `The key expiry must be a whole number of days from ${CLI_AUTH_MIN_EXPIRY_DAYS} to ${CLI_AUTH_MAX_EXPIRY_DAYS}.`, + } + } + return { valid: true, - request: { request, challenge, pairing, scope, suggestedWorkspaceId: workspace || null }, + request: { + request, + challenge, + pairing, + scope, + suggestedWorkspaceId: workspace || null, + access: keyAccess, + expiresInDays, + }, } } diff --git a/apps/sim/app/cli/auth/cli-auth-view.test.tsx b/apps/sim/app/cli/auth/cli-auth-view.test.tsx index 8a50017df7a..da83c5409d0 100644 --- a/apps/sim/app/cli/auth/cli-auth-view.test.tsx +++ b/apps/sim/app/cli/auth/cli-auth-view.test.tsx @@ -22,6 +22,8 @@ vi.mock('nuqs', () => ({ pairing: 'ABCD-2345', scope: 'platform', workspace: null, + access: null, + expires: null, }, ], })) diff --git a/apps/sim/app/cli/auth/cli-auth-view.tsx b/apps/sim/app/cli/auth/cli-auth-view.tsx index d96486d4296..599bdaba960 100644 --- a/apps/sim/app/cli/auth/cli-auth-view.tsx +++ b/apps/sim/app/cli/auth/cli-auth-view.tsx @@ -94,6 +94,17 @@ export function CliAuthView() { : (selected ?? suggested ?? workspaces.data?.lastActiveWorkspaceId ?? null) const chosen = workspaces.data?.workspaces.find((w) => w.id === workspaceId) + /** + * The terminal's requested access and lifetime are stated before approval: + * they change what the key can do, and this card is the only place the user + * consents to them. + */ + const keyLifetime = + request.expiresInDays === null + ? ' that never expires' + : ` that expires in ${request.expiresInDays} ${request.expiresInDays === 1 ? 'day' : 'days'}` + const keyDescription = `${request.access === 'read' ? 'a read-only' : 'a'} personal key tied to your account${keyLifetime}` + return (
)} @@ -166,6 +177,8 @@ export function CliAuthView() { // always mints a personal key so the profile can switch workspaces. ...(isPlatform && chosen ? { workspaceId: chosen.id } : {}), bindKeyToWorkspace: false, + access: request.access, + expiresInDays: request.expiresInDays ?? undefined, }, { onSuccess: () => router.push('/cli/auth/done') } ) diff --git a/apps/sim/app/cli/auth/page.test.tsx b/apps/sim/app/cli/auth/page.test.tsx index 24c9a7e2bbc..aa121932f58 100644 --- a/apps/sim/app/cli/auth/page.test.tsx +++ b/apps/sim/app/cli/auth/page.test.tsx @@ -42,4 +42,26 @@ describe('CliAuthPage signed-out bounce', () => { `NEXT_REDIRECT:/signup?callbackUrl=${EXPECTED_CALLBACK}` ) }) + + it('keeps a requested read-only, expiring platform key through the bounce', async () => { + const error = await CliAuthPage({ + searchParams: Promise.resolve({ + request: REQUEST, + challenge: CHALLENGE, + pairing: PAIRING, + scope: 'platform', + access: 'read', + expires: '30', + }), + }).catch((e: unknown) => e) + + const location = new URL( + String((error as Error).message).replace('NEXT_REDIRECT:', ''), + 'https://sim.test' + ) + const callback = new URL(location.searchParams.get('callbackUrl') ?? '', 'https://sim.test') + expect(callback.searchParams.get('scope')).toBe('platform') + expect(callback.searchParams.get('access')).toBe('read') + expect(callback.searchParams.get('expires')).toBe('30') + }) }) diff --git a/apps/sim/app/cli/auth/page.tsx b/apps/sim/app/cli/auth/page.tsx index e49a47be4cc..ecfc2a9bf8a 100644 --- a/apps/sim/app/cli/auth/page.tsx +++ b/apps/sim/app/cli/auth/page.tsx @@ -9,7 +9,10 @@ import { AuthShell } from '@/app/(auth)/components' import { resolveCliAuthRequest } from '@/app/cli/auth/cli-auth-request' import { CliAuthView } from '@/app/cli/auth/cli-auth-view' import { CliAuthLoading } from '@/app/cli/auth/loading' -import { cliAuthSearchParamsCache } from '@/app/cli/auth/search-params' +import { + cliAuthSearchParamsCache, + serializeCliAuthSearchParams, +} from '@/app/cli/auth/search-params' export const metadata: Metadata = { title: 'Connect your terminal', @@ -50,14 +53,9 @@ export default async function CliAuthPage({ const resolution = resolveCliAuthRequest(params) if (resolution.valid && !session?.user) { - const query = new URLSearchParams({ - request: resolution.request.request, - challenge: resolution.request.challenge, - pairing: resolution.request.pairing, - }) redirect( buildAuthCrossLink(isRegistrationDisabled ? '/login' : '/signup', { - callbackUrl: `/cli/auth?${query}`, + callbackUrl: serializeCliAuthSearchParams('/cli/auth', params), isInviteFlow: false, }) ) diff --git a/apps/sim/app/cli/auth/search-params.ts b/apps/sim/app/cli/auth/search-params.ts index 375a1c77ab3..012375ea93b 100644 --- a/apps/sim/app/cli/auth/search-params.ts +++ b/apps/sim/app/cli/auth/search-params.ts @@ -1,4 +1,9 @@ -import { createSearchParamsCache, parseAsString, parseAsStringLiteral } from 'nuqs/server' +import { + createSearchParamsCache, + createSerializer, + parseAsString, + parseAsStringLiteral, +} from 'nuqs/server' /** Key spaces the handoff can mint from. Mirrors `cliAuthScopeSchema`. */ export const CLI_AUTH_SCOPES = ['copilot', 'platform'] as const @@ -15,6 +20,11 @@ export const CLI_AUTH_SCOPES = ['copilot', 'platform'] as const * — which sent no scope — still lands on the key space it expects. `workspace` * is only a preselection hint for the picker; the workspace that ends up bound * to the key is the one the user confirms, and it is re-authorized server-side. + * + * `access` and `expires` (whole days) are what the terminal asked the platform + * key to be. Absent means a full-access key that never expires. Read as plain + * strings so a malformed value is refused by `resolveCliAuthRequest` rather than + * parsed to null and approved as the full-access, never-expiring default. */ export const cliAuthParsers = { request: parseAsString, @@ -22,6 +32,8 @@ export const cliAuthParsers = { pairing: parseAsString, scope: parseAsStringLiteral(CLI_AUTH_SCOPES).withDefault('copilot'), workspace: parseAsString, + access: parseAsString, + expires: parseAsString, } as const /** @@ -29,3 +41,10 @@ export const cliAuthParsers = { * login bounce from exactly the values the client component will re-read. */ export const cliAuthSearchParamsCache = createSearchParamsCache(cliAuthParsers) + +/** + * Re-serializes the handoff for the signed-out login bounce. Only params the + * page understands survive, and a value the terminal asked for — a read-only + * key, say — is carried through rather than silently dropped on the way back. + */ +export const serializeCliAuthSearchParams = createSerializer(cliAuthParsers) diff --git a/apps/sim/app/o/[organizationId]/layout.tsx b/apps/sim/app/o/[organizationId]/layout.tsx index 4f9527a7af7..ddaa31bf583 100644 --- a/apps/sim/app/o/[organizationId]/layout.tsx +++ b/apps/sim/app/o/[organizationId]/layout.tsx @@ -3,7 +3,6 @@ import { dehydrate, HydrationBoundary } from '@tanstack/react-query' import { cookies } from 'next/headers' import { redirect } from 'next/navigation' import { SettingsNavigationProvider } from '@/components/settings/settings-navigation-provider' -import { isReadOnlyApiKeyCreationEnabled } from '@/lib/api-key/feature-flag' import { getSession } from '@/lib/auth' import { getActiveOrganizationId } from '@/lib/auth/session-response' import { canUseBenchmarks } from '@/lib/benchmarks/config' @@ -90,7 +89,6 @@ export default async function OrganizationLayout({ simLinksEnabled, peopleEnabled, simLinkOrganizationId, - readOnlyApiKeysEnabled, ] = await Promise.all([ prefetchOrganizationSidebar( queryClient, @@ -115,7 +113,6 @@ export default async function OrganizationLayout({ userId: session.user.id, sessionId: session.session.id, }), - isReadOnlyApiKeyCreationEnabled(), ]) const initialSidebarCollapsed = cookieStore.get('sidebar_collapsed')?.value === '1' @@ -131,7 +128,6 @@ export default async function OrganizationLayout({ 'mothership-plan-mode': benchmarkEnabled, 'mothership-memory-spaces': benchmarkEnabled, projects: projectsEnabled, - 'api-key-read-only': readOnlyApiKeysEnabled, }} > diff --git a/apps/sim/app/oauth/credential-connected/page.tsx b/apps/sim/app/oauth/credential-connected/page.tsx index 135514232b3..9a714c2da12 100644 --- a/apps/sim/app/oauth/credential-connected/page.tsx +++ b/apps/sim/app/oauth/credential-connected/page.tsx @@ -1,5 +1,6 @@ import { AuthLinkButton as ChipLink, AuthStatusContent as StatusPageContent } from '@sim/auth-ui' import type { Metadata } from 'next' +import { OAUTH_LINK_UNAVAILABLE_ERROR } from '@/lib/credentials/draft-constants' import { APP_ENTRY_PATH } from '@/lib/navigation/paths' import { AuthShell as LogoShell } from '@/app/(auth)/components' @@ -27,7 +28,9 @@ export default async function CredentialConnectedPage({ description={ connected ? 'The credential is ready to use. You can close this tab and return to the app that started the connection.' - : 'The credential could not be connected. Return to the app that started the connection and try again.' + : error === OAUTH_LINK_UNAVAILABLE_ERROR + ? 'This connection link has expired or belongs to a different Sim account. Sign in as the account that created the link, or create a new one.' + : 'The credential could not be connected. Return to the app that started the connection and try again.' } > diff --git a/apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/rich-markdown-editor/list-item.ts b/apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/rich-markdown-editor/list-item.ts index 763d71426cd..0c834b3339a 100644 --- a/apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/rich-markdown-editor/list-item.ts +++ b/apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/rich-markdown-editor/list-item.ts @@ -12,6 +12,8 @@ function listItemMarker(context: RenderContext): string { /** * Markdown items may start with a nested block, but the list-item schema requires a paragraph * first. Supply that empty paragraph before Yjs hydration can discard the invalid subtree. + * Upstream demotes an opening heading (`- # Title`) to the item's paragraph, dropping its level; + * CommonMark reads it as a heading inside the item, so parse it as a block like any other. * * Nested blocks keep the two-space indent under every marker. Upstream aligns ordered-item * children to the marker width, but its ordered-list tokenizer strips one column less, so a @@ -20,7 +22,13 @@ function listItemMarker(context: RenderContext): string { */ export const MarkdownListItem = ListItem.extend({ parseMarkdown(token, helpers) { - const parsed = this.parent?.(token, helpers) ?? [] + const parsed = + token.type === 'list_item' && token.tokens?.[0]?.type === 'heading' + ? { + type: this.name, + content: (helpers.parseBlockChildren ?? helpers.parseChildren)(token.tokens), + } + : (this.parent?.(token, helpers) ?? []) if (Array.isArray(parsed)) return parsed const content = (parsed.content ?? []).flatMap(splitBlockImageParagraph) if (content[0]?.type !== 'paragraph') content.unshift({ type: 'paragraph' }) diff --git a/apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/text-editor.tsx b/apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/text-editor.tsx index 29954e3d954..545b44c43a4 100644 --- a/apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/text-editor.tsx +++ b/apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/text-editor.tsx @@ -146,25 +146,25 @@ const MonacoEditor = dynamic( getWorker(_: string, label: string) { if (label === 'json') { return new Worker( - new URL('monaco-editor/esm/vs/language/json/json.worker', import.meta.url) + new URL('monaco-editor/languages/features/json/json.worker', import.meta.url) ) } if (label === 'css' || label === 'scss' || label === 'less') { return new Worker( - new URL('monaco-editor/esm/vs/language/css/css.worker', import.meta.url) + new URL('monaco-editor/languages/features/css/css.worker', import.meta.url) ) } if (label === 'html' || label === 'handlebars' || label === 'razor') { return new Worker( - new URL('monaco-editor/esm/vs/language/html/html.worker', import.meta.url) + new URL('monaco-editor/languages/features/html/html.worker', import.meta.url) ) } if (label === 'typescript' || label === 'javascript') { return new Worker( - new URL('monaco-editor/esm/vs/language/typescript/ts.worker', import.meta.url) + new URL('monaco-editor/languages/features/typescript/ts.worker', import.meta.url) ) } - return new Worker(new URL('monaco-editor/esm/vs/editor/editor.worker', import.meta.url)) + return new Worker(new URL('monaco-editor/editor/editor.worker', import.meta.url)) }, } } diff --git a/apps/sim/app/workspace/[workspaceId]/home/components/message-content/components/chat-content/chat-content.tsx b/apps/sim/app/workspace/[workspaceId]/home/components/message-content/components/chat-content/chat-content.tsx index f1fdb49e952..a5dc59d2132 100644 --- a/apps/sim/app/workspace/[workspaceId]/home/components/message-content/components/chat-content/chat-content.tsx +++ b/apps/sim/app/workspace/[workspaceId]/home/components/message-content/components/chat-content/chat-content.tsx @@ -189,6 +189,14 @@ function SourceReference({ index, children }: SourceReferenceProps) { ) } +/** + * Placeholder URLs Streamdown's streaming parser substitutes for a link or image + * whose URL is still arriving. Neither resolves, so a pending link renders as + * its text and a pending image is withheld until the URL completes. + */ +const INCOMPLETE_LINK_HREF = 'streamdown:incomplete-link' +const INCOMPLETE_IMAGE_SRC = 'streamdown:incomplete-image' + const WORKSPACE_LINK_PREFIX = '#sim-workspace-ref-' const WorkspaceRefsContext = createContext<{ resources: WorkspaceResourceTagData[] @@ -375,6 +383,7 @@ const MARKDOWN_COMPONENTS = { href?: string 'data-footnote-ref'?: boolean }) { + if (href === INCOMPLETE_LINK_HREF) return <>{children} if (href?.startsWith(SOURCE_LINK_PREFIX)) { return ( @@ -488,7 +497,7 @@ const MARKDOWN_COMPONENTS = { return {children} }, img({ src, alt }: ComponentPropsWithoutRef<'img'>) { - if (typeof src !== 'string' || !src) return null + if (typeof src !== 'string' || !src || src === INCOMPLETE_IMAGE_SRC) return null return (