mirror of
https://github.com/simstudioai/sim.git
synced 2026-10-31 03:47:12 +08:00
* fix(v2): give every collection one pagination contract and close four envelope holes
A fractional `limit` reached Postgres as `LIMIT 2.5` and answered 500 on both
`GET /workflows` and `GET /audit-logs`: each list re-declared the param inline,
and these two copies lost their `.int()`. The same divergence left `limit`
validated five different ways and five collections emitting `nextCursor` while
accepting no `limit` at all, or accepting one and silently discarding it.
Adds `v2PaginationFields()` in `contracts/v2/shared.ts` — a bounded integer
`limit` and an opaque `cursor` — and adopts it across all 17 paged lists, so the
family cannot drift again. `/files`, `/logs` and `/tables` keep the truncate-and-
clamp leniency they published, now as an explicit named mode rather than three
hand-rolled copies.
Gives `/skills`, `/custom-tools`, `/secrets`, `/credentials` and `/knowledge`
real pagination using the existing cursor codecs: a keyset for the four whose
page comes from one ordered SQL read, and the offset cursor for `/skills`, whose
merge of the static builtin registry into DB rows cannot be expressed as a SQL
keyset. Each keyset sort now ends in a unique `id`; knowledge tie-broke on
`createdAt`, which cannot separate rows sharing a millisecond.
Two correctness fixes pagination forced: the secrets visibility filter moved from
a post-query JS pass into SQL, because trimming rows after the page is cut
returns fewer than `limit` while `nextCursor` claims more; and the skills list
stopped selecting the 50k-char `content` column only to discard it.
Also restores the canonical error envelope where it had holes: a malformed JSON
body returned a bare `{"error":string}` because the envelope was a per-route
opt-in only 8 of 77 routes remembered, and an unknown `/api/v2` path returned an
HTML 404. Both are now defaults — `V2_PARSE_DEFAULTS` on the builders and the two
raw routes, and a catch-all whose body is byte-identical to the rollout gate's so
an unknown path stays indistinguishable from an ungated one.
Consolidates the keyset paging block (`resumeKeyset`/`keysetPage` in
`list-query.ts`) that had been open-coded in six modules, and folds the bespoke
`InvalidWorkflowListCursorError` into the `OrchestrationError` every other list
already used.
Prevention: the contract sweep in `list-pagination.test.ts` now also asserts that
every paged list rejects a fractional `limit`, that every list query is
`.strict()`, and that the three clamping lists still truncate. The fractional-
limit assertion is what caught `/audit-logs`. Documented in
`.agents/skills/v2-api-conventions/SKILL.md`.
405 responses still carry no `Allow` header — Next.js generates those before any
handler runs. Recorded as a known gap.
* fix(v2): bind the offset cursor to the query state it counts positions in
An offset names a position in one exact sequence. `GET /skills` accepted a bare
`{offset}` cursor and applied it to whatever sequence the next request asked
for, so following `nextCursor` with a different `search`, `sortBy` or
`sortOrder` silently skipped rows, repeated them, or landed past the end and
returned an empty page while the cursor implied more.
Fixed in the codec rather than the route so the sibling could not keep the gap:
`decodeOffsetCursor` now takes a scope stamp and rejects a cursor minted under a
different one, which is what `decodeSortedCursor` has always done for keysets.
`offsetCursorScope()` builds the stamp from every param that filters or orders
the sequence; `limit` is excluded because it selects how much of the sequence to
return, not what the sequence is, so paging with a different page size still
works.
`GET /knowledge/{id}/documents` had the identical latent gap and gets the same
treatment — the compiler surfaced it as soon as the signature changed.
1352 lines
46 KiB
JSON
1352 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. Values outside 1–1000 are truncated and clamped into that range rather than rejected. Defaults to 100.",
|
||
"schema": {
|
||
"description": "Maximum log entries per page. Values outside 1–1000 are truncated and clamped into that range rather than rejected. Defaults to 100.",
|
||
"type": "integer",
|
||
"minimum": 1,
|
||
"maximum": 1000,
|
||
"default": 100
|
||
}
|
||
},
|
||
{
|
||
"name": "cursor",
|
||
"in": "query",
|
||
"required": false,
|
||
"description": "Opaque cursor returned by the previous page.",
|
||
"schema": {
|
||
"description": "Opaque cursor returned by the previous page.",
|
||
"type": "string",
|
||
"minLength": 1
|
||
}
|
||
},
|
||
{
|
||
"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"
|
||
}
|