mirror of
https://github.com/simstudioai/sim.git
synced 2026-09-21 13:00:04 +08:00
* fix(v2): derive the log and run status enums from the persisted status list
`GET /api/v2/logs` and `GET /api/v2/logs/{runId}` parse the raw
`workflow_execution_logs.status` column against a six-value enum that omits
`paused`, so a run holding that value returns 500. The list response is
validated whole-page, so one such row 500s every page it lands on, and the
row is durable until the run is resumed, cancelled, or failed.
`paused` is not written by an ordinary human-in-the-loop pause — that path
persists `pending` (logging-session.ts:1180). It is written by
`PauseResumeManager.markResumeAttemptFailed`, which fires on any
`ResumeAdmissionError`: a workspace over its usage limit, an archived or
undeployed workflow, or a concurrent resume losing the claim race. That is a
routine business path.
The enum was supposed to be protected by an `AssertNever` exhaustiveness gate,
but the gate was vacuous: it compared against `PersistedWorkflowExecutionStatus`,
a hand-written union that was itself missing `paused`, because the write goes
through a raw `sql` CASE fragment Drizzle cannot type-check. Adding `paused` to
both lists would leave the same vacuous gate in place for the next status.
Instead, `PERSISTED_WORKFLOW_EXECUTION_STATUSES` becomes the single runtime
source of truth, `PersistedWorkflowExecutionStatus` is derived from it, and both
v2 contracts derive their enums from the const rather than re-declaring them.
Both surfaces pass the column through verbatim, so their reported set is the
persisted set by definition — there is no editorial choice for a gate to force,
only the question of whether a newly persisted status should be public, which
the option-list tests now pin. The `[...V2_PERSISTED_RUN_STATUSES, 'paused']`
append on the runs contract is deleted rather than adjusted; it would otherwise
be a duplicate.
Alternatives rejected:
- A `.catch()` or `safeParse` in the presenters is dead code:
`v2-json-route.ts:271` re-parses the whole body with the same schema.
- Normalizing `markResumeAttemptFailed` to write `pending` would remove the
distinction the resume claim query at human-in-the-loop-manager.ts:973 relies
on, and leaves the contract wrong for any other future status.
- Typing the Drizzle column does not help: the offending write is a raw `sql`
fragment, and `packages/db` cannot import the app's status list.
The v2 workflows spec changes are reordering and description only — the value
set there already contained `paused`. The v2 logs spec gains `paused`, which is
additive and safe while the whole `/api/v2` surface is behind the off-by-default
`v2-api` flag; it must land before v2 GA, after which it would be breaking.
* fix(v2): document both provenances of a reported paused run status
* fix(v2): stop promising a paused discriminator the response cannot always provide
* fix(v2): describe the paused discriminator as the code actually records it
1349 lines
46 KiB
JSON
1349 lines
46 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. This list predates the shared sort convention: it has no `sortBy` (the sort column is fixed to execution start time) and spells the direction `order` rather than `sortOrder`. Trace spans are stored separately from the log row and are pruned on their own retention schedule: `includeTraceSpans=true` on a run whose stored spans have aged out returns `traceSpans: []` rather than an error, so an empty array does not mean the run recorded no spans.",
|
||
"tags": ["Logs"],
|
||
"parameters": [
|
||
{
|
||
"name": "workspaceId",
|
||
"in": "query",
|
||
"required": true,
|
||
"description": "Workspace whose execution logs should be returned.",
|
||
"schema": {
|
||
"type": "string",
|
||
"minLength": 1,
|
||
"description": "Workspace whose execution logs should be returned."
|
||
}
|
||
},
|
||
{
|
||
"name": "workflowIds",
|
||
"in": "query",
|
||
"required": false,
|
||
"description": "Comma-separated workflow identifiers to include.",
|
||
"schema": {
|
||
"type": "string",
|
||
"description": "Comma-separated workflow identifiers to include."
|
||
}
|
||
},
|
||
{
|
||
"name": "triggers",
|
||
"in": "query",
|
||
"required": false,
|
||
"description": "Comma-separated trigger types to include.",
|
||
"schema": {
|
||
"type": "string",
|
||
"description": "Comma-separated trigger types to include."
|
||
}
|
||
},
|
||
{
|
||
"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 ISO 8601 timestamp.",
|
||
"schema": {
|
||
"type": "string",
|
||
"description": "Only include runs started at or after this ISO 8601 timestamp."
|
||
}
|
||
},
|
||
{
|
||
"name": "endDate",
|
||
"in": "query",
|
||
"required": false,
|
||
"description": "Only include runs started at or before this ISO 8601 timestamp.",
|
||
"schema": {
|
||
"type": "string",
|
||
"description": "Only include runs started at or before this ISO 8601 timestamp."
|
||
}
|
||
},
|
||
{
|
||
"name": "minDurationMs",
|
||
"in": "query",
|
||
"required": false,
|
||
"description": "Minimum total execution duration in milliseconds.",
|
||
"schema": {
|
||
"type": "number",
|
||
"description": "Minimum total execution duration in milliseconds."
|
||
}
|
||
},
|
||
{
|
||
"name": "maxDurationMs",
|
||
"in": "query",
|
||
"required": false,
|
||
"description": "Maximum total execution duration in milliseconds.",
|
||
"schema": {
|
||
"type": "number",
|
||
"description": "Maximum total execution duration in milliseconds."
|
||
}
|
||
},
|
||
{
|
||
"name": "minCost",
|
||
"in": "query",
|
||
"required": false,
|
||
"description": "Minimum execution cost in USD.",
|
||
"schema": {
|
||
"type": "number",
|
||
"description": "Minimum execution cost in USD."
|
||
}
|
||
},
|
||
{
|
||
"name": "maxCost",
|
||
"in": "query",
|
||
"required": false,
|
||
"description": "Maximum execution cost in USD.",
|
||
"schema": {
|
||
"type": "number",
|
||
"description": "Maximum execution cost in USD."
|
||
}
|
||
},
|
||
{
|
||
"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.",
|
||
"schema": {
|
||
"default": "basic",
|
||
"type": "string",
|
||
"enum": ["basic", "full"],
|
||
"description": "Response detail level."
|
||
}
|
||
},
|
||
{
|
||
"name": "includeTraceSpans",
|
||
"in": "query",
|
||
"required": false,
|
||
"description": "Whether to include block-level trace spans.",
|
||
"schema": {
|
||
"description": "Whether to include block-level trace spans.",
|
||
"type": "boolean"
|
||
}
|
||
},
|
||
{
|
||
"name": "includeFinalOutput",
|
||
"in": "query",
|
||
"required": false,
|
||
"description": "Whether to include the final workflow output.",
|
||
"schema": {
|
||
"description": "Whether to include the final workflow output.",
|
||
"type": "boolean"
|
||
}
|
||
},
|
||
{
|
||
"name": "limit",
|
||
"in": "query",
|
||
"required": false,
|
||
"description": "Maximum log entries per page, clamped to 1–1000.",
|
||
"schema": {
|
||
"description": "Maximum log entries per page, clamped to 1–1000.",
|
||
"default": 100,
|
||
"type": "number"
|
||
}
|
||
},
|
||
{
|
||
"name": "cursor",
|
||
"in": "query",
|
||
"required": false,
|
||
"description": "Opaque cursor returned by a previous page.",
|
||
"schema": {
|
||
"type": "string",
|
||
"description": "Opaque cursor returned by a previous page."
|
||
}
|
||
},
|
||
{
|
||
"name": "order",
|
||
"in": "query",
|
||
"required": false,
|
||
"description": "Sort order by execution start time.",
|
||
"schema": {
|
||
"default": "desc",
|
||
"type": "string",
|
||
"enum": ["desc", "asc"],
|
||
"description": "Sort order by execution start time."
|
||
}
|
||
},
|
||
{
|
||
"name": "runId",
|
||
"in": "query",
|
||
"required": false,
|
||
"description": "Exact run identifier to match.",
|
||
"schema": {
|
||
"type": "string",
|
||
"minLength": 1,
|
||
"description": "Exact run identifier to match."
|
||
}
|
||
},
|
||
{
|
||
"name": "folderPaths",
|
||
"in": "query",
|
||
"required": false,
|
||
"description": "Comma-separated workflow folder paths to include.",
|
||
"schema": {
|
||
"type": "string",
|
||
"description": "Comma-separated workflow folder paths to include."
|
||
}
|
||
}
|
||
],
|
||
"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. The returned `workflowState` snapshot has credential values redacted: OAuth credential references and secret (`password`) sub-block values are null, while `{{VAR}}` environment-variable references are preserved so consecutive snapshots stay diffable. Trace spans are stored separately from the log row and are pruned on their own retention schedule: a run whose stored spans have aged out returns `traceSpans: []` rather than an error, so an empty array does not mean the run recorded no spans.",
|
||
"tags": ["Logs"],
|
||
"parameters": [
|
||
{
|
||
"name": "runId",
|
||
"in": "path",
|
||
"required": true,
|
||
"description": "The unique run identifier shared by lifecycle and diagnostic resources.",
|
||
"schema": {
|
||
"type": "string",
|
||
"minLength": 1,
|
||
"description": "The unique run identifier shared by lifecycle and diagnostic resources."
|
||
}
|
||
}
|
||
],
|
||
"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 from the Sim dashboard under Settings > API Keys. A workspace API key is not accepted everywhere: operations that act on behalf of a specific human — administrative reads, secret access, and irreversible or governance-affecting writes — always reject it, whatever role the key carries. Each such operation says so in its own description, and the rejection surfaces as `403` unless the operation conceals unauthorized resources, in which case it is reported as `404`. Use a personal API key for those."
|
||
}
|
||
},
|
||
"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 a rate-limited request.",
|
||
"schema": {
|
||
"type": "integer",
|
||
"minimum": 0,
|
||
"maximum": 9007199254740991,
|
||
"title": "Retry after",
|
||
"description": "Seconds to wait before retrying a rate-limited request."
|
||
}
|
||
},
|
||
"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.",
|
||
"content": {
|
||
"application/json": {
|
||
"schema": {
|
||
"$ref": "#/components/schemas/V2Error"
|
||
}
|
||
}
|
||
}
|
||
},
|
||
"Unauthorized": {
|
||
"description": "The API key is missing or invalid.",
|
||
"content": {
|
||
"application/json": {
|
||
"schema": {
|
||
"$ref": "#/components/schemas/V2Error"
|
||
}
|
||
}
|
||
}
|
||
},
|
||
"UsageLimitExceeded": {
|
||
"description": "The workspace has exceeded its usage or billing limits.",
|
||
"content": {
|
||
"application/json": {
|
||
"schema": {
|
||
"$ref": "#/components/schemas/V2Error"
|
||
}
|
||
}
|
||
}
|
||
},
|
||
"Forbidden": {
|
||
"description": "The caller lacks access to the resource.",
|
||
"content": {
|
||
"application/json": {
|
||
"schema": {
|
||
"$ref": "#/components/schemas/V2Error"
|
||
}
|
||
}
|
||
}
|
||
},
|
||
"NotFound": {
|
||
"description": "The requested resource was not found.",
|
||
"content": {
|
||
"application/json": {
|
||
"schema": {
|
||
"$ref": "#/components/schemas/V2Error"
|
||
}
|
||
}
|
||
}
|
||
},
|
||
"Conflict": {
|
||
"description": "The request conflicts with current resource state.",
|
||
"content": {
|
||
"application/json": {
|
||
"schema": {
|
||
"$ref": "#/components/schemas/V2Error"
|
||
}
|
||
}
|
||
}
|
||
},
|
||
"RunIdConflict": {
|
||
"description": "The run cannot be started. Two causes share this status, distinguished by `error.details.code`: `RUN_ID_CONFLICT` when the supplied `X-Run-Id` is already associated with a different request, and `CALL_CHAIN_DEPTH_EXCEEDED` when the incoming `X-Sim-Via` chain has already reached the maximum workflow-to-workflow call depth.",
|
||
"headers": {
|
||
"X-Run-Id": {
|
||
"$ref": "#/components/headers/X-Run-Id"
|
||
}
|
||
},
|
||
"content": {
|
||
"application/json": {
|
||
"schema": {
|
||
"$ref": "#/components/schemas/V2Error"
|
||
}
|
||
}
|
||
}
|
||
},
|
||
"Gone": {
|
||
"description": "The requested generated resource has expired.",
|
||
"content": {
|
||
"application/json": {
|
||
"schema": {
|
||
"$ref": "#/components/schemas/V2Error"
|
||
}
|
||
}
|
||
}
|
||
},
|
||
"PayloadTooLarge": {
|
||
"description": "The request, or a resource collection it must materialize, exceeds the allowed size. Besides an oversized request body, this covers a generated artifact that renders past the download ceiling and a workspace folder tree too large to load in full.",
|
||
"content": {
|
||
"application/json": {
|
||
"schema": {
|
||
"$ref": "#/components/schemas/V2Error"
|
||
}
|
||
}
|
||
}
|
||
},
|
||
"UnsupportedMediaType": {
|
||
"description": "The request uses an unsupported media type.",
|
||
"content": {
|
||
"application/json": {
|
||
"schema": {
|
||
"$ref": "#/components/schemas/V2Error"
|
||
}
|
||
}
|
||
}
|
||
},
|
||
"Locked": {
|
||
"description": "The resource is locked and cannot be modified.",
|
||
"content": {
|
||
"application/json": {
|
||
"schema": {
|
||
"$ref": "#/components/schemas/V2Error"
|
||
}
|
||
}
|
||
}
|
||
},
|
||
"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"
|
||
}
|
||
}
|
||
}
|
||
},
|
||
"ClientClosedRequest": {
|
||
"description": "The client closed the connection before the response was produced.",
|
||
"content": {
|
||
"application/json": {
|
||
"schema": {
|
||
"$ref": "#/components/schemas/V2Error"
|
||
}
|
||
}
|
||
}
|
||
},
|
||
"InternalError": {
|
||
"description": "An unexpected server error occurred.",
|
||
"content": {
|
||
"application/json": {
|
||
"schema": {
|
||
"$ref": "#/components/schemas/V2Error"
|
||
}
|
||
}
|
||
}
|
||
},
|
||
"ServiceUnavailable": {
|
||
"description": "A required service is temporarily unavailable.",
|
||
"content": {
|
||
"application/json": {
|
||
"schema": {
|
||
"$ref": "#/components/schemas/V2Error"
|
||
}
|
||
}
|
||
}
|
||
}
|
||
},
|
||
"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": "Optional structured error details."
|
||
}
|
||
},
|
||
"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. `redacting` is transient while run output is scrubbed. `paused` is reported when a resume attempt did not run to completion and the run is waiting to be resumed again."
|
||
},
|
||
"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` to continue, and stop when it is null. Most v2 lists page, so null means the last page was reached. A few are full-set lists that return their whole bounded result in one response and therefore always report null; those say so in the operation description. Either way, null means there is nothing further to fetch — never construct a cursor 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. `redacting` is transient while run output is scrubbed. `paused` is reported when a resume attempt did not run to completion and the run is waiting to be resumed again."
|
||
},
|
||
"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",
|
||
"description": "Canonical slash-prefixed folder path. `/` is the workspace root."
|
||
},
|
||
{
|
||
"type": "null"
|
||
}
|
||
],
|
||
"description": "Workflow folder path, or null when unavailable."
|
||
},
|
||
"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, with credential values redacted: `oauth-input`, `password: true`, and table sub-block values are null; sensitive nested tool parameters and every parameter without authoritative codec metadata are null; and `{{VAR}}` references in non-opaque fields are preserved. Null when no snapshot is retained."
|
||
},
|
||
"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"
|
||
}
|