Files
sim/apps/docs/openapi-v2-logs.json
T
Waleed 6bbabbbac6 fix(cli): close follow-up gaps (#7137)
* chore: run the orphaned migration-safety test, and stop publishing real-looking ids

One of the eight script tests was reachable from no entrypoint, so it had
never run in CI — it passes, it was simply never invoked. This is the second
time that hand-maintained list has drifted from the files beside it; the
audit runner's own header records the first. The guard against a third is a
`check:*` script rather than a test, because the runner derives its list
from that namespace and so picks the guard up by name — a test would have
had to be hand-added to the very list it guards.

A published spec sat outside the generator's manifest and so outside its
drift check, and carried six example ids with the texture of real generated
ones rather than the pandigital placeholders the rest of the repo uses. The
new check globs the directory instead of reading the manifest, since the
manifest is what omitted the file. The one pre-existing borderline id is
allowlisted with a reason: loosening the threshold to admit it would have
hidden one of the six.

* fix(cli): close the gaps black-box testing the shipped CLI found

`sim profiles <anything>` still exited 0, so a probe reading the exit code
to ask whether a command exists was told yes — the one group the earlier
guard missed. The exemption was written for commands that are both a group
and a leaf, but only `files restore` takes an operand; `profiles` takes
none. Registering its listing as a default subcommand puts it back among
the pure dispatchers the existing guard already covers, so the guard itself
did not need widening.

Three commands refuse a workspace API key and said nothing, while their
menu siblings said so — reading as though they accept one. They are
hand-written, so they never reached the code that appends the note. That
note now comes from a helper taking the operation, so a command names the
operation it invokes and the two cannot disagree, and a test fails if a
hand-written command ever calls a restricted operation without it.

A blank numeric value in a request body still became a real zero, the same
coercion already fixed for query strings: the guard keyed off the slot when
the distinction is the field's declared type. Twenty-one fields across
fifteen operations were affected. An empty body string still clears a
description.

Blank values for the root endpoint, workspace and profile flags fell back
to what was configured instead of being refused, and a whitespace workspace
was accepted verbatim. A hand-written profile name carrying padding listed
as reachable but resolved to defaults rather than erroring. Two schema
descriptions named request fields that no flag spells, and a rejected value
was echoed unredacted by four messages while their siblings redacted it.

A write now re-emits a section header it was not asked to touch byte for
byte. The blank-line normalisation around it is left alone: making the
writer position-faithful is a change to its model, not a fix.

* fix(scripts): match example uuids case-insensitively in the spec audit

The pattern only recognised lowercase hex, so an uppercase id in a
published spec was never examined and the audit reported success without
having looked at it.

Matching case-insensitively is not enough on its own: hex is
case-insensitive, so a mixed-case id counts `A` and `a` as two digits and
reports twenty distinct ones rather than sixteen. That inflated count
clears the threshold the texture test uses to recognise a hand-authored
placeholder, so a real id could have passed for one. The allowlist is an
exact-string lookup and would likewise have missed an uppercase spelling of
an entry. Both checks and the lookup now take a normalised id, while the
finding still reports the spelling as it appears in the file.

* fix(cli): stop a refusal being swallowed, and gate example ids by name

A blank root flag was refused everywhere except `profiles`, where the catch
that lets a broken profile still list absorbed it and the command exited 0
after printing the table. The refusal now carries its own error class, which
is what the listing rethrows on — the two are distinguished by type rather
than by matching message text, and a genuinely broken profile still lists.

The unknown-profile message redacted the name the caller typed but not the
suggestion or the list of configured names beside it, which come from the
same file and are equally attacker-influenced once it has been hand-edited.
Those are redacted now, as is every other message in these two files that
quotes a name read out of the config, and the profile listing flattens the
names it renders the way it already flattened the error column.

The example-id audit judged a uuid by its digit texture, on the premise that
a real one essentially never looks hand-authored. Measured against ten
million generated ids, 0.81% of them do — one in 124, where this change
alone replaced six. Requiring each digit exactly twice takes that to zero
but rejects all fourteen placeholders now in the specs, so it is no cheaper
than the alternative. The audit now holds the eighteen ids the specs
actually use, which is one file rather than the twenty-seven a reserved
format would touch, and a new id fails until someone lists it — which is the
review the check exists to force.

* fix(scripts): match the uuid sentinels exactly rather than by shape

Accepting any id built from at most two distinct hex digits let something
through that was never on the approved list. A generated id essentially
never has that shape, so the practical risk was small — but this check had
just stopped being a shape test and become a list, and a structural
exception is the one thing that undoes that. The two ids it exists for are
the nil and max sentinels, and both are matched by value now.
2026-08-26 17:53:39 -07:00

1860 lines
81 KiB
JSON
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{
"openapi": "3.1.0",
"info": {
"title": "Sim API v2 — Logs",
"description": "Version 2 of the Sim REST API for workflow execution logs: listing and sorting runs with filters, retrieving complete diagnostic run snapshots, and reading bucketed execution statistics.",
"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, sorting by start time, duration, cost, or status, and opaque cursor pagination. Chat and Sim-agent job runs join the sequence with `includeJobRuns=true`, which is accepted only under `sortBy=startedAt` — their cost is stored as a document and their status is not comparable, so they cannot participate in the other orderings. Each item's `files` lists only the files the run itself produced, addressed by `downloadPath`; input attachments a caller supplied are read through the files API instead. 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. A workspace folder tree over 10,000 folders is a `413`.",
"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. At most 200 entries.",
"schema": {
"type": "string",
"description": "Comma-separated workflow identifiers to include. An empty entry is rejected. At most 200 entries."
}
},
{
"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`. At most 100 entries.",
"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`. At most 100 entries."
}
},
{
"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 workflow run; a job run never carries one, whatever this is set to. `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 workflow run; a job run never carries one, whatever this is set to. `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 11000 are truncated and clamped into that range rather than rejected. Defaults to 100.",
"schema": {
"description": "Maximum log entries per page. Values outside 11000 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": "status",
"in": "query",
"required": false,
"description": "Comma-separated execution statuses to include, from `pending` | `running` | `paused` | `redacting` | `completed` | `failed` | `cancelled`. An empty entry is rejected. ANDed with `level`, which reports severity rather than lifecycle.",
"schema": {
"type": "string",
"description": "Comma-separated execution statuses to include, from `pending` | `running` | `paused` | `redacting` | `completed` | `failed` | `cancelled`. An empty entry is rejected. ANDed with `level`, which reports severity rather than lifecycle."
}
},
{
"name": "workflowName",
"in": "query",
"required": false,
"description": "Case-insensitive substring match against the run's workflow name. Runs whose workflow has been deleted match nothing, because the name is no longer joinable.",
"schema": {
"type": "string",
"minLength": 1,
"maxLength": 200,
"description": "Case-insensitive substring match against the run's workflow name. Runs whose workflow has been deleted match nothing, because the name is no longer joinable."
}
},
{
"name": "includeJobRuns",
"in": "query",
"required": false,
"description": "Whether Chat and Sim-agent job runs join the sequence alongside workflow runs. Job runs report `kind: \"job\"`, carry no `workflow` summary, and never carry a cost ledger. They are dropped entirely — not partially matched — whenever a filter they cannot answer is set: by workflow, workflow name, folder, model, or status. A filter therefore never means two different things across the union. Accepted only when sorting by `startedAt`: job runs record cost as a document and no comparable status, so they cannot participate in the other orderings.",
"schema": {
"description": "Whether Chat and Sim-agent job runs join the sequence alongside workflow runs. Job runs report `kind: \"job\"`, carry no `workflow` summary, and never carry a cost ledger. They are dropped entirely — not partially matched — whenever a filter they cannot answer is set: by workflow, workflow name, folder, model, or status. A filter therefore never means two different things across the union. Accepted only when sorting by `startedAt`: job runs record cost as a document and no comparable status, so they cannot participate in the other orderings.",
"type": "boolean"
}
},
{
"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": "sortBy",
"in": "query",
"required": false,
"description": "Field used to sort the result. `durationMs` and `cost` are null until a run settles; those runs order as though the value were below every recorded one, so they trail an ascending page and lead a descending one. Only `startedAt` can order Chat and Sim-agent job runs, so any other value is rejected when job runs are included.",
"schema": {
"default": "startedAt",
"description": "Field used to sort the result. `durationMs` and `cost` are null until a run settles; those runs order as though the value were below every recorded one, so they trail an ascending page and lead a descending one. Only `startedAt` can order Chat and Sim-agent job runs, so any other value is rejected when job runs are included.",
"type": "string",
"enum": ["startedAt", "durationMs", "cost", "status"]
}
},
{
"name": "sortOrder",
"in": "query",
"required": false,
"description": "Sort direction.",
"schema": {
"default": "desc",
"description": "Sort direction.",
"type": "string",
"enum": ["asc", "desc"]
}
},
{
"name": "folderPaths",
"in": "query",
"required": false,
"description": "Comma-separated workflow folder paths to include. At most 100 entries. A path covers its whole subtree, so `/prod` also selects runs in `/prod/nested`. 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. At most 100 entries. A path covers its whole subtree, so `/prod` also selects runs in `/prod/nested`. 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"
},
"413": {
"$ref": "#/components/responses/PayloadTooLarge"
},
"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. A workspace folder tree over 10,000 folders is a `413`. 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": "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"
},
"413": {
"$ref": "#/components/responses/PayloadTooLarge"
},
"429": {
"$ref": "#/components/responses/RateLimited"
},
"500": {
"$ref": "#/components/responses/InternalError"
},
"503": {
"$ref": "#/components/responses/ServiceUnavailable"
}
}
}
},
"/api/v2/logs/stats": {
"get": {
"operationId": "getLogStats",
"summary": "Get Log Statistics",
"description": "Bucketed run counts, success rate, error count, and mean latency for a workspace and for each of its workflows — the aggregate a caller would otherwise have to page every run to compute. The window spans `startDate` through `endDate` when both are supplied; an omitted edge falls back to the oldest matching run on the left and to the later of the newest matching run and now on the right. With no matching runs the right edge falls back to now and the left to 24 hours before that right edge — the trailing 24 hours when neither edge was supplied, and the 24 hours preceding `endDate` when only `endDate` was supplied. A supplied `startDate` is still used verbatim, so a `startDate` without an `endDate` yields `[startDate, now]`, which can be any width. The window is divided into exactly `segmentCount` equal buckets whose width is `max(60000, floor(windowMs / segmentCount))` milliseconds. The one-minute floor is a floor on bucket width, not on the window: when it applies, the series runs past `timeBounds.end` and the trailing buckets are empty rather than the window being compressed. A folder path covers its whole subtree. Per-workflow series are capped and `workflowsTruncated` reports whether the cap applied; the workspace totals are always computed from every workflow. 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. A workspace folder tree over 10,000 folders is a `413`.",
"tags": ["Logs"],
"parameters": [
{
"name": "workspaceId",
"in": "query",
"required": true,
"description": "Workspace whose execution statistics to summarize.",
"schema": {
"type": "string",
"minLength": 1,
"maxLength": 128,
"description": "Workspace whose execution statistics to summarize."
}
},
{
"name": "workflowIds",
"in": "query",
"required": false,
"description": "Comma-separated workflow identifiers to include. At most 200 entries. An empty entry is rejected.",
"schema": {
"type": "string",
"description": "Comma-separated workflow identifiers to include. At most 200 entries. An empty entry is rejected."
}
},
{
"name": "folderPaths",
"in": "query",
"required": false,
"description": "Comma-separated workflow folder paths to include. At most 100 entries. A path covers its whole subtree. 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. At most 100 entries. A path covers its whole subtree. A path that names no folder narrows the result to nothing, so the response is an empty page rather than an error."
}
},
{
"name": "triggers",
"in": "query",
"required": false,
"description": "Comma-separated trigger types to include. An empty entry is rejected. The vocabulary is open, so an unrecognized member selects no runs; the literal `all` disables this filter.",
"schema": {
"type": "string",
"description": "Comma-separated trigger types to include. An empty entry is rejected. The vocabulary is open, so an unrecognized member selects no runs; the literal `all` disables this filter."
}
},
{
"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": "segmentCount",
"in": "query",
"required": false,
"description": "Number of equal time buckets to divide the window into, from 1 to 500. Exactly this many buckets are always returned. Buckets are never narrower than one minute, so on a short window the series extends past the end of the window rather than being compressed, and the trailing buckets are empty.",
"schema": {
"default": 72,
"description": "Number of equal time buckets to divide the window into, from 1 to 500. Exactly this many buckets are always returned. Buckets are never narrower than one minute, so on a short window the series extends past the end of the window rather than being compressed, and the trailing buckets are empty.",
"type": "integer",
"minimum": 1,
"maximum": 500
}
}
],
"responses": {
"200": {
"description": "Bucketed execution statistics for the 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/V2LogStatsResponse"
}
}
}
},
"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"
},
"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"
}
}
}
}
},
"PayloadTooLarge": {
"description": "The request, or a resource collection it must materialize, exceeds the allowed size: an oversized request body, a generated artifact past the download ceiling, or a workspace folder tree too large to load in full.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/V2Error"
},
"example": {
"error": {
"code": "PAYLOAD_TOO_LARGE",
"message": "Request body is too large"
}
}
}
}
},
"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 callers 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.\n- `WORKSPACE_PLAN_CAPABILITY_REQUIRED` — The workspace's plan does not include a capability this request depends on. The message names the capability; upgrading the workspace's plan is the remedy.\n- `CHAT_AUTH_MODE_NOT_PERMITTED` — The workspace's permission group does not allow the chat authentication mode the request selected. A mode already saved on the deployment may still be re-saved; changing to a disallowed one cannot.\n- `CONNECTOR_MANAGED_RESOURCE_READ_ONLY` — This resource is managed by a knowledge base connector and cannot be edited directly. Change it at the source and re-sync, or exclude the document from the connector."
}
},
"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": {
"kind": {
"type": "string",
"enum": ["workflow", "job"],
"description": "Whether the run executed a workflow or a Chat / Sim-agent job. Job runs appear only when `includeJobRuns=true`."
},
"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 the run has neither a recorded total nor an itemized ledger."
},
"files": {
"anyOf": [
{
"type": "array",
"items": {
"$ref": "#/components/schemas/V2LogFile"
}
},
{
"type": "null"
}
],
"description": "Files the run produced, or null when none are recorded. Only the run's own output files appear; input attachments a caller supplied are addressed through the files API instead."
},
"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": [
"kind",
"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."
},
"V2LogFile": {
"type": "object",
"properties": {
"id": {
"type": "string",
"description": "Identifier to address this file by on the download endpoint."
},
"name": {
"type": "string",
"description": "File name, including its extension."
},
"size": {
"type": "integer",
"minimum": 0,
"maximum": 9007199254740991,
"description": "File size in bytes."
},
"type": {
"type": "string",
"description": "MIME type recorded for the file."
},
"downloadPath": {
"type": "string",
"description": "Path to fetch this file's bytes from, relative to the API host."
}
},
"required": ["id", "name", "size", "type", "downloadPath"],
"additionalProperties": false,
"title": "Execution log file",
"description": "A file produced by the run this log records."
},
"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": [
{
"kind": "workflow",
"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": [
{
"id": "f1c3a7d0-4b52-4a8e-9f61-2d7c8b3e5a04",
"name": "summary.pdf",
"size": 18422,
"type": "application/pdf",
"downloadPath": "/api/v2/workflows/3b1f7c92-8d4e-4a6b-9c0d-5e2f8a714b36/runs/e4f8d2b6-9a1c-4e3d-8b7f-5c0a2d9e6f13/files/f1c3a7d0-4b52-4a8e-9f61-2d7c8b3e5a04"
}
]
}
],
"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": {
"$ref": "#/components/schemas/V2LogFile"
}
},
{
"type": "null"
}
],
"description": "Files the run produced, or null when none are recorded. Only the run's own output files appear; input attachments a caller supplied are addressed through the files API instead."
},
"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."
},
"items": {
"anyOf": [
{
"type": "array",
"items": {
"type": "object",
"properties": {
"category": {
"type": "string",
"enum": ["fixed", "model", "tool"],
"description": "What the line is for: the run's base fee (`fixed`), one model's inference (`model`), or one metered tool or integration call (`tool`)."
},
"description": {
"type": "string",
"description": "Human-readable name of the billed item, such as the model or tool id."
},
"cost": {
"type": "number",
"description": "Amount billed for this line, in USD."
},
"inputTokens": {
"description": "Input tokens attributed to this line. Absent for lines that do not bill tokens.",
"type": "number"
},
"outputTokens": {
"description": "Output tokens attributed to this line. Absent for lines that do not bill tokens.",
"type": "number"
}
},
"required": ["category", "description", "cost"],
"additionalProperties": false,
"description": "One billed line of a run, folded across every event that billed it."
}
},
{
"type": "null"
}
],
"description": "Billed lines reconciling to `total`, or null when no itemized ledger exists for the run."
}
},
"required": ["total", "items"],
"additionalProperties": false
},
{
"type": "null"
}
],
"description": "Cost charged for the run, or null when unavailable."
},
"workflowInput": {
"anyOf": [
{
"description": "Caller-supplied trigger payload for the run."
},
{
"type": "null"
}
],
"description": "Input the run was triggered with, or null when the run recorded none. Credential-bearing and PII-masked values are redacted the same way `finalOutput` is."
},
"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",
"workflowInput",
"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,
"items": [
{
"category": "fixed",
"description": "Base execution charge",
"cost": 0.001
},
{
"category": "model",
"description": "gpt-5",
"cost": 0.0022,
"inputTokens": 1840,
"outputTokens": 260
}
]
},
"workflowInput": {
"ticketId": "T-4821"
},
"createdAt": "2026-01-15T10:30:00.000Z"
}
}
]
},
"V2WorkflowLogStats": {
"type": "object",
"properties": {
"workflowId": {
"type": "string",
"description": "Workflow identifier, or the literal `deleted` for the single series that collects runs whose workflow no longer exists."
},
"workflowName": {
"type": "string",
"description": "Workflow name, or `Deleted Workflow`."
},
"segments": {
"type": "array",
"items": {
"$ref": "#/components/schemas/V2LogStatsSegment"
},
"description": "One entry per bucket, in order, including buckets with no runs."
},
"totalExecutions": {
"type": "number",
"description": "Runs for this workflow across the window."
},
"totalSuccessful": {
"type": "number",
"description": "Runs for this workflow that did not error."
},
"overallSuccessRate": {
"type": "number",
"description": "Percentage of runs that did not error, from 0 to 100. 100 when there were no runs."
}
},
"required": [
"workflowId",
"workflowName",
"segments",
"totalExecutions",
"totalSuccessful",
"overallSuccessRate"
],
"additionalProperties": false,
"title": "Per-workflow log stats",
"description": "Bucketed run counts and success rate for one workflow."
},
"V2LogStatsSegment": {
"type": "object",
"properties": {
"timestamp": {
"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 start of the bucket."
},
"totalExecutions": {
"type": "number",
"description": "Runs that started inside the bucket."
},
"successfulExecutions": {
"type": "number",
"description": "Runs in the bucket that did not error."
},
"avgDurationMs": {
"type": "number",
"description": "Mean duration of the bucket's runs in milliseconds, weighted by run count. Zero when no run in the bucket recorded a duration."
}
},
"required": ["timestamp", "totalExecutions", "successfulExecutions", "avgDurationMs"],
"additionalProperties": false,
"title": "Log stats bucket",
"description": "Run counts and mean latency for one time bucket."
},
"V2LogStats": {
"type": "object",
"properties": {
"workflows": {
"type": "array",
"items": {
"$ref": "#/components/schemas/V2WorkflowLogStats"
},
"description": "Per-workflow series, ordered by error rate descending then by name, capped at 200 entries."
},
"workflowsTruncated": {
"type": "boolean",
"description": "Whether `workflows` was cut to 200 entries. The workspace totals and `aggregateSegments` are computed from every workflow before the cut, so they stay exact either way."
},
"aggregateSegments": {
"type": "array",
"items": {
"$ref": "#/components/schemas/V2LogStatsSegment"
},
"description": "Workspace-wide totals per bucket, in the same order as each workflow series."
},
"totalRuns": {
"type": "number",
"description": "Runs in the window across the whole workspace."
},
"totalErrors": {
"type": "number",
"description": "Runs in the window that errored."
},
"avgLatency": {
"type": "number",
"description": "Mean run duration in milliseconds across the window, weighted by run count."
},
"timeBounds": {
"type": "object",
"properties": {
"start": {
"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 start of the window."
},
"end": {
"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 end of the window."
}
},
"required": ["start", "end"],
"additionalProperties": false,
"description": "The window the buckets span. `startDate` and `endDate` are used verbatim when supplied; an omitted edge falls back to the oldest matching run on the left and to the later of the newest matching run and now on the right. With no matching runs the right edge falls back to now and the left to 24 hours before that right edge — the trailing 24 hours when neither edge was supplied, and the 24 hours preceding `endDate` when only `endDate` was supplied. A supplied `startDate` is still used verbatim, so a `startDate` without an `endDate` yields `[startDate, now]`, which can be any width."
},
"segmentMs": {
"type": "number",
"description": "Width of one bucket in milliseconds."
}
},
"required": [
"workflows",
"workflowsTruncated",
"aggregateSegments",
"totalRuns",
"totalErrors",
"avgLatency",
"timeBounds",
"segmentMs"
],
"additionalProperties": false,
"title": "Execution log statistics",
"description": "Bucketed success rate, error count, and latency for a workspace and each of its workflows."
},
"V2LogStatsResponse": {
"type": "object",
"properties": {
"data": {
"description": "Response data.",
"$ref": "#/components/schemas/V2LogStats"
}
},
"required": ["data"],
"additionalProperties": false,
"title": "Log statistics response",
"description": "Bucketed success rate, error count, and latency for a workspace and its workflows.",
"examples": [
{
"data": {
"workflows": [
{
"workflowId": "3b1f7c92-8d4e-4a6b-9c0d-5e2f8a714b36",
"workflowName": "Customer Support Agent",
"segments": [
{
"timestamp": "2026-01-15T10:00:00.000Z",
"totalExecutions": 40,
"successfulExecutions": 38,
"avgDurationMs": 1180
}
],
"totalExecutions": 40,
"totalSuccessful": 38,
"overallSuccessRate": 95
}
],
"workflowsTruncated": false,
"aggregateSegments": [
{
"timestamp": "2026-01-15T10:00:00.000Z",
"totalExecutions": 40,
"successfulExecutions": 38,
"avgDurationMs": 1180
}
],
"totalRuns": 40,
"totalErrors": 2,
"avgLatency": 1180,
"timeBounds": {
"start": "2026-01-15T10:00:00.000Z",
"end": "2026-01-15T22:00:00.000Z"
},
"segmentMs": 600000
}
}
]
}
}
},
"x-generated-by": "scripts/generate-openapi.ts"
}