{
  "openapi": "3.1.0",
  "info": {
    "title": "Edgaze API",
    "version": "1.1.0",
    "description": "Public REST API for running Edgaze workflows programmatically. Every response uses the same envelope: success returns the payload directly; failure returns `{ error: { code, message, details? } }`. Versioning: within /v1 only additive changes are made (new endpoints, optional parameters, response fields, and values in open sets such as event types and error codes), so ignore what you do not recognize. Breaking changes are not made within v1. Deprecations are marked `deprecated: true` and announced with `Deprecation` and `Link: rel=\"deprecation\"` response headers; deprecated v1 features keep working while v1 is served. `info.version` is semver: minor for additions, patch for document fixes. Policy: https://www.edgaze.ai/docs/api/versioning",
    "contact": {
      "name": "Edgaze",
      "url": "https://edgaze.ai/docs/api"
    }
  },
  "servers": [
    {
      "url": "https://api.edgaze.ai/v1",
      "description": "Production"
    }
  ],
  "tags": [
    {
      "name": "Workflows",
      "description": "Discover and inspect runnable workflows."
    },
    {
      "name": "Runs",
      "description": "Start runs and read their durable event log."
    }
  ],
  "security": [
    {
      "bearerAuth": []
    }
  ],
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "Send `Authorization: Bearer <api key>` on every request. Each operation lists the scopes it accepts in `x-required-scopes`, evaluated as `x-scope-match: any`: holding ANY ONE of the listed scopes authorizes the call. A key missing all of them gets 403 `insufficient_scope`."
      }
    },
    "schemas": {
      "ErrorCode": {
        "type": "string",
        "enum": [
          "unauthorized",
          "insufficient_scope",
          "forbidden",
          "not_found",
          "invalid_request",
          "invalid_json",
          "invalid_inputs",
          "missing_workflow",
          "invalid_cursor",
          "method_not_allowed",
          "spend_cap_reached",
          "idempotency_conflict",
          "rate_limited",
          "account_restricted",
          "integration_connection_required",
          "creator_listings_paused",
          "output_too_large",
          "cancel_unavailable",
          "internal_error"
        ],
        "description": "Stable, machine-friendly error identifier. Branch on this value; the human `message` may change. Two codes are transport-level rather than operation-specific and so are not listed under any single path: `method_not_allowed` (an HTTP method this route does not implement, returned with an `Allow` header) and `invalid_json` (a request body that is not parseable JSON).",
        "examples": ["invalid_inputs"]
      },
      "ErrorDetail": {
        "type": "object",
        "properties": {
          "input": {
            "type": "string",
            "description": "The offending input key (`nodeId` or exact `name`).",
            "examples": ["topic"]
          },
          "message": {
            "type": "string",
            "description": "Human-readable explanation of why this input was rejected.",
            "examples": ["is required"]
          }
        },
        "required": ["input", "message"],
        "description": "Per-input failure attached to `invalid_inputs` errors."
      },
      "InvalidInputsError": {
        "type": "object",
        "properties": {
          "code": {
            "type": "string",
            "const": "invalid_inputs"
          },
          "message": {
            "type": "string",
            "description": "Human-readable failure reason. Do not parse; branch on `code` instead.",
            "examples": ["Rate limit exceeded. Slow down and retry."]
          },
          "details": {
            "description": "Each rejected input. Absent when the request was rejected as a whole (for example `inputs` was not an object).",
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ErrorDetail"
            }
          }
        },
        "required": ["code", "message"],
        "description": "Inner error body for `invalid_inputs`."
      },
      "IntegrationConnectionRequiredError": {
        "type": "object",
        "properties": {
          "code": {
            "type": "string",
            "const": "integration_connection_required"
          },
          "message": {
            "type": "string",
            "description": "Human-readable failure reason. Do not parse; branch on `code` instead.",
            "examples": ["Rate limit exceeded. Slow down and retry."]
          },
          "details": {
            "minItems": 1,
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/IntegrationConnectionDetail"
            },
            "description": "Each account the caller must connect before the workflow can run."
          }
        },
        "required": ["code", "message", "details"],
        "description": "Inner error body for `integration_connection_required`."
      },
      "IntegrationConnectionDetail": {
        "type": "object",
        "properties": {
          "provider": {
            "type": "string",
            "description": "Display name of the service.",
            "examples": ["GitHub"]
          },
          "providerId": {
            "type": "string",
            "description": "Stable provider id.",
            "examples": ["github"]
          },
          "makesChanges": {
            "type": "boolean",
            "description": "True when the workflow writes to this account, not only reads it."
          },
          "connectUrl": {
            "type": "string",
            "description": "Where the account owner connects the service in Edgaze, in a browser.",
            "examples": ["https://www.edgaze.ai/settings#gateway"]
          }
        },
        "required": ["provider", "providerId", "makesChanges", "connectUrl"],
        "description": "A third-party account the caller must connect, attached to `integration_connection_required`."
      },
      "PlainError": {
        "type": "object",
        "properties": {
          "code": {
            "type": "string",
            "enum": [
              "unauthorized",
              "insufficient_scope",
              "forbidden",
              "not_found",
              "invalid_request",
              "invalid_json",
              "missing_workflow",
              "invalid_cursor",
              "method_not_allowed",
              "spend_cap_reached",
              "idempotency_conflict",
              "rate_limited",
              "account_restricted",
              "creator_listings_paused",
              "output_too_large",
              "cancel_unavailable",
              "internal_error"
            ],
            "description": "Any code other than `invalid_inputs` and `integration_connection_required`."
          },
          "message": {
            "type": "string",
            "description": "Human-readable failure reason. Do not parse; branch on `code` instead.",
            "examples": ["Rate limit exceeded. Slow down and retry."]
          }
        },
        "required": ["code", "message"],
        "description": "Inner error body for codes that carry no `details`."
      },
      "ErrorObject": {
        "oneOf": [
          {
            "$ref": "#/components/schemas/InvalidInputsError"
          },
          {
            "$ref": "#/components/schemas/IntegrationConnectionRequiredError"
          },
          {
            "$ref": "#/components/schemas/PlainError"
          },
          {
            "$ref": "#/components/schemas/UnknownError"
          }
        ],
        "description": "Inner error body carried by every failure response, discriminated by `code`. Only `invalid_inputs` and `integration_connection_required` carry `details`."
      },
      "ErrorEnvelope": {
        "type": "object",
        "properties": {
          "error": {
            "$ref": "#/components/schemas/ErrorObject"
          }
        },
        "required": ["error"],
        "description": "Envelope returned by every non-2xx response from the v1 REST API.",
        "examples": [
          {
            "error": {
              "code": "unauthorized",
              "message": "Invalid or missing API key."
            }
          }
        ]
      },
      "RunModel": {
        "type": "string",
        "enum": ["free", "per_run"],
        "description": "Pricing model for a workflow listing."
      },
      "UserAccess": {
        "type": "string",
        "enum": ["owned", "purchased", "wallet", "purchase_required"],
        "description": "How the authenticated key holder can currently pay to run this workflow: `owned` (the caller is the creator), `purchased` (a prior purchase covers it), or `wallet` (it will be billed to wallet balance at run time). `purchase_required` marks legacy unlock listings that cannot be run or bought through the API — `apiRunnableNow` is false and POST /runs returns 403 `forbidden`. Purchasing is a web-only checkout flow; send the buyer to `purchaseUrl` on the workflow detail response."
      },
      "CredentialMode": {
        "type": "string",
        "enum": ["edgaze_hosted", "byok"],
        "description": "Whether the workflow runs on Edgaze-hosted credentials or the caller's own keys."
      },
      "RequiredProvider": {
        "type": "string",
        "enum": ["openai", "anthropic", "google", "azure_openai", "vertex", "openrouter"],
        "description": "External model provider a BYOK workflow expects the caller to configure."
      },
      "PublicRunStatus": {
        "type": "string",
        "enum": ["pending", "running", "suspended", "completed", "failed", "cancelled"],
        "description": "Canonical run status exposed by the public API."
      },
      "BillingStatus": {
        "type": "string",
        "enum": ["pending", "settled", "not_charged"],
        "description": "Whether the buyer-facing charge for this run is still pending, finalized, or not applicable."
      },
      "RunListStatus": {
        "type": "string",
        "enum": ["pending", "running", "completed", "failed", "cancelled"],
        "description": "Run status as reported by the run list. A suspended run is reported as `running` — read GET /runs/{id} for the precise state."
      },
      "Pagination": {
        "type": "object",
        "properties": {
          "offset": {
            "type": "integer",
            "minimum": 0,
            "description": "Offset of the first row in this page."
          },
          "limit": {
            "type": "integer",
            "minimum": 1,
            "maximum": 50,
            "description": "Page size actually applied (max 50)."
          },
          "hasMore": {
            "type": "boolean",
            "description": "True when another page follows this one."
          }
        },
        "required": ["offset", "limit", "hasMore"],
        "description": "Offset-based pagination cursor for list endpoints."
      },
      "WorkflowListItem": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "minLength": 1,
            "format": "uuid"
          },
          "name": {
            "type": "string"
          },
          "description": {
            "type": ["string", "null"]
          },
          "ownerHandle": {
            "type": ["string", "null"]
          },
          "edgazeCode": {
            "type": ["string", "null"]
          },
          "runModel": {
            "$ref": "#/components/schemas/RunModel"
          },
          "displayedRunPriceUsd": {
            "description": "Estimated run price shown to the buyer. The final price is confirmed when the run starts and may differ slightly from older catalog data. USD amount as a JSON number with up to six decimal places. Preserve the documented precision and do not round intermediate calculations to two decimal places.",
            "type": ["number", "null"]
          },
          "pricingExplanation": {
            "description": "Human-readable note on how this listing is priced. Present whenever `displayedRunPriceUsd` is a positive estimate; explains that settlement uses the live quote at run start.",
            "type": ["string", "null"]
          },
          "userAccess": {
            "$ref": "#/components/schemas/UserAccess"
          },
          "accessLabel": {
            "type": "string"
          },
          "apiRunnableNow": {
            "type": "boolean"
          },
          "runsCount": {
            "anyOf": [
              {
                "type": "integer",
                "minimum": 0
              },
              {
                "type": "null"
              }
            ]
          },
          "credentialMode": {
            "$ref": "#/components/schemas/CredentialMode"
          },
          "requiredProviders": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/RequiredProvider"
            }
          }
        },
        "required": [
          "id",
          "name",
          "description",
          "ownerHandle",
          "edgazeCode",
          "runModel",
          "displayedRunPriceUsd",
          "pricingExplanation",
          "userAccess",
          "accessLabel",
          "apiRunnableNow",
          "runsCount",
          "credentialMode",
          "requiredProviders"
        ],
        "description": "Compact row in the paginated workflow catalog. Fetch GET /workflows/{id} for the input schema."
      },
      "WorkflowListResponse": {
        "type": "object",
        "properties": {
          "workflows": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/WorkflowListItem"
            }
          },
          "pagination": {
            "$ref": "#/components/schemas/Pagination"
          }
        },
        "required": ["workflows", "pagination"],
        "description": "Response body for GET /workflows."
      },
      "WorkflowInputType": {
        "type": "string",
        "enum": ["text", "password", "number", "textarea", "url", "file", "json", "dropdown"],
        "description": "Control type for an input field. `dropdown` is the only type that populates `options`. `password` fields are per-run credentials: encrypted at rest and deleted when the run ends. `file` and `json` expect a JSON-encoded value."
      },
      "WorkflowInputOption": {
        "type": "object",
        "properties": {
          "label": {
            "type": "string",
            "description": "Human-readable choice shown to a person."
          },
          "value": {
            "type": "string",
            "description": "Value to send in `inputs` when this choice is picked."
          }
        },
        "required": ["label", "value"],
        "description": "One selectable choice for a `dropdown` input."
      },
      "WorkflowInputField": {
        "type": "object",
        "properties": {
          "nodeId": {
            "type": "string",
            "description": "Stable key for this field. Use it as the `inputs` key when calling POST /runs — `name` is a display label and is not guaranteed unique."
          },
          "specId": {
            "description": "Identifier of the node kind that produced this field. Informational.",
            "type": "string"
          },
          "name": {
            "type": "string",
            "description": "Display label for the field."
          },
          "description": {
            "description": "Helper text explaining the field.",
            "type": "string"
          },
          "type": {
            "$ref": "#/components/schemas/WorkflowInputType"
          },
          "required": {
            "description": "When true, POST /runs rejects the run if this key is absent.",
            "type": "boolean"
          },
          "placeholder": {
            "description": "Suggested placeholder text.",
            "type": "string"
          },
          "defaultValue": {
            "description": "Pre-filled value to use when the caller supplies nothing.",
            "type": "string"
          },
          "options": {
            "description": "Allowed choices. Present only when `type` is `dropdown`.",
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/WorkflowInputOption"
            }
          }
        },
        "required": ["nodeId", "name", "type"],
        "description": "Single input field from the workflow's published input schema."
      },
      "WorkflowRunResponseShape": {
        "type": "object",
        "properties": {
          "run": {
            "type": "object",
            "properties": {
              "id": {
                "type": "string",
                "minLength": 1,
                "format": "uuid"
              },
              "status": {
                "type": "string"
              },
              "statusUrl": {
                "type": "string",
                "format": "uri"
              },
              "eventsUrl": {
                "type": "string",
                "format": "uri"
              },
              "streamUrl": {
                "type": "string",
                "format": "uri"
              }
            },
            "required": ["id", "status", "statusUrl", "eventsUrl", "streamUrl"]
          }
        },
        "required": ["run"],
        "description": "Documentation-only shape showing what POST /runs returns for this workflow."
      },
      "WorkflowExampleRunRequest": {
        "type": "object",
        "properties": {
          "method": {
            "type": "string",
            "const": "POST"
          },
          "url": {
            "type": "string",
            "format": "uri"
          },
          "body": {
            "type": "object",
            "properties": {
              "workflow": {
                "type": "string",
                "minLength": 1,
                "format": "uuid"
              },
              "inputs": {
                "type": "object",
                "propertyNames": {
                  "type": "string"
                },
                "additionalProperties": {}
              },
              "idempotencyKey": {
                "type": "string"
              }
            },
            "required": ["workflow", "inputs"],
            "additionalProperties": {}
          }
        },
        "required": ["method", "url", "body"],
        "description": "Ready-to-copy example POST /runs payload for this workflow."
      },
      "WorkflowDetail": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "minLength": 1,
            "format": "uuid"
          },
          "name": {
            "type": "string"
          },
          "description": {
            "type": ["string", "null"]
          },
          "ownerHandle": {
            "type": ["string", "null"]
          },
          "edgazeCode": {
            "type": ["string", "null"]
          },
          "runModel": {
            "$ref": "#/components/schemas/RunModel"
          },
          "displayedRunPriceUsd": {
            "description": "Estimated run price shown to the buyer. The final price is confirmed when the run starts and may differ slightly from older catalog data. USD amount as a JSON number with up to six decimal places. Preserve the documented precision and do not round intermediate calculations to two decimal places.",
            "type": ["number", "null"]
          },
          "pricingExplanation": {
            "description": "Human-readable note on how this listing is priced. Present whenever `displayedRunPriceUsd` is a positive estimate; explains that settlement uses the live quote at run start.",
            "type": ["string", "null"]
          },
          "userAccess": {
            "$ref": "#/components/schemas/UserAccess"
          },
          "accessLabel": {
            "type": "string"
          },
          "apiRunnableNow": {
            "type": "boolean"
          },
          "apiAccessAllowed": {
            "type": "boolean"
          },
          "purchaseUrl": {
            "anyOf": [
              {
                "type": "string",
                "format": "uri"
              },
              {
                "type": "null"
              }
            ],
            "description": "Web checkout page for this workflow, or `null` when it has no public listing. Purchasing is not available through the API; send a buyer here when `userAccess` is `purchase_required`."
          },
          "credentialMode": {
            "$ref": "#/components/schemas/CredentialMode"
          },
          "requiredProviders": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/RequiredProvider"
            }
          },
          "versionId": {
            "anyOf": [
              {
                "type": "string",
                "minLength": 1,
                "format": "uuid"
              },
              {
                "type": "null"
              }
            ]
          },
          "versionLabel": {
            "type": ["string", "null"]
          },
          "pinnedVersionId": {
            "anyOf": [
              {
                "type": "string",
                "minLength": 1,
                "format": "uuid"
              },
              {
                "type": "null"
              }
            ],
            "description": "Version the caller is pinned to via a prior purchase, or `null` when they follow the active version."
          },
          "activeVersionId": {
            "anyOf": [
              {
                "type": "string",
                "minLength": 1,
                "format": "uuid"
              },
              {
                "type": "null"
              }
            ],
            "description": "Current active version published by the workflow owner."
          },
          "updateAvailable": {
            "type": "boolean",
            "description": "True when the caller is pinned to an older version than the active one. Same workflow id can execute different immutable graphs."
          },
          "inputCount": {
            "type": "integer",
            "minimum": 0
          },
          "inputSchema": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/WorkflowInputField"
            }
          },
          "responseShape": {
            "$ref": "#/components/schemas/WorkflowRunResponseShape"
          },
          "exampleRequest": {
            "$ref": "#/components/schemas/WorkflowExampleRunRequest"
          }
        },
        "required": [
          "id",
          "name",
          "description",
          "ownerHandle",
          "edgazeCode",
          "runModel",
          "displayedRunPriceUsd",
          "pricingExplanation",
          "userAccess",
          "accessLabel",
          "apiRunnableNow",
          "apiAccessAllowed",
          "purchaseUrl",
          "credentialMode",
          "requiredProviders",
          "versionId",
          "versionLabel",
          "pinnedVersionId",
          "activeVersionId",
          "updateAvailable",
          "inputCount",
          "inputSchema",
          "responseShape",
          "exampleRequest"
        ],
        "description": "Full workflow detail with input schema and version visibility."
      },
      "CreateRunRequest": {
        "type": "object",
        "properties": {
          "workflow": {
            "description": "Workflow UUID to run.",
            "examples": ["550e8400-e29b-41d4-a716-446655440000"],
            "type": "string",
            "minLength": 1,
            "format": "uuid"
          },
          "workflowId": {
            "description": "Deprecated compatibility alias for `workflow`. Prefer `workflow`. When both are set, `workflow` wins.",
            "deprecated": true,
            "examples": ["550e8400-e29b-41d4-a716-446655440000"],
            "type": "string",
            "minLength": 1,
            "format": "uuid"
          },
          "inputs": {
            "description": "Map of exact input `nodeId` (preferred) or `name` to value, using the keys from the workflow's `inputSchema`. Unknown or ambiguous keys are rejected with 400 `invalid_inputs`. Omit entirely for a workflow that takes no inputs.",
            "type": "object",
            "propertyNames": {
              "type": "string"
            },
            "additionalProperties": {}
          },
          "idempotencyKey": {
            "description": "Currently optional for backward compatibility, but strongly recommended for every request. Account-scoped and case-sensitive (max 255 chars). Prefer the `Idempotency-Key` header; when both are set they must match. The fingerprint covers workflow, normalized inputs, and requested version.",
            "type": "string",
            "maxLength": 255
          },
          "version": {
            "description": "Optional immutable workflow version to run. When omitted, resolves to the caller's pinned version, or the workflow's active version when there is no pin.",
            "type": "string",
            "minLength": 1,
            "format": "uuid"
          }
        },
        "description": "Request body for POST /runs."
      },
      "CreateRunResponse": {
        "type": "object",
        "properties": {
          "run": {
            "$ref": "#/components/schemas/AcceptedRun"
          }
        },
        "required": ["run"],
        "description": "Response body for POST /runs (202 Accepted)."
      },
      "AcceptedRun": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "minLength": 1,
            "format": "uuid",
            "description": "Run UUID. Use it for every follow-up run call."
          },
          "status": {
            "type": "string",
            "const": "accepted",
            "description": "Always the constant `accepted`. This is an acknowledgement that the run was durably enqueued, not a lifecycle status: it is deliberately NOT a `PublicRunStatus` value, because no execution has happened yet. Read GET /runs/{id} for the real status."
          },
          "statusUrl": {
            "type": "string",
            "format": "uri",
            "description": "Absolute URL for GET /runs/{id}."
          },
          "eventsUrl": {
            "type": "string",
            "format": "uri",
            "description": "Absolute URL for GET /runs/{id}/events."
          },
          "streamUrl": {
            "type": "string",
            "format": "uri",
            "description": "Absolute URL for the SSE stream."
          }
        },
        "required": ["id", "status", "statusUrl", "eventsUrl", "streamUrl"],
        "description": "The `run` sub-object returned by POST /runs (202 Accepted)."
      },
      "RunOutputField": {
        "type": "object",
        "properties": {
          "nodeId": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "type": {
            "type": "string"
          },
          "value": {}
        },
        "required": ["nodeId", "name", "type", "value"],
        "additionalProperties": {},
        "description": "One output produced by the workflow."
      },
      "Run": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "minLength": 1,
            "format": "uuid"
          },
          "status": {
            "$ref": "#/components/schemas/PublicRunStatus"
          },
          "reason": {
            "description": "Buyer-safe failure explanation. Non-null only when `status` is `failed`; provider-specific detail is deliberately generalized.",
            "type": ["string", "null"]
          },
          "suspended": {
            "type": "boolean",
            "description": "True exactly when `status` is `suspended`; kept as a separate flag so a caller can test for 'waiting' without matching on the status enum. A run suspends for one of two reasons, reported as `reason` on the `run.suspended` event: `wait_timer` (a delay node) resumes itself when the timer fires, while `wait_signal` (the workflow needs credentials) waits for the buyer to supply them on the web run page. The API cannot resume a run; POST /runs/{id}/cancel is the only way for an API caller to end one early."
          },
          "workflowId": {
            "anyOf": [
              {
                "type": "string",
                "minLength": 1,
                "format": "uuid"
              },
              {
                "type": "null"
              }
            ]
          },
          "workflowVersionId": {
            "anyOf": [
              {
                "type": "string",
                "minLength": 1,
                "format": "uuid"
              },
              {
                "type": "null"
              }
            ]
          },
          "startedAt": {
            "anyOf": [
              {
                "type": "string",
                "format": "date-time"
              },
              {
                "type": "null"
              }
            ]
          },
          "completedAt": {
            "anyOf": [
              {
                "type": "string",
                "format": "date-time"
              },
              {
                "type": "null"
              }
            ]
          },
          "displayedChargeUsd": {
            "description": "Buyer-facing charge. `null` until the run settles. USD amount as a JSON number with up to six decimal places. Preserve the documented precision and do not round intermediate calculations to two decimal places.",
            "type": ["number", "null"]
          },
          "billingStatus": {
            "$ref": "#/components/schemas/BillingStatus"
          },
          "outcome": {
            "$ref": "#/components/schemas/RunOutcome"
          },
          "queue": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/RunQueue"
              },
              {
                "type": "null"
              }
            ],
            "description": "Non-null while the run is `pending` because it is waiting for capacity; null otherwise."
          },
          "outputs": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/RunOutputField"
            }
          }
        },
        "required": [
          "id",
          "status",
          "reason",
          "suspended",
          "workflowId",
          "workflowVersionId",
          "startedAt",
          "completedAt",
          "displayedChargeUsd",
          "billingStatus",
          "outcome",
          "queue",
          "outputs"
        ],
        "description": "Response body for GET /runs/{id}."
      },
      "RunOutcome": {
        "anyOf": [
          {
            "type": "string",
            "enum": ["completed", "completed_with_issues"]
          },
          {
            "type": "null"
          }
        ],
        "description": "Set when `status` is `completed`, null otherwise. `completed`: every step finished. `completed_with_issues`: the run returned its outputs but some steps failed; it is charged for the completed steps' compute only, without the creator's price."
      },
      "RunQueue": {
        "type": "object",
        "properties": {
          "state": {
            "type": "string",
            "enum": ["waiting", "starting"],
            "description": "`waiting`: in line for capacity. `starting`: it just left the line and is being started."
          },
          "position": {
            "type": "integer",
            "minimum": 0,
            "description": "Place in line, 1 = next. 0 while starting."
          },
          "etaSeconds": {
            "anyOf": [
              {
                "type": "integer",
                "minimum": 1
              },
              {
                "type": "null"
              }
            ],
            "description": "Estimated seconds until it starts, or null when unknown."
          }
        },
        "required": ["state", "position", "etaSeconds"],
        "description": "Edgaze starts runs immediately while it has capacity. At full capacity a run waits in line and starts automatically, in order; nothing is needed from the caller and nothing is charged for waiting. A run that waits past its limit fails with a reason and is not charged."
      },
      "RunListItem": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "minLength": 1,
            "format": "uuid"
          },
          "status": {
            "$ref": "#/components/schemas/RunListStatus"
          },
          "workflowId": {
            "anyOf": [
              {
                "type": "string",
                "minLength": 1,
                "format": "uuid"
              },
              {
                "type": "null"
              }
            ]
          },
          "workflowVersionId": {
            "anyOf": [
              {
                "type": "string",
                "minLength": 1,
                "format": "uuid"
              },
              {
                "type": "null"
              }
            ]
          },
          "startedAt": {
            "anyOf": [
              {
                "type": "string",
                "format": "date-time"
              },
              {
                "type": "null"
              }
            ]
          },
          "completedAt": {
            "anyOf": [
              {
                "type": "string",
                "format": "date-time"
              },
              {
                "type": "null"
              }
            ]
          },
          "displayedChargeUsd": {
            "type": ["number", "null"]
          },
          "billingStatus": {
            "$ref": "#/components/schemas/BillingStatus"
          },
          "outcome": {
            "$ref": "#/components/schemas/RunOutcome"
          }
        },
        "required": [
          "id",
          "status",
          "workflowId",
          "workflowVersionId",
          "startedAt",
          "completedAt",
          "displayedChargeUsd",
          "billingStatus",
          "outcome"
        ],
        "description": "Compact row in the run history. Outputs and failure reasons are omitted here — read GET /runs/{id} for a single run's full detail."
      },
      "RunListPagination": {
        "type": "object",
        "properties": {
          "offset": {
            "type": "integer",
            "minimum": 0,
            "description": "Offset of the first row in this page. Always 0 when the request used `cursor`."
          },
          "limit": {
            "type": "integer",
            "minimum": 1,
            "maximum": 50,
            "description": "Page size actually applied (max 50)."
          },
          "hasMore": {
            "type": "boolean",
            "description": "True when another page follows this one."
          },
          "nextCursor": {
            "description": "Pass as `cursor` to fetch the next page; `null` when `hasMore` is false. Opaque: do not parse or build it. Present on offset pages too, so you can switch to cursors at any point.",
            "type": ["string", "null"]
          }
        },
        "required": ["offset", "limit", "hasMore", "nextCursor"],
        "description": "Pagination for GET /runs. Prefer `nextCursor`: an offset shifts by one row for every run created while you page, so offset paging can repeat or skip runs; a cursor cannot."
      },
      "RunListResponse": {
        "type": "object",
        "properties": {
          "runs": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/RunListItem"
            }
          },
          "pagination": {
            "$ref": "#/components/schemas/RunListPagination"
          }
        },
        "required": ["runs", "pagination"],
        "description": "Response body for GET /runs."
      },
      "CancelRunResponse": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "minLength": 1,
            "format": "uuid"
          },
          "status": {
            "description": "Status after the cancel request. `cancelled` once the run is torn down; a terminal status is returned unchanged when the run had already finished.",
            "$ref": "#/components/schemas/PublicRunStatus"
          },
          "cancelRequested": {
            "type": "boolean",
            "description": "True when this call initiated the cancellation, false when the run was already in a terminal state and nothing changed."
          }
        },
        "required": ["id", "status", "cancelRequested"],
        "description": "Response body for POST /runs/{id}/cancel."
      },
      "RunEventActivity": {
        "type": "object",
        "properties": {
          "kind": {
            "type": "string",
            "description": "Kind of work. Known values: `writing`, `thinking`, `image`, `understanding`, `reading`, `watching`, `fetching`, `integration`, `specialist`, `calculating`. Treat unknown values as generic work."
          },
          "label": {
            "type": "string",
            "description": "Customer-facing description of the stage."
          },
          "model": {
            "description": "Display name of the model, when one runs.",
            "type": "string"
          },
          "icon": {
            "description": "Public image path of the model or app mark, e.g. `/misc/claude.png`.",
            "type": "string"
          },
          "aspect": {
            "description": "Image steps: the picture's shape as `W:H`, e.g. `16:9`.",
            "type": "string"
          },
          "pass": {
            "description": "Repeated stages: which pass this is, and how many when known.",
            "type": "object",
            "properties": {
              "current": {
                "type": "integer",
                "minimum": 1
              },
              "total": {
                "type": "integer",
                "minimum": 1
              }
            },
            "required": ["current"]
          }
        },
        "required": ["kind", "label"],
        "description": "Presented runs only: a customer-facing description of what a stage is doing."
      },
      "RunProgressHint": {
        "type": "object",
        "properties": {
          "floor": {
            "type": "number",
            "minimum": 0,
            "maximum": 100,
            "description": "Confirmed progress, in percent."
          },
          "ceiling": {
            "type": "number",
            "minimum": 0,
            "maximum": 100,
            "description": "Progress this band may approach, in percent, until the next hint arrives."
          },
          "etaMs": {
            "type": "number",
            "minimum": 0,
            "description": "Estimated milliseconds to reach `ceiling`."
          }
        },
        "required": ["floor", "ceiling", "etaMs"],
        "description": "Presented runs only: ease from `floor` toward `ceiling` over `etaMs` without reaching it; the next hint moves the band."
      },
      "RunEventData": {
        "type": "object",
        "properties": {
          "text": {
            "description": "Incremental output text. Present on `node.output.delta`; concatenate in `sequence` order to reconstruct a node's streamed output.",
            "type": "string"
          },
          "status": {
            "description": "Run status implied by this event. Present on run lifecycle events.",
            "$ref": "#/components/schemas/PublicRunStatus"
          },
          "reason": {
            "description": "Why the run failed, suspended, or a node was skipped. Sanitized to the same buyer-safe text as `Run.reason`.",
            "type": ["string", "null"]
          },
          "queue": {
            "description": "Present on `run.queue_position`: the run is waiting for capacity (`state: waiting`, its place in line and an estimated wait) or has just left the line (`state: starting`, position 0).",
            "$ref": "#/components/schemas/RunQueue"
          },
          "activity": {
            "$ref": "#/components/schemas/RunEventActivity"
          },
          "progress": {
            "$ref": "#/components/schemas/RunProgressHint"
          },
          "isOutput": {
            "description": "Presented runs only: `true` on a stage that produces a result.",
            "type": "boolean",
            "const": true
          },
          "outputLabel": {
            "description": "Presented runs only: the name of the result this stage produces.",
            "type": "string"
          },
          "output": {
            "description": "Presented runs only: the result's value when it is small enough to inline. GET /runs/{id} always carries the full outputs."
          }
        },
        "description": "Event-specific content, projected to the fields that are safe for the caller. `activity`, `progress`, `isOutput`, `outputLabel` and `output` appear only on presented runs (workflows the caller did not create)."
      },
      "RunEvent": {
        "type": "object",
        "properties": {
          "type": {
            "anyOf": [
              {
                "type": "string",
                "enum": [
                  "run.queued",
                  "run.queue_position",
                  "run.preparing",
                  "run.started",
                  "run.suspended",
                  "run.resumed",
                  "run.completed",
                  "run.failed",
                  "run.cancelled",
                  "node.started",
                  "node.output.delta",
                  "node.completed",
                  "node.failed",
                  "node.retry_scheduled",
                  "node.skipped"
                ]
              },
              {
                "type": "string"
              }
            ],
            "description": "Event name. The listed values are every type emitted today; branch on them. The union stays open to plain strings on purpose, because adding an event type is not a breaking change: ignore values you do not recognize instead of failing."
          },
          "sequence": {
            "type": "integer",
            "minimum": 0,
            "description": "Monotonic, gap-free position in this run's log. Use as the `afterSequence` cursor and to order events."
          },
          "ts": {
            "type": "string",
            "format": "date-time"
          },
          "nodeId": {
            "description": "Workflow node this event belongs to, or `null` for run-level events. On presented runs (workflows the caller did not create) this is an opaque stage id: stable within one run, unrelated across runs and to the workflow's input `nodeId`s.",
            "type": ["string", "null"]
          },
          "attempt": {
            "anyOf": [
              {
                "type": "integer",
                "minimum": 1
              },
              {
                "type": "null"
              }
            ],
            "description": "1-based execution attempt for node events; `null` for run-level events."
          },
          "data": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/RunEventData"
              },
              {
                "type": "null"
              }
            ],
            "description": "Event-specific content, or `null` when this event type carries none."
          }
        },
        "required": ["type", "sequence", "ts", "nodeId", "attempt", "data"],
        "description": "One row in the durable run event log."
      },
      "RunEventsPage": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "minLength": 1,
            "format": "uuid"
          },
          "events": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/RunEvent"
            }
          },
          "hasMore": {
            "type": "boolean"
          },
          "nextAfterSequence": {
            "anyOf": [
              {
                "type": "integer",
                "minimum": 0
              },
              {
                "type": "null"
              }
            ],
            "description": "Pass as `afterSequence` to fetch the next page. `null` when `hasMore` is false."
          }
        },
        "required": ["id", "events", "hasMore", "nextAfterSequence"],
        "description": "Response body for GET /runs/{id}/events."
      },
      "RunStreamEvent": {
        "type": "object",
        "properties": {
          "type": {
            "anyOf": [
              {
                "type": "string",
                "enum": [
                  "run.queued",
                  "run.queue_position",
                  "run.preparing",
                  "run.started",
                  "run.suspended",
                  "run.resumed",
                  "run.completed",
                  "run.failed",
                  "run.cancelled",
                  "node.started",
                  "node.output.delta",
                  "node.completed",
                  "node.failed",
                  "node.retry_scheduled",
                  "node.skipped"
                ]
              },
              {
                "type": "string"
              }
            ],
            "description": "Event name, the same values as `RunEvent.type`. Ignore values you do not recognize."
          },
          "runId": {
            "type": "string",
            "minLength": 1,
            "format": "uuid"
          },
          "sequence": {
            "type": "integer",
            "minimum": 0,
            "description": "Same as the frame's SSE `id:`. Resume with it as `Last-Event-ID`."
          },
          "occurredAt": {
            "type": "string",
            "format": "date-time",
            "description": "When the event happened. `RunEvent.ts` on GET /runs/{id}/events."
          },
          "nodeId": {
            "description": "Node (or, on presented runs, opaque per-run stage id) the event belongs to. Omitted on run-level events.",
            "type": "string"
          },
          "attempt": {
            "description": "1-based execution attempt on node events. Omitted on run-level events.",
            "type": "integer",
            "minimum": 1
          },
          "status": {
            "description": "Run status this event implies. Present exactly on run lifecycle events, which are sent with `event: run.status`.",
            "$ref": "#/components/schemas/PublicRunStatus"
          },
          "reason": {
            "description": "Buyer-safe failure text, on the event that fails the run.",
            "type": "string"
          },
          "data": {
            "description": "Event-specific content. Omitted when the event carries none.",
            "$ref": "#/components/schemas/RunEventData"
          }
        },
        "required": ["type", "runId", "sequence", "occurredAt"],
        "description": "One run event on the SSE stream. The frame's `event:` is `run.status` for run lifecycle events (`status` is set) and the event's `type` otherwise; its `id:` is `sequence`. Same events as GET /runs/{id}/events, with this envelope: `occurredAt` instead of `ts`, `runId` added, `status`/`reason` lifted to the top level, and absent fields omitted rather than null."
      },
      "RunStreamSnapshot": {
        "type": "object",
        "properties": {
          "run": {
            "type": "object",
            "properties": {
              "id": {
                "type": "string",
                "minLength": 1,
                "format": "uuid"
              },
              "status": {
                "$ref": "#/components/schemas/PublicRunStatus"
              },
              "reason": {
                "description": "Buyer-safe failure text when `status` is `failed`, otherwise null.",
                "type": ["string", "null"]
              },
              "lastEventSequence": {
                "type": "integer",
                "minimum": 0,
                "description": "Highest sequence in the run's log when the snapshot was taken."
              }
            },
            "required": ["id", "status", "reason", "lastEventSequence"]
          }
        },
        "required": ["run"],
        "description": "The run's folded state. Sent first on every connection and again as the last frame when the run ends. Carries no SSE `id:`, so it never moves your resume cursor. Read outputs from GET /runs/{id}."
      },
      "RunStreamPing": {
        "type": "object",
        "properties": {
          "t": {
            "type": "integer",
            "description": "Server time, in Unix milliseconds."
          }
        },
        "required": ["t"],
        "description": "Heartbeat sent every 15 seconds while idle. No SSE `id:`; ignore it."
      },
      "RunStreamReconnect": {
        "type": "object",
        "properties": {
          "lastSequence": {
            "type": "integer",
            "minimum": 0,
            "description": "Last sequence sent on this connection; also the frame's SSE `id:`."
          },
          "retryMs": {
            "type": "integer",
            "minimum": 0,
            "description": "Suggested wait before reconnecting; also sent as SSE `retry:`."
          }
        },
        "required": ["lastSequence", "retryMs"],
        "description": "Planned roll-over, about every 5 minutes. The connection then closes; reconnect with `Last-Event-ID: <lastSequence>` and nothing is lost or repeated. Not an error."
      },
      "RunStreamError": {
        "type": "object",
        "properties": {
          "error": {
            "type": "string",
            "description": "Human-readable explanation. Do not parse."
          }
        },
        "required": ["error"],
        "description": "The stream failed on the server. The connection then closes; reconnect with the last `id:` you processed."
      },
      "WorkflowVersionSummary": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "minLength": 1,
            "format": "uuid"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "releaseNotes": {
            "type": ["string", "null"]
          },
          "isActive": {
            "type": "boolean"
          },
          "isPurchased": {
            "type": "boolean"
          }
        },
        "required": ["id", "createdAt", "releaseNotes", "isActive", "isPurchased"],
        "description": "Compact metadata for one immutable workflow version."
      },
      "WorkflowVersionsResponse": {
        "type": "object",
        "properties": {
          "isOwner": {
            "type": "boolean"
          },
          "activeVersionId": {
            "anyOf": [
              {
                "type": "string",
                "minLength": 1,
                "format": "uuid"
              },
              {
                "type": "null"
              }
            ]
          },
          "pinnedVersionId": {
            "anyOf": [
              {
                "type": "string",
                "minLength": 1,
                "format": "uuid"
              },
              {
                "type": "null"
              }
            ]
          },
          "updateAvailable": {
            "type": "boolean"
          },
          "versions": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/WorkflowVersionSummary"
            }
          }
        },
        "required": [
          "isOwner",
          "activeVersionId",
          "pinnedVersionId",
          "updateAvailable",
          "versions"
        ],
        "description": "Response body for GET /workflows/{id}/versions. Owners see the full history; buyers see the versions their pin or entitlement covers."
      },
      "AcceptWorkflowUpdateResponse": {
        "type": "object",
        "properties": {
          "ok": {
            "type": "boolean",
            "const": true
          },
          "workflowId": {
            "type": "string",
            "minLength": 1,
            "format": "uuid"
          },
          "pinnedVersionId": {
            "type": "string",
            "minLength": 1,
            "format": "uuid"
          }
        },
        "required": ["ok", "workflowId", "pinnedVersionId"],
        "description": "Confirms the buyer's purchase pin was re-pinned to the workflow's current active version."
      },
      "WebhookRunEventPayload": {
        "type": "object",
        "properties": {
          "type": {
            "type": "string",
            "enum": ["run.completed", "run.failed", "run.cancelled"],
            "description": "Event type. Matches the `edgaze-event` header."
          },
          "runId": {
            "type": "string",
            "minLength": 1,
            "format": "uuid",
            "description": "The run that reached a terminal state."
          },
          "status": {
            "type": "string",
            "enum": ["completed", "failed", "cancelled"],
            "description": "Terminal run status, as GET /runs/{id} reports it."
          },
          "outcome": {
            "$ref": "#/components/schemas/RunOutcome"
          },
          "reason": {
            "description": "Buyer-safe failure explanation, the same text as `Run.reason`. Non-null only when `status` is `failed`.",
            "type": ["string", "null"]
          },
          "runUrl": {
            "type": "string",
            "format": "uri",
            "description": "Absolute URL for GET /runs/{id}. The body carries no outputs or charge: fetch this URL with an API key that has `run:read` to read them."
          },
          "occurredAt": {
            "type": "string",
            "format": "date-time",
            "description": "When the run reached its terminal state."
          }
        },
        "required": ["type", "runId", "status", "outcome", "reason", "runUrl", "occurredAt"],
        "description": "Body posted to a webhook endpoint when a run reaches a terminal state. To deduplicate, use the `edgaze-delivery-id` header, or `runId` plus `type` when that header is absent."
      },
      "WebhookTestPayload": {
        "type": "object",
        "properties": {
          "type": {
            "type": "string",
            "const": "webhook.test"
          },
          "message": {
            "type": "string",
            "examples": ["This is a test delivery from Edgaze."]
          },
          "occurredAt": {
            "type": "string",
            "format": "date-time"
          }
        },
        "required": ["type", "message", "occurredAt"],
        "description": "Body posted when a developer clicks Send test in the developer console. It carries no run."
      },
      "UnknownError": {
        "type": "object",
        "description": "Inner error body for an error code this version of the document does not list yet. Handle it like any other error with the same HTTP status.",
        "properties": {
          "code": {
            "type": "string",
            "not": {
              "enum": [
                "unauthorized",
                "insufficient_scope",
                "forbidden",
                "not_found",
                "invalid_request",
                "invalid_json",
                "invalid_inputs",
                "missing_workflow",
                "invalid_cursor",
                "method_not_allowed",
                "spend_cap_reached",
                "idempotency_conflict",
                "rate_limited",
                "account_restricted",
                "integration_connection_required",
                "creator_listings_paused",
                "output_too_large",
                "cancel_unavailable",
                "internal_error"
              ]
            },
            "description": "A code added after this version of the document."
          },
          "message": {
            "type": "string",
            "description": "Human-readable failure reason. Do not parse; branch on `code` instead."
          }
        },
        "required": ["code", "message"]
      },
      "ListWorkflowsQuery": {
        "type": "object",
        "properties": {
          "scope": {
            "description": "Marketplace catalog or the key holder's current workflows. Defaults to `marketplace`.",
            "type": "string",
            "enum": ["marketplace", "mine"]
          },
          "q": {
            "description": "Search title, description, tags, owner handle, or Edgaze Code.",
            "type": "string"
          },
          "offset": {
            "description": "Pagination offset; used with `marketplace` scope.",
            "type": "integer",
            "minimum": 0
          },
          "limit": {
            "description": "Page size (max 50). Defaults to 24.",
            "type": "integer",
            "minimum": 1,
            "maximum": 50
          },
          "sort": {
            "description": "Marketplace sort order. Defaults to `popular`.",
            "type": "string",
            "enum": ["popular", "newest"]
          },
          "runModel": {
            "description": "Filter by pricing model.",
            "$ref": "#/components/schemas/RunModel"
          },
          "runnableOnly": {
            "description": "When `true`, only workflows the key holder can run right now.",
            "type": "string",
            "enum": ["true", "false"]
          }
        },
        "description": "Query parameters accepted by GET /workflows."
      },
      "ListRunsQuery": {
        "type": "object",
        "properties": {
          "status": {
            "description": "Return only runs currently in this status.",
            "$ref": "#/components/schemas/RunListStatus"
          },
          "workflow": {
            "description": "Return only runs of this workflow.",
            "type": "string",
            "minLength": 1,
            "format": "uuid"
          },
          "cursor": {
            "description": "`pagination.nextCursor` from the previous page. Recommended over `offset`, which can repeat or skip runs created while you page. Cannot be combined with `offset`.",
            "type": "string"
          },
          "offset": {
            "description": "Pagination offset. Defaults to 0. Prefer `cursor`; cannot be combined with it.",
            "type": "integer",
            "minimum": 0
          },
          "limit": {
            "description": "Page size (max 50). Defaults to 24.",
            "type": "integer",
            "minimum": 1,
            "maximum": 50
          }
        },
        "description": "Query parameters accepted by GET /runs."
      },
      "WorkflowIdParam": {
        "type": "string",
        "minLength": 1,
        "description": "UUID of the workflow.",
        "examples": ["550e8400-e29b-41d4-a716-446655440000"]
      },
      "RunIdParam": {
        "type": "string",
        "minLength": 1,
        "description": "UUID returned from POST /runs.",
        "examples": ["7c9e6679-7425-40de-944b-e07fc1f90ae7"]
      }
    }
  },
  "paths": {
    "/workflows": {
      "get": {
        "operationId": "listWorkflows",
        "summary": "List workflows",
        "description": "Discover current free and pay-per-run workflows in the paginated marketplace catalog. `scope=mine` returns the caller's current workflows.",
        "tags": ["Workflows"],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "x-required-scopes": ["run:read", "run:execute"],
        "parameters": [
          {
            "name": "scope",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "q",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "sort",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "runModel",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "runnableOnly",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated workflow catalog.",
            "headers": {
              "X-Request-Id": {
                "description": "Identifies this request in Edgaze's logs. Present on every response, success or error. Quote it when you report a problem. Opaque: do not parse it.",
                "schema": {
                  "type": "string"
                }
              },
              "Cache-Control": {
                "description": "Always `private, no-store` on authenticated endpoints to prevent shared caching.",
                "schema": {
                  "type": "string"
                }
              },
              "Vary": {
                "description": "Always includes `Authorization` on authenticated endpoints.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WorkflowListResponse"
                }
              }
            }
          },
          "400": {
            "description": "Invalid query parameters.",
            "headers": {
              "X-Request-Id": {
                "description": "Identifies this request in Edgaze's logs. Present on every response, success or error. Quote it when you report a problem. Opaque: do not parse it.",
                "schema": {
                  "type": "string"
                }
              },
              "Cache-Control": {
                "description": "Always `private, no-store` on authenticated endpoints to prevent shared caching.",
                "schema": {
                  "type": "string"
                }
              },
              "Vary": {
                "description": "Always includes `Authorization` on authenticated endpoints.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "examples": {
                  "default": {
                    "value": {
                      "error": {
                        "code": "invalid_request",
                        "message": "Invalid query parameters."
                      }
                    }
                  }
                }
              }
            },
            "x-error-codes": ["invalid_request"]
          },
          "401": {
            "description": "Invalid or missing API key.",
            "headers": {
              "X-Request-Id": {
                "description": "Identifies this request in Edgaze's logs. Present on every response, success or error. Quote it when you report a problem. Opaque: do not parse it.",
                "schema": {
                  "type": "string"
                }
              },
              "Cache-Control": {
                "description": "Always `private, no-store` on authenticated endpoints to prevent shared caching.",
                "schema": {
                  "type": "string"
                }
              },
              "Vary": {
                "description": "Always includes `Authorization` on authenticated endpoints.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "examples": {
                  "default": {
                    "value": {
                      "error": {
                        "code": "unauthorized",
                        "message": "Invalid or missing API key."
                      }
                    }
                  }
                }
              }
            },
            "x-error-codes": ["unauthorized"]
          },
          "403": {
            "description": "API key is missing the required scope. `insufficient_scope`: the API key lacks every scope this operation accepts (`x-required-scopes`). `account_restricted`: the account that owns the API key is restricted. Not retryable.",
            "headers": {
              "X-Request-Id": {
                "description": "Identifies this request in Edgaze's logs. Present on every response, success or error. Quote it when you report a problem. Opaque: do not parse it.",
                "schema": {
                  "type": "string"
                }
              },
              "Cache-Control": {
                "description": "Always `private, no-store` on authenticated endpoints to prevent shared caching.",
                "schema": {
                  "type": "string"
                }
              },
              "Vary": {
                "description": "Always includes `Authorization` on authenticated endpoints.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "examples": {
                  "default": {
                    "value": {
                      "error": {
                        "code": "insufficient_scope",
                        "message": "API key is missing the required scope."
                      }
                    }
                  }
                }
              }
            },
            "x-error-codes": ["insufficient_scope", "account_restricted"]
          },
          "429": {
            "description": "`rate_limited`: too many requests. Wait `Retry-After` seconds, then retry. Only POST /runs is limited today; every operation lists this so clients already handle it if that changes.",
            "headers": {
              "X-Request-Id": {
                "description": "Identifies this request in Edgaze's logs. Present on every response, success or error. Quote it when you report a problem. Opaque: do not parse it.",
                "schema": {
                  "type": "string"
                }
              },
              "Cache-Control": {
                "description": "Always `private, no-store` on authenticated endpoints to prevent shared caching.",
                "schema": {
                  "type": "string"
                }
              },
              "Vary": {
                "description": "Always includes `Authorization` on authenticated endpoints.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "examples": {
                  "default": {
                    "value": {
                      "error": {
                        "code": "rate_limited",
                        "message": "too many requests. Wait `Retry-After` seconds, then retry. Only POST /runs is limited today; every operation lists this so clients already handle it if that changes."
                      }
                    }
                  }
                }
              }
            },
            "x-error-codes": ["rate_limited"]
          },
          "500": {
            "description": "Unexpected server error.",
            "headers": {
              "X-Request-Id": {
                "description": "Identifies this request in Edgaze's logs. Present on every response, success or error. Quote it when you report a problem. Opaque: do not parse it.",
                "schema": {
                  "type": "string"
                }
              },
              "Cache-Control": {
                "description": "Always `private, no-store` on authenticated endpoints to prevent shared caching.",
                "schema": {
                  "type": "string"
                }
              },
              "Vary": {
                "description": "Always includes `Authorization` on authenticated endpoints.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "examples": {
                  "default": {
                    "value": {
                      "error": {
                        "code": "internal_error",
                        "message": "Unexpected server error."
                      }
                    }
                  }
                }
              }
            },
            "x-error-codes": ["internal_error"]
          }
        },
        "x-scope-match": "any"
      }
    },
    "/workflows/{id}": {
      "get": {
        "operationId": "getWorkflow",
        "summary": "Get workflow",
        "description": "Retrieve a single workflow with its input schema, access state, pricing, version visibility, and an example run request.",
        "tags": ["Workflows"],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "x-required-scopes": ["run:read", "run:execute"],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "UUID of the workflow.",
            "schema": {
              "$ref": "#/components/schemas/WorkflowIdParam"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Workflow detail.",
            "headers": {
              "X-Request-Id": {
                "description": "Identifies this request in Edgaze's logs. Present on every response, success or error. Quote it when you report a problem. Opaque: do not parse it.",
                "schema": {
                  "type": "string"
                }
              },
              "Cache-Control": {
                "description": "Always `private, no-store` on authenticated endpoints to prevent shared caching.",
                "schema": {
                  "type": "string"
                }
              },
              "Vary": {
                "description": "Always includes `Authorization` on authenticated endpoints.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WorkflowDetail"
                }
              }
            }
          },
          "400": {
            "description": "Missing or malformed workflow id.",
            "headers": {
              "X-Request-Id": {
                "description": "Identifies this request in Edgaze's logs. Present on every response, success or error. Quote it when you report a problem. Opaque: do not parse it.",
                "schema": {
                  "type": "string"
                }
              },
              "Cache-Control": {
                "description": "Always `private, no-store` on authenticated endpoints to prevent shared caching.",
                "schema": {
                  "type": "string"
                }
              },
              "Vary": {
                "description": "Always includes `Authorization` on authenticated endpoints.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "examples": {
                  "default": {
                    "value": {
                      "error": {
                        "code": "missing_workflow",
                        "message": "Missing or malformed workflow id."
                      }
                    }
                  }
                }
              }
            },
            "x-error-codes": ["missing_workflow"]
          },
          "401": {
            "description": "Invalid or missing API key.",
            "headers": {
              "X-Request-Id": {
                "description": "Identifies this request in Edgaze's logs. Present on every response, success or error. Quote it when you report a problem. Opaque: do not parse it.",
                "schema": {
                  "type": "string"
                }
              },
              "Cache-Control": {
                "description": "Always `private, no-store` on authenticated endpoints to prevent shared caching.",
                "schema": {
                  "type": "string"
                }
              },
              "Vary": {
                "description": "Always includes `Authorization` on authenticated endpoints.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "examples": {
                  "default": {
                    "value": {
                      "error": {
                        "code": "unauthorized",
                        "message": "Invalid or missing API key."
                      }
                    }
                  }
                }
              }
            },
            "x-error-codes": ["unauthorized"]
          },
          "403": {
            "description": "Not entitled to this workflow. `forbidden`: the caller has no access to this workflow, version, or purchase (non-leaky). `insufficient_scope`: the API key lacks every scope this operation accepts (`x-required-scopes`). `account_restricted`: the account that owns the API key is restricted. Not retryable.",
            "headers": {
              "X-Request-Id": {
                "description": "Identifies this request in Edgaze's logs. Present on every response, success or error. Quote it when you report a problem. Opaque: do not parse it.",
                "schema": {
                  "type": "string"
                }
              },
              "Cache-Control": {
                "description": "Always `private, no-store` on authenticated endpoints to prevent shared caching.",
                "schema": {
                  "type": "string"
                }
              },
              "Vary": {
                "description": "Always includes `Authorization` on authenticated endpoints.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "examples": {
                  "default": {
                    "value": {
                      "error": {
                        "code": "forbidden",
                        "message": "Not entitled to this workflow."
                      }
                    }
                  }
                }
              }
            },
            "x-error-codes": ["forbidden", "insufficient_scope", "account_restricted"]
          },
          "404": {
            "description": "Workflow not found.",
            "headers": {
              "X-Request-Id": {
                "description": "Identifies this request in Edgaze's logs. Present on every response, success or error. Quote it when you report a problem. Opaque: do not parse it.",
                "schema": {
                  "type": "string"
                }
              },
              "Cache-Control": {
                "description": "Always `private, no-store` on authenticated endpoints to prevent shared caching.",
                "schema": {
                  "type": "string"
                }
              },
              "Vary": {
                "description": "Always includes `Authorization` on authenticated endpoints.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "examples": {
                  "default": {
                    "value": {
                      "error": {
                        "code": "not_found",
                        "message": "Workflow not found."
                      }
                    }
                  }
                }
              }
            },
            "x-error-codes": ["not_found"]
          },
          "429": {
            "description": "`rate_limited`: too many requests. Wait `Retry-After` seconds, then retry. Only POST /runs is limited today; every operation lists this so clients already handle it if that changes.",
            "headers": {
              "X-Request-Id": {
                "description": "Identifies this request in Edgaze's logs. Present on every response, success or error. Quote it when you report a problem. Opaque: do not parse it.",
                "schema": {
                  "type": "string"
                }
              },
              "Cache-Control": {
                "description": "Always `private, no-store` on authenticated endpoints to prevent shared caching.",
                "schema": {
                  "type": "string"
                }
              },
              "Vary": {
                "description": "Always includes `Authorization` on authenticated endpoints.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "examples": {
                  "default": {
                    "value": {
                      "error": {
                        "code": "rate_limited",
                        "message": "too many requests. Wait `Retry-After` seconds, then retry. Only POST /runs is limited today; every operation lists this so clients already handle it if that changes."
                      }
                    }
                  }
                }
              }
            },
            "x-error-codes": ["rate_limited"]
          },
          "500": {
            "description": "Unexpected server error.",
            "headers": {
              "X-Request-Id": {
                "description": "Identifies this request in Edgaze's logs. Present on every response, success or error. Quote it when you report a problem. Opaque: do not parse it.",
                "schema": {
                  "type": "string"
                }
              },
              "Cache-Control": {
                "description": "Always `private, no-store` on authenticated endpoints to prevent shared caching.",
                "schema": {
                  "type": "string"
                }
              },
              "Vary": {
                "description": "Always includes `Authorization` on authenticated endpoints.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "examples": {
                  "default": {
                    "value": {
                      "error": {
                        "code": "internal_error",
                        "message": "Unexpected server error."
                      }
                    }
                  }
                }
              }
            },
            "x-error-codes": ["internal_error"]
          }
        },
        "x-scope-match": "any"
      }
    },
    "/workflows/{id}/versions": {
      "get": {
        "operationId": "listWorkflowVersions",
        "summary": "List workflow versions",
        "description": "Return the versions the caller is entitled to see for a workflow. Owners see the full history; buyers see the version their pin covers plus the current active version. Also returns the caller's `pinnedVersionId` / `activeVersionId` and an `updateAvailable` flag so integrators can decide whether to POST `/workflows/{id}/accept-update`.",
        "tags": ["Workflows"],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "x-required-scopes": ["run:read", "run:execute"],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "UUID of the workflow.",
            "schema": {
              "$ref": "#/components/schemas/WorkflowIdParam"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Entitled workflow versions.",
            "headers": {
              "X-Request-Id": {
                "description": "Identifies this request in Edgaze's logs. Present on every response, success or error. Quote it when you report a problem. Opaque: do not parse it.",
                "schema": {
                  "type": "string"
                }
              },
              "Cache-Control": {
                "description": "Always `private, no-store` on authenticated endpoints to prevent shared caching.",
                "schema": {
                  "type": "string"
                }
              },
              "Vary": {
                "description": "Always includes `Authorization` on authenticated endpoints.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WorkflowVersionsResponse"
                }
              }
            }
          },
          "400": {
            "description": "Missing or malformed workflow id.",
            "headers": {
              "X-Request-Id": {
                "description": "Identifies this request in Edgaze's logs. Present on every response, success or error. Quote it when you report a problem. Opaque: do not parse it.",
                "schema": {
                  "type": "string"
                }
              },
              "Cache-Control": {
                "description": "Always `private, no-store` on authenticated endpoints to prevent shared caching.",
                "schema": {
                  "type": "string"
                }
              },
              "Vary": {
                "description": "Always includes `Authorization` on authenticated endpoints.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "examples": {
                  "default": {
                    "value": {
                      "error": {
                        "code": "missing_workflow",
                        "message": "Missing or malformed workflow id."
                      }
                    }
                  }
                }
              }
            },
            "x-error-codes": ["missing_workflow"]
          },
          "401": {
            "description": "Invalid or missing API key.",
            "headers": {
              "X-Request-Id": {
                "description": "Identifies this request in Edgaze's logs. Present on every response, success or error. Quote it when you report a problem. Opaque: do not parse it.",
                "schema": {
                  "type": "string"
                }
              },
              "Cache-Control": {
                "description": "Always `private, no-store` on authenticated endpoints to prevent shared caching.",
                "schema": {
                  "type": "string"
                }
              },
              "Vary": {
                "description": "Always includes `Authorization` on authenticated endpoints.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "examples": {
                  "default": {
                    "value": {
                      "error": {
                        "code": "unauthorized",
                        "message": "Invalid or missing API key."
                      }
                    }
                  }
                }
              }
            },
            "x-error-codes": ["unauthorized"]
          },
          "403": {
            "description": "Not entitled to this workflow. `forbidden`: the caller has no access to this workflow, version, or purchase (non-leaky). `insufficient_scope`: the API key lacks every scope this operation accepts (`x-required-scopes`). `account_restricted`: the account that owns the API key is restricted. Not retryable.",
            "headers": {
              "X-Request-Id": {
                "description": "Identifies this request in Edgaze's logs. Present on every response, success or error. Quote it when you report a problem. Opaque: do not parse it.",
                "schema": {
                  "type": "string"
                }
              },
              "Cache-Control": {
                "description": "Always `private, no-store` on authenticated endpoints to prevent shared caching.",
                "schema": {
                  "type": "string"
                }
              },
              "Vary": {
                "description": "Always includes `Authorization` on authenticated endpoints.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "examples": {
                  "default": {
                    "value": {
                      "error": {
                        "code": "forbidden",
                        "message": "Not entitled to this workflow."
                      }
                    }
                  }
                }
              }
            },
            "x-error-codes": ["forbidden", "insufficient_scope", "account_restricted"]
          },
          "404": {
            "description": "Workflow not found.",
            "headers": {
              "X-Request-Id": {
                "description": "Identifies this request in Edgaze's logs. Present on every response, success or error. Quote it when you report a problem. Opaque: do not parse it.",
                "schema": {
                  "type": "string"
                }
              },
              "Cache-Control": {
                "description": "Always `private, no-store` on authenticated endpoints to prevent shared caching.",
                "schema": {
                  "type": "string"
                }
              },
              "Vary": {
                "description": "Always includes `Authorization` on authenticated endpoints.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "examples": {
                  "default": {
                    "value": {
                      "error": {
                        "code": "not_found",
                        "message": "Workflow not found."
                      }
                    }
                  }
                }
              }
            },
            "x-error-codes": ["not_found"]
          },
          "429": {
            "description": "`rate_limited`: too many requests. Wait `Retry-After` seconds, then retry. Only POST /runs is limited today; every operation lists this so clients already handle it if that changes.",
            "headers": {
              "X-Request-Id": {
                "description": "Identifies this request in Edgaze's logs. Present on every response, success or error. Quote it when you report a problem. Opaque: do not parse it.",
                "schema": {
                  "type": "string"
                }
              },
              "Cache-Control": {
                "description": "Always `private, no-store` on authenticated endpoints to prevent shared caching.",
                "schema": {
                  "type": "string"
                }
              },
              "Vary": {
                "description": "Always includes `Authorization` on authenticated endpoints.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "examples": {
                  "default": {
                    "value": {
                      "error": {
                        "code": "rate_limited",
                        "message": "too many requests. Wait `Retry-After` seconds, then retry. Only POST /runs is limited today; every operation lists this so clients already handle it if that changes."
                      }
                    }
                  }
                }
              }
            },
            "x-error-codes": ["rate_limited"]
          },
          "500": {
            "description": "Unexpected server error.",
            "headers": {
              "X-Request-Id": {
                "description": "Identifies this request in Edgaze's logs. Present on every response, success or error. Quote it when you report a problem. Opaque: do not parse it.",
                "schema": {
                  "type": "string"
                }
              },
              "Cache-Control": {
                "description": "Always `private, no-store` on authenticated endpoints to prevent shared caching.",
                "schema": {
                  "type": "string"
                }
              },
              "Vary": {
                "description": "Always includes `Authorization` on authenticated endpoints.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "examples": {
                  "default": {
                    "value": {
                      "error": {
                        "code": "internal_error",
                        "message": "Unexpected server error."
                      }
                    }
                  }
                }
              }
            },
            "x-error-codes": ["internal_error"]
          }
        },
        "x-scope-match": "any"
      }
    },
    "/workflows/{id}/accept-update": {
      "post": {
        "operationId": "acceptWorkflowUpdate",
        "summary": "Accept workflow update",
        "description": "Re-pin the caller's purchase to the workflow's current active version. Requires the `workflow:write` scope (kept out of the default key scopes on purpose). Idempotent: accepting when already on the active version returns 200 with the current pin unchanged. Owner keys do not have a purchase to re-pin and receive 403.",
        "tags": ["Workflows"],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "x-required-scopes": ["workflow:write"],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "UUID of the workflow.",
            "schema": {
              "$ref": "#/components/schemas/WorkflowIdParam"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Purchase pin updated.",
            "headers": {
              "X-Request-Id": {
                "description": "Identifies this request in Edgaze's logs. Present on every response, success or error. Quote it when you report a problem. Opaque: do not parse it.",
                "schema": {
                  "type": "string"
                }
              },
              "Cache-Control": {
                "description": "Always `private, no-store` on authenticated endpoints to prevent shared caching.",
                "schema": {
                  "type": "string"
                }
              },
              "Vary": {
                "description": "Always includes `Authorization` on authenticated endpoints.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AcceptWorkflowUpdateResponse"
                }
              }
            }
          },
          "400": {
            "description": "Missing or malformed workflow id.",
            "headers": {
              "X-Request-Id": {
                "description": "Identifies this request in Edgaze's logs. Present on every response, success or error. Quote it when you report a problem. Opaque: do not parse it.",
                "schema": {
                  "type": "string"
                }
              },
              "Cache-Control": {
                "description": "Always `private, no-store` on authenticated endpoints to prevent shared caching.",
                "schema": {
                  "type": "string"
                }
              },
              "Vary": {
                "description": "Always includes `Authorization` on authenticated endpoints.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "examples": {
                  "default": {
                    "value": {
                      "error": {
                        "code": "missing_workflow",
                        "message": "Missing or malformed workflow id."
                      }
                    }
                  }
                }
              }
            },
            "x-error-codes": ["missing_workflow"]
          },
          "401": {
            "description": "Invalid or missing API key.",
            "headers": {
              "X-Request-Id": {
                "description": "Identifies this request in Edgaze's logs. Present on every response, success or error. Quote it when you report a problem. Opaque: do not parse it.",
                "schema": {
                  "type": "string"
                }
              },
              "Cache-Control": {
                "description": "Always `private, no-store` on authenticated endpoints to prevent shared caching.",
                "schema": {
                  "type": "string"
                }
              },
              "Vary": {
                "description": "Always includes `Authorization` on authenticated endpoints.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "examples": {
                  "default": {
                    "value": {
                      "error": {
                        "code": "unauthorized",
                        "message": "Invalid or missing API key."
                      }
                    }
                  }
                }
              }
            },
            "x-error-codes": ["unauthorized"]
          },
          "403": {
            "description": "Rejected for one of two reasons that need different client handling. `insufficient_scope`: the key lacks `workflow:write` — mint a key with that scope. `forbidden`: the key is valid and scoped, but the caller has no purchase to re-pin (including owner keys, which have no purchase) — there is nothing to retry. Branch on `error.code`. `account_restricted`: the account that owns the API key is restricted. Not retryable.",
            "headers": {
              "X-Request-Id": {
                "description": "Identifies this request in Edgaze's logs. Present on every response, success or error. Quote it when you report a problem. Opaque: do not parse it.",
                "schema": {
                  "type": "string"
                }
              },
              "Cache-Control": {
                "description": "Always `private, no-store` on authenticated endpoints to prevent shared caching.",
                "schema": {
                  "type": "string"
                }
              },
              "Vary": {
                "description": "Always includes `Authorization` on authenticated endpoints.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "examples": {
                  "missing_scope": {
                    "value": {
                      "error": {
                        "code": "insufficient_scope",
                        "message": "This API key is missing the required scope: workflow:write."
                      }
                    }
                  },
                  "no_purchase": {
                    "value": {
                      "error": {
                        "code": "forbidden",
                        "message": "You do not have access to this workflow."
                      }
                    }
                  }
                }
              }
            },
            "x-error-codes": ["forbidden", "insufficient_scope", "account_restricted"]
          },
          "409": {
            "description": "Workflow has no active version to accept.",
            "headers": {
              "X-Request-Id": {
                "description": "Identifies this request in Edgaze's logs. Present on every response, success or error. Quote it when you report a problem. Opaque: do not parse it.",
                "schema": {
                  "type": "string"
                }
              },
              "Cache-Control": {
                "description": "Always `private, no-store` on authenticated endpoints to prevent shared caching.",
                "schema": {
                  "type": "string"
                }
              },
              "Vary": {
                "description": "Always includes `Authorization` on authenticated endpoints.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "examples": {
                  "default": {
                    "value": {
                      "error": {
                        "code": "invalid_request",
                        "message": "Workflow has no active version to accept."
                      }
                    }
                  }
                }
              }
            },
            "x-error-codes": ["invalid_request"]
          },
          "429": {
            "description": "`rate_limited`: too many requests. Wait `Retry-After` seconds, then retry. Only POST /runs is limited today; every operation lists this so clients already handle it if that changes.",
            "headers": {
              "X-Request-Id": {
                "description": "Identifies this request in Edgaze's logs. Present on every response, success or error. Quote it when you report a problem. Opaque: do not parse it.",
                "schema": {
                  "type": "string"
                }
              },
              "Cache-Control": {
                "description": "Always `private, no-store` on authenticated endpoints to prevent shared caching.",
                "schema": {
                  "type": "string"
                }
              },
              "Vary": {
                "description": "Always includes `Authorization` on authenticated endpoints.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "examples": {
                  "default": {
                    "value": {
                      "error": {
                        "code": "rate_limited",
                        "message": "too many requests. Wait `Retry-After` seconds, then retry. Only POST /runs is limited today; every operation lists this so clients already handle it if that changes."
                      }
                    }
                  }
                }
              }
            },
            "x-error-codes": ["rate_limited"]
          },
          "500": {
            "description": "Unexpected server error.",
            "headers": {
              "X-Request-Id": {
                "description": "Identifies this request in Edgaze's logs. Present on every response, success or error. Quote it when you report a problem. Opaque: do not parse it.",
                "schema": {
                  "type": "string"
                }
              },
              "Cache-Control": {
                "description": "Always `private, no-store` on authenticated endpoints to prevent shared caching.",
                "schema": {
                  "type": "string"
                }
              },
              "Vary": {
                "description": "Always includes `Authorization` on authenticated endpoints.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "examples": {
                  "default": {
                    "value": {
                      "error": {
                        "code": "internal_error",
                        "message": "Unexpected server error."
                      }
                    }
                  }
                }
              }
            },
            "x-error-codes": ["internal_error"]
          }
        },
        "x-scope-match": "any"
      }
    },
    "/runs": {
      "get": {
        "operationId": "listRuns",
        "summary": "List runs",
        "description": "Return the caller's own runs, newest first. Projected from the indexed run row rather than folded from the event log, so it omits `outputs` and `reason` and reports a suspended run as `running` — read GET /runs/{id} for a single run in full. Page with `cursor`: pass `pagination.nextCursor` until it is null. An `offset` shifts by one row for every run created while you page, so offset paging can repeat or skip runs; a cursor cannot. Rate limiting: reads are not metered per-key today; only POST /runs is (up to 60 per minute is never limited). Reads may become metered without a breaking change, so treat 429 with `Retry-After` as possible on any endpoint and back off rather than polling in a tight loop. For run progress prefer GET /runs/{id}/stream, or poll GET /runs/{id} no more than once per second.",
        "tags": ["Runs"],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "x-required-scopes": ["run:read"],
        "parameters": [
          {
            "name": "status",
            "in": "query",
            "required": false,
            "description": "Return only runs currently in this status.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "workflow",
            "in": "query",
            "required": false,
            "description": "Return only runs of this workflow id.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "description": "`pagination.nextCursor` from the previous page. Opaque. Cannot be combined with `offset`.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "description": "Pagination offset. Defaults to 0. Prefer `cursor`.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Page size from 1 to 50. Defaults to 24.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated run history.",
            "headers": {
              "X-Request-Id": {
                "description": "Identifies this request in Edgaze's logs. Present on every response, success or error. Quote it when you report a problem. Opaque: do not parse it.",
                "schema": {
                  "type": "string"
                }
              },
              "Cache-Control": {
                "description": "Always `private, no-store` on authenticated endpoints to prevent shared caching.",
                "schema": {
                  "type": "string"
                }
              },
              "Vary": {
                "description": "Always includes `Authorization` on authenticated endpoints.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RunListResponse"
                }
              }
            }
          },
          "400": {
            "description": "Invalid query parameters (`invalid_request`, including `cursor` combined with `offset`), or a `cursor` this endpoint did not issue (`invalid_cursor`).",
            "headers": {
              "X-Request-Id": {
                "description": "Identifies this request in Edgaze's logs. Present on every response, success or error. Quote it when you report a problem. Opaque: do not parse it.",
                "schema": {
                  "type": "string"
                }
              },
              "Cache-Control": {
                "description": "Always `private, no-store` on authenticated endpoints to prevent shared caching.",
                "schema": {
                  "type": "string"
                }
              },
              "Vary": {
                "description": "Always includes `Authorization` on authenticated endpoints.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "examples": {
                  "default": {
                    "value": {
                      "error": {
                        "code": "invalid_request",
                        "message": "Invalid query parameters (`invalid_request`, including `cursor` combined with `offset`), or a `cursor` this endpoint did not issue (`invalid_cursor`)."
                      }
                    }
                  }
                }
              }
            },
            "x-error-codes": ["invalid_request", "invalid_cursor"]
          },
          "401": {
            "description": "Invalid or missing API key.",
            "headers": {
              "X-Request-Id": {
                "description": "Identifies this request in Edgaze's logs. Present on every response, success or error. Quote it when you report a problem. Opaque: do not parse it.",
                "schema": {
                  "type": "string"
                }
              },
              "Cache-Control": {
                "description": "Always `private, no-store` on authenticated endpoints to prevent shared caching.",
                "schema": {
                  "type": "string"
                }
              },
              "Vary": {
                "description": "Always includes `Authorization` on authenticated endpoints.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "examples": {
                  "default": {
                    "value": {
                      "error": {
                        "code": "unauthorized",
                        "message": "Invalid or missing API key."
                      }
                    }
                  }
                }
              }
            },
            "x-error-codes": ["unauthorized"]
          },
          "403": {
            "description": "API key is missing the required scope. `insufficient_scope`: the API key lacks every scope this operation accepts (`x-required-scopes`). `account_restricted`: the account that owns the API key is restricted. Not retryable.",
            "headers": {
              "X-Request-Id": {
                "description": "Identifies this request in Edgaze's logs. Present on every response, success or error. Quote it when you report a problem. Opaque: do not parse it.",
                "schema": {
                  "type": "string"
                }
              },
              "Cache-Control": {
                "description": "Always `private, no-store` on authenticated endpoints to prevent shared caching.",
                "schema": {
                  "type": "string"
                }
              },
              "Vary": {
                "description": "Always includes `Authorization` on authenticated endpoints.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "examples": {
                  "default": {
                    "value": {
                      "error": {
                        "code": "insufficient_scope",
                        "message": "API key is missing the required scope."
                      }
                    }
                  }
                }
              }
            },
            "x-error-codes": ["insufficient_scope", "account_restricted"]
          },
          "429": {
            "description": "`rate_limited`: too many requests. Wait `Retry-After` seconds, then retry. Only POST /runs is limited today; every operation lists this so clients already handle it if that changes.",
            "headers": {
              "X-Request-Id": {
                "description": "Identifies this request in Edgaze's logs. Present on every response, success or error. Quote it when you report a problem. Opaque: do not parse it.",
                "schema": {
                  "type": "string"
                }
              },
              "Cache-Control": {
                "description": "Always `private, no-store` on authenticated endpoints to prevent shared caching.",
                "schema": {
                  "type": "string"
                }
              },
              "Vary": {
                "description": "Always includes `Authorization` on authenticated endpoints.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "examples": {
                  "default": {
                    "value": {
                      "error": {
                        "code": "rate_limited",
                        "message": "too many requests. Wait `Retry-After` seconds, then retry. Only POST /runs is limited today; every operation lists this so clients already handle it if that changes."
                      }
                    }
                  }
                }
              }
            },
            "x-error-codes": ["rate_limited"]
          },
          "500": {
            "description": "Unexpected server error.",
            "headers": {
              "X-Request-Id": {
                "description": "Identifies this request in Edgaze's logs. Present on every response, success or error. Quote it when you report a problem. Opaque: do not parse it.",
                "schema": {
                  "type": "string"
                }
              },
              "Cache-Control": {
                "description": "Always `private, no-store` on authenticated endpoints to prevent shared caching.",
                "schema": {
                  "type": "string"
                }
              },
              "Vary": {
                "description": "Always includes `Authorization` on authenticated endpoints.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "examples": {
                  "default": {
                    "value": {
                      "error": {
                        "code": "internal_error",
                        "message": "Unexpected server error."
                      }
                    }
                  }
                }
              }
            },
            "x-error-codes": ["internal_error"]
          }
        },
        "x-scope-match": "any"
      },
      "post": {
        "operationId": "createRun",
        "summary": "Create run",
        "description": "Start an asynchronous workflow run. Returns 202 with URLs to poll status, read events, and stream progress.",
        "tags": ["Runs"],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "x-required-scopes": ["run:execute"],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Currently optional for backward compatibility, but strongly recommended for every request (max 255 chars). Prefer the header over `body.idempotencyKey`; when both are set they must match, or the request is rejected with 400 `invalid_request`. Values are case-sensitive and scoped to the Edgaze account. The fingerprint covers workflow, normalized inputs, and requested version. An exact replay returns the original run; a different request returns 409 `idempotency_conflict`. Keys have no time-based expiry while the run record exists.",
            "schema": {
              "type": "string",
              "maxLength": 255
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateRunRequest"
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Run accepted.",
            "headers": {
              "X-Request-Id": {
                "description": "Identifies this request in Edgaze's logs. Present on every response, success or error. Quote it when you report a problem. Opaque: do not parse it.",
                "schema": {
                  "type": "string"
                }
              },
              "Cache-Control": {
                "description": "Always `private, no-store` on authenticated endpoints to prevent shared caching.",
                "schema": {
                  "type": "string"
                }
              },
              "Vary": {
                "description": "Always includes `Authorization` on authenticated endpoints.",
                "schema": {
                  "type": "string"
                }
              },
              "Deprecation": {
                "description": "Present only when the request used the deprecated `workflowId` field. RFC 9745 date the field was deprecated, `@1786492800` (2026-08-12). Send `workflow` instead.",
                "schema": {
                  "type": "string",
                  "examples": ["@1786492800"]
                }
              },
              "Link": {
                "description": "Present only with `Deprecation`: `<https://www.edgaze.ai/docs/api/versioning>; rel=\"deprecation\"`.",
                "schema": {
                  "type": "string"
                }
              },
              "Warning": {
                "description": "Present only with `Deprecation`: `299 - \"workflowId is deprecated; use workflow\"`.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CreateRunResponse"
                }
              }
            }
          },
          "400": {
            "description": "Request rejected before the run started. `invalid_json`: the body was not parseable JSON. `missing_workflow`: no `workflow` id was supplied. `invalid_inputs`: one or more inputs were rejected — `error.details` lists the offending `input` keys. `invalid_request`: the `Idempotency-Key` header conflicted with `body.idempotencyKey`.",
            "headers": {
              "X-Request-Id": {
                "description": "Identifies this request in Edgaze's logs. Present on every response, success or error. Quote it when you report a problem. Opaque: do not parse it.",
                "schema": {
                  "type": "string"
                }
              },
              "Cache-Control": {
                "description": "Always `private, no-store` on authenticated endpoints to prevent shared caching.",
                "schema": {
                  "type": "string"
                }
              },
              "Vary": {
                "description": "Always includes `Authorization` on authenticated endpoints.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "examples": {
                  "invalid_inputs": {
                    "value": {
                      "error": {
                        "code": "invalid_inputs",
                        "message": "One or more inputs were rejected.",
                        "details": [
                          {
                            "input": "topic",
                            "message": "is required"
                          }
                        ]
                      }
                    }
                  },
                  "missing_workflow": {
                    "value": {
                      "error": {
                        "code": "missing_workflow",
                        "message": "Provide a `workflow` id to run."
                      }
                    }
                  },
                  "invalid_json": {
                    "value": {
                      "error": {
                        "code": "invalid_json",
                        "message": "Request body must be valid JSON."
                      }
                    }
                  }
                }
              }
            },
            "x-error-codes": [
              "invalid_json",
              "missing_workflow",
              "invalid_inputs",
              "invalid_request"
            ]
          },
          "401": {
            "description": "Invalid or missing API key.",
            "headers": {
              "X-Request-Id": {
                "description": "Identifies this request in Edgaze's logs. Present on every response, success or error. Quote it when you report a problem. Opaque: do not parse it.",
                "schema": {
                  "type": "string"
                }
              },
              "Cache-Control": {
                "description": "Always `private, no-store` on authenticated endpoints to prevent shared caching.",
                "schema": {
                  "type": "string"
                }
              },
              "Vary": {
                "description": "Always includes `Authorization` on authenticated endpoints.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "examples": {
                  "default": {
                    "value": {
                      "error": {
                        "code": "unauthorized",
                        "message": "Invalid or missing API key."
                      }
                    }
                  }
                }
              }
            },
            "x-error-codes": ["unauthorized"]
          },
          "402": {
            "description": "This API key has reached its spend cap.",
            "headers": {
              "X-Request-Id": {
                "description": "Identifies this request in Edgaze's logs. Present on every response, success or error. Quote it when you report a problem. Opaque: do not parse it.",
                "schema": {
                  "type": "string"
                }
              },
              "Cache-Control": {
                "description": "Always `private, no-store` on authenticated endpoints to prevent shared caching.",
                "schema": {
                  "type": "string"
                }
              },
              "Vary": {
                "description": "Always includes `Authorization` on authenticated endpoints.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "examples": {
                  "default": {
                    "value": {
                      "error": {
                        "code": "spend_cap_reached",
                        "message": "This API key has reached its spend cap."
                      }
                    }
                  }
                }
              }
            },
            "x-error-codes": ["spend_cap_reached"]
          },
          "403": {
            "description": "Not entitled to run this workflow (`forbidden`), the account is restricted (`account_restricted`), or the creator's paid listings are paused (`creator_listings_paused`). `insufficient_scope`: the API key lacks every scope this operation accepts (`x-required-scopes`).",
            "headers": {
              "X-Request-Id": {
                "description": "Identifies this request in Edgaze's logs. Present on every response, success or error. Quote it when you report a problem. Opaque: do not parse it.",
                "schema": {
                  "type": "string"
                }
              },
              "Cache-Control": {
                "description": "Always `private, no-store` on authenticated endpoints to prevent shared caching.",
                "schema": {
                  "type": "string"
                }
              },
              "Vary": {
                "description": "Always includes `Authorization` on authenticated endpoints.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "examples": {
                  "default": {
                    "value": {
                      "error": {
                        "code": "forbidden",
                        "message": "Not entitled to run this workflow."
                      }
                    }
                  }
                }
              }
            },
            "x-error-codes": [
              "forbidden",
              "creator_listings_paused",
              "insufficient_scope",
              "account_restricted"
            ]
          },
          "404": {
            "description": "Workflow not found.",
            "headers": {
              "X-Request-Id": {
                "description": "Identifies this request in Edgaze's logs. Present on every response, success or error. Quote it when you report a problem. Opaque: do not parse it.",
                "schema": {
                  "type": "string"
                }
              },
              "Cache-Control": {
                "description": "Always `private, no-store` on authenticated endpoints to prevent shared caching.",
                "schema": {
                  "type": "string"
                }
              },
              "Vary": {
                "description": "Always includes `Authorization` on authenticated endpoints.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "examples": {
                  "default": {
                    "value": {
                      "error": {
                        "code": "not_found",
                        "message": "Workflow not found."
                      }
                    }
                  }
                }
              }
            },
            "x-error-codes": ["not_found"]
          },
          "409": {
            "description": "Idempotency key reused with a different request (`idempotency_conflict`), or the workflow needs third-party accounts the caller has not connected (`integration_connection_required`; `error.details` lists them, each with a `connectUrl`).",
            "headers": {
              "X-Request-Id": {
                "description": "Identifies this request in Edgaze's logs. Present on every response, success or error. Quote it when you report a problem. Opaque: do not parse it.",
                "schema": {
                  "type": "string"
                }
              },
              "Cache-Control": {
                "description": "Always `private, no-store` on authenticated endpoints to prevent shared caching.",
                "schema": {
                  "type": "string"
                }
              },
              "Vary": {
                "description": "Always includes `Authorization` on authenticated endpoints.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "examples": {
                  "default": {
                    "value": {
                      "error": {
                        "code": "idempotency_conflict",
                        "message": "Idempotency key reused with a different request."
                      }
                    }
                  }
                }
              }
            },
            "x-error-codes": ["idempotency_conflict", "integration_connection_required"]
          },
          "429": {
            "description": "Rate limited. A key that sends up to 60 POST /runs requests per minute is never rate limited. Above that, requests may be rejected with 429 `rate_limited`; how many more get through is not fixed, so do not plan around it. Wait `Retry-After` seconds, then retry with the same `Idempotency-Key`.",
            "headers": {
              "X-Request-Id": {
                "description": "Identifies this request in Edgaze's logs. Present on every response, success or error. Quote it when you report a problem. Opaque: do not parse it.",
                "schema": {
                  "type": "string"
                }
              },
              "Cache-Control": {
                "description": "Always `private, no-store` on authenticated endpoints to prevent shared caching.",
                "schema": {
                  "type": "string"
                }
              },
              "Vary": {
                "description": "Always includes `Authorization` on authenticated endpoints.",
                "schema": {
                  "type": "string"
                }
              },
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The per-minute rate that is guaranteed not to be limited (60). Sent only on a 429 response.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Always `0`. Sent only on a 429 response.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "examples": {
                  "default": {
                    "value": {
                      "error": {
                        "code": "rate_limited",
                        "message": "Rate limit exceeded. Slow down and retry."
                      }
                    }
                  }
                }
              }
            },
            "x-error-codes": ["rate_limited"]
          },
          "500": {
            "description": "Unexpected server error.",
            "headers": {
              "X-Request-Id": {
                "description": "Identifies this request in Edgaze's logs. Present on every response, success or error. Quote it when you report a problem. Opaque: do not parse it.",
                "schema": {
                  "type": "string"
                }
              },
              "Cache-Control": {
                "description": "Always `private, no-store` on authenticated endpoints to prevent shared caching.",
                "schema": {
                  "type": "string"
                }
              },
              "Vary": {
                "description": "Always includes `Authorization` on authenticated endpoints.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "examples": {
                  "default": {
                    "value": {
                      "error": {
                        "code": "internal_error",
                        "message": "Unexpected server error."
                      }
                    }
                  }
                }
              }
            },
            "x-error-codes": ["internal_error"]
          }
        },
        "x-scope-match": "any"
      }
    },
    "/runs/{id}": {
      "get": {
        "operationId": "getRun",
        "summary": "Get run",
        "description": "Read the run's canonical status, sanitized failure reason, buyer-facing displayed charge, and outputs. Rate limiting: reads are not metered per-key today; only POST /runs is (up to 60 per minute is never limited). Reads may become metered without a breaking change, so treat 429 with `Retry-After` as possible on any endpoint and back off rather than polling in a tight loop. For run progress prefer GET /runs/{id}/stream, or poll GET /runs/{id} no more than once per second.",
        "tags": ["Runs"],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "x-required-scopes": ["run:read"],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "UUID returned from POST /runs.",
            "schema": {
              "$ref": "#/components/schemas/RunIdParam"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Run detail.",
            "headers": {
              "X-Request-Id": {
                "description": "Identifies this request in Edgaze's logs. Present on every response, success or error. Quote it when you report a problem. Opaque: do not parse it.",
                "schema": {
                  "type": "string"
                }
              },
              "Cache-Control": {
                "description": "Always `private, no-store` on authenticated endpoints to prevent shared caching.",
                "schema": {
                  "type": "string"
                }
              },
              "Vary": {
                "description": "Always includes `Authorization` on authenticated endpoints.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Run"
                }
              }
            }
          },
          "401": {
            "description": "Invalid or missing API key.",
            "headers": {
              "X-Request-Id": {
                "description": "Identifies this request in Edgaze's logs. Present on every response, success or error. Quote it when you report a problem. Opaque: do not parse it.",
                "schema": {
                  "type": "string"
                }
              },
              "Cache-Control": {
                "description": "Always `private, no-store` on authenticated endpoints to prevent shared caching.",
                "schema": {
                  "type": "string"
                }
              },
              "Vary": {
                "description": "Always includes `Authorization` on authenticated endpoints.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "examples": {
                  "default": {
                    "value": {
                      "error": {
                        "code": "unauthorized",
                        "message": "Invalid or missing API key."
                      }
                    }
                  }
                }
              }
            },
            "x-error-codes": ["unauthorized"]
          },
          "403": {
            "description": "API key is missing the required scope. `insufficient_scope`: the API key lacks every scope this operation accepts (`x-required-scopes`). `account_restricted`: the account that owns the API key is restricted. Not retryable.",
            "headers": {
              "X-Request-Id": {
                "description": "Identifies this request in Edgaze's logs. Present on every response, success or error. Quote it when you report a problem. Opaque: do not parse it.",
                "schema": {
                  "type": "string"
                }
              },
              "Cache-Control": {
                "description": "Always `private, no-store` on authenticated endpoints to prevent shared caching.",
                "schema": {
                  "type": "string"
                }
              },
              "Vary": {
                "description": "Always includes `Authorization` on authenticated endpoints.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "examples": {
                  "default": {
                    "value": {
                      "error": {
                        "code": "insufficient_scope",
                        "message": "API key is missing the required scope."
                      }
                    }
                  }
                }
              }
            },
            "x-error-codes": ["insufficient_scope", "account_restricted"]
          },
          "404": {
            "description": "Run not found (also returned for cross-tenant reads).",
            "headers": {
              "X-Request-Id": {
                "description": "Identifies this request in Edgaze's logs. Present on every response, success or error. Quote it when you report a problem. Opaque: do not parse it.",
                "schema": {
                  "type": "string"
                }
              },
              "Cache-Control": {
                "description": "Always `private, no-store` on authenticated endpoints to prevent shared caching.",
                "schema": {
                  "type": "string"
                }
              },
              "Vary": {
                "description": "Always includes `Authorization` on authenticated endpoints.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "examples": {
                  "default": {
                    "value": {
                      "error": {
                        "code": "not_found",
                        "message": "Run not found (also returned for cross-tenant reads)."
                      }
                    }
                  }
                }
              }
            },
            "x-error-codes": ["not_found"]
          },
          "413": {
            "description": "The run's outputs are too large to return in one response.",
            "headers": {
              "X-Request-Id": {
                "description": "Identifies this request in Edgaze's logs. Present on every response, success or error. Quote it when you report a problem. Opaque: do not parse it.",
                "schema": {
                  "type": "string"
                }
              },
              "Cache-Control": {
                "description": "Always `private, no-store` on authenticated endpoints to prevent shared caching.",
                "schema": {
                  "type": "string"
                }
              },
              "Vary": {
                "description": "Always includes `Authorization` on authenticated endpoints.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "examples": {
                  "default": {
                    "value": {
                      "error": {
                        "code": "output_too_large",
                        "message": "The run's outputs are too large to return in one response."
                      }
                    }
                  }
                }
              }
            },
            "x-error-codes": ["output_too_large"]
          },
          "429": {
            "description": "`rate_limited`: too many requests. Wait `Retry-After` seconds, then retry. Only POST /runs is limited today; every operation lists this so clients already handle it if that changes.",
            "headers": {
              "X-Request-Id": {
                "description": "Identifies this request in Edgaze's logs. Present on every response, success or error. Quote it when you report a problem. Opaque: do not parse it.",
                "schema": {
                  "type": "string"
                }
              },
              "Cache-Control": {
                "description": "Always `private, no-store` on authenticated endpoints to prevent shared caching.",
                "schema": {
                  "type": "string"
                }
              },
              "Vary": {
                "description": "Always includes `Authorization` on authenticated endpoints.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "examples": {
                  "default": {
                    "value": {
                      "error": {
                        "code": "rate_limited",
                        "message": "too many requests. Wait `Retry-After` seconds, then retry. Only POST /runs is limited today; every operation lists this so clients already handle it if that changes."
                      }
                    }
                  }
                }
              }
            },
            "x-error-codes": ["rate_limited"]
          },
          "500": {
            "description": "Unexpected server error.",
            "headers": {
              "X-Request-Id": {
                "description": "Identifies this request in Edgaze's logs. Present on every response, success or error. Quote it when you report a problem. Opaque: do not parse it.",
                "schema": {
                  "type": "string"
                }
              },
              "Cache-Control": {
                "description": "Always `private, no-store` on authenticated endpoints to prevent shared caching.",
                "schema": {
                  "type": "string"
                }
              },
              "Vary": {
                "description": "Always includes `Authorization` on authenticated endpoints.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "examples": {
                  "default": {
                    "value": {
                      "error": {
                        "code": "internal_error",
                        "message": "Unexpected server error."
                      }
                    }
                  }
                }
              }
            },
            "x-error-codes": ["internal_error"]
          }
        },
        "x-scope-match": "any"
      }
    },
    "/runs/{id}/cancel": {
      "post": {
        "operationId": "cancelRun",
        "summary": "Cancel run",
        "description": "Request cancellation of a run that is still in flight. Idempotent: a run that already reached a terminal state is returned unchanged with `cancelRequested: false`. A run that has not started executing yet is cancelled before this call returns. A run that is executing is stopped asynchronously: the response carries its current status with `cancelRequested: true`, and the run reaches `cancelled` shortly after, with a `run.cancelled` webhook. A cancelled run is never charged to the buyer (`billingStatus` settles to `not_charged`). Requires `run:execute`, not `run:read`, because it changes the run's outcome.",
        "tags": ["Runs"],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "x-required-scopes": ["run:execute"],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "UUID returned from POST /runs.",
            "schema": {
              "$ref": "#/components/schemas/RunIdParam"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Cancellation recorded.",
            "headers": {
              "X-Request-Id": {
                "description": "Identifies this request in Edgaze's logs. Present on every response, success or error. Quote it when you report a problem. Opaque: do not parse it.",
                "schema": {
                  "type": "string"
                }
              },
              "Cache-Control": {
                "description": "Always `private, no-store` on authenticated endpoints to prevent shared caching.",
                "schema": {
                  "type": "string"
                }
              },
              "Vary": {
                "description": "Always includes `Authorization` on authenticated endpoints.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CancelRunResponse"
                }
              }
            }
          },
          "401": {
            "description": "Invalid or missing API key.",
            "headers": {
              "X-Request-Id": {
                "description": "Identifies this request in Edgaze's logs. Present on every response, success or error. Quote it when you report a problem. Opaque: do not parse it.",
                "schema": {
                  "type": "string"
                }
              },
              "Cache-Control": {
                "description": "Always `private, no-store` on authenticated endpoints to prevent shared caching.",
                "schema": {
                  "type": "string"
                }
              },
              "Vary": {
                "description": "Always includes `Authorization` on authenticated endpoints.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "examples": {
                  "default": {
                    "value": {
                      "error": {
                        "code": "unauthorized",
                        "message": "Invalid or missing API key."
                      }
                    }
                  }
                }
              }
            },
            "x-error-codes": ["unauthorized"]
          },
          "403": {
            "description": "API key is missing the required scope. `insufficient_scope`: the API key lacks every scope this operation accepts (`x-required-scopes`). `account_restricted`: the account that owns the API key is restricted. Not retryable.",
            "headers": {
              "X-Request-Id": {
                "description": "Identifies this request in Edgaze's logs. Present on every response, success or error. Quote it when you report a problem. Opaque: do not parse it.",
                "schema": {
                  "type": "string"
                }
              },
              "Cache-Control": {
                "description": "Always `private, no-store` on authenticated endpoints to prevent shared caching.",
                "schema": {
                  "type": "string"
                }
              },
              "Vary": {
                "description": "Always includes `Authorization` on authenticated endpoints.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "examples": {
                  "default": {
                    "value": {
                      "error": {
                        "code": "insufficient_scope",
                        "message": "API key is missing the required scope."
                      }
                    }
                  }
                }
              }
            },
            "x-error-codes": ["insufficient_scope", "account_restricted"]
          },
          "404": {
            "description": "Run not found (also returned for cross-tenant calls).",
            "headers": {
              "X-Request-Id": {
                "description": "Identifies this request in Edgaze's logs. Present on every response, success or error. Quote it when you report a problem. Opaque: do not parse it.",
                "schema": {
                  "type": "string"
                }
              },
              "Cache-Control": {
                "description": "Always `private, no-store` on authenticated endpoints to prevent shared caching.",
                "schema": {
                  "type": "string"
                }
              },
              "Vary": {
                "description": "Always includes `Authorization` on authenticated endpoints.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "examples": {
                  "default": {
                    "value": {
                      "error": {
                        "code": "not_found",
                        "message": "Run not found (also returned for cross-tenant calls)."
                      }
                    }
                  }
                }
              }
            },
            "x-error-codes": ["not_found"]
          },
          "429": {
            "description": "`rate_limited`: too many requests. Wait `Retry-After` seconds, then retry. Only POST /runs is limited today; every operation lists this so clients already handle it if that changes.",
            "headers": {
              "X-Request-Id": {
                "description": "Identifies this request in Edgaze's logs. Present on every response, success or error. Quote it when you report a problem. Opaque: do not parse it.",
                "schema": {
                  "type": "string"
                }
              },
              "Cache-Control": {
                "description": "Always `private, no-store` on authenticated endpoints to prevent shared caching.",
                "schema": {
                  "type": "string"
                }
              },
              "Vary": {
                "description": "Always includes `Authorization` on authenticated endpoints.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "examples": {
                  "default": {
                    "value": {
                      "error": {
                        "code": "rate_limited",
                        "message": "too many requests. Wait `Retry-After` seconds, then retry. Only POST /runs is limited today; every operation lists this so clients already handle it if that changes."
                      }
                    }
                  }
                }
              }
            },
            "x-error-codes": ["rate_limited"]
          },
          "500": {
            "description": "Unexpected server error.",
            "headers": {
              "X-Request-Id": {
                "description": "Identifies this request in Edgaze's logs. Present on every response, success or error. Quote it when you report a problem. Opaque: do not parse it.",
                "schema": {
                  "type": "string"
                }
              },
              "Cache-Control": {
                "description": "Always `private, no-store` on authenticated endpoints to prevent shared caching.",
                "schema": {
                  "type": "string"
                }
              },
              "Vary": {
                "description": "Always includes `Authorization` on authenticated endpoints.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "examples": {
                  "default": {
                    "value": {
                      "error": {
                        "code": "internal_error",
                        "message": "Unexpected server error."
                      }
                    }
                  }
                }
              }
            },
            "x-error-codes": ["internal_error"]
          },
          "503": {
            "description": "Cancellation could not be delivered. Safe to retry.",
            "headers": {
              "X-Request-Id": {
                "description": "Identifies this request in Edgaze's logs. Present on every response, success or error. Quote it when you report a problem. Opaque: do not parse it.",
                "schema": {
                  "type": "string"
                }
              },
              "Cache-Control": {
                "description": "Always `private, no-store` on authenticated endpoints to prevent shared caching.",
                "schema": {
                  "type": "string"
                }
              },
              "Vary": {
                "description": "Always includes `Authorization` on authenticated endpoints.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "examples": {
                  "default": {
                    "value": {
                      "error": {
                        "code": "cancel_unavailable",
                        "message": "Cancellation could not be delivered. Retry shortly."
                      }
                    }
                  }
                }
              }
            },
            "x-error-codes": ["cancel_unavailable"]
          }
        },
        "x-scope-match": "any"
      }
    },
    "/runs/{id}/events": {
      "get": {
        "operationId": "listRunEvents",
        "summary": "List run events",
        "description": "Return an ordered, bounded page of durable run lifecycle events. Continue from `nextAfterSequence` while `hasMore` is true. Each event carries a `data` object with the content for its type — most importantly `data.text` on `node.output.delta`, which you concatenate in `sequence` order to reconstruct a node's streamed output. Rate limiting: reads are not metered per-key today; only POST /runs is (up to 60 per minute is never limited). Reads may become metered without a breaking change, so treat 429 with `Retry-After` as possible on any endpoint and back off rather than polling in a tight loop. For run progress prefer GET /runs/{id}/stream, or poll GET /runs/{id} no more than once per second.",
        "tags": ["Runs"],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "x-required-scopes": ["run:read"],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "UUID returned from POST /runs.",
            "schema": {
              "$ref": "#/components/schemas/RunIdParam"
            }
          },
          {
            "name": "afterSequence",
            "in": "query",
            "required": false,
            "description": "Return events strictly after this sequence. Defaults to 0.",
            "schema": {
              "type": "integer",
              "minimum": 0
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Page size from 1 to 500. Defaults to 100.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 500
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Ordered page of events.",
            "headers": {
              "X-Request-Id": {
                "description": "Identifies this request in Edgaze's logs. Present on every response, success or error. Quote it when you report a problem. Opaque: do not parse it.",
                "schema": {
                  "type": "string"
                }
              },
              "Cache-Control": {
                "description": "Always `private, no-store` on authenticated endpoints to prevent shared caching.",
                "schema": {
                  "type": "string"
                }
              },
              "Vary": {
                "description": "Always includes `Authorization` on authenticated endpoints.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RunEventsPage"
                }
              }
            }
          },
          "400": {
            "description": "Invalid pagination cursor.",
            "headers": {
              "X-Request-Id": {
                "description": "Identifies this request in Edgaze's logs. Present on every response, success or error. Quote it when you report a problem. Opaque: do not parse it.",
                "schema": {
                  "type": "string"
                }
              },
              "Cache-Control": {
                "description": "Always `private, no-store` on authenticated endpoints to prevent shared caching.",
                "schema": {
                  "type": "string"
                }
              },
              "Vary": {
                "description": "Always includes `Authorization` on authenticated endpoints.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "examples": {
                  "default": {
                    "value": {
                      "error": {
                        "code": "invalid_request",
                        "message": "Invalid pagination cursor."
                      }
                    }
                  }
                }
              }
            },
            "x-error-codes": ["invalid_request"]
          },
          "401": {
            "description": "Invalid or missing API key.",
            "headers": {
              "X-Request-Id": {
                "description": "Identifies this request in Edgaze's logs. Present on every response, success or error. Quote it when you report a problem. Opaque: do not parse it.",
                "schema": {
                  "type": "string"
                }
              },
              "Cache-Control": {
                "description": "Always `private, no-store` on authenticated endpoints to prevent shared caching.",
                "schema": {
                  "type": "string"
                }
              },
              "Vary": {
                "description": "Always includes `Authorization` on authenticated endpoints.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "examples": {
                  "default": {
                    "value": {
                      "error": {
                        "code": "unauthorized",
                        "message": "Invalid or missing API key."
                      }
                    }
                  }
                }
              }
            },
            "x-error-codes": ["unauthorized"]
          },
          "403": {
            "description": "API key is missing the required scope. `insufficient_scope`: the API key lacks every scope this operation accepts (`x-required-scopes`). `account_restricted`: the account that owns the API key is restricted. Not retryable.",
            "headers": {
              "X-Request-Id": {
                "description": "Identifies this request in Edgaze's logs. Present on every response, success or error. Quote it when you report a problem. Opaque: do not parse it.",
                "schema": {
                  "type": "string"
                }
              },
              "Cache-Control": {
                "description": "Always `private, no-store` on authenticated endpoints to prevent shared caching.",
                "schema": {
                  "type": "string"
                }
              },
              "Vary": {
                "description": "Always includes `Authorization` on authenticated endpoints.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "examples": {
                  "default": {
                    "value": {
                      "error": {
                        "code": "insufficient_scope",
                        "message": "API key is missing the required scope."
                      }
                    }
                  }
                }
              }
            },
            "x-error-codes": ["insufficient_scope", "account_restricted"]
          },
          "404": {
            "description": "Run not found.",
            "headers": {
              "X-Request-Id": {
                "description": "Identifies this request in Edgaze's logs. Present on every response, success or error. Quote it when you report a problem. Opaque: do not parse it.",
                "schema": {
                  "type": "string"
                }
              },
              "Cache-Control": {
                "description": "Always `private, no-store` on authenticated endpoints to prevent shared caching.",
                "schema": {
                  "type": "string"
                }
              },
              "Vary": {
                "description": "Always includes `Authorization` on authenticated endpoints.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "examples": {
                  "default": {
                    "value": {
                      "error": {
                        "code": "not_found",
                        "message": "Run not found."
                      }
                    }
                  }
                }
              }
            },
            "x-error-codes": ["not_found"]
          },
          "429": {
            "description": "`rate_limited`: too many requests. Wait `Retry-After` seconds, then retry. Only POST /runs is limited today; every operation lists this so clients already handle it if that changes.",
            "headers": {
              "X-Request-Id": {
                "description": "Identifies this request in Edgaze's logs. Present on every response, success or error. Quote it when you report a problem. Opaque: do not parse it.",
                "schema": {
                  "type": "string"
                }
              },
              "Cache-Control": {
                "description": "Always `private, no-store` on authenticated endpoints to prevent shared caching.",
                "schema": {
                  "type": "string"
                }
              },
              "Vary": {
                "description": "Always includes `Authorization` on authenticated endpoints.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "examples": {
                  "default": {
                    "value": {
                      "error": {
                        "code": "rate_limited",
                        "message": "too many requests. Wait `Retry-After` seconds, then retry. Only POST /runs is limited today; every operation lists this so clients already handle it if that changes."
                      }
                    }
                  }
                }
              }
            },
            "x-error-codes": ["rate_limited"]
          },
          "500": {
            "description": "Unexpected server error.",
            "headers": {
              "X-Request-Id": {
                "description": "Identifies this request in Edgaze's logs. Present on every response, success or error. Quote it when you report a problem. Opaque: do not parse it.",
                "schema": {
                  "type": "string"
                }
              },
              "Cache-Control": {
                "description": "Always `private, no-store` on authenticated endpoints to prevent shared caching.",
                "schema": {
                  "type": "string"
                }
              },
              "Vary": {
                "description": "Always includes `Authorization` on authenticated endpoints.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "examples": {
                  "default": {
                    "value": {
                      "error": {
                        "code": "internal_error",
                        "message": "Unexpected server error."
                      }
                    }
                  }
                }
              }
            },
            "x-error-codes": ["internal_error"]
          }
        },
        "x-scope-match": "any"
      }
    },
    "/runs/{id}/stream": {
      "get": {
        "operationId": "streamRun",
        "summary": "Stream run",
        "description": "Server-Sent Events stream of the run's log. Every frame's `data:` is one JSON object, and the `event:` name says which schema it follows (listed in `x-sse-events` on the 200 response). A connection opens with an `event: snapshot` frame (`RunStreamSnapshot`), replays the run's events, then tails new ones. Each event frame's `data` is a `RunStreamEvent` and its `id:` is the event's `sequence`; its `event:` is `run.status` for run lifecycle events and the event type otherwise. `RunStreamEvent` carries the same events as GET /runs/{id}/events in a different envelope (`occurredAt` instead of `ts`, `status`/`reason` at the top level, absent fields omitted). Node output arrives as `node.output.delta` frames whose `data.text` you append in order, so this is the endpoint to build a live progress UI on rather than polling. While idle the server sends `event: ping` every 15 seconds (`RunStreamPing`, no `id:`). When the run ends it sends a final `snapshot` and closes. A connection rolls over about every 5 minutes: it ends with `retry: 1000` and an `event: reconnect` frame (`RunStreamReconnect`) whose `id:` is the last sequence it sent; reconnect with that id as `Last-Event-ID` and nothing is lost or repeated. If the stream fails on the server it sends `event: error` (`RunStreamError`) and closes; reconnect the same way. Ignore `event:` names you do not recognize, and frames without an `id:` never move your resume cursor.",
        "tags": ["Runs"],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "x-required-scopes": ["run:read"],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "UUID returned from POST /runs.",
            "schema": {
              "$ref": "#/components/schemas/RunIdParam"
            }
          },
          {
            "name": "Last-Event-ID",
            "in": "header",
            "required": false,
            "description": "SSE resume cursor. When present, must be a non-negative integer; the server replays events with a strictly greater sequence.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "afterSequence",
            "in": "query",
            "required": false,
            "description": "Alternative resume cursor when the client cannot set the header.",
            "schema": {
              "type": "integer",
              "minimum": 0
            }
          }
        ],
        "responses": {
          "200": {
            "description": "SSE stream of run events.",
            "headers": {
              "Cache-Control": {
                "description": "`private, no-store, no-transform` so intermediaries never buffer the stream.",
                "schema": {
                  "type": "string"
                }
              },
              "Vary": {
                "description": "Includes `Authorization`.",
                "schema": {
                  "type": "string"
                }
              },
              "X-Request-Id": {
                "description": "Identifies this request in Edgaze's logs. Present on every response, success or error. Quote it when you report a problem. Opaque: do not parse it.",
                "schema": {
                  "type": "string"
                }
              },
              "X-Accel-Buffering": {
                "description": "`no` — signals proxies to disable buffering.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "text/event-stream": {
                "schema": {
                  "type": "string",
                  "description": "SSE-encoded event stream."
                }
              }
            },
            "x-sse-events": {
              "snapshot": {
                "description": "First frame of every connection, and the last frame when the run ends.",
                "schema": {
                  "$ref": "#/components/schemas/RunStreamSnapshot"
                },
                "hasId": false
              },
              "run.status": {
                "description": "A run lifecycle event (`status` is set).",
                "schema": {
                  "$ref": "#/components/schemas/RunStreamEvent"
                },
                "hasId": true
              },
              "*": {
                "description": "Any other event name is the event's `type` (for example `node.output.delta`) and carries a `RunStreamEvent`.",
                "schema": {
                  "$ref": "#/components/schemas/RunStreamEvent"
                },
                "hasId": true
              },
              "ping": {
                "description": "Idle heartbeat every 15 seconds. Ignore it.",
                "schema": {
                  "$ref": "#/components/schemas/RunStreamPing"
                },
                "hasId": false
              },
              "reconnect": {
                "description": "Planned roll-over about every 5 minutes; reconnect with its `id:` as `Last-Event-ID`.",
                "schema": {
                  "$ref": "#/components/schemas/RunStreamReconnect"
                },
                "hasId": true
              },
              "error": {
                "description": "The stream failed on the server; reconnect with the last `id:` you processed.",
                "schema": {
                  "$ref": "#/components/schemas/RunStreamError"
                },
                "hasId": false
              }
            }
          },
          "400": {
            "description": "Invalid `Last-Event-ID` cursor.",
            "headers": {
              "X-Request-Id": {
                "description": "Identifies this request in Edgaze's logs. Present on every response, success or error. Quote it when you report a problem. Opaque: do not parse it.",
                "schema": {
                  "type": "string"
                }
              },
              "Cache-Control": {
                "description": "Always `private, no-store` on authenticated endpoints to prevent shared caching.",
                "schema": {
                  "type": "string"
                }
              },
              "Vary": {
                "description": "Always includes `Authorization` on authenticated endpoints.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "examples": {
                  "default": {
                    "value": {
                      "error": {
                        "code": "invalid_cursor",
                        "message": "Invalid `Last-Event-ID` cursor."
                      }
                    }
                  }
                }
              }
            },
            "x-error-codes": ["invalid_cursor"]
          },
          "401": {
            "description": "Invalid or missing API key.",
            "headers": {
              "X-Request-Id": {
                "description": "Identifies this request in Edgaze's logs. Present on every response, success or error. Quote it when you report a problem. Opaque: do not parse it.",
                "schema": {
                  "type": "string"
                }
              },
              "Cache-Control": {
                "description": "Always `private, no-store` on authenticated endpoints to prevent shared caching.",
                "schema": {
                  "type": "string"
                }
              },
              "Vary": {
                "description": "Always includes `Authorization` on authenticated endpoints.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "examples": {
                  "default": {
                    "value": {
                      "error": {
                        "code": "unauthorized",
                        "message": "Invalid or missing API key."
                      }
                    }
                  }
                }
              }
            },
            "x-error-codes": ["unauthorized"]
          },
          "403": {
            "description": "API key is missing the required scope. `insufficient_scope`: the API key lacks every scope this operation accepts (`x-required-scopes`). `account_restricted`: the account that owns the API key is restricted. Not retryable.",
            "headers": {
              "X-Request-Id": {
                "description": "Identifies this request in Edgaze's logs. Present on every response, success or error. Quote it when you report a problem. Opaque: do not parse it.",
                "schema": {
                  "type": "string"
                }
              },
              "Cache-Control": {
                "description": "Always `private, no-store` on authenticated endpoints to prevent shared caching.",
                "schema": {
                  "type": "string"
                }
              },
              "Vary": {
                "description": "Always includes `Authorization` on authenticated endpoints.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "examples": {
                  "default": {
                    "value": {
                      "error": {
                        "code": "insufficient_scope",
                        "message": "API key is missing the required scope."
                      }
                    }
                  }
                }
              }
            },
            "x-error-codes": ["insufficient_scope", "account_restricted"]
          },
          "404": {
            "description": "Run not found.",
            "headers": {
              "X-Request-Id": {
                "description": "Identifies this request in Edgaze's logs. Present on every response, success or error. Quote it when you report a problem. Opaque: do not parse it.",
                "schema": {
                  "type": "string"
                }
              },
              "Cache-Control": {
                "description": "Always `private, no-store` on authenticated endpoints to prevent shared caching.",
                "schema": {
                  "type": "string"
                }
              },
              "Vary": {
                "description": "Always includes `Authorization` on authenticated endpoints.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "examples": {
                  "default": {
                    "value": {
                      "error": {
                        "code": "not_found",
                        "message": "Run not found."
                      }
                    }
                  }
                }
              }
            },
            "x-error-codes": ["not_found"]
          },
          "429": {
            "description": "`rate_limited`: too many requests. Wait `Retry-After` seconds, then retry. Only POST /runs is limited today; every operation lists this so clients already handle it if that changes.",
            "headers": {
              "X-Request-Id": {
                "description": "Identifies this request in Edgaze's logs. Present on every response, success or error. Quote it when you report a problem. Opaque: do not parse it.",
                "schema": {
                  "type": "string"
                }
              },
              "Cache-Control": {
                "description": "Always `private, no-store` on authenticated endpoints to prevent shared caching.",
                "schema": {
                  "type": "string"
                }
              },
              "Vary": {
                "description": "Always includes `Authorization` on authenticated endpoints.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "examples": {
                  "default": {
                    "value": {
                      "error": {
                        "code": "rate_limited",
                        "message": "too many requests. Wait `Retry-After` seconds, then retry. Only POST /runs is limited today; every operation lists this so clients already handle it if that changes."
                      }
                    }
                  }
                }
              }
            },
            "x-error-codes": ["rate_limited"]
          },
          "500": {
            "description": "Unexpected server error before the stream opened.",
            "headers": {
              "X-Request-Id": {
                "description": "Identifies this request in Edgaze's logs. Present on every response, success or error. Quote it when you report a problem. Opaque: do not parse it.",
                "schema": {
                  "type": "string"
                }
              },
              "Cache-Control": {
                "description": "Always `private, no-store` on authenticated endpoints to prevent shared caching.",
                "schema": {
                  "type": "string"
                }
              },
              "Vary": {
                "description": "Always includes `Authorization` on authenticated endpoints.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "examples": {
                  "default": {
                    "value": {
                      "error": {
                        "code": "internal_error",
                        "message": "Unexpected server error before the stream opened."
                      }
                    }
                  }
                }
              }
            },
            "x-error-codes": ["internal_error"]
          }
        },
        "x-scope-match": "any"
      }
    }
  },
  "webhooks": {
    "run.completed": {
      "post": {
        "summary": "Run reached the completed state.",
        "description": "Sent once a run settles successfully, after billing has committed. The body carries no outputs or charge; fetch `runUrl` (GET /runs/{id}) to read them, and `displayedChargeUsd` there is final. Delivery is at-least-once. Each attempt has a 5 second timeout; a 5xx, 408, 425, 429, connection error, or timeout is retried with exponential backoff (up to 6 attempts over roughly 9 hours, honouring a longer Retry-After). Any other 4xx or a redirect is treated as a permanent rejection and is not retried. Because an event can therefore arrive more than once, deduplicate on the edgaze-delivery-id header, which is stable across retries (or on the body's `runId` plus `type` when that header is absent). Endpoints are delivered in parallel and ordering is not guaranteed. An endpoint is paused after 15 consecutive deliveries that exhaust every attempt, until it is re-enabled in the developer console. Return 2xx as soon as you have persisted the payload, and do the real work asynchronously.",
        "parameters": [
          {
            "name": "edgaze-signature",
            "in": "header",
            "required": true,
            "description": "Signature of this delivery, formatted `t=<unix seconds>,v1=<hex hmac>`. Signed with HMAC-SHA256 over the exact string `{t}.{raw request body}`, using your endpoint secret. Compare in constant time against the raw bytes you received, before JSON parsing: re-serializing the body changes the signature. Reject any delivery whose `t` is more than 300 seconds from your clock, otherwise a captured payload can be replayed indefinitely.",
            "schema": {
              "type": "string",
              "examples": ["t=1800000000,v1=5257a869e7…"]
            }
          },
          {
            "name": "edgaze-webhook-id",
            "in": "header",
            "required": true,
            "description": "UUID of the endpoint that this delivery was sent to. Use it to select the right secret when one receiver serves several endpoints.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "edgaze-delivery-id",
            "in": "header",
            "required": false,
            "description": "UUID of this delivery, repeated byte-for-byte on every retry of the same event. This is the deduplication key: record it once you have processed a delivery and return 2xx without reprocessing if it arrives again. Absent on a manual test delivery and on a rare single-attempt fallback delivery (sent when the delivery could not be queued); neither is retried. Deduplicate those on the body's `runId` plus `type`.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "edgaze-attempt",
            "in": "header",
            "required": true,
            "description": "1-indexed attempt number for this delivery. A value above 1 means an earlier attempt did not return 2xx.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "examples": [1]
            }
          },
          {
            "name": "edgaze-event",
            "in": "header",
            "required": true,
            "description": "Event type carried by the payload, so a receiver can route the delivery before parsing the body.",
            "schema": {
              "type": "string",
              "examples": ["run.completed"]
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/WebhookRunEventPayload"
              }
            }
          },
          "description": "Signed with HMAC-SHA256 over the exact string `{t}.{raw request body}`, using your endpoint secret. Compare in constant time against the raw bytes you received, before JSON parsing: re-serializing the body changes the signature. Reject any delivery whose `t` is more than 300 seconds from your clock, otherwise a captured payload can be replayed indefinitely."
        },
        "responses": {
          "200": {
            "description": "Acknowledge receipt. Any 2xx settles the delivery. A 5xx, 408, 425, 429, timeout, or connection error is retried with backoff; any other 4xx or a redirect permanently fails the delivery."
          }
        }
      }
    },
    "run.failed": {
      "post": {
        "summary": "Run reached the failed state.",
        "description": "Sent once a run terminates unsuccessfully. The body's `reason` is the same buyer-safe text as GET /runs/{id}. Delivery is at-least-once. Each attempt has a 5 second timeout; a 5xx, 408, 425, 429, connection error, or timeout is retried with exponential backoff (up to 6 attempts over roughly 9 hours, honouring a longer Retry-After). Any other 4xx or a redirect is treated as a permanent rejection and is not retried. Because an event can therefore arrive more than once, deduplicate on the edgaze-delivery-id header, which is stable across retries (or on the body's `runId` plus `type` when that header is absent). Endpoints are delivered in parallel and ordering is not guaranteed. An endpoint is paused after 15 consecutive deliveries that exhaust every attempt, until it is re-enabled in the developer console. Return 2xx as soon as you have persisted the payload, and do the real work asynchronously.",
        "parameters": [
          {
            "name": "edgaze-signature",
            "in": "header",
            "required": true,
            "description": "Signature of this delivery, formatted `t=<unix seconds>,v1=<hex hmac>`. Signed with HMAC-SHA256 over the exact string `{t}.{raw request body}`, using your endpoint secret. Compare in constant time against the raw bytes you received, before JSON parsing: re-serializing the body changes the signature. Reject any delivery whose `t` is more than 300 seconds from your clock, otherwise a captured payload can be replayed indefinitely.",
            "schema": {
              "type": "string",
              "examples": ["t=1800000000,v1=5257a869e7…"]
            }
          },
          {
            "name": "edgaze-webhook-id",
            "in": "header",
            "required": true,
            "description": "UUID of the endpoint that this delivery was sent to. Use it to select the right secret when one receiver serves several endpoints.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "edgaze-delivery-id",
            "in": "header",
            "required": false,
            "description": "UUID of this delivery, repeated byte-for-byte on every retry of the same event. This is the deduplication key: record it once you have processed a delivery and return 2xx without reprocessing if it arrives again. Absent on a manual test delivery and on a rare single-attempt fallback delivery (sent when the delivery could not be queued); neither is retried. Deduplicate those on the body's `runId` plus `type`.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "edgaze-attempt",
            "in": "header",
            "required": true,
            "description": "1-indexed attempt number for this delivery. A value above 1 means an earlier attempt did not return 2xx.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "examples": [1]
            }
          },
          {
            "name": "edgaze-event",
            "in": "header",
            "required": true,
            "description": "Event type carried by the payload, so a receiver can route the delivery before parsing the body.",
            "schema": {
              "type": "string",
              "examples": ["run.completed"]
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/WebhookRunEventPayload"
              }
            }
          },
          "description": "Signed with HMAC-SHA256 over the exact string `{t}.{raw request body}`, using your endpoint secret. Compare in constant time against the raw bytes you received, before JSON parsing: re-serializing the body changes the signature. Reject any delivery whose `t` is more than 300 seconds from your clock, otherwise a captured payload can be replayed indefinitely."
        },
        "responses": {
          "200": {
            "description": "Acknowledge receipt. Any 2xx settles the delivery. A 5xx, 408, 425, 429, timeout, or connection error is retried with backoff; any other 4xx or a redirect permanently fails the delivery."
          }
        }
      }
    },
    "run.cancelled": {
      "post": {
        "summary": "Run was cancelled.",
        "description": "Sent after POST /runs/{id}/cancel or a cancellation from the web UI. A cancelled run is never charged. Delivery is at-least-once. Each attempt has a 5 second timeout; a 5xx, 408, 425, 429, connection error, or timeout is retried with exponential backoff (up to 6 attempts over roughly 9 hours, honouring a longer Retry-After). Any other 4xx or a redirect is treated as a permanent rejection and is not retried. Because an event can therefore arrive more than once, deduplicate on the edgaze-delivery-id header, which is stable across retries (or on the body's `runId` plus `type` when that header is absent). Endpoints are delivered in parallel and ordering is not guaranteed. An endpoint is paused after 15 consecutive deliveries that exhaust every attempt, until it is re-enabled in the developer console. Return 2xx as soon as you have persisted the payload, and do the real work asynchronously.",
        "parameters": [
          {
            "name": "edgaze-signature",
            "in": "header",
            "required": true,
            "description": "Signature of this delivery, formatted `t=<unix seconds>,v1=<hex hmac>`. Signed with HMAC-SHA256 over the exact string `{t}.{raw request body}`, using your endpoint secret. Compare in constant time against the raw bytes you received, before JSON parsing: re-serializing the body changes the signature. Reject any delivery whose `t` is more than 300 seconds from your clock, otherwise a captured payload can be replayed indefinitely.",
            "schema": {
              "type": "string",
              "examples": ["t=1800000000,v1=5257a869e7…"]
            }
          },
          {
            "name": "edgaze-webhook-id",
            "in": "header",
            "required": true,
            "description": "UUID of the endpoint that this delivery was sent to. Use it to select the right secret when one receiver serves several endpoints.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "edgaze-delivery-id",
            "in": "header",
            "required": false,
            "description": "UUID of this delivery, repeated byte-for-byte on every retry of the same event. This is the deduplication key: record it once you have processed a delivery and return 2xx without reprocessing if it arrives again. Absent on a manual test delivery and on a rare single-attempt fallback delivery (sent when the delivery could not be queued); neither is retried. Deduplicate those on the body's `runId` plus `type`.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "edgaze-attempt",
            "in": "header",
            "required": true,
            "description": "1-indexed attempt number for this delivery. A value above 1 means an earlier attempt did not return 2xx.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "examples": [1]
            }
          },
          {
            "name": "edgaze-event",
            "in": "header",
            "required": true,
            "description": "Event type carried by the payload, so a receiver can route the delivery before parsing the body.",
            "schema": {
              "type": "string",
              "examples": ["run.completed"]
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/WebhookRunEventPayload"
              }
            }
          },
          "description": "Signed with HMAC-SHA256 over the exact string `{t}.{raw request body}`, using your endpoint secret. Compare in constant time against the raw bytes you received, before JSON parsing: re-serializing the body changes the signature. Reject any delivery whose `t` is more than 300 seconds from your clock, otherwise a captured payload can be replayed indefinitely."
        },
        "responses": {
          "200": {
            "description": "Acknowledge receipt. Any 2xx settles the delivery. A 5xx, 408, 425, 429, timeout, or connection error is retried with backoff; any other 4xx or a redirect permanently fails the delivery."
          }
        }
      }
    },
    "webhook.test": {
      "post": {
        "summary": "Manual test delivery from the developer console.",
        "description": "A synthetic payload triggered from the Edgaze dashboard to verify a receiver. It is never produced by a real run, so ignore it in production handlers. Unlike run events, a test delivery is a single attempt: it is never queued and never retried.",
        "parameters": [
          {
            "name": "edgaze-signature",
            "in": "header",
            "required": true,
            "description": "Signature of this delivery, formatted `t=<unix seconds>,v1=<hex hmac>`. Signed with HMAC-SHA256 over the exact string `{t}.{raw request body}`, using your endpoint secret. Compare in constant time against the raw bytes you received, before JSON parsing: re-serializing the body changes the signature. Reject any delivery whose `t` is more than 300 seconds from your clock, otherwise a captured payload can be replayed indefinitely.",
            "schema": {
              "type": "string",
              "examples": ["t=1800000000,v1=5257a869e7…"]
            }
          },
          {
            "name": "edgaze-webhook-id",
            "in": "header",
            "required": true,
            "description": "UUID of the endpoint that this delivery was sent to. Use it to select the right secret when one receiver serves several endpoints.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "edgaze-delivery-id",
            "in": "header",
            "required": false,
            "description": "UUID of this delivery, repeated byte-for-byte on every retry of the same event. This is the deduplication key: record it once you have processed a delivery and return 2xx without reprocessing if it arrives again. Absent on a manual test delivery and on a rare single-attempt fallback delivery (sent when the delivery could not be queued); neither is retried. Deduplicate those on the body's `runId` plus `type`.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "edgaze-attempt",
            "in": "header",
            "required": true,
            "description": "1-indexed attempt number for this delivery. A value above 1 means an earlier attempt did not return 2xx.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "examples": [1]
            }
          },
          {
            "name": "edgaze-event",
            "in": "header",
            "required": true,
            "description": "Event type carried by the payload, so a receiver can route the delivery before parsing the body.",
            "schema": {
              "type": "string",
              "examples": ["run.completed"]
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/WebhookTestPayload"
              }
            }
          },
          "description": "Signed with HMAC-SHA256 over the exact string `{t}.{raw request body}`, using your endpoint secret. Compare in constant time against the raw bytes you received, before JSON parsing: re-serializing the body changes the signature. Reject any delivery whose `t` is more than 300 seconds from your clock, otherwise a captured payload can be replayed indefinitely."
        },
        "responses": {
          "200": {
            "description": "Acknowledge receipt. Any 2xx settles the delivery. A 5xx, 408, 425, 429, timeout, or connection error is retried with backoff; any other 4xx or a redirect permanently fails the delivery."
          }
        }
      }
    }
  }
}
