mirror of
https://github.com/simstudioai/sim.git
synced 2026-09-21 13:00:04 +08:00
* feat(byok): add organization key management * feat(byok): inherit organization keys at runtime * feat(byok): add organization scope to BYOK settings * fix(byok): refresh org key state after mutations * fix(byok): hide stale inherited status badges * chore(db): drop colliding byok migration ahead of staging merge Staging independently claimed 0293. Remove ours so the merge is clean; it is regenerated at the next free index right after. * chore(db): regenerate byok migration at 0296 Staging claimed 0293-0295 during the merge; the regenerated SQL is byte-identical to the dropped 0293. * docs(byok): document organization scope, precedence, and the full provider list The BYOK section described workspace-scoped keys only. Add the organization scope, its Enterprise requirement, the per-provider precedence rule, what an entitlement lapse does, and the Pi sandbox exposure. Refresh the provider table from the settings page, which had drifted from 14 to 34 entries. * feat(byok): open organization keys to every organization plan Organization BYOK was gated on Enterprise, but an organization is the only thing that can hold the keys, so every plan that can own an organization should qualify — Pro for Teams, Max for Teams, and Enterprise. Add checkOrgPlan/resolveOrganizationPlan beside the Enterprise pair rather than widening checkEnterprisePlan, so the Enterprise-only gates (Access Control, whitelabeling) are untouched, and restore resolveOrganizationEnterprisePlan to module-private now that BYOK no longer needs it. * perf(byok): cache the organization entitlement, not the key material getBYOKKey runs once per agent block and once per hosted-capable tool call, so a loop over N items resolved N times — and each organization-inheriting resolution paid three sequential billing queries on top of the two key reads. Split the two reads by staleness tolerance. Key rows stay fresh, because revocation must be immediate. The entitlement is a billing gate that tolerates bounded staleness in the harmless direction (a lapsed organization keeps using its own key for <=60s), so cache it per organization with an in-flight share so concurrent blocks issue one query set. The management surfaces keep reading it fresh, so an organization that just upgraded is never told otherwise. Also run the block check and subscription read in parallel inside resolveOrganizationPlan, and carry the resolved scope on BYOKKeyResult so a log line can say whether a run used the workspace's key or an inherited one. * feat(byok): let workspaces store the Z.ai and Cohere keys the runtime reads Both ids were already in the BYOK contract enum and both are resolved at execution time — getApiKeyWithBYOK reaches 'zai' (GLM models are in the hosted catalog, so the BYOK branch runs), and 'cohere' backs both the Embeddings block and Knowledge Base reranking — but neither appeared in the settings list, so there was no way to store the key either path looks for. Cohere had no icon; add one from the official multi-color mark so it stays legible on a light and a dark page. Cohere's embed-v4.0 is kbEligible:false, so the description says 'Embeddings and Knowledge Base reranking' rather than claiming KB embeddings. * improvement(byok): shorten the workspace scope chip to 'Workspace' It sits beside 'Organization', so the scope reads from the pair; 'This' only added width. * fix(byok): do not cache a billing outage as an unentitled organization resolveOrganizationPlan maps a failed billing read to false, which is indistinguishable from a real plan lapse. The entitlement cache stored that, so one transient outage held the gate shut for the full TTL and every inheriting run silently fell back to a metered hosted key — and the cache's rejection path, which exists to prevent exactly this, was unreachable. Give the resolver the onError option its neighbours already have and let the cached read ask for 'throw', so a failure stays out of the cache and the next resolution retries. Behavior for the call that saw the error is unchanged: getBYOKKey still fails closed. Reported by Cursor Bugbot. * fix(byok): propagate the subscription read's failure too The previous commit threaded onError through resolveOrganizationPlan's own catch, but getOrganizationSubscriptionUsable soft-fails to null on its own, so a failed subscription read still arrived as an ordinary 'no usable subscription' and returned a successful false — which the entitlement cache then stored for the full TTL. Thread the option into that call as well. Test it at the billing layer rather than the cache layer: the entitlement test mocks resolveOrganizationPlan wholesale, so it could never have caught this. Verified the new test fails against the previous commit. Reported by Cursor Bugbot. * refactor(byok): cache the entitlement with LRUCache, like copilot entitlements The hand-rolled version reinvented three things the codebase already has a canonical answer for. lru-cache is a declared dependency of apps/sim and lib/copilot/entitlements.ts already caches an entitlement with it — by storing the in-flight Promise, which is what makes concurrent callers collapse onto one resolution with no in-flight bookkeeping at all. TTL and the size bound come from the library. That removes the second Map, the manual eviction (and its interaction with an in-flight entry), and the dead value-while-refreshing state: 23 executable lines. The one thing the library does not cover is dropping a rejected promise so a billing outage is not cached for the TTL, which is kept and pinned by a test that fails without it. TTL expiry is no longer re-tested — that is the library's behavior, not ours, and lru-cache reads its clock at module load so faking timers never moved it. * refactor(byok): coalesce the entitlement read with the shared singleflight lib/concurrency/singleflight.ts is the codebase's coalescing primitive and oauth/credential-service.ts already pairs it with a read-through cache. Adopting that shape fixes a case caching the promise directly did not: a *hung* billing read wedged every caller for the full 60s TTL, where coalesceLocally evicts and rejects at its settle deadline. It also removes the hand-rolled rejection eviction — the cache is written only on the success path, so an outage leaves no entry by construction. The cache now holds booleans, which introduces the one trap worth a test: a truthiness check would read a cached false as a miss and re-query billing on every resolution for lapsed organizations. Pinned. * fix(byok): keep an abandoned entitlement producer from writing the cache coalesceLocally does not cancel a producer it timed out — its docstring says so explicitly — so writing the cache from inside the producer let a late billing result overwrite a fresher answer a retry had already cached, and hold it for a full TTL. Move the write onto the value the caller actually received. A caller that timed out throws before reaching it, so an abandoned producer now resolves into nothing. The test reproduces the overwrite and fails against the previous shape. Reported by Cursor Bugbot. --------- Co-authored-by: Bill Leoutsakos <billleoutsakos@Bills-MacBook-Pro.local> Co-authored-by: Vikhyath Mondreti <vikhyath@simstudio.ai>
1337 lines
53 KiB
JSON
1337 lines
53 KiB
JSON
{
|
||
"openapi": "3.1.0",
|
||
"info": {
|
||
"title": "Sim API v2 — Logs",
|
||
"description": "Version 2 of the Sim REST API for listing workflow execution logs and retrieving complete diagnostic run snapshots.",
|
||
"version": "2.0.0",
|
||
"contact": {
|
||
"name": "Sim Support",
|
||
"email": "help@sim.ai",
|
||
"url": "https://www.sim.ai"
|
||
},
|
||
"license": {
|
||
"name": "Apache 2.0",
|
||
"url": "https://www.apache.org/licenses/LICENSE-2.0.html"
|
||
}
|
||
},
|
||
"servers": [
|
||
{
|
||
"url": "https://www.sim.ai",
|
||
"description": "Production"
|
||
}
|
||
],
|
||
"tags": [
|
||
{
|
||
"name": "Logs",
|
||
"description": "Query workflow execution logs and retrieve complete run diagnostics."
|
||
}
|
||
],
|
||
"security": [
|
||
{
|
||
"apiKey": []
|
||
}
|
||
],
|
||
"paths": {
|
||
"/api/v2/logs": {
|
||
"get": {
|
||
"operationId": "listLogs",
|
||
"summary": "List Logs",
|
||
"description": "List workflow execution logs for a workspace with filters, selectable detail, and opaque cursor pagination. Runs are hard-deleted once they pass the payer's log retention window, so an older run is simply absent rather than reported as removed. The window is 30 days from run start on the free plan, unbounded on Pro and Team, and set per organization on Enterprise with an optional per-workspace override.",
|
||
"tags": ["Logs"],
|
||
"parameters": [
|
||
{
|
||
"name": "workspaceId",
|
||
"in": "query",
|
||
"required": true,
|
||
"description": "Workspace whose execution logs should be returned.",
|
||
"schema": {
|
||
"type": "string",
|
||
"minLength": 1,
|
||
"maxLength": 128,
|
||
"description": "Workspace whose execution logs should be returned."
|
||
}
|
||
},
|
||
{
|
||
"name": "workflowIds",
|
||
"in": "query",
|
||
"required": false,
|
||
"description": "Comma-separated workflow identifiers to include. An empty entry is rejected.",
|
||
"schema": {
|
||
"type": "string",
|
||
"description": "Comma-separated workflow identifiers to include. An empty entry is rejected."
|
||
}
|
||
},
|
||
{
|
||
"name": "triggers",
|
||
"in": "query",
|
||
"required": false,
|
||
"description": "Comma-separated trigger types to include. An empty entry is rejected. Values are matched exactly and are case-sensitive — every recorded trigger is lowercase, so `API` matches nothing while `api` matches. The vocabulary is open: it covers the core trigger types (`manual`, `api`, `schedule`, `chat`, `webhook`, `mcp`, `copilot`, `workflow`, `custom_block`) and the provider id of any webhook trigger (`slack`, `gmail`, `github`, …), so an unrecognized member is not rejected — it selects no runs. The literal value `all` is a sentinel that disables this filter entirely, so a list containing it returns runs of every trigger type; no real trigger type is named `all`.",
|
||
"schema": {
|
||
"type": "string",
|
||
"description": "Comma-separated trigger types to include. An empty entry is rejected. Values are matched exactly and are case-sensitive — every recorded trigger is lowercase, so `API` matches nothing while `api` matches. The vocabulary is open: it covers the core trigger types (`manual`, `api`, `schedule`, `chat`, `webhook`, `mcp`, `copilot`, `workflow`, `custom_block`) and the provider id of any webhook trigger (`slack`, `gmail`, `github`, …), so an unrecognized member is not rejected — it selects no runs. The literal value `all` is a sentinel that disables this filter entirely, so a list containing it returns runs of every trigger type; no real trigger type is named `all`."
|
||
}
|
||
},
|
||
{
|
||
"name": "level",
|
||
"in": "query",
|
||
"required": false,
|
||
"description": "Severity level to include.",
|
||
"schema": {
|
||
"type": "string",
|
||
"enum": ["info", "error"],
|
||
"description": "Severity level to include."
|
||
}
|
||
},
|
||
{
|
||
"name": "startDate",
|
||
"in": "query",
|
||
"required": false,
|
||
"description": "Only include runs started at or after this UTC ISO 8601 timestamp, e.g. `2026-08-06T00:00:00Z`. A date without a time, or a timestamp carrying a UTC offset instead of `Z`, is rejected, as is year `0000`, which names no storable instant.",
|
||
"schema": {
|
||
"type": "string",
|
||
"format": "date-time",
|
||
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$",
|
||
"description": "Only include runs started at or after this UTC ISO 8601 timestamp, e.g. `2026-08-06T00:00:00Z`. A date without a time, or a timestamp carrying a UTC offset instead of `Z`, is rejected, as is year `0000`, which names no storable instant."
|
||
}
|
||
},
|
||
{
|
||
"name": "endDate",
|
||
"in": "query",
|
||
"required": false,
|
||
"description": "Only include runs started at or before this UTC ISO 8601 timestamp, e.g. `2026-08-06T00:00:00Z`. A date without a time, or a timestamp carrying a UTC offset instead of `Z`, is rejected, as is year `0000`, which names no storable instant.",
|
||
"schema": {
|
||
"type": "string",
|
||
"format": "date-time",
|
||
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$",
|
||
"description": "Only include runs started at or before this UTC ISO 8601 timestamp, e.g. `2026-08-06T00:00:00Z`. A date without a time, or a timestamp carrying a UTC offset instead of `Z`, is rejected, as is year `0000`, which names no storable instant."
|
||
}
|
||
},
|
||
{
|
||
"name": "minDurationMs",
|
||
"in": "query",
|
||
"required": false,
|
||
"description": "Minimum total execution duration in milliseconds. Whole milliseconds from 0 to 2147483647; the stored duration is a 32-bit integer, so a fractional or out-of-range bound is rejected.",
|
||
"schema": {
|
||
"type": "integer",
|
||
"minimum": 0,
|
||
"maximum": 2147483647,
|
||
"description": "Minimum total execution duration in milliseconds. Whole milliseconds from 0 to 2147483647; the stored duration is a 32-bit integer, so a fractional or out-of-range bound is rejected."
|
||
}
|
||
},
|
||
{
|
||
"name": "maxDurationMs",
|
||
"in": "query",
|
||
"required": false,
|
||
"description": "Maximum total execution duration in milliseconds. Whole milliseconds from 0 to 2147483647; the stored duration is a 32-bit integer, so a fractional or out-of-range bound is rejected.",
|
||
"schema": {
|
||
"type": "integer",
|
||
"minimum": 0,
|
||
"maximum": 2147483647,
|
||
"description": "Maximum total execution duration in milliseconds. Whole milliseconds from 0 to 2147483647; the stored duration is a 32-bit integer, so a fractional or out-of-range bound is rejected."
|
||
}
|
||
},
|
||
{
|
||
"name": "minCost",
|
||
"in": "query",
|
||
"required": false,
|
||
"description": "Minimum execution cost in USD, from 0 to 1000000. A run is never charged a negative amount, so a negative bound is rejected rather than treated as a filter that matches every run.",
|
||
"schema": {
|
||
"type": "number",
|
||
"minimum": 0,
|
||
"maximum": 1000000,
|
||
"description": "Minimum execution cost in USD, from 0 to 1000000. A run is never charged a negative amount, so a negative bound is rejected rather than treated as a filter that matches every run."
|
||
}
|
||
},
|
||
{
|
||
"name": "maxCost",
|
||
"in": "query",
|
||
"required": false,
|
||
"description": "Maximum execution cost in USD, from 0 to 1000000. A run is never charged a negative amount, so a negative bound is rejected rather than treated as a filter that matches every run.",
|
||
"schema": {
|
||
"type": "number",
|
||
"minimum": 0,
|
||
"maximum": 1000000,
|
||
"description": "Maximum execution cost in USD, from 0 to 1000000. A run is never charged a negative amount, so a negative bound is rejected rather than treated as a filter that matches every run."
|
||
}
|
||
},
|
||
{
|
||
"name": "model",
|
||
"in": "query",
|
||
"required": false,
|
||
"description": "AI model used during execution.",
|
||
"schema": {
|
||
"type": "string",
|
||
"description": "AI model used during execution."
|
||
}
|
||
},
|
||
{
|
||
"name": "details",
|
||
"in": "query",
|
||
"required": false,
|
||
"description": "Response detail level. `full` adds the `workflow` summary to every item. `includeTraceSpans=true` and `includeFinalOutput=true` each imply `full`, so either one adds `workflow` even when `details=basic` is sent explicitly.",
|
||
"schema": {
|
||
"default": "basic",
|
||
"type": "string",
|
||
"enum": ["basic", "full"],
|
||
"description": "Response detail level. `full` adds the `workflow` summary to every item. `includeTraceSpans=true` and `includeFinalOutput=true` each imply `full`, so either one adds `workflow` even when `details=basic` is sent explicitly."
|
||
}
|
||
},
|
||
{
|
||
"name": "includeTraceSpans",
|
||
"in": "query",
|
||
"required": false,
|
||
"description": "Whether to include block-level trace spans. Implies `details=full`. Spans are pruned on their own retention schedule, so a run whose spans have aged out returns `traceSpans: []` rather than an error.",
|
||
"schema": {
|
||
"description": "Whether to include block-level trace spans. Implies `details=full`. Spans are pruned on their own retention schedule, so a run whose spans have aged out returns `traceSpans: []` rather than an error.",
|
||
"type": "boolean"
|
||
}
|
||
},
|
||
{
|
||
"name": "includeFinalOutput",
|
||
"in": "query",
|
||
"required": false,
|
||
"description": "Whether to include the final workflow output. Implies `details=full`, so the `workflow` summary is present regardless of what `details` is set to.",
|
||
"schema": {
|
||
"description": "Whether to include the final workflow output. Implies `details=full`, so the `workflow` summary is present regardless of what `details` is set to.",
|
||
"type": "boolean"
|
||
}
|
||
},
|
||
{
|
||
"name": "limit",
|
||
"in": "query",
|
||
"required": false,
|
||
"description": "Maximum log entries per page. Values outside 1–1000 are truncated and clamped into that range rather than rejected. Defaults to 100.",
|
||
"schema": {
|
||
"description": "Maximum log entries per page. Values outside 1–1000 are truncated and clamped into that range rather than rejected. Defaults to 100.",
|
||
"type": "integer",
|
||
"default": 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": "order",
|
||
"in": "query",
|
||
"required": false,
|
||
"description": "Sort direction by execution start time. This list is sortable only by execution start time, so it takes `order` in place of `sortBy`/`sortOrder`, which it rejects.",
|
||
"schema": {
|
||
"default": "desc",
|
||
"description": "Sort direction by execution start time. This list is sortable only by execution start time, so it takes `order` in place of `sortBy`/`sortOrder`, which it rejects.",
|
||
"type": "string",
|
||
"enum": ["asc", "desc"]
|
||
}
|
||
},
|
||
{
|
||
"name": "runId",
|
||
"in": "query",
|
||
"required": false,
|
||
"description": "Exact run identifier to match.",
|
||
"schema": {
|
||
"type": "string",
|
||
"minLength": 1,
|
||
"maxLength": 128,
|
||
"pattern": "^[A-Za-z0-9._:-]+$",
|
||
"description": "Exact run identifier to match."
|
||
}
|
||
},
|
||
{
|
||
"name": "folderPaths",
|
||
"in": "query",
|
||
"required": false,
|
||
"description": "Comma-separated workflow folder paths to include. A path that names no folder narrows the result to nothing, so the response is an empty page rather than an error.",
|
||
"schema": {
|
||
"type": "string",
|
||
"description": "Comma-separated workflow folder paths to include. A path that names no folder narrows the result to nothing, so the response is an empty page rather than an error."
|
||
}
|
||
}
|
||
],
|
||
"responses": {
|
||
"200": {
|
||
"description": "A page of execution logs matching the filters.",
|
||
"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/V2LogListResponse"
|
||
}
|
||
}
|
||
}
|
||
},
|
||
"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/logs/{runId}": {
|
||
"get": {
|
||
"operationId": "getLog",
|
||
"summary": "Get Log",
|
||
"description": "Retrieve the diagnostic representation of a run, including its workflow snapshot, trace spans, final output, and cost. Trace spans are pruned on their own retention schedule, so an empty `traceSpans` array does not mean the run recorded none.",
|
||
"tags": ["Logs"],
|
||
"parameters": [
|
||
{
|
||
"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."
|
||
}
|
||
}
|
||
],
|
||
"responses": {
|
||
"200": {
|
||
"description": "The requested diagnostic log representation.",
|
||
"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/V2LogDetailResponse"
|
||
}
|
||
}
|
||
}
|
||
},
|
||
"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"
|
||
}
|
||
}
|
||
}
|
||
}
|
||
},
|
||
"components": {
|
||
"securitySchemes": {
|
||
"apiKey": {
|
||
"type": "apiKey",
|
||
"in": "header",
|
||
"name": "X-API-Key",
|
||
"description": "Your Sim API key, personal or workspace-scoped. Generate one under Settings, then API Keys. Operations that reject workspace keys say so in their own description."
|
||
}
|
||
},
|
||
"headers": {
|
||
"X-RateLimit-Limit": {
|
||
"description": "Maximum requests allowed in the current window.",
|
||
"schema": {
|
||
"type": "integer",
|
||
"minimum": 0,
|
||
"maximum": 9007199254740991,
|
||
"title": "Rate limit",
|
||
"description": "Maximum requests allowed in the current window."
|
||
}
|
||
},
|
||
"X-RateLimit-Remaining": {
|
||
"description": "Requests remaining in the current window.",
|
||
"schema": {
|
||
"type": "integer",
|
||
"minimum": 0,
|
||
"maximum": 9007199254740991,
|
||
"title": "Rate limit remaining",
|
||
"description": "Requests remaining in the current window."
|
||
}
|
||
},
|
||
"X-RateLimit-Reset": {
|
||
"description": "ISO 8601 timestamp when the current rate-limit window resets.",
|
||
"schema": {
|
||
"type": "string",
|
||
"format": "date-time",
|
||
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$",
|
||
"title": "Rate limit reset",
|
||
"description": "ISO 8601 timestamp when the current rate-limit window resets."
|
||
}
|
||
},
|
||
"Retry-After": {
|
||
"description": "Seconds to wait before retrying, sent on `429` and `503`. Add jitter rather than retrying at exactly this offset.",
|
||
"schema": {
|
||
"type": "integer",
|
||
"minimum": 0,
|
||
"maximum": 9007199254740991,
|
||
"title": "Retry after",
|
||
"description": "Seconds to wait before retrying, sent on `429` and `503`. Add jitter rather than retrying at exactly this offset."
|
||
}
|
||
},
|
||
"X-Run-Id": {
|
||
"description": "Identifier assigned to the workflow run.",
|
||
"schema": {
|
||
"type": "string",
|
||
"minLength": 1,
|
||
"title": "Run identifier",
|
||
"description": "Identifier assigned to the workflow run."
|
||
}
|
||
}
|
||
},
|
||
"responses": {
|
||
"BadRequest": {
|
||
"description": "The request is invalid. This includes a query parameter sent with no value (`?limit=`, `?search=`), which is rejected rather than read as zero, empty, or the parameter default — omit the parameter instead.",
|
||
"content": {
|
||
"application/json": {
|
||
"schema": {
|
||
"$ref": "#/components/schemas/V2Error"
|
||
},
|
||
"example": {
|
||
"error": {
|
||
"code": "BAD_REQUEST",
|
||
"message": "Invalid request"
|
||
}
|
||
}
|
||
}
|
||
}
|
||
},
|
||
"Unauthorized": {
|
||
"description": "The API key is missing or invalid.",
|
||
"content": {
|
||
"application/json": {
|
||
"schema": {
|
||
"$ref": "#/components/schemas/V2Error"
|
||
},
|
||
"example": {
|
||
"error": {
|
||
"code": "UNAUTHORIZED",
|
||
"message": "API key required"
|
||
}
|
||
}
|
||
}
|
||
}
|
||
},
|
||
"Forbidden": {
|
||
"description": "The caller lacks the rights this operation requires. When the cause is one a caller can act on, `error.details.code` names it. A resource in a workspace the caller cannot reach at all answers `404` instead, so absence and denial are indistinguishable.",
|
||
"content": {
|
||
"application/json": {
|
||
"schema": {
|
||
"$ref": "#/components/schemas/V2Error"
|
||
},
|
||
"example": {
|
||
"error": {
|
||
"code": "FORBIDDEN",
|
||
"message": "Insufficient workspace permissions",
|
||
"details": {
|
||
"code": "INSUFFICIENT_WORKSPACE_ROLE"
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
},
|
||
"NotFound": {
|
||
"description": "The requested resource was not found.",
|
||
"content": {
|
||
"application/json": {
|
||
"schema": {
|
||
"$ref": "#/components/schemas/V2Error"
|
||
},
|
||
"example": {
|
||
"error": {
|
||
"code": "NOT_FOUND",
|
||
"message": "Not found"
|
||
}
|
||
}
|
||
}
|
||
}
|
||
},
|
||
"RateLimited": {
|
||
"description": "The caller exceeded the request rate limit.",
|
||
"headers": {
|
||
"Retry-After": {
|
||
"$ref": "#/components/headers/Retry-After"
|
||
}
|
||
},
|
||
"content": {
|
||
"application/json": {
|
||
"schema": {
|
||
"$ref": "#/components/schemas/V2Error"
|
||
},
|
||
"example": {
|
||
"error": {
|
||
"code": "RATE_LIMITED",
|
||
"message": "API rate limit exceeded",
|
||
"details": {
|
||
"retryAfter": "2026-01-01T00:00:30.000Z"
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
},
|
||
"InternalError": {
|
||
"description": "An unexpected server error occurred.",
|
||
"content": {
|
||
"application/json": {
|
||
"schema": {
|
||
"$ref": "#/components/schemas/V2Error"
|
||
},
|
||
"example": {
|
||
"error": {
|
||
"code": "INTERNAL_ERROR",
|
||
"message": "Internal server error"
|
||
}
|
||
}
|
||
}
|
||
}
|
||
},
|
||
"ServiceUnavailable": {
|
||
"description": "A required service is temporarily unavailable. `Retry-After` carries the seconds to wait; treat it as a floor and add jitter. The header is omitted when `error.details.code` is `ASYNC_ENQUEUE_AMBIGUOUS`, because the run may already have started — reconcile against the returned run id instead of retrying.",
|
||
"headers": {
|
||
"Retry-After": {
|
||
"$ref": "#/components/headers/Retry-After"
|
||
}
|
||
},
|
||
"content": {
|
||
"application/json": {
|
||
"schema": {
|
||
"$ref": "#/components/schemas/V2Error"
|
||
},
|
||
"example": {
|
||
"error": {
|
||
"code": "SERVICE_UNAVAILABLE",
|
||
"message": "Service temporarily unavailable"
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
},
|
||
"schemas": {
|
||
"V2Error": {
|
||
"type": "object",
|
||
"properties": {
|
||
"error": {
|
||
"type": "object",
|
||
"properties": {
|
||
"code": {
|
||
"type": "string",
|
||
"description": "Stable machine-readable error code."
|
||
},
|
||
"message": {
|
||
"type": "string",
|
||
"description": "Human-readable explanation of the error."
|
||
},
|
||
"details": {
|
||
"description": "Structured error details. On a `403` whose cause a caller can act on, this carries a `code` from a closed set:\n- `INSUFFICIENT_WORKSPACE_ROLE` — The caller has access to the workspace but its role is below the one this operation requires.\n- `PERSONAL_API_KEYS_DISABLED` — The workspace's organization does not allow personal API keys. Use a workspace API key.\n- `WORKSPACE_KEY_OPERATION_NOT_PERMITTED` — This operation is not available to a workspace-scoped API key. Use a personal API key.\n- `PRINCIPAL_KIND_NOT_PERMITTED` — This operation does not accept the caller’s kind of API key.\n- `ORGANIZATION_MEMBERSHIP_REQUIRED` — The caller is not a member of the organization it named.\n- `ORGANIZATION_ADMIN_REQUIRED` — The caller is a member of the organization but not an admin or owner.\n- `ENTERPRISE_PLAN_REQUIRED` — The organization has no active enterprise subscription.\n- `ORGANIZATION_PLAN_REQUIRED` — The organization has no active organization subscription (Pro for Teams, Max for Teams, or Enterprise).\n- `AUDIT_LOGS_DISABLED` — Audit logging is not enabled for this deployment.\n- `SKILL_EDITOR_ACCESS_REQUIRED` — The caller can write in the workspace but is not an editor of this skill.\n- `SECRET_ADMIN_ACCESS_REQUIRED` — The caller can write in the workspace but is not an admin of this secret. Ask a workspace admin, or someone holding admin on the secret, to grant access or set the value.\n- `WORKSPACE_RESOURCE_LIMIT_REACHED` — The workspace already holds the maximum number of resources of this kind. Delete one, or contact Sim to raise the limit; the message names the ceiling.\n- `PUBLIC_SHARING_NOT_ALLOWED` — The workspace's organization does not permit sharing this resource publicly. An organization admin controls the policy.\n- `CREDENTIAL_ADMIN_ACCESS_REQUIRED` — The caller can reach the workspace but cannot administer this credential.\n- `MCP_SERVER_URL_NOT_ALLOWED` — The supplied MCP server URL is outside the allowed domains or resolves to an internal address."
|
||
}
|
||
},
|
||
"required": ["code", "message"],
|
||
"additionalProperties": false,
|
||
"description": "Canonical error details."
|
||
}
|
||
},
|
||
"required": ["error"],
|
||
"additionalProperties": false,
|
||
"title": "v2 error response",
|
||
"description": "Canonical error envelope returned by the public v2 API.",
|
||
"examples": [
|
||
{
|
||
"error": {
|
||
"code": "BAD_REQUEST",
|
||
"message": "The request is invalid."
|
||
}
|
||
}
|
||
]
|
||
},
|
||
"V2LogListItem": {
|
||
"type": "object",
|
||
"properties": {
|
||
"runId": {
|
||
"type": "string",
|
||
"description": "Unique run identifier."
|
||
},
|
||
"workflowId": {
|
||
"anyOf": [
|
||
{
|
||
"type": "string"
|
||
},
|
||
{
|
||
"type": "null"
|
||
}
|
||
],
|
||
"description": "Workflow identifier, or null when unavailable."
|
||
},
|
||
"deploymentVersionId": {
|
||
"anyOf": [
|
||
{
|
||
"type": "string"
|
||
},
|
||
{
|
||
"type": "null"
|
||
}
|
||
],
|
||
"description": "Deployment version identifier, or null when unavailable."
|
||
},
|
||
"status": {
|
||
"type": "string",
|
||
"enum": [
|
||
"pending",
|
||
"running",
|
||
"paused",
|
||
"redacting",
|
||
"completed",
|
||
"failed",
|
||
"cancelled"
|
||
],
|
||
"description": "Current execution status, reported as persisted. `redacting` is transient while run output is scrubbed. `paused` is reported only when a resume attempt did not complete; a run held at a human-in-the-loop pause point reads `pending` here, and `paused` on the workflow run resources. Use those when the pause state matters."
|
||
},
|
||
"level": {
|
||
"type": "string",
|
||
"description": "Log severity level."
|
||
},
|
||
"trigger": {
|
||
"type": "string",
|
||
"description": "Trigger that started the run."
|
||
},
|
||
"startedAt": {
|
||
"type": "string",
|
||
"format": "date-time",
|
||
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$",
|
||
"description": "ISO 8601 execution start timestamp."
|
||
},
|
||
"endedAt": {
|
||
"anyOf": [
|
||
{
|
||
"type": "string",
|
||
"format": "date-time",
|
||
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"
|
||
},
|
||
{
|
||
"type": "null"
|
||
}
|
||
],
|
||
"description": "ISO 8601 execution end timestamp, or null while the run is active."
|
||
},
|
||
"totalDurationMs": {
|
||
"anyOf": [
|
||
{
|
||
"type": "number"
|
||
},
|
||
{
|
||
"type": "null"
|
||
}
|
||
],
|
||
"description": "Total execution duration in milliseconds, or null while unavailable."
|
||
},
|
||
"cost": {
|
||
"anyOf": [
|
||
{
|
||
"type": "object",
|
||
"properties": {
|
||
"total": {
|
||
"type": "number",
|
||
"description": "Total execution cost in USD."
|
||
}
|
||
},
|
||
"required": ["total"],
|
||
"additionalProperties": false
|
||
},
|
||
{
|
||
"type": "null"
|
||
}
|
||
],
|
||
"description": "Cost charged for the run, or null when unavailable."
|
||
},
|
||
"files": {
|
||
"anyOf": [
|
||
{
|
||
"type": "array",
|
||
"items": {
|
||
"description": "Attachment metadata captured for the execution."
|
||
}
|
||
},
|
||
{
|
||
"type": "null"
|
||
}
|
||
],
|
||
"description": "Files attached to the run, or null when none are recorded."
|
||
},
|
||
"workflow": {
|
||
"type": "object",
|
||
"properties": {
|
||
"id": {
|
||
"anyOf": [
|
||
{
|
||
"type": "string"
|
||
},
|
||
{
|
||
"type": "null"
|
||
}
|
||
],
|
||
"description": "Workflow identifier, or null when unavailable."
|
||
},
|
||
"name": {
|
||
"type": "string",
|
||
"description": "Workflow name."
|
||
},
|
||
"description": {
|
||
"anyOf": [
|
||
{
|
||
"type": "string"
|
||
},
|
||
{
|
||
"type": "null"
|
||
}
|
||
],
|
||
"description": "Workflow description, or null when unset."
|
||
},
|
||
"deleted": {
|
||
"type": "boolean",
|
||
"description": "Whether the workflow has been deleted."
|
||
}
|
||
},
|
||
"required": ["id", "name", "description", "deleted"],
|
||
"additionalProperties": false,
|
||
"description": "Workflow summary for a full-detail result."
|
||
},
|
||
"finalOutput": {
|
||
"description": "Final workflow output."
|
||
},
|
||
"traceSpans": {
|
||
"type": "array",
|
||
"items": {
|
||
"$ref": "#/components/schemas/LogTraceSpan"
|
||
},
|
||
"description": "Block-level execution trace spans."
|
||
}
|
||
},
|
||
"required": [
|
||
"runId",
|
||
"workflowId",
|
||
"deploymentVersionId",
|
||
"status",
|
||
"level",
|
||
"trigger",
|
||
"startedAt",
|
||
"endedAt",
|
||
"totalDurationMs",
|
||
"cost",
|
||
"files"
|
||
],
|
||
"additionalProperties": false,
|
||
"title": "Execution log summary",
|
||
"description": "Summary information for one workflow execution log."
|
||
},
|
||
"LogTraceSpan": {
|
||
"title": "Log trace span",
|
||
"description": "One recursive operation span in a workflow execution trace.",
|
||
"type": "object",
|
||
"properties": {
|
||
"id": {
|
||
"type": "string",
|
||
"description": "Trace-span identifier."
|
||
},
|
||
"name": {
|
||
"type": "string",
|
||
"description": "Trace-span name."
|
||
},
|
||
"type": {
|
||
"type": "string",
|
||
"description": "Trace-span category."
|
||
},
|
||
"duration": {
|
||
"type": "number",
|
||
"description": "Legacy span duration in milliseconds."
|
||
},
|
||
"durationMs": {
|
||
"type": "number",
|
||
"description": "Span duration in milliseconds."
|
||
},
|
||
"startTime": {
|
||
"type": "string",
|
||
"description": "ISO 8601 span start timestamp."
|
||
},
|
||
"endTime": {
|
||
"type": "string",
|
||
"description": "ISO 8601 span end timestamp."
|
||
},
|
||
"status": {
|
||
"type": "string",
|
||
"description": "Trace-span status."
|
||
},
|
||
"errorHandled": {
|
||
"type": "boolean",
|
||
"description": "Whether the recorded error was handled."
|
||
},
|
||
"errorType": {
|
||
"type": "string",
|
||
"description": "Recorded error type."
|
||
},
|
||
"errorMessage": {
|
||
"type": "string",
|
||
"description": "Recorded error message."
|
||
},
|
||
"blockId": {
|
||
"type": "string",
|
||
"description": "Workflow block associated with the span."
|
||
},
|
||
"input": {
|
||
"description": "Input captured for the traced operation."
|
||
},
|
||
"output": {
|
||
"description": "Output captured for the traced operation."
|
||
},
|
||
"tokens": {
|
||
"anyOf": [
|
||
{
|
||
"type": "number",
|
||
"description": "Total tokens attributed to the span."
|
||
},
|
||
{
|
||
"type": "object",
|
||
"properties": {
|
||
"total": {
|
||
"type": "number",
|
||
"description": "Total tokens."
|
||
},
|
||
"input": {
|
||
"type": "number",
|
||
"description": "Input tokens."
|
||
},
|
||
"output": {
|
||
"type": "number",
|
||
"description": "Output tokens."
|
||
}
|
||
},
|
||
"additionalProperties": false
|
||
}
|
||
],
|
||
"description": "Token usage attributed to the span."
|
||
},
|
||
"cost": {
|
||
"type": "object",
|
||
"properties": {
|
||
"total": {
|
||
"type": "number",
|
||
"description": "Total span cost in USD."
|
||
},
|
||
"input": {
|
||
"type": "number",
|
||
"description": "Input-token cost in USD."
|
||
},
|
||
"output": {
|
||
"type": "number",
|
||
"description": "Output-token cost in USD."
|
||
},
|
||
"toolCost": {
|
||
"type": "number",
|
||
"description": "Tool cost in USD."
|
||
}
|
||
},
|
||
"additionalProperties": false,
|
||
"description": "Cost attributed to the span."
|
||
},
|
||
"relativeStartMs": {
|
||
"type": "number",
|
||
"description": "Offset from the root span in milliseconds."
|
||
},
|
||
"toolCalls": {
|
||
"type": "array",
|
||
"items": {
|
||
"type": "object",
|
||
"properties": {
|
||
"id": {
|
||
"type": "string",
|
||
"description": "Tool-call identifier."
|
||
},
|
||
"name": {
|
||
"type": "string",
|
||
"description": "Invoked tool name."
|
||
},
|
||
"arguments": {
|
||
"description": "Arguments supplied to the tool call."
|
||
},
|
||
"result": {
|
||
"description": "Value returned by the tool call."
|
||
},
|
||
"error": {
|
||
"type": "string",
|
||
"description": "Tool-call error message."
|
||
},
|
||
"startTime": {
|
||
"type": "string",
|
||
"description": "ISO 8601 tool-call start timestamp."
|
||
},
|
||
"endTime": {
|
||
"type": "string",
|
||
"description": "ISO 8601 tool-call end timestamp."
|
||
},
|
||
"duration": {
|
||
"type": "number",
|
||
"description": "Tool-call duration in milliseconds."
|
||
}
|
||
},
|
||
"additionalProperties": {
|
||
"description": "Additional provider-specific tool-call metadata."
|
||
}
|
||
},
|
||
"description": "Tool calls recorded by the span."
|
||
},
|
||
"children": {
|
||
"type": "array",
|
||
"items": {
|
||
"$ref": "#/components/schemas/LogTraceSpan"
|
||
},
|
||
"description": "Nested child trace spans."
|
||
}
|
||
},
|
||
"required": ["id", "name", "type"],
|
||
"additionalProperties": {
|
||
"description": "Additional provider-specific trace-span metadata."
|
||
}
|
||
},
|
||
"V2LogListResponse": {
|
||
"type": "object",
|
||
"properties": {
|
||
"data": {
|
||
"type": "array",
|
||
"items": {
|
||
"$ref": "#/components/schemas/V2LogListItem"
|
||
},
|
||
"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 is nothing further to fetch. Never construct one yourself."
|
||
}
|
||
},
|
||
"required": ["data", "nextCursor"],
|
||
"additionalProperties": false,
|
||
"title": "Log list response",
|
||
"description": "A cursor-paginated page of workflow execution logs.",
|
||
"examples": [
|
||
{
|
||
"data": [
|
||
{
|
||
"runId": "e4f8d2b6-9a1c-4e3d-8b7f-5c0a2d9e6f13",
|
||
"workflowId": "3b1f7c92-8d4e-4a6b-9c0d-5e2f8a714b36",
|
||
"deploymentVersionId": "dep_2c4e6a8b0d1f",
|
||
"status": "completed",
|
||
"level": "info",
|
||
"trigger": "api",
|
||
"startedAt": "2026-01-15T10:30:00.000Z",
|
||
"endedAt": "2026-01-15T10:30:01.250Z",
|
||
"totalDurationMs": 1250,
|
||
"cost": {
|
||
"total": 0.0032
|
||
},
|
||
"files": null
|
||
}
|
||
],
|
||
"nextCursor": "eyJzdGFydGVkQXQiOiIyMDI2LTAxLTE1VDEwOjMwOjAwMFoifQ=="
|
||
}
|
||
]
|
||
},
|
||
"V2LogDetail": {
|
||
"type": "object",
|
||
"properties": {
|
||
"runId": {
|
||
"type": "string",
|
||
"description": "Unique run identifier."
|
||
},
|
||
"workflowId": {
|
||
"anyOf": [
|
||
{
|
||
"type": "string"
|
||
},
|
||
{
|
||
"type": "null"
|
||
}
|
||
],
|
||
"description": "Workflow identifier, or null when unavailable."
|
||
},
|
||
"deploymentVersionId": {
|
||
"anyOf": [
|
||
{
|
||
"type": "string"
|
||
},
|
||
{
|
||
"type": "null"
|
||
}
|
||
],
|
||
"description": "Deployment version identifier, or null when unavailable."
|
||
},
|
||
"status": {
|
||
"type": "string",
|
||
"enum": [
|
||
"pending",
|
||
"running",
|
||
"paused",
|
||
"redacting",
|
||
"completed",
|
||
"failed",
|
||
"cancelled"
|
||
],
|
||
"description": "Current execution status, reported as persisted. `redacting` is transient while run output is scrubbed. `paused` is reported only when a resume attempt did not complete; a run held at a human-in-the-loop pause point reads `pending` here, and `paused` on the workflow run resources. Use those when the pause state matters."
|
||
},
|
||
"level": {
|
||
"type": "string",
|
||
"description": "Log severity level."
|
||
},
|
||
"trigger": {
|
||
"type": "string",
|
||
"description": "Trigger that started the run."
|
||
},
|
||
"startedAt": {
|
||
"type": "string",
|
||
"format": "date-time",
|
||
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$",
|
||
"description": "ISO 8601 execution start timestamp."
|
||
},
|
||
"endedAt": {
|
||
"anyOf": [
|
||
{
|
||
"type": "string",
|
||
"format": "date-time",
|
||
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"
|
||
},
|
||
{
|
||
"type": "null"
|
||
}
|
||
],
|
||
"description": "ISO 8601 execution end timestamp, or null while the run is active."
|
||
},
|
||
"totalDurationMs": {
|
||
"anyOf": [
|
||
{
|
||
"type": "number"
|
||
},
|
||
{
|
||
"type": "null"
|
||
}
|
||
],
|
||
"description": "Total execution duration in milliseconds, or null while unavailable."
|
||
},
|
||
"files": {
|
||
"anyOf": [
|
||
{
|
||
"type": "array",
|
||
"items": {
|
||
"description": "Attachment metadata captured for the execution."
|
||
}
|
||
},
|
||
{
|
||
"type": "null"
|
||
}
|
||
],
|
||
"description": "Files attached to the run, or null when none are recorded."
|
||
},
|
||
"workflow": {
|
||
"type": "object",
|
||
"properties": {
|
||
"id": {
|
||
"anyOf": [
|
||
{
|
||
"type": "string"
|
||
},
|
||
{
|
||
"type": "null"
|
||
}
|
||
],
|
||
"description": "Workflow identifier, or null when unavailable."
|
||
},
|
||
"name": {
|
||
"type": "string",
|
||
"description": "Workflow name."
|
||
},
|
||
"description": {
|
||
"anyOf": [
|
||
{
|
||
"type": "string"
|
||
},
|
||
{
|
||
"type": "null"
|
||
}
|
||
],
|
||
"description": "Workflow description, or null when unset."
|
||
},
|
||
"folderPath": {
|
||
"anyOf": [
|
||
{
|
||
"type": "string",
|
||
"title": "Folder path",
|
||
"description": "Canonical slash-prefixed folder path. `/` is the workspace root. Segments are percent-encoded, so a folder shown as \"New folder\" is `/New%20folder`: everything outside `A-Z a-z 0-9 - _ . ~` is escaped as uppercase hex, and only that exact encoding is accepted. A trailing slash, an empty segment, and a literal `.` or `..` segment are rejected. At most 64 segments and 4096 encoded bytes.",
|
||
"maxLength": 4096
|
||
},
|
||
{
|
||
"type": "null"
|
||
}
|
||
],
|
||
"description": "Canonical folder path of the workflow, in the same form `folderPaths` accepts as a filter: `/` for a workflow at the workspace root. Null only when the path cannot be resolved — the folder has been deleted, or the workflow itself no longer exists."
|
||
},
|
||
"ownerEmail": {
|
||
"anyOf": [
|
||
{
|
||
"type": "string",
|
||
"format": "email",
|
||
"pattern": "^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"
|
||
},
|
||
{
|
||
"type": "null"
|
||
}
|
||
],
|
||
"description": "Workflow owner email, or null when unavailable."
|
||
},
|
||
"workspaceId": {
|
||
"anyOf": [
|
||
{
|
||
"type": "string"
|
||
},
|
||
{
|
||
"type": "null"
|
||
}
|
||
],
|
||
"description": "Owning workspace identifier, or null when unavailable."
|
||
},
|
||
"createdAt": {
|
||
"anyOf": [
|
||
{
|
||
"type": "string",
|
||
"format": "date-time",
|
||
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"
|
||
},
|
||
{
|
||
"type": "null"
|
||
}
|
||
],
|
||
"description": "ISO 8601 workflow creation timestamp, or null when unavailable."
|
||
},
|
||
"updatedAt": {
|
||
"anyOf": [
|
||
{
|
||
"type": "string",
|
||
"format": "date-time",
|
||
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"
|
||
},
|
||
{
|
||
"type": "null"
|
||
}
|
||
],
|
||
"description": "ISO 8601 workflow update timestamp, or null when unavailable."
|
||
},
|
||
"deleted": {
|
||
"type": "boolean",
|
||
"description": "Whether the workflow has been deleted."
|
||
}
|
||
},
|
||
"required": [
|
||
"id",
|
||
"name",
|
||
"description",
|
||
"folderPath",
|
||
"ownerEmail",
|
||
"workspaceId",
|
||
"createdAt",
|
||
"updatedAt",
|
||
"deleted"
|
||
],
|
||
"additionalProperties": false,
|
||
"description": "Workflow snapshot associated with the execution."
|
||
},
|
||
"workflowState": {
|
||
"anyOf": [
|
||
{
|
||
"type": "object",
|
||
"properties": {},
|
||
"additionalProperties": {
|
||
"description": "One top-level snapshot section — `blocks`, `edges`, `loops`, `parallels`, or `variables` — passed through as stored."
|
||
}
|
||
},
|
||
{
|
||
"type": "null"
|
||
}
|
||
],
|
||
"description": "Workflow graph snapshot captured for the run, or null when none is retained. Credential-bearing values are redacted to null: `oauth-input`, `password: true`, table sub-block values, sensitive nested tool parameters, and any parameter without authoritative codec metadata. `{{VAR}}` references in non-opaque fields are preserved."
|
||
},
|
||
"traceSpans": {
|
||
"type": "array",
|
||
"items": {
|
||
"$ref": "#/components/schemas/LogTraceSpan"
|
||
},
|
||
"description": "Materialized block-level execution trace spans."
|
||
},
|
||
"finalOutput": {
|
||
"anyOf": [
|
||
{
|
||
"description": "Materialized final workflow output value."
|
||
},
|
||
{
|
||
"type": "null"
|
||
}
|
||
],
|
||
"description": "Materialized final workflow output, or null when none was produced."
|
||
},
|
||
"cost": {
|
||
"anyOf": [
|
||
{
|
||
"type": "object",
|
||
"properties": {
|
||
"total": {
|
||
"type": "number",
|
||
"description": "Total execution cost in USD."
|
||
}
|
||
},
|
||
"required": ["total"],
|
||
"additionalProperties": false
|
||
},
|
||
{
|
||
"type": "null"
|
||
}
|
||
],
|
||
"description": "Cost charged for the run, or null when unavailable."
|
||
},
|
||
"createdAt": {
|
||
"type": "string",
|
||
"format": "date-time",
|
||
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$",
|
||
"description": "ISO 8601 log creation timestamp."
|
||
}
|
||
},
|
||
"required": [
|
||
"runId",
|
||
"workflowId",
|
||
"deploymentVersionId",
|
||
"status",
|
||
"level",
|
||
"trigger",
|
||
"startedAt",
|
||
"endedAt",
|
||
"totalDurationMs",
|
||
"files",
|
||
"workflow",
|
||
"workflowState",
|
||
"traceSpans",
|
||
"finalOutput",
|
||
"cost",
|
||
"createdAt"
|
||
],
|
||
"additionalProperties": false,
|
||
"title": "Execution log detail",
|
||
"description": "Detailed workflow execution log including state, trace, output, and cost."
|
||
},
|
||
"V2LogDetailResponse": {
|
||
"type": "object",
|
||
"properties": {
|
||
"data": {
|
||
"description": "Response data.",
|
||
"$ref": "#/components/schemas/V2LogDetail"
|
||
}
|
||
},
|
||
"required": ["data"],
|
||
"additionalProperties": false,
|
||
"title": "Log detail response",
|
||
"description": "The complete diagnostic representation of a workflow run.",
|
||
"examples": [
|
||
{
|
||
"data": {
|
||
"runId": "e4f8d2b6-9a1c-4e3d-8b7f-5c0a2d9e6f13",
|
||
"workflowId": "3b1f7c92-8d4e-4a6b-9c0d-5e2f8a714b36",
|
||
"deploymentVersionId": "dep_2c4e6a8b0d1f",
|
||
"status": "completed",
|
||
"level": "info",
|
||
"trigger": "api",
|
||
"startedAt": "2026-01-15T10:30:00.000Z",
|
||
"endedAt": "2026-01-15T10:30:01.250Z",
|
||
"totalDurationMs": 1250,
|
||
"files": null,
|
||
"workflow": {
|
||
"id": "3b1f7c92-8d4e-4a6b-9c0d-5e2f8a714b36",
|
||
"name": "Customer Support Agent",
|
||
"description": "Routes incoming support tickets and drafts responses",
|
||
"folderPath": "/",
|
||
"ownerEmail": "jane@example.com",
|
||
"workspaceId": "a91c4b2e-6d3f-4e8a-b5c7-0d9e2f1a8c64",
|
||
"createdAt": "2025-01-10T09:00:00.000Z",
|
||
"updatedAt": "2025-06-18T16:45:00.000Z",
|
||
"deleted": false
|
||
},
|
||
"workflowState": {
|
||
"blocks": {},
|
||
"edges": []
|
||
},
|
||
"traceSpans": [],
|
||
"finalOutput": {
|
||
"result": "Hello, world!"
|
||
},
|
||
"cost": {
|
||
"total": 0.0032
|
||
},
|
||
"createdAt": "2026-01-15T10:30:00.000Z"
|
||
}
|
||
}
|
||
]
|
||
}
|
||
}
|
||
},
|
||
"x-generated-by": "scripts/generate-openapi.ts"
|
||
}
|