{
  "openapi": "3.0.3",
  "info": {
    "title": "SOAT REST API",
    "version": "0.37.1",
    "description": "Complete REST surface of a SOAT deployment, merged from the per-module OpenAPI\nspecs that also generate the TypeScript SDK, the `soat` CLI and the MCP tool\nsurface. SOAT is self-hosted: point the `baseUrl` server variable at your own\ndeployment — this documentation site serves the description only, not the API.\n\nAuthentication is a bearer token: a user JWT from `POST /api/v1/users/login`,\nor a project key (`sk_…`). Request and response bodies are snake_case.\n\nEvery error response — including the ones a client is most tempted to\nspecial-case (401, 403, 429, 500) — is JSON of the same shape:\n`{ \"error\": { \"code\": \"RESOURCE_NOT_FOUND\", \"message\": \"…\", \"hint\": \"…\",\n\"docs_url\": \"…\", \"meta\": { … } } }`.\n`error.code` is stable and safe to branch on, and `error.hint` says what to do\nabout the failure; the full catalog of codes, their\nHTTP statuses and what they mean is in this document's `x-error-codes`\nextension and at https://soat.ttoss.dev/errors.json.",
    "license": {
      "name": "Apache-2.0",
      "url": "https://github.com/ttoss/soat/blob/main/LICENSE"
    },
    "contact": {
      "name": "SOAT on GitHub",
      "url": "https://github.com/ttoss/soat"
    }
  },
  "servers": [
    {
      "url": "{baseUrl}",
      "description": "Base URL of your SOAT deployment (e.g. https://soat.example.com or http://localhost:5047).",
      "variables": {
        "baseUrl": {
          "default": "http://localhost:5047",
          "description": "The base URL of your SOAT deployment."
        }
      }
    }
  ],
  "security": [
    {
      "bearerAuth": []
    }
  ],
  "tags": [
    {
      "name": "Activity",
      "description": "Read the autonomous-execution activity feed"
    },
    {
      "name": "Actors",
      "description": "Manage actors associated with projects"
    },
    {
      "name": "Agents",
      "description": "Manage AI agents"
    },
    {
      "name": "Agent Versions",
      "description": "Agent config history and staged rollout"
    },
    {
      "name": "Agent Traces",
      "description": "View agent traces"
    },
    {
      "name": "AI Providers",
      "description": "Manage AI providers"
    },
    {
      "name": "API Keys",
      "description": "Manage API keys"
    },
    {
      "name": "Approvals",
      "description": "Manage the human-decision approval queue"
    },
    {
      "name": "Audit Log",
      "description": "Query the append-only audit log"
    },
    {
      "name": "Chains",
      "description": "Inspect continuation chains and how large they have grown"
    },
    {
      "name": "Chats",
      "description": "Manage chats"
    },
    {
      "name": "Conversations",
      "description": "Manage conversations"
    },
    {
      "name": "Documents",
      "description": "Manage documents"
    },
    {
      "name": "Embeddings",
      "description": "Generate text embeddings"
    },
    {
      "name": "Evaluations",
      "description": "Datasets, evals, and eval runs"
    },
    {
      "name": "Exceptions",
      "description": "Triage the failure/anomaly exception queue"
    },
    {
      "name": "Files",
      "description": "Manage files"
    },
    {
      "name": "Formations",
      "description": "Manage declarative formation stacks"
    },
    {
      "name": "Generations",
      "description": "Inspect generation records"
    },
    {
      "name": "Guardrails",
      "description": "Manage guardrails"
    },
    {
      "name": "Ingestion Rules",
      "description": "Route content types to converter tools or agents during ingestion"
    },
    {
      "name": "Knowledge",
      "description": "Unified search across documents and knowledge sources"
    },
    {
      "name": "Memories",
      "description": "Manage memory configurations for document retrieval"
    },
    {
      "name": "Memory Entries",
      "description": "Manage individual memory entries (the actual knowledge items stored in a memory)"
    },
    {
      "name": "Model Routes",
      "description": "Manage ordered provider+model failover routes"
    },
    {
      "name": "OAuth",
      "description": "OAuth 2.1 discovery, dynamic client registration, and the authorization code flow"
    },
    {
      "name": "Orchestrations",
      "description": "Manage orchestrations and their runs"
    },
    {
      "name": "Policies",
      "description": "Manage policies"
    },
    {
      "name": "Projects",
      "description": "Manage projects"
    },
    {
      "name": "Quotas",
      "description": "Manage quotas and rate limits"
    },
    {
      "name": "Secrets",
      "description": "Manage secrets"
    },
    {
      "name": "Sessions",
      "description": "Manage agent sessions"
    },
    {
      "name": "Tasks",
      "description": "Manage tasks and their transitions"
    },
    {
      "name": "Tools",
      "description": "Manage tools"
    },
    {
      "name": "Traces",
      "description": "Inspect execution traces and trace trees"
    },
    {
      "name": "Triggers",
      "description": "Manage triggers and inspect firings"
    },
    {
      "name": "Usage",
      "description": "Inspect LLM token-usage meter rows"
    },
    {
      "name": "Users",
      "description": "Manage users"
    },
    {
      "name": "User Policies",
      "description": "Manage user policies"
    },
    {
      "name": "Webhooks",
      "description": "Manage webhooks"
    },
    {
      "name": "Workflows",
      "description": "Manage workflow definitions"
    }
  ],
  "paths": {
    "/api/v1/activity": {
      "get": {
        "tags": [
          "Activity"
        ],
        "summary": "List activity feed entries",
        "description": "Returns activity entries for a project, newest first, filterable by kind and severity. Paginated with an opaque cursor rather than offset/limit — pass the previous page's `next_cursor` to fetch the next one; a `null` `next_cursor` means there is no more data.",
        "operationId": "listActivity",
        "parameters": [
          {
            "name": "project_id",
            "in": "query",
            "description": "Project ID (required if not using project key auth)",
            "schema": {
              "type": "string",
              "example": "proj_V1StGXR8Z5jdHi6B"
            }
          },
          {
            "name": "kind",
            "in": "query",
            "description": "Filter by activity kind",
            "schema": {
              "type": "string",
              "enum": [
                "action_executed",
                "approval_resolved",
                "exception_created",
                "schedule_fired"
              ]
            }
          },
          {
            "name": "severity",
            "in": "query",
            "description": "Filter by severity",
            "schema": {
              "type": "string",
              "enum": [
                "info",
                "warning",
                "critical"
              ]
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "description": "Opaque cursor from a previous page's `next_cursor`",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Maximum number of results to return",
            "schema": {
              "type": "integer",
              "default": 50
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Page of activity entries",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "next_cursor"
                  ],
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/ActivityEntry"
                      }
                    },
                    "next_cursor": {
                      "type": "string",
                      "nullable": true,
                      "description": "Pass as `cursor` to fetch the next page; `null` when this is the last page"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Malformed cursor"
          },
          "401": {
            "description": "Unauthorized"
          },
          "403": {
            "description": "Forbidden"
          },
          "500": {
            "description": "Internal server error"
          }
        }
      }
    },
    "/api/v1/actors": {
      "get": {
        "tags": [
          "Actors"
        ],
        "summary": "List actors",
        "description": "Returns all actors the caller has access to. If projectId is provided, returns only actors in that project. project keys are scoped to a single project automatically. JWT users without projectId receive actors across all their accessible projects.",
        "operationId": "listActors",
        "parameters": [
          {
            "name": "project_id",
            "in": "query",
            "required": false,
            "description": "Project ID (optional)",
            "schema": {
              "type": "string",
              "example": "proj_V1StGXR8Z5jdHi6B"
            }
          },
          {
            "name": "external_id",
            "in": "query",
            "required": false,
            "description": "External ID to filter by (e.g. WhatsApp phone number)",
            "schema": {
              "type": "string",
              "example": "+15551234567"
            }
          },
          {
            "name": "agent_id",
            "in": "query",
            "required": false,
            "description": "Return only actors linked to this agent",
            "schema": {
              "type": "string",
              "example": "agent_V1StGXR8Z5jdHi6B"
            }
          },
          {
            "name": "chat_id",
            "in": "query",
            "required": false,
            "description": "Return only actors linked to this chat",
            "schema": {
              "type": "string",
              "example": "chat_V1StGXR8Z5jdHi6B"
            }
          },
          {
            "name": "conversation_id",
            "in": "query",
            "required": false,
            "description": "Return only actors that participate in this conversation (derived from the conversation's messages).\n",
            "schema": {
              "type": "string",
              "example": "conv_V1StGXR8Z5jdHi6B"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Maximum number of results to return",
            "schema": {
              "type": "integer",
              "default": 50
            }
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "description": "Number of results to skip",
            "schema": {
              "type": "integer",
              "default": 0
            }
          }
        ],
        "responses": {
          "200": {
            "description": "List of actors",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/ActorRecord"
                      }
                    },
                    "total": {
                      "type": "integer"
                    },
                    "limit": {
                      "type": "integer"
                    },
                    "offset": {
                      "type": "integer"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      },
      "post": {
        "tags": [
          "Actors"
        ],
        "summary": "Create an actor",
        "description": "Creates a new actor. project keys automatically infer the project from the key's scope; JWT callers must supply projectId.",
        "operationId": "createActor",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "name"
                ],
                "properties": {
                  "project_id": {
                    "x-soat-ref": "projects",
                    "type": "string",
                    "description": "Project ID. Required for JWT auth; omit when using an project key.",
                    "example": "proj_V1StGXR8Z5jdHi6B"
                  },
                  "name": {
                    "type": "string",
                    "example": "Alice"
                  },
                  "external_id": {
                    "type": "string",
                    "description": "Optional external identifier (e.g. WhatsApp phone number). If provided and an actor with this externalId already exists in the project, the existing actor is returned (idempotent — 200 OK).",
                    "example": "+15551234567"
                  },
                  "instructions": {
                    "type": "string",
                    "nullable": true,
                    "description": "Persona-specific instructions composed into the effective system prompt during conversation generation."
                  },
                  "agent_id": {
                    "x-soat-ref": "agents",
                    "type": "string",
                    "description": "Agent to link this actor to. Mutually exclusive with chat_id.",
                    "example": "agent_V1StGXR8Z5jdHi6B"
                  },
                  "chat_id": {
                    "x-soat-ref": "chats",
                    "type": "string",
                    "description": "Chat to link this actor to. Mutually exclusive with agent_id.",
                    "example": "chat_V1StGXR8Z5jdHi6B"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Actor already exists — returned when externalId matches an existing actor in this project (idempotent)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ActorRecord"
                }
              }
            }
          },
          "201": {
            "description": "Actor created",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ActorRecord"
                }
              }
            }
          },
          "400": {
            "description": "Invalid request body",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/actors/{actor_id}": {
      "get": {
        "tags": [
          "Actors"
        ],
        "summary": "Get an actor by ID",
        "description": "Returns an actor by its ID",
        "operationId": "getActor",
        "parameters": [
          {
            "name": "actor_id",
            "in": "path",
            "required": true,
            "description": "Actor ID",
            "schema": {
              "type": "string",
              "example": "actor_V1StGXR8Z5jdHi6B"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Actor found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ActorRecord"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Actor not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      },
      "delete": {
        "tags": [
          "Actors"
        ],
        "summary": "Delete an actor",
        "description": "Deletes an actor by its ID",
        "operationId": "deleteActor",
        "parameters": [
          {
            "name": "actor_id",
            "in": "path",
            "required": true,
            "description": "Actor ID",
            "schema": {
              "type": "string",
              "example": "actor_V1StGXR8Z5jdHi6B"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "Actor deleted"
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Actor not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      },
      "patch": {
        "tags": [
          "Actors"
        ],
        "summary": "Update an actor",
        "description": "Updates an actor's properties",
        "operationId": "updateActor",
        "parameters": [
          {
            "name": "actor_id",
            "in": "path",
            "required": true,
            "description": "Actor ID",
            "schema": {
              "type": "string",
              "example": "actor_V1StGXR8Z5jdHi6B"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string",
                    "example": "Alice Smith"
                  },
                  "external_id": {
                    "type": "string",
                    "description": "External identifier (e.g. WhatsApp phone number)",
                    "example": "+15551234567"
                  },
                  "instructions": {
                    "type": "string",
                    "description": "Persona-specific instructions"
                  },
                  "agent_id": {
                    "x-soat-ref": "agents",
                    "type": "string",
                    "nullable": true,
                    "description": "Agent to link this actor to. Mutually exclusive with chat_id."
                  },
                  "chat_id": {
                    "x-soat-ref": "chats",
                    "type": "string",
                    "nullable": true,
                    "description": "Chat to link this actor to. Mutually exclusive with agent_id."
                  },
                  "tags": {
                    "type": "object",
                    "additionalProperties": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Actor updated",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ActorRecord"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Actor not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/actors/{actor_id}/tags": {
      "get": {
        "tags": [
          "Actors"
        ],
        "summary": "Get actor tags",
        "description": "Returns all tags attached to the actor",
        "operationId": "getActorTags",
        "parameters": [
          {
            "name": "actor_id",
            "in": "path",
            "required": true,
            "description": "Actor ID",
            "schema": {
              "type": "string",
              "example": "actor_V1StGXR8Z5jdHi6B"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Actor tags",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": {
                    "type": "string"
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Actor not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      },
      "put": {
        "tags": [
          "Actors"
        ],
        "summary": "Replace actor tags",
        "description": "Replaces all tags on the actor with the provided tags (not merged)",
        "operationId": "replaceActorTags",
        "parameters": [
          {
            "name": "actor_id",
            "in": "path",
            "required": true,
            "description": "Actor ID",
            "schema": {
              "type": "string",
              "example": "actor_V1StGXR8Z5jdHi6B"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": {
                  "type": "string"
                },
                "example": {
                  "type": "customer",
                  "status": "active"
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Tags replaced",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": {
                    "type": "string"
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Actor not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      },
      "patch": {
        "tags": [
          "Actors"
        ],
        "summary": "Merge actor tags",
        "description": "Merges provided tags with existing tags (existing tags are preserved unless overridden)",
        "operationId": "mergeActorTags",
        "parameters": [
          {
            "name": "actor_id",
            "in": "path",
            "required": true,
            "description": "Actor ID",
            "schema": {
              "type": "string",
              "example": "actor_V1StGXR8Z5jdHi6B"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": {
                  "type": "string"
                },
                "example": {
                  "priority": "high"
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Tags merged",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": {
                    "type": "string"
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Actor not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/agents": {
      "post": {
        "tags": [
          "Agents"
        ],
        "summary": "Create an agent",
        "description": "Creates a new agent bound to an AI provider.",
        "operationId": "createAgent",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateAgentRequest"
              },
              "examples": {
                "minimal": {
                  "summary": "Minimal agent",
                  "value": {
                    "ai_provider_id": "aip_V1StGXR8Z5jdHi6B"
                  }
                },
                "full": {
                  "summary": "Agent with tools and instructions",
                  "value": {
                    "ai_provider_id": "aip_V1StGXR8Z5jdHi6B",
                    "name": "Research Assistant",
                    "instructions": "You are a helpful research assistant.",
                    "model": "gpt-4o",
                    "tool_bindings": [
                      {
                        "tool_id": "tool_abc123"
                      }
                    ],
                    "max_steps": 10,
                    "temperature": 0.7
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Agent created",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Agent"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "AI provider not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      },
      "get": {
        "tags": [
          "Agents"
        ],
        "summary": "List agents",
        "description": "Returns all agents in the project.",
        "operationId": "listAgents",
        "parameters": [
          {
            "name": "project_id",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Project public ID to filter by"
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Maximum number of results to return",
            "schema": {
              "type": "integer",
              "default": 50
            }
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "description": "Number of results to skip",
            "schema": {
              "type": "integer",
              "default": 0
            }
          }
        ],
        "responses": {
          "200": {
            "description": "List of agents",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "total",
                    "limit",
                    "offset"
                  ],
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Agent"
                      }
                    },
                    "total": {
                      "type": "integer"
                    },
                    "limit": {
                      "type": "integer"
                    },
                    "offset": {
                      "type": "integer"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/agents/{agent_id}": {
      "get": {
        "tags": [
          "Agents"
        ],
        "summary": "Get an agent",
        "description": "Returns a single agent by ID.",
        "operationId": "getAgent",
        "parameters": [
          {
            "name": "agent_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Agent details",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Agent"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      },
      "put": {
        "tags": [
          "Agents"
        ],
        "summary": "Update an agent",
        "description": "Updates an existing agent. Identical to PATCH — both perform partial updates.",
        "operationId": "updateAgent",
        "parameters": [
          {
            "name": "agent_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateAgentRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Agent updated",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Agent"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      },
      "patch": {
        "tags": [
          "Agents"
        ],
        "summary": "Partially update an agent",
        "description": "Partially updates an existing agent. Identical to PUT — both perform partial updates.",
        "operationId": "patchAgent",
        "parameters": [
          {
            "name": "agent_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateAgentRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Agent updated",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Agent"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      },
      "delete": {
        "tags": [
          "Agents"
        ],
        "summary": "Delete an agent",
        "description": "Deletes an agent by ID. Fails with `409` if the agent has dependent generations or traces, unless `force=true` is passed, in which case those generations and traces are deleted along with the agent.\n",
        "operationId": "deleteAgent",
        "parameters": [
          {
            "name": "agent_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "force",
            "in": "query",
            "required": false,
            "description": "When `true`, deletes the agent's dependent generations and traces instead of returning `409 AGENT_HAS_DEPENDENTS`.\n",
            "schema": {
              "type": "boolean",
              "default": false
            }
          }
        ],
        "responses": {
          "204": {
            "description": "Deleted"
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "409": {
            "description": "Agent has dependent generations or traces (pass `force=true` to delete anyway). `error.meta` carries `generation_count` and `trace_count` so a caller can tell which one is nonzero.\n",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/agents/{agent_id}/generate": {
      "post": {
        "tags": [
          "Agents"
        ],
        "summary": "Run an agent generation",
        "description": "Sends messages to the agent, resolves its tools, and runs the AI model loop. Background by default: returns `202 Accepted` with a `generation_id` to poll via `GET /api/v1/generations/{generation_id}`. Pass `?wait=true` to block and receive the result inline, where client tools pause the generation and return `requires_action`. Streaming (`stream: true`) implies waiting.\n",
        "operationId": "createAgentGeneration",
        "parameters": [
          {
            "name": "agent_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "wait",
            "in": "query",
            "required": false,
            "x-soat-tool-forced": "true",
            "description": "When omitted or `false` (default), the generation runs in the background and `202 Accepted` is returned immediately with a `generation_id` to poll. Pass `true` to block until the generation settles and receive the result. Mutually exclusive with `stream: true`. A `builtin` tool call always waits.",
            "schema": {
              "type": "boolean",
              "default": false
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateAgentGenerationRequest"
              },
              "examples": {
                "basic": {
                  "summary": "Simple generation",
                  "value": {
                    "messages": [
                      {
                        "role": "user",
                        "content": "What is the weather in Tokyo?"
                      }
                    ]
                  }
                },
                "toolOutput": {
                  "summary": "Use a tool output as user message content",
                  "value": {
                    "messages": [
                      {
                        "role": "user",
                        "content": {
                          "type": "tool_output",
                          "tool_id": "tool_audio_to_text",
                          "input": {
                            "url": "https://example.com/audio.mp3"
                          },
                          "output_path": "text"
                        }
                      }
                    ]
                  }
                },
                "streaming": {
                  "summary": "Streaming generation",
                  "value": {
                    "messages": [
                      {
                        "role": "user",
                        "content": "Summarize the latest report."
                      }
                    ],
                    "stream": true
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Generation result or SSE stream (only when `?wait=true` or `stream: true`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AgentGenerationResponse"
                }
              },
              "text/event-stream": {
                "schema": {
                  "type": "string",
                  "description": "SSE stream of delta chunks ending with `data: [DONE]`.\n\nThe response headers are written before the provider is called, so a failure cannot become a status code once the stream is open. It arrives instead as a terminal `data: {\"error\": \"...\"}` frame carrying the same mapped message the non-streaming path returns in its `502` body (e.g. `Provider returned 404: ...`), and the stream then ends **without** a `[DONE]` — the absence of that sentinel is how a caller tells a truncated answer from a complete one. Chunks produced before the failure are still delivered, and the generation is recorded as `failed`.\n"
                }
              }
            }
          },
          "202": {
            "description": "Generation accepted and running in the background (default, when `wait` is omitted or `false`). Poll `GET /api/v1/generations/{generation_id}` for the result.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AcceptedGenerationResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Agent or AI provider not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "502": {
            "description": "Upstream AI provider error (AI_PROVIDER_ERROR); model output that does not satisfy the agent's `output_schema` (OUTPUT_SCHEMA_VALIDATION_FAILED — the violated field is named in the message); or a model that wrote a tool invocation out as plain assistant text instead of calling the tool, so the tool never ran (TEXT_ENCODED_TOOL_CALL — `meta.tool_name` names the tool). The error `meta` includes the `generation_id` and `trace_id` of the failed generation for post-mortem debugging via GET /api/v1/generations/{generation_id}. Streaming requests report the provider error in a terminal SSE frame instead, since their status line is already on the wire.\n",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/agents/{agent_id}/generate/{generation_id}/tool-outputs": {
      "post": {
        "tags": [
          "Agents"
        ],
        "summary": "Submit tool outputs for a paused generation",
        "description": "Resumes a generation that was paused due to client tool calls. Provide tool outputs for each pending tool call.\n",
        "operationId": "submitAgentToolOutputs",
        "parameters": [
          {
            "name": "agent_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "generation_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SubmitToolOutputsRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Generation result after resuming",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AgentGenerationResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Agent or generation not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "502": {
            "description": "Upstream AI provider error (AI_PROVIDER_ERROR); model output that does not satisfy the agent's `output_schema` (OUTPUT_SCHEMA_VALIDATION_FAILED); or a model that wrote a tool invocation out as plain assistant text instead of calling the tool (TEXT_ENCODED_TOOL_CALL — `meta.tool_name` names the tool). The resumed generation is recorded `failed`.\n",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/agents/{agent_id}/versions": {
      "get": {
        "tags": [
          "Agent Versions"
        ],
        "summary": "List an agent's config versions",
        "description": "Returns the agent's archived configurations, newest first. A version is written on create and on every subsequent write that changes the config — through the REST API or a formation apply alike. See [Versioning and Staged Rollout](/docs/modules/agents#versioning-and-staged-rollout).\n",
        "operationId": "listAgentVersions",
        "parameters": [
          {
            "name": "agent_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Maximum number of results to return",
            "schema": {
              "type": "integer",
              "default": 50
            }
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "description": "Number of results to skip",
            "schema": {
              "type": "integer",
              "default": 0
            }
          }
        ],
        "responses": {
          "200": {
            "description": "List of agent versions, newest first",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "total",
                    "limit",
                    "offset"
                  ],
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/AgentVersion"
                      }
                    },
                    "total": {
                      "type": "integer"
                    },
                    "limit": {
                      "type": "integer"
                    },
                    "offset": {
                      "type": "integer"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Agent not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/agents/{agent_id}/versions/{version}": {
      "get": {
        "tags": [
          "Agent Versions"
        ],
        "summary": "Get an archived agent config version",
        "description": "Returns the exact configuration the agent held at a given version, so a generation can be traced back to the config that produced it.\n",
        "operationId": "getAgentVersion",
        "parameters": [
          {
            "name": "agent_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "version",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer",
              "minimum": 1
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Archived agent version",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AgentVersion"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request — version is not a positive integer",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Agent or version not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/agents/{agent_id}/versions/{version}/restore": {
      "post": {
        "tags": [
          "Agent Versions"
        ],
        "summary": "Restore an archived config as a new version",
        "description": "Copies the named version's configuration onto the agent as a **new** version rather than rewinding the counter, so history stays append-only and the versions in between remain retrievable. Restoring the config the agent already holds is a no-op and creates no version.\n\nThe restored config fully replaces the current one: a field the archived version did not set is cleared, not merged. Restore re-validates the config, so a tool, provider, or guardrail deleted since the snapshot was taken fails the request instead of writing a broken agent.\n",
        "operationId": "restoreAgentVersion",
        "parameters": [
          {
            "name": "agent_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "version",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer",
              "minimum": 1
            }
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/RestoreAgentVersionRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The agent, at its new version",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Agent"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request — invalid version, or the archived config no longer validates",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Agent or version not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/agents/{agent_id}/release": {
      "put": {
        "tags": [
          "Agent Versions"
        ],
        "summary": "Set or replace a staged rollout",
        "description": "Starts serving two archived versions side by side: `canary_percent` of traffic gets `canary_version`, the rest gets `stable_version`.\n\nAssignment is deterministic — it hashes the actor behind the request's session (falling back to the session itself), so one end user never flip-flops between configs mid-conversation. Requests with neither are split randomly.\n\nWhile a release is active the agent's live columns act as a **draft**: further edits archive new versions but do not disturb either side of the running split. End the rollout with `promote` or `abort`.\n",
        "operationId": "setAgentRelease",
        "parameters": [
          {
            "name": "agent_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SetAgentReleaseRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The agent, with its active release set",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Agent"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request — malformed input, or a version that does not exist",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Agent not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/agents/{agent_id}/release/promote": {
      "post": {
        "tags": [
          "Agent Versions"
        ],
        "summary": "Promote the canary and end the rollout",
        "description": "Makes the canary version's config the agent's live config and clears the release. The canary is pinned by version, so an edit that landed mid-rollout is not promoted in its place — it stays an unreleased draft in the version history.\n\nWhen the release carries a `promotion_gate`, the eval it names must have a run that finished `completed` with `passed: true` **and** was pinned to the canary version (`agent_version`); otherwise the call is a `409` and the rollout is left running untouched. The run that cleared the gate is recorded as `eval_run_id` on the version that goes live.\n",
        "operationId": "promoteAgentRelease",
        "parameters": [
          {
            "name": "agent_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The agent, now serving the promoted config",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Agent"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Agent not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "409": {
            "description": "Conflict — the agent has no active release (`NO_ACTIVE_RELEASE`), or its `promotion_gate` has no passing eval run against the canary version (`PROMOTION_GATE_UNMET`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/agents/{agent_id}/release/abort": {
      "post": {
        "tags": [
          "Agent Versions"
        ],
        "summary": "Abort the rollout and roll back to stable",
        "description": "Restores the stable version's config as the agent's live config and clears the release, so all traffic returns to the configuration the rollout was measured against — not to whatever draft the live columns happened to hold.\n",
        "operationId": "abortAgentRelease",
        "parameters": [
          {
            "name": "agent_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The agent, back on the stable config",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Agent"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Agent not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "409": {
            "description": "Conflict — the agent has no active release",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/ai-providers": {
      "get": {
        "tags": [
          "AI Providers"
        ],
        "summary": "List AI providers",
        "description": "Returns a list of AI provider configurations for a project",
        "operationId": "listAiProviders",
        "parameters": [
          {
            "name": "project_id",
            "in": "query",
            "description": "Project ID (required if not using project key auth)",
            "schema": {
              "type": "string",
              "example": "proj_V1StGXR8Z5jdHi6B"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "description": "Number of results per page",
            "schema": {
              "type": "integer",
              "default": 25
            }
          },
          {
            "name": "offset",
            "in": "query",
            "description": "Number of results to skip",
            "schema": {
              "type": "integer",
              "default": 0
            }
          }
        ],
        "responses": {
          "200": {
            "description": "List of AI providers",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "total",
                    "limit",
                    "offset"
                  ],
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "string"
                          },
                          "name": {
                            "type": "string"
                          },
                          "provider": {
                            "type": "string",
                            "enum": [
                              "openai",
                              "anthropic",
                              "google",
                              "xai",
                              "groq",
                              "ollama",
                              "azure",
                              "bedrock",
                              "vertex",
                              "gateway",
                              "custom"
                            ]
                          },
                          "default_model": {
                            "type": "string"
                          },
                          "secret_id": {
                            "x-soat-ref": "secrets",
                            "type": "string",
                            "nullable": true,
                            "description": "Secret ID containing API credentials, or null when the record links none."
                          },
                          "base_url": {
                            "type": "string",
                            "description": "Custom base URL for the provider. Absent when the record sets none."
                          },
                          "config": {
                            "type": "object",
                            "description": "Additional provider-specific configuration. Absent when the record sets none."
                          },
                          "project_id": {
                            "x-soat-ref": "projects",
                            "type": "string"
                          },
                          "created_at": {
                            "type": "string",
                            "format": "date-time"
                          },
                          "updated_at": {
                            "type": "string",
                            "format": "date-time"
                          }
                        }
                      }
                    },
                    "total": {
                      "type": "integer"
                    },
                    "limit": {
                      "type": "integer"
                    },
                    "offset": {
                      "type": "integer"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized"
          },
          "403": {
            "description": "Forbidden"
          },
          "500": {
            "description": "Internal server error"
          }
        }
      },
      "post": {
        "tags": [
          "AI Providers"
        ],
        "summary": "Create an AI provider",
        "description": "Creates a new LLM provider configuration",
        "operationId": "createAiProvider",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "name",
                  "provider",
                  "default_model"
                ],
                "properties": {
                  "project_id": {
                    "x-soat-ref": "projects",
                    "type": "string",
                    "description": "Project ID (required if not using project key auth)",
                    "example": "proj_V1StGXR8Z5jdHi6B"
                  },
                  "name": {
                    "type": "string",
                    "description": "Provider configuration name",
                    "example": "OpenAI Production"
                  },
                  "provider": {
                    "type": "string",
                    "enum": [
                      "openai",
                      "anthropic",
                      "google",
                      "xai",
                      "groq",
                      "ollama",
                      "azure",
                      "bedrock",
                      "vertex",
                      "gateway",
                      "custom"
                    ],
                    "description": "LLM provider",
                    "example": "openai"
                  },
                  "default_model": {
                    "type": "string",
                    "description": "Default model to use",
                    "example": "gpt-4"
                  },
                  "secret_id": {
                    "x-soat-ref": "secrets",
                    "type": "string",
                    "description": "Secret ID containing API credentials",
                    "example": "sec_V1StGXR8Z5jdHi6B"
                  },
                  "base_url": {
                    "type": "string",
                    "description": "Custom base URL for the provider"
                  },
                  "config": {
                    "type": "object",
                    "description": "Additional provider-specific configuration"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "AI provider created successfully",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string"
                    },
                    "name": {
                      "type": "string"
                    },
                    "provider": {
                      "type": "string"
                    },
                    "default_model": {
                      "type": "string"
                    },
                    "project_id": {
                      "x-soat-ref": "projects",
                      "type": "string"
                    },
                    "created_at": {
                      "type": "string",
                      "format": "date-time"
                    },
                    "updated_at": {
                      "type": "string",
                      "format": "date-time"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Bad request (invalid provider or missing fields)"
          },
          "401": {
            "description": "Unauthorized"
          },
          "403": {
            "description": "Forbidden"
          },
          "500": {
            "description": "Internal server error"
          }
        }
      }
    },
    "/api/v1/ai-providers/{ai_provider_id}": {
      "get": {
        "tags": [
          "AI Providers"
        ],
        "summary": "Get an AI provider",
        "description": "Returns a specific AI provider configuration",
        "operationId": "getAiProvider",
        "parameters": [
          {
            "name": "ai_provider_id",
            "in": "path",
            "required": true,
            "description": "AI Provider ID",
            "schema": {
              "type": "string",
              "example": "aip_V1StGXR8Z5jdHi6B"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "AI provider details",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string"
                    },
                    "name": {
                      "type": "string"
                    },
                    "provider": {
                      "type": "string"
                    },
                    "default_model": {
                      "type": "string"
                    },
                    "project_id": {
                      "x-soat-ref": "projects",
                      "type": "string"
                    },
                    "secret_id": {
                      "x-soat-ref": "secrets",
                      "type": "string",
                      "nullable": true
                    },
                    "base_url": {
                      "type": "string"
                    },
                    "config": {
                      "type": "object"
                    },
                    "created_at": {
                      "type": "string",
                      "format": "date-time"
                    },
                    "updated_at": {
                      "type": "string",
                      "format": "date-time"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized"
          },
          "403": {
            "description": "Forbidden"
          },
          "404": {
            "description": "AI provider not found"
          }
        }
      },
      "patch": {
        "tags": [
          "AI Providers"
        ],
        "summary": "Update an AI provider",
        "description": "Updates an AI provider configuration",
        "operationId": "updateAiProvider",
        "parameters": [
          {
            "name": "ai_provider_id",
            "in": "path",
            "required": true,
            "description": "AI Provider ID",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string"
                  },
                  "provider": {
                    "type": "string",
                    "enum": [
                      "openai",
                      "anthropic",
                      "google",
                      "xai",
                      "groq",
                      "ollama",
                      "azure",
                      "bedrock",
                      "vertex",
                      "gateway",
                      "custom"
                    ],
                    "description": "LLM provider",
                    "example": "openai"
                  },
                  "default_model": {
                    "type": "string"
                  },
                  "secret_id": {
                    "x-soat-ref": "secrets",
                    "type": "string",
                    "nullable": true
                  },
                  "base_url": {
                    "type": "string"
                  },
                  "config": {
                    "type": "object"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "AI provider updated successfully"
          },
          "400": {
            "description": "Bad request"
          },
          "401": {
            "description": "Unauthorized"
          },
          "403": {
            "description": "Forbidden"
          },
          "404": {
            "description": "AI provider not found"
          }
        }
      },
      "delete": {
        "tags": [
          "AI Providers"
        ],
        "summary": "Delete an AI provider",
        "description": "Deletes an AI provider configuration.\n\nLive references — chats, agents, and model routes whose targets name this provider — always block deletion with `409 AI_PROVIDER_HAS_DEPENDENTS`; `force` does not override them, so delete or repoint those resources first. Soft dependents — price overrides and usage/generation records — also block with `409` unless `force=true`, which deletes the provider's price overrides and unlinks (nulls) its usage history, preserving those rows. The `409` body's `error.meta` reports the counts, a sample of offending IDs, and a `forcible` flag that is `true` when a `force=true` retry would succeed.\n",
        "operationId": "deleteAiProvider",
        "parameters": [
          {
            "name": "ai_provider_id",
            "in": "path",
            "required": true,
            "description": "AI Provider ID",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "force",
            "in": "query",
            "required": false,
            "description": "When `true`, delete the provider's price overrides and unlink its usage history so a provider with only soft dependents can be removed. Has no effect on live references (chats, agents, model routes), which always block deletion.\n",
            "schema": {
              "type": "boolean",
              "default": false
            }
          }
        ],
        "responses": {
          "204": {
            "description": "AI provider deleted successfully"
          },
          "401": {
            "description": "Unauthorized"
          },
          "403": {
            "description": "Forbidden"
          },
          "404": {
            "description": "AI provider not found"
          },
          "409": {
            "description": "The AI provider still has dependents. Live references always block; soft dependents block unless force=true. See error.meta for counts, offending IDs, and the forcible flag.\n"
          }
        }
      }
    },
    "/api/v1/ai-providers/{ai_provider_id}/models": {
      "get": {
        "tags": [
          "AI Providers"
        ],
        "summary": "List the models this provider can run",
        "description": "Asks the provider which models it can run, using this provider record's own credentials and configuration, and returns provider-native model ids — the same strings `default_model` and an agent's `model` carry.\nWhich models are reachable is a property of the credential, not of the provider type: a Vertex provider sees only the publisher models its Google Cloud project and location serve, and a Bedrock provider only the foundation models enabled in its region. Reading the list is how a caller avoids pinning a model that fails at generation time.\nNot every provider type can answer. `azure` lists deployments an operator named rather than models, and `ollama` lists whatever was pulled onto that host, so both return `400 MODEL_LISTING_UNSUPPORTED`.\nListing resolves credentials the same way generation does, so a record that can generate can list. The API-key types (`openai`, `groq`, `xai`, `gateway`, `custom`, `anthropic`, `google`) use the record's linked secret and cannot list without one. `bedrock` and `vertex` use the linked secret when there is one — IAM keys or a Bedrock API key, a Google service-account key — and otherwise fall back to the server environment (the AWS default credential chain, Google Application Default Credentials), so a record with no `secret_id` can still list.\nA Vertex record needs no `config.project` when its secret is a service-account key, since the key file names its own project. A Vertex record in express mode (API key) cannot list at all: the publisher-model listing rejects API keys and needs a credential that asserts a principal, so it returns `400 MODEL_LISTING_UNSUPPORTED`.\nThe Vertex answer is the publisher catalogue the record's `config.location` region serves. The project behind the credential is billed and quota'd for the call but does not filter the result, so a listed model may still be unavailable to that project at generation time.\n",
        "operationId": "listAiProviderModels",
        "parameters": [
          {
            "name": "ai_provider_id",
            "in": "path",
            "required": true,
            "description": "AI Provider ID",
            "schema": {
              "type": "string",
              "example": "aip_V1StGXR8Z5jdHi6B"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The models this provider can run",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProviderModelsResponse"
                }
              }
            }
          },
          "400": {
            "description": "The provider type or authentication mode cannot enumerate models (including Vertex express mode), or the record is missing configuration the listing needs (a Vertex project from either `config.project` or the service-account key file, a Bedrock region, or — for the API-key provider types — a linked secret).\n"
          },
          "401": {
            "description": "Unauthorized"
          },
          "403": {
            "description": "Forbidden"
          },
          "404": {
            "description": "AI provider not found"
          },
          "502": {
            "description": "The provider rejected the listing request"
          }
        }
      }
    },
    "/api/v1/ai-providers/{ai_provider_id}/prices": {
      "get": {
        "tags": [
          "AI Providers"
        ],
        "summary": "List per-provider price overrides",
        "description": "Returns the per-provider price overrides for this AI provider instance. An override prices this specific provider (e.g. an enterprise-negotiated rate or a gateway with markup) and wins over the global default at cost time. Authorized by the caller's access to the provider's project — so, unlike the global price book, a project's own overrides are visible here.\n",
        "operationId": "getAiProviderPrices",
        "parameters": [
          {
            "name": "ai_provider_id",
            "in": "path",
            "required": true,
            "description": "AI Provider ID",
            "schema": {
              "type": "string",
              "example": "aip_V1StGXR8Z5jdHi6B"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The provider's price overrides",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProviderPricesResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized"
          },
          "403": {
            "description": "Forbidden"
          },
          "404": {
            "description": "AI provider not found"
          }
        }
      },
      "put": {
        "tags": [
          "AI Providers"
        ],
        "summary": "Upsert per-provider price overrides",
        "description": "Upserts price overrides for this AI provider instance, keyed on (model, effective_from). The provider slug is taken from the AI provider itself, so only the model, rates, and effective_from are supplied. Authorized by the caller's access to the provider's project. `effective_from` must be in the future once the (model, component) has a price row — past prices are immutable, so corrections ship as new future-dated rows. A first price for a (model, component) nothing prices yet may be dated now or earlier, so a new provider is never live and unpriced.\n",
        "operationId": "updateAiProviderPrices",
        "parameters": [
          {
            "name": "ai_provider_id",
            "in": "path",
            "required": true,
            "description": "AI Provider ID",
            "schema": {
              "type": "string",
              "example": "aip_V1StGXR8Z5jdHi6B"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpsertProviderPricesRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The upserted price overrides",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProviderPricesResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request (e.g. non-future effective_from)"
          },
          "401": {
            "description": "Unauthorized"
          },
          "403": {
            "description": "Forbidden"
          },
          "404": {
            "description": "AI provider not found"
          }
        }
      }
    },
    "/api/v1/api-keys": {
      "get": {
        "tags": [
          "API Keys"
        ],
        "summary": "List API keys",
        "description": "Lists API keys accessible to the caller. - JWT admin: returns all API keys. - JWT regular user: returns only the user's own API keys. - Project-scoped credential (API key or OAuth token): returns only API keys scoped to that project.\n",
        "operationId": "listApiKeys",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Maximum number of results to return",
            "schema": {
              "type": "integer",
              "default": 50
            }
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "description": "Number of results to skip",
            "schema": {
              "type": "integer",
              "default": 0
            }
          }
        ],
        "responses": {
          "200": {
            "description": "List of API keys",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "total",
                    "limit",
                    "offset"
                  ],
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/ApiKeyRecord"
                      }
                    },
                    "total": {
                      "type": "integer"
                    },
                    "limit": {
                      "type": "integer"
                    },
                    "offset": {
                      "type": "integer"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized"
          }
        }
      },
      "post": {
        "tags": [
          "API Keys"
        ],
        "summary": "Create an API key",
        "description": "Creates a new API key for the authenticated user. - `project_id` is optional. When set, the key is scoped to that single project. When omitted or null, the key is **unscoped** and spans every project its owner can reach. - If `policy_ids` is provided, the key's effective permissions are the intersection of the user's policies and the key's policies. - Otherwise the key inherits the user's permissions (confined to the key's project when scoped). - When the request is authenticated with a **project-scoped credential**, the new key is confined to that same project: omitting `project_id` defaults to it, naming a different project returns `403 API_KEY_PROJECT_SCOPE`, and `project_id: null` (an unscoped key) is likewise refused. Minting an unscoped key requires an unscoped credential.\n",
        "operationId": "createApiKey",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "name"
                ],
                "properties": {
                  "name": {
                    "type": "string",
                    "description": "Key name for identification",
                    "example": "CI/CD Pipeline"
                  },
                  "project_id": {
                    "type": "string",
                    "nullable": true,
                    "description": "Optional project ID to scope the key to. Omit or set null to create an unscoped key that spans projects.",
                    "example": "proj_V1StGXR8Z5jdHi6B",
                    "x-soat-ref": "projects"
                  },
                  "policy_ids": {
                    "x-soat-ref": "policies",
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "description": "Optional list of policy IDs to attach. Key permissions become the intersection of user policies and these policies.",
                    "example": [
                      "pol_V1StGXR8Z5jdHi6B"
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "API key created successfully. The raw key value is only returned once.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiKeyCreated"
                }
              }
            }
          },
          "400": {
            "description": "Bad request (missing name, invalid project or policy IDs)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized"
          },
          "403": {
            "description": "Forbidden (a project-scoped credential named a different project, or asked for an unscoped key)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/api-keys/{api_key_id}": {
      "get": {
        "tags": [
          "API Keys"
        ],
        "summary": "Get an API key",
        "description": "Returns details of an API key. Only the owner or an admin can access it, and a project-scoped credential can only reach keys in its own project.",
        "operationId": "getApiKey",
        "parameters": [
          {
            "name": "api_key_id",
            "in": "path",
            "required": true,
            "description": "API key public ID (key_ prefix)",
            "schema": {
              "type": "string",
              "example": "key_V1StGXR8Z5jdHi6B"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "API key details",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiKeyRecord"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized"
          },
          "403": {
            "description": "Forbidden (not the key owner or admin, or the credential is scoped to a different project)"
          },
          "404": {
            "description": "API key not found"
          }
        }
      },
      "put": {
        "tags": [
          "API Keys"
        ],
        "summary": "Update an API key",
        "description": "Updates an API key's name, project scope, or policies. The project scope can be changed to another project, set (scoping a previously unscoped key), or cleared with null (unscoping the key). Only the owner or an admin can update it. A project-scoped credential can only update keys in its own project, and cannot move a key to another project or unscope it.",
        "operationId": "updateApiKey",
        "parameters": [
          {
            "name": "api_key_id",
            "in": "path",
            "required": true,
            "description": "API key public ID (key_ prefix)",
            "schema": {
              "type": "string",
              "example": "key_V1StGXR8Z5jdHi6B"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string",
                    "example": "Updated Key Name"
                  },
                  "project_id": {
                    "x-soat-ref": "projects",
                    "type": "string",
                    "nullable": true,
                    "description": "Re-scope the key to a different project, or set null to clear the scope (unscoped key). Omit to leave the scope unchanged.",
                    "example": "proj_V1StGXR8Z5jdHi6B"
                  },
                  "policy_ids": {
                    "x-soat-ref": "policies",
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "description": "Replace the key's policy list (empty array removes all)",
                    "example": [
                      "pol_V1StGXR8Z5jdHi6B"
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "API key updated successfully",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiKeyRecord"
                }
              }
            }
          },
          "400": {
            "description": "Bad request (invalid project or policy IDs)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized"
          },
          "403": {
            "description": "Forbidden (not the key owner or admin, or the credential is scoped to a different project)"
          },
          "404": {
            "description": "API key not found"
          }
        }
      },
      "delete": {
        "tags": [
          "API Keys"
        ],
        "summary": "Delete an API key",
        "description": "Deletes an API key. Only the owner or an admin can delete it, and a project-scoped credential can only delete keys in its own project.",
        "operationId": "deleteApiKey",
        "parameters": [
          {
            "name": "api_key_id",
            "in": "path",
            "required": true,
            "description": "API key public ID (key_ prefix)",
            "schema": {
              "type": "string",
              "example": "key_V1StGXR8Z5jdHi6B"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "API key deleted successfully"
          },
          "401": {
            "description": "Unauthorized"
          },
          "403": {
            "description": "Forbidden (not the key owner or admin, or the credential is scoped to a different project)"
          },
          "404": {
            "description": "API key not found"
          }
        }
      }
    },
    "/api/v1/approvals": {
      "get": {
        "tags": [
          "Approvals"
        ],
        "summary": "List approval items",
        "description": "Returns approval items for a project, filterable by status, origin, and expiry.",
        "operationId": "listApprovals",
        "parameters": [
          {
            "name": "project_id",
            "in": "query",
            "description": "Project ID (required if not using project key auth)",
            "schema": {
              "type": "string",
              "example": "proj_V1StGXR8Z5jdHi6B"
            }
          },
          {
            "name": "status",
            "in": "query",
            "description": "Filter by lifecycle status",
            "schema": {
              "type": "string",
              "enum": [
                "pending",
                "approved",
                "rejected",
                "expired"
              ]
            }
          },
          {
            "name": "origin",
            "in": "query",
            "description": "Filter by producer origin",
            "schema": {
              "type": "string",
              "enum": [
                "node",
                "tool_call",
                "task_transition"
              ]
            }
          },
          {
            "name": "expires_before",
            "in": "query",
            "description": "Return only items expiring at or before this timestamp",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Maximum number of results to return",
            "schema": {
              "type": "integer",
              "default": 50
            }
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "description": "Number of results to skip",
            "schema": {
              "type": "integer",
              "default": 0
            }
          }
        ],
        "responses": {
          "200": {
            "description": "List of approval items",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "total",
                    "limit",
                    "offset"
                  ],
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/ApprovalItem"
                      }
                    },
                    "total": {
                      "type": "integer"
                    },
                    "limit": {
                      "type": "integer"
                    },
                    "offset": {
                      "type": "integer"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid `status` or `origin` filter value"
          },
          "401": {
            "description": "Unauthorized"
          },
          "403": {
            "description": "Forbidden"
          },
          "500": {
            "description": "Internal server error"
          }
        }
      }
    },
    "/api/v1/approvals/recurrences": {
      "get": {
        "tags": [
          "Approvals"
        ],
        "summary": "List recurring approval groups",
        "description": "Read-only rollup answering \"what keeps coming back?\" — groups items by `dedup_key` and returns those recurring at least `min_count` times, most-recurrent first. Each group carries the ordered item chain (via `previous_item_id`) and the resolution reasons in order, so a human can read recurring rejections side by side and graduate the pattern into a guardrail `deny`. Exact-key grouping only; no cluster state is stored.",
        "operationId": "listApprovalRecurrences",
        "parameters": [
          {
            "name": "project_id",
            "in": "query",
            "description": "Project ID (required if not using project key auth)",
            "schema": {
              "type": "string",
              "example": "proj_V1StGXR8Z5jdHi6B"
            }
          },
          {
            "name": "status",
            "in": "query",
            "description": "Lifecycle status the groups are built from (default `rejected`)",
            "schema": {
              "type": "string",
              "enum": [
                "pending",
                "approved",
                "rejected",
                "expired"
              ],
              "default": "rejected"
            }
          },
          {
            "name": "min_count",
            "in": "query",
            "description": "Minimum items in a group for it to be returned",
            "schema": {
              "type": "integer",
              "default": 2,
              "minimum": 1
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Maximum number of groups to return",
            "schema": {
              "type": "integer",
              "default": 50
            }
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "description": "Number of groups to skip",
            "schema": {
              "type": "integer",
              "default": 0
            }
          }
        ],
        "responses": {
          "200": {
            "description": "List of recurrence groups",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "total",
                    "limit",
                    "offset"
                  ],
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/ApprovalRecurrenceGroup"
                      }
                    },
                    "total": {
                      "type": "integer"
                    },
                    "limit": {
                      "type": "integer"
                    },
                    "offset": {
                      "type": "integer"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid `status` filter value"
          },
          "401": {
            "description": "Unauthorized"
          },
          "403": {
            "description": "Forbidden"
          },
          "500": {
            "description": "Internal server error"
          }
        }
      }
    },
    "/api/v1/approvals/{approval_id}": {
      "get": {
        "tags": [
          "Approvals"
        ],
        "summary": "Get an approval item",
        "description": "Returns a single approval item with its full evidence.",
        "operationId": "getApproval",
        "parameters": [
          {
            "$ref": "#/components/parameters/approval_id"
          }
        ],
        "responses": {
          "200": {
            "description": "Approval item",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApprovalItem"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized"
          },
          "403": {
            "description": "Forbidden"
          },
          "404": {
            "description": "Approval item not found"
          }
        }
      }
    },
    "/api/v1/approvals/{approval_id}/approve": {
      "post": {
        "tags": [
          "Approvals"
        ],
        "summary": "Approve an approval item",
        "description": "Approves the item. Optionally supply edited `arguments` to replace the proposed arguments (edit-then-approve); the original is preserved on the item. Expiry is re-checked at decision time — an expired item can never be approved.",
        "operationId": "approveApproval",
        "parameters": [
          {
            "$ref": "#/components/parameters/approval_id"
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "arguments": {
                    "type": "object",
                    "description": "Edited arguments to execute instead of the proposed ones"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Approval item approved",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApprovalItem"
                }
              }
            }
          },
          "400": {
            "description": "Edited arguments are not a JSON object"
          },
          "401": {
            "description": "Unauthorized"
          },
          "403": {
            "description": "Forbidden"
          },
          "404": {
            "description": "Approval item not found"
          },
          "409": {
            "description": "Item already resolved or expired"
          }
        }
      }
    },
    "/api/v1/approvals/{approval_id}/reject": {
      "post": {
        "tags": [
          "Approvals"
        ],
        "summary": "Reject an approval item",
        "description": "Rejects the item. A reason is required and preserved on the item.",
        "operationId": "rejectApproval",
        "parameters": [
          {
            "$ref": "#/components/parameters/approval_id"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "reason"
                ],
                "properties": {
                  "reason": {
                    "type": "string",
                    "description": "Why the item is being rejected (required)",
                    "example": "Amount exceeds the approved monthly budget."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Approval item rejected",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApprovalItem"
                }
              }
            }
          },
          "400": {
            "description": "Reason missing"
          },
          "401": {
            "description": "Unauthorized"
          },
          "403": {
            "description": "Forbidden"
          },
          "404": {
            "description": "Approval item not found"
          },
          "409": {
            "description": "Item already resolved or expired"
          }
        }
      }
    },
    "/api/v1/audit-log": {
      "get": {
        "tags": [
          "Audit Log"
        ],
        "summary": "List audit entries",
        "description": "Returns audit-log entries visible to the caller, newest first. All filters are optional and combine with AND. `resource_srn` is a prefix match (e.g. `srn:{project}:secret:` matches every secret action); every other filter is exact.",
        "operationId": "listAuditEntries",
        "parameters": [
          {
            "name": "project_id",
            "in": "query",
            "description": "Project ID (scopes results; required if not using project key auth for a specific project)",
            "schema": {
              "type": "string",
              "example": "proj_V1StGXR8Z5jdHi6B"
            }
          },
          {
            "name": "action",
            "in": "query",
            "description": "Exact permission-action string, e.g. `secrets:DeleteSecret`",
            "schema": {
              "type": "string",
              "example": "secrets:DeleteSecret"
            }
          },
          {
            "name": "principal_id",
            "in": "query",
            "description": "Public id of the principal (`user_…` or `key_…`)",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "resource_public_id",
            "in": "query",
            "description": "Exact target resource public id, e.g. `sec_…`",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "resource_srn",
            "in": "query",
            "description": "SRN prefix match, e.g. `srn:{project}:secret:`. The log is append-only, so a stored SRN is never rewritten; the filter matches it as stored.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "from",
            "in": "query",
            "description": "Only entries created at or after this timestamp (ISO 8601)",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "to",
            "in": "query",
            "description": "Only entries created at or before this timestamp (ISO 8601)",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "description": "Number of results per page (1–200, default 25)",
            "schema": {
              "type": "integer",
              "default": 25
            }
          },
          {
            "name": "offset",
            "in": "query",
            "description": "Number of results to skip",
            "schema": {
              "type": "integer",
              "default": 0
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A page of audit entries",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/AuditEntry"
                      }
                    },
                    "total": {
                      "type": "integer"
                    },
                    "limit": {
                      "type": "integer"
                    },
                    "offset": {
                      "type": "integer"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "`from` or `to` is present but not a valid ISO 8601 date"
          },
          "401": {
            "description": "Unauthorized"
          },
          "403": {
            "description": "Forbidden"
          },
          "500": {
            "description": "Internal server error"
          }
        }
      }
    },
    "/api/v1/audit-log/export": {
      "get": {
        "tags": [
          "Audit Log"
        ],
        "summary": "Export audit entries as NDJSON",
        "description": "Streams a project's audit-log entries as newline-delimited JSON — one entry object per line, oldest first — for archival before the retention window expires, or for shipping into an external system. `project_id` is required: the export is per-project by design. Filters behave exactly as they do on the list endpoint.",
        "operationId": "exportAuditEntries",
        "x-soat-mcp-exclude": true,
        "parameters": [
          {
            "name": "project_id",
            "in": "query",
            "required": true,
            "description": "Project whose entries are exported",
            "schema": {
              "type": "string",
              "example": "proj_V1StGXR8Z5jdHi6B"
            }
          },
          {
            "name": "action",
            "in": "query",
            "description": "Exact permission-action string, e.g. `secrets:DeleteSecret`",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "principal_id",
            "in": "query",
            "description": "Public id of the principal (`user_…` or `key_…`)",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "resource_public_id",
            "in": "query",
            "description": "Exact target resource public id, e.g. `sec_…`",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "resource_srn",
            "in": "query",
            "description": "SRN prefix match, e.g. `srn:{project}:secret:`. The log is append-only, so a stored SRN is never rewritten; the filter matches it as stored.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "from",
            "in": "query",
            "description": "Only entries created at or after this timestamp (ISO 8601)",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "to",
            "in": "query",
            "description": "Only entries created at or before this timestamp (ISO 8601)",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A newline-delimited stream of audit entries. Each line is a JSON object with the same fields as `AuditEntry`.",
            "content": {
              "application/x-ndjson": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "400": {
            "description": "`project_id` is required, or `from`/`to` is present but not a valid ISO 8601 date"
          },
          "401": {
            "description": "Unauthorized"
          },
          "403": {
            "description": "Forbidden"
          }
        }
      }
    },
    "/api/v1/audit-log/{entry_id}": {
      "get": {
        "tags": [
          "Audit Log"
        ],
        "summary": "Get an audit entry",
        "description": "Returns a single audit-log entry, including its `detail` payload",
        "operationId": "getAuditEntry",
        "parameters": [
          {
            "name": "entry_id",
            "in": "path",
            "required": true,
            "description": "Audit entry ID",
            "schema": {
              "type": "string",
              "example": "audit_V1StGXR8Z5jdHi6B"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Audit entry details",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AuditEntry"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized"
          },
          "403": {
            "description": "Forbidden"
          },
          "404": {
            "description": "Audit entry not found"
          }
        }
      }
    },
    "/api/v1/chains": {
      "get": {
        "tags": [
          "Chains"
        ],
        "summary": "List continuation chains",
        "description": "Returns the continuation chains in a project, newest first. Filter by `status` to find the chains that may still be spending (`active`) or the ones a budget stopped (`budget_exhausted`).",
        "operationId": "listChains",
        "parameters": [
          {
            "name": "project_id",
            "in": "query",
            "description": "Project ID (required if not using project key auth)",
            "schema": {
              "type": "string",
              "example": "proj_V1StGXR8Z5jdHi6B"
            }
          },
          {
            "name": "status",
            "in": "query",
            "description": "Filter by chain status",
            "schema": {
              "type": "string",
              "enum": [
                "active",
                "concluded",
                "expired",
                "budget_exhausted"
              ]
            }
          },
          {
            "name": "agent_id",
            "in": "query",
            "description": "Filter by the agent whose continuation opened the chain",
            "schema": {
              "type": "string",
              "example": "agent_V1StGXR8Z5jdHi6B"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Maximum number of results to return",
            "schema": {
              "type": "integer",
              "default": 50
            }
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "description": "Number of results to skip",
            "schema": {
              "type": "integer",
              "default": 0
            }
          }
        ],
        "responses": {
          "200": {
            "description": "List of continuation chains",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "total",
                    "limit",
                    "offset"
                  ],
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Chain"
                      }
                    },
                    "total": {
                      "type": "integer"
                    },
                    "limit": {
                      "type": "integer"
                    },
                    "offset": {
                      "type": "integer"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized"
          },
          "403": {
            "description": "Forbidden"
          },
          "500": {
            "description": "Internal server error"
          }
        }
      }
    },
    "/api/v1/chains/{chain_id}": {
      "get": {
        "tags": [
          "Chains"
        ],
        "summary": "Get a continuation chain",
        "description": "Returns a single continuation chain. To read the generations in it, list generations filtered by `chain_id`.",
        "operationId": "getChain",
        "parameters": [
          {
            "$ref": "#/components/parameters/chain_id"
          }
        ],
        "responses": {
          "200": {
            "description": "Continuation chain",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Chain"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized"
          },
          "403": {
            "description": "Forbidden"
          },
          "404": {
            "description": "Chain not found"
          }
        }
      }
    },
    "/api/v1/chats": {
      "post": {
        "tags": [
          "Chats"
        ],
        "summary": "Create a chat",
        "description": "Creates a new chat resource bound to an AI provider.",
        "operationId": "createChat",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateChatRequest"
              },
              "examples": {
                "basic": {
                  "summary": "Minimal chat",
                  "value": {
                    "ai_provider_id": "aip_V1StGXR8Z5jdHi6B"
                  }
                },
                "with_instructions": {
                  "summary": "Chat with instructions",
                  "value": {
                    "ai_provider_id": "aip_V1StGXR8Z5jdHi6B",
                    "name": "Support Bot",
                    "instructions": "You are a helpful support assistant.",
                    "model": "gpt-4o"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Chat created",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Chat"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "AI provider not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      },
      "get": {
        "tags": [
          "Chats"
        ],
        "summary": "List chats",
        "description": "Returns all chats in the project.",
        "operationId": "listChats",
        "parameters": [
          {
            "name": "project_id",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Project public ID to filter by"
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Maximum number of results to return",
            "schema": {
              "type": "integer",
              "default": 50
            }
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "description": "Number of results to skip",
            "schema": {
              "type": "integer",
              "default": 0
            }
          }
        ],
        "responses": {
          "200": {
            "description": "List of chats",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "total",
                    "limit",
                    "offset"
                  ],
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Chat"
                      }
                    },
                    "total": {
                      "type": "integer"
                    },
                    "limit": {
                      "type": "integer"
                    },
                    "offset": {
                      "type": "integer"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/chats/{chat_id}": {
      "get": {
        "tags": [
          "Chats"
        ],
        "summary": "Get a chat",
        "description": "Returns a single chat by ID.",
        "operationId": "getChat",
        "parameters": [
          {
            "name": "chat_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Chat record",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Chat"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Chat not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      },
      "delete": {
        "tags": [
          "Chats"
        ],
        "summary": "Delete a chat",
        "description": "Deletes a chat by ID.",
        "operationId": "deleteChat",
        "parameters": [
          {
            "name": "chat_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "Chat deleted"
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Chat not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/chat/completions": {
      "post": {
        "tags": [
          "Chats"
        ],
        "summary": "Create a chat completion",
        "description": "OpenAI Chat Completions-compatible endpoint. Mirrors OpenAI's `POST /v1/chat/completions` path so an OpenAI SDK can target it by base URL alone.\n\nNames exactly one target. With `ai_provider_id` the completion is stateless: the provider's secret is decrypted and the appropriate Vercel AI SDK provider is called, with no server-side model fallback. With `chat_id` the stored chat supplies the provider (or the project's `default_model_route_id`), model and instructions.\n\nSystem content travels only in `instructions` — a `role: \"system\"` entry in `messages` is refused with `400 SYSTEM_MESSAGE_NOT_ALLOWED`. With `chat_id`, a request `instructions` replaces the chat's stored one for this call only; the stored value applies when the request carries none, and the two are never merged.\n\nMessages may use `document_id` instead of `content` with either target. Chats hold no message history — send the full `messages` array every time.\n",
        "operationId": "createChatCompletion",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ChatCompletionRequest"
              },
              "examples": {
                "basic": {
                  "summary": "Simple user message",
                  "value": {
                    "ai_provider_id": "aip_V1StGXR8Z5jdHi6B",
                    "messages": [
                      {
                        "role": "user",
                        "content": "Hello, how are you?"
                      }
                    ]
                  }
                },
                "with_chat": {
                  "summary": "Against a stored chat",
                  "value": {
                    "chat_id": "chat_V1StGXR8Z5jdHi6B",
                    "messages": [
                      {
                        "role": "user",
                        "content": "What can you help me with?"
                      }
                    ]
                  }
                },
                "with_document": {
                  "summary": "Message referencing a document",
                  "value": {
                    "chat_id": "chat_V1StGXR8Z5jdHi6B",
                    "messages": [
                      {
                        "role": "user",
                        "document_id": "doc_V1StGXR8Z5jdHi6B"
                      }
                    ]
                  }
                },
                "with_provider": {
                  "summary": "With explicit AI provider and streaming",
                  "value": {
                    "ai_provider_id": "aip_V1StGXR8Z5jdHi6B",
                    "model": "gpt-4o",
                    "instructions": "You are a helpful assistant.",
                    "messages": [
                      {
                        "role": "user",
                        "content": "What files do I have?"
                      }
                    ],
                    "stream": true
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Chat completion result (JSON or SSE stream)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ChatCompletionResponse"
                }
              },
              "text/event-stream": {
                "schema": {
                  "type": "string",
                  "description": "SSE stream of JSON objects, one per line, prefixed with `data: `. The stream ends with `data: [DONE]`.\nThe response headers are written before the provider is called, so a failure cannot become a status code once the stream is open. It arrives instead as a terminal `data: {\"error\": \"...\"}` frame carrying the same mapped message the non-streaming path returns in its `502` body (e.g. `Provider returned 404: ...`), and the stream then ends without a `[DONE]`.\n"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request — `messages` is missing or empty; a `role: \"system\"` entry appears in `messages`; neither `ai_provider_id` nor `chat_id` was given, or both were; or `chat_id` names a chat that does not exist.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized — missing or invalid bearer token",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden — the caller lacks `chats:CreateChatCompletion` on the project the call belongs to: the chat's project for `chat_id`, the AI provider's project for `ai_provider_id`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "AI provider not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "502": {
            "description": "Upstream AI provider error (AI_PROVIDER_ERROR) — the provider rejected the completion (an unavailable model, a refused credential) or could not be reached. The message names the provider's own status and message, so an unavailable model is distinguishable from a fault in the runtime itself. Streaming requests report this in a terminal SSE frame instead, since their status line is already on the wire.\n",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/conversations": {
      "get": {
        "tags": [
          "Conversations"
        ],
        "summary": "List conversations",
        "description": "Returns all conversations the caller has access to. If projectId is provided, returns only conversations in that project. project keys are scoped to a single project automatically.",
        "operationId": "listConversations",
        "parameters": [
          {
            "name": "project_id",
            "in": "query",
            "required": false,
            "description": "Project ID (optional)",
            "schema": {
              "type": "string",
              "example": "proj_V1StGXR8Z5jdHi6B"
            }
          },
          {
            "name": "actor_id",
            "in": "query",
            "required": false,
            "description": "Filter by actor ID",
            "schema": {
              "type": "string",
              "example": "actor_V1StGXR8Z5jdHi6B"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Maximum number of results to return",
            "schema": {
              "type": "integer",
              "default": 50
            }
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "description": "Number of results to skip",
            "schema": {
              "type": "integer",
              "default": 0
            }
          }
        ],
        "responses": {
          "200": {
            "description": "List of conversations",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/ConversationRecord"
                      }
                    },
                    "total": {
                      "type": "integer"
                    },
                    "limit": {
                      "type": "integer"
                    },
                    "offset": {
                      "type": "integer"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      },
      "post": {
        "tags": [
          "Conversations"
        ],
        "summary": "Create a conversation",
        "description": "Creates a new conversation. project keys automatically infer the project from the key's scope; JWT callers must supply projectId.",
        "operationId": "createConversation",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "project_id": {
                    "x-soat-ref": "projects",
                    "type": "string",
                    "description": "Project ID. Required for JWT auth; omit when using an project key.",
                    "example": "proj_V1StGXR8Z5jdHi6B"
                  },
                  "status": {
                    "type": "string",
                    "enum": [
                      "open",
                      "closed"
                    ],
                    "default": "open",
                    "description": "Initial conversation status"
                  },
                  "name": {
                    "type": "string",
                    "nullable": true,
                    "description": "Optional name for the conversation"
                  },
                  "actor_id": {
                    "x-soat-ref": "actors",
                    "type": "string",
                    "nullable": true,
                    "description": "Actor ID to associate with this conversation",
                    "example": "actor_V1StGXR8Z5jdHi6B"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Conversation created",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ConversationRecord"
                }
              }
            }
          },
          "400": {
            "description": "Invalid request body",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/conversations/{conversation_id}": {
      "get": {
        "tags": [
          "Conversations"
        ],
        "summary": "Get a conversation by ID",
        "description": "Returns a conversation by its ID",
        "operationId": "getConversation",
        "parameters": [
          {
            "name": "conversation_id",
            "in": "path",
            "required": true,
            "description": "Conversation ID",
            "schema": {
              "type": "string",
              "example": "conv_V1StGXR8Z5jdHi6B"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Conversation found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ConversationRecord"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Conversation not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      },
      "patch": {
        "tags": [
          "Conversations"
        ],
        "summary": "Update a conversation",
        "description": "Updates the status of a conversation",
        "operationId": "updateConversation",
        "parameters": [
          {
            "name": "conversation_id",
            "in": "path",
            "required": true,
            "description": "Conversation ID",
            "schema": {
              "type": "string",
              "example": "conv_V1StGXR8Z5jdHi6B"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "status": {
                    "type": "string",
                    "enum": [
                      "open",
                      "closed"
                    ],
                    "description": "New conversation status"
                  },
                  "name": {
                    "type": "string",
                    "nullable": true,
                    "description": "New conversation name"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Conversation updated",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ConversationRecord"
                }
              }
            }
          },
          "400": {
            "description": "Invalid request body",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Conversation not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      },
      "delete": {
        "tags": [
          "Conversations"
        ],
        "summary": "Delete a conversation",
        "description": "Deletes a conversation by its ID",
        "operationId": "deleteConversation",
        "parameters": [
          {
            "name": "conversation_id",
            "in": "path",
            "required": true,
            "description": "Conversation ID",
            "schema": {
              "type": "string",
              "example": "conv_V1StGXR8Z5jdHi6B"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "Conversation deleted"
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Conversation not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/conversations/{conversation_id}/messages": {
      "get": {
        "tags": [
          "Conversations"
        ],
        "summary": "List conversation messages",
        "description": "Returns all messages (documents) attached to a conversation, ordered by position",
        "operationId": "listConversationMessages",
        "parameters": [
          {
            "name": "conversation_id",
            "in": "path",
            "required": true,
            "description": "Conversation ID",
            "schema": {
              "type": "string",
              "example": "conv_V1StGXR8Z5jdHi6B"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Maximum number of results to return",
            "schema": {
              "type": "integer",
              "default": 50
            }
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "description": "Number of results to skip",
            "schema": {
              "type": "integer",
              "default": 0
            }
          }
        ],
        "responses": {
          "200": {
            "description": "List of messages",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/ConversationMessageRecord"
                      }
                    },
                    "total": {
                      "type": "integer"
                    },
                    "limit": {
                      "type": "integer"
                    },
                    "offset": {
                      "type": "integer"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Conversation not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      },
      "post": {
        "tags": [
          "Conversations"
        ],
        "summary": "Add a message to a conversation",
        "description": "Creates a document from the message text and attaches it to the conversation at the given position. If position is omitted, it is appended at the end.",
        "operationId": "addConversationMessage",
        "parameters": [
          {
            "name": "conversation_id",
            "in": "path",
            "required": true,
            "description": "Conversation ID",
            "schema": {
              "type": "string",
              "example": "conv_V1StGXR8Z5jdHi6B"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "message",
                  "role"
                ],
                "properties": {
                  "message": {
                    "type": "string",
                    "description": "Message text content to add to the conversation",
                    "example": "Hello, how can I help you?"
                  },
                  "role": {
                    "type": "string",
                    "enum": [
                      "user",
                      "assistant"
                    ],
                    "description": "Role of the message sender",
                    "example": "user"
                  },
                  "actor_id": {
                    "x-soat-ref": "actors",
                    "type": "string",
                    "nullable": true,
                    "description": "Optional actor ID to associate with this message (user identity)",
                    "example": "actor_V1StGXR8Z5jdHi6B"
                  },
                  "position": {
                    "type": "integer",
                    "description": "Zero-based position. Defaults to MAX+1 (append).",
                    "example": 0
                  },
                  "metadata": {
                    "type": "object",
                    "description": "Optional structured metadata to attach to the message (e.g. phone number, channel). Stored as-is and injected into the AI prompt context.",
                    "nullable": true,
                    "additionalProperties": true,
                    "example": {
                      "phone": "5511999998888",
                      "channel": "whatsapp"
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Message added",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ConversationMessageRecord"
                }
              }
            }
          },
          "400": {
            "description": "Invalid request body",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Conversation or actor not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/conversations/{conversation_id}/generate": {
      "post": {
        "tags": [
          "Conversations"
        ],
        "summary": "Generate the next message in a conversation",
        "description": "Generates the next message using the specified actor's linked agent or chat.\nBackground by default: returns `202 Accepted` immediately and the reply\nlands as a new ConversationMessage when it completes — poll\n`GET /api/v1/conversations/{conversation_id}/messages` for it.\nPass `?wait=true` to block and receive the result inline. On\n`completed`, the reply is persisted as a new ConversationMessage\nauthored by that actor. On `requires_action`, nothing is persisted; the\ncaller must submit tool outputs via the Agents module and re-invoke\ngenerate — so a flow using client tools should pass `?wait=true`.\n",
        "operationId": "generateConversationMessage",
        "parameters": [
          {
            "name": "conversation_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "wait",
            "in": "query",
            "required": false,
            "x-soat-tool-forced": "true",
            "description": "When omitted or `false` (default), the generation runs in the background and `202 Accepted` is returned immediately. Pass `true` to block until the generation settles and receive the result. A `builtin` tool call always waits.",
            "schema": {
              "type": "boolean",
              "default": false
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "agent_id"
                ],
                "properties": {
                  "agent_id": {
                    "x-soat-ref": "agents",
                    "type": "string",
                    "description": "ID of the agent that will produce the next message."
                  },
                  "model": {
                    "type": "string",
                    "description": "Optional model override."
                  },
                  "stream": {
                    "type": "boolean",
                    "description": "If true, stream tokens via SSE. NOT IMPLEMENTED in v1 — returns 501."
                  },
                  "tool_context": {
                    "type": "object",
                    "additionalProperties": {
                      "type": "string"
                    },
                    "nullable": true,
                    "description": "Key-value pairs forwarded as `X-Soat-Context-<key>` headers on every `http`, `mcp` and `builtin` tool call in this generation. The header name is the deployment's configured context prefix (`X-Soat-Context-` by default) plus the key verbatim — no character is re-cased and keys are never case-converted, so they round-trip exactly as sent. An invalid or colliding key is rejected with `400 INVALID_TOOL_CONTEXT_KEY`."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Generation completed or requires action (only when `?wait=true`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GenerateConversationMessageResponse"
                }
              }
            }
          },
          "202": {
            "description": "Generation accepted and running in the background (default, when `wait` is omitted or `false`). The reply is persisted as a ConversationMessage when it completes.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "status",
                    "conversation_id"
                  ],
                  "properties": {
                    "status": {
                      "type": "string",
                      "enum": [
                        "accepted"
                      ],
                      "example": "accepted"
                    },
                    "conversation_id": {
                      "type": "string",
                      "example": "conv_V1StGXR8Z5jdHi6B"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Conversation or actor not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "501": {
            "description": "Streaming not implemented",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/conversations/{conversation_id}/messages/{document_id}": {
      "delete": {
        "tags": [
          "Conversations"
        ],
        "summary": "Remove a message from a conversation",
        "description": "Removes a document from a conversation",
        "operationId": "removeConversationMessage",
        "parameters": [
          {
            "name": "conversation_id",
            "in": "path",
            "required": true,
            "description": "Conversation ID",
            "schema": {
              "type": "string",
              "example": "conv_V1StGXR8Z5jdHi6B"
            }
          },
          {
            "name": "document_id",
            "in": "path",
            "required": true,
            "description": "Document ID",
            "schema": {
              "type": "string",
              "example": "doc_V1StGXR8Z5jdHi6B"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "Message removed"
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Conversation or message not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/conversations/{conversation_id}/tags": {
      "get": {
        "tags": [
          "Conversations"
        ],
        "summary": "Get conversation tags",
        "description": "Returns all tags attached to the conversation",
        "operationId": "getConversationTags",
        "parameters": [
          {
            "name": "conversation_id",
            "in": "path",
            "required": true,
            "description": "Conversation ID",
            "schema": {
              "type": "string",
              "example": "conv_V1StGXR8Z5jdHi6B"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Conversation tags",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": {
                    "type": "string"
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Conversation not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      },
      "put": {
        "tags": [
          "Conversations"
        ],
        "summary": "Replace conversation tags",
        "description": "Replaces all tags on the conversation with the provided tags",
        "operationId": "replaceConversationTags",
        "parameters": [
          {
            "name": "conversation_id",
            "in": "path",
            "required": true,
            "description": "Conversation ID",
            "schema": {
              "type": "string",
              "example": "conv_V1StGXR8Z5jdHi6B"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": {
                  "type": "string"
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Tags replaced",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": {
                    "type": "string"
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Conversation not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      },
      "patch": {
        "tags": [
          "Conversations"
        ],
        "summary": "Merge conversation tags",
        "description": "Merges provided tags with existing tags",
        "operationId": "mergeConversationTags",
        "parameters": [
          {
            "name": "conversation_id",
            "in": "path",
            "required": true,
            "description": "Conversation ID",
            "schema": {
              "type": "string",
              "example": "conv_V1StGXR8Z5jdHi6B"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": {
                  "type": "string"
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Tags merged",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": {
                    "type": "string"
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Conversation not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/documents": {
      "get": {
        "tags": [
          "Documents"
        ],
        "summary": "List documents",
        "description": "Returns all documents the caller has access to. If projectId is provided, returns only documents in that project. project keys are scoped to a single project automatically. JWT users without projectId receive documents across all their accessible projects.",
        "operationId": "listDocuments",
        "parameters": [
          {
            "name": "project_id",
            "in": "query",
            "required": false,
            "description": "Project ID (optional)",
            "schema": {
              "type": "string",
              "example": "proj_V1StGXR8Z5jdHi6B"
            }
          },
          {
            "name": "path_prefix",
            "in": "query",
            "required": false,
            "description": "Only documents filed under this directory. The prefix is a path boundary, not a substring: `/reports` returns `/reports/q1.txt` and never `/reports-archive/q1.txt`, and `/` selects the whole project. A leading slash is optional and a trailing one is ignored, so `reports`, `/reports` and `/reports/` are the same filter. `%` and `_` are literal characters, not wildcards.",
            "schema": {
              "type": "string",
              "example": "/reports/"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Maximum number of results to return",
            "schema": {
              "type": "integer",
              "default": 50
            }
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "description": "Number of results to skip",
            "schema": {
              "type": "integer",
              "default": 0
            }
          }
        ],
        "responses": {
          "200": {
            "description": "List of documents",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/DocumentRecord"
                      }
                    },
                    "total": {
                      "type": "integer"
                    },
                    "limit": {
                      "type": "integer"
                    },
                    "offset": {
                      "type": "integer"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      },
      "post": {
        "tags": [
          "Documents"
        ],
        "summary": "Create a document",
        "description": "Creates a new text document and generates an embedding vector for semantic search. project keys automatically infer the project from the key's scope; JWT callers must supply projectId.",
        "operationId": "createDocument",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "content"
                ],
                "properties": {
                  "project_id": {
                    "x-soat-ref": "projects",
                    "type": "string",
                    "description": "Project ID. Required for JWT auth; omit when using an project key.",
                    "example": "proj_V1StGXR8Z5jdHi6B"
                  },
                  "content": {
                    "type": "string",
                    "example": "The quick brown fox jumps over the lazy dog."
                  },
                  "path": {
                    "type": "string",
                    "description": "Logical path within the project (e.g. /reports/q1.txt). Defaults to /filename if omitted.",
                    "example": "/reports/q1.txt"
                  },
                  "filename": {
                    "type": "string",
                    "example": "my-doc.txt"
                  },
                  "title": {
                    "type": "string",
                    "description": "Document title"
                  },
                  "metadata": {
                    "type": "object",
                    "description": "Arbitrary metadata object. Unlike other body fields, keys are stored and returned verbatim in the casing supplied — they are not converted between snake_case and camelCase."
                  },
                  "tags": {
                    "type": "object",
                    "additionalProperties": {
                      "type": "string"
                    },
                    "description": "Key-value tags"
                  },
                  "chunk_strategy": {
                    "type": "string",
                    "enum": [
                      "page",
                      "whole",
                      "size"
                    ],
                    "description": "How to split the content into embeddable chunks. `whole` (default) stores the content as a single chunk; `size` splits into fixed-size character windows with overlap. `page` is equivalent to `whole` for plain text.",
                    "default": "whole"
                  },
                  "chunk_size": {
                    "type": "integer",
                    "description": "Window size in characters when `chunk_strategy=size`. Defaults to 1000.",
                    "example": 1000
                  },
                  "chunk_overlap": {
                    "type": "integer",
                    "description": "Overlap in characters between consecutive windows when `chunk_strategy=size`. Defaults to 200.",
                    "example": 200
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Document created",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DocumentRecord"
                }
              }
            }
          },
          "400": {
            "description": "Invalid request body",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/documents/ingest": {
      "post": {
        "tags": [
          "Documents"
        ],
        "summary": "Ingest a file into a chunked document",
        "description": "Parses an already-uploaded file and creates one Document split into one or\nmore embedded chunks. The source format is detected from the file's content\ntype: PDFs are parsed page-by-page; `text/plain` and `text/markdown` files\nare read as a single source. How the source is chunked is controlled by\n`chunk_strategy`.\n\nA file can only back one Document — a second call with the same `file_id`\nreturns `409 FILE_ALREADY_INGESTED`. To re-process an already-ingested file\n(e.g. with a different `chunk_strategy`), use\n`POST /documents/{document_id}/ingest`; to ingest the same source under a\ndifferent path, upload a new copy of the file first.\n",
        "operationId": "ingestDocument",
        "x-iam-action": "documents:IngestDocument",
        "parameters": [
          {
            "name": "wait",
            "in": "query",
            "required": false,
            "description": "When omitted or `false` (default), processing runs in the background and `202 Accepted` is returned immediately with `status=pending`. Pass `true` to block until processing completes and receive `201 Created` with `status=ready`.",
            "schema": {
              "type": "boolean",
              "default": false
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "file_id"
                ],
                "properties": {
                  "file_id": {
                    "x-soat-ref": "files",
                    "type": "string",
                    "description": "ID of the uploaded file. Must be one of application/pdf, text/plain, text/markdown.",
                    "example": "file_V1StGXR8Z5jdHi6B"
                  },
                  "project_id": {
                    "x-soat-ref": "projects",
                    "type": "string",
                    "description": "Project ID. Required for JWT auth; omit when using a project key.",
                    "example": "proj_V1StGXR8Z5jdHi6B"
                  },
                  "path_prefix": {
                    "type": "string",
                    "description": "Path prefix under which to store the document (e.g. /docs/). The filename is appended automatically.",
                    "example": "/docs/"
                  },
                  "tags": {
                    "type": "object",
                    "additionalProperties": {
                      "type": "string"
                    },
                    "description": "Key-value tags to attach to the document."
                  },
                  "chunk_strategy": {
                    "type": "string",
                    "enum": [
                      "page",
                      "whole",
                      "size"
                    ],
                    "description": "How to split the source into chunks. `page` (default) creates one chunk per non-empty page (PDF); for non-paged sources it yields a single chunk. `whole` joins everything into one chunk. `size` splits into fixed-size character windows with overlap.",
                    "default": "page"
                  },
                  "chunk_size": {
                    "type": "integer",
                    "description": "Window size in characters when `chunk_strategy=size`. Defaults to 1000.",
                    "example": 1000
                  },
                  "chunk_overlap": {
                    "type": "integer",
                    "description": "Overlap in characters between consecutive windows when `chunk_strategy=size`. Defaults to 200.",
                    "example": 200
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Ingestion completed synchronously (only when `?wait=true`). The document is fully indexed and ready for search.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/IngestedDocumentRecord"
                }
              }
            }
          },
          "202": {
            "description": "Ingestion accepted. The document record has been created with `status=pending` and processing runs in the background. Poll `GET /api/v1/documents/{document_id}` until `status` is `ready` or `failed`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/IngestedDocumentRecord"
                }
              }
            }
          },
          "400": {
            "description": "Invalid request, file not found, or unsupported content type",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "409": {
            "description": "The file already backs a Document (a file can only be ingested once). Use `POST /documents/{document_id}/ingest` to re-process the existing document, or upload a new copy of the file to ingest it separately.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "413": {
            "description": "The file is too large to ingest synchronously (`?wait=true`). Retry in background mode and poll the document status.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/documents/{document_id}": {
      "get": {
        "tags": [
          "Documents"
        ],
        "summary": "Get a document by ID",
        "description": "Returns a document with its text content",
        "operationId": "getDocument",
        "parameters": [
          {
            "name": "document_id",
            "in": "path",
            "required": true,
            "description": "Document ID",
            "schema": {
              "type": "string",
              "example": "doc_V1StGXR8Z5jdHi6B"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Document found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DocumentRecord"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Document not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      },
      "delete": {
        "tags": [
          "Documents"
        ],
        "summary": "Delete a document",
        "description": "Deletes a document and its underlying file",
        "operationId": "deleteDocument",
        "parameters": [
          {
            "name": "document_id",
            "in": "path",
            "required": true,
            "description": "Document ID",
            "schema": {
              "type": "string",
              "example": "doc_V1StGXR8Z5jdHi6B"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "Document deleted"
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Document not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      },
      "patch": {
        "tags": [
          "Documents"
        ],
        "summary": "Update a document",
        "description": "Updates document content, title, path, metadata, or tags. Supplying `path` moves the document to a new logical path within the project.",
        "operationId": "updateDocument",
        "parameters": [
          {
            "name": "document_id",
            "in": "path",
            "required": true,
            "description": "Document ID",
            "schema": {
              "type": "string",
              "example": "doc_V1StGXR8Z5jdHi6B"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "content": {
                    "type": "string",
                    "description": "New text content"
                  },
                  "title": {
                    "type": "string",
                    "description": "New title"
                  },
                  "path": {
                    "type": "string",
                    "nullable": true,
                    "description": "Logical path within the project (e.g. /reports/q1.txt). Pass null to clear.",
                    "example": "/reports/q1.txt"
                  },
                  "metadata": {
                    "type": "object",
                    "description": "Arbitrary metadata object. Unlike other body fields, keys are stored and returned verbatim in the casing supplied — they are not converted between snake_case and camelCase."
                  },
                  "tags": {
                    "type": "object",
                    "additionalProperties": {
                      "type": "string"
                    },
                    "description": "Key-value tags"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Document updated",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DocumentRecord"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Document not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/documents/{document_id}/status": {
      "get": {
        "tags": [
          "Documents"
        ],
        "summary": "Get document ingestion status",
        "description": "Returns a lightweight ingestion status payload for polling — `status`,\n`chunk_count`, `total_pages`, and (when failed) `error`. Unlike\n`GET /documents/{document_id}`, it never returns the assembled chunk\ncontent, so it is cheap to poll on large documents. A document whose\ningestion has stalled (no progress past the configured timeout) is\ntransitioned to `failed` with `error=INGESTION_TIMEOUT` on read.\n",
        "operationId": "getDocumentStatus",
        "x-iam-action": "documents:GetDocument",
        "parameters": [
          {
            "name": "document_id",
            "in": "path",
            "required": true,
            "description": "Document ID",
            "schema": {
              "type": "string",
              "example": "doc_V1StGXR8Z5jdHi6B"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Document ingestion status",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DocumentStatusRecord"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Document not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/documents/{document_id}/ingest": {
      "post": {
        "tags": [
          "Documents"
        ],
        "summary": "Re-ingest an existing document",
        "description": "Re-runs ingestion for an existing document against its already-stored\nsource file. Existing chunks are discarded and the document is reset to\n`status=pending` before re-processing. Use this to recover a document\nstuck in `processing`/`failed` or to re-chunk with a different strategy\nwithout re-uploading the file. Background by default (`202`); pass\n`?wait=true` to run synchronously (`201`).\n",
        "operationId": "reingestDocument",
        "x-iam-action": "documents:IngestDocument",
        "parameters": [
          {
            "name": "document_id",
            "in": "path",
            "required": true,
            "description": "Document ID",
            "schema": {
              "type": "string",
              "example": "doc_V1StGXR8Z5jdHi6B"
            }
          },
          {
            "name": "wait",
            "in": "query",
            "required": false,
            "description": "When omitted or `false` (default), processing runs in the background and `202 Accepted` is returned immediately with `status=pending`. Pass `true` to block until processing completes and receive `201 Created` with `status=ready`.",
            "schema": {
              "type": "boolean",
              "default": false
            }
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "chunk_strategy": {
                    "type": "string",
                    "enum": [
                      "page",
                      "whole",
                      "size"
                    ],
                    "description": "How to split the source into chunks. Defaults to `page`.",
                    "default": "page"
                  },
                  "chunk_size": {
                    "type": "integer",
                    "description": "Window size in characters when `chunk_strategy=size`. Defaults to 1000.",
                    "example": 1000
                  },
                  "chunk_overlap": {
                    "type": "integer",
                    "description": "Overlap in characters between consecutive windows when `chunk_strategy=size`. Defaults to 200.",
                    "example": 200
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Re-ingestion completed synchronously (only when `?wait=true`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/IngestedDocumentRecord"
                }
              }
            }
          },
          "202": {
            "description": "Re-ingestion accepted. The document was reset to `status=pending` and processing runs in the background. Poll `GET /api/v1/documents/{document_id}/status`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/IngestedDocumentRecord"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Document not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "413": {
            "description": "The file is too large to re-ingest synchronously (`?wait=true`). Retry in background mode.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/documents/{document_id}/ingestion-callback": {
      "post": {
        "tags": [
          "Documents"
        ],
        "summary": "Deliver an async converter result",
        "description": "Token-authed callback for a tool converter that deferred conversion by\nreturning `{ \"status\": \"pending\" }` (see the Ingestion Rules module\ndocs). Not IAM-gated — the external converter is not a SOAT principal,\nso it authenticates with the single-use token minted for this\ndocument and ingestion attempt (delivered as `callback.token` /\nembedded in `callback.url` in the original converter invocation).\nAccepted only while the document is still awaiting that exact\nattempt; rejected with `409` if the attempt already completed, timed\nout, or was superseded by a re-ingest.\n",
        "operationId": "completeIngestionCallback",
        "parameters": [
          {
            "name": "document_id",
            "in": "path",
            "required": true,
            "description": "Document ID",
            "schema": {
              "type": "string",
              "example": "doc_V1StGXR8Z5jdHi6B"
            }
          },
          {
            "name": "token",
            "in": "query",
            "required": true,
            "description": "Single-use signed token from the original `callback.token`",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "description": "The converter output contract, adapted for a JSON request body: a single page as `{ text }`, or `{ pages: [{ text, page_number }] }` for multiple pages.",
          "content": {
            "application/json": {
              "schema": {
                "oneOf": [
                  {
                    "type": "object",
                    "required": [
                      "text"
                    ],
                    "properties": {
                      "text": {
                        "type": "string"
                      }
                    }
                  },
                  {
                    "type": "object",
                    "required": [
                      "pages"
                    ],
                    "properties": {
                      "pages": {
                        "type": "array",
                        "items": {
                          "type": "object",
                          "properties": {
                            "text": {
                              "type": "string"
                            },
                            "page_number": {
                              "type": "integer"
                            }
                          }
                        }
                      }
                    }
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "204": {
            "description": "Conversion completed — the document was chunked and marked `ready` (or `failed` with `FILE_PARSE_FAILED` if the output produced no text)."
          },
          "401": {
            "description": "The token is missing, invalid, or does not match this document.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Document not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "409": {
            "description": "The document is no longer awaiting this conversion attempt (already completed, timed out, or superseded by a re-ingest).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "422": {
            "description": "The output shape is unrecognized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/documents/{document_id}/tags": {
      "get": {
        "tags": [
          "Documents"
        ],
        "summary": "Get document tags",
        "description": "Returns all tags attached to the document",
        "operationId": "getDocumentTags",
        "parameters": [
          {
            "name": "document_id",
            "in": "path",
            "required": true,
            "description": "Document ID",
            "schema": {
              "type": "string",
              "example": "doc_V1StGXR8Z5jdHi6B"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Document tags",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": {
                    "type": "string"
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Document not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      },
      "put": {
        "tags": [
          "Documents"
        ],
        "summary": "Replace document tags",
        "description": "Replaces all tags on the document with the provided tags (not merged)",
        "operationId": "replaceDocumentTags",
        "parameters": [
          {
            "name": "document_id",
            "in": "path",
            "required": true,
            "description": "Document ID",
            "schema": {
              "type": "string",
              "example": "doc_V1StGXR8Z5jdHi6B"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": {
                  "type": "string"
                },
                "example": {
                  "category": "research",
                  "status": "draft"
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Tags replaced",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": {
                    "type": "string"
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Document not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      },
      "patch": {
        "tags": [
          "Documents"
        ],
        "summary": "Merge document tags",
        "description": "Merges provided tags with existing tags (existing tags are preserved unless overridden)",
        "operationId": "mergeDocumentTags",
        "parameters": [
          {
            "name": "document_id",
            "in": "path",
            "required": true,
            "description": "Document ID",
            "schema": {
              "type": "string",
              "example": "doc_V1StGXR8Z5jdHi6B"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": {
                  "type": "string"
                },
                "example": {
                  "priority": "high"
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Tags merged",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": {
                    "type": "string"
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Document not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/embeddings": {
      "post": {
        "tags": [
          "Embeddings"
        ],
        "summary": "Create embeddings",
        "description": "Generates embedding vectors for one or more text inputs using the server's configured embedding model.\nProvide `input` for a single text or `inputs` for a batch. At least one is required.\nReturns `embedding` when `input` is used, and `embeddings` when `inputs` is used.\n",
        "operationId": "createEmbeddings",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "input": {
                    "type": "string",
                    "description": "Single text to embed.",
                    "example": "The quick brown fox jumps over the lazy dog."
                  },
                  "inputs": {
                    "type": "array",
                    "description": "Batch of texts to embed.",
                    "items": {
                      "type": "string"
                    },
                    "example": [
                      "The quick brown fox.",
                      "Pack my box with five dozen liquor jugs."
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Embeddings generated successfully",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/EmbeddingsResponse"
                }
              }
            }
          },
          "400": {
            "description": "Invalid request body",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "503": {
            "description": "Embedding service not configured",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/datasets": {
      "get": {
        "tags": [
          "Evaluations"
        ],
        "summary": "List datasets",
        "description": "Returns the datasets defined in a project",
        "operationId": "listDatasets",
        "parameters": [
          {
            "name": "project_id",
            "in": "query",
            "description": "Project ID (required if not using project key auth)",
            "schema": {
              "type": "string",
              "example": "proj_V1StGXR8Z5jdHi6B"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Maximum number of results to return",
            "schema": {
              "type": "integer",
              "default": 50
            }
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "description": "Number of results to skip",
            "schema": {
              "type": "integer",
              "default": 0
            }
          }
        ],
        "responses": {
          "200": {
            "description": "List of datasets",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "total",
                    "limit",
                    "offset"
                  ],
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Dataset"
                      }
                    },
                    "total": {
                      "type": "integer"
                    },
                    "limit": {
                      "type": "integer"
                    },
                    "offset": {
                      "type": "integer"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized"
          },
          "403": {
            "description": "Forbidden"
          },
          "500": {
            "description": "Internal server error"
          }
        }
      },
      "post": {
        "tags": [
          "Evaluations"
        ],
        "summary": "Create a dataset",
        "description": "Creates a project-scoped dataset — a named collection of test cases an eval runs an agent against. Names are unique per project.\n\nDatasets are operator-owned **fixtures**. The platform's content purge never deletes or mutates a dataset item, so erasing a generation cannot silently stop a test suite from being runnable.",
        "operationId": "createDataset",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "name"
                ],
                "properties": {
                  "project_id": {
                    "x-soat-ref": "projects",
                    "type": "string",
                    "description": "Project ID (required if not using project key auth)",
                    "example": "proj_V1StGXR8Z5jdHi6B"
                  },
                  "name": {
                    "type": "string",
                    "description": "Unique name within the project",
                    "example": "billing-regressions"
                  },
                  "description": {
                    "type": "string",
                    "nullable": true,
                    "description": "What this suite covers",
                    "example": "Questions the billing agent regressed on in Q2"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Dataset created successfully",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Dataset"
                }
              }
            }
          },
          "400": {
            "description": "Bad request (missing or invalid name)"
          },
          "401": {
            "description": "Unauthorized"
          },
          "403": {
            "description": "Forbidden"
          },
          "409": {
            "description": "A dataset with that name already exists in the project"
          },
          "500": {
            "description": "Internal server error"
          }
        }
      }
    },
    "/api/v1/datasets/{dataset_id}": {
      "get": {
        "tags": [
          "Evaluations"
        ],
        "summary": "Get a dataset",
        "description": "Returns a specific dataset",
        "operationId": "getDataset",
        "parameters": [
          {
            "name": "dataset_id",
            "in": "path",
            "required": true,
            "description": "Dataset ID",
            "schema": {
              "type": "string",
              "example": "dset_V1StGXR8Z5jdHi6B"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Dataset details",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Dataset"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized"
          },
          "403": {
            "description": "Forbidden"
          },
          "404": {
            "description": "Dataset not found"
          }
        }
      },
      "put": {
        "tags": [
          "Evaluations"
        ],
        "summary": "Update a dataset",
        "description": "Updates a dataset's name and/or description",
        "operationId": "updateDataset",
        "parameters": [
          {
            "name": "dataset_id",
            "in": "path",
            "required": true,
            "description": "Dataset ID",
            "schema": {
              "type": "string",
              "example": "dset_V1StGXR8Z5jdHi6B"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string",
                    "example": "billing-regressions"
                  },
                  "description": {
                    "type": "string",
                    "nullable": true,
                    "example": "Questions the billing agent regressed on in Q2"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Dataset updated successfully",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Dataset"
                }
              }
            }
          },
          "400": {
            "description": "Bad request"
          },
          "401": {
            "description": "Unauthorized"
          },
          "403": {
            "description": "Forbidden"
          },
          "404": {
            "description": "Dataset not found"
          },
          "409": {
            "description": "A dataset with that name already exists in the project"
          }
        }
      },
      "delete": {
        "tags": [
          "Evaluations"
        ],
        "summary": "Delete a dataset",
        "description": "Deletes a dataset, its items, and every eval bound to it. Results of runs that already scored those items keep their frozen copies of the input and expected output.",
        "operationId": "deleteDataset",
        "parameters": [
          {
            "name": "dataset_id",
            "in": "path",
            "required": true,
            "description": "Dataset ID",
            "schema": {
              "type": "string",
              "example": "dset_V1StGXR8Z5jdHi6B"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "Dataset deleted successfully"
          },
          "401": {
            "description": "Unauthorized"
          },
          "403": {
            "description": "Forbidden"
          },
          "404": {
            "description": "Dataset not found"
          }
        }
      }
    },
    "/api/v1/datasets/{dataset_id}/items": {
      "get": {
        "tags": [
          "Evaluations"
        ],
        "summary": "List dataset items",
        "description": "Returns the test cases in a dataset, oldest first",
        "operationId": "listDatasetItems",
        "parameters": [
          {
            "name": "dataset_id",
            "in": "path",
            "required": true,
            "description": "Dataset ID",
            "schema": {
              "type": "string",
              "example": "dset_V1StGXR8Z5jdHi6B"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Maximum number of results to return",
            "schema": {
              "type": "integer",
              "default": 50
            }
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "description": "Number of results to skip",
            "schema": {
              "type": "integer",
              "default": 0
            }
          }
        ],
        "responses": {
          "200": {
            "description": "List of dataset items",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "total",
                    "limit",
                    "offset"
                  ],
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/DatasetItem"
                      }
                    },
                    "total": {
                      "type": "integer"
                    },
                    "limit": {
                      "type": "integer"
                    },
                    "offset": {
                      "type": "integer"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized"
          },
          "403": {
            "description": "Forbidden"
          },
          "404": {
            "description": "Dataset not found"
          }
        }
      },
      "post": {
        "tags": [
          "Evaluations"
        ],
        "summary": "Add a dataset item",
        "description": "Adds one test case. `input` is replayed verbatim as the generation's messages, so it must be a non-empty array of `{ role, content }`.",
        "operationId": "createDatasetItem",
        "parameters": [
          {
            "name": "dataset_id",
            "in": "path",
            "required": true,
            "description": "Dataset ID",
            "schema": {
              "type": "string",
              "example": "dset_V1StGXR8Z5jdHi6B"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "input"
                ],
                "properties": {
                  "input": {
                    "$ref": "#/components/schemas/DatasetItemInput"
                  },
                  "expected_output": {
                    "type": "string",
                    "nullable": true,
                    "description": "Reference answer for exact_match / embedding_similarity / llm_judge scorers",
                    "example": "Your invoice is issued on the first of each month."
                  },
                  "metadata": {
                    "type": "object",
                    "nullable": true,
                    "additionalProperties": true,
                    "description": "Free-form tags, opaque to the platform",
                    "example": {
                      "topic": "billing"
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Dataset item created successfully",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DatasetItem"
                }
              }
            }
          },
          "400": {
            "description": "Bad request (input is not message-shaped)"
          },
          "401": {
            "description": "Unauthorized"
          },
          "403": {
            "description": "Forbidden"
          },
          "404": {
            "description": "Dataset not found"
          }
        }
      }
    },
    "/api/v1/datasets/{dataset_id}/items/from-generation": {
      "post": {
        "tags": [
          "Evaluations"
        ],
        "summary": "Curate a dataset item from a generation",
        "description": "Promotes a real, completed generation into a test case: its input messages become the item's `input`, and its own answer becomes `expected_output` unless you supply one. Use it to build an evaluation set out of production traffic rather than hand-authoring fixtures.\n\nThe item is a **copy**, not a view. It keeps working after the source generation's content is purged, and `source_generation_id` goes null if that generation is deleted — a purge can never quietly stop a suite from being runnable.\n\nRequires both `evaluations:CreateDataset` and `generations:GetGeneration`: the call copies content out of a generation, so a principal that may not read that generation may not curate it either.\n\nOnly a **completed** generation can be promoted (`409 GENERATION_NOT_COMPLETED`), and only while its content is still available: an agent or project running with `trace_content_mode: none` never stored the input, and a purged or expired generation no longer has it (`409 GENERATION_CONTENT_UNAVAILABLE`). Generations that predate input recording answer the same way.",
        "operationId": "createDatasetItemFromGeneration",
        "parameters": [
          {
            "name": "dataset_id",
            "in": "path",
            "required": true,
            "description": "Dataset ID",
            "schema": {
              "type": "string",
              "example": "dset_V1StGXR8Z5jdHi6B"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "generation_id"
                ],
                "properties": {
                  "generation_id": {
                    "type": "string",
                    "description": "The completed generation to promote. Must belong to the same project as the dataset.",
                    "example": "gen_V1StGXR8Z5jdHi6B"
                  },
                  "expected_output": {
                    "type": "string",
                    "nullable": true,
                    "description": "Reference answer. Omit to use the generation's own answer; pass `null` to store the item with no reference answer.",
                    "example": "Your invoice is issued on the first of each month."
                  },
                  "metadata": {
                    "type": "object",
                    "nullable": true,
                    "additionalProperties": true,
                    "description": "Free-form tags, opaque to the platform",
                    "example": {
                      "topic": "billing"
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Dataset item created from the generation",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DatasetItem"
                }
              }
            }
          },
          "400": {
            "description": "Bad request (generation_id missing, or the generation belongs to a different project than the dataset)"
          },
          "401": {
            "description": "Unauthorized"
          },
          "403": {
            "description": "Forbidden"
          },
          "404": {
            "description": "Dataset or generation not found"
          },
          "409": {
            "description": "The generation has not completed, or its content was never stored or has been purged"
          }
        }
      }
    },
    "/api/v1/datasets/{dataset_id}/items/{item_id}": {
      "put": {
        "tags": [
          "Evaluations"
        ],
        "summary": "Update a dataset item",
        "description": "Updates a test case. Runs that already scored it are unaffected — each result carries its own frozen copy of the input and expected output.",
        "operationId": "updateDatasetItem",
        "parameters": [
          {
            "name": "dataset_id",
            "in": "path",
            "required": true,
            "description": "Dataset ID",
            "schema": {
              "type": "string",
              "example": "dset_V1StGXR8Z5jdHi6B"
            }
          },
          {
            "name": "item_id",
            "in": "path",
            "required": true,
            "description": "Dataset item ID",
            "schema": {
              "type": "string",
              "example": "dsit_V1StGXR8Z5jdHi6B"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "input": {
                    "$ref": "#/components/schemas/DatasetItemInput"
                  },
                  "expected_output": {
                    "type": "string",
                    "nullable": true,
                    "example": "Your invoice is issued on the first of each month."
                  },
                  "metadata": {
                    "type": "object",
                    "nullable": true,
                    "additionalProperties": true,
                    "example": {
                      "topic": "billing"
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Dataset item updated successfully",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DatasetItem"
                }
              }
            }
          },
          "400": {
            "description": "Bad request"
          },
          "401": {
            "description": "Unauthorized"
          },
          "403": {
            "description": "Forbidden"
          },
          "404": {
            "description": "Dataset or item not found"
          }
        }
      },
      "delete": {
        "tags": [
          "Evaluations"
        ],
        "summary": "Delete a dataset item",
        "description": "Deletes a test case. Results of runs that already scored it stay readable; their `dataset_item_id` becomes null.",
        "operationId": "deleteDatasetItem",
        "parameters": [
          {
            "name": "dataset_id",
            "in": "path",
            "required": true,
            "description": "Dataset ID",
            "schema": {
              "type": "string",
              "example": "dset_V1StGXR8Z5jdHi6B"
            }
          },
          {
            "name": "item_id",
            "in": "path",
            "required": true,
            "description": "Dataset item ID",
            "schema": {
              "type": "string",
              "example": "dsit_V1StGXR8Z5jdHi6B"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "Dataset item deleted successfully"
          },
          "401": {
            "description": "Unauthorized"
          },
          "403": {
            "description": "Forbidden"
          },
          "404": {
            "description": "Dataset or item not found"
          }
        }
      }
    },
    "/api/v1/evals": {
      "get": {
        "tags": [
          "Evaluations"
        ],
        "summary": "List evals",
        "description": "Returns the evals defined in a project",
        "operationId": "listEvals",
        "parameters": [
          {
            "name": "project_id",
            "in": "query",
            "description": "Project ID (required if not using project key auth)",
            "schema": {
              "type": "string",
              "example": "proj_V1StGXR8Z5jdHi6B"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Maximum number of results to return",
            "schema": {
              "type": "integer",
              "default": 50
            }
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "description": "Number of results to skip",
            "schema": {
              "type": "integer",
              "default": 0
            }
          }
        ],
        "responses": {
          "200": {
            "description": "List of evals",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "total",
                    "limit",
                    "offset"
                  ],
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Eval"
                      }
                    },
                    "total": {
                      "type": "integer"
                    },
                    "limit": {
                      "type": "integer"
                    },
                    "offset": {
                      "type": "integer"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized"
          },
          "403": {
            "description": "Forbidden"
          },
          "500": {
            "description": "Internal server error"
          }
        }
      },
      "post": {
        "tags": [
          "Evaluations"
        ],
        "summary": "Create an eval",
        "description": "Binds an agent under test to a dataset and a list of scorers. The agent and the dataset must belong to the same project as the eval; a cross-project reference is rejected with 400.\n\nScorer config is frozen here rather than read from the agent at run time, so two runs of the same eval are always judged by the same criteria and their comparison measures the agent instead of the config drifting underneath it. Each scorer `type` may appear at most once.",
        "operationId": "createEval",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "name",
                  "agent_id",
                  "dataset_id",
                  "scorers"
                ],
                "properties": {
                  "project_id": {
                    "x-soat-ref": "projects",
                    "type": "string",
                    "description": "Project ID (required if not using project key auth)",
                    "example": "proj_V1StGXR8Z5jdHi6B"
                  },
                  "name": {
                    "type": "string",
                    "description": "Unique name within the project",
                    "example": "billing-regression-suite"
                  },
                  "agent_id": {
                    "x-soat-ref": "agents",
                    "type": "string",
                    "description": "The agent under test",
                    "example": "agent_V1StGXR8Z5jdHi6B"
                  },
                  "dataset_id": {
                    "type": "string",
                    "description": "The dataset to run it against",
                    "example": "dset_V1StGXR8Z5jdHi6B"
                  },
                  "scorers": {
                    "$ref": "#/components/schemas/Scorers"
                  },
                  "pass_threshold": {
                    "type": "number",
                    "nullable": true,
                    "description": "0–1. The run passes iff its pass rate — passed items over non-errored items — is at least this. Null reports scores without gating on them.",
                    "example": 0.8
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Eval created successfully",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Eval"
                }
              }
            }
          },
          "400": {
            "description": "Bad request (unknown scorer type, cross-project reference, invalid threshold)"
          },
          "401": {
            "description": "Unauthorized"
          },
          "403": {
            "description": "Forbidden"
          },
          "409": {
            "description": "An eval with that name already exists in the project"
          },
          "500": {
            "description": "Internal server error"
          }
        }
      }
    },
    "/api/v1/evals/{eval_id}": {
      "get": {
        "tags": [
          "Evaluations"
        ],
        "summary": "Get an eval",
        "description": "Returns a specific eval",
        "operationId": "getEval",
        "parameters": [
          {
            "name": "eval_id",
            "in": "path",
            "required": true,
            "description": "Eval ID",
            "schema": {
              "type": "string",
              "example": "eval_V1StGXR8Z5jdHi6B"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Eval details",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Eval"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized"
          },
          "403": {
            "description": "Forbidden"
          },
          "404": {
            "description": "Eval not found"
          }
        }
      },
      "put": {
        "tags": [
          "Evaluations"
        ],
        "summary": "Update an eval",
        "description": "Updates an eval. Changing `agent_id` re-validates the scorers against the new agent, since an `output_schema` scorer that was legal against the old one may not be.",
        "operationId": "updateEval",
        "parameters": [
          {
            "name": "eval_id",
            "in": "path",
            "required": true,
            "description": "Eval ID",
            "schema": {
              "type": "string",
              "example": "eval_V1StGXR8Z5jdHi6B"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string",
                    "example": "billing-regression-suite"
                  },
                  "agent_id": {
                    "x-soat-ref": "agents",
                    "type": "string",
                    "example": "agent_V1StGXR8Z5jdHi6B"
                  },
                  "dataset_id": {
                    "type": "string",
                    "example": "dset_V1StGXR8Z5jdHi6B"
                  },
                  "scorers": {
                    "$ref": "#/components/schemas/Scorers"
                  },
                  "pass_threshold": {
                    "type": "number",
                    "nullable": true,
                    "example": 0.8
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Eval updated successfully",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Eval"
                }
              }
            }
          },
          "400": {
            "description": "Bad request"
          },
          "401": {
            "description": "Unauthorized"
          },
          "403": {
            "description": "Forbidden"
          },
          "404": {
            "description": "Eval not found"
          },
          "409": {
            "description": "An eval with that name already exists in the project"
          }
        }
      },
      "delete": {
        "tags": [
          "Evaluations"
        ],
        "summary": "Delete an eval",
        "description": "Deletes an eval, its runs, and their results",
        "operationId": "deleteEval",
        "parameters": [
          {
            "name": "eval_id",
            "in": "path",
            "required": true,
            "description": "Eval ID",
            "schema": {
              "type": "string",
              "example": "eval_V1StGXR8Z5jdHi6B"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "Eval deleted successfully"
          },
          "401": {
            "description": "Unauthorized"
          },
          "403": {
            "description": "Forbidden"
          },
          "404": {
            "description": "Eval not found"
          }
        }
      }
    },
    "/api/v1/evals/{eval_id}/runs": {
      "get": {
        "tags": [
          "Evaluations"
        ],
        "summary": "List eval runs",
        "description": "Returns an eval's runs, newest first",
        "operationId": "listEvalRuns",
        "parameters": [
          {
            "name": "eval_id",
            "in": "path",
            "required": true,
            "description": "Eval ID",
            "schema": {
              "type": "string",
              "example": "eval_V1StGXR8Z5jdHi6B"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Maximum number of results to return",
            "schema": {
              "type": "integer",
              "default": 50
            }
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "description": "Number of results to skip",
            "schema": {
              "type": "integer",
              "default": 0
            }
          }
        ],
        "responses": {
          "200": {
            "description": "List of eval runs",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "total",
                    "limit",
                    "offset"
                  ],
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/EvalRun"
                      }
                    },
                    "total": {
                      "type": "integer"
                    },
                    "limit": {
                      "type": "integer"
                    },
                    "offset": {
                      "type": "integer"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized"
          },
          "403": {
            "description": "Forbidden"
          },
          "404": {
            "description": "Eval not found"
          }
        }
      },
      "post": {
        "tags": [
          "Evaluations"
        ],
        "summary": "Start an eval run",
        "description": "Runs the eval against its dataset, creating one real agent generation per item and scoring the outputs.\n\n`wait: true` executes the run synchronously and returns it terminal, with its scores. The dataset is capped at 25 items for a synchronous run; a larger one is rejected with 400 rather than partially scored.\n\n`wait: false` (the default) enqueues one task per item and returns immediately with `status: \"queued\"`. A worker executes the items and the run settles itself; poll `GET /evals/{eval_id}/runs/{eval_run_id}` for the terminal status, or subscribe to the `eval_run.completed` webhook. There is no item cap on a queued run.\n\nThe whole run is pinned to **one** agent version, stamped on `agent_version`: pass one explicitly to evaluate a canary before promoting it, or omit it to use the active release's stable version (or the live draft when no release is in effect). Without the pin, release assignment would bucket each item independently and blend two configs into a single score.\n\nWith `baseline_run_id`, the finished run's `aggregate_scores.baseline` carries per-scorer deltas against that run, computed over the items present and scorable in **both** runs, with the divergence counted. A delta over a shifted dataset is therefore never presented as a clean comparison.",
        "operationId": "startEvalRun",
        "parameters": [
          {
            "name": "eval_id",
            "in": "path",
            "required": true,
            "description": "Eval ID",
            "schema": {
              "type": "string",
              "example": "eval_V1StGXR8Z5jdHi6B"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "wait": {
                    "type": "boolean",
                    "default": false,
                    "description": "True runs the eval synchronously (25-item cap) and returns a terminal run with its scores. False — the default — enqueues the items and returns a `queued` run immediately.",
                    "example": true
                  },
                  "agent_version": {
                    "type": "integer",
                    "nullable": true,
                    "description": "An archived agent version to evaluate. Defaults to the active release's stable version, or the live draft version when no release is in effect.",
                    "example": 3
                  },
                  "baseline_run_id": {
                    "type": "string",
                    "nullable": true,
                    "description": "A terminal run of the same eval to compare against. The finished run's `aggregate_scores.baseline` reports per-scorer deltas over the item intersection. A run of a different eval is rejected with 400.",
                    "example": "evrun_V1StGXR8Z5jdHi6B"
                  },
                  "metadata": {
                    "type": "object",
                    "additionalProperties": true,
                    "description": "Caller-supplied key/value metadata attached to the run record for attribution — what this measurement was of (the commit or release candidate being scored, the CI job that asked for it). Round-trips verbatim on every read of the run, the list included.\n\nThe bag is caller-owned and no key is reserved: everything the platform decides about a run (`status`, `agent_version`, `baseline_run_id`, `aggregate_scores`, `passed`, the counts) is a field of its own and cannot be written from here. Nothing in the scoring path reads it. A non-object is rejected with `400 VALIDATION_FAILED` and no run is created.",
                    "example": {
                      "commit_sha": "9f2c1ab",
                      "ci_job": "nightly-evals"
                    }
                  },
                  "tool_context": {
                    "type": "object",
                    "additionalProperties": {
                      "type": "string"
                    },
                    "description": "Key/value context forwarded to every item's generation, so an agent whose tools authorize through `tool_context` is scored against the configuration it runs in production rather than with an empty bag. Each key is forwarded as one `X-Soat-Context-<key>` header and resolves any `{{context:<key>}}` token in a bound tool's headers or `preset_parameters`.\n\nStored on the run and re-read per item, since a queued run (the default) is driven by a worker with no request behind it. **Write-only**: no read of the run returns it, unlike `metadata` — a run is a report other people read, and a credential in it is not theirs to see. Cleared once the run reaches a terminal state.\n\nAn eval generation has no session, so the reserved keys `session_id`, `actor_id` and `actor_external_id` are dropped (in any casing) rather than forwarded. Every other key becomes an HTTP header name and must match that grammar, or the request is rejected with `400 INVALID_TOOL_CONTEXT_KEY` and no run is created.",
                    "example": {
                      "ocaToken": "eyJhbGciOiJIUzI1NiJ9.abc",
                      "tenant": "acme"
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Eval run finished (`wait: true`) or queued (`wait: false`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/EvalRun"
                }
              }
            }
          },
          "400": {
            "description": "Bad request (non-boolean wait, dataset empty or over the synchronous cap, unknown agent_version, invalid baseline, scorers no longer valid against the agent, a `tool_context` key that cannot become a header)"
          },
          "401": {
            "description": "Unauthorized"
          },
          "403": {
            "description": "Forbidden"
          },
          "404": {
            "description": "Eval not found"
          },
          "500": {
            "description": "Internal server error"
          }
        }
      }
    },
    "/api/v1/evals/{eval_id}/runs/{eval_run_id}": {
      "get": {
        "tags": [
          "Evaluations"
        ],
        "summary": "Get an eval run",
        "description": "Returns a run's status, counts, and aggregate scores",
        "operationId": "getEvalRun",
        "parameters": [
          {
            "name": "eval_id",
            "in": "path",
            "required": true,
            "description": "Eval ID",
            "schema": {
              "type": "string",
              "example": "eval_V1StGXR8Z5jdHi6B"
            }
          },
          {
            "name": "eval_run_id",
            "in": "path",
            "required": true,
            "description": "Eval run ID",
            "schema": {
              "type": "string",
              "example": "evrun_V1StGXR8Z5jdHi6B"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Eval run details",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/EvalRun"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized"
          },
          "403": {
            "description": "Forbidden"
          },
          "404": {
            "description": "Eval or run not found"
          }
        }
      }
    },
    "/api/v1/evals/{eval_id}/runs/{eval_run_id}/results": {
      "get": {
        "tags": [
          "Evaluations"
        ],
        "summary": "List eval run results",
        "description": "Returns the per-item results of a run, oldest first",
        "operationId": "listEvalResults",
        "parameters": [
          {
            "name": "eval_id",
            "in": "path",
            "required": true,
            "description": "Eval ID",
            "schema": {
              "type": "string",
              "example": "eval_V1StGXR8Z5jdHi6B"
            }
          },
          {
            "name": "eval_run_id",
            "in": "path",
            "required": true,
            "description": "Eval run ID",
            "schema": {
              "type": "string",
              "example": "evrun_V1StGXR8Z5jdHi6B"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Maximum number of results to return",
            "schema": {
              "type": "integer",
              "default": 50
            }
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "description": "Number of results to skip",
            "schema": {
              "type": "integer",
              "default": 0
            }
          }
        ],
        "responses": {
          "200": {
            "description": "List of eval results",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "total",
                    "limit",
                    "offset"
                  ],
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/EvalResult"
                      }
                    },
                    "total": {
                      "type": "integer"
                    },
                    "limit": {
                      "type": "integer"
                    },
                    "offset": {
                      "type": "integer"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized"
          },
          "403": {
            "description": "Forbidden"
          },
          "404": {
            "description": "Eval or run not found"
          }
        }
      }
    },
    "/api/v1/evals/{eval_id}/runs/{eval_run_id}/cancel": {
      "post": {
        "tags": [
          "Evaluations"
        ],
        "summary": "Cancel an eval run",
        "description": "Cancels a queued or running run: its outstanding item tasks are dropped so it stops consuming provider budget, and the run settles as `canceled`.\n\nResults already written are kept — they are real measurements of generations that were really paid for — and `completed_count` / `errored_count` report what ran. `aggregate_scores` is deliberately left null: a partial roll-up in the same field a completed run uses would read as a whole-dataset verdict.\n\nA run that has already finished is rejected with 400.",
        "operationId": "cancelEvalRun",
        "parameters": [
          {
            "name": "eval_id",
            "in": "path",
            "required": true,
            "description": "Eval ID",
            "schema": {
              "type": "string",
              "example": "eval_V1StGXR8Z5jdHi6B"
            }
          },
          {
            "name": "eval_run_id",
            "in": "path",
            "required": true,
            "description": "Eval run ID",
            "schema": {
              "type": "string",
              "example": "evrun_V1StGXR8Z5jdHi6B"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Eval run canceled",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/EvalRun"
                }
              }
            }
          },
          "400": {
            "description": "The run has already finished"
          },
          "401": {
            "description": "Unauthorized"
          },
          "403": {
            "description": "Forbidden"
          },
          "404": {
            "description": "Eval or run not found"
          },
          "500": {
            "description": "Internal server error"
          }
        }
      }
    },
    "/api/v1/exceptions": {
      "get": {
        "tags": [
          "Exceptions"
        ],
        "summary": "List exception items",
        "description": "Returns exception items for a project, filterable by status, severity, and kind.",
        "operationId": "listExceptions",
        "parameters": [
          {
            "name": "project_id",
            "in": "query",
            "description": "Project ID (required if not using project key auth)",
            "schema": {
              "type": "string",
              "example": "proj_V1StGXR8Z5jdHi6B"
            }
          },
          {
            "name": "status",
            "in": "query",
            "description": "Filter by triage status",
            "schema": {
              "type": "string",
              "enum": [
                "open",
                "acknowledged",
                "resolved"
              ]
            }
          },
          {
            "name": "severity",
            "in": "query",
            "description": "Filter by severity",
            "schema": {
              "type": "string",
              "enum": [
                "info",
                "warning",
                "critical"
              ]
            }
          },
          {
            "name": "kind",
            "in": "query",
            "description": "Filter by how the exception was filed",
            "schema": {
              "type": "string",
              "enum": [
                "run_failed",
                "guardrail_tripwire",
                "approval_expired",
                "quota_unpriced",
                "event_trigger_loop",
                "chain_limit",
                "manual"
              ]
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Maximum number of results to return",
            "schema": {
              "type": "integer",
              "default": 50
            }
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "description": "Number of results to skip",
            "schema": {
              "type": "integer",
              "default": 0
            }
          }
        ],
        "responses": {
          "200": {
            "description": "List of exception items",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "total",
                    "limit",
                    "offset"
                  ],
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/ExceptionItem"
                      }
                    },
                    "total": {
                      "type": "integer"
                    },
                    "limit": {
                      "type": "integer"
                    },
                    "offset": {
                      "type": "integer"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized"
          },
          "403": {
            "description": "Forbidden"
          },
          "500": {
            "description": "Internal server error"
          }
        }
      }
    },
    "/api/v1/exceptions/{exception_id}": {
      "get": {
        "tags": [
          "Exceptions"
        ],
        "summary": "Get an exception item",
        "description": "Returns a single exception item with its full detail.",
        "operationId": "getException",
        "parameters": [
          {
            "$ref": "#/components/parameters/exception_id"
          }
        ],
        "responses": {
          "200": {
            "description": "Exception item",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ExceptionItem"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized"
          },
          "403": {
            "description": "Forbidden"
          },
          "404": {
            "description": "Exception item not found"
          }
        }
      }
    },
    "/api/v1/exceptions/{exception_id}/acknowledge": {
      "post": {
        "tags": [
          "Exceptions"
        ],
        "summary": "Acknowledge an exception item",
        "description": "Moves the item to `acknowledged` (\"someone is on it\"), recording who. A no-op that returns the item unchanged when already acknowledged; rejected when already resolved.",
        "operationId": "acknowledgeException",
        "parameters": [
          {
            "$ref": "#/components/parameters/exception_id"
          }
        ],
        "responses": {
          "200": {
            "description": "Exception item acknowledged",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ExceptionItem"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized"
          },
          "403": {
            "description": "Forbidden"
          },
          "404": {
            "description": "Exception item not found"
          },
          "409": {
            "description": "Item already resolved"
          }
        }
      }
    },
    "/api/v1/exceptions/{exception_id}/resolve": {
      "post": {
        "tags": [
          "Exceptions"
        ],
        "summary": "Resolve an exception item",
        "description": "Moves the item to `resolved` (\"fixed\"), recording who and an optional note.",
        "operationId": "resolveException",
        "parameters": [
          {
            "$ref": "#/components/parameters/exception_id"
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "note": {
                    "type": "string",
                    "description": "Optional resolution note",
                    "example": "Root cause fixed; retried the run successfully."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Exception item resolved",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ExceptionItem"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized"
          },
          "403": {
            "description": "Forbidden"
          },
          "404": {
            "description": "Exception item not found"
          },
          "409": {
            "description": "Item already resolved"
          }
        }
      }
    },
    "/api/v1/files": {
      "get": {
        "tags": [
          "Files"
        ],
        "summary": "List all files",
        "description": "Returns a list of all stored files",
        "operationId": "listFiles",
        "parameters": [
          {
            "name": "project_id",
            "in": "query",
            "required": false,
            "description": "Filter files by project ID",
            "schema": {
              "type": "string",
              "example": "proj_V1StGXR8Z5jdHi6B"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Maximum number of results to return",
            "schema": {
              "type": "integer",
              "default": 50
            }
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "description": "Number of results to skip",
            "schema": {
              "type": "integer",
              "default": 0
            }
          }
        ],
        "responses": {
          "200": {
            "description": "List of files returned successfully",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/FileRecord"
                      }
                    },
                    "total": {
                      "type": "integer"
                    },
                    "limit": {
                      "type": "integer"
                    },
                    "offset": {
                      "type": "integer"
                    }
                  }
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      },
      "post": {
        "tags": [
          "Files"
        ],
        "summary": "Create a file",
        "description": "Creates a new file record in the system",
        "operationId": "createFile",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "project_id": {
                    "x-soat-ref": "projects",
                    "type": "string",
                    "description": "Public ID of the project. Optional when authenticating with a project-scoped API key, which defaults to the key's project; required otherwise.",
                    "example": "proj_V1StGXR8Z5jdHi6B"
                  },
                  "prefix": {
                    "type": "string",
                    "description": "Directory within the project (e.g. /images). Optional; defaults to / (root). Combined with filename to form the file's key (path).",
                    "example": "/images"
                  },
                  "filename": {
                    "type": "string",
                    "description": "Original / download name and the key's leaf segment (e.g. logo.png).",
                    "example": "logo.png"
                  },
                  "content_type": {
                    "type": "string",
                    "description": "MIME type of the file",
                    "example": "application/pdf"
                  },
                  "size": {
                    "type": "integer",
                    "nullable": true,
                    "description": "File size in bytes",
                    "example": 1024
                  },
                  "metadata": {
                    "type": "string",
                    "description": "JSON string with additional metadata",
                    "example": "{\"author\":\"John\"}"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "File created successfully",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/FileRecord"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/files/upload": {
      "post": {
        "tags": [
          "Files"
        ],
        "summary": "Upload a file",
        "description": "Uploads a file to the server and stores it in the configured storage directory",
        "operationId": "uploadFile",
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "required": [
                  "file"
                ],
                "properties": {
                  "file": {
                    "type": "string",
                    "format": "binary",
                    "description": "File content"
                  },
                  "project_id": {
                    "x-soat-ref": "projects",
                    "type": "string",
                    "description": "Project ID to associate the file with. Optional when authenticating with a project-scoped API key, which defaults to the key's project; required otherwise.",
                    "example": "proj_V1StGXR8Z5jdHi6B"
                  },
                  "prefix": {
                    "type": "string",
                    "description": "Directory within the project (e.g. /images). Optional; defaults to / (root).",
                    "example": "/images"
                  },
                  "filename": {
                    "type": "string",
                    "description": "Original / download name. Optional; defaults to the uploaded file's name.",
                    "example": "logo.png"
                  },
                  "metadata": {
                    "type": "string",
                    "description": "Additional metadata as a JSON string",
                    "example": "{\"author\":\"John\"}"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "File uploaded successfully",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/FileRecord"
                }
              }
            }
          },
          "400": {
            "description": "Missing file or invalid project",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      }
    },
    "/api/v1/files/upload/base64": {
      "post": {
        "tags": [
          "Files"
        ],
        "summary": "Upload a file using base64 encoding",
        "description": "Uploads a file to the server using base64-encoded content",
        "operationId": "uploadFileBase64",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UploadFileBase64Request"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "File uploaded successfully",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/FileRecord"
                }
              }
            }
          },
          "400": {
            "description": "Missing content or invalid project",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      }
    },
    "/api/v1/files/presigned-url": {
      "post": {
        "tags": [
          "Files"
        ],
        "summary": "Create a presigned upload URL",
        "description": "Creates a short-lived, single-use presigned upload URL — the local-storage equivalent of an S3 presigned URL. The client then uploads the file content directly to the returned `upload_url` via `POST /api/v1/files/upload/{token}`, bypassing MCP payload size limits. When the server is configured with `SOAT_BASE_URL`, `upload_url` is a fully-qualified absolute URL so MCP agents and other clients can POST to it without knowing the server base URL in advance.",
        "operationId": "createPresignedUrl",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PresignedUrlRequest"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Presigned URL created successfully",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PresignedUrlResponse"
                }
              }
            }
          },
          "400": {
            "description": "Missing or invalid project",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      }
    },
    "/api/v1/files/upload/{token}": {
      "post": {
        "tags": [
          "Files"
        ],
        "summary": "Upload a file using an upload token",
        "description": "Uploads file content authorized by a single-use token from `POST /api/v1/files/presigned-url`. No bearer credential is required — the token is the credential. Accepts either multipart/form-data (field `file`) or JSON with a base64-encoded `content` field.",
        "operationId": "uploadFileWithToken",
        "security": [],
        "parameters": [
          {
            "name": "token",
            "in": "path",
            "required": true,
            "description": "The single-use upload token (e.g. upt_...)",
            "schema": {
              "type": "string",
              "example": "upt_V1StGXR8Z5jdHi6B"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "required": [
                  "file"
                ],
                "properties": {
                  "file": {
                    "type": "string",
                    "format": "binary",
                    "description": "File content"
                  },
                  "metadata": {
                    "type": "string",
                    "description": "Additional metadata as a JSON string",
                    "example": "{\"author\":\"John\"}"
                  }
                }
              }
            },
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UploadFileWithTokenRequest"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "File uploaded successfully",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/FileRecord"
                }
              }
            }
          },
          "400": {
            "description": "Missing content",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Upload token not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "409": {
            "description": "Upload token already used",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "410": {
            "description": "Upload token expired",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/files/{file_id}": {
      "get": {
        "tags": [
          "Files"
        ],
        "summary": "Get a file by ID",
        "description": "Returns the data and metadata of a specific file",
        "operationId": "getFile",
        "parameters": [
          {
            "name": "file_id",
            "in": "path",
            "required": true,
            "description": "File ID",
            "schema": {
              "type": "string",
              "example": "abc123"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "File found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/FileRecord"
                }
              }
            }
          },
          "404": {
            "description": "File not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      },
      "delete": {
        "tags": [
          "Files"
        ],
        "summary": "Delete a file",
        "description": "Removes a file from the system by ID",
        "operationId": "deleteFile",
        "parameters": [
          {
            "name": "file_id",
            "in": "path",
            "required": true,
            "description": "ID of the file to delete",
            "schema": {
              "type": "string",
              "example": "abc123"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "File deleted successfully"
          },
          "404": {
            "description": "File not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/files/{file_id}/download": {
      "get": {
        "tags": [
          "Files"
        ],
        "summary": "Download a file",
        "description": "Streams the file content to the client",
        "operationId": "downloadFile",
        "x-soat-mcp-exclude": true,
        "parameters": [
          {
            "name": "file_id",
            "in": "path",
            "required": true,
            "description": "File ID",
            "schema": {
              "type": "string",
              "example": "file_abc123"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "File content",
            "content": {
              "application/octet-stream": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "File not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/files/{file_id}/metadata": {
      "patch": {
        "tags": [
          "Files"
        ],
        "summary": "Update file metadata",
        "description": "Updates the metadata field of a file",
        "operationId": "updateFileMetadata",
        "parameters": [
          {
            "name": "file_id",
            "in": "path",
            "required": true,
            "description": "File ID",
            "schema": {
              "type": "string",
              "example": "file_abc123"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "metadata": {
                    "type": "string",
                    "description": "New metadata as a JSON string",
                    "example": "{\"author\":\"Jane\",\"tags\":[\"report\"]}"
                  },
                  "prefix": {
                    "type": "string",
                    "description": "New directory — moves the file. The resulting path (prefix + filename) must be unique within the project.",
                    "example": "/reports"
                  },
                  "filename": {
                    "type": "string",
                    "description": "New filename — renames the key's leaf and the download name.",
                    "example": "renamed-file.txt"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Metadata updated successfully",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/FileRecord"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "File not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "409": {
            "description": "A file already exists at the target path in this project",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/files/{file_id}/download/base64": {
      "get": {
        "tags": [
          "Files"
        ],
        "summary": "Download file as base64",
        "description": "Returns the file content encoded as base64",
        "operationId": "downloadFileBase64",
        "parameters": [
          {
            "name": "file_id",
            "in": "path",
            "required": true,
            "description": "File ID",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "File content as base64",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "content": {
                      "type": "string",
                      "description": "Base64-encoded file content"
                    },
                    "filename": {
                      "type": "string",
                      "description": "Original filename"
                    },
                    "content_type": {
                      "type": "string",
                      "description": "MIME type of the file"
                    },
                    "size": {
                      "type": "integer",
                      "nullable": true,
                      "description": "File size in bytes"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "File not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/files/{file_id}/tags": {
      "get": {
        "tags": [
          "Files"
        ],
        "summary": "Get file tags",
        "description": "Returns all tags attached to the file",
        "operationId": "getFileTags",
        "parameters": [
          {
            "name": "file_id",
            "in": "path",
            "required": true,
            "description": "File ID",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "File tags",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": {
                    "type": "string"
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "File not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      },
      "put": {
        "tags": [
          "Files"
        ],
        "summary": "Replace file tags",
        "description": "Replaces all tags on the file with the provided tags",
        "operationId": "replaceFileTags",
        "parameters": [
          {
            "name": "file_id",
            "in": "path",
            "required": true,
            "description": "File ID",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": {
                  "type": "string"
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Tags replaced",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": {
                    "type": "string"
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "File not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      },
      "patch": {
        "tags": [
          "Files"
        ],
        "summary": "Merge file tags",
        "description": "Merges provided tags with existing tags",
        "operationId": "mergeFileTags",
        "parameters": [
          {
            "name": "file_id",
            "in": "path",
            "required": true,
            "description": "File ID",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": {
                  "type": "string"
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Tags merged",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": {
                    "type": "string"
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "File not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/formations/validate": {
      "post": {
        "tags": [
          "Formations"
        ],
        "summary": "Validate a formation template",
        "description": "Validates a formation template without creating any resources. Returns a list of errors and warnings. Accepts the template as a JSON object or as a YAML/JSON string.\n",
        "operationId": "validateFormation",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "template": {
                    "$ref": "#/components/schemas/FormationTemplateInput"
                  },
                  "parameters": {
                    "type": "object",
                    "additionalProperties": {
                      "type": "string"
                    },
                    "description": "Runtime parameter values that override or supply template parameter defaults. Keys must match parameter names declared in `template.parameters`. When provided, the validation result also reports required parameters that are still missing after applying these values.\n",
                    "nullable": true
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Validation result",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidationResult"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized"
          }
        }
      }
    },
    "/api/v1/formations/plan": {
      "post": {
        "tags": [
          "Formations"
        ],
        "summary": "Plan a formation deployment",
        "description": "Computes a diff between the desired template and the current stack state without making any changes. Returns the list of planned actions.\n",
        "operationId": "planFormation",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "template"
                ],
                "properties": {
                  "project_id": {
                    "x-soat-ref": "projects",
                    "type": "string",
                    "description": "Project ID. Optional when authenticating with a project-scoped API key, which defaults to the key's project; required otherwise.",
                    "example": "proj_V1StGXR8Z5jdHi6B"
                  },
                  "formation_id": {
                    "x-soat-ref": "formations",
                    "type": "string",
                    "description": "Existing formation ID to compare against. Omit for new formation planning."
                  },
                  "template": {
                    "$ref": "#/components/schemas/FormationTemplateInput"
                  },
                  "parameters": {
                    "type": "object",
                    "additionalProperties": {
                      "type": "string"
                    },
                    "description": "Runtime parameter values that override or supply template parameter defaults. Keys must match parameter names declared in `template.parameters`. A parameter declared with `use_previous_value: true` may be omitted to reuse its stored value.\n",
                    "nullable": true
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Plan result",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PlanResult"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request"
          },
          "401": {
            "description": "Unauthorized"
          },
          "403": {
            "description": "Forbidden"
          }
        }
      }
    },
    "/api/v1/formations": {
      "get": {
        "tags": [
          "Formations"
        ],
        "summary": "List formations",
        "description": "Returns all formation stacks for a project",
        "operationId": "listFormations",
        "parameters": [
          {
            "name": "project_id",
            "in": "query",
            "description": "Project ID (required if not using project key auth)",
            "schema": {
              "type": "string",
              "example": "proj_V1StGXR8Z5jdHi6B"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Maximum number of results to return",
            "schema": {
              "type": "integer",
              "default": 50
            }
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "description": "Number of results to skip",
            "schema": {
              "type": "integer",
              "default": 0
            }
          }
        ],
        "responses": {
          "200": {
            "description": "List of formations",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "total",
                    "limit",
                    "offset"
                  ],
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Formation"
                      }
                    },
                    "total": {
                      "type": "integer"
                    },
                    "limit": {
                      "type": "integer"
                    },
                    "offset": {
                      "type": "integer"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized"
          },
          "403": {
            "description": "Forbidden"
          }
        }
      },
      "post": {
        "tags": [
          "Formations"
        ],
        "summary": "Create a new formation",
        "description": "Validates the template, creates the formation record, then provisions all declared resources in dependency order.\n\nA **template-shape** error is refused with `400`. A **deploy** failure is not: the operation ran, so the formation is returned with `201` and `status: \"failed\"`, and `error` explains why (the resources created before the failure are rolled back). Read `status` — a `2xx` here means the deploy was attempted, not that it worked. The `builtin` CLI exits non-zero on that body so `create-formation && …` does not lie.\n",
        "operationId": "createFormation",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "name",
                  "template"
                ],
                "properties": {
                  "project_id": {
                    "x-soat-ref": "projects",
                    "type": "string",
                    "description": "Project ID. Optional when authenticating with a project-scoped API key, which defaults to the key's project; required otherwise.",
                    "example": "proj_V1StGXR8Z5jdHi6B"
                  },
                  "name": {
                    "type": "string",
                    "description": "Human-readable name for the formation stack",
                    "example": "my-agent-stack"
                  },
                  "template": {
                    "$ref": "#/components/schemas/FormationTemplateInput"
                  },
                  "parameters": {
                    "type": "object",
                    "additionalProperties": {
                      "type": "string"
                    },
                    "description": "Runtime parameter values that override or supply template parameter defaults. Keys must match parameter names declared in `template.parameters`. Required parameters (those without a default) must be provided here.\n",
                    "nullable": true
                  },
                  "metadata": {
                    "type": "object",
                    "additionalProperties": true,
                    "nullable": true,
                    "description": "Static annotations stored on the formation record. This field is NOT a substitution site: `sub`/`param`/`ref` expressions are rejected with 400 (`FORMATION_INVALID_METADATA`). For deploy-time substitution use the template's top-level `metadata` block, which is resolved into `resolved_metadata`.\n"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Formation created",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Formation"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request"
          },
          "401": {
            "description": "Unauthorized"
          },
          "403": {
            "description": "Forbidden — either the caller may not operate on formations in the project, or it lacks an action a resource this template declares requires. `error.meta.denied_actions` names every missing action; nothing is applied."
          },
          "409": {
            "description": "Formation with this name already exists"
          }
        }
      }
    },
    "/api/v1/formations/{formation_id}": {
      "get": {
        "tags": [
          "Formations"
        ],
        "summary": "Get a specific formation",
        "description": "Returns the formation stack including its current resources.",
        "operationId": "getFormation",
        "parameters": [
          {
            "name": "formation_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "example": "form_V1StGXR8Z5jdHi6B"
          }
        ],
        "responses": {
          "200": {
            "description": "Formation details",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Formation"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized"
          },
          "403": {
            "description": "Forbidden"
          },
          "404": {
            "description": "Not Found"
          }
        }
      },
      "put": {
        "tags": [
          "Formations"
        ],
        "summary": "Update an formation",
        "description": "Applies a new template to the formation. Resources are created, updated, or deleted to reconcile the current state with the desired state.\n\nA **template-shape** error is refused with `400`. A **deploy** failure is not: the operation ran, so the formation is returned with `200` and `status: \"failed\"`, and `error` explains why. Read `status` — a `2xx` here means the deploy was attempted, not that it worked. The `builtin` CLI exits non-zero on that body so `update-formation && …` does not lie.\n\nA deploy that replaced a resource and could not delete the superseded one answers `status: \"active\"` with `error.code: \"FORMATION_REPLACE_CLEANUP_FAILED\"` — the desired state is realised, and `error.meta.failures` names every resource still live. The next deploy retries the disposal.\n",
        "operationId": "updateFormation",
        "parameters": [
          {
            "name": "formation_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "example": "form_V1StGXR8Z5jdHi6B"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "template": {
                    "$ref": "#/components/schemas/FormationTemplateInput"
                  },
                  "parameters": {
                    "type": "object",
                    "additionalProperties": {
                      "type": "string"
                    },
                    "description": "Runtime parameter values that override or supply template parameter defaults. Keys must match parameter names declared in `template.parameters`. Required parameters (those without a default) must be provided here, unless the parameter is declared with `use_previous_value: true`, in which case omitting it reuses the previously stored value.\n",
                    "nullable": true
                  },
                  "metadata": {
                    "type": "object",
                    "additionalProperties": true,
                    "nullable": true,
                    "description": "Static annotations stored on the formation record. This field is NOT a substitution site: `sub`/`param`/`ref` expressions are rejected with 400 (`FORMATION_INVALID_METADATA`). For deploy-time substitution use the template's top-level `metadata` block, which is resolved into `resolved_metadata`.\n"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated formation",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Formation"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request"
          },
          "401": {
            "description": "Unauthorized"
          },
          "403": {
            "description": "Forbidden — either the caller may not operate on formations in the project, or it lacks an action a resource this template declares requires. `error.meta.denied_actions` names every missing action; nothing is applied."
          },
          "404": {
            "description": "Not Found"
          }
        }
      },
      "delete": {
        "tags": [
          "Formations"
        ],
        "summary": "Delete an formation",
        "description": "Deletes the formation stack and all its managed resources in reverse dependency order.\n\nA resource the platform refuses to delete on its own — most often an agent that has generation or trace history — fails the teardown with `409 FORMATION_DELETE_FAILED`, naming every blocking resource in `error.meta.failures`. Resolve the blockers (for an agent, `DELETE /api/v1/agents/{agent_id}?force=true` also removes its generations and traces, and `deletion_policy: retain` exempts it from teardown entirely) and delete the formation again.\n\nA refusal the platform can foresee is found by a pre-flight, before the first delete: nothing is removed, and the formation stays `active` and intact for the retry. An unforeseeable error surfaces mid-teardown instead, where resources deleted before the blocker stay deleted and the formation is left in `delete_failed`. The error message states which happened.\n",
        "operationId": "deleteFormation",
        "parameters": [
          {
            "name": "formation_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "example": "form_V1StGXR8Z5jdHi6B"
          }
        ],
        "responses": {
          "200": {
            "description": "Deleted",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean"
                    }
                  },
                  "required": [
                    "success"
                  ]
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized"
          },
          "403": {
            "description": "Forbidden — either the caller may not operate on formations in the project, or it lacks an action a resource this template declares requires. `error.meta.denied_actions` names every missing action; nothing is applied."
          },
          "404": {
            "description": "Not Found"
          },
          "409": {
            "description": "One or more resources could not be deleted (`FORMATION_DELETE_FAILED`). `error.meta.failures` lists each one as `{ logical_id, resource_type, error }`. The `message` says whether the pre-flight caught it (nothing deleted, formation still `active`) or it surfaced mid-teardown (formation left in `delete_failed`).\n"
          }
        }
      }
    },
    "/api/v1/formations/{formation_id}/events": {
      "get": {
        "tags": [
          "Formations"
        ],
        "summary": "List formation operation events",
        "description": "Returns all operations (create, update, delete) with their event logs for the formation, ordered chronologically.\n",
        "operationId": "listFormationEvents",
        "parameters": [
          {
            "name": "formation_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "example": "form_V1StGXR8Z5jdHi6B"
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Maximum number of results to return",
            "schema": {
              "type": "integer",
              "default": 50
            }
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "description": "Number of results to skip",
            "schema": {
              "type": "integer",
              "default": 0
            }
          }
        ],
        "responses": {
          "200": {
            "description": "List of operations",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "total",
                    "limit",
                    "offset"
                  ],
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/FormationOperation"
                      }
                    },
                    "total": {
                      "type": "integer"
                    },
                    "limit": {
                      "type": "integer"
                    },
                    "offset": {
                      "type": "integer"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized"
          },
          "403": {
            "description": "Forbidden"
          },
          "404": {
            "description": "Not Found"
          }
        }
      }
    },
    "/api/v1/generations": {
      "get": {
        "tags": [
          "Generations"
        ],
        "summary": "List generations",
        "description": "Returns generations the caller can access, optionally filtered by agent, trace, orchestration run, node, and status. Replaces the former per-trace generations endpoint (use the trace_id query filter).\n\nFiltering by `orchestration_run_id` is the supported way to get from an orchestration run to the generations its agent nodes produced: a node execution record carries no generation id, so the pointer lives here, alongside the run's other attribution columns.\n",
        "operationId": "listGenerations",
        "parameters": [
          {
            "name": "agent_id",
            "in": "query",
            "required": false,
            "description": "Filter by agent public ID",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "trace_id",
            "in": "query",
            "required": false,
            "description": "Filter by trace public ID",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "initiator_generation_id",
            "in": "query",
            "required": false,
            "description": "Filter by the public ID of the parent generation. Returns all generations triggered by that generation — sub-agent invocations. Null-initiated (top-level) generations are not returned.\n",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "chain_id",
            "in": "query",
            "required": false,
            "description": "Filter by the continuation chain the generation belongs to. This is how a chain is expanded into its members — the chain record carries only their count.\n",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "orchestration_run_id",
            "in": "query",
            "required": false,
            "description": "Filter by the orchestration run that dispatched the generation. This is how a run is traced back to what its agent nodes did — a node execution record stores no generation id.\n",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "node_id",
            "in": "query",
            "required": false,
            "description": "Filter by the orchestration node that dispatched the generation. Combine with `orchestration_run_id` to narrow to one node of one run; a retried node returns one generation per `node_attempt`.\n",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "status",
            "in": "query",
            "required": false,
            "description": "Filter by lifecycle status",
            "schema": {
              "type": "string",
              "enum": [
                "in_progress",
                "requires_action",
                "completed",
                "failed"
              ]
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "default": 50
            }
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "default": 0
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated list of generations",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Generation"
                      }
                    },
                    "total": {
                      "type": "integer"
                    },
                    "limit": {
                      "type": "integer"
                    },
                    "offset": {
                      "type": "integer"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/generations/{generation_id}": {
      "get": {
        "tags": [
          "Generations"
        ],
        "summary": "Get a generation",
        "description": "Returns a single generation record by ID, including its status and the structured `error` payload when the generation failed (e.g. because the upstream AI provider returned an error).\n",
        "operationId": "getGeneration",
        "parameters": [
          {
            "name": "generation_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Public ID of the generation"
          }
        ],
        "responses": {
          "200": {
            "description": "Generation details",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Generation"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Generation not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      },
      "patch": {
        "tags": [
          "Generations"
        ],
        "summary": "Update generation metadata",
        "description": "Attaches caller-supplied key/value metadata to a generation record for per-run audit attribution (e.g. recording which knowledge-corpus version produced an AI action). The provided keys are shallow-merged over the existing `metadata`, so repeated patches accumulate. The bag is caller-owned and no key is reserved: server-owned state (usage attribution, the served agent version, the route's record, the extraction summary) lives in its own top-level fields and cannot be written from here.\n",
        "operationId": "updateGeneration",
        "parameters": [
          {
            "name": "generation_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Public ID of the generation"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateGenerationRequest"
              },
              "examples": {
                "audit": {
                  "summary": "Attach caller audit metadata",
                  "value": {
                    "metadata": {
                      "team": "payments",
                      "ticket_id": "OPS-4821"
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated generation",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Generation"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request (e.g. metadata is not a JSON object)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Generation not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/generations/{generation_id}/content": {
      "delete": {
        "tags": [
          "Generations"
        ],
        "summary": "Purge generation content",
        "description": "Clears the generation's content — `metadata`, `error`, `extraction`, and the internal recovery state of a paused run — and stamps `content_redacted_at`.\n\nThe usage and audit skeleton is preserved: ids, timestamps, status, stop reason, and the attribution fields (`action_id`, `trigger_id`, `orchestration_run_id`, `node_id`, `node_attempt`, `agent_version`, `routing`) the billing ledger reads. A purged generation reads back as that skeleton, not a 404.\n\nThis does **not** delete the parent trace's steps object, which holds this generation's content alongside its siblings'. To erase the run's content completely, purge the trace (`DELETE /api/v1/traces/{trace_id}/content`), which cascades here.\n\nIdempotent — purging an already-purged generation succeeds and leaves the original `content_redacted_at` in place.\n",
        "operationId": "purgeGenerationContent",
        "parameters": [
          {
            "name": "generation_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Public ID of the generation"
          }
        ],
        "responses": {
          "200": {
            "description": "The purged generation skeleton",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Generation"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Generation not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/generations/{generation_id}/transcript": {
      "get": {
        "tags": [
          "Generations"
        ],
        "summary": "Get a generation's transcript",
        "description": "Returns one generation's turn read back as an ordered sequence of steps: what it was asked, each model step with its tool calls and results, and how it ended.\n\nThe transcript is assembled at read time from the generation record and the trace's steps object; nothing is stored, so it cannot outlive the content it projects. Requires `traces:GetTrace` in addition to `generations:GetGeneration`, because the response merges content from both resources.\n\nA generation whose content is unavailable — never written under zero-retention, or cleared by a purge — returns `200` with the skeleton rather than an error: `input` and `output` are null, `steps` is empty, and the `content_redacted_*` fields say which happened. `content_redacted_by_principal_id` is `zero_retention` when the content was never stored, and the purging principal's ID when it was erased later. A generation that is still running returns the same shape with an empty `steps`; `status` disambiguates the two.\n",
        "operationId": "getGenerationTranscript",
        "parameters": [
          {
            "name": "generation_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Public ID of the generation"
          }
        ],
        "responses": {
          "200": {
            "description": "The generation's transcript",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GenerationTranscript"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Generation not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/guardrails": {
      "post": {
        "tags": [
          "Guardrails"
        ],
        "summary": "Create a guardrail",
        "description": "Creates a new guardrail in the project, archiving its document as version 1. The `document` is validated on write: `class` must be a literal (A/B/C/D) or a JSON Logic expression, and every variable it (and `guard`) reference must resolve to the `args.*` / `context.*` / `runtime.*` namespaces — an out-of-catalog `runtime.*` key is rejected with 400.\n",
        "operationId": "createGuardrail",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateGuardrailRequest"
              },
              "examples": {
                "always_approve": {
                  "summary": "Always require human sign-off",
                  "value": {
                    "name": "Sign-off Guardrail",
                    "document": {
                      "class": "C"
                    }
                  }
                },
                "budget_threshold": {
                  "summary": "Class B below a threshold, C at or above, guarded by 24h spend",
                  "value": {
                    "name": "Budget Update Guardrail",
                    "document": {
                      "default_class": "C",
                      "class": {
                        "if": [
                          {
                            "<": [
                              {
                                "var": "args.amount"
                              },
                              500
                            ]
                          },
                          "B",
                          "C"
                        ]
                      },
                      "guard": {
                        "<": [
                          {
                            "var": "runtime.usage.cost_usd_24h"
                          },
                          1000
                        ]
                      }
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Guardrail created",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Guardrail"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request — invalid document or variable reference",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      },
      "get": {
        "tags": [
          "Guardrails"
        ],
        "summary": "List guardrails",
        "description": "Returns all guardrails in the project.",
        "operationId": "listGuardrails",
        "parameters": [
          {
            "name": "project_id",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Project public ID to filter by"
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Maximum number of results to return",
            "schema": {
              "type": "integer",
              "default": 50
            }
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "description": "Number of results to skip",
            "schema": {
              "type": "integer",
              "default": 0
            }
          }
        ],
        "responses": {
          "200": {
            "description": "List of guardrails",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "total",
                    "limit",
                    "offset"
                  ],
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Guardrail"
                      }
                    },
                    "total": {
                      "type": "integer"
                    },
                    "limit": {
                      "type": "integer"
                    },
                    "offset": {
                      "type": "integer"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/guardrails/{guardrail_id}": {
      "get": {
        "tags": [
          "Guardrails"
        ],
        "summary": "Get a guardrail",
        "description": "Returns a single guardrail by ID.",
        "operationId": "getGuardrail",
        "parameters": [
          {
            "name": "guardrail_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Guardrail",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Guardrail"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      },
      "patch": {
        "tags": [
          "Guardrails"
        ],
        "summary": "Update a guardrail",
        "description": "Updates an existing guardrail. A `document` write that actually changes the policy increments `version` and archives the new document as a GuardrailVersion; metadata-only edits (name / description / context), and re-writing the document the guardrail already holds, leave the version untouched.\n",
        "operationId": "updateGuardrail",
        "parameters": [
          {
            "name": "guardrail_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateGuardrailRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Guardrail updated",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Guardrail"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request — invalid document or variable reference",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      },
      "delete": {
        "tags": [
          "Guardrails"
        ],
        "summary": "Delete a guardrail",
        "description": "Deletes a guardrail and its archived versions by ID.",
        "operationId": "deleteGuardrail",
        "parameters": [
          {
            "name": "guardrail_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "Deleted"
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/guardrails/{guardrail_id}/versions": {
      "get": {
        "tags": [
          "Guardrails"
        ],
        "summary": "List a guardrail's config versions",
        "description": "Returns the guardrail's archived configurations, newest first. A version is written on create and on every subsequent write that changes the policy `document` — through the REST API or a formation apply alike. Metadata-only edits (name, description, context binding) do not archive a version. See [Versioning](/docs/modules/guardrails#versioning).\n",
        "operationId": "listGuardrailVersions",
        "parameters": [
          {
            "name": "guardrail_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Maximum number of results to return",
            "schema": {
              "type": "integer",
              "default": 50
            }
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "description": "Number of results to skip",
            "schema": {
              "type": "integer",
              "default": 0
            }
          }
        ],
        "responses": {
          "200": {
            "description": "List of guardrail versions, newest first",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "total",
                    "limit",
                    "offset"
                  ],
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/GuardrailVersion"
                      }
                    },
                    "total": {
                      "type": "integer"
                    },
                    "limit": {
                      "type": "integer"
                    },
                    "offset": {
                      "type": "integer"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Guardrail not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/guardrails/{guardrail_id}/versions/{version}": {
      "get": {
        "tags": [
          "Guardrails"
        ],
        "summary": "Fetch an archived guardrail version",
        "description": "Returns the exact configuration — and so the exact `document` — that governed at a given version. Approval items, activity entries, and exceptions record the version that governed them, so the audit chain survives edits.\n",
        "operationId": "getGuardrailVersion",
        "parameters": [
          {
            "name": "guardrail_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "version",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer",
              "minimum": 1
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Archived guardrail version",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GuardrailVersion"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request — version is not a positive integer",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/guardrails/{guardrail_id}/versions/{version}/restore": {
      "post": {
        "tags": [
          "Guardrails"
        ],
        "summary": "Restore an archived guardrail config",
        "description": "Writes an archived version's `document` back as the guardrail's live policy, which archives it again as a **new** version rather than rewinding the counter — so an approval item or exception citing any version in between still resolves.\n\nThe restore runs through the ordinary update path, so the archived document is re-validated; restoring the policy the guardrail already holds is a no-op and archives nothing.\n",
        "operationId": "restoreGuardrailVersion",
        "parameters": [
          {
            "name": "guardrail_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "version",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer",
              "minimum": 1
            }
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/RestoreGuardrailVersionRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The guardrail, at its new version",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Guardrail"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request — invalid version, or the archived document no longer validates",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Guardrail or version not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/guardrails/{guardrail_id}/evaluate": {
      "post": {
        "tags": [
          "Guardrails"
        ],
        "summary": "Dry-run evaluate a guardrail",
        "description": "Runs the full evaluation pipeline — the `class` expression, the guard, the context tool per `context_mode`, live `runtime.*` resolution — against caller-supplied `args` and `guardrail_context`, and returns the exact `guardrail_evaluation` record a real call would produce. Nothing executes, no approval item is filed, and no activity entry is written. This is the adoption path: preview a document's decisions against production-shaped calls before attaching it — or before editing a widely-attached one. Pass an optional `tool_id` to resolve `runtime.tool.*`.\n",
        "operationId": "evaluateGuardrail",
        "parameters": [
          {
            "name": "guardrail_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "args": {
                    "type": "object",
                    "additionalProperties": true,
                    "description": "The proposed call's arguments (the `args.*` namespace)."
                  },
                  "guardrail_context": {
                    "type": "object",
                    "additionalProperties": true,
                    "description": "The caller-supplied guardrail context (the `context.*` namespace), combined with the context tool per `context_mode`.\n"
                  },
                  "tool_id": {
                    "type": "string",
                    "description": "Optional tool to resolve `runtime.tool.*` against."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The would-be evaluation record (nothing executed)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GuardrailEvaluation"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/ingestion-rules": {
      "get": {
        "tags": [
          "Ingestion Rules"
        ],
        "summary": "List ingestion rules",
        "description": "Returns the ingestion rules for a project",
        "operationId": "listIngestionRules",
        "parameters": [
          {
            "name": "project_id",
            "in": "query",
            "description": "Project ID (required if not using project key auth)",
            "schema": {
              "type": "string",
              "example": "proj_V1StGXR8Z5jdHi6B"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "description": "Number of results per page",
            "schema": {
              "type": "integer",
              "default": 25
            }
          },
          {
            "name": "offset",
            "in": "query",
            "description": "Number of results to skip",
            "schema": {
              "type": "integer",
              "default": 0
            }
          }
        ],
        "responses": {
          "200": {
            "description": "List of ingestion rules",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "total",
                    "limit",
                    "offset"
                  ],
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/IngestionRule"
                      }
                    },
                    "total": {
                      "type": "integer"
                    },
                    "limit": {
                      "type": "integer"
                    },
                    "offset": {
                      "type": "integer"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized"
          },
          "403": {
            "description": "Forbidden"
          },
          "500": {
            "description": "Internal server error"
          }
        }
      },
      "post": {
        "tags": [
          "Ingestion Rules"
        ],
        "summary": "Create an ingestion rule",
        "description": "Creates a rule mapping a content_type glob to a converter. Exactly one of tool_id or agent_id must be set.",
        "operationId": "createIngestionRule",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "content_type_glob"
                ],
                "properties": {
                  "project_id": {
                    "x-soat-ref": "projects",
                    "type": "string",
                    "description": "Project ID (required if not using project key auth)",
                    "example": "proj_V1StGXR8Z5jdHi6B"
                  },
                  "content_type_glob": {
                    "type": "string",
                    "description": "MIME type glob matched against a file's content_type",
                    "example": "image/*"
                  },
                  "tool_id": {
                    "x-soat-ref": "tools",
                    "type": "string",
                    "description": "Converter tool id (mutually exclusive with agent_id)",
                    "example": "tool_V1StGXR8Z5jdHi6B"
                  },
                  "agent_id": {
                    "x-soat-ref": "agents",
                    "type": "string",
                    "description": "Converter agent id (mutually exclusive with tool_id)",
                    "example": "agent_V1StGXR8Z5jdHi6B"
                  },
                  "action": {
                    "type": "string",
                    "description": "Operation id, required for builtin/mcp tool converters"
                  },
                  "preset_parameters": {
                    "type": "object",
                    "description": "Merged into the tool input before invocation (tool converters only)"
                  },
                  "native_extraction": {
                    "type": "string",
                    "enum": [
                      "first",
                      "skip"
                    ],
                    "description": "For native types (PDF/text): `first` (default) converts only when native extraction yields no text; `skip` always converts."
                  },
                  "file_delivery": {
                    "type": "string",
                    "enum": [
                      "base64",
                      "download_url"
                    ],
                    "description": "How the file reaches a tool converter (default base64)"
                  },
                  "chunk_strategy": {
                    "type": "string",
                    "enum": [
                      "page",
                      "whole",
                      "size"
                    ],
                    "description": "Default chunk strategy, overridable per ingest request"
                  },
                  "chunk_size": {
                    "type": "integer",
                    "description": "Default window size in characters for the size strategy"
                  },
                  "chunk_overlap": {
                    "type": "integer",
                    "description": "Default overlap in characters for the size strategy"
                  },
                  "metadata": {
                    "type": "object",
                    "description": "Arbitrary JSON metadata"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Ingestion rule created",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/IngestionRule"
                }
              }
            }
          },
          "400": {
            "description": "Validation failed (e.g. tool_id and agent_id both set or both missing)"
          },
          "401": {
            "description": "Unauthorized"
          },
          "403": {
            "description": "Forbidden"
          },
          "409": {
            "description": "A rule for this content_type_glob already exists in the project"
          },
          "500": {
            "description": "Internal server error"
          }
        }
      }
    },
    "/api/v1/ingestion-rules/{ingestion_rule_id}": {
      "get": {
        "tags": [
          "Ingestion Rules"
        ],
        "summary": "Get an ingestion rule",
        "description": "Returns a specific ingestion rule",
        "operationId": "getIngestionRule",
        "parameters": [
          {
            "name": "ingestion_rule_id",
            "in": "path",
            "required": true,
            "description": "Ingestion rule ID",
            "schema": {
              "type": "string",
              "example": "igr_V1StGXR8Z5jdHi6B"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Ingestion rule details",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/IngestionRule"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized"
          },
          "403": {
            "description": "Forbidden"
          },
          "404": {
            "description": "Ingestion rule not found"
          }
        }
      },
      "patch": {
        "tags": [
          "Ingestion Rules"
        ],
        "summary": "Update an ingestion rule",
        "description": "Updates fields of an ingestion rule",
        "operationId": "updateIngestionRule",
        "parameters": [
          {
            "name": "ingestion_rule_id",
            "in": "path",
            "required": true,
            "description": "Ingestion rule ID",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "content_type_glob": {
                    "type": "string"
                  },
                  "tool_id": {
                    "x-soat-ref": "tools",
                    "type": "string",
                    "nullable": true
                  },
                  "agent_id": {
                    "x-soat-ref": "agents",
                    "type": "string",
                    "nullable": true
                  },
                  "action": {
                    "type": "string",
                    "nullable": true
                  },
                  "preset_parameters": {
                    "type": "object",
                    "nullable": true
                  },
                  "native_extraction": {
                    "type": "string",
                    "enum": [
                      "first",
                      "skip"
                    ]
                  },
                  "file_delivery": {
                    "type": "string",
                    "enum": [
                      "base64",
                      "download_url"
                    ]
                  },
                  "chunk_strategy": {
                    "type": "string",
                    "enum": [
                      "page",
                      "whole",
                      "size",
                      null
                    ],
                    "nullable": true,
                    "description": "Send `null` to clear the rule's override and fall back to the per-request default."
                  },
                  "chunk_size": {
                    "type": "integer",
                    "nullable": true
                  },
                  "chunk_overlap": {
                    "type": "integer",
                    "nullable": true
                  },
                  "metadata": {
                    "type": "object",
                    "nullable": true
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Ingestion rule updated",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/IngestionRule"
                }
              }
            }
          },
          "400": {
            "description": "Validation failed"
          },
          "401": {
            "description": "Unauthorized"
          },
          "403": {
            "description": "Forbidden"
          },
          "404": {
            "description": "Ingestion rule not found"
          },
          "409": {
            "description": "A rule for this content_type_glob already exists in the project"
          }
        }
      },
      "delete": {
        "tags": [
          "Ingestion Rules"
        ],
        "summary": "Delete an ingestion rule",
        "description": "Deletes an ingestion rule",
        "operationId": "deleteIngestionRule",
        "parameters": [
          {
            "name": "ingestion_rule_id",
            "in": "path",
            "required": true,
            "description": "Ingestion rule ID",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "Ingestion rule deleted"
          },
          "401": {
            "description": "Unauthorized"
          },
          "403": {
            "description": "Forbidden"
          },
          "404": {
            "description": "Ingestion rule not found"
          }
        }
      }
    },
    "/api/v1/knowledge/search": {
      "post": {
        "tags": [
          "Knowledge"
        ],
        "summary": "Search knowledge",
        "description": "Searches across documents and memory entries using semantic search, file paths, document IDs, or memory IDs/tags. At least one of `query`, `document_paths`, `document_ids`, `memory_ids`, or `memory_tags` must be provided.",
        "operationId": "searchKnowledge",
        "x-iam-action": "knowledge:SearchKnowledge",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "project_id": {
                    "x-soat-ref": "projects",
                    "type": "string",
                    "description": "Limit search to a specific project",
                    "example": "proj_V1StGXR8Z5jdHi6B"
                  },
                  "query": {
                    "type": "string",
                    "description": "Semantic search query text",
                    "example": "customer communication preferences"
                  },
                  "min_score": {
                    "type": "number",
                    "description": "Minimum `score` a result must reach to be returned. Filters on the implementation-defined `score`, not on `similarity_score`, so the cutoff follows the ranking. Only applies when `query` is provided. Because the scale behind `score` is not part of the contract, treat a tuned value as tied to the deployment rather than portable.",
                    "minimum": 0,
                    "maximum": 1,
                    "example": 0.5
                  },
                  "limit": {
                    "type": "integer",
                    "description": "Maximum number of results to return (default 10)",
                    "minimum": 1,
                    "example": 10
                  },
                  "memory_ids": {
                    "x-soat-ref": "memories",
                    "type": "array",
                    "description": "Search entries within these specific memories",
                    "items": {
                      "type": "string"
                    },
                    "example": [
                      "mem_V1StGXR8Z5jdHi6B"
                    ]
                  },
                  "memory_tags": {
                    "type": "array",
                    "description": "Search entries in memories whose tags match any of these patterns (glob supported)",
                    "items": {
                      "type": "string"
                    },
                    "example": [
                      "customer*",
                      "support"
                    ]
                  },
                  "document_paths": {
                    "type": "array",
                    "description": "Filter results to documents whose file path starts with one of these prefixes",
                    "items": {
                      "type": "string"
                    },
                    "example": [
                      "/sales/",
                      "/hr/"
                    ]
                  },
                  "document_ids": {
                    "x-soat-ref": "documents",
                    "type": "array",
                    "description": "Filter results to specific document IDs",
                    "items": {
                      "type": "string"
                    },
                    "example": [
                      "doc_V1StGXR8Z5jdHi6B"
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Search results",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "results"
                  ],
                  "properties": {
                    "results": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/KnowledgeResult"
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Bad request — at least one search parameter is required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/memories": {
      "get": {
        "tags": [
          "Memories"
        ],
        "summary": "List memories",
        "description": "Returns a list of memory configurations for a project",
        "operationId": "listMemories",
        "parameters": [
          {
            "name": "project_id",
            "in": "query",
            "description": "Project ID (required if not using project key auth)",
            "schema": {
              "type": "string",
              "example": "proj_V1StGXR8Z5jdHi6B"
            }
          },
          {
            "name": "tags",
            "in": "query",
            "description": "Filter memories by tag patterns. Supports glob syntax (`*` matches any substring, `?` matches any single character). Multiple values are ORed — a memory is returned if any of its tags match any of the provided patterns. Omit to return all memories.\n",
            "schema": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "style": "form",
            "explode": true,
            "example": [
              "customer*",
              "support"
            ]
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Maximum number of results to return",
            "schema": {
              "type": "integer",
              "default": 50
            }
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "description": "Number of results to skip",
            "schema": {
              "type": "integer",
              "default": 0
            }
          }
        ],
        "responses": {
          "200": {
            "description": "List of memories",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "total",
                    "limit",
                    "offset"
                  ],
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Memory"
                      }
                    },
                    "total": {
                      "type": "integer"
                    },
                    "limit": {
                      "type": "integer"
                    },
                    "offset": {
                      "type": "integer"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized"
          },
          "403": {
            "description": "Forbidden"
          },
          "500": {
            "description": "Internal server error"
          }
        }
      },
      "post": {
        "tags": [
          "Memories"
        ],
        "summary": "Create a memory",
        "description": "Creates a new memory configuration in a project",
        "operationId": "createMemory",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "name"
                ],
                "properties": {
                  "project_id": {
                    "x-soat-ref": "projects",
                    "type": "string",
                    "description": "Project ID (required if not using project key auth)",
                    "example": "proj_V1StGXR8Z5jdHi6B"
                  },
                  "name": {
                    "type": "string",
                    "description": "Memory name",
                    "example": "Product Documentation"
                  },
                  "description": {
                    "type": "string",
                    "description": "Optional description",
                    "example": "Retrieves product docs for support queries"
                  },
                  "tags": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "description": "Optional list of tags for filtering in knowledge search",
                    "example": [
                      "projectA",
                      "customer-support"
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Memory created",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Memory"
                }
              }
            }
          },
          "400": {
            "description": "Bad request (missing required fields or invalid config)"
          },
          "401": {
            "description": "Unauthorized"
          },
          "403": {
            "description": "Forbidden"
          },
          "500": {
            "description": "Internal server error"
          }
        }
      }
    },
    "/api/v1/memories/{memory_id}": {
      "get": {
        "tags": [
          "Memories"
        ],
        "summary": "Get a memory",
        "description": "Returns a single memory configuration by ID",
        "operationId": "getMemory",
        "parameters": [
          {
            "name": "memory_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "example": "mem_V1StGXR8Z5jdHi6B"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Memory found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Memory"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized"
          },
          "403": {
            "description": "Forbidden"
          },
          "404": {
            "description": "Memory not found"
          },
          "500": {
            "description": "Internal server error"
          }
        }
      },
      "put": {
        "tags": [
          "Memories"
        ],
        "summary": "Update a memory",
        "description": "Updates an existing memory configuration",
        "operationId": "updateMemory",
        "parameters": [
          {
            "name": "memory_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "example": "mem_V1StGXR8Z5jdHi6B"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string",
                    "description": "Memory name"
                  },
                  "description": {
                    "type": "string",
                    "nullable": true,
                    "description": "Optional description"
                  },
                  "tags": {
                    "type": "array",
                    "nullable": true,
                    "items": {
                      "type": "string"
                    },
                    "description": "Optional list of tags for filtering in knowledge search"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Memory updated",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Memory"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized"
          },
          "403": {
            "description": "Forbidden"
          },
          "404": {
            "description": "Memory not found"
          },
          "500": {
            "description": "Internal server error"
          }
        }
      },
      "delete": {
        "tags": [
          "Memories"
        ],
        "summary": "Delete a memory",
        "description": "Deletes a memory configuration",
        "operationId": "deleteMemory",
        "parameters": [
          {
            "name": "memory_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "example": "mem_V1StGXR8Z5jdHi6B"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "Memory deleted"
          },
          "401": {
            "description": "Unauthorized"
          },
          "403": {
            "description": "Forbidden"
          },
          "404": {
            "description": "Memory not found"
          },
          "500": {
            "description": "Internal server error"
          }
        }
      }
    },
    "/api/v1/memory-entries": {
      "get": {
        "tags": [
          "Memory Entries"
        ],
        "summary": "List memory entries",
        "description": "Returns all entries in a memory container",
        "operationId": "listMemoryEntries",
        "x-iam-action": "memories:ListMemoryEntries",
        "parameters": [
          {
            "name": "memory_id",
            "in": "query",
            "required": true,
            "description": "Memory container to list entries from (mem_...)",
            "schema": {
              "type": "string",
              "example": "mem_V1StGXR8Z5jdHi6B"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Maximum number of results to return",
            "schema": {
              "type": "integer",
              "default": 50
            }
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "description": "Number of results to skip",
            "schema": {
              "type": "integer",
              "default": 0
            }
          },
          {
            "name": "include_invalidated",
            "in": "query",
            "required": false,
            "description": "Include invalidated (superseded) entries. They are excluded by default; set this to audit the supersede history.",
            "schema": {
              "type": "boolean",
              "default": false
            }
          }
        ],
        "responses": {
          "200": {
            "description": "List of memory entries",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "total",
                    "limit",
                    "offset"
                  ],
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/MemoryEntry"
                      }
                    },
                    "total": {
                      "type": "integer"
                    },
                    "limit": {
                      "type": "integer"
                    },
                    "offset": {
                      "type": "integer"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized"
          },
          "403": {
            "description": "Forbidden"
          },
          "404": {
            "description": "Memory not found"
          },
          "500": {
            "description": "Internal server error"
          }
        }
      },
      "post": {
        "tags": [
          "Memory Entries"
        ],
        "summary": "Create a memory entry",
        "description": "Creates a new entry in the specified memory container. Automatically generates an embedding for semantic search, and skips the write when an existing entry is a near-duplicate (see `duplicate_threshold`). A merely similar fact is stored as its own entry: this path has no agent context and therefore no model to consolidate two facts into one.",
        "operationId": "createMemoryEntry",
        "x-iam-action": "memories:CreateMemoryEntry",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "memory_id",
                  "content"
                ],
                "properties": {
                  "memory_id": {
                    "x-soat-ref": "memories",
                    "type": "string",
                    "description": "Memory container to add the entry to (mem_...)",
                    "example": "mem_V1StGXR8Z5jdHi6B"
                  },
                  "content": {
                    "type": "string",
                    "description": "The text content of the memory entry",
                    "example": "The customer prefers email communication over phone calls"
                  },
                  "source_type": {
                    "type": "string",
                    "enum": [
                      "manual",
                      "agent",
                      "extraction",
                      "orchestration"
                    ],
                    "description": "How this entry was created",
                    "default": "manual",
                    "example": "manual"
                  },
                  "tags": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "description": "Per-entry tag strings, used for entry-granularity filtering in search-knowledge (memory_tags)",
                    "example": [
                      "role:traffic-manager",
                      "source:rejected_approval"
                    ]
                  },
                  "metadata": {
                    "type": "object",
                    "additionalProperties": true,
                    "description": "Arbitrary structured metadata attached to the entry",
                    "example": {
                      "evidence": "high",
                      "quarter": "Q3"
                    }
                  },
                  "duplicate_threshold": {
                    "type": "number",
                    "description": "Cosine similarity score at or above which the incoming content is considered a duplicate of an existing entry and skipped (default 0.95). Below it the entry is always created.",
                    "default": 0.95,
                    "minimum": 0,
                    "maximum": 1
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Memory entry deduplicated (action is \"skipped\")",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MemoryEntryWriteResult"
                }
              }
            }
          },
          "201": {
            "description": "Memory entry created",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MemoryEntryWriteResult"
                }
              }
            }
          },
          "400": {
            "description": "Bad request (missing required fields)"
          },
          "401": {
            "description": "Unauthorized"
          },
          "403": {
            "description": "Forbidden"
          },
          "404": {
            "description": "Memory not found"
          },
          "500": {
            "description": "Internal server error"
          }
        }
      }
    },
    "/api/v1/memory-entries/{entry_id}": {
      "get": {
        "tags": [
          "Memory Entries"
        ],
        "summary": "Get a memory entry",
        "description": "Returns a single memory entry by ID",
        "operationId": "getMemoryEntry",
        "x-iam-action": "memories:GetMemoryEntry",
        "parameters": [
          {
            "name": "entry_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "example": "mem_entry_V1StGXR8Z5jdHi6B"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Memory entry found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MemoryEntry"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized"
          },
          "403": {
            "description": "Forbidden"
          },
          "404": {
            "description": "Memory or entry not found"
          },
          "500": {
            "description": "Internal server error"
          }
        }
      },
      "put": {
        "tags": [
          "Memory Entries"
        ],
        "summary": "Update a memory entry",
        "description": "Updates an existing memory entry. Regenerates the embedding if content changes.",
        "operationId": "updateMemoryEntry",
        "x-iam-action": "memories:UpdateMemoryEntry",
        "parameters": [
          {
            "name": "entry_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "example": "mem_entry_V1StGXR8Z5jdHi6B"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "content": {
                    "type": "string",
                    "description": "Updated text content"
                  },
                  "tags": {
                    "type": "array",
                    "nullable": true,
                    "items": {
                      "type": "string"
                    },
                    "description": "Replaces the entry's tags. Pass null or an empty array to clear.",
                    "example": [
                      "role:traffic-manager"
                    ]
                  },
                  "metadata": {
                    "type": "object",
                    "nullable": true,
                    "additionalProperties": true,
                    "description": "Replaces the entry's metadata. Pass null to clear."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Memory entry updated",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MemoryEntry"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized"
          },
          "403": {
            "description": "Forbidden"
          },
          "404": {
            "description": "Memory or entry not found"
          },
          "500": {
            "description": "Internal server error"
          }
        }
      },
      "delete": {
        "tags": [
          "Memory Entries"
        ],
        "summary": "Delete a memory entry",
        "description": "Deletes a memory entry",
        "operationId": "deleteMemoryEntry",
        "x-iam-action": "memories:DeleteMemoryEntry",
        "parameters": [
          {
            "name": "entry_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "example": "mem_entry_V1StGXR8Z5jdHi6B"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "Memory entry deleted"
          },
          "401": {
            "description": "Unauthorized"
          },
          "403": {
            "description": "Forbidden"
          },
          "404": {
            "description": "Memory or entry not found"
          },
          "500": {
            "description": "Internal server error"
          }
        }
      }
    },
    "/api/v1/model-routes": {
      "get": {
        "tags": [
          "Model Routes"
        ],
        "summary": "List model routes",
        "description": "Returns the model routes defined in a project",
        "operationId": "listModelRoutes",
        "parameters": [
          {
            "name": "project_id",
            "in": "query",
            "description": "Project ID (required if not using project key auth)",
            "schema": {
              "type": "string",
              "example": "proj_V1StGXR8Z5jdHi6B"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Maximum number of results to return",
            "schema": {
              "type": "integer",
              "default": 50
            }
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "description": "Number of results to skip",
            "schema": {
              "type": "integer",
              "default": 0
            }
          }
        ],
        "responses": {
          "200": {
            "description": "List of model routes",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "total",
                    "limit",
                    "offset"
                  ],
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/ModelRoute"
                      }
                    },
                    "total": {
                      "type": "integer"
                    },
                    "limit": {
                      "type": "integer"
                    },
                    "offset": {
                      "type": "integer"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized"
          },
          "403": {
            "description": "Forbidden"
          },
          "500": {
            "description": "Internal server error"
          }
        }
      },
      "post": {
        "tags": [
          "Model Routes"
        ],
        "summary": "Create a model route",
        "description": "Creates a project-scoped model route: a named, ordered list of provider+model targets tried in array order. Every target must reference an AI provider in the same project (400 otherwise), and the total attempt budget — the sum of `1 + max_retries` over all targets — may not exceed 10 (400 naming the computed total). A duplicate `name` in the project is rejected with 409.",
        "operationId": "createModelRoute",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "name",
                  "targets"
                ],
                "properties": {
                  "project_id": {
                    "x-soat-ref": "projects",
                    "type": "string",
                    "description": "Project ID (required if not using project key auth)",
                    "example": "proj_V1StGXR8Z5jdHi6B"
                  },
                  "name": {
                    "type": "string",
                    "description": "Human-readable name, unique per project",
                    "example": "primary-with-fallback"
                  },
                  "targets": {
                    "type": "array",
                    "minItems": 1,
                    "description": "Ordered failover targets. Position is priority: target 0 is tried first, and a retryable failure falls through to the next target.",
                    "items": {
                      "$ref": "#/components/schemas/ModelRouteTarget"
                    }
                  },
                  "retry_on": {
                    "type": "array",
                    "description": "Which failure classes are failover-eligible. A failure whose class is not listed (and every deterministic failure — 400-class, auth, content policy) fails the generation immediately instead of spending another target's budget.",
                    "items": {
                      "type": "string",
                      "enum": [
                        "provider_error",
                        "timeout",
                        "rate_limited"
                      ]
                    },
                    "default": [
                      "provider_error",
                      "timeout",
                      "rate_limited"
                    ]
                  },
                  "failure_threshold": {
                    "type": "integer",
                    "default": 3,
                    "description": "Consecutive retryable failures after which a target is skipped for `cooldown_seconds`. Breaker state is in-process per node and keyed by (provider, model), so it is shared by every route pointing at the same backend.",
                    "example": 3
                  },
                  "cooldown_seconds": {
                    "type": "integer",
                    "default": 60,
                    "description": "How long a tripped target is skipped before being probed again",
                    "example": 60
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Model route created successfully",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ModelRoute"
                }
              }
            }
          },
          "400": {
            "description": "Bad request (invalid targets, attempt cap exceeded, unknown provider)"
          },
          "401": {
            "description": "Unauthorized"
          },
          "403": {
            "description": "Forbidden"
          },
          "409": {
            "description": "A model route with this name already exists in the project"
          },
          "500": {
            "description": "Internal server error"
          }
        }
      }
    },
    "/api/v1/model-routes/{route_id}": {
      "get": {
        "tags": [
          "Model Routes"
        ],
        "summary": "Get a model route",
        "description": "Returns a specific model route",
        "operationId": "getModelRoute",
        "parameters": [
          {
            "name": "route_id",
            "in": "path",
            "required": true,
            "description": "Model route ID",
            "schema": {
              "type": "string",
              "example": "route_V1StGXR8Z5jdHi6B"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Model route details",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ModelRoute"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized"
          },
          "403": {
            "description": "Forbidden"
          },
          "404": {
            "description": "Model route not found"
          }
        }
      },
      "put": {
        "tags": [
          "Model Routes"
        ],
        "summary": "Update a model route",
        "description": "Updates a model route's name, targets, retry classes, or breaker configuration. Omitted fields are left unchanged.",
        "operationId": "updateModelRoute",
        "parameters": [
          {
            "name": "route_id",
            "in": "path",
            "required": true,
            "description": "Model route ID",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string",
                    "description": "New name (unique per project)"
                  },
                  "targets": {
                    "type": "array",
                    "minItems": 1,
                    "description": "Replacement target list (ordered)",
                    "items": {
                      "$ref": "#/components/schemas/ModelRouteTarget"
                    }
                  },
                  "retry_on": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "enum": [
                        "provider_error",
                        "timeout",
                        "rate_limited"
                      ]
                    }
                  },
                  "failure_threshold": {
                    "type": "integer"
                  },
                  "cooldown_seconds": {
                    "type": "integer"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Model route updated successfully",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ModelRoute"
                }
              }
            }
          },
          "400": {
            "description": "Bad request"
          },
          "401": {
            "description": "Unauthorized"
          },
          "403": {
            "description": "Forbidden"
          },
          "404": {
            "description": "Model route not found"
          },
          "409": {
            "description": "A model route with this name already exists in the project"
          }
        }
      },
      "delete": {
        "tags": [
          "Model Routes"
        ],
        "summary": "Delete a model route",
        "description": "Deletes a model route. Returns 409 when an agent still references it — a routed agent has no pinned provider to fall back on, so the reference must be repointed or the agent deleted first.",
        "operationId": "deleteModelRoute",
        "parameters": [
          {
            "name": "route_id",
            "in": "path",
            "required": true,
            "description": "Model route ID",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "Model route deleted successfully"
          },
          "401": {
            "description": "Unauthorized"
          },
          "403": {
            "description": "Forbidden"
          },
          "404": {
            "description": "Model route not found"
          },
          "409": {
            "description": "The model route is still referenced by one or more agents"
          }
        }
      }
    },
    "/.well-known/oauth-authorization-server": {
      "get": {
        "tags": [
          "OAuth"
        ],
        "summary": "Get authorization server metadata",
        "description": "Returns the [RFC 8414](https://www.rfc-editor.org/rfc/rfc8414) Authorization Server Metadata\ndocument: where `/authorize`, `/token` and `/register` are, which grants and PKCE methods are\nsupported, and which scopes exist.\n\nDiscovery precedes authentication by definition, so this endpoint takes no credential.\n",
        "operationId": "getOauthAuthorizationServerMetadata",
        "security": [],
        "responses": {
          "200": {
            "description": "Authorization server metadata",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OauthAuthorizationServerMetadata"
                }
              }
            }
          }
        }
      }
    },
    "/.well-known/oauth-protected-resource": {
      "get": {
        "tags": [
          "OAuth"
        ],
        "summary": "Get protected resource metadata",
        "description": "Returns the [RFC 9728](https://www.rfc-editor.org/rfc/rfc9728) Protected Resource Metadata\ndocument, naming the authorization server that guards `/mcp`. An unauthenticated request to\n`/mcp` answers `401` with a `WWW-Authenticate: Bearer resource_metadata=\"…\"` header pointing\nhere, which is how an OAuth-aware MCP client bootstraps the flow.\n",
        "operationId": "getOauthProtectedResourceMetadata",
        "security": [],
        "responses": {
          "200": {
            "description": "Protected resource metadata",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OauthProtectedResourceMetadata"
                }
              }
            }
          }
        }
      }
    },
    "/register": {
      "post": {
        "tags": [
          "OAuth"
        ],
        "summary": "Register an OAuth client",
        "description": "[RFC 7591](https://www.rfc-editor.org/rfc/rfc7591) Dynamic Client Registration — the\nself-serve half of the flow, which lets a client onboard itself with no operator step.\n\nA client that registers with `token_endpoint_auth_method: none` is public and receives no\nsecret; any other method yields a `client_secret` that does not expire.\n",
        "operationId": "registerOauthClient",
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/OauthClientRegistrationRequest"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Client registered",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OauthClientRegistrationResponse"
                }
              }
            }
          },
          "400": {
            "description": "`redirect_uris` is missing or is not an array of strings",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OauthErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/authorize": {
      "get": {
        "tags": [
          "OAuth"
        ],
        "summary": "Start the authorization code flow",
        "description": "The authorization endpoint. PKCE is mandatory — `code_challenge` is required and\n`code_challenge_method` must be `S256`; OAuth 2.1 forbids `plain`.\n\nA user who has not yet consented is redirected to the consent screen at\n`/app/oauth/consent`; once consent is recorded, a second call to this endpoint redirects to\n`redirect_uri` with a single-use `code`. Parameter errors that can be attributed to a\nregistered `redirect_uri` are reported as a redirect carrying `error`, per RFC 6749;\nan unknown `client_id` or an unregistered `redirect_uri` answers `400` directly, because\nthere is no trustworthy place to send the user.\n",
        "operationId": "authorizeOauthClient",
        "security": [],
        "parameters": [
          {
            "name": "client_id",
            "in": "query",
            "required": true,
            "description": "The registered client identifier.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "redirect_uri",
            "in": "query",
            "required": true,
            "description": "Must exactly match one of the client's registered redirect URIs.",
            "schema": {
              "type": "string",
              "format": "uri"
            }
          },
          {
            "name": "response_type",
            "in": "query",
            "required": true,
            "description": "Must be `code`.",
            "schema": {
              "type": "string",
              "enum": [
                "code"
              ]
            }
          },
          {
            "name": "code_challenge",
            "in": "query",
            "required": true,
            "description": "PKCE challenge, the base64url-encoded SHA-256 of the verifier.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "code_challenge_method",
            "in": "query",
            "required": true,
            "description": "Must be `S256`.",
            "schema": {
              "type": "string",
              "enum": [
                "S256"
              ]
            }
          },
          {
            "name": "scope",
            "in": "query",
            "required": false,
            "description": "Space-separated scopes, e.g. `mcp:access`.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "state",
            "in": "query",
            "required": false,
            "description": "Opaque value echoed back on the redirect. Optional.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "302": {
            "description": "Redirect to `redirect_uri` with `code` and `state`, to the consent screen when consent is\nstill needed, or back to `redirect_uri` with `error` for a parameter the client can fix.\n",
            "headers": {
              "Location": {
                "description": "Where the user agent is sent next.",
                "schema": {
                  "type": "string",
                  "format": "uri"
                }
              }
            }
          },
          "400": {
            "description": "`client_id` is missing or unknown, or `redirect_uri` is not registered for it",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OauthErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/token": {
      "post": {
        "tags": [
          "OAuth"
        ],
        "summary": "Exchange a code or refresh token for an access token",
        "description": "The token endpoint, serving the `authorization_code` and `refresh_token` grants. The client\nauthenticates with `client_secret_basic`, `client_secret_post`, or `none` for a public client.\n\nThe access token it returns is a SOAT-issued JWT: send it as `Authorization: Bearer <token>`\nto `/mcp` and to the REST API. Refresh tokens are single-use and rotated on every exchange.\n",
        "operationId": "createOauthToken",
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/x-www-form-urlencoded": {
              "schema": {
                "$ref": "#/components/schemas/OauthTokenRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Access token issued",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OauthTokenResponse"
                }
              }
            }
          },
          "400": {
            "description": "Invalid grant, missing parameter, or unsupported `grant_type`",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OauthErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Client authentication failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OauthErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/orchestrations": {
      "post": {
        "tags": [
          "Orchestrations"
        ],
        "summary": "Create an orchestration",
        "description": "Creates a new orchestration (pipeline) definition in the project.",
        "operationId": "createOrchestration",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateOrchestrationRequest"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Orchestration created",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Orchestration"
                }
              }
            }
          },
          "400": {
            "description": "Validation error"
          },
          "401": {
            "description": "Unauthorized"
          },
          "403": {
            "description": "Forbidden"
          }
        }
      },
      "get": {
        "tags": [
          "Orchestrations"
        ],
        "summary": "List orchestrations",
        "description": "Returns orchestrations accessible to the caller.",
        "operationId": "listOrchestrations",
        "parameters": [
          {
            "in": "query",
            "name": "project_id",
            "schema": {
              "type": "string"
            },
            "description": "Filter by project public ID"
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Maximum number of results to return",
            "schema": {
              "type": "integer",
              "default": 50
            }
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "description": "Number of results to skip",
            "schema": {
              "type": "integer",
              "default": 0
            }
          }
        ],
        "responses": {
          "200": {
            "description": "List of orchestrations",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "total",
                    "limit",
                    "offset"
                  ],
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Orchestration"
                      }
                    },
                    "total": {
                      "type": "integer"
                    },
                    "limit": {
                      "type": "integer"
                    },
                    "offset": {
                      "type": "integer"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized"
          },
          "403": {
            "description": "Forbidden"
          }
        }
      }
    },
    "/api/v1/orchestrations/validate": {
      "post": {
        "tags": [
          "Orchestrations"
        ],
        "summary": "Validate an orchestration graph",
        "description": "Statically validates an orchestration graph without persisting anything. Checks that every node has its required field, node ids are unique, edges reference existing nodes, the graph is acyclic (unless it contains a loop node), and every `input_mapping` `{\"var\": \"...\"}` reference resolves to a state key written by an upstream node or seeded by `input_schema`. Returns blocking `errors` and non-blocking `warnings` (e.g. a state key only written on a conditional branch). The same `errors` checks are enforced on create and update, which fail with `400` when any error is present.\n",
        "operationId": "validateOrchestration",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ValidateOrchestrationRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Validation result",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidationResult"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized"
          }
        }
      }
    },
    "/api/v1/orchestrations/queue/stats": {
      "get": {
        "tags": [
          "Orchestrations"
        ],
        "summary": "Get orchestration queue stats",
        "description": "Returns a point-in-time snapshot of the orchestration run queue: how many tasks are waiting to be claimed (`queue_depth`), how many are currently claimed with a valid lease (`claimed_tasks`), the age of the oldest waiting task, recent claim-latency percentiles over a rolling in-process window, and a per-project breakdown. Intended for admin/operator policies; guarded by `orchestrations:GetQueueStats`. A project-scoped caller sees only their own projects under `per_project`.\n",
        "operationId": "getQueueStats",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Queue stats snapshot",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/QueueStats"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized"
          },
          "403": {
            "description": "Forbidden"
          }
        }
      }
    },
    "/api/v1/orchestrations/{orchestration_id}": {
      "get": {
        "tags": [
          "Orchestrations"
        ],
        "summary": "Get an orchestration",
        "description": "Returns the orchestration with nodes and edges.",
        "operationId": "getOrchestration",
        "parameters": [
          {
            "$ref": "#/components/parameters/orchestration_id"
          }
        ],
        "responses": {
          "200": {
            "description": "Orchestration details",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Orchestration"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized"
          },
          "403": {
            "description": "Forbidden"
          },
          "404": {
            "description": "Not found"
          }
        }
      },
      "patch": {
        "tags": [
          "Orchestrations"
        ],
        "summary": "Update an orchestration",
        "description": "Partially updates an orchestration definition.",
        "operationId": "updateOrchestration",
        "parameters": [
          {
            "$ref": "#/components/parameters/orchestration_id"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateOrchestrationRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated orchestration",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Orchestration"
                }
              }
            }
          },
          "400": {
            "description": "Validation error"
          },
          "401": {
            "description": "Unauthorized"
          },
          "403": {
            "description": "Forbidden"
          },
          "404": {
            "description": "Not found"
          }
        }
      },
      "delete": {
        "tags": [
          "Orchestrations"
        ],
        "summary": "Delete an orchestration",
        "description": "Deletes an orchestration definition and all its runs.",
        "operationId": "deleteOrchestration",
        "parameters": [
          {
            "$ref": "#/components/parameters/orchestration_id"
          }
        ],
        "responses": {
          "204": {
            "description": "Deleted"
          },
          "401": {
            "description": "Unauthorized"
          },
          "403": {
            "description": "Forbidden"
          },
          "404": {
            "description": "Not found"
          }
        }
      }
    },
    "/api/v1/orchestrations/{orchestration_id}/versions": {
      "get": {
        "tags": [
          "Orchestrations"
        ],
        "summary": "List an orchestration's graph versions",
        "description": "Returns the orchestration's archived graphs, newest first. A version is written on create and on every subsequent write that changes the graph (`nodes`, `edges`, `state_schema`, `input_schema`) — through the REST API or a formation apply alike. Metadata-only edits (name, description) do not archive a version. See [Versioning](/docs/modules/orchestrations#versioning).\n",
        "operationId": "listOrchestrationVersions",
        "parameters": [
          {
            "$ref": "#/components/parameters/orchestration_id"
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Maximum number of results to return",
            "schema": {
              "type": "integer",
              "default": 50
            }
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "description": "Number of results to skip",
            "schema": {
              "type": "integer",
              "default": 0
            }
          }
        ],
        "responses": {
          "200": {
            "description": "List of orchestration versions, newest first",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "total",
                    "limit",
                    "offset"
                  ],
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/OrchestrationVersion"
                      }
                    },
                    "total": {
                      "type": "integer"
                    },
                    "limit": {
                      "type": "integer"
                    },
                    "offset": {
                      "type": "integer"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized"
          },
          "403": {
            "description": "Forbidden"
          },
          "404": {
            "description": "Orchestration not found"
          }
        }
      }
    },
    "/api/v1/orchestrations/{orchestration_id}/versions/{version}": {
      "get": {
        "tags": [
          "Orchestrations"
        ],
        "summary": "Fetch an archived orchestration version",
        "description": "Returns the exact graph a given version describes. Every run records the version it started on in `orchestration_version` and executes that graph for its whole life, so this is how you read the topology a run actually took — including a run whose orchestration has been rewired since.\n",
        "operationId": "getOrchestrationVersion",
        "parameters": [
          {
            "$ref": "#/components/parameters/orchestration_id"
          },
          {
            "name": "version",
            "in": "path",
            "required": true,
            "description": "The archived version number",
            "schema": {
              "type": "integer",
              "minimum": 1
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Archived orchestration version",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OrchestrationVersion"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request — version is not a positive integer"
          },
          "401": {
            "description": "Unauthorized"
          },
          "403": {
            "description": "Forbidden"
          },
          "404": {
            "description": "Not found"
          }
        }
      }
    },
    "/api/v1/orchestrations/{orchestration_id}/versions/{version}/restore": {
      "post": {
        "tags": [
          "Orchestrations"
        ],
        "summary": "Restore an archived orchestration graph",
        "description": "Writes an archived version's graph back as the orchestration's live definition, which archives it again as a **new** version rather than rewinding the counter — so a run pinned to any version in between still resolves the graph it started on.\n\nThe restore runs through the ordinary update path, so the archived graph goes through the same static validation as an authored one. Node resource references (`agent_id`, `tool_id`, `orchestration_id`) resolve when a run reaches the node, so a target deleted since the snapshot was taken restores cleanly and surfaces as a failed run rather than a `400`. Restoring the graph the orchestration already holds is a no-op and archives nothing. Runs already in flight are unaffected either way — a restore is an ordinary edit, and pinning is what keeps it from reaching them.\n",
        "operationId": "restoreOrchestrationVersion",
        "parameters": [
          {
            "$ref": "#/components/parameters/orchestration_id"
          },
          {
            "name": "version",
            "in": "path",
            "required": true,
            "description": "The archived version number",
            "schema": {
              "type": "integer",
              "minimum": 1
            }
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/RestoreOrchestrationVersionRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The orchestration, at its new version",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Orchestration"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request — version is not a positive integer"
          },
          "401": {
            "description": "Unauthorized"
          },
          "403": {
            "description": "Forbidden"
          },
          "404": {
            "description": "Not found"
          }
        }
      }
    },
    "/api/v1/orchestration-runs": {
      "post": {
        "tags": [
          "Orchestrations"
        ],
        "summary": "Start an orchestration run",
        "description": "Creates a new run for the orchestration named by orchestration_id. By default the run executes durably in the background: the response returns immediately with status \"queued\" (a worker then claims it and moves it to \"running\") and progress is observed via get-orchestration-run or run lifecycle webhook events (orchestration_runs.started/awaiting_input/succeeded/failed). Delay and poll waits park the run as \"sleeping\" and are woken by a background scheduler, surviving restarts. Pass wait=true to block until the run reaches a terminal or awaiting_input state.",
        "operationId": "startOrchestrationRun",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/StartRunRequest"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Run created and executed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OrchestrationRun"
                }
              }
            }
          },
          "400": {
            "description": "Validation error (e.g. a `tool_context` key that cannot become a header, or `metadata` that is not a JSON object). No run is created."
          },
          "401": {
            "description": "Unauthorized"
          },
          "403": {
            "description": "Forbidden"
          },
          "404": {
            "description": "Orchestration not found"
          }
        }
      },
      "get": {
        "tags": [
          "Orchestrations"
        ],
        "summary": "List orchestration runs",
        "description": "Returns orchestration runs the caller can access, optionally filtered by orchestration, by parent run, or by whether the run has a parent at all.\n\nNote when aggregating: a run's `usage` covers its whole subtree, so summing it over a list that contains both a parent and its children counts the children more than once. Pass `nested=false` to sum over runs a caller started.",
        "operationId": "listOrchestrationRuns",
        "parameters": [
          {
            "name": "orchestration_id",
            "in": "query",
            "required": false,
            "description": "Filter by orchestration public ID (orch_...)",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "parent_orchestration_run_id",
            "in": "query",
            "required": false,
            "description": "Filter to the runs one specific parent run's `loop` / `sub_orchestration` nodes started (run_...). This is how a caller holding a parent names the individual children behind its `usage`.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "nested",
            "in": "query",
            "required": false,
            "description": "Filter by whether the run was started by another run. `false` returns only the runs a caller started (no parent), which is the set to sum `usage` over; `true` returns only the runs a `loop` / `sub_orchestration` node started, across every parent. Omit to return both.\n\nContradicting `parent_orchestration_run_id` with `nested=false` is a `400`; any value other than `true` or `false` is a `400`.",
            "schema": {
              "type": "boolean"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Maximum number of results to return",
            "schema": {
              "type": "integer",
              "default": 50
            }
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "description": "Number of results to skip",
            "schema": {
              "type": "integer",
              "default": 0
            }
          }
        ],
        "responses": {
          "200": {
            "description": "List of runs",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "total",
                    "limit",
                    "offset"
                  ],
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/OrchestrationRun"
                      }
                    },
                    "total": {
                      "type": "integer"
                    },
                    "limit": {
                      "type": "integer"
                    },
                    "offset": {
                      "type": "integer"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized"
          },
          "403": {
            "description": "Forbidden"
          }
        }
      }
    },
    "/api/v1/orchestration-runs/{orchestration_run_id}/cancel": {
      "post": {
        "tags": [
          "Orchestrations"
        ],
        "summary": "Cancel an orchestration run",
        "description": "Cancels a run that has not yet reached a terminal state.",
        "operationId": "cancelOrchestrationRun",
        "parameters": [
          {
            "$ref": "#/components/parameters/orchestration_run_id"
          }
        ],
        "responses": {
          "200": {
            "description": "Cancelled run",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OrchestrationRun"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized"
          },
          "403": {
            "description": "Forbidden"
          },
          "404": {
            "description": "Not found"
          },
          "409": {
            "description": "Run is already in a terminal state"
          }
        }
      }
    },
    "/api/v1/orchestration-runs/{orchestration_run_id}/human-input": {
      "post": {
        "tags": [
          "Orchestrations"
        ],
        "summary": "Submit human input",
        "description": "Provides human input to a run that is awaiting_input at a human node.",
        "operationId": "submitHumanInput",
        "parameters": [
          {
            "$ref": "#/components/parameters/orchestration_run_id"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/HumanInputRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Run after processing human input",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OrchestrationRun"
                }
              }
            }
          },
          "400": {
            "description": "Invalid input"
          },
          "401": {
            "description": "Unauthorized"
          },
          "403": {
            "description": "Forbidden"
          },
          "404": {
            "description": "Not found"
          },
          "409": {
            "description": "Run is not awaiting input"
          }
        }
      }
    },
    "/api/v1/orchestration-runs/{orchestration_run_id}/resume": {
      "post": {
        "tags": [
          "Orchestrations"
        ],
        "summary": "Resume an orchestration run",
        "description": "Re-drives an awaiting_input orchestration run from its last checkpoint. This does not satisfy the pause itself — it carries no node_id or payload, so a run parked on a human or webhook-receive node re-parks on the same node. Use submit-human-input to supply the awaited payload and advance the run.",
        "operationId": "resumeOrchestrationRun",
        "parameters": [
          {
            "$ref": "#/components/parameters/orchestration_run_id"
          }
        ],
        "responses": {
          "200": {
            "description": "Resumed run",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OrchestrationRun"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized"
          },
          "403": {
            "description": "Forbidden"
          },
          "404": {
            "description": "Not found"
          },
          "409": {
            "description": "Run is not awaiting input"
          }
        }
      }
    },
    "/api/v1/orchestration-runs/{orchestration_run_id}": {
      "get": {
        "tags": [
          "Orchestrations"
        ],
        "summary": "Get an orchestration run",
        "description": "Returns the status, state, and artifacts of a specific run.",
        "operationId": "getOrchestrationRun",
        "parameters": [
          {
            "$ref": "#/components/parameters/orchestration_run_id"
          }
        ],
        "responses": {
          "200": {
            "description": "Run details",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OrchestrationRun"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized"
          },
          "403": {
            "description": "Forbidden"
          },
          "404": {
            "description": "Not found"
          }
        }
      }
    },
    "/api/v1/policies": {
      "get": {
        "tags": [
          "Policies"
        ],
        "summary": "List all policies",
        "description": "Returns global policies. Requires admin role. Pass user_id to list only the policies attached to that user (replaces the former per-user policies endpoint).\n",
        "operationId": "listPolicies",
        "parameters": [
          {
            "name": "user_id",
            "in": "query",
            "required": false,
            "description": "Return only policies attached to this user (user_...)",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Maximum number of results to return",
            "schema": {
              "type": "integer",
              "default": 50
            }
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "description": "Number of results to skip",
            "schema": {
              "type": "integer",
              "default": 0
            }
          }
        ],
        "responses": {
          "200": {
            "description": "List of policies",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "total",
                    "limit",
                    "offset"
                  ],
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/PolicyRecord"
                      }
                    },
                    "total": {
                      "type": "integer"
                    },
                    "limit": {
                      "type": "integer"
                    },
                    "offset": {
                      "type": "integer"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized"
          },
          "403": {
            "description": "Forbidden (non-admin user)"
          }
        }
      },
      "post": {
        "tags": [
          "Policies"
        ],
        "summary": "Create a policy",
        "description": "Creates a new global policy. Requires admin role.",
        "operationId": "createPolicy",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "document"
                ],
                "properties": {
                  "name": {
                    "type": "string",
                    "example": "ReadOnlyAccess"
                  },
                  "description": {
                    "type": "string",
                    "example": "Allows read-only access to all resources"
                  },
                  "document": {
                    "$ref": "#/components/schemas/PolicyDocument"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Policy created successfully",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PolicyRecord"
                }
              }
            }
          },
          "400": {
            "description": "Bad request (invalid policy document)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized"
          },
          "403": {
            "description": "Forbidden (non-admin user)"
          }
        }
      }
    },
    "/api/v1/policies/{policy_id}": {
      "get": {
        "tags": [
          "Policies"
        ],
        "summary": "Get a policy",
        "description": "Returns details of a specific policy. Requires admin role.",
        "operationId": "getPolicy",
        "parameters": [
          {
            "name": "policy_id",
            "in": "path",
            "required": true,
            "description": "Policy public ID (pol_ prefix)",
            "schema": {
              "type": "string",
              "example": "pol_V1StGXR8Z5jdHi6B"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Policy details",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PolicyRecord"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized"
          },
          "403": {
            "description": "Forbidden (non-admin user)"
          },
          "404": {
            "description": "Policy not found"
          }
        }
      },
      "put": {
        "tags": [
          "Policies"
        ],
        "summary": "Update a policy",
        "description": "Updates an existing global policy. Requires admin role.",
        "operationId": "updatePolicy",
        "parameters": [
          {
            "name": "policy_id",
            "in": "path",
            "required": true,
            "description": "Policy public ID (pol_ prefix)",
            "schema": {
              "type": "string",
              "example": "pol_V1StGXR8Z5jdHi6B"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "document"
                ],
                "properties": {
                  "name": {
                    "type": "string"
                  },
                  "description": {
                    "type": "string"
                  },
                  "document": {
                    "$ref": "#/components/schemas/PolicyDocument"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Policy updated successfully",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PolicyRecord"
                }
              }
            }
          },
          "400": {
            "description": "Bad request (invalid policy document)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized"
          },
          "403": {
            "description": "Forbidden (non-admin user)"
          },
          "404": {
            "description": "Policy not found"
          }
        }
      },
      "delete": {
        "tags": [
          "Policies"
        ],
        "summary": "Delete a policy",
        "description": "Deletes a global policy. Requires admin role.",
        "operationId": "deletePolicy",
        "parameters": [
          {
            "name": "policy_id",
            "in": "path",
            "required": true,
            "description": "Policy public ID (pol_ prefix)",
            "schema": {
              "type": "string",
              "example": "pol_V1StGXR8Z5jdHi6B"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "Policy deleted successfully"
          },
          "401": {
            "description": "Unauthorized"
          },
          "403": {
            "description": "Forbidden (non-admin user)"
          },
          "404": {
            "description": "Policy not found"
          }
        }
      }
    },
    "/api/v1/projects": {
      "get": {
        "tags": [
          "Projects"
        ],
        "summary": "List projects",
        "description": "Lists projects accessible to the caller. - JWT admin: returns all projects. - JWT regular user: returns only projects granted by the user's policies. - API key scoped to a project: returns only that project.\n",
        "operationId": "listProjects",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Maximum number of results to return",
            "schema": {
              "type": "integer",
              "default": 50
            }
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "description": "Number of results to skip",
            "schema": {
              "type": "integer",
              "default": 0
            }
          }
        ],
        "responses": {
          "200": {
            "description": "List of projects",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "total",
                    "limit",
                    "offset"
                  ],
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/ProjectRecord"
                      }
                    },
                    "total": {
                      "type": "integer"
                    },
                    "limit": {
                      "type": "integer"
                    },
                    "offset": {
                      "type": "integer"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized"
          }
        }
      },
      "post": {
        "tags": [
          "Projects"
        ],
        "summary": "Create a project",
        "description": "Creates a new project. Requires admin role.",
        "operationId": "createProject",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "name"
                ],
                "properties": {
                  "name": {
                    "type": "string",
                    "example": "My Project"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Project created successfully",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string",
                      "example": "proj_V1StGXR8Z5jdHi6B"
                    },
                    "name": {
                      "type": "string"
                    },
                    "created_at": {
                      "type": "string",
                      "format": "date-time"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized"
          },
          "403": {
            "description": "Forbidden (non-admin user)"
          },
          "500": {
            "description": "Internal server error"
          }
        }
      }
    },
    "/api/v1/projects/{project_id}": {
      "get": {
        "tags": [
          "Projects"
        ],
        "summary": "Get a project",
        "description": "Returns details of a specific project.",
        "operationId": "getProject",
        "parameters": [
          {
            "name": "project_id",
            "in": "path",
            "required": true,
            "description": "Project public ID (proj_ prefix)",
            "schema": {
              "type": "string",
              "example": "proj_V1StGXR8Z5jdHi6B"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Project details",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProjectRecord"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized"
          },
          "403": {
            "description": "Forbidden"
          },
          "404": {
            "description": "Project not found"
          }
        }
      },
      "patch": {
        "tags": [
          "Projects"
        ],
        "summary": "Update a project",
        "description": "Updates a project's name, its attached guardrails (`guardrail_ids` — the project-scope baseline governing every tool call by every agent in the project), its orchestration concurrency limit (`max_concurrent_runs`), its continuation-chain ceiling (`max_chain_generations`), its orchestration nesting bound (`max_run_depth`), its inherited model route (`default_model_route_id`), its read-auditing opt-in (`audit_reads_enabled`), its trace-content retention window (`trace_content_retention_days`), and/or its zero-retention setting (`trace_content_mode`). At least one field is required. Requires admin role. Detaching a guardrail (removing an id) additionally requires guardrails:DetachGuardrail.",
        "operationId": "updateProject",
        "parameters": [
          {
            "name": "project_id",
            "in": "path",
            "required": true,
            "description": "Project public ID (proj_ prefix)",
            "schema": {
              "type": "string",
              "example": "proj_V1StGXR8Z5jdHi6B"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string",
                    "example": "Renamed Project"
                  },
                  "guardrail_ids": {
                    "x-soat-ref": "guardrails",
                    "type": "array",
                    "nullable": true,
                    "items": {
                      "type": "string"
                    },
                    "description": "Guardrails attached at the project scope."
                  },
                  "max_concurrent_runs": {
                    "type": "integer",
                    "nullable": true,
                    "minimum": 1,
                    "example": 5,
                    "description": "Maximum orchestration runs of this project driven at once. `null` clears the limit (unlimited); otherwise an integer >= 1. Enforced at queue claim time — excess runs stay queued until a slot frees."
                  },
                  "max_chain_generations": {
                    "type": "integer",
                    "nullable": true,
                    "minimum": 1,
                    "example": 25,
                    "description": "Generations one continuation chain in this project may hold before the platform stops resuming it. `null` clears the project's ceiling, leaving the deployment-wide `MAX_CONTINUATION_CHAIN_GENERATIONS`; otherwise an integer >= 1. The effective budget is the smallest of the deployment's ceiling, this one, and the agent's own `max_chain_generations` stop condition, so an agent author can be stricter than this number but never exceed it."
                  },
                  "max_run_depth": {
                    "type": "integer",
                    "nullable": true,
                    "minimum": 1,
                    "example": 5,
                    "description": "`loop` / `sub_orchestration` nesting levels a run tree in this project may reach before the engine refuses to start the next child. `null` clears the project's bound, leaving the deployment-wide `MAX_ORCHESTRATION_RUN_DEPTH` (default 10); otherwise an integer >= 1. The effective bound is the smaller of the two, so this can only be stricter than the deployment's — which is what makes it a bound a graph author cannot opt out of. A refused child fails its parent run with `ORCHESTRATION_RUN_DEPTH_LIMIT`."
                  },
                  "default_model_route_id": {
                    "x-soat-ref": "model-routes",
                    "type": "string",
                    "nullable": true,
                    "example": "route_V1StGXR8Z5jdHi6B",
                    "description": "Model route inherited by consumers in this project that bind neither `model_route_id` nor `ai_provider_id`. The route must belong to this project (`400` otherwise). `null` clears the default, which is refused with `409` while any consumer inherits it — repointing it to another route is always allowed and immediately changes which targets those consumers use."
                  },
                  "audit_reads_enabled": {
                    "type": "boolean",
                    "example": true,
                    "description": "Opt the project into read auditing. When true, `GET` requests that name this project are recorded in the audit log alongside mutations. Defaults to `false`."
                  },
                  "trace_content_retention_days": {
                    "type": "integer",
                    "nullable": true,
                    "minimum": 1,
                    "example": 90,
                    "description": "How long trace and generation content is kept before the daily retention sweep content-purges it. `null` (the default) disables retention; otherwise an integer >= 1. The sweep uses the same purge path as `DELETE /traces/{trace_id}/content`, so the row survives as an auditable skeleton with `content_redacted_at` set."
                  },
                  "trace_content_mode": {
                    "type": "string",
                    "enum": [
                      "full",
                      "none"
                    ],
                    "example": "none",
                    "description": "Whether trace and generation content is persisted at all. `full` (the default) stores it; `none` is zero-retention — content is never written, for every agent in the project. An agent may tighten this to `none` on its own but cannot loosen a `none` project back to `full`."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Project updated successfully",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProjectRecord"
                }
              }
            }
          },
          "400": {
            "description": "Invalid request body"
          },
          "401": {
            "description": "Unauthorized"
          },
          "403": {
            "description": "Forbidden (non-admin user)"
          },
          "404": {
            "description": "Project not found"
          },
          "409": {
            "description": "`default_model_route_id` cannot be cleared while consumers inherit it (PROJECT_DEFAULT_ROUTE_INHERITED)."
          }
        }
      },
      "delete": {
        "tags": [
          "Projects"
        ],
        "summary": "Delete a project",
        "description": "Deletes a project. Requires admin role. Fails with `409` if the project has any dependent resource, unless `force=true` is passed, in which case those resources are deleted along with the project.\n\nEvery project-scoped resource counts: agents, ai providers, model routes, tools, ingestion rules, actors, chats, conversations, sessions, generations, traces, datasets, evals, workflows, tasks, triggers, orchestrations and their runs, formations, memories, secrets, files, guardrails, quotas, usage history — and the activity, approval, exception and guardrail-evaluation records a project accumulates while it runs. Audit log entries are the one exception: they outlive the project, keeping their record with `project_id` cleared.\n",
        "operationId": "deleteProject",
        "parameters": [
          {
            "name": "project_id",
            "in": "path",
            "required": true,
            "description": "Project public ID (proj_ prefix)",
            "schema": {
              "type": "string",
              "example": "proj_V1StGXR8Z5jdHi6B"
            }
          },
          {
            "name": "force",
            "in": "query",
            "required": false,
            "description": "When `true`, deletes all of the project's dependent resources instead of returning `409 PROJECT_HAS_DEPENDENTS`.\n",
            "schema": {
              "type": "boolean",
              "default": false
            }
          }
        ],
        "responses": {
          "204": {
            "description": "Project deleted successfully"
          },
          "401": {
            "description": "Unauthorized"
          },
          "403": {
            "description": "Forbidden (non-admin user)"
          },
          "404": {
            "description": "Project not found"
          },
          "409": {
            "description": "Project has dependent resources (pass `force=true` to delete anyway)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/projects/{project_id}/prices": {
      "get": {
        "tags": [
          "Projects"
        ],
        "summary": "List a project's price rows",
        "description": "Returns the project's per-provider-slug price rows — the middle pricing tier that covers every one of the project's instances of a given provider slug. At cost time a per-provider-instance override wins over these, and these win over the global default. Authorized by the caller's access to the project.\n",
        "operationId": "getProjectPrices",
        "parameters": [
          {
            "name": "project_id",
            "in": "path",
            "required": true,
            "description": "Project public ID (proj_ prefix)",
            "schema": {
              "type": "string",
              "example": "proj_V1StGXR8Z5jdHi6B"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The project's price rows",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProjectPricesResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized"
          },
          "403": {
            "description": "Forbidden"
          },
          "404": {
            "description": "Project not found"
          }
        }
      },
      "put": {
        "tags": [
          "Projects"
        ],
        "summary": "Upsert a project's price rows",
        "description": "Upserts project + provider-slug price rows, keyed on (provider, model, effective_from). A row covers all of the project's instances of that provider slug. Authorized by the caller's access to the project. `effective_from` must be in the future once the (provider, model, component) is priced — past prices are immutable, so corrections ship as new future-dated rows. A first price, with no row for the project or the global default, may be dated now or earlier.\n",
        "operationId": "updateProjectPrices",
        "parameters": [
          {
            "name": "project_id",
            "in": "path",
            "required": true,
            "description": "Project public ID (proj_ prefix)",
            "schema": {
              "type": "string",
              "example": "proj_V1StGXR8Z5jdHi6B"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpsertProjectPricesRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The upserted price rows",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProjectPricesResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request (e.g. non-future effective_from)"
          },
          "401": {
            "description": "Unauthorized"
          },
          "403": {
            "description": "Forbidden"
          },
          "404": {
            "description": "Project not found"
          }
        }
      }
    },
    "/api/v1/quotas": {
      "get": {
        "tags": [
          "Quotas"
        ],
        "summary": "List quotas",
        "description": "Returns the quotas defined in a project",
        "operationId": "listQuotas",
        "parameters": [
          {
            "name": "project_id",
            "in": "query",
            "description": "Project ID (required if not using project key auth)",
            "schema": {
              "type": "string",
              "example": "proj_V1StGXR8Z5jdHi6B"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Maximum number of results to return",
            "schema": {
              "type": "integer",
              "default": 50
            }
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "description": "Number of results to skip",
            "schema": {
              "type": "integer",
              "default": 0
            }
          }
        ],
        "responses": {
          "200": {
            "description": "List of quotas",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "total",
                    "limit",
                    "offset"
                  ],
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Quota"
                      }
                    },
                    "total": {
                      "type": "integer"
                    },
                    "limit": {
                      "type": "integer"
                    },
                    "offset": {
                      "type": "integer"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized"
          },
          "403": {
            "description": "Forbidden"
          },
          "500": {
            "description": "Internal server error"
          }
        }
      },
      "post": {
        "tags": [
          "Quotas"
        ],
        "summary": "Create a quota",
        "description": "Creates a project-scoped quota. `requests` is valid for `scope: project`/`api_key`; `tokens` and `cost_usd` are valid for `scope: project`/`agent`/`actor`. Any other scope/metric pair is rejected with 400 (no attribution exists to enforce it). An `actor` quota caps one end user's spend, matched from the generation's session; a null `scope_ref` means one budget *per* actor rather than a pooled project total. A duplicate quota (same project, scope, scope_ref, metric, window) is rejected with 409.",
        "operationId": "createQuota",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "scope",
                  "metric",
                  "window",
                  "limit"
                ],
                "properties": {
                  "project_id": {
                    "x-soat-ref": "projects",
                    "type": "string",
                    "description": "Project ID (required if not using project key auth)",
                    "example": "proj_V1StGXR8Z5jdHi6B"
                  },
                  "scope": {
                    "type": "string",
                    "enum": [
                      "project",
                      "api_key",
                      "agent",
                      "actor"
                    ],
                    "description": "The scope the quota applies to"
                  },
                  "scope_ref": {
                    "type": "string",
                    "nullable": true,
                    "description": "Public id of the api key / agent / actor the quota applies to. For `api_key` and `agent` scope, NULL means all entities of that scope type in the project. For `actor` scope, NULL means one budget *per* actor — each end user gets their own allowance — rather than a pooled total across all actors.",
                    "example": "key_V1StGXR8Z5jdHi6B"
                  },
                  "metric": {
                    "type": "string",
                    "enum": [
                      "requests",
                      "tokens",
                      "cost_usd"
                    ],
                    "description": "The metric being capped",
                    "example": "requests"
                  },
                  "window": {
                    "type": "string",
                    "enum": [
                      "rolling_1m",
                      "rolling_1h",
                      "rolling_24h",
                      "calendar_month"
                    ],
                    "description": "The window over which the metric is aggregated"
                  },
                  "limit": {
                    "type": "number",
                    "description": "The cap. Must be a positive integer for requests/tokens; fractional values are allowed for cost_usd.",
                    "example": 600
                  },
                  "mode": {
                    "type": "string",
                    "enum": [
                      "enforce",
                      "monitor"
                    ],
                    "default": "enforce",
                    "description": "enforce blocks with 429 (requests at the middleware, tokens/cost_usd at the pre-generation check); monitor observes without blocking — a breach fires the quota.exceeded webhook and writes a quotas:MonitorBreach audit entry, but the request is let through.",
                    "example": "enforce"
                  },
                  "on_unpriced": {
                    "type": "string",
                    "enum": [
                      "block",
                      "allow"
                    ],
                    "default": "block",
                    "description": "Only for metric cost_usd (400 on any other metric). What an enforce quota does when the current window is a pricing blackout — several metered llm_tokens events, none of them priced, so the aggregate is 0 however much was actually spent. Platform meters such as compute_execution are read for the aggregate but never for this verdict. block (the default) refuses new generations with 409 QUOTA_UNENFORCEABLE until pricing is configured; allow accepts the unmeasurable spend explicitly. Either way a quota_unpriced exception is filed. monitor-mode quotas never block regardless.",
                    "example": "block"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Quota created successfully",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Quota"
                }
              }
            }
          },
          "400": {
            "description": "Bad request (invalid scope/metric/window/mode/limit)"
          },
          "401": {
            "description": "Unauthorized"
          },
          "403": {
            "description": "Forbidden"
          },
          "409": {
            "description": "A matching quota already exists"
          },
          "500": {
            "description": "Internal server error"
          }
        }
      }
    },
    "/api/v1/quotas/{quota_id}": {
      "get": {
        "tags": [
          "Quotas"
        ],
        "summary": "Get a quota",
        "description": "Returns a specific quota, including current window usage",
        "operationId": "getQuota",
        "parameters": [
          {
            "name": "quota_id",
            "in": "path",
            "required": true,
            "description": "Quota ID",
            "schema": {
              "type": "string",
              "example": "quota_V1StGXR8Z5jdHi6B"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Quota details",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Quota"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized"
          },
          "403": {
            "description": "Forbidden"
          },
          "404": {
            "description": "Quota not found"
          }
        }
      },
      "patch": {
        "tags": [
          "Quotas"
        ],
        "summary": "Update a quota",
        "description": "Updates a quota's limit and/or mode. Other fields are immutable.",
        "operationId": "updateQuota",
        "parameters": [
          {
            "name": "quota_id",
            "in": "path",
            "required": true,
            "description": "Quota ID",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "limit": {
                    "type": "number",
                    "description": "New limit",
                    "example": 1000
                  },
                  "mode": {
                    "type": "string",
                    "enum": [
                      "enforce",
                      "monitor"
                    ],
                    "description": "New mode",
                    "example": "monitor"
                  },
                  "on_unpriced": {
                    "type": "string",
                    "enum": [
                      "block",
                      "allow"
                    ],
                    "description": "New pricing posture. Only for metric cost_usd (400 on any other metric); see the create operation for what block and allow mean.",
                    "example": "allow"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Quota updated successfully",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Quota"
                }
              }
            }
          },
          "400": {
            "description": "Bad request"
          },
          "401": {
            "description": "Unauthorized"
          },
          "403": {
            "description": "Forbidden"
          },
          "404": {
            "description": "Quota not found"
          }
        }
      },
      "delete": {
        "tags": [
          "Quotas"
        ],
        "summary": "Delete a quota",
        "description": "Deletes a quota and drops its window counters",
        "operationId": "deleteQuota",
        "parameters": [
          {
            "name": "quota_id",
            "in": "path",
            "required": true,
            "description": "Quota ID",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "Quota deleted successfully"
          },
          "401": {
            "description": "Unauthorized"
          },
          "403": {
            "description": "Forbidden"
          },
          "404": {
            "description": "Quota not found"
          }
        }
      }
    },
    "/api/v1/secrets": {
      "get": {
        "tags": [
          "Secrets"
        ],
        "summary": "List secrets",
        "description": "Returns a list of secrets for a project",
        "operationId": "listSecrets",
        "parameters": [
          {
            "name": "project_id",
            "in": "query",
            "description": "Project ID (required if not using project key auth)",
            "schema": {
              "type": "string",
              "example": "proj_V1StGXR8Z5jdHi6B"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "description": "Number of results per page",
            "schema": {
              "type": "integer",
              "default": 25
            }
          },
          {
            "name": "offset",
            "in": "query",
            "description": "Number of results to skip",
            "schema": {
              "type": "integer",
              "default": 0
            }
          }
        ],
        "responses": {
          "200": {
            "description": "List of secrets",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "total",
                    "limit",
                    "offset"
                  ],
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "string"
                          },
                          "name": {
                            "type": "string"
                          },
                          "has_value": {
                            "type": "boolean",
                            "description": "Whether an encrypted value is stored for this secret"
                          },
                          "project_id": {
                            "x-soat-ref": "projects",
                            "type": "string"
                          },
                          "created_at": {
                            "type": "string",
                            "format": "date-time"
                          },
                          "updated_at": {
                            "type": "string",
                            "format": "date-time"
                          }
                        }
                      }
                    },
                    "total": {
                      "type": "integer"
                    },
                    "limit": {
                      "type": "integer"
                    },
                    "offset": {
                      "type": "integer"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized"
          },
          "403": {
            "description": "Forbidden"
          },
          "500": {
            "description": "Internal server error"
          }
        }
      },
      "post": {
        "tags": [
          "Secrets"
        ],
        "summary": "Create a secret",
        "description": "Creates a new encrypted secret in a project",
        "operationId": "createSecret",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "name",
                  "value"
                ],
                "properties": {
                  "project_id": {
                    "x-soat-ref": "projects",
                    "type": "string",
                    "description": "Project ID (required if not using project key auth)",
                    "example": "proj_V1StGXR8Z5jdHi6B"
                  },
                  "name": {
                    "type": "string",
                    "description": "Secret name",
                    "example": "DATABASE_PASSWORD"
                  },
                  "value": {
                    "type": "string",
                    "description": "Secret value (will be encrypted)",
                    "example": "supersecretpassword"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Secret created successfully",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string"
                    },
                    "name": {
                      "type": "string"
                    },
                    "has_value": {
                      "type": "boolean",
                      "description": "Whether an encrypted value is stored for this secret"
                    },
                    "project_id": {
                      "x-soat-ref": "projects",
                      "type": "string"
                    },
                    "created_at": {
                      "type": "string",
                      "format": "date-time"
                    },
                    "updated_at": {
                      "type": "string",
                      "format": "date-time"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Bad request (missing required fields)"
          },
          "401": {
            "description": "Unauthorized"
          },
          "403": {
            "description": "Forbidden"
          },
          "500": {
            "description": "Internal server error"
          }
        }
      }
    },
    "/api/v1/secrets/{secret_id}": {
      "get": {
        "tags": [
          "Secrets"
        ],
        "summary": "Get a secret",
        "description": "Returns a specific secret",
        "operationId": "getSecret",
        "parameters": [
          {
            "name": "secret_id",
            "in": "path",
            "required": true,
            "description": "Secret ID",
            "schema": {
              "type": "string",
              "example": "sec_V1StGXR8Z5jdHi6B"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Secret details",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string"
                    },
                    "name": {
                      "type": "string"
                    },
                    "has_value": {
                      "type": "boolean",
                      "description": "Whether an encrypted value is stored for this secret"
                    },
                    "project_id": {
                      "x-soat-ref": "projects",
                      "type": "string"
                    },
                    "created_at": {
                      "type": "string",
                      "format": "date-time"
                    },
                    "updated_at": {
                      "type": "string",
                      "format": "date-time"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized"
          },
          "403": {
            "description": "Forbidden"
          },
          "404": {
            "description": "Secret not found"
          }
        }
      },
      "patch": {
        "tags": [
          "Secrets"
        ],
        "summary": "Update a secret",
        "description": "Updates a secret's name and/or value",
        "operationId": "updateSecret",
        "parameters": [
          {
            "name": "secret_id",
            "in": "path",
            "required": true,
            "description": "Secret ID",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string",
                    "description": "New secret name"
                  },
                  "value": {
                    "type": "string",
                    "description": "New secret value"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Secret updated successfully"
          },
          "400": {
            "description": "Bad request"
          },
          "401": {
            "description": "Unauthorized"
          },
          "403": {
            "description": "Forbidden"
          },
          "404": {
            "description": "Secret not found"
          }
        }
      },
      "delete": {
        "tags": [
          "Secrets"
        ],
        "summary": "Delete a secret",
        "description": "Deletes a secret",
        "operationId": "deleteSecret",
        "parameters": [
          {
            "name": "secret_id",
            "in": "path",
            "required": true,
            "description": "Secret ID",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Secret deleted successfully"
          },
          "401": {
            "description": "Unauthorized"
          },
          "403": {
            "description": "Forbidden"
          },
          "404": {
            "description": "Secret not found"
          }
        }
      }
    },
    "/api/v1/sessions": {
      "post": {
        "tags": [
          "Sessions"
        ],
        "summary": "Create a session",
        "description": "Creates a new session for the specified agent, along with the underlying conversation, so the caller only needs this single call to start interacting with the agent. No actor is created: pass `actor_id` to attach an existing actor as the session's end user. When it is omitted the session has no actor, and generations in it carry no end-user attribution — they are not billed to an actor in the usage meter and they match no `actor`-scoped quota.\n",
        "operationId": "createSession",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateSessionRequest"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Session created",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SessionRecord"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "description": "An open session already exists for this actor (single_session_per_actor is enabled)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "SINGLE_SESSION_CONFLICT",
                    "message": "An open session already exists for this actor.",
                    "hint": "The agent allows one open session per actor. Reuse the session named in `meta.session_id`, or close it first.",
                    "docs_url": "https://soat.ttoss.dev/docs/error-codes#single_session_conflict",
                    "meta": {
                      "session_id": "sess_abc123"
                    }
                  }
                }
              }
            }
          }
        }
      },
      "get": {
        "tags": [
          "Sessions"
        ],
        "summary": "List sessions",
        "description": "Returns sessions the caller can access, optionally filtered by agent, actor and status.",
        "operationId": "listSessions",
        "parameters": [
          {
            "name": "agent_id",
            "in": "query",
            "required": false,
            "description": "Filter by agent public ID",
            "schema": {
              "type": "string",
              "example": "agent_V1StGXR8Z5jdHi6B"
            }
          },
          {
            "name": "actor_id",
            "in": "query",
            "required": false,
            "description": "Filter by actor public ID",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "status",
            "in": "query",
            "required": false,
            "description": "Filter by session status (open, closed, or expired)",
            "schema": {
              "type": "string",
              "enum": [
                "open",
                "closed",
                "expired"
              ]
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "default": 50
            }
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "default": 0
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated list of sessions",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/SessionRecord"
                      }
                    },
                    "total": {
                      "type": "integer"
                    },
                    "limit": {
                      "type": "integer"
                    },
                    "offset": {
                      "type": "integer"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/v1/sessions/{session_id}": {
      "get": {
        "tags": [
          "Sessions"
        ],
        "summary": "Get a session",
        "description": "Returns details of a single session.",
        "operationId": "getSession",
        "parameters": [
          {
            "$ref": "#/components/parameters/SessionId"
          }
        ],
        "responses": {
          "200": {
            "description": "Session details",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SessionRecord"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "patch": {
        "tags": [
          "Sessions"
        ],
        "summary": "Update a session",
        "description": "Updates the session name and/or status.",
        "operationId": "updateSession",
        "parameters": [
          {
            "$ref": "#/components/parameters/SessionId"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateSessionRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated session",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SessionRecord"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "delete": {
        "tags": [
          "Sessions"
        ],
        "summary": "Delete a session",
        "description": "Deletes the session and its underlying conversation and messages. The session's actor is not deleted. Generations and traces produced by the session are not deleted either, since they are not linked to the session or conversation.\n",
        "operationId": "deleteSession",
        "parameters": [
          {
            "$ref": "#/components/parameters/SessionId"
          }
        ],
        "responses": {
          "204": {
            "description": "Session deleted"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/v1/sessions/{session_id}/messages": {
      "post": {
        "tags": [
          "Sessions"
        ],
        "summary": "Add a user message",
        "description": "Saves a user message to the session. When autoGenerate is enabled on the session and no generation is currently in progress, generation is triggered automatically and the response mirrors GenerateSessionResponse. Otherwise returns the saved user message.\n",
        "operationId": "addSessionMessage",
        "parameters": [
          {
            "$ref": "#/components/parameters/SessionId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AddSessionMessageRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Duplicate request — original message returned (idempotency_key matched)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AddSessionMessageSaved"
                }
              }
            }
          },
          "201": {
            "description": "User message saved",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AddSessionMessageResponse"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/v1/sessions/{session_id}/generate": {
      "post": {
        "tags": [
          "Sessions"
        ],
        "summary": "Trigger agent generation",
        "description": "Triggers the agent to generate a response based on the current conversation. Background by default: returns `202 Accepted` immediately while the generation runs. Pass ?wait=true to block and receive the assistant reply (or a requires_action status if the agent needs client tool outputs) in the response.\n",
        "operationId": "generateSessionResponse",
        "parameters": [
          {
            "$ref": "#/components/parameters/SessionId"
          },
          {
            "name": "wait",
            "in": "query",
            "required": false,
            "description": "When omitted or `false` (default), generation runs in the background and `202 Accepted` is returned immediately. Pass `true` to block until the generation settles and receive the result.",
            "schema": {
              "type": "boolean",
              "default": false
            }
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/GenerateSessionRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Agent reply or requires_action (only when `?wait=true`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GenerateSessionResponse"
                }
              }
            }
          },
          "202": {
            "description": "Generation accepted and running in the background (default, when `wait` is omitted or `false`)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "status": {
                      "type": "string",
                      "enum": [
                        "accepted"
                      ]
                    },
                    "session_id": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "description": "Generation already in progress",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "410": {
            "description": "Session has expired due to inactivity",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "502": {
            "description": "Upstream AI provider error (AI_PROVIDER_ERROR). The error `meta` includes the `generation_id` and `trace_id` of the failed generation for post-mortem debugging via GET /api/v1/generations/{generation_id}.\n",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/sessions/{session_id}/tool-outputs": {
      "post": {
        "tags": [
          "Sessions"
        ],
        "summary": "Submit tool outputs",
        "description": "Submits client tool outputs for a generation that returned requires_action. The agent continues its loop and returns the final or next requires_action result.\n",
        "operationId": "submitSessionToolOutputs",
        "parameters": [
          {
            "$ref": "#/components/parameters/SessionId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SubmitSessionToolOutputsRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Generation result",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SendSessionMessageResponse"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/v1/sessions/{session_id}/fork": {
      "post": {
        "tags": [
          "Sessions"
        ],
        "summary": "Fork a session",
        "description": "Branches a new session from a point in this session's history: same context, different continuation.\n\nThe fork gets its own conversation whose messages **reference the same documents** as the parent rather than copying them, so there is one stored copy of the content and a retention purge erases it from both. Recorded tool results ride along on those messages and are **replayed** as model input on the fork's next turn — forking never re-invokes a tool, so exploring a \"what if\" cannot send an email or charge a card a second time. The consequence to accept is that a forked turn sees the tool data as it was, not as it is now.\n\nThe fork is created **inert**: `auto_generate` is false and no generation is triggered. Drive it with the normal message and generate endpoints. The fork has no actor — attach one only if the branch is meant to be driven by the same end user, since `single_session_per_actor` agents allow one open session per actor.\n",
        "operationId": "forkSession",
        "parameters": [
          {
            "$ref": "#/components/parameters/SessionId"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ForkSessionRequest"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Fork created",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SessionRecord"
                }
              }
            }
          },
          "400": {
            "description": "`fork_at_position` names no message in the parent conversation, or `agent_id` is unknown or belongs to another project",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "VALIDATION_FAILED",
                    "message": "fork_at_position 9 does not exist in the parent conversation.",
                    "hint": "Fix the request and retry. Unknown fields are rejected outright, so compare the payload against the operation in `/openapi.json`; `meta` names the offending field when the check can identify one.",
                    "docs_url": "https://soat.ttoss.dev/docs/error-codes#validation_failed"
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/v1/sessions/{session_id}/forks": {
      "get": {
        "tags": [
          "Sessions"
        ],
        "summary": "List a session's forks",
        "description": "Returns the sessions forked directly from this one. One level of lineage: a fork of a fork is listed under its own parent.\n",
        "operationId": "listSessionForks",
        "parameters": [
          {
            "$ref": "#/components/parameters/SessionId"
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "default": 50
            }
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "default": 0
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated list of forks",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/SessionRecord"
                      }
                    },
                    "total": {
                      "type": "integer"
                    },
                    "limit": {
                      "type": "integer"
                    },
                    "offset": {
                      "type": "integer"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/v1/sessions/{session_id}/tags": {
      "get": {
        "tags": [
          "Sessions"
        ],
        "summary": "Get session tags",
        "description": "Returns the session's tags object.",
        "operationId": "getSessionTags",
        "parameters": [
          {
            "$ref": "#/components/parameters/SessionId"
          }
        ],
        "responses": {
          "200": {
            "description": "Session tags",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": {
                    "type": "string"
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "put": {
        "tags": [
          "Sessions"
        ],
        "summary": "Replace session tags",
        "description": "Replaces all tags on the session.",
        "operationId": "replaceSessionTags",
        "parameters": [
          {
            "$ref": "#/components/parameters/SessionId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": {
                  "type": "string"
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated tags",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": {
                    "type": "string"
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "patch": {
        "tags": [
          "Sessions"
        ],
        "summary": "Merge session tags",
        "description": "Merges the provided tags into the session's existing tags.",
        "operationId": "mergeSessionTags",
        "parameters": [
          {
            "$ref": "#/components/parameters/SessionId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": {
                  "type": "string"
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated tags",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": {
                    "type": "string"
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/v1/tasks": {
      "get": {
        "description": "Lists tasks (the board query). Filter by workflow, state, status, or assignee — `GET /tasks?workflow_id=...&state=...` is one board column.",
        "tags": [
          "Tasks"
        ],
        "summary": "List tasks",
        "operationId": "listTasks",
        "parameters": [
          {
            "name": "project_id",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "workflow_id",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "state",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "status",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "open",
                "closed"
              ]
            }
          },
          {
            "name": "assignee",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Maximum number of results to return",
            "schema": {
              "type": "integer",
              "default": 50
            }
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "description": "Number of results to skip",
            "schema": {
              "type": "integer",
              "default": 0
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A list of tasks",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "total",
                    "limit",
                    "offset"
                  ],
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Task"
                      }
                    },
                    "total": {
                      "type": "integer"
                    },
                    "limit": {
                      "type": "integer"
                    },
                    "offset": {
                      "type": "integer"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized"
          },
          "403": {
            "description": "Forbidden"
          }
        }
      },
      "post": {
        "description": "Creates a task bound to a workflow. By default the task is placed in the workflow's initial state; passing `state` places it directly in that named state instead — an alternate entry point for starting a task mid-flow (e.g. \"a new recorte for an existing theme by id\"), rather than re-submitting from the initial state and hoping a guard or similarity gate recognizes it. Entering the resulting state, initial or named, behaves identically: that state's `on_enter` automation fires and its `stalled_after` clock arms.",
        "tags": [
          "Tasks"
        ],
        "summary": "Create a task",
        "operationId": "createTask",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateTaskRequest"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Task created",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Task"
                }
              }
            }
          },
          "400": {
            "description": "Bad request — invalid payload (`TASK_PAYLOAD_INVALID`), or `state` does not name a declared state of the workflow (`TASK_STATE_NOT_FOUND`)"
          },
          "401": {
            "description": "Unauthorized"
          },
          "403": {
            "description": "Forbidden"
          },
          "404": {
            "description": "Workflow not found"
          }
        }
      }
    },
    "/api/v1/tasks/{task_id}": {
      "get": {
        "description": "Retrieves a task, including its active dispatch and automation status.",
        "tags": [
          "Tasks"
        ],
        "summary": "Get a task",
        "operationId": "getTask",
        "parameters": [
          {
            "name": "task_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Task details",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Task"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized"
          },
          "403": {
            "description": "Forbidden"
          },
          "404": {
            "description": "Task not found"
          }
        }
      },
      "patch": {
        "description": "Updates a task's payload, title, or assignee. `state` is never directly writable — move it with a transition; sending a `state` field is rejected as an unknown field (`VALIDATION_FAILED`). `payload` is shallow-merged over the existing payload (PATCH semantics): keys the request omits are preserved. The payload is caller-owned; the automation result lives in the read-only `last_result` field, which no patch can reach. The merged payload is validated against the workflow's `payload_schema`.",
        "tags": [
          "Tasks"
        ],
        "summary": "Update a task",
        "operationId": "updateTask",
        "parameters": [
          {
            "name": "task_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateTaskRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Task updated",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Task"
                }
              }
            }
          },
          "400": {
            "description": "Bad request (invalid payload)"
          },
          "401": {
            "description": "Unauthorized"
          },
          "403": {
            "description": "Forbidden"
          },
          "404": {
            "description": "Task not found"
          }
        }
      },
      "delete": {
        "description": "Deletes a task. Its transition history cascades.",
        "tags": [
          "Tasks"
        ],
        "summary": "Delete a task",
        "operationId": "deleteTask",
        "parameters": [
          {
            "name": "task_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "Task deleted"
          },
          "401": {
            "description": "Unauthorized"
          },
          "403": {
            "description": "Forbidden"
          },
          "404": {
            "description": "Task not found"
          }
        }
      }
    },
    "/api/v1/tasks/{task_id}/transitions": {
      "post": {
        "description": "Fires a named transition on a task. The transition must exist in the workflow and be valid from the task's current state; its guard must pass. This is the single path every state change routes through. A transition declaring `requires_approval` does not move the task — it parks a pending ApprovalItem and returns the task with `pending_transition` set; the move applies only when the approval is approved.",
        "tags": [
          "Tasks"
        ],
        "summary": "Transition a task",
        "operationId": "transitionTask",
        "parameters": [
          {
            "name": "task_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/TransitionTaskRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The task after the transition",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Task"
                }
              }
            }
          },
          "400": {
            "description": "The transition does not exist, is not valid, or its guard rejected the move"
          },
          "401": {
            "description": "Unauthorized"
          },
          "403": {
            "description": "Forbidden"
          },
          "404": {
            "description": "Task not found"
          },
          "409": {
            "description": "A concurrent transition made this one invalid, or the task is closed"
          }
        }
      }
    },
    "/api/v1/tasks/{task_id}/history": {
      "get": {
        "description": "Returns the append-only transition history of a task.",
        "tags": [
          "Tasks"
        ],
        "summary": "Get task history",
        "operationId": "getTaskHistory",
        "parameters": [
          {
            "name": "task_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The task's transition history, oldest first",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/TaskTransition"
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized"
          },
          "403": {
            "description": "Forbidden"
          },
          "404": {
            "description": "Task not found"
          }
        }
      }
    },
    "/api/v1/tools": {
      "post": {
        "tags": [
          "Tools"
        ],
        "summary": "Create a tool",
        "description": "Creates a new tool in the project.",
        "operationId": "createTool",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateToolRequest"
              },
              "examples": {
                "http": {
                  "summary": "HTTP tool",
                  "value": {
                    "name": "get-weather",
                    "type": "http",
                    "description": "Fetches current weather for a city",
                    "parameters": {
                      "type": "object",
                      "properties": {
                        "city": {
                          "type": "string"
                        }
                      },
                      "required": [
                        "city"
                      ]
                    },
                    "execute": {
                      "url": "https://api.weather.example/v1/current?city={city}"
                    }
                  }
                },
                "http_with_output_mapping": {
                  "summary": "HTTP tool reshaping its result with output_mapping",
                  "value": {
                    "name": "transcribe-audio",
                    "type": "http",
                    "description": "Transcribes an audio file and returns the bare text",
                    "parameters": {
                      "type": "object",
                      "properties": {
                        "file": {
                          "type": "string"
                        }
                      },
                      "required": [
                        "file"
                      ]
                    },
                    "execute": {
                      "url": "https://api.x.ai/v1/stt",
                      "method": "POST",
                      "body_mode": "multipart"
                    },
                    "output_mapping": {
                      "var": "output.text"
                    }
                  }
                },
                "http_aws_sigv4": {
                  "summary": "HTTP tool signed with AWS Signature Version 4",
                  "value": {
                    "name": "get-s3-object",
                    "type": "http",
                    "description": "Reads an object from an S3 bucket",
                    "parameters": {
                      "type": "object",
                      "properties": {
                        "key": {
                          "type": "string"
                        }
                      },
                      "required": [
                        "key"
                      ]
                    },
                    "execute": {
                      "url": "https://my-bucket.s3.us-east-1.amazonaws.com/{key}",
                      "method": "GET",
                      "auth": {
                        "type": "aws_sigv4",
                        "region": "us-east-1",
                        "service": "s3",
                        "access_key_id": "{{secret:sec_awsKeyId}}",
                        "secret_access_key": "{{secret:sec_awsSecret}}"
                      }
                    }
                  }
                },
                "http_gcp_service_account": {
                  "summary": "HTTP tool authenticated as a GCP service account",
                  "value": {
                    "name": "create-bigquery-job",
                    "type": "http",
                    "description": "Submits a BigQuery job",
                    "parameters": {
                      "type": "object",
                      "properties": {
                        "query": {
                          "type": "string"
                        }
                      },
                      "required": [
                        "query"
                      ]
                    },
                    "execute": {
                      "url": "https://bigquery.googleapis.com/bigquery/v2/projects/my-gcp-project/jobs",
                      "method": "POST",
                      "auth": {
                        "type": "gcp_service_account",
                        "credentials": "{{secret:sec_gcpServiceAccount}}",
                        "scopes": [
                          "https://www.googleapis.com/auth/bigquery"
                        ]
                      }
                    }
                  }
                },
                "client": {
                  "summary": "Client tool",
                  "value": {
                    "name": "show-dialog",
                    "type": "client",
                    "description": "Displays a confirmation dialog to the user",
                    "parameters": {
                      "type": "object",
                      "properties": {
                        "message": {
                          "type": "string"
                        }
                      }
                    }
                  }
                },
                "builtin": {
                  "summary": "SOAT platform tool",
                  "value": {
                    "name": "builtin-files",
                    "type": "builtin",
                    "actions": [
                      "files:ListFiles",
                      "files:GetFile"
                    ]
                  }
                },
                "mcp_scoped": {
                  "summary": "Read-only MCP tool (allowlisted to a subset of actions)",
                  "value": {
                    "name": "oneclick",
                    "type": "mcp",
                    "mcp": {
                      "url": "https://mcp.oneclick.example/sse"
                    },
                    "actions": [
                      "list_campaigns",
                      "get_campaign"
                    ]
                  }
                },
                "mcp_denylist": {
                  "summary": "Read-only MCP tool (whole surface minus write actions)",
                  "value": {
                    "name": "oneclick",
                    "type": "mcp",
                    "mcp": {
                      "url": "https://mcp.oneclick.example/sse"
                    },
                    "denied_actions": [
                      "create_optimization",
                      "update_optimization",
                      "deactivate_all_optimizations"
                    ]
                  }
                },
                "pipeline": {
                  "summary": "Pipeline tool (compute → persist)",
                  "value": {
                    "name": "compute-and-save",
                    "type": "pipeline",
                    "description": "Computes a sum and persists the result",
                    "parameters": {
                      "type": "object",
                      "properties": {
                        "x": {
                          "type": "number"
                        },
                        "y": {
                          "type": "number"
                        }
                      },
                      "required": [
                        "x",
                        "y"
                      ]
                    },
                    "pipeline": {
                      "steps": [
                        {
                          "id": "compute",
                          "tool_id": "tool_calc",
                          "action": "add",
                          "input": {
                            "a": {
                              "var": "input.x"
                            },
                            "b": {
                              "var": "input.y"
                            }
                          }
                        },
                        {
                          "id": "persist",
                          "tool_id": "tool_save_record",
                          "input": {
                            "value": {
                              "var": "steps.compute.sum"
                            }
                          }
                        }
                      ],
                      "output": {
                        "saved_id": {
                          "var": "steps.persist.id"
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Tool created",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Tool"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      },
      "get": {
        "tags": [
          "Tools"
        ],
        "summary": "List tools",
        "description": "Returns all tools in the project.",
        "operationId": "listTools",
        "parameters": [
          {
            "name": "project_id",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Project public ID to filter by"
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Maximum number of results to return",
            "schema": {
              "type": "integer",
              "default": 50
            }
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "description": "Number of results to skip",
            "schema": {
              "type": "integer",
              "default": 0
            }
          }
        ],
        "responses": {
          "200": {
            "description": "List of tools",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "total",
                    "limit",
                    "offset"
                  ],
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Tool"
                      }
                    },
                    "total": {
                      "type": "integer"
                    },
                    "limit": {
                      "type": "integer"
                    },
                    "offset": {
                      "type": "integer"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/tools/{tool_id}": {
      "get": {
        "tags": [
          "Tools"
        ],
        "summary": "Get a tool",
        "description": "Returns a single tool by ID.",
        "operationId": "getTool",
        "parameters": [
          {
            "name": "tool_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Tool",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Tool"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      },
      "patch": {
        "tags": [
          "Tools"
        ],
        "summary": "Update a tool",
        "description": "Updates an existing tool.",
        "operationId": "updateTool",
        "parameters": [
          {
            "name": "tool_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateToolRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Tool updated",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Tool"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      },
      "delete": {
        "tags": [
          "Tools"
        ],
        "summary": "Delete a tool",
        "description": "Deletes a tool by ID.",
        "operationId": "deleteTool",
        "parameters": [
          {
            "name": "tool_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "Deleted"
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/tools/{tool_id}/call": {
      "post": {
        "tags": [
          "Tools"
        ],
        "summary": "Call a tool",
        "description": "Directly invokes a tool and returns its output. Supported for `http`, `builtin`, `mcp`, and `pipeline` tools. `client` tools cannot be invoked server-side and will return 422. A `pipeline` tool runs its declared steps in order and returns the mapped `output` (or the last step's output); `action` is ignored and `input` is the pipeline input.\nFor `builtin` and `mcp` tools the `action` field is required and identifies which action (SOAT) or tool name (MCP) to invoke. For `http` tools `action` is ignored. When a `builtin` or `mcp` tool declares an `actions` allowlist, an action outside it is rejected with `400 VALIDATION_FAILED` (\"not available on this tool\") before any outbound request is made.\n`preset_parameters` stored on the tool are pinned over the caller-supplied `input` before execution: a key the tool presets keeps its preset value even when `input` sets it. Keys the presets do not name are taken from `input` as sent.\n",
        "operationId": "callTool",
        "parameters": [
          {
            "name": "tool_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CallToolRequest"
              },
              "examples": {
                "builtin_list_documents": {
                  "summary": "Call a SOAT list-documents action",
                  "value": {
                    "action": "list-documents",
                    "input": {
                      "project_id": "proj_abc123"
                    }
                  }
                },
                "http_tool": {
                  "summary": "Call an HTTP tool",
                  "value": {
                    "input": {
                      "city": "London"
                    }
                  }
                },
                "mcp_tool": {
                  "summary": "Call an MCP tool",
                  "value": {
                    "action": "get_weather",
                    "input": {
                      "location": "Paris"
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Tool output",
            "content": {
              "application/json": {
                "schema": {
                  "description": "The raw output returned by the tool — any JSON value (object, array, string, number, boolean). `null` when the tool produced no output — a `builtin` action answering `204 No Content`, for instance."
                }
              }
            }
          },
          "400": {
            "description": "Bad Request — invalid input or unknown action",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden — the caller lacks permission, or the tool's target is blocked by the deployment's egress policy (TOOL_EGRESS_BLOCKED). An `http`/`mcp` tool may only reach publicly routable addresses unless the destination is listed in the server's TOOL_EGRESS_ALLOWED_HOSTS; the check runs against the resolved address and on every redirect hop, so `meta.tool_address` names the address that was refused.\n",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Tool not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "422": {
            "description": "Unprocessable — tool type cannot be invoked server-side",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "502": {
            "description": "Upstream tool target error (TOOL_HTTP_ERROR). Returned when an `http`-type tool's target responds with a non-2xx status. The error `meta` carries the real upstream `tool_status_code`, `tool_response_body`, `tool_url`, and `tool_method`.\n",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/traces": {
      "get": {
        "tags": [
          "Traces"
        ],
        "summary": "List traces",
        "description": "Returns a paginated list of execution traces for the project.",
        "operationId": "listTraces",
        "parameters": [
          {
            "name": "project_id",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Project public ID to filter by"
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Maximum number of results to return",
            "schema": {
              "type": "integer",
              "default": 50
            }
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "description": "Number of results to skip",
            "schema": {
              "type": "integer",
              "default": 0
            }
          }
        ],
        "responses": {
          "200": {
            "description": "List of traces",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Trace"
                      }
                    },
                    "total": {
                      "type": "integer"
                    },
                    "limit": {
                      "type": "integer"
                    },
                    "offset": {
                      "type": "integer"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/traces/{trace_id}": {
      "get": {
        "tags": [
          "Traces"
        ],
        "summary": "Get a trace",
        "description": "Returns a single trace by ID.",
        "operationId": "getTrace",
        "parameters": [
          {
            "name": "trace_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Public ID of the trace"
          }
        ],
        "responses": {
          "200": {
            "description": "Trace details",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Trace"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Trace not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/traces/{trace_id}/tree": {
      "get": {
        "tags": [
          "Traces"
        ],
        "summary": "Get trace tree",
        "description": "Returns the full execution tree rooted at the given trace (or its root if the given trace is a child). Each node represents one agent's execution session. The `children` array contains traces triggered by sub-agent tool calls from that trace.\n",
        "operationId": "getTraceTree",
        "parameters": [
          {
            "name": "trace_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Public ID of any trace in the tree (root or child)"
          },
          {
            "name": "include",
            "in": "query",
            "required": false,
            "description": "Comma-separated list of related resources to embed on each node. Supported value: `generations` — attaches all generations that belong to each trace node (including sub-agent generations linked via `initiator_generation_id`).\n",
            "schema": {
              "type": "string",
              "example": "generations"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Trace tree rooted at the resolved root trace",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TraceTreeNode"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Trace not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/traces/{trace_id}/content": {
      "delete": {
        "tags": [
          "Traces"
        ],
        "summary": "Purge trace content",
        "description": "Deletes the trace's steps object from storage and clears its content columns (`file_id`, `error`), cascading to every descendant trace and to all of their generations. A descendant holds its own steps object covering the same run, so the cascade is what makes the erasure complete rather than merely partial.\n\nThe rows survive as auditable skeletons with `content_redacted_at` set — ids, timestamps, step counts, and the generations' usage-attribution fields are preserved, because the billing and audit ledger must outlive a tenant's erasure of the content. A purged trace therefore reads back as a skeleton, not a 404: a 404 would prove nothing.\n\nIdempotent — purging an already-purged trace succeeds and leaves the original `content_redacted_at` in place.\n",
        "operationId": "purgeTraceContent",
        "parameters": [
          {
            "name": "trace_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Public ID of the trace"
          }
        ],
        "responses": {
          "200": {
            "description": "The purged trace skeleton",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Trace"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Trace not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/triggers": {
      "get": {
        "description": "Lists triggers. Filter by project, starter type, or target type.",
        "tags": [
          "Triggers"
        ],
        "summary": "List triggers",
        "operationId": "listTriggers",
        "parameters": [
          {
            "name": "project_id",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "type",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "manual",
                "webhook",
                "schedule",
                "event"
              ]
            }
          },
          {
            "name": "target_type",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "orchestration",
                "agent",
                "tool",
                "eval"
              ]
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Maximum number of results to return",
            "schema": {
              "type": "integer",
              "default": 50
            }
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "description": "Number of results to skip",
            "schema": {
              "type": "integer",
              "default": 0
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A list of triggers",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "total",
                    "limit",
                    "offset"
                  ],
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Trigger"
                      }
                    },
                    "total": {
                      "type": "integer"
                    },
                    "limit": {
                      "type": "integer"
                    },
                    "offset": {
                      "type": "integer"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized"
          },
          "403": {
            "description": "Forbidden"
          }
        }
      },
      "post": {
        "description": "Creates a new trigger for a project",
        "tags": [
          "Triggers"
        ],
        "summary": "Create a trigger",
        "operationId": "createTrigger",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateTriggerRequest"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Trigger created",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TriggerWithSecret"
                }
              }
            }
          },
          "400": {
            "description": "Bad request"
          },
          "401": {
            "description": "Unauthorized"
          },
          "403": {
            "description": "Forbidden"
          }
        }
      }
    },
    "/api/v1/triggers/{trigger_id}": {
      "get": {
        "description": "Retrieves the details of a specific trigger",
        "tags": [
          "Triggers"
        ],
        "summary": "Get a trigger",
        "operationId": "getTrigger",
        "parameters": [
          {
            "name": "trigger_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Trigger details",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Trigger"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized"
          },
          "403": {
            "description": "Forbidden"
          },
          "404": {
            "description": "Trigger not found"
          }
        }
      },
      "patch": {
        "description": "Updates an existing trigger's configuration. The type is immutable.",
        "tags": [
          "Triggers"
        ],
        "summary": "Update a trigger",
        "operationId": "updateTrigger",
        "parameters": [
          {
            "name": "trigger_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateTriggerRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Trigger updated",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Trigger"
                }
              }
            }
          },
          "400": {
            "description": "Bad request"
          },
          "401": {
            "description": "Unauthorized"
          },
          "403": {
            "description": "Forbidden"
          },
          "404": {
            "description": "Trigger not found"
          }
        }
      },
      "delete": {
        "description": "Deletes a trigger",
        "tags": [
          "Triggers"
        ],
        "summary": "Delete a trigger",
        "operationId": "deleteTrigger",
        "parameters": [
          {
            "name": "trigger_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "Trigger deleted"
          },
          "401": {
            "description": "Unauthorized"
          },
          "403": {
            "description": "Forbidden"
          },
          "404": {
            "description": "Trigger not found"
          }
        }
      }
    },
    "/api/v1/triggers/{trigger_id}/fire": {
      "post": {
        "description": "Fires a trigger synchronously and returns the terminal firing record. The firing itself always settles here; an `eval` target's run is queued rather than executed inline, so the record names a `queued` run to poll.",
        "tags": [
          "Triggers"
        ],
        "summary": "Fire a trigger",
        "operationId": "fireTrigger",
        "parameters": [
          {
            "name": "trigger_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/FireTriggerRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Terminal firing record",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TriggerFiring"
                }
              }
            }
          },
          "400": {
            "description": "Bad request"
          },
          "401": {
            "description": "Unauthorized"
          },
          "403": {
            "description": "Forbidden"
          },
          "404": {
            "description": "Trigger not found"
          },
          "409": {
            "description": "Trigger inactive or creator unavailable"
          }
        }
      }
    },
    "/api/v1/triggers/{trigger_id}/secret": {
      "get": {
        "description": "Retrieves the signing secret for a webhook trigger",
        "tags": [
          "Triggers"
        ],
        "summary": "Get trigger secret",
        "operationId": "getTriggerSecret",
        "parameters": [
          {
            "name": "trigger_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Trigger secret",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TriggerSecretResponse"
                }
              }
            }
          },
          "400": {
            "description": "Trigger is not a webhook trigger"
          },
          "401": {
            "description": "Unauthorized"
          },
          "403": {
            "description": "Forbidden"
          },
          "404": {
            "description": "Trigger not found"
          }
        }
      }
    },
    "/api/v1/triggers/{trigger_id}/rotate-secret": {
      "post": {
        "description": "Rotates the signing secret for a webhook trigger",
        "tags": [
          "Triggers"
        ],
        "summary": "Rotate trigger secret",
        "operationId": "rotateTriggerSecret",
        "parameters": [
          {
            "name": "trigger_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Secret rotated",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TriggerWithSecret"
                }
              }
            }
          },
          "400": {
            "description": "Trigger is not a webhook trigger"
          },
          "401": {
            "description": "Unauthorized"
          },
          "403": {
            "description": "Forbidden"
          },
          "404": {
            "description": "Trigger not found"
          }
        }
      }
    },
    "/api/v1/trigger-firings": {
      "get": {
        "description": "Lists firings for a trigger (trigger_id is required).",
        "tags": [
          "Triggers"
        ],
        "summary": "List trigger firings",
        "operationId": "listTriggerFirings",
        "parameters": [
          {
            "name": "trigger_id",
            "in": "query",
            "required": true,
            "description": "Trigger to list firings for (trg_...)",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "default": 50
            }
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "default": 0
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A list of firings",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TriggerFiringListResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized"
          },
          "403": {
            "description": "Forbidden"
          },
          "404": {
            "description": "Trigger not found"
          }
        }
      }
    },
    "/api/v1/trigger-firings/{firing_id}": {
      "get": {
        "description": "Retrieves the details of a specific trigger firing",
        "tags": [
          "Triggers"
        ],
        "summary": "Get a trigger firing",
        "operationId": "getTriggerFiring",
        "parameters": [
          {
            "name": "firing_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Firing details",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TriggerFiring"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized"
          },
          "403": {
            "description": "Forbidden"
          },
          "404": {
            "description": "Firing not found"
          }
        }
      }
    },
    "/api/v1/usage/meters": {
      "get": {
        "tags": [
          "Usage"
        ],
        "summary": "List usage meters",
        "description": "Returns the raw usage-meter rows the caller can access, most recent first, optionally filtered by agent, generation, trace, actor, session, or `source`. Each row is the per-generation token usage as reported by the provider, for audit and reconciliation.\n",
        "operationId": "listUsageMeters",
        "parameters": [
          {
            "name": "agent_id",
            "in": "query",
            "required": false,
            "description": "Filter by agent public ID",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "generation_id",
            "in": "query",
            "required": false,
            "description": "Filter by generation public ID",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "trace_id",
            "in": "query",
            "required": false,
            "description": "Filter by trace public ID",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "actor_id",
            "in": "query",
            "required": false,
            "description": "Filter by the actor (end user) the usage is attributed to. An actor that does not exist in scope yields an empty page.\n",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "session_id",
            "in": "query",
            "required": false,
            "description": "Filter by the session the usage was produced in. A session that does not exist in scope yields an empty page.\n",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "trigger_id",
            "in": "query",
            "required": false,
            "description": "Filter by the trigger that initiated the generation",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "action_id",
            "in": "query",
            "required": false,
            "description": "Filter by logical action id",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "meter_type",
            "in": "query",
            "required": false,
            "description": "Filter by meter type (e.g. `llm_tokens`, `compute_execution`, `api_request`, `storage`)\n",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "source",
            "in": "query",
            "required": false,
            "description": "Filter by what the spend was incurred for. `eval` is an eval run's item generations and `eval_judge` an `llm_judge` scorer's own completion, so verification spend is `source` in (`eval`, `eval_judge`). Ordinary agent traffic carries no source and is matched by neither.\n",
            "schema": {
              "type": "string",
              "example": "eval"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "default": 50
            }
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "default": 0
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated list of usage-meter rows",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/UsageEvent"
                      }
                    },
                    "total": {
                      "type": "integer"
                    },
                    "limit": {
                      "type": "integer"
                    },
                    "offset": {
                      "type": "integer"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/usage": {
      "get": {
        "tags": [
          "Usage"
        ],
        "summary": "Get aggregated usage for a project",
        "description": "Returns a project's usage rolled up over an optional `[from, to]` time window, bucketed by a single dimension and optionally narrowed to one `meter_type`. Each group and the grand total carry summed token counts, a measured `quantity` per component (so infra meters report their real amount, not zeros), and `cost_usd` (null when no event in the bucket was priced). This is the per-project cost-by-range/by-category query — a monthly figure without scanning raw meter rows client-side.\n",
        "operationId": "getUsage",
        "parameters": [
          {
            "name": "project_id",
            "in": "query",
            "required": true,
            "description": "Project public ID to aggregate usage for",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "group_by",
            "in": "query",
            "required": true,
            "description": "Dimension to bucket by. `day` buckets on the event's UTC calendar day; the others bucket on the matching column. `ai_provider` buckets on the provider the spend was billed against — a routed generation's serving target, or the agent's pinned provider. `model` buckets on the model id *and* the provider that served it, so one model name served by two providers is two groups (see `ai_provider_id`). `source` buckets by what the spend was incurred for (`eval`, `eval_judge`), which is how verification spend is priced apart from the traffic serving real users; unlabelled traffic collapses into the single `null` bucket.\n",
            "schema": {
              "type": "string",
              "enum": [
                "model",
                "ai_provider",
                "agent",
                "run",
                "day",
                "meter_type",
                "actor",
                "session",
                "source"
              ]
            }
          },
          {
            "name": "from",
            "in": "query",
            "required": false,
            "description": "Inclusive lower bound (ISO-8601 timestamp) on the event created_at. Omit for no lower bound.\n",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "to",
            "in": "query",
            "required": false,
            "description": "Inclusive upper bound (ISO-8601 timestamp) on the event created_at. Omit for no upper bound.\n",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "meter_type",
            "in": "query",
            "required": false,
            "description": "Narrow the rollup to one meter type (e.g. `llm_tokens`, `compute_execution`, `api_request`, `storage`). Omit to include every meter. An unknown type yields an empty rollup, not an error.\n",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The aggregated usage rollup",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/UsageAggregate"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request (missing project_id, invalid group_by or timestamp)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/usage/thresholds": {
      "get": {
        "tags": [
          "Usage"
        ],
        "summary": "List usage thresholds",
        "description": "Lists the usage alert thresholds the caller can access, optionally filtered by project_id. Each threshold fires the `usage.threshold_crossed` webhook when a project's cost or token usage over a calendar-month or rolling-24h window crosses the configured value.\n",
        "operationId": "listUsageThresholds",
        "parameters": [
          {
            "name": "project_id",
            "in": "query",
            "required": false,
            "description": "Filter by project public ID",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Maximum number of results to return",
            "schema": {
              "type": "integer",
              "default": 50
            }
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "description": "Number of results to skip",
            "schema": {
              "type": "integer",
              "default": 0
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The usage thresholds",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "total",
                    "limit",
                    "offset"
                  ],
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/UsageThreshold"
                      }
                    },
                    "total": {
                      "type": "integer"
                    },
                    "limit": {
                      "type": "integer"
                    },
                    "offset": {
                      "type": "integer"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      },
      "post": {
        "tags": [
          "Usage"
        ],
        "summary": "Create a usage threshold",
        "description": "Creates a usage alert threshold on a project. Thresholds are immutable apart from deletion — to change one, delete and recreate it (which resets its fire state).\n",
        "operationId": "createUsageThreshold",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateUsageThresholdRequest"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The created threshold",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/UsageThreshold"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request (missing/invalid metric, window, or threshold)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/usage/thresholds/{threshold_id}": {
      "delete": {
        "tags": [
          "Usage"
        ],
        "summary": "Delete a usage threshold",
        "description": "Deletes a usage threshold, resetting its fire state. Recreating a threshold starts its once-per-window / hysteresis state fresh.\n",
        "operationId": "deleteUsageThreshold",
        "parameters": [
          {
            "name": "threshold_id",
            "in": "path",
            "required": true,
            "description": "Threshold public ID",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "Deleted"
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Threshold not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/usage/receipt": {
      "get": {
        "tags": [
          "Usage"
        ],
        "summary": "Get a generation or run billing receipt",
        "description": "Returns a billing receipt. Pass generation_id for a per-generation receipt, or orchestration_run_id for a per-run receipt summed across the orchestration run's meters — both share the same shape (per-model line items with tokens, the price-book version that priced them, and cost, plus totals). Exactly one of generation_id or orchestration_run_id must be supplied.\n",
        "operationId": "getUsageReceipt",
        "parameters": [
          {
            "name": "generation_id",
            "in": "query",
            "required": false,
            "description": "Generation public ID. Mutually exclusive with orchestration_run_id.\n",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "orchestration_run_id",
            "in": "query",
            "required": false,
            "description": "Orchestration run public ID. Returns the receipt summed across every generation the run metered. Mutually exclusive with generation_id.\n",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The generation or run usage receipt",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/UsageReceipt"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Generation not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/usage/prices": {
      "get": {
        "tags": [
          "Usage"
        ],
        "summary": "Get the price book",
        "description": "Returns the global price book — the versioned per-provider/model unit prices used to compute usage cost at write time. Readable by any authenticated user.\n",
        "operationId": "getPriceBook",
        "responses": {
          "200": {
            "description": "The current price book",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PriceBookResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      },
      "put": {
        "tags": [
          "Usage"
        ],
        "summary": "Upsert price-book rows",
        "description": "Upserts price rows keyed on (provider, model, effective_from). Admin only. `effective_from` must be in the future once the (provider, model, component) is priced — past prices are immutable so recorded costs stay explainable; ship corrections as new future-dated rows. A first price, with no row in this scope or any broader one, may be dated now or earlier.\n",
        "operationId": "upsertPriceBook",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpsertPricesRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The upserted price rows",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PriceBookResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request (e.g. non-future effective_from)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/users/me": {
      "get": {
        "tags": [
          "Users"
        ],
        "summary": "Get the current authenticated user",
        "description": "Returns the profile of the currently authenticated user",
        "operationId": "getCurrentUser",
        "responses": {
          "200": {
            "description": "Current user returned successfully",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/UserRecord"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/users": {
      "get": {
        "tags": [
          "Users"
        ],
        "summary": "List all users",
        "description": "Returns a list of all users",
        "operationId": "listUsers",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Maximum number of results to return",
            "schema": {
              "type": "integer",
              "default": 50
            }
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "description": "Number of results to skip",
            "schema": {
              "type": "integer",
              "default": 0
            }
          }
        ],
        "responses": {
          "200": {
            "description": "List of users returned successfully",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "total",
                    "limit",
                    "offset"
                  ],
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/UserRecord"
                      }
                    },
                    "total": {
                      "type": "integer"
                    },
                    "limit": {
                      "type": "integer"
                    },
                    "offset": {
                      "type": "integer"
                    }
                  }
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      },
      "post": {
        "tags": [
          "Users"
        ],
        "summary": "Create a user",
        "description": "Creates a new user in the system",
        "operationId": "createUser",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "username",
                  "password"
                ],
                "properties": {
                  "username": {
                    "type": "string",
                    "example": "johndoe"
                  },
                  "password": {
                    "type": "string",
                    "format": "password",
                    "example": "supersecret"
                  },
                  "role": {
                    "type": "string",
                    "enum": [
                      "admin",
                      "user"
                    ],
                    "example": "user"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "User created successfully",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/UserRecord"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/users/{user_id}": {
      "get": {
        "tags": [
          "Users"
        ],
        "summary": "Get a user by ID",
        "description": "Returns the data of a specific user",
        "operationId": "getUser",
        "parameters": [
          {
            "name": "user_id",
            "in": "path",
            "required": true,
            "description": "User ID",
            "schema": {
              "type": "string",
              "example": "user_V1StGXR8Z5jdHi6B"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "User found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/UserRecord"
                }
              }
            }
          },
          "404": {
            "description": "User not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      },
      "delete": {
        "tags": [
          "Users"
        ],
        "summary": "Delete a user by ID",
        "description": "Deletes a specific user",
        "operationId": "deleteUser",
        "parameters": [
          {
            "name": "user_id",
            "in": "path",
            "required": true,
            "description": "User ID",
            "schema": {
              "type": "string",
              "example": "user_V1StGXR8Z5jdHi6B"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "User deleted successfully"
          },
          "404": {
            "description": "User not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/users/bootstrap": {
      "post": {
        "tags": [
          "Users"
        ],
        "summary": "Create the first admin user",
        "description": "Creates the first admin user. Returns 409 if any user already exists.",
        "operationId": "bootstrapUser",
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "username",
                  "password"
                ],
                "properties": {
                  "username": {
                    "type": "string",
                    "example": "admin"
                  },
                  "password": {
                    "type": "string",
                    "format": "password",
                    "example": "supersecret"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Admin user created successfully",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/UserRecord"
                }
              }
            }
          },
          "409": {
            "description": "Users already exist",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/users/login": {
      "post": {
        "tags": [
          "Users"
        ],
        "summary": "Login user",
        "description": "Authenticates a user and returns a JWT token",
        "operationId": "loginUser",
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "username",
                  "password"
                ],
                "properties": {
                  "username": {
                    "type": "string",
                    "example": "admin"
                  },
                  "password": {
                    "type": "string",
                    "format": "password",
                    "example": "supersecret"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Login successful",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/LoginResponse"
                }
              }
            }
          },
          "401": {
            "description": "Invalid credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/users/{user_id}/policies": {
      "put": {
        "tags": [
          "Users"
        ],
        "summary": "Attach policies to a user",
        "description": "Replaces the user's policy list with the provided policy IDs. Requires admin role.",
        "operationId": "attachUserPolicies",
        "parameters": [
          {
            "name": "user_id",
            "in": "path",
            "required": true,
            "description": "User public ID (user_ prefix)",
            "schema": {
              "type": "string",
              "example": "user_V1StGXR8Z5jdHi6B"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "policy_ids"
                ],
                "properties": {
                  "policy_ids": {
                    "x-soat-ref": "policies",
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "description": "List of policy public IDs to attach (replaces existing)",
                    "example": [
                      "pol_V1StGXR8Z5jdHi6B"
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "204": {
            "description": "Policies attached successfully"
          },
          "400": {
            "description": "Bad request (policy_ids must be an array)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized"
          },
          "403": {
            "description": "Forbidden (non-admin user)"
          },
          "404": {
            "description": "User or policy not found"
          }
        }
      }
    },
    "/api/v1/webhooks": {
      "get": {
        "description": "Lists webhooks. Use the project_id query parameter to filter by project.",
        "tags": [
          "Webhooks"
        ],
        "summary": "List webhooks",
        "operationId": "listWebhooks",
        "parameters": [
          {
            "name": "project_id",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Maximum number of results to return",
            "schema": {
              "type": "integer",
              "default": 50
            }
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "description": "Number of results to skip",
            "schema": {
              "type": "integer",
              "default": 0
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A list of webhooks",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "total",
                    "limit",
                    "offset"
                  ],
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Webhook"
                      }
                    },
                    "total": {
                      "type": "integer"
                    },
                    "limit": {
                      "type": "integer"
                    },
                    "offset": {
                      "type": "integer"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized"
          },
          "403": {
            "description": "Forbidden"
          }
        }
      },
      "post": {
        "description": "Creates a new webhook for a project",
        "tags": [
          "Webhooks"
        ],
        "summary": "Create a webhook",
        "operationId": "createWebhook",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateWebhookRequest"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Webhook created",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookWithSecret"
                }
              }
            }
          },
          "400": {
            "description": "Bad request"
          },
          "401": {
            "description": "Unauthorized"
          },
          "403": {
            "description": "Forbidden"
          }
        }
      }
    },
    "/api/v1/webhooks/{webhook_id}": {
      "get": {
        "description": "Retrieves the details of a specific webhook",
        "tags": [
          "Webhooks"
        ],
        "summary": "Get a webhook",
        "operationId": "getWebhook",
        "parameters": [
          {
            "name": "webhook_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Webhook details",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Webhook"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized"
          },
          "403": {
            "description": "Forbidden"
          },
          "404": {
            "description": "Webhook not found"
          }
        }
      },
      "put": {
        "description": "Updates an existing webhook's configuration",
        "tags": [
          "Webhooks"
        ],
        "summary": "Update a webhook",
        "operationId": "updateWebhook",
        "parameters": [
          {
            "name": "webhook_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateWebhookRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Webhook updated",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Webhook"
                }
              }
            }
          },
          "400": {
            "description": "Bad request"
          },
          "401": {
            "description": "Unauthorized"
          },
          "403": {
            "description": "Forbidden"
          },
          "404": {
            "description": "Webhook not found"
          }
        }
      },
      "delete": {
        "description": "Deletes a webhook and stops all event deliveries",
        "tags": [
          "Webhooks"
        ],
        "summary": "Delete a webhook",
        "operationId": "deleteWebhook",
        "parameters": [
          {
            "name": "webhook_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "Webhook deleted"
          },
          "401": {
            "description": "Unauthorized"
          },
          "403": {
            "description": "Forbidden"
          },
          "404": {
            "description": "Webhook not found"
          }
        }
      }
    },
    "/api/v1/webhook-deliveries": {
      "get": {
        "description": "Lists event deliveries for a webhook (webhook_id is required).",
        "tags": [
          "Webhooks"
        ],
        "summary": "List webhook deliveries",
        "operationId": "listWebhookDeliveries",
        "parameters": [
          {
            "name": "webhook_id",
            "in": "query",
            "required": true,
            "description": "Webhook to list deliveries for (wh_...)",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "default": 50
            }
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "default": 0
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A list of deliveries",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DeliveryListResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized"
          },
          "403": {
            "description": "Forbidden"
          },
          "404": {
            "description": "Webhook not found"
          }
        }
      }
    },
    "/api/v1/webhook-deliveries/{delivery_id}": {
      "get": {
        "description": "Retrieves the details of a specific webhook delivery",
        "tags": [
          "Webhooks"
        ],
        "summary": "Get a delivery",
        "operationId": "getWebhookDelivery",
        "parameters": [
          {
            "name": "delivery_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Delivery details",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Delivery"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized"
          },
          "403": {
            "description": "Forbidden"
          },
          "404": {
            "description": "Delivery not found"
          }
        }
      }
    },
    "/api/v1/webhook-deliveries/{delivery_id}/redeliver": {
      "post": {
        "description": "Queues the stored payload of an existing delivery to be sent again.\n\nA new delivery record is created rather than the original being reset,\nso the original attempt stays in the history. The send happens in the\nbackground: poll the returned delivery to observe its outcome.\n",
        "tags": [
          "Webhooks"
        ],
        "summary": "Redeliver a webhook delivery",
        "operationId": "redeliverWebhookDelivery",
        "parameters": [
          {
            "name": "delivery_id",
            "in": "path",
            "required": true,
            "description": "Delivery to send again (wh_deliv_...)",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "202": {
            "description": "Redelivery queued; poll the returned delivery for its outcome",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Delivery"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized"
          },
          "403": {
            "description": "Forbidden"
          },
          "404": {
            "description": "Delivery not found"
          }
        }
      }
    },
    "/api/v1/webhooks/{webhook_id}/secret": {
      "get": {
        "description": "Retrieves the signing secret for the specified webhook",
        "tags": [
          "Webhooks"
        ],
        "summary": "Get webhook secret",
        "operationId": "getWebhookSecret",
        "parameters": [
          {
            "name": "webhook_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Webhook secret",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookSecretResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized"
          },
          "403": {
            "description": "Forbidden"
          },
          "404": {
            "description": "Webhook not found"
          }
        }
      }
    },
    "/api/v1/webhooks/{webhook_id}/rotate-secret": {
      "post": {
        "description": "Rotates the secret key for the specified webhook",
        "tags": [
          "Webhooks"
        ],
        "summary": "Rotate webhook secret",
        "operationId": "rotateWebhookSecret",
        "parameters": [
          {
            "name": "webhook_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Secret rotated",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookWithSecret"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized"
          },
          "403": {
            "description": "Forbidden"
          },
          "404": {
            "description": "Webhook not found"
          }
        }
      }
    },
    "/api/v1/workflows": {
      "get": {
        "description": "Lists workflow definitions in a project.",
        "tags": [
          "Workflows"
        ],
        "summary": "List workflows",
        "operationId": "listWorkflows",
        "parameters": [
          {
            "name": "project_id",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Maximum number of results to return",
            "schema": {
              "type": "integer",
              "default": 50
            }
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "description": "Number of results to skip",
            "schema": {
              "type": "integer",
              "default": 0
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A list of workflows",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "total",
                    "limit",
                    "offset"
                  ],
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Workflow"
                      }
                    },
                    "total": {
                      "type": "integer"
                    },
                    "limit": {
                      "type": "integer"
                    },
                    "offset": {
                      "type": "integer"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized"
          },
          "403": {
            "description": "Forbidden"
          }
        }
      },
      "post": {
        "description": "Creates a new workflow definition. The definition is statically validated.",
        "tags": [
          "Workflows"
        ],
        "summary": "Create a workflow",
        "operationId": "createWorkflow",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateWorkflowRequest"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Workflow created",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Workflow"
                }
              }
            }
          },
          "400": {
            "description": "Bad request (invalid definition)"
          },
          "401": {
            "description": "Unauthorized"
          },
          "403": {
            "description": "Forbidden"
          },
          "409": {
            "description": "A workflow with this name already exists"
          }
        }
      }
    },
    "/api/v1/workflows/{workflow_id}": {
      "get": {
        "description": "Retrieves a workflow definition.",
        "tags": [
          "Workflows"
        ],
        "summary": "Get a workflow",
        "operationId": "getWorkflow",
        "parameters": [
          {
            "name": "workflow_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Workflow details",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Workflow"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized"
          },
          "403": {
            "description": "Forbidden"
          },
          "404": {
            "description": "Workflow not found"
          }
        }
      },
      "patch": {
        "description": "Updates a workflow definition. Structural changes (states/transitions) are re-validated. Existing tasks in a removed state stay put but can only leave via transitions valid in the new definition.",
        "tags": [
          "Workflows"
        ],
        "summary": "Update a workflow",
        "operationId": "updateWorkflow",
        "parameters": [
          {
            "name": "workflow_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateWorkflowRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Workflow updated",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Workflow"
                }
              }
            }
          },
          "400": {
            "description": "Bad request (invalid definition)"
          },
          "401": {
            "description": "Unauthorized"
          },
          "403": {
            "description": "Forbidden"
          },
          "404": {
            "description": "Workflow not found"
          }
        }
      },
      "delete": {
        "description": "Deletes a workflow. Rejected while open tasks exist.",
        "tags": [
          "Workflows"
        ],
        "summary": "Delete a workflow",
        "operationId": "deleteWorkflow",
        "parameters": [
          {
            "name": "workflow_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "Workflow deleted"
          },
          "401": {
            "description": "Unauthorized"
          },
          "403": {
            "description": "Forbidden"
          },
          "404": {
            "description": "Workflow not found"
          },
          "409": {
            "description": "The workflow has open tasks and cannot be deleted"
          }
        }
      }
    },
    "/api/v1/workflows/{workflow_id}/versions": {
      "get": {
        "description": "Returns the workflow's archived state machines, newest first. A version is written on create and on every subsequent write that changes the definition (`states`, `transitions`, `payload_schema`) — through the REST API or a formation apply alike. Metadata-only edits (name, description) do not archive a version. See [Versioning](/docs/modules/workflows#versioning).\n",
        "tags": [
          "Workflows"
        ],
        "summary": "List a workflow's versions",
        "operationId": "listWorkflowVersions",
        "parameters": [
          {
            "name": "workflow_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Maximum number of results to return",
            "schema": {
              "type": "integer",
              "default": 50
            }
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "description": "Number of results to skip",
            "schema": {
              "type": "integer",
              "default": 0
            }
          }
        ],
        "responses": {
          "200": {
            "description": "List of workflow versions, newest first",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "total",
                    "limit",
                    "offset"
                  ],
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/WorkflowVersion"
                      }
                    },
                    "total": {
                      "type": "integer"
                    },
                    "limit": {
                      "type": "integer"
                    },
                    "offset": {
                      "type": "integer"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized"
          },
          "403": {
            "description": "Forbidden"
          },
          "404": {
            "description": "Workflow not found"
          }
        }
      }
    },
    "/api/v1/workflows/{workflow_id}/versions/{version}": {
      "get": {
        "description": "Returns the exact state machine a given version describes. Every task records the version it entered on in `workflow_version` and runs on that machine for its whole life, so this is how you read the definition a task is actually being validated against — including a task whose workflow has been rewired since.\n",
        "tags": [
          "Workflows"
        ],
        "summary": "Fetch an archived workflow version",
        "operationId": "getWorkflowVersion",
        "parameters": [
          {
            "name": "workflow_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "version",
            "in": "path",
            "required": true,
            "description": "The archived version number",
            "schema": {
              "type": "integer",
              "minimum": 1
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Archived workflow version",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WorkflowVersion"
                }
              }
            }
          },
          "400": {
            "description": "Bad request — version is not a positive integer"
          },
          "401": {
            "description": "Unauthorized"
          },
          "403": {
            "description": "Forbidden"
          },
          "404": {
            "description": "Not found"
          }
        }
      }
    },
    "/api/v1/workflows/{workflow_id}/versions/{version}/restore": {
      "post": {
        "description": "Writes an archived version's state machine back as the workflow's live definition, which archives it again as a **new** version rather than rewinding the counter — so a task pinned to any version in between still runs on the machine it entered on.\n\nThe restore runs through the ordinary update path, so the archived definition goes through the same validation as an authored one. That includes resolving every `on_enter` dispatch target, so restoring a version whose agent or orchestration has since been deleted fails with `400` rather than writing a definition that would strand a task on entry. Restoring the definition the workflow already holds is a no-op and archives nothing. Tasks already in flight are unaffected either way — a restore is an ordinary edit, and pinning is what keeps it from reaching them.\n",
        "tags": [
          "Workflows"
        ],
        "summary": "Restore an archived workflow state machine",
        "operationId": "restoreWorkflowVersion",
        "parameters": [
          {
            "name": "workflow_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "version",
            "in": "path",
            "required": true,
            "description": "The archived version number",
            "schema": {
              "type": "integer",
              "minimum": 1
            }
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/RestoreWorkflowVersionRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The workflow, at its new version",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Workflow"
                }
              }
            }
          },
          "400": {
            "description": "Bad request — version is not a positive integer, or the restored definition is invalid"
          },
          "401": {
            "description": "Unauthorized"
          },
          "403": {
            "description": "Forbidden"
          },
          "404": {
            "description": "Not found"
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "ActivityEntry": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "example": "acte_V1StGXR8Z5jdHi6B"
          },
          "project_id": {
            "x-soat-ref": "projects",
            "type": "string"
          },
          "kind": {
            "type": "string",
            "enum": [
              "action_executed",
              "approval_resolved",
              "exception_created",
              "schedule_fired"
            ],
            "description": "How the entry was produced"
          },
          "severity": {
            "type": "string",
            "enum": [
              "info",
              "warning",
              "critical"
            ]
          },
          "summary": {
            "type": "string",
            "description": "One-line, human-readable description"
          },
          "detail": {
            "type": "object",
            "nullable": true,
            "description": "Kind-specific structured context (tool, args digest, node id, generation id, guardrail policy version)"
          },
          "orchestration_run_id": {
            "type": "string",
            "nullable": true,
            "description": "Originating orchestration run, if any"
          },
          "agent_id": {
            "type": "string",
            "nullable": true,
            "description": "Associated agent, if any"
          },
          "ref_id": {
            "type": "string",
            "nullable": true,
            "description": "Producer-specific reference (the approval, exception, or trigger firing id the entry came from, or the executed tool's id)"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "ActorRecord": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "Actor ID",
            "example": "actor_V1StGXR8Z5jdHi6B"
          },
          "project_id": {
            "x-soat-ref": "projects",
            "type": "string",
            "description": "Project ID",
            "example": "proj_V1StGXR8Z5jdHi6B"
          },
          "name": {
            "type": "string",
            "example": "Alice"
          },
          "external_id": {
            "type": "string",
            "nullable": true,
            "description": "External identifier (e.g. WhatsApp phone number)",
            "example": "+15551234567"
          },
          "instructions": {
            "type": "string",
            "nullable": true,
            "description": "Persona-specific instructions composed into the effective system prompt during conversation generation."
          },
          "agent_id": {
            "x-soat-ref": "agents",
            "type": "string",
            "nullable": true,
            "description": "Agent this actor is linked to (mutually exclusive with chatId)."
          },
          "chat_id": {
            "x-soat-ref": "chats",
            "type": "string",
            "nullable": true,
            "description": "Chat this actor is linked to (mutually exclusive with agentId)."
          },
          "tags": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            }
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "ErrorResponse": {
        "type": "object",
        "required": [
          "error"
        ],
        "properties": {
          "error": {
            "type": "object",
            "description": "Structured error. Every error response uses this shape — 401, 403 and the 500 catch-all included — so `code` can be read without first checking the type of `error`.",
            "required": [
              "code",
              "message",
              "hint",
              "docs_url"
            ],
            "properties": {
              "code": {
                "type": "string",
                "description": "A key from the server's ERROR_CODES registry.",
                "example": "RESOURCE_NOT_FOUND"
              },
              "message": {
                "type": "string",
                "example": "Resource not found"
              },
              "hint": {
                "type": "string",
                "description": "What to do about this error. Resolved per code, so a caller that has never seen the code before can act on the response without leaving it.",
                "example": "Check the id, and check that the credential can see the project that owns the resource."
              },
              "docs_url": {
                "type": "string",
                "format": "uri",
                "description": "The reference-page anchor documenting this code.",
                "example": "https://soat.ttoss.dev/docs/error-codes#resource_not_found"
              },
              "meta": {
                "type": "object",
                "description": "Optional structured context for the error."
              }
            }
          }
        }
      },
      "Agent": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "Public ID of the agent",
            "example": "agent_V1StGXR8Z5jdHi6B"
          },
          "project_id": {
            "type": "string",
            "description": "Public ID of the owning project",
            "x-soat-ref": "projects"
          },
          "ai_provider_id": {
            "type": "string",
            "nullable": true,
            "description": "Public ID of the pinned AI provider. Null when the agent resolves its model through `model_route_id` instead.",
            "x-soat-ref": "ai-providers"
          },
          "model_route_id": {
            "type": "string",
            "nullable": true,
            "description": "Public ID of the model route that resolves this agent's completion model. Null when the agent pins a provider through `ai_provider_id`. Mutually exclusive with `ai_provider_id` and `model`.",
            "x-soat-ref": "model-routes"
          },
          "name": {
            "type": "string",
            "nullable": true,
            "description": "Display name"
          },
          "instructions": {
            "type": "string",
            "nullable": true,
            "description": "System instructions guiding behavior"
          },
          "model": {
            "type": "string",
            "nullable": true,
            "description": "Model identifier"
          },
          "tool_bindings": {
            "type": "array",
            "nullable": true,
            "items": {
              "$ref": "#/components/schemas/ToolBinding"
            },
            "description": "Tools attached to this agent, one binding object per tool — the canonical attachment field. See [Tool Bindings](/docs/modules/agents#tool-bindings)."
          },
          "max_steps": {
            "type": "integer",
            "nullable": true,
            "description": "Maximum agent loop steps before stopping. The budget bounds a **turn**: a generation that pauses at `requires_action` and resumes after `submit-tool-outputs` continues the same turn and spends what is left of it, so a turn that arrives with nothing left completes with `stop_reason: \"max_steps\"` instead of calling the model again."
          },
          "tool_choice": {
            "nullable": true,
            "description": "Tool choice strategy. Accepts a string (`\"auto\"`, `\"required\"`) or an object (`{ \"type\": \"tool\", \"tool_name\": \"my_tool\" }`). A forcing value (`\"required\"` or the object form) forbids a final assistant message on every step of every turn, including a resumed or continued one, so it requires a `has_tool_call` entry in `stop_conditions` — otherwise the write is refused with `FORCED_TOOL_CHOICE_CANNOT_STOP`."
          },
          "stop_conditions": {
            "type": "array",
            "nullable": true,
            "items": {
              "type": "object"
            },
            "description": "Conditions that end the agent's work early, on top of `max_steps` — turn-scoped (`has_tool_call`) or chain-scoped (`max_chain_generations`). See the create request body for the accepted shapes."
          },
          "active_tool_ids": {
            "x-soat-ref": "tools",
            "type": "array",
            "nullable": true,
            "items": {
              "type": "string"
            },
            "description": "Subset of the bound tools that are active"
          },
          "guardrail_ids": {
            "x-soat-ref": "guardrails",
            "type": "array",
            "nullable": true,
            "items": {
              "type": "string"
            },
            "description": "Guardrails attached at the agent scope, governing every tool call the agent makes."
          },
          "step_rules": {
            "type": "array",
            "nullable": true,
            "items": {
              "type": "object"
            },
            "description": "Per-step overrides of `tool_choice` and `active_tool_ids`. Steps are numbered from the first step of the **turn**, and that numbering spans a `requires_action` pause — a rule fires once per turn, not once per resumption."
          },
          "boundary_policy": {
            "type": "object",
            "nullable": true,
            "description": "Allowed/denied SOAT actions"
          },
          "temperature": {
            "type": "number",
            "nullable": true,
            "description": "Sampling temperature"
          },
          "knowledge_config": {
            "type": "object",
            "nullable": true,
            "description": "Knowledge retrieval config injected before every generation",
            "properties": {
              "memory_ids": {
                "x-soat-ref": "memories",
                "type": "array",
                "items": {
                  "type": "string"
                }
              },
              "memory_tags": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              },
              "document_ids": {
                "x-soat-ref": "documents",
                "type": "array",
                "items": {
                  "type": "string"
                }
              },
              "document_paths": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              },
              "min_score": {
                "type": "number"
              },
              "limit": {
                "type": "integer"
              },
              "write_memory_id": {
                "x-soat-ref": "memories",
                "type": "string",
                "nullable": true,
                "description": "Public ID of the memory the agent can write to during generation. When set, a write_memory tool is automatically available to the agent."
              },
              "extraction": {
                "description": "Automatic fact extraction from completed generation turns (requires write_memory_id). Pass `true` to enable with defaults, or an object to customize the provider, model, and prompt used for the extraction completion. Extracted facts are written to the write memory through the standard dedup/merge/skip algorithm.",
                "oneOf": [
                  {
                    "type": "boolean"
                  },
                  {
                    "type": "object",
                    "properties": {
                      "enabled": {
                        "type": "boolean",
                        "description": "Defaults to true when the object form is used. Set false to keep the configuration but disable extraction."
                      },
                      "ai_provider_id": {
                        "x-soat-ref": "ai-providers",
                        "type": "string",
                        "description": "AI provider override for extraction calls. Must belong to the agent's project. Its default_model becomes the model fallback."
                      },
                      "model": {
                        "type": "string",
                        "description": "Model override for extraction calls."
                      },
                      "prompt": {
                        "type": "string",
                        "description": "Replaces the default task instructions. The JSON response contract and the conversation transcript are always appended by the server."
                      }
                    }
                  }
                ]
              }
            }
          },
          "output_schema": {
            "type": "object",
            "nullable": true,
            "description": "JSON Schema describing the structured object the model must return. When set, non-streaming generations constrain output to this schema and the parsed value is returned as `output.object`. The schema is enforced on the way back, not just sent to the model: an object that violates it fails the generation with 502 `OUTPUT_SCHEMA_VALIDATION_FAILED`, naming the violated field. Constraints beyond `required`/`type` (`minLength`, `enum`, `pattern`, `minItems`) are honored and are what reject a structurally valid but degenerate answer. See the Structured Output section in the Agents module docs."
          },
          "max_context_messages": {
            "type": "integer",
            "nullable": true,
            "description": "Maximum number of recent messages to include in the context window sent to the model. When null, all messages are included."
          },
          "single_session_per_actor": {
            "type": "boolean",
            "description": "When true, only one open session per actor_id is allowed for this agent. Creating a second open session for the same actor returns 409."
          },
          "trace_content_mode": {
            "type": "string",
            "nullable": true,
            "enum": [
              "full",
              "none",
              null
            ],
            "description": "Agent-scope zero-retention setting. `null` (the default) inherits the project's `trace_content_mode`; `none` means this agent's trace and generation content is never persisted. An agent may tighten a storing project to `none` but cannot loosen a `none` project back to `full`."
          },
          "on_approval_expiry": {
            "type": "string",
            "nullable": true,
            "enum": [
              "terminate",
              "react",
              null
            ],
            "description": "What happens when one of this agent's held tool calls expires un-approved. `null` (the default) and `terminate` end the chain there — the expired approval, its `approvals.expired` event and the auto-filed `approval_expired` exception are the whole record. `react` spawns a continuation that reports the staleness to the agent, for an agent that acts on it."
          },
          "version": {
            "type": "integer",
            "description": "Current config version. Starts at 1 and increments on every write that changes the config; each increment archives the new config as an `AgentVersion`. A write that changes nothing leaves it untouched.",
            "example": 3
          },
          "active_release": {
            "nullable": true,
            "allOf": [
              {
                "$ref": "#/components/schemas/AgentRelease"
              }
            ],
            "description": "Staged rollout in progress, or null when all traffic serves this config."
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "AgentRelease": {
        "type": "object",
        "description": "A staged rollout splitting traffic between two archived versions. See [Versioning and Staged Rollout](/docs/modules/agents#versioning-and-staged-rollout).",
        "required": [
          "stable_version",
          "canary_version",
          "canary_percent"
        ],
        "properties": {
          "stable_version": {
            "type": "integer",
            "minimum": 1,
            "description": "Version served to traffic not assigned to the canary",
            "example": 3
          },
          "canary_version": {
            "type": "integer",
            "minimum": 1,
            "description": "Version under trial. Must differ from `stable_version`.",
            "example": 4
          },
          "canary_percent": {
            "type": "integer",
            "minimum": 0,
            "maximum": 100,
            "description": "Percentage of traffic assigned to `canary_version`",
            "example": 20
          },
          "promotion_gate": {
            "x-soat-ref": "evals",
            "type": "string",
            "nullable": true,
            "description": "Eval that must have a passing run against `canary_version` before `promote` is allowed, or null for an ungated rollout. The gate constrains only how the rollout ends — traffic is split the same way either way. See [Eval-gated promotion](/docs/modules/agents#eval-gated-promotion).",
            "example": "eval_V1StGXR8Z5jdHi6B"
          }
        }
      },
      "AgentVersion": {
        "type": "object",
        "description": "An immutable archive of an agent's configuration at one version.",
        "properties": {
          "id": {
            "type": "string",
            "description": "Public ID of the archived version",
            "example": "agver_V1StGXR8Z5jdHi6B"
          },
          "agent_id": {
            "x-soat-ref": "agents",
            "type": "string",
            "description": "Public ID of the agent this version belongs to",
            "example": "agent_V1StGXR8Z5jdHi6B"
          },
          "version": {
            "type": "integer",
            "description": "The archived version number",
            "example": 1
          },
          "config": {
            "type": "object",
            "additionalProperties": true,
            "description": "The agent's configuration as it stood at this version: every mutable field of the `Agent` schema (`instructions`, `model`, `tool_bindings`, `max_steps`, `tool_choice`, `stop_conditions`, `active_tool_ids`, `step_rules`, `boundary_policy`, `temperature`, `knowledge_config`, `output_schema`, `max_context_messages`, `single_session_per_actor`, `on_approval_expiry`, `guardrail_ids`, `ai_provider_id`, `model_route_id`, `name`), and none of its identity or bookkeeping fields (`id`, `project_id`, `version`, `active_release`, timestamps).\n\nDeliberately open rather than a fixed schema: an archive written by an earlier release of SOAT reflects the agent surface **of its own time**, so it may carry fields the current schema no longer defines, or lack ones it has since gained. Knowledge retrieval is not part of the snapshot — a version records which `knowledge_config` applied, while the documents and memories it resolves keep their own histories and are pinned at generation time."
          },
          "label": {
            "type": "string",
            "nullable": true,
            "description": "Optional human tag, set with `version_label` on the write that created this version. Restore, promote and abort set one automatically (e.g. `restored from v1`).",
            "example": "pre-tone-change"
          },
          "eval_run_id": {
            "x-soat-ref": "eval-runs",
            "type": "string",
            "nullable": true,
            "description": "The eval run that cleared the release's `promotion_gate` when this version was promoted. Null for every version that did not go live through a gated promotion — which is most of them.",
            "example": "evrun_V1StGXR8Z5jdHi6B"
          },
          "created_by": {
            "x-soat-ref": "users",
            "type": "string",
            "nullable": true,
            "description": "Public ID of the user whose action produced this version. A formation apply is attributed to the project's owning identity; null when no principal could be resolved."
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "RestoreAgentVersionRequest": {
        "type": "object",
        "properties": {
          "label": {
            "type": "string",
            "description": "Tag for the version this restore creates. Defaults to `restored from v{version}`.",
            "example": "rollback-incident-42"
          }
        }
      },
      "SetAgentReleaseRequest": {
        "type": "object",
        "required": [
          "stable_version",
          "canary_version",
          "canary_percent"
        ],
        "properties": {
          "stable_version": {
            "type": "integer",
            "minimum": 1,
            "description": "An existing version to serve as the baseline",
            "example": 3
          },
          "canary_version": {
            "type": "integer",
            "minimum": 1,
            "description": "An existing version to trial. Must differ from `stable_version`.",
            "example": 4
          },
          "canary_percent": {
            "type": "integer",
            "minimum": 0,
            "maximum": 100,
            "description": "Percentage of traffic to assign to `canary_version`",
            "example": 20
          },
          "promotion_gate": {
            "x-soat-ref": "evals",
            "type": "string",
            "nullable": true,
            "description": "Eval to gate promotion on. It must belong to this project and evaluate this agent; anything else is a `400`. Omit it, or send null, for a rollout that can be promoted at will.",
            "example": "eval_V1StGXR8Z5jdHi6B"
          }
        }
      },
      "ToolBinding": {
        "type": "object",
        "description": "One agent↔tool attachment. Exactly one of `tool_id` (persisted tool reference) or `tool` (inline ephemeral definition) per entry. Tool-call gating is owned by [Guardrails](/docs/modules/guardrails), attached via `guardrail_ids` on the project, agent, or tool — not on the binding.",
        "properties": {
          "tool_id": {
            "x-soat-ref": "tools",
            "type": "string",
            "description": "Public ID of a persisted tool. Exactly one of `tool_id`/`tool`.",
            "example": "tool_V1StGXR8Z5jdHi6B"
          },
          "tool": {
            "$ref": "#/components/schemas/CreateToolRequest"
          }
        }
      },
      "CreateAgentRequest": {
        "type": "object",
        "description": "Exactly one of `ai_provider_id` or `model_route_id` must be set (400 otherwise). `model` names the model on a pinned provider and cannot be combined with `model_route_id`, whose targets each name their own model.",
        "properties": {
          "project_id": {
            "x-soat-ref": "projects",
            "type": "string",
            "description": "Public ID of the project"
          },
          "ai_provider_id": {
            "x-soat-ref": "ai-providers",
            "type": "string",
            "description": "Public ID of the AI provider to pin. Mutually exclusive with `model_route_id`."
          },
          "model_route_id": {
            "x-soat-ref": "model-routes",
            "type": "string",
            "description": "Public ID of a model route in the same project. The agent's completion model is then resolved through the route's ordered targets with failover. Mutually exclusive with `ai_provider_id` and `model`."
          },
          "name": {
            "type": "string"
          },
          "instructions": {
            "type": "string"
          },
          "model": {
            "type": "string"
          },
          "tool_bindings": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ToolBinding"
            },
            "description": "Tools to attach, one binding object per tool — the only attachment field. An entry is either a reference (`{ \"tool_id\": … }`) or an inline definition (`{ \"tool\": … }`). See [Tool Bindings](/docs/modules/agents#tool-bindings)."
          },
          "max_steps": {
            "type": "integer"
          },
          "tool_choice": {
            "description": "Tool choice strategy. Accepts a string (`\"auto\"`, `\"required\"`) or an object (`{ \"type\": \"tool\", \"tool_name\": \"my_tool\" }`). A forcing value (`\"required\"` or the object form) forbids a final assistant message on every step of every turn, including a resumed or continued one, so it requires a `has_tool_call` entry in `stop_conditions` — otherwise the write is refused with `FORCED_TOOL_CHOICE_CANNOT_STOP`."
          },
          "stop_conditions": {
            "type": "array",
            "items": {
              "type": "object"
            },
            "description": "Conditions that end the agent's work early, on top of `max_steps`. Two scopes:\n\n`{\"type\": \"has_tool_call\", \"tool_name\": \"<resolved tool name>\"}` ends the **turn** after the step that calls the named tool. It narrows when the loop ends — it never lets it run past `max_steps`.\n\n`{\"type\": \"max_chain_generations\", \"max_generations\": <n>}` bounds the **continuation chain** instead: once the chain has spawned that many generations, further resumptions stop with `chain_limit` rather than extending it. It never shortens a turn. The effective ceiling is the smaller of this and the deployment's `MAX_CONTINUATION_CHAIN_GENERATIONS`, so an agent can be stricter than the platform but never looser.\n\nAn unknown `type`, a `has_tool_call` without a `tool_name`, a `max_chain_generations` whose `max_generations` is not a positive integer, or a non-object entry is rejected with 400."
          },
          "active_tool_ids": {
            "x-soat-ref": "tools",
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "guardrail_ids": {
            "x-soat-ref": "guardrails",
            "type": "array",
            "nullable": true,
            "items": {
              "type": "string"
            },
            "description": "Guardrails attached at the agent scope."
          },
          "step_rules": {
            "type": "array",
            "items": {
              "type": "object"
            }
          },
          "boundary_policy": {
            "type": "object"
          },
          "temperature": {
            "type": "number"
          },
          "knowledge_config": {
            "type": "object",
            "properties": {
              "memory_ids": {
                "x-soat-ref": "memories",
                "type": "array",
                "items": {
                  "type": "string"
                }
              },
              "memory_tags": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              },
              "document_ids": {
                "x-soat-ref": "documents",
                "type": "array",
                "items": {
                  "type": "string"
                }
              },
              "document_paths": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              },
              "min_score": {
                "type": "number"
              },
              "limit": {
                "type": "integer"
              },
              "write_memory_id": {
                "x-soat-ref": "memories",
                "type": "string",
                "nullable": true,
                "description": "Public ID of the memory the agent can write to during generation. When set, a write_memory tool is automatically available to the agent."
              },
              "extraction": {
                "description": "Automatic fact extraction from completed generation turns (requires write_memory_id). Pass `true` to enable with defaults, or an object to customize the provider, model, and prompt used for the extraction completion. Extracted facts are written to the write memory through the standard dedup/merge/skip algorithm.",
                "oneOf": [
                  {
                    "type": "boolean"
                  },
                  {
                    "type": "object",
                    "properties": {
                      "enabled": {
                        "type": "boolean",
                        "description": "Defaults to true when the object form is used. Set false to keep the configuration but disable extraction."
                      },
                      "ai_provider_id": {
                        "x-soat-ref": "ai-providers",
                        "type": "string",
                        "description": "AI provider override for extraction calls. Must belong to the agent's project. Its default_model becomes the model fallback."
                      },
                      "model": {
                        "type": "string",
                        "description": "Model override for extraction calls."
                      },
                      "prompt": {
                        "type": "string",
                        "description": "Replaces the default task instructions. The JSON response contract and the conversation transcript are always appended by the server."
                      }
                    }
                  }
                ]
              }
            }
          },
          "output_schema": {
            "type": "object",
            "nullable": true,
            "description": "JSON Schema describing the structured object the model must return. When set, non-streaming generations constrain output to this schema and the parsed value is returned as `output.object`. The schema is enforced on the way back, not just sent to the model: an object that violates it fails the generation with 502 `OUTPUT_SCHEMA_VALIDATION_FAILED`, naming the violated field. Constraints beyond `required`/`type` (`minLength`, `enum`, `pattern`, `minItems`) are honored and are what reject a structurally valid but degenerate answer. See the Structured Output section in the Agents module docs."
          },
          "max_context_messages": {
            "type": "integer",
            "description": "Maximum number of recent messages included in the context window. Null means no limit."
          },
          "single_session_per_actor": {
            "type": "boolean",
            "description": "When true, only one open session per actor_id is allowed for this agent."
          },
          "trace_content_mode": {
            "type": "string",
            "nullable": true,
            "enum": [
              "full",
              "none",
              null
            ],
            "description": "Zero-retention opt-in for this agent. `null` inherits the project's setting; `none` means trace and generation content is never written. Setting `full` under a project whose own mode is `none` is refused with 400 — the project is a floor an agent may only tighten."
          },
          "on_approval_expiry": {
            "type": "string",
            "nullable": true,
            "enum": [
              "terminate",
              "react",
              null
            ],
            "description": "What happens when one of this agent's held tool calls expires un-approved. `null` (the default) and `terminate` end the chain there — the expired approval, its `approvals.expired` event and the auto-filed `approval_expired` exception are the whole record. `react` spawns a continuation that reports the staleness to the agent, for an agent that acts on it."
          },
          "version_label": {
            "type": "string",
            "nullable": true,
            "description": "Optional tag for the config version this write archives (e.g. `initial`). Annotates the version only — it is not stored on the agent and is not part of the config, so labelling a change is never itself a change.",
            "example": "initial"
          }
        }
      },
      "UpdateAgentRequest": {
        "type": "object",
        "description": "The post-update state must still set exactly one of `ai_provider_id` or `model_route_id`. To switch a pinned agent to a route, send `model_route_id` together with `ai_provider_id: null` in the same request (and vice versa).",
        "properties": {
          "ai_provider_id": {
            "x-soat-ref": "ai-providers",
            "type": "string",
            "nullable": true
          },
          "model_route_id": {
            "x-soat-ref": "model-routes",
            "type": "string",
            "nullable": true,
            "description": "Model route in the same project. Mutually exclusive with `ai_provider_id` and `model`; set to null to clear."
          },
          "name": {
            "type": "string",
            "nullable": true
          },
          "instructions": {
            "type": "string",
            "nullable": true
          },
          "model": {
            "type": "string",
            "nullable": true
          },
          "tool_bindings": {
            "type": "array",
            "nullable": true,
            "items": {
              "$ref": "#/components/schemas/ToolBinding"
            },
            "description": "Tools attached to the agent — the only attachment field. Replaces the whole binding list; set to `null` to clear. See [Tool Bindings](/docs/modules/agents#tool-bindings)."
          },
          "max_steps": {
            "type": "integer",
            "nullable": true
          },
          "tool_choice": {
            "nullable": true,
            "description": "Tool choice strategy. Accepts a string (`\"auto\"`, `\"required\"`) or an object (`{ \"type\": \"tool\", \"tool_name\": \"my_tool\" }`). A forcing value (`\"required\"` or the object form) forbids a final assistant message on every step of every turn, including a resumed or continued one, so it requires a `has_tool_call` entry in `stop_conditions` — otherwise the write is refused with `FORCED_TOOL_CHOICE_CANNOT_STOP`."
          },
          "stop_conditions": {
            "type": "array",
            "nullable": true,
            "items": {
              "type": "object"
            },
            "description": "Conditions that end the agent's work early, on top of `max_steps`. Two scopes:\n\n`{\"type\": \"has_tool_call\", \"tool_name\": \"<resolved tool name>\"}` ends the **turn** after the step that calls the named tool. It narrows when the loop ends — it never lets it run past `max_steps`.\n\n`{\"type\": \"max_chain_generations\", \"max_generations\": <n>}` bounds the **continuation chain** instead: once the chain has spawned that many generations, further resumptions stop with `chain_limit` rather than extending it. It never shortens a turn. The effective ceiling is the smaller of this and the deployment's `MAX_CONTINUATION_CHAIN_GENERATIONS`, so an agent can be stricter than the platform but never looser.\n\nAn unknown `type`, a `has_tool_call` without a `tool_name`, a `max_chain_generations` whose `max_generations` is not a positive integer, or a non-object entry is rejected with 400."
          },
          "active_tool_ids": {
            "x-soat-ref": "tools",
            "type": "array",
            "nullable": true,
            "items": {
              "type": "string"
            }
          },
          "guardrail_ids": {
            "x-soat-ref": "guardrails",
            "type": "array",
            "nullable": true,
            "items": {
              "type": "string"
            },
            "description": "Guardrails attached at the agent scope."
          },
          "step_rules": {
            "type": "array",
            "nullable": true,
            "items": {
              "type": "object"
            }
          },
          "boundary_policy": {
            "type": "object",
            "nullable": true
          },
          "temperature": {
            "type": "number",
            "nullable": true
          },
          "knowledge_config": {
            "type": "object",
            "nullable": true,
            "properties": {
              "memory_ids": {
                "x-soat-ref": "memories",
                "type": "array",
                "items": {
                  "type": "string"
                }
              },
              "memory_tags": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              },
              "document_ids": {
                "x-soat-ref": "documents",
                "type": "array",
                "items": {
                  "type": "string"
                }
              },
              "document_paths": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              },
              "min_score": {
                "type": "number"
              },
              "limit": {
                "type": "integer"
              },
              "write_memory_id": {
                "x-soat-ref": "memories",
                "type": "string",
                "nullable": true,
                "description": "Public ID of the memory the agent can write to during generation. When set, a write_memory tool is automatically available to the agent."
              },
              "extraction": {
                "description": "Automatic fact extraction from completed generation turns (requires write_memory_id). Pass `true` to enable with defaults, or an object to customize the provider, model, and prompt used for the extraction completion. Extracted facts are written to the write memory through the standard dedup/merge/skip algorithm.",
                "oneOf": [
                  {
                    "type": "boolean"
                  },
                  {
                    "type": "object",
                    "properties": {
                      "enabled": {
                        "type": "boolean",
                        "description": "Defaults to true when the object form is used. Set false to keep the configuration but disable extraction."
                      },
                      "ai_provider_id": {
                        "x-soat-ref": "ai-providers",
                        "type": "string",
                        "description": "AI provider override for extraction calls. Must belong to the agent's project. Its default_model becomes the model fallback."
                      },
                      "model": {
                        "type": "string",
                        "description": "Model override for extraction calls."
                      },
                      "prompt": {
                        "type": "string",
                        "description": "Replaces the default task instructions. The JSON response contract and the conversation transcript are always appended by the server."
                      }
                    }
                  }
                ]
              }
            }
          },
          "output_schema": {
            "type": "object",
            "nullable": true,
            "description": "JSON Schema describing the structured object the model must return. When set, non-streaming generations constrain output to this schema and the parsed value is returned as `output.object`. The schema is enforced on the way back, not just sent to the model: an object that violates it fails the generation with 502 `OUTPUT_SCHEMA_VALIDATION_FAILED`, naming the violated field. Constraints beyond `required`/`type` (`minLength`, `enum`, `pattern`, `minItems`) are honored and are what reject a structurally valid but degenerate answer. See the Structured Output section in the Agents module docs."
          },
          "max_context_messages": {
            "type": "integer",
            "nullable": true,
            "description": "Maximum number of recent messages included in the context window. Null means no limit."
          },
          "single_session_per_actor": {
            "type": "boolean",
            "nullable": true,
            "description": "When true, only one open session per actor_id is allowed for this agent."
          },
          "trace_content_mode": {
            "type": "string",
            "nullable": true,
            "enum": [
              "full",
              "none",
              null
            ],
            "description": "Zero-retention opt-in for this agent. `null` inherits the project's setting; `none` means trace and generation content is never written. Setting `full` under a project whose own mode is `none` is refused with 400."
          },
          "on_approval_expiry": {
            "type": "string",
            "nullable": true,
            "enum": [
              "terminate",
              "react",
              null
            ],
            "description": "What happens when one of this agent's held tool calls expires un-approved. `null` (the default) and `terminate` end the chain there — the expired approval, its `approvals.expired` event and the auto-filed `approval_expired` exception are the whole record. `react` spawns a continuation that reports the staleness to the agent, for an agent that acts on it."
          },
          "version_label": {
            "type": "string",
            "nullable": true,
            "description": "Optional tag for the config version this write archives (e.g. `pre-tone-change`). Annotates the version only — it is not stored on the agent and is not part of the config, so labelling a change is never itself a change. Ignored when the write changes nothing, since no version is created.",
            "example": "pre-tone-change"
          }
        }
      },
      "CreateAgentGenerationRequest": {
        "type": "object",
        "required": [
          "messages"
        ],
        "properties": {
          "messages": {
            "type": "array",
            "minItems": 1,
            "items": {
              "type": "object",
              "additionalProperties": false,
              "required": [
                "role",
                "content"
              ],
              "properties": {
                "role": {
                  "type": "string",
                  "enum": [
                    "user",
                    "assistant"
                  ]
                },
                "content": {
                  "oneOf": [
                    {
                      "type": "string"
                    },
                    {
                      "$ref": "#/components/schemas/ToolOutputMessageContent"
                    },
                    {
                      "$ref": "#/components/schemas/DocumentMessageContent"
                    }
                  ]
                }
              }
            }
          },
          "stream": {
            "type": "boolean",
            "default": false,
            "x-soat-tool-unsupported": true,
            "description": "When true the response is an SSE stream"
          },
          "trace_id": {
            "x-soat-ref": "traces",
            "type": "string",
            "x-soat-server-managed": true,
            "description": "Optional trace ID to group generations. Each generation appends its own steps to the trace's steps object, and `step_count` covers them all."
          },
          "parent_trace_id": {
            "x-soat-ref": "traces",
            "type": "string",
            "nullable": true,
            "x-soat-server-managed": true,
            "description": "The trace ID of the parent agent generation that triggered this one (for agent-to-agent calls)"
          },
          "root_trace_id": {
            "x-soat-ref": "traces",
            "type": "string",
            "nullable": true,
            "x-soat-server-managed": true,
            "description": "The trace ID of the root generation in the call chain; if omitted, this generation is the root"
          },
          "max_call_depth": {
            "type": "integer",
            "minimum": 0,
            "default": 10,
            "x-soat-server-managed": true,
            "description": "Maximum nested agent-call depth; 0 short-circuits with a depth-guard response"
          },
          "tool_context": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            },
            "nullable": true,
            "description": "Key-value pairs forwarded as `X-Soat-Context-<key>` headers on every `http`, `mcp` and `builtin` tool call in this generation. The header name is the deployment's configured context prefix (`X-Soat-Context-` by default) plus the key verbatim — no character is re-cased. Keys are never case-converted — they round-trip exactly as sent. An invalid or colliding key is rejected with `400 INVALID_TOOL_CONTEXT_KEY`."
          },
          "action_id": {
            "type": "string",
            "description": "Logical action label recorded on the generation's usage meter, so spend can be rolled up per action (e.g. an A/B/C/D operating action)."
          },
          "guardrail_context": {
            "type": "object",
            "additionalProperties": true,
            "nullable": true,
            "description": "Caller-supplied guardrail context (the `context.*` namespace guard and class expressions read at tool-dispatch time). Free-form and never interpreted by the platform; a guardrail may combine it with a `context_tool` per its `context_mode`. See the guardrails module."
          },
          "metadata": {
            "type": "object",
            "additionalProperties": true,
            "nullable": true,
            "description": "Caller-supplied key/value metadata attached to the generation record for per-run audit attribution (e.g. the knowledge-corpus version that produced this action). Round-trips verbatim when the generation is fetched via the generations API. The bag is caller-owned and no key is reserved: server-owned state (usage attribution, the served agent version, the model route's record, the extraction summary) lives in its own top-level generation fields and cannot be written from here. Use the request's own `action_id` field to set the usage-attribution label."
          },
          "extract": {
            "type": "boolean",
            "description": "Per-turn override of the agent's `knowledge_config.extraction` default. Omit to follow the agent's stored config. Set `false` to suppress automatic memory extraction for this turn (e.g. an operational or tool-listing turn that would only add noise to a curated memory). Set `true` to force extraction on for this turn even when the agent does not enable it by default, provided the agent has a `write_memory_id`. Has no effect on streaming or `requires_action` turns, which never extract."
          },
          "knowledge_config": {
            "type": "object",
            "nullable": true,
            "description": "Per-generation knowledge retrieval override. Array filters (memory_ids, memory_tags, document_ids, document_paths) are unioned with the agent's stored knowledge_config; scalar fields (min_score, limit) use the per-generation value when present.",
            "properties": {
              "memory_ids": {
                "x-soat-ref": "memories",
                "type": "array",
                "items": {
                  "type": "string"
                }
              },
              "memory_tags": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              },
              "document_ids": {
                "x-soat-ref": "documents",
                "type": "array",
                "items": {
                  "type": "string"
                }
              },
              "document_paths": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              },
              "min_score": {
                "type": "number"
              },
              "limit": {
                "type": "integer"
              }
            }
          }
        }
      },
      "ToolOutputMessageContent": {
        "type": "object",
        "required": [
          "type",
          "tool_id"
        ],
        "properties": {
          "type": {
            "type": "string",
            "enum": [
              "tool_output"
            ]
          },
          "tool_id": {
            "x-soat-ref": "tools",
            "type": "string",
            "description": "Public ID of the tool to execute before generation."
          },
          "action": {
            "type": "string",
            "nullable": true,
            "description": "Optional action name for tools that require action selection (for example builtin and mcp tools)."
          },
          "input": {
            "type": "object",
            "nullable": true,
            "additionalProperties": true,
            "description": "Input payload passed to the tool call."
          },
          "output_path": {
            "type": "string",
            "nullable": true,
            "description": "Optional dot-notation path used to extract a value from the tool output."
          }
        }
      },
      "DocumentMessageContent": {
        "type": "object",
        "required": [
          "type",
          "document_id"
        ],
        "properties": {
          "type": {
            "type": "string",
            "enum": [
              "document"
            ]
          },
          "document_id": {
            "x-soat-ref": "documents",
            "type": "string",
            "description": "Public ID of a document to use as the message content."
          }
        }
      },
      "SubmitToolOutputsRequest": {
        "type": "object",
        "required": [
          "tool_outputs"
        ],
        "properties": {
          "tool_outputs": {
            "type": "array",
            "minItems": 1,
            "items": {
              "type": "object",
              "required": [
                "tool_call_id",
                "output"
              ],
              "properties": {
                "tool_call_id": {
                  "type": "string",
                  "description": "ID of the tool call to respond to"
                },
                "output": {
                  "description": "Result of the tool execution"
                }
              }
            }
          }
        }
      },
      "AcceptedGenerationResponse": {
        "type": "object",
        "description": "Handle for a generation running in the background. The generation record already exists when this is returned, so the id is immediately pollable via `GET /api/v1/generations/{generation_id}`.\n",
        "required": [
          "status",
          "generation_id",
          "trace_id"
        ],
        "properties": {
          "status": {
            "type": "string",
            "enum": [
              "accepted"
            ],
            "example": "accepted"
          },
          "generation_id": {
            "type": "string",
            "example": "gen_V1StGXR8Z5jdHi6B"
          },
          "trace_id": {
            "type": "string",
            "example": "trace_V1StGXR8Z5jdHi6B"
          }
        }
      },
      "AgentGenerationResponse": {
        "type": "object",
        "description": "Result of an agent generation. Mirrors the server's `GenerationResult`. When `status` is `completed` the model output is under `output`; when it is `requires_action` the pending client tool calls are under `required_action`.\n",
        "required": [
          "id",
          "trace_id",
          "status"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "Public ID of the generation",
            "example": "gen_V1StGXR8Z5jdHi6B"
          },
          "trace_id": {
            "type": "string",
            "description": "Public ID of the trace for this generation",
            "example": "trace_V1StGXR8Z5jdHi6B"
          },
          "status": {
            "type": "string",
            "enum": [
              "completed",
              "requires_action"
            ],
            "description": "Generation status"
          },
          "ai_provider_id": {
            "type": "string",
            "x-soat-ref": "ai-providers",
            "nullable": true,
            "description": "Public ID of the AI provider that served `output.model` — the target a model route picked, or the agent's pinned provider. A model string alone does not identify its provider: two providers in one project can serve byte-identical model names, so this is what makes the value safe to map back to a name a gateway in front of this runtime publishes. Null when the generation resolved no serving provider.\n",
            "example": "aip_V1StGXR8Z5jdHi6B"
          },
          "output": {
            "type": "object",
            "nullable": true,
            "description": "Model output (present when `status` is `completed`).",
            "required": [
              "model",
              "content",
              "finish_reason"
            ],
            "properties": {
              "model": {
                "type": "string",
                "description": "Model that produced the output"
              },
              "content": {
                "type": "string",
                "description": "Final text output"
              },
              "finish_reason": {
                "type": "string",
                "description": "Reason the model stopped generating"
              },
              "response_messages": {
                "type": "array",
                "nullable": true,
                "description": "Full AI SDK response messages (tool calls, tool results, final text)",
                "items": {
                  "type": "object"
                }
              },
              "object": {
                "type": "object",
                "nullable": true,
                "description": "Structured object matching the agent's `output_schema` (when `output_schema` is set)"
              }
            }
          },
          "required_action": {
            "type": "object",
            "nullable": true,
            "description": "Pending action the caller must satisfy (present when `status` is `requires_action`).",
            "required": [
              "type",
              "tool_calls"
            ],
            "properties": {
              "type": {
                "type": "string",
                "enum": [
                  "submit_tool_outputs"
                ],
                "description": "The kind of action required"
              },
              "tool_calls": {
                "type": "array",
                "description": "Pending tool calls to execute and submit outputs for",
                "items": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string",
                      "description": "Tool call ID"
                    },
                    "tool_name": {
                      "type": "string",
                      "description": "Name of the tool to invoke"
                    },
                    "args": {
                      "type": "object",
                      "description": "Arguments for the tool call"
                    }
                  }
                }
              }
            }
          }
        }
      },
      "ProviderPrice": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "Public ID of the price row",
            "example": "price_V1StGXR8Z5jdHi6B"
          },
          "ai_provider_id": {
            "x-soat-ref": "ai-providers",
            "type": "string",
            "description": "The AI provider instance this override prices",
            "example": "aip_V1StGXR8Z5jdHi6B"
          },
          "meter_type": {
            "type": "string",
            "description": "Always `llm_tokens` for provider price overrides"
          },
          "provider": {
            "type": "string",
            "description": "Provider slug (taken from the AI provider instance)",
            "example": "openai"
          },
          "model": {
            "type": "string",
            "example": "gpt-4o"
          },
          "component": {
            "type": "string",
            "description": "The token component this row prices (`input_tokens`, `output_tokens`, `cached_tokens`)"
          },
          "unit": {
            "type": "string",
            "description": "Always `token` for provider price overrides"
          },
          "unit_price": {
            "type": "number",
            "description": "USD per token for this component"
          },
          "effective_from": {
            "type": "string",
            "format": "date-time",
            "description": "The row with the latest effective_from <= now() prices a call"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "ProviderPricesResponse": {
        "type": "object",
        "properties": {
          "prices": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ProviderPrice"
            }
          }
        }
      },
      "UpsertProviderPricesRequest": {
        "type": "object",
        "required": [
          "prices"
        ],
        "properties": {
          "prices": {
            "type": "array",
            "items": {
              "type": "object",
              "required": [
                "model",
                "component",
                "unit",
                "unit_price",
                "effective_from"
              ],
              "properties": {
                "model": {
                  "type": "string",
                  "example": "gpt-4o"
                },
                "component": {
                  "type": "string",
                  "description": "The token component this row prices (`input_tokens`, `output_tokens`, `cached_tokens`)"
                },
                "unit": {
                  "type": "string",
                  "description": "Always `token` for token pricing"
                },
                "unit_price": {
                  "type": "number",
                  "description": "USD per token for this component"
                },
                "effective_from": {
                  "type": "string",
                  "format": "date-time",
                  "description": "Must be in the future; past prices are immutable"
                }
              }
            }
          }
        }
      },
      "ProviderModelsResponse": {
        "type": "object",
        "required": [
          "provider",
          "models"
        ],
        "properties": {
          "provider": {
            "type": "string",
            "description": "The provider slug the listing came from",
            "example": "vertex"
          },
          "models": {
            "type": "array",
            "items": {
              "type": "object",
              "required": [
                "id"
              ],
              "properties": {
                "id": {
                  "type": "string",
                  "description": "The provider-native model id, ready to use as `default_model` or an agent's `model`\n",
                  "example": "gemini-2.5-flash"
                },
                "display_name": {
                  "type": "string",
                  "description": "The provider's own human-readable name, when it reports one",
                  "example": "Gemini 2.5 Flash"
                },
                "vendor": {
                  "type": "string",
                  "description": "Who makes the model, when the provider reports it",
                  "example": "google"
                },
                "input_modalities": {
                  "type": "array",
                  "description": "Lowercased input modalities, when the provider reports them",
                  "items": {
                    "type": "string"
                  },
                  "example": [
                    "text",
                    "image"
                  ]
                },
                "output_modalities": {
                  "type": "array",
                  "description": "Lowercased output modalities, when the provider reports them",
                  "items": {
                    "type": "string"
                  },
                  "example": [
                    "text"
                  ]
                },
                "streaming": {
                  "type": "boolean",
                  "description": "Whether the model supports streaming responses"
                },
                "lifecycle": {
                  "type": "string",
                  "description": "`active`, `legacy` or `deprecated`, as the provider reports it. A model that is not `active` still serves today but should not be pinned by anything new.\n",
                  "enum": [
                    "active",
                    "legacy",
                    "deprecated"
                  ]
                },
                "inference_types": {
                  "type": "array",
                  "description": "Lowercased inference types the model supports, when reported. A Bedrock model offering only `inference_profile` must be invoked through a cross-region profile id rather than the bare model id.\n",
                  "items": {
                    "type": "string"
                  },
                  "example": [
                    "on_demand",
                    "inference_profile"
                  ]
                }
              }
            }
          }
        }
      },
      "ApiKeyRecord": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "Public API key ID (key_ prefix)",
            "example": "key_V1StGXR8Z5jdHi6B"
          },
          "name": {
            "type": "string",
            "example": "CI/CD Pipeline"
          },
          "key_prefix": {
            "type": "string",
            "description": "First 8 characters of the raw key for identification",
            "example": "sk_a1b2c3"
          },
          "user_id": {
            "x-soat-ref": "users",
            "type": "string",
            "description": "Owner user public ID",
            "example": "user_V1StGXR8Z5jdHi6B"
          },
          "project_id": {
            "x-soat-ref": "projects",
            "type": "string",
            "nullable": true,
            "description": "The project this key is scoped to, or null for an unscoped key that spans projects",
            "example": "proj_V1StGXR8Z5jdHi6B"
          },
          "policy_ids": {
            "x-soat-ref": "policies",
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Public IDs of policies attached to this key",
            "example": [
              "pol_V1StGXR8Z5jdHi6B"
            ]
          },
          "created_at": {
            "type": "string",
            "format": "date-time",
            "example": "2024-01-01T00:00:00.000Z"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time",
            "example": "2024-01-01T00:00:00.000Z"
          }
        }
      },
      "ApiKeyCreated": {
        "allOf": [
          {
            "$ref": "#/components/schemas/ApiKeyRecord"
          },
          {
            "type": "object",
            "properties": {
              "key": {
                "type": "string",
                "description": "The raw API key value (only returned once at creation). Use as Bearer token.",
                "example": "sk_a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4"
              }
            }
          }
        ]
      },
      "ApprovalRecurrenceGroup": {
        "type": "object",
        "description": "A set of approval items sharing a `dedup_key` — the same proposed action recurring — with the ordered chain and its resolution reasons.",
        "properties": {
          "dedup_key": {
            "type": "string",
            "description": "The shared dedup key that defines the group"
          },
          "agent_id": {
            "type": "string",
            "nullable": true,
            "description": "Proposing agent (shared across the group)"
          },
          "tool_id": {
            "type": "string",
            "nullable": true,
            "description": "Proposed tool (shared across the group)"
          },
          "count": {
            "type": "integer",
            "description": "Number of items in the group"
          },
          "chain": {
            "type": "array",
            "description": "The items oldest → newest (the `previous_item_id` chain)",
            "items": {
              "type": "object",
              "properties": {
                "id": {
                  "type": "string",
                  "example": "apr_V1StGXR8Z5jdHi6B"
                },
                "status": {
                  "type": "string",
                  "enum": [
                    "pending",
                    "approved",
                    "rejected",
                    "expired"
                  ]
                },
                "resolution_reason": {
                  "type": "string",
                  "nullable": true
                },
                "created_at": {
                  "type": "string",
                  "format": "date-time"
                }
              }
            }
          },
          "reasons": {
            "type": "array",
            "description": "The chain's resolution reasons in order (empty entries omitted)",
            "items": {
              "type": "string"
            }
          }
        }
      },
      "ApprovalItem": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "example": "apr_V1StGXR8Z5jdHi6B"
          },
          "project_id": {
            "x-soat-ref": "projects",
            "type": "string"
          },
          "origin": {
            "type": "string",
            "enum": [
              "node",
              "tool_call",
              "task_transition"
            ],
            "description": "How the item was produced (analytics/filtering only)"
          },
          "status": {
            "type": "string",
            "enum": [
              "pending",
              "approved",
              "rejected",
              "expired"
            ]
          },
          "proposed_action": {
            "type": "object",
            "nullable": true,
            "description": "The frozen proposed action. Null for producers whose proposal is not a tool call (a `task_transition` item gates a workflow transition, named by `task_transition`).",
            "properties": {
              "tool_id": {
                "type": "string"
              },
              "action": {
                "type": "string",
                "description": "Resolved action name (the builtin/mcp action) for `tool_call`-origin items — always present there, even for single-action tools. Omitted for `node`-origin items, whose downstream execution is wired by a separate `tool` node in the graph."
              },
              "arguments": {
                "type": "object"
              }
            }
          },
          "reasoning": {
            "type": "string",
            "nullable": true,
            "description": "The proposing agent's rationale"
          },
          "evidence": {
            "type": "object",
            "nullable": true,
            "description": "Supporting structured data"
          },
          "predicted_impact": {
            "type": "string",
            "nullable": true,
            "description": "Expected execution effect"
          },
          "expires_at": {
            "type": "string",
            "format": "date-time",
            "description": "Server-enforced hard gate; the item can never execute after this"
          },
          "dedup_key": {
            "type": "string",
            "nullable": true,
            "description": "Set on tool-call items to suppress duplicate proposals"
          },
          "orchestration_run_id": {
            "type": "string",
            "nullable": true,
            "description": "Originating orchestration run (node producer)"
          },
          "node_id": {
            "type": "string",
            "nullable": true,
            "description": "Originating node id within the run's graph"
          },
          "generation_id": {
            "type": "string",
            "nullable": true,
            "description": "Originating generation (tool-call producer)"
          },
          "session_id": {
            "type": "string",
            "nullable": true,
            "description": "Session the originating generation ran in (tool-call producer)"
          },
          "agent_id": {
            "type": "string",
            "nullable": true,
            "description": "Proposing agent"
          },
          "task_id": {
            "type": "string",
            "nullable": true,
            "description": "Gated task (task_transition producer)"
          },
          "task_transition": {
            "type": "string",
            "nullable": true,
            "description": "Transition fired on approval (task_transition producer)"
          },
          "policy_version": {
            "type": "string",
            "nullable": true
          },
          "previous_item_id": {
            "type": "string",
            "nullable": true,
            "description": "Prior item's ID when this proposal was re-filed after an earlier matching item (same dedup_key) had been rejected",
            "example": "apr_V1StGXR8Z5jdHi6B"
          },
          "resolved_by": {
            "type": "string",
            "nullable": true,
            "description": "Resolving user's public ID; null on expiry"
          },
          "resolution_reason": {
            "type": "string",
            "nullable": true,
            "description": "Required on rejection"
          },
          "edited_arguments": {
            "type": "object",
            "nullable": true,
            "description": "Set on edit-then-approve"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "AuditEntry": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "example": "audit_V1StGXR8Z5jdHi6B"
          },
          "project_id": {
            "x-soat-ref": "projects",
            "type": "string",
            "nullable": true,
            "description": "Project the action targeted; null for global actions"
          },
          "principal_type": {
            "type": "string",
            "nullable": true,
            "enum": [
              "user",
              "api_key",
              null
            ],
            "description": "Principal kind; null for platform-originated entries (those are identified by their `action`, e.g. `quotas:MonitorBreach`)"
          },
          "principal_id": {
            "type": "string",
            "nullable": true,
            "description": "Public id of the principal (`user_…` or `key_…`); null for platform-originated entries"
          },
          "action": {
            "type": "string",
            "description": "The permission-action string that authorized the request",
            "example": "secrets:DeleteSecret"
          },
          "resource_srn": {
            "type": "string",
            "nullable": true,
            "description": "SRN the action targeted (type-level `srn:{project}:{type}:*` on creates)",
            "example": "srn:proj_V1StGXR8Z5jdHi6B:secret:sec_V1StGXR8Z5jdHi6B"
          },
          "resource_public_id": {
            "type": "string",
            "nullable": true,
            "description": "Target resource public id (from the SRN, or the response body on creates)"
          },
          "status": {
            "type": "integer",
            "description": "HTTP status of the response",
            "example": 200
          },
          "request_id": {
            "type": "string",
            "nullable": true,
            "description": "Per-request correlation id (also returned in the X-Request-Id header)"
          },
          "ip": {
            "type": "string",
            "nullable": true
          },
          "user_agent": {
            "type": "string",
            "nullable": true
          },
          "detail": {
            "type": "object",
            "nullable": true,
            "description": "Kind-specific payload. Multi-check routes record the remaining checks under `additional_checks`. Platform-originated entries set a `detail.kind` discriminator, e.g. `quota_monitor_breach` or `guardrail_evaluation`.",
            "additionalProperties": true
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "Chain": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "example": "chain_V1StGXR8Z5jdHi6B"
          },
          "project_id": {
            "x-soat-ref": "projects",
            "type": "string"
          },
          "agent_id": {
            "x-soat-ref": "agents",
            "type": "string",
            "nullable": true,
            "description": "The agent whose continuation opened the chain. A chain can span agents, so this names its origin rather than an owner. Held as a plain id, not a reference the platform maintains — deleting the agent leaves the chain record intact."
          },
          "status": {
            "type": "string",
            "enum": [
              "active",
              "concluded",
              "expired",
              "budget_exhausted"
            ],
            "description": "`active` — hops are still being spawned. `concluded` — a member finished with nothing left pending; not terminal, since a decision months later can spawn another hop and put the chain back to `active`. `expired` — a held approval lapsed and the agent does not react to expiry, so nothing resumed it. `budget_exhausted` — a hop was refused by the chain budget."
          },
          "generation_count": {
            "type": "integer",
            "description": "Generations in the chain, the root included — the same population `GET /api/v1/generations?chain_id=<id>` returns. Re-derived on every hop, so it is a description of the chain, never the thing the budget is enforced against."
          },
          "last_generation_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true,
            "description": "When the chain last gained a generation"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "Chat": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "Public ID of the chat",
            "example": "chat_V1StGXR8Z5jdHi6B"
          },
          "project_id": {
            "x-soat-ref": "projects",
            "type": "string",
            "description": "Public ID of the owning project",
            "example": "proj_V1StGXR8Z5jdHi6B"
          },
          "ai_provider_id": {
            "x-soat-ref": "ai-providers",
            "type": "string",
            "nullable": true,
            "description": "Public ID of the pinned AI provider, or `null` when the chat pins none and inherits its project's `default_model_route_id`.",
            "example": "aip_V1StGXR8Z5jdHi6B"
          },
          "name": {
            "type": "string",
            "nullable": true,
            "description": "Optional human-readable name",
            "example": "Support Bot"
          },
          "instructions": {
            "type": "string",
            "nullable": true,
            "description": "Optional system message sent with every completion",
            "example": "You are a helpful support assistant."
          },
          "model": {
            "type": "string",
            "nullable": true,
            "description": "Optional model override for this chat",
            "example": "gpt-4o"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "CreateChatRequest": {
        "type": "object",
        "properties": {
          "ai_provider_id": {
            "x-soat-ref": "ai-providers",
            "type": "string",
            "description": "Public ID of the AI provider to pin. Optional: omit it to inherit the project's `default_model_route_id`, which gives the chat provider failover. Omitting it returns `400` when the project has no default, and cannot be combined with `model` — each route target names its own.",
            "example": "aip_V1StGXR8Z5jdHi6B"
          },
          "project_id": {
            "x-soat-ref": "projects",
            "type": "string",
            "description": "Public ID of the project. Required when the user belongs to multiple projects and no project key is used.\n",
            "example": "proj_V1StGXR8Z5jdHi6B"
          },
          "name": {
            "type": "string",
            "description": "Optional human-readable name",
            "example": "Support Bot"
          },
          "instructions": {
            "type": "string",
            "description": "Optional system message applied to all completions on this chat",
            "example": "You are a helpful support assistant."
          },
          "model": {
            "type": "string",
            "description": "Optional default model override",
            "example": "gpt-4o"
          }
        }
      },
      "ChatMessageInput": {
        "type": "object",
        "required": [
          "role"
        ],
        "properties": {
          "role": {
            "type": "string",
            "enum": [
              "user",
              "assistant"
            ],
            "example": "user"
          },
          "content": {
            "type": "string",
            "description": "Text content of the message (mutually exclusive with documentId)",
            "example": "What can you help me with?"
          },
          "document_id": {
            "x-soat-ref": "documents",
            "type": "string",
            "description": "Public ID of a document whose content is used as the message body (mutually exclusive with content). Only valid for user/assistant roles.\n",
            "example": "doc_V1StGXR8Z5jdHi6B"
          }
        }
      },
      "ChatCompletionRequest": {
        "type": "object",
        "required": [
          "messages"
        ],
        "properties": {
          "ai_provider_id": {
            "x-soat-ref": "ai-providers",
            "type": "string",
            "description": "Public ID of the AI provider to run the completion against. Mutually exclusive with `chat_id`; exactly one of the two is required.\n",
            "example": "aip_V1StGXR8Z5jdHi6B"
          },
          "chat_id": {
            "x-soat-ref": "chats",
            "type": "string",
            "description": "Public ID of a stored chat supplying the provider, model and instructions. Mutually exclusive with `ai_provider_id`; exactly one of the two is required.\n",
            "example": "chat_V1StGXR8Z5jdHi6B"
          },
          "model": {
            "type": "string",
            "description": "Model identifier. Overrides the provider's `default_model`, or the chat's `model`, when specified.\n",
            "example": "gpt-4o"
          },
          "instructions": {
            "type": "string",
            "description": "System instructions for this call. Sent to the provider as its `instructions` argument rather than as a message, which is the only place the AI SDK accepts system content (`allowSystemInMessages` defaults to false). This field is the only channel — a `role: \"system\"` entry in `messages` is refused with `400 SYSTEM_MESSAGE_NOT_ALLOWED`. With `chat_id`, this replaces the chat's stored `instructions` for this call only; the stored value applies when the request carries none, and the two are never merged.\n",
            "example": "You are a helpful assistant."
          },
          "messages": {
            "type": "array",
            "minItems": 1,
            "description": "Ordered list of chat messages",
            "items": {
              "$ref": "#/components/schemas/ChatMessageInput"
            }
          },
          "stream": {
            "type": "boolean",
            "default": false,
            "description": "When `true` the response is an SSE stream of delta chunks. When `false` (default) a single JSON object is returned.\n"
          }
        }
      },
      "ChatCompletionResponseMessage": {
        "type": "object",
        "properties": {
          "role": {
            "type": "string",
            "example": "assistant"
          },
          "content": {
            "type": "string",
            "example": "Hello! I am doing well, thank you."
          }
        }
      },
      "ChatCompletionChoice": {
        "type": "object",
        "properties": {
          "index": {
            "type": "integer",
            "example": 0
          },
          "message": {
            "$ref": "#/components/schemas/ChatCompletionResponseMessage"
          },
          "finish_reason": {
            "type": "string",
            "example": "stop"
          }
        }
      },
      "ChatCompletionResponse": {
        "type": "object",
        "properties": {
          "object": {
            "type": "string",
            "example": "chat.completion"
          },
          "model": {
            "type": "string",
            "example": "gpt-4o"
          },
          "choices": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ChatCompletionChoice"
            }
          }
        }
      },
      "ConversationRecord": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "Conversation ID",
            "example": "conv_V1StGXR8Z5jdHi6B"
          },
          "project_id": {
            "x-soat-ref": "projects",
            "type": "string",
            "description": "Project ID",
            "example": "proj_V1StGXR8Z5jdHi6B"
          },
          "name": {
            "type": "string",
            "nullable": true,
            "description": "Optional human-readable name for the conversation."
          },
          "status": {
            "type": "string",
            "enum": [
              "open",
              "closed"
            ],
            "description": "Conversation status",
            "example": "open"
          },
          "created_at": {
            "type": "string",
            "format": "date-time",
            "description": "Creation timestamp"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time",
            "description": "Last update timestamp"
          },
          "actor_id": {
            "x-soat-ref": "actors",
            "type": "string",
            "nullable": true,
            "description": "Actor ID associated with this conversation",
            "example": "actor_V1StGXR8Z5jdHi6B"
          }
        }
      },
      "ConversationMessageRecord": {
        "type": "object",
        "properties": {
          "document_id": {
            "x-soat-ref": "documents",
            "type": "string",
            "description": "Document ID",
            "example": "doc_V1StGXR8Z5jdHi6B"
          },
          "role": {
            "type": "string",
            "enum": [
              "user",
              "assistant",
              "system"
            ],
            "description": "Role of the message sender",
            "example": "user"
          },
          "actor_id": {
            "x-soat-ref": "actors",
            "type": "string",
            "nullable": true,
            "description": "Optional actor ID associated with this message",
            "example": "actor_V1StGXR8Z5jdHi6B"
          },
          "agent_id": {
            "x-soat-ref": "agents",
            "type": "string",
            "nullable": true,
            "description": "Optional agent ID that generated this message (set for assistant messages produced by generate)",
            "example": "agent_V1StGXR8Z5jdHi6B"
          },
          "position": {
            "type": "integer",
            "description": "Zero-based position in the conversation",
            "example": 0
          },
          "metadata": {
            "type": "object",
            "description": "Optional structured metadata attached to the message",
            "nullable": true,
            "additionalProperties": true,
            "example": {
              "phone": "5511999998888",
              "channel": "whatsapp"
            }
          },
          "content": {
            "type": "string",
            "nullable": true,
            "description": "Full text content of the message"
          }
        }
      },
      "GenerateConversationMessageCompleted": {
        "type": "object",
        "required": [
          "status",
          "content",
          "message",
          "generation_id",
          "trace_id"
        ],
        "properties": {
          "status": {
            "type": "string",
            "enum": [
              "completed"
            ],
            "description": "Indicates generation finished successfully."
          },
          "content": {
            "type": "string",
            "description": "The AI-generated text of the reply. This is the canonical field for the assistant's response text.\n",
            "example": "Hello! How can I help you today?"
          },
          "message": {
            "$ref": "#/components/schemas/ConversationMessageRecord"
          },
          "generation_id": {
            "x-soat-ref": "generations",
            "type": "string",
            "description": "ID of the underlying generation record.",
            "example": "gen_V1StGXR8Z5jdHi6B"
          },
          "trace_id": {
            "x-soat-ref": "traces",
            "type": "string",
            "description": "Trace ID for observability.",
            "example": "trace_V1StGXR8Z5jdHi6B"
          },
          "model": {
            "type": "string",
            "description": "Model used for generation.",
            "example": "gpt-4o"
          }
        }
      },
      "GenerateConversationMessageRequiresAction": {
        "type": "object",
        "required": [
          "status",
          "generation_id",
          "trace_id",
          "required_action"
        ],
        "properties": {
          "status": {
            "type": "string",
            "enum": [
              "requires_action"
            ],
            "description": "Indicates the agent requires tool-call outputs before it can produce a reply. No message is persisted yet.\n"
          },
          "generation_id": {
            "x-soat-ref": "generations",
            "type": "string",
            "description": "ID of the paused generation. Pass to the tool-outputs endpoint.",
            "example": "gen_V1StGXR8Z5jdHi6B"
          },
          "trace_id": {
            "x-soat-ref": "traces",
            "type": "string",
            "description": "Trace ID for observability.",
            "example": "trace_V1StGXR8Z5jdHi6B"
          },
          "required_action": {
            "type": "object",
            "description": "Tool-call information the client must resolve."
          }
        }
      },
      "GenerateConversationMessageResponse": {
        "oneOf": [
          {
            "$ref": "#/components/schemas/GenerateConversationMessageCompleted"
          },
          {
            "$ref": "#/components/schemas/GenerateConversationMessageRequiresAction"
          }
        ],
        "discriminator": {
          "propertyName": "status",
          "mapping": {
            "completed": "#/components/schemas/GenerateConversationMessageCompleted",
            "requires_action": "#/components/schemas/GenerateConversationMessageRequiresAction"
          }
        }
      },
      "DocumentRecord": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "Document ID",
            "example": "doc_V1StGXR8Z5jdHi6B"
          },
          "file_id": {
            "x-soat-ref": "files",
            "type": "string",
            "description": "Underlying file ID",
            "example": "file_V1StGXR8Z5jdHi6B"
          },
          "project_id": {
            "x-soat-ref": "projects",
            "type": "string",
            "description": "Project ID",
            "example": "proj_V1StGXR8Z5jdHi6B"
          },
          "path": {
            "type": "string",
            "nullable": true,
            "description": "Logical path of the document within the project (e.g. /reports/q1.txt)",
            "example": "/reports/q1.txt"
          },
          "filename": {
            "type": "string",
            "description": "Original filename",
            "example": "my-doc.txt"
          },
          "content_type": {
            "type": "string",
            "description": "Media type of the source file the document was ingested from. Absent when the underlying file is gone.",
            "example": "application/pdf"
          },
          "size": {
            "type": "integer",
            "description": "File size in bytes",
            "example": 42
          },
          "status": {
            "type": "string",
            "enum": [
              "pending",
              "processing",
              "ready",
              "failed"
            ],
            "description": "Ingestion lifecycle state. `pending` — enqueued; `processing` — chunks being extracted and embedded; `ready` — fully indexed; `failed` — processing error (see the `error` field on `GET /documents/{id}/status`).",
            "example": "ready"
          },
          "content": {
            "type": "string",
            "nullable": true,
            "description": "Text content (only present on getDocument, and only when status is ready)",
            "example": "The quick brown fox jumps over the lazy dog."
          },
          "chunk_strategy": {
            "type": "string",
            "enum": [
              "page",
              "whole",
              "size"
            ],
            "description": "The chunk strategy the document was last (re-)ingested with. Absent when the default (`whole`) was used — the mapper omits the key rather than sending `null`.",
            "example": "size"
          },
          "chunk_size": {
            "type": "integer",
            "description": "Window size in characters used when `chunk_strategy=size`. Absent otherwise.",
            "example": 800
          },
          "chunk_overlap": {
            "type": "integer",
            "description": "Overlap in characters between consecutive windows used when `chunk_strategy=size`. Absent otherwise.",
            "example": 120
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "IngestedDocumentRecord": {
        "allOf": [
          {
            "$ref": "#/components/schemas/DocumentRecord"
          },
          {
            "type": "object",
            "properties": {
              "chunk_count": {
                "type": "integer",
                "description": "Number of chunks created from the file.",
                "example": 10
              }
            }
          }
        ]
      },
      "DocumentStatusRecord": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "Document ID",
            "example": "doc_V1StGXR8Z5jdHi6B"
          },
          "status": {
            "type": "string",
            "enum": [
              "pending",
              "processing",
              "ready",
              "failed"
            ],
            "description": "Ingestion lifecycle state.",
            "example": "ready"
          },
          "chunk_count": {
            "type": "integer",
            "description": "Number of chunks **currently indexed** for this document (a live count). Grows while `status=processing` and equals the final total once `ready`; `0` while `pending`.",
            "example": 10
          },
          "total_chunks": {
            "type": "integer",
            "nullable": true,
            "description": "Planned total number of chunks, known once chunking begins. `null` until then. Used as the denominator for `progress`.",
            "example": 12
          },
          "total_pages": {
            "type": "integer",
            "nullable": true,
            "description": "Number of source pages extracted. Only known after extraction, so it is `null` until `status` is `ready` or `failed` (not the same as zero pages).",
            "example": 12
          },
          "progress": {
            "type": "integer",
            "nullable": true,
            "description": "Ingestion progress as a percentage (`chunk_count / total_chunks`). `0` while `pending`, climbs while `processing` (capped at 99), `100` when `ready`, and `null` when `failed` or not yet computable.",
            "example": 100
          },
          "error": {
            "type": "string",
            "nullable": true,
            "description": "Failure reason when `status` is `failed` (e.g. `FILE_PARSE_FAILED`, `INGESTION_TIMEOUT`).",
            "example": "INGESTION_TIMEOUT"
          }
        }
      },
      "EmbeddingsResponse": {
        "type": "object",
        "description": "Response containing generated embeddings. Fields present depend on whether `input` or `inputs` was provided.",
        "properties": {
          "embedding": {
            "type": "array",
            "description": "Embedding vector for the single `input` text.",
            "items": {
              "type": "number"
            },
            "example": [
              0.123,
              -0.456,
              0.789
            ]
          },
          "embeddings": {
            "type": "array",
            "description": "Embedding vectors for each item in the `inputs` batch.",
            "items": {
              "type": "array",
              "items": {
                "type": "number"
              }
            },
            "example": [
              [
                0.123,
                -0.456,
                0.789
              ],
              [
                0.321,
                -0.654,
                0.987
              ]
            ]
          }
        }
      },
      "DatasetItemInput": {
        "type": "array",
        "description": "Messages replayed verbatim as the generation's input",
        "items": {
          "type": "object",
          "required": [
            "role",
            "content"
          ],
          "properties": {
            "role": {
              "type": "string",
              "example": "user"
            },
            "content": {
              "description": "Message content — a string, or AI SDK content parts",
              "example": "When is my invoice issued?"
            }
          }
        }
      },
      "Scorers": {
        "type": "array",
        "description": "Scorer configs, a discriminated union on `type`. Each type may appear at most once. Every scorer produces `{ score: 0–1, passed: boolean }`; binary scorers emit 0 or 1.\n\n`exact_match` compares the trimmed output text to `expected_output`. `contains` looks for `value` in the output text. `json_logic` evaluates `expression` over `{ input, output, object, expected, item.metadata }`, where `object` is the structured output (absent when the agent has no `output_schema`). `output_schema` validates the structured output against the scorer's own `schema`, falling back to the agent's; it requires the agent to carry an `output_schema`, because without one the platform emits no structured output and every item would score 0.\n\n`llm_judge` grades the output with a model completion, returning a continuous score plus its `reasoning`. Its `pass_threshold` is required: a continuous score says nothing about where \"good enough\" is, and a defaulted cutoff would silently decide the gate.\n\n`embedding_similarity` embeds the output text and `expected_output` with the platform's configured embedding model (`EMBEDDING_PROVIDER` / `EMBEDDING_MODEL` — the same stack document ingestion uses) and scores their cosine similarity, clamped to 0-1. Its `pass_threshold` is required for the same reason as the judge's. An item without an `expected_output` scores 0; an embedding backend failure marks the **item** errored, never a score of 0.\n\n`tool` runs a custom scoring algorithm: a server-callable project tool the engine invokes once per item with the item's context. Unlike the built-in types it may appear several times, each under a distinct `name` — outcomes and aggregates key on the name.",
        "items": {
          "oneOf": [
            {
              "$ref": "#/components/schemas/ExactMatchScorer"
            },
            {
              "$ref": "#/components/schemas/ContainsScorer"
            },
            {
              "$ref": "#/components/schemas/JsonLogicScorer"
            },
            {
              "$ref": "#/components/schemas/OutputSchemaScorer"
            },
            {
              "$ref": "#/components/schemas/EmbeddingSimilarityScorer"
            },
            {
              "$ref": "#/components/schemas/LlmJudgeScorer"
            },
            {
              "$ref": "#/components/schemas/ToolScorer"
            }
          ]
        }
      },
      "ExactMatchScorer": {
        "type": "object",
        "required": [
          "type"
        ],
        "properties": {
          "type": {
            "type": "string",
            "enum": [
              "exact_match"
            ]
          }
        }
      },
      "ContainsScorer": {
        "type": "object",
        "required": [
          "type",
          "value"
        ],
        "properties": {
          "type": {
            "type": "string",
            "enum": [
              "contains"
            ]
          },
          "value": {
            "type": "string",
            "example": "invoice"
          },
          "case_sensitive": {
            "type": "boolean",
            "default": false
          }
        }
      },
      "JsonLogicScorer": {
        "type": "object",
        "required": [
          "type",
          "expression"
        ],
        "properties": {
          "type": {
            "type": "string",
            "enum": [
              "json_logic"
            ]
          },
          "expression": {
            "type": "object",
            "additionalProperties": true,
            "description": "A JSON Logic expression; a truthy result scores 1"
          }
        }
      },
      "OutputSchemaScorer": {
        "type": "object",
        "required": [
          "type"
        ],
        "properties": {
          "type": {
            "type": "string",
            "enum": [
              "output_schema"
            ]
          },
          "schema": {
            "type": "object",
            "additionalProperties": true,
            "description": "JSON Schema the structured output is validated against. Frozen here so two runs stay comparable; falls back to the agent's `output_schema` when omitted."
          }
        }
      },
      "EmbeddingSimilarityScorer": {
        "type": "object",
        "required": [
          "type",
          "pass_threshold"
        ],
        "properties": {
          "type": {
            "type": "string",
            "enum": [
              "embedding_similarity"
            ]
          },
          "pass_threshold": {
            "type": "number",
            "minimum": 0,
            "maximum": 1,
            "description": "The item passes this scorer when the cosine similarity between the embeddings of the output text and `expected_output` is greater than or equal to this value. Required.",
            "example": 0.85
          }
        }
      },
      "LlmJudgeScorer": {
        "type": "object",
        "required": [
          "type",
          "prompt",
          "pass_threshold"
        ],
        "properties": {
          "type": {
            "type": "string",
            "enum": [
              "llm_judge"
            ]
          },
          "prompt": {
            "type": "string",
            "description": "The judge prompt. `{{input}}`, `{{output}}` and `{{expected}}` are replaced with the item's input messages, the agent's output text, and the item's `expected_output`. Slots are filled in one pass, so a slot value that itself contains `{{output}}` is not re-expanded. The judge must answer with a JSON object carrying a numeric `score` between 0 and 1 and an optional `reasoning` string; a reply that does not marks the **item** errored, never the run failed and never a score of 0.",
            "example": "Rate 0-1 how well the answer matches the reference. Answer with {\"score\": <0-1>, \"reasoning\": \"<why>\"}. Question: {{input}} Answer: {{output}} Reference: {{expected}}"
          },
          "pass_threshold": {
            "type": "number",
            "minimum": 0,
            "maximum": 1,
            "description": "The item passes this scorer when the judge's score is greater than or equal to this value. Required.",
            "example": 0.7
          },
          "ai_provider_id": {
            "type": "string",
            "nullable": true,
            "description": "The AI provider that runs the judge; it must belong to the eval's project. Omit to use the project's default model route.",
            "example": "aip_V1StGXR8Z5jdHi6B"
          },
          "model": {
            "type": "string",
            "nullable": true,
            "description": "Overrides the provider's default model. Pinned per scorer, because deltas between runs judged by different models are not comparable.",
            "example": "gpt-4o-mini"
          }
        }
      },
      "ToolScorer": {
        "type": "object",
        "description": "A custom scoring algorithm — a server-callable project tool the engine invokes once per item. The tool receives the same variables a `json_logic` expression reads — `input`, `output`, `object` (when the agent has an `output_schema`), `expected`, and `item.metadata` — with `preset_parameters` merged in at the top level, and must answer with a JSON object carrying a numeric `score` between 0 and 1, an optional boolean `passed`, and an optional `reasoning` string. A malformed answer or a failed call marks the **item** errored, never the run failed and never a score of 0.",
        "required": [
          "type",
          "name",
          "tool_id"
        ],
        "properties": {
          "type": {
            "type": "string",
            "enum": [
              "tool"
            ]
          },
          "name": {
            "type": "string",
            "description": "Keys this scorer's outcomes and aggregate scores, so it must be unique within the eval and must not shadow a built-in scorer type. Unlike the built-in types, several `tool` scorers may coexist under distinct names.",
            "example": "toxicity"
          },
          "tool_id": {
            "x-soat-ref": "tools",
            "type": "string",
            "description": "The tool that scores each item. It must belong to the eval's project and be server-callable (`http`, `mcp`, `builtin`, or `pipeline` — a `client` tool pauses for a calling client an eval run does not have).",
            "example": "tool_V1StGXR8Z5jdHi6B"
          },
          "action": {
            "type": "string",
            "nullable": true,
            "description": "The operation to invoke; required when the tool type is `builtin` or `mcp`.",
            "example": "score-toxicity"
          },
          "preset_parameters": {
            "type": "object",
            "nullable": true,
            "additionalProperties": true,
            "description": "Fixed values merged into every call's input at the top level. The engine-injected keys (`input`, `output`, `object`, `expected`, `item`) are reserved and rejected."
          },
          "pass_threshold": {
            "type": "number",
            "nullable": true,
            "minimum": 0,
            "maximum": 1,
            "description": "Fallback verdict cutoff when the tool answers without a `passed` flag: the item passes this scorer when `score` is greater than or equal to this value. A tool-returned `passed` always wins. When the tool omits `passed` and no threshold is set, the item is recorded as errored — the scorer produced no verdict.",
            "example": 0.5
          }
        }
      },
      "ScorerResult": {
        "type": "object",
        "properties": {
          "scorer": {
            "type": "string",
            "description": "The scorer that produced this entry — the scorer type, or for a `tool` scorer its `name`",
            "example": "contains"
          },
          "score": {
            "type": "number",
            "example": 1
          },
          "passed": {
            "type": "boolean"
          },
          "reasoning": {
            "type": "string",
            "description": "The stated rationale; present for `llm_judge` and for `tool` scorers whose tool returned one"
          }
        }
      },
      "Dataset": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "example": "dset_V1StGXR8Z5jdHi6B"
          },
          "project_id": {
            "x-soat-ref": "projects",
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "description": {
            "type": "string",
            "nullable": true
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "DatasetItem": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "example": "dsit_V1StGXR8Z5jdHi6B"
          },
          "dataset_id": {
            "type": "string",
            "example": "dset_V1StGXR8Z5jdHi6B"
          },
          "input": {
            "$ref": "#/components/schemas/DatasetItemInput"
          },
          "expected_output": {
            "type": "string",
            "nullable": true
          },
          "metadata": {
            "type": "object",
            "nullable": true,
            "additionalProperties": true
          },
          "source_generation_id": {
            "type": "string",
            "nullable": true,
            "description": "The generation this item was curated from. A curated item is a deliberate fixture: erasing the source generation neither deletes nor mutates it.",
            "example": "gen_V1StGXR8Z5jdHi6B"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "Eval": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "example": "eval_V1StGXR8Z5jdHi6B"
          },
          "project_id": {
            "x-soat-ref": "projects",
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "agent_id": {
            "x-soat-ref": "agents",
            "type": "string",
            "example": "agent_V1StGXR8Z5jdHi6B"
          },
          "dataset_id": {
            "type": "string",
            "example": "dset_V1StGXR8Z5jdHi6B"
          },
          "scorers": {
            "$ref": "#/components/schemas/Scorers"
          },
          "pass_threshold": {
            "type": "number",
            "nullable": true
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "AggregateScores": {
        "type": "object",
        "nullable": true,
        "description": "Per-scorer rollup plus the run-level pass rate; null until the run is terminal",
        "properties": {
          "scorers": {
            "type": "object",
            "additionalProperties": {
              "type": "object",
              "properties": {
                "mean": {
                  "type": "number"
                },
                "pass_rate": {
                  "type": "number"
                }
              }
            }
          },
          "pass_rate": {
            "type": "number",
            "nullable": true,
            "description": "Passed items over non-errored items; null when nothing was scorable"
          },
          "scored_item_count": {
            "type": "integer",
            "description": "Items that produced a score — errored items are excluded"
          },
          "baseline": {
            "$ref": "#/components/schemas/BaselineComparison"
          }
        }
      },
      "BaselineComparison": {
        "type": "object",
        "nullable": true,
        "description": "Comparison against the run named by `baseline_run_id`; absent when the run named none.\n\nEvery number here is computed over the **item intersection** — the dataset items present and scorable in both runs — because a delta only means something when both sides answered the same question. The compared/added/removed counts make any dataset drift visible instead of letting it read as agent regression. Positive deltas mean this run scored higher than the baseline.",
        "properties": {
          "run_id": {
            "type": "string",
            "example": "evrun_V1StGXR8Z5jdHi6B"
          },
          "compared_item_count": {
            "type": "integer",
            "description": "Items scorable in both runs — the basis of every delta"
          },
          "added_item_count": {
            "type": "integer",
            "description": "Scorable here but not in the baseline (added, or errored there)"
          },
          "removed_item_count": {
            "type": "integer",
            "description": "Scorable in the baseline but not here (removed, or errored here)"
          },
          "pass_rate_delta": {
            "type": "number",
            "nullable": true,
            "description": "Run-level pass-rate delta over the intersection; null when the two runs share no comparable item"
          },
          "scorers": {
            "type": "object",
            "description": "Per-scorer deltas, keyed by scorer type. A scorer only one of the two runs ran is omitted rather than compared against nothing.",
            "additionalProperties": {
              "type": "object",
              "properties": {
                "mean_delta": {
                  "type": "number"
                },
                "pass_rate_delta": {
                  "type": "number"
                }
              }
            }
          }
        }
      },
      "EvalRun": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "example": "evrun_V1StGXR8Z5jdHi6B"
          },
          "eval_id": {
            "type": "string",
            "example": "eval_V1StGXR8Z5jdHi6B"
          },
          "agent_version": {
            "type": "integer",
            "description": "The one agent version every item in this run executed against",
            "example": 3
          },
          "status": {
            "type": "string",
            "enum": [
              "queued",
              "running",
              "completed",
              "failed",
              "canceled"
            ]
          },
          "baseline_run_id": {
            "type": "string",
            "nullable": true,
            "example": "evrun_V1StGXR8Z5jdHi6B"
          },
          "trigger_id": {
            "type": "string",
            "nullable": true,
            "description": "The trigger that started this run — set when a schedule (or a manual trigger fire) started it, null for a run started through this API. Kept even if the trigger is later deleted.",
            "example": "trg_V1StGXR8Z5jdHi6B"
          },
          "aggregate_scores": {
            "$ref": "#/components/schemas/AggregateScores"
          },
          "passed": {
            "type": "boolean",
            "nullable": true,
            "description": "Null when the eval declares no pass_threshold, and until the run is terminal"
          },
          "item_count": {
            "type": "integer"
          },
          "completed_count": {
            "type": "integer"
          },
          "errored_count": {
            "type": "integer"
          },
          "metadata": {
            "type": "object",
            "additionalProperties": true,
            "nullable": true,
            "description": "The caller-owned key/value metadata supplied when the run was started, returned verbatim. Null when the run was started without any (a trigger-started run included — see `trigger_id` for that provenance). The server writes nothing here.",
            "example": {
              "commit_sha": "9f2c1ab",
              "ci_job": "nightly-evals"
            }
          },
          "started_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "finished_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "EvalResult": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "example": "evres_V1StGXR8Z5jdHi6B"
          },
          "eval_run_id": {
            "type": "string",
            "example": "evrun_V1StGXR8Z5jdHi6B"
          },
          "dataset_item_id": {
            "type": "string",
            "nullable": true,
            "description": "Null once the dataset item has been deleted",
            "example": "dsit_V1StGXR8Z5jdHi6B"
          },
          "input": {
            "$ref": "#/components/schemas/DatasetItemInput"
          },
          "expected_output": {
            "type": "string",
            "nullable": true
          },
          "generation_id": {
            "type": "string",
            "nullable": true,
            "example": "gen_V1StGXR8Z5jdHi6B"
          },
          "output": {
            "type": "string",
            "nullable": true,
            "description": "The agent's final output text. Cleared when the linked generation's content is purged; the scores and the frozen input survive."
          },
          "scores": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ScorerResult"
            }
          },
          "passed": {
            "type": "boolean",
            "description": "AND over the per-scorer passed flags"
          },
          "error": {
            "type": "string",
            "nullable": true,
            "description": "Item-level failure reason. A generation that did not complete — a `requires_action` pause, a provider failure — is recorded here and excluded from the aggregates rather than scored 0."
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "ExceptionItem": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "example": "exc_V1StGXR8Z5jdHi6B"
          },
          "project_id": {
            "x-soat-ref": "projects",
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": [
              "open",
              "acknowledged",
              "resolved"
            ]
          },
          "severity": {
            "type": "string",
            "enum": [
              "info",
              "warning",
              "critical"
            ]
          },
          "kind": {
            "type": "string",
            "enum": [
              "run_failed",
              "guardrail_tripwire",
              "approval_expired",
              "quota_unpriced",
              "event_trigger_loop",
              "chain_limit",
              "manual"
            ],
            "description": "How the exception was filed"
          },
          "title": {
            "type": "string",
            "description": "Human-readable one-line summary"
          },
          "detail": {
            "type": "object",
            "nullable": true,
            "description": "Structured context (tool, args digest, error message, guardrail version)"
          },
          "occurrence_count": {
            "type": "integer",
            "description": "How many times this exact failure has been observed while open"
          },
          "last_seen_at": {
            "type": "string",
            "format": "date-time",
            "description": "Timestamp of the most recent occurrence"
          },
          "orchestration_run_id": {
            "type": "string",
            "nullable": true,
            "description": "Originating orchestration run"
          },
          "node_id": {
            "type": "string",
            "nullable": true,
            "description": "Originating node id within the run's graph"
          },
          "agent_id": {
            "type": "string",
            "nullable": true,
            "description": "Associated agent"
          },
          "guardrail_version": {
            "type": "string",
            "nullable": true,
            "description": "`<guardrailId>@<version>` for a guardrail_tripwire exception"
          },
          "acknowledged_by": {
            "type": "string",
            "nullable": true,
            "description": "Acknowledging user's public ID"
          },
          "resolved_by": {
            "type": "string",
            "nullable": true,
            "description": "Resolving user's public ID"
          },
          "resolution_note": {
            "type": "string",
            "nullable": true,
            "description": "Optional note recorded at resolution"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "UploadFileBase64Request": {
        "type": "object",
        "required": [
          "content"
        ],
        "properties": {
          "project_id": {
            "x-soat-ref": "projects",
            "type": "string",
            "description": "Public ID of the project",
            "example": "proj_V1StGXR8Z5jdHi6B"
          },
          "content": {
            "type": "string",
            "description": "Base64-encoded file content",
            "example": "SGVsbG8gV29ybGQ="
          },
          "prefix": {
            "type": "string",
            "description": "Directory within the project (e.g. /documents). Optional; defaults to / (root).",
            "example": "/documents"
          },
          "filename": {
            "type": "string",
            "description": "Original / download name and the key's leaf segment.",
            "example": "document.txt"
          },
          "content_type": {
            "type": "string",
            "description": "MIME type of the file",
            "example": "text/plain"
          },
          "metadata": {
            "type": "string",
            "description": "JSON string with additional metadata",
            "example": "{\"author\":\"John\"}"
          }
        }
      },
      "PresignedUrlRequest": {
        "type": "object",
        "required": [
          "project_id"
        ],
        "properties": {
          "project_id": {
            "x-soat-ref": "projects",
            "type": "string",
            "description": "Public ID of the project",
            "example": "proj_V1StGXR8Z5jdHi6B"
          },
          "prefix": {
            "type": "string",
            "description": "Directory within the project (e.g. /documents). Optional; defaults to / (root).",
            "example": "/documents"
          },
          "filename": {
            "type": "string",
            "description": "Original / download name and the key's leaf segment.",
            "example": "report.pdf"
          },
          "content_type": {
            "type": "string",
            "description": "MIME type of the file to be uploaded",
            "example": "application/pdf"
          }
        }
      },
      "PresignedUrlResponse": {
        "type": "object",
        "properties": {
          "upload_token": {
            "type": "string",
            "description": "Single-use token value",
            "example": "upt_V1StGXR8Z5jdHi6B"
          },
          "upload_url": {
            "type": "string",
            "description": "Fully-qualified URL to POST the file content to (absolute when SOAT_BASE_URL is configured, relative otherwise).",
            "example": "https://api.example.com/api/v1/files/upload/upt_V1StGXR8Z5jdHi6B"
          },
          "expires_at": {
            "type": "string",
            "format": "date-time",
            "description": "Token expiration timestamp"
          }
        }
      },
      "UploadFileWithTokenRequest": {
        "type": "object",
        "properties": {
          "content": {
            "type": "string",
            "description": "Base64-encoded file content (alternative to multipart `file`)",
            "example": "SGVsbG8gV29ybGQ="
          },
          "filename": {
            "type": "string",
            "description": "Original / download name (overrides the token's filename)",
            "example": "document.txt"
          },
          "content_type": {
            "type": "string",
            "description": "MIME type of the file (overrides the token's content type)",
            "example": "text/plain"
          },
          "metadata": {
            "type": "string",
            "description": "JSON string with additional metadata",
            "example": "{\"author\":\"John\"}"
          }
        }
      },
      "FileRecord": {
        "type": "object",
        "description": "Stored file metadata",
        "properties": {
          "id": {
            "type": "string",
            "description": "Unique file identifier",
            "example": "abc123"
          },
          "prefix": {
            "type": "string",
            "readOnly": true,
            "description": "Directory of the file (the `path` without its last segment). Read-only — set it via `prefix` on write.",
            "example": "/images"
          },
          "filename": {
            "type": "string",
            "description": "Original / download name and the key's leaf segment.",
            "example": "logo.png"
          },
          "path": {
            "type": "string",
            "nullable": true,
            "readOnly": true,
            "description": "Full key of the file within the project — `prefix` + `/` + `filename` (e.g. /images/logo.png). Read-only; unique per project; the file's identity and policy-SRN target.",
            "example": "/images/logo.png"
          },
          "content_type": {
            "type": "string",
            "nullable": true,
            "description": "MIME type of the file",
            "example": "application/pdf"
          },
          "size": {
            "type": "integer",
            "nullable": true,
            "description": "File size in bytes",
            "example": 1024
          },
          "metadata": {
            "type": "string",
            "nullable": true,
            "description": "JSON string with additional metadata",
            "example": "{\"author\":\"John\"}"
          },
          "tags": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            },
            "description": "Key-value tags attached to the file."
          },
          "created_at": {
            "type": "string",
            "format": "date-time",
            "description": "Creation timestamp"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time",
            "description": "Last update timestamp"
          }
        }
      },
      "FormationTemplateInput": {
        "description": "A formation template supplied as either a JSON object or a YAML/JSON string. When a string is provided the server parses it with a YAML parser (JSON is valid YAML) before processing.\n",
        "oneOf": [
          {
            "$ref": "#/components/schemas/FormationTemplate"
          },
          {
            "type": "string",
            "description": "YAML or JSON string representation of a FormationTemplate"
          }
        ]
      },
      "FormationTemplate": {
        "type": "object",
        "required": [
          "resources"
        ],
        "properties": {
          "parameters": {
            "type": "object",
            "additionalProperties": {
              "$ref": "#/components/schemas/ParameterDeclaration"
            },
            "description": "Declared parameters for this template. Each parameter may have a default value and an optional description. Parameters without a default must be supplied in the `parameters` field of the deploy request.\n",
            "nullable": true
          },
          "resources": {
            "type": "object",
            "additionalProperties": {
              "$ref": "#/components/schemas/ResourceDeclaration"
            },
            "description": "Map of logical resource IDs to resource declarations"
          },
          "outputs": {
            "type": "object",
            "additionalProperties": true,
            "description": "Map of output names to values. Values may use `{ \"ref\": \"logicalId\" }` to reference physical IDs of created resources, or `{ \"param\": \"ParamName\" }` and `{ \"sub\": \"text ${ParamName}\" }` to embed parameter values.\n",
            "nullable": true
          },
          "metadata": {
            "type": "object",
            "additionalProperties": true,
            "nullable": true,
            "description": "Arbitrary metadata attached to the template. Supports the same substitution as `outputs`: `{ \"ref\": \"logicalId\" }` resolves to a created resource's physical ID, and `{ \"param\": \"ParamName\" }` / `{ \"sub\": \"text ${ParamName}\" }` embed parameter values. The raw expressions are preserved here; the resolved values from the last deploy are exposed on the formation's `resolved_metadata` field.\n"
          }
        }
      },
      "ParameterDeclaration": {
        "type": "object",
        "properties": {
          "type": {
            "type": "string",
            "description": "Parameter type (currently only 'string' is supported)",
            "example": "string"
          },
          "default": {
            "type": "string",
            "description": "Default value used when the parameter is not supplied at deploy time",
            "nullable": true
          },
          "description": {
            "type": "string",
            "description": "Human-readable description of what this parameter represents",
            "nullable": true
          },
          "no_echo": {
            "type": "boolean",
            "description": "When true, the parameter value should be treated as sensitive and not echoed in logs or UI. Analogous to NoEcho in CloudFormation.\n",
            "nullable": true
          },
          "use_previous_value": {
            "type": "boolean",
            "description": "When true, omitting this parameter on update reuses its previously stored value instead of failing the required-parameter check — analogous to CloudFormation's UsePreviousValue, declared in the template. An explicitly supplied value still overrides. Has no effect on create (there is no previous value yet). The value is reused only where the underlying resource retains it (e.g. a secret's encrypted value); otherwise the last-applied value is used.\n",
            "nullable": true
          }
        }
      },
      "AgentResourceProperties": {
        "description": "Creates an AI agent backed by a provider. The agent handles requests, runs tools, and can be attached to actors. Exactly one of `ai_provider_id` or `model_route_id` must be declared. Switching an existing agent between the two declares the new field together with an explicit `null` for the old one.",
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "ai_provider_id": {
            "x-soat-ref": "ai-providers",
            "type": "string",
            "nullable": true,
            "description": "Public ID of the AI provider to pin. Mutually exclusive with `model_route_id`."
          },
          "model_route_id": {
            "x-soat-ref": "model-routes",
            "type": "string",
            "nullable": true,
            "description": "Public ID of a model route in the same project — the agent's completion model is resolved through the route's ordered targets with failover. Mutually exclusive with `ai_provider_id` and `model`."
          },
          "name": {
            "type": "string",
            "nullable": true,
            "description": "Agent display name"
          },
          "instructions": {
            "type": "string",
            "nullable": true,
            "description": "System instructions for the agent"
          },
          "model": {
            "type": "string",
            "nullable": true,
            "description": "Model identifier (overrides provider default)"
          },
          "tool_bindings": {
            "type": "array",
            "nullable": true,
            "items": {
              "type": "object"
            },
            "description": "Tools to attach, one binding object per tool: `{ tool_id }`. Tool-call gating is owned by guardrails (attached via `guardrail_ids` on the project, agent, or tool), not by the binding. Inline `tool` entries are not supported in templates; declare a tool resource and reference it via `tool_id` (a `{ \"ref\": … }` to a tool resource in the same template resolves at deploy time)."
          },
          "max_steps": {
            "type": "integer",
            "nullable": true,
            "description": "Maximum number of agentic steps per generation"
          },
          "tool_choice": {
            "nullable": true,
            "description": "Controls how the model selects tools. Accepts a string (`\"auto\"`, `\"required\"`) or an object (`{ \"type\": \"tool\", \"tool_name\": \"my_tool\" }`)."
          },
          "stop_conditions": {
            "type": "array",
            "nullable": true,
            "description": "Conditions that stop the agent's work early — turn-scoped (`has_tool_call`) or chain-scoped (`max_chain_generations`).",
            "items": {
              "type": "object",
              "properties": {
                "type": {
                  "type": "string",
                  "description": "Condition type — `has_tool_call` or `max_chain_generations`"
                },
                "tool_name": {
                  "type": "string",
                  "nullable": true,
                  "description": "Tool name to match when type is `has_tool_call`"
                },
                "max_generations": {
                  "type": "integer",
                  "nullable": true,
                  "description": "Generations the continuation chain may reach when type is `max_chain_generations`"
                }
              }
            }
          },
          "active_tool_ids": {
            "x-soat-ref": "tools",
            "type": "array",
            "nullable": true,
            "items": {
              "type": "string"
            },
            "description": "Subset of the bound tools that are active"
          },
          "guardrail_ids": {
            "x-soat-ref": "guardrails",
            "type": "array",
            "nullable": true,
            "items": {
              "type": "string"
            },
            "description": "Guardrails attached at the agent scope."
          },
          "step_rules": {
            "type": "array",
            "nullable": true,
            "description": "Per-step overrides applied during multi-step generation. Steps not covered by a rule use the agent defaults.",
            "items": {
              "type": "object",
              "properties": {
                "step": {
                  "type": "integer",
                  "description": "1-indexed step number this rule applies to"
                },
                "tool_choice": {
                  "type": "object",
                  "nullable": true,
                  "description": "Tool choice override for this step, e.g. `auto`, `required`, or `{ type: tool, tool_name: search }`"
                },
                "active_tool_ids": {
                  "x-soat-ref": "tools",
                  "type": "array",
                  "nullable": true,
                  "items": {
                    "type": "string"
                  },
                  "description": "Tool IDs active on this step"
                }
              }
            }
          },
          "boundary_policy": {
            "type": "object",
            "nullable": true,
            "description": "Restricts which SOAT actions the agent may invoke. Evaluated as the intersection with the caller's own policy.",
            "properties": {
              "statement": {
                "type": "array",
                "description": "List of IAM policy statements",
                "items": {
                  "type": "object",
                  "properties": {
                    "effect": {
                      "type": "string",
                      "description": "Effect — `Allow` or `Deny`"
                    },
                    "action": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      },
                      "description": "IAM action strings, e.g. `memories:*` or `agents:DeleteAgent`"
                    },
                    "resource": {
                      "type": "array",
                      "nullable": true,
                      "items": {
                        "type": "string"
                      },
                      "description": "Resource SRN patterns (optional; omit to match all resources)"
                    }
                  }
                }
              }
            }
          },
          "temperature": {
            "type": "number",
            "nullable": true,
            "description": "Sampling temperature"
          },
          "max_context_messages": {
            "type": "integer",
            "nullable": true,
            "description": "Maximum number of recent messages to include in the context window sent to the model. When null, all messages are included."
          },
          "single_session_per_actor": {
            "type": "boolean",
            "nullable": true,
            "description": "When true, only one open session per actor_id is allowed for this agent."
          },
          "trace_content_mode": {
            "type": "string",
            "nullable": true,
            "description": "Agent-scope zero-retention setting (`full` or `none`). `null` inherits the project's setting. `full` is refused when the project's own mode is `none`."
          },
          "on_approval_expiry": {
            "type": "string",
            "nullable": true,
            "description": "What happens when a held tool call expires un-approved: `terminate` (the default when null) ends the chain, `react` spawns a continuation that reports the staleness to the agent."
          },
          "knowledge_config": {
            "type": "object",
            "nullable": true,
            "description": "Knowledge retrieval configuration. When set, relevant documents and memory entries are injected into every generation.",
            "properties": {
              "memory_ids": {
                "x-soat-ref": "memories",
                "type": "array",
                "items": {
                  "type": "string"
                },
                "description": "Public IDs of memories to retrieve from"
              },
              "memory_tags": {
                "type": "array",
                "items": {
                  "type": "string"
                },
                "description": "Retrieve from all memories matching these tags"
              },
              "document_ids": {
                "x-soat-ref": "documents",
                "type": "array",
                "items": {
                  "type": "string"
                },
                "description": "Public IDs of documents to retrieve from"
              },
              "document_paths": {
                "type": "array",
                "items": {
                  "type": "string"
                },
                "description": "Retrieve from all documents matching these path prefixes"
              },
              "min_score": {
                "type": "number",
                "description": "Minimum similarity score (0–1) for retrieved chunks"
              },
              "limit": {
                "type": "integer",
                "description": "Maximum number of chunks to inject"
              },
              "write_memory_id": {
                "x-soat-ref": "memories",
                "type": "string",
                "nullable": true,
                "description": "Public ID of the memory the agent can write to. When set, a `write_memory` tool is automatically available to the agent."
              },
              "extraction": {
                "description": "Automatic fact extraction from completed generation turns (requires write_memory_id). Pass `true` to enable with defaults, or an object to customize the provider, model, and prompt used for the extraction completion.",
                "oneOf": [
                  {
                    "type": "boolean"
                  },
                  {
                    "type": "object",
                    "properties": {
                      "enabled": {
                        "type": "boolean",
                        "description": "Defaults to true when the object form is used. Set false to keep the configuration but disable extraction."
                      },
                      "ai_provider_id": {
                        "x-soat-ref": "ai-providers",
                        "type": "string",
                        "description": "AI provider override for extraction calls. Must belong to the agent's project. Its default_model becomes the model fallback."
                      },
                      "model": {
                        "type": "string",
                        "description": "Model override for extraction calls."
                      },
                      "prompt": {
                        "type": "string",
                        "description": "Replaces the default task instructions. The JSON response contract and the conversation transcript are always appended by the server."
                      }
                    }
                  }
                ]
              }
            }
          },
          "output_schema": {
            "type": "object",
            "nullable": true,
            "description": "JSON Schema describing the structured object the model must return. Non-streaming generations are constrained to this schema; the parsed value is returned as `output.object`."
          }
        }
      },
      "ActorResourceProperties": {
        "description": "Creates a stateful conversation actor that wraps an agent or chat session and optionally links to a memory store.",
        "type": "object",
        "additionalProperties": false,
        "required": [
          "name"
        ],
        "properties": {
          "name": {
            "type": "string",
            "description": "Actor display name"
          },
          "external_id": {
            "type": "string",
            "nullable": true,
            "description": "External identifier for idempotent actor creation"
          },
          "instructions": {
            "type": "string",
            "nullable": true,
            "description": "Persona-specific instructions"
          },
          "agent_id": {
            "x-soat-ref": "agents",
            "type": "string",
            "nullable": true,
            "description": "Linked agent ID (mutually exclusive with chat_id)"
          },
          "chat_id": {
            "x-soat-ref": "chats",
            "type": "string",
            "nullable": true,
            "description": "Linked chat ID (mutually exclusive with agent_id)"
          }
        }
      },
      "AiProviderResourceProperties": {
        "description": "Configures an LLM provider connection (API key, model, endpoint) that agents use to generate responses.",
        "type": "object",
        "additionalProperties": false,
        "required": [
          "name",
          "provider",
          "default_model"
        ],
        "properties": {
          "name": {
            "type": "string",
            "description": "Provider display name"
          },
          "provider": {
            "type": "string",
            "enum": [
              "openai",
              "anthropic",
              "google",
              "xai",
              "groq",
              "ollama",
              "azure",
              "bedrock",
              "vertex",
              "gateway",
              "custom"
            ],
            "description": "Provider type"
          },
          "default_model": {
            "type": "string",
            "description": "Default model identifier (e.g. gpt-4o, claude-3-7-sonnet)"
          },
          "secret_id": {
            "x-soat-ref": "secrets",
            "type": "string",
            "nullable": true,
            "description": "Public ID of the secret containing the API key"
          },
          "base_url": {
            "type": "string",
            "nullable": true,
            "description": "Custom base URL for the provider API (self-hosted or proxy)"
          },
          "config": {
            "type": "object",
            "nullable": true,
            "description": "Provider-specific extra configuration"
          }
        }
      },
      "ToolResourceProperties": {
        "description": "Defines a tool (HTTP endpoint, MCP server, SOAT action, or pipeline) that agents can invoke during a generation.",
        "type": "object",
        "additionalProperties": false,
        "required": [
          "name"
        ],
        "properties": {
          "name": {
            "type": "string",
            "description": "Tool display name"
          },
          "type": {
            "type": "string",
            "nullable": true,
            "description": "Tool type hint (e.g. http, mcp, builtin, pipeline)"
          },
          "description": {
            "type": "string",
            "nullable": true,
            "description": "Tool description shown to the model"
          },
          "parameters": {
            "type": "object",
            "nullable": true,
            "description": "JSON Schema describing the tool's input parameters (free-form, user-defined)"
          },
          "execute": {
            "type": "object",
            "nullable": true,
            "description": "HTTP execution configuration. Required for `http` tools.",
            "properties": {
              "url": {
                "type": "string",
                "description": "Endpoint URL. Supports `{param}` placeholders resolved from tool arguments."
              },
              "method": {
                "type": "string",
                "nullable": true,
                "description": "HTTP method (default: `POST`)"
              },
              "headers": {
                "type": "object",
                "nullable": true,
                "description": "Static headers included in every request"
              },
              "body_mode": {
                "type": "string",
                "nullable": true,
                "description": "Request body encoding for `POST`/`PUT`/`PATCH`: `json` (default) or `multipart`. Incompatible with `auth.type: aws_sigv4`."
              },
              "auth": {
                "type": "object",
                "nullable": true,
                "description": "Computed request credential. `type` is `aws_sigv4` (with `region`, `service`, `access_key_id`, `secret_access_key` and optional `session_token`) or `gcp_service_account` (with `credentials` and `scopes`). Credential fields accept `{{secret:...}}` references."
              }
            }
          },
          "mcp": {
            "type": "object",
            "nullable": true,
            "description": "MCP server connection configuration. Required for `mcp` tools.",
            "properties": {
              "url": {
                "type": "string",
                "description": "MCP server URL"
              },
              "headers": {
                "type": "object",
                "nullable": true,
                "description": "Headers included in every MCP request"
              }
            }
          },
          "actions": {
            "type": "array",
            "nullable": true,
            "items": {
              "type": "string"
            },
            "description": "Allowlist of actions the tool exposes. For `builtin` tools: SOAT platform action names. For `mcp` tools: an optional allowlist of MCP tool names to scope the server surface (`null` exposes every tool)."
          },
          "denied_actions": {
            "type": "array",
            "nullable": true,
            "items": {
              "type": "string"
            },
            "description": "For `mcp` tools: an optional denylist of MCP tool names to hide. Applied after `actions` and taking precedence over it — the ergonomic way to scope a read+write MCP server read-only by denying just the write tools. `null` denies nothing."
          },
          "context_keys": {
            "type": "array",
            "nullable": true,
            "items": {
              "type": "string"
            },
            "description": "Optional allowlist of `tool_context` keys forwarded to this tool as prefixed context headers. `null` or omitted forwards every key; `[]` forwards none. The server-pinned identity keys (`session_id`, `actor_id`, `actor_external_id`) are always forwarded, and a key consumed by a `{{context:<key>}}` token in this tool's own headers is substituted regardless of this list."
          },
          "preset_parameters": {
            "type": "object",
            "nullable": true,
            "description": "Pre-filled parameter values injected at execution time"
          },
          "pipeline": {
            "type": "object",
            "nullable": true,
            "description": "Pipeline definition for `pipeline` tools: an ordered `steps` array, each invoking another tool by `tool_id` (optional `action`) with an `input` built from earlier results via JSON Logic over `{ input, steps }`, plus an optional `output` mapping. Step `input` keys and `var` paths use camelCase (the runtime form). Free-form, user-defined."
          },
          "output_mapping": {
            "type": "object",
            "nullable": true,
            "description": "Universal JSON Logic mapping applied to the tool's raw result, for every tool type. Evaluated over `{ output: <raw result> }`, e.g. `{ \"var\": \"output.text\" }`. For `pipeline` tools this runs after the pipeline's own `output` mapping."
          },
          "guardrail_ids": {
            "x-soat-ref": "guardrails",
            "type": "array",
            "nullable": true,
            "items": {
              "type": "string"
            },
            "description": "Guardrails attached at the tool scope."
          }
        }
      },
      "DatasetResourceProperties": {
        "description": "Declares an evaluation dataset — the named fixture suite an eval runs an agent against. Its test cases are declared separately as `dataset_item` resources, so an item curated through the API is never collateral of a formation apply. Deleting the dataset deletes its items and the evals bound to it.",
        "type": "object",
        "additionalProperties": false,
        "required": [
          "name"
        ],
        "properties": {
          "name": {
            "type": "string",
            "description": "Dataset name, unique within the project"
          },
          "description": {
            "type": "string",
            "nullable": true,
            "description": "Optional description"
          }
        }
      },
      "DatasetItemResourceProperties": {
        "description": "One test case in a dataset: the messages sent to the agent under test and, optionally, the reference answer scorers compare against. Editing or removing an item never rewrites a run that already scored it — each result froze its own copy.",
        "type": "object",
        "additionalProperties": false,
        "required": [
          "dataset_id",
          "input"
        ],
        "properties": {
          "dataset_id": {
            "x-soat-ref": "datasets",
            "type": "string",
            "description": "Public ID of the parent dataset (or ref expression)"
          },
          "input": {
            "type": "array",
            "items": {
              "type": "object"
            },
            "description": "The messages sent to the agent, as `{role, content}` objects"
          },
          "expected_output": {
            "type": "string",
            "nullable": true,
            "description": "Reference answer for exact_match / contains / embedding_similarity / llm_judge scorers"
          },
          "metadata": {
            "type": "object",
            "nullable": true,
            "additionalProperties": true,
            "description": "Free-form tags on the case, e.g. `{\"topic\": \"billing\"}`"
          }
        }
      },
      "EvalResourceProperties": {
        "description": "Binds an agent under test to a dataset and the scorers its outputs are judged by. `pass_threshold` is the pass rate a run must reach for its `passed` verdict — the gate an agent-version promotion consumes.",
        "type": "object",
        "additionalProperties": false,
        "required": [
          "name",
          "agent_id",
          "dataset_id",
          "scorers"
        ],
        "properties": {
          "name": {
            "type": "string",
            "description": "Eval name, unique within the project"
          },
          "agent_id": {
            "x-soat-ref": "agents",
            "type": "string",
            "description": "Public ID of the agent under test (or ref expression)"
          },
          "dataset_id": {
            "x-soat-ref": "datasets",
            "type": "string",
            "description": "Public ID of the dataset to run against (or ref expression)"
          },
          "scorers": {
            "type": "array",
            "items": {
              "type": "object"
            },
            "description": "Scorer configs — `exact_match`, `contains`, `json_logic`, `output_schema`, `embedding_similarity`, `llm_judge`, or `tool`. Same shape as the evals REST contract."
          },
          "pass_threshold": {
            "type": "number",
            "nullable": true,
            "description": "0–1. A run passes when its pass rate over non-errored items reaches this. Omit for a run that reports scores without a verdict."
          }
        }
      },
      "DocumentResourceProperties": {
        "description": "Stores a text document in a project, optionally indexing it for knowledge retrieval.",
        "type": "object",
        "additionalProperties": false,
        "required": [
          "content"
        ],
        "properties": {
          "content": {
            "type": "string",
            "description": "Document text content"
          },
          "path": {
            "type": "string",
            "nullable": true,
            "description": "Virtual path for organising the document"
          },
          "filename": {
            "type": "string",
            "nullable": true,
            "description": "Original filename"
          },
          "title": {
            "type": "string",
            "nullable": true,
            "description": "Document title"
          },
          "metadata": {
            "type": "object",
            "nullable": true,
            "description": "Arbitrary metadata key-value pairs"
          },
          "tags": {
            "type": "object",
            "nullable": true,
            "description": "Tag key-value pairs for filtering"
          },
          "chunk_strategy": {
            "type": "string",
            "enum": [
              "page",
              "whole",
              "size"
            ],
            "description": "How to split the content into embeddable chunks, matching `POST /documents`. `whole` (default) stores the content as a single chunk; `size` splits into fixed-size character windows with overlap. `page` is equivalent to `whole` for plain text.",
            "default": "whole"
          },
          "chunk_size": {
            "type": "integer",
            "description": "Window size in characters when `chunk_strategy=size`. Defaults to 1000."
          },
          "chunk_overlap": {
            "type": "integer",
            "description": "Overlap in characters between consecutive windows when `chunk_strategy=size`. Defaults to 200."
          }
        }
      },
      "MemoryResourceProperties": {
        "description": "Creates a named memory store that actors can read from and write to across conversations.",
        "type": "object",
        "additionalProperties": false,
        "required": [
          "name"
        ],
        "properties": {
          "name": {
            "type": "string",
            "description": "Memory display name"
          },
          "description": {
            "type": "string",
            "nullable": true,
            "description": "What this memory stores"
          },
          "tags": {
            "type": "array",
            "nullable": true,
            "items": {
              "type": "string"
            },
            "description": "Tag strings for filtering"
          }
        }
      },
      "MemoryEntryResourceProperties": {
        "description": "Adds a single text entry to a memory store.",
        "type": "object",
        "additionalProperties": false,
        "required": [
          "memory_id",
          "content"
        ],
        "properties": {
          "memory_id": {
            "x-soat-ref": "memories",
            "type": "string",
            "description": "Public ID of the parent memory (or ref expression)"
          },
          "content": {
            "type": "string",
            "description": "Text content of the memory entry"
          },
          "source_type": {
            "type": "string",
            "enum": [
              "manual",
              "agent",
              "extraction",
              "orchestration"
            ],
            "description": "How this entry was created (defaults to manual)"
          },
          "tags": {
            "type": "array",
            "nullable": true,
            "items": {
              "type": "string"
            },
            "description": "Per-entry tag strings for entry-granularity filtering"
          },
          "metadata": {
            "type": "object",
            "nullable": true,
            "additionalProperties": true,
            "description": "Arbitrary structured metadata attached to the entry"
          }
        }
      },
      "ModelRouteResourceProperties": {
        "description": "Declares a model route within the formation's project: a named, ordered list of provider+model failover targets with retry and circuit-breaker configuration. Consumers reference it through their own `model_route_id`, or inherit it as the project's `default_model_route_id`.",
        "type": "object",
        "additionalProperties": false,
        "required": [
          "name",
          "targets"
        ],
        "properties": {
          "name": {
            "type": "string",
            "description": "Route name, unique within the project"
          },
          "targets": {
            "type": "array",
            "description": "Ordered failover targets, tried in array order. Each entry is `{ ai_provider_id, model, timeout_seconds?, max_retries? }`; every provider must belong to this project, and the total attempt budget (sum of `1 + max_retries`) is capped at 10.",
            "items": {
              "type": "object"
            }
          },
          "retry_on": {
            "type": "array",
            "description": "Which failure classes fail over: any of `provider_error`, `timeout`, `rate_limited`. Defaults to all three. Deterministic rejections (400-class, auth, content policy) never fail over.",
            "items": {
              "type": "string"
            }
          },
          "failure_threshold": {
            "type": "integer",
            "nullable": true,
            "description": "Consecutive retryable failures before a target is skipped (default 3)"
          },
          "cooldown_seconds": {
            "type": "integer",
            "nullable": true,
            "description": "How long a tripped target is skipped before being probed again (default 60)"
          }
        }
      },
      "WebhookResourceProperties": {
        "description": "Registers an HTTPS endpoint to receive SOAT platform event notifications.",
        "type": "object",
        "additionalProperties": false,
        "required": [
          "name",
          "url",
          "events"
        ],
        "properties": {
          "name": {
            "type": "string",
            "description": "Webhook display name"
          },
          "description": {
            "type": "string",
            "nullable": true,
            "description": "Optional description"
          },
          "url": {
            "type": "string",
            "description": "HTTPS endpoint that receives event payloads"
          },
          "events": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Event types to subscribe to (e.g. memory.updated)"
          }
        }
      },
      "TriggerResourceProperties": {
        "description": "Binds a starter (manual, webhook, schedule, or event) to an executable target (orchestration, agent, tool, or eval). Firings run under the confined run-as identity of the caller who deployed the formation, so a firing never exceeds what that caller could do directly.",
        "type": "object",
        "additionalProperties": false,
        "required": [
          "name",
          "type",
          "target_type",
          "target_id"
        ],
        "properties": {
          "name": {
            "type": "string",
            "description": "Trigger display name (unique within the project)"
          },
          "description": {
            "type": "string",
            "nullable": true,
            "description": "Optional description"
          },
          "type": {
            "type": "string",
            "enum": [
              "manual",
              "webhook",
              "schedule",
              "event"
            ],
            "description": "Starter type. Immutable after creation"
          },
          "target_type": {
            "type": "string",
            "enum": [
              "orchestration",
              "agent",
              "tool",
              "eval"
            ],
            "description": "The kind of resource this trigger activates"
          },
          "target_id": {
            "type": "string",
            "description": "Public ID of the target resource. Use { \"ref\": \"LogicalId\" } to reference an orchestration, agent, tool, or eval defined in the template."
          },
          "action": {
            "type": "string",
            "nullable": true,
            "description": "Tool targets only — the action for builtin/mcp tools"
          },
          "input": {
            "type": "object",
            "nullable": true,
            "description": "Static input shallow-merged under each firing's runtime input"
          },
          "cron": {
            "type": "string",
            "nullable": true,
            "description": "5-field cron expression (UTC). Required when type is schedule"
          },
          "event_pattern": {
            "type": "string",
            "nullable": true,
            "description": "Internal-event subscription pattern (`*`, `prefix.*`, or an exact event name). Required when type is event, rejected otherwise"
          },
          "active": {
            "type": "boolean",
            "description": "Whether the trigger fires (default true)"
          },
          "policy_id": {
            "x-soat-ref": "policies",
            "type": "string",
            "nullable": true,
            "description": "Optional boundary policy that further confines the run-as identity"
          }
        }
      },
      "ApiKeyResourceProperties": {
        "description": "Creates an API key scoped to the formation's project and optionally restricted by a set of policies. The key is owned by the caller who deployed the formation, exactly as one created through the API is, so it never carries more access than they already have.",
        "type": "object",
        "additionalProperties": false,
        "required": [
          "name"
        ],
        "properties": {
          "name": {
            "type": "string",
            "description": "Human-readable label for the API key"
          },
          "policy_ids": {
            "x-soat-ref": "policies",
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Optional list of policy public IDs that further restrict the key's permissions"
          }
        }
      },
      "ChatResourceProperties": {
        "description": "Creates a chat within the formation's project, connected either to an AI provider or — by declaring no provider — to the project's `default_model_route_id`.",
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "ai_provider_id": {
            "x-soat-ref": "ai-providers",
            "type": "string",
            "nullable": true,
            "description": "Public ID of the AI provider to use for this chat. Omit (or declare `null`) to inherit the project's `default_model_route_id`, which requires the project to have one and cannot be combined with `model`."
          },
          "name": {
            "type": "string",
            "nullable": true,
            "description": "Human-readable label for the chat"
          },
          "instructions": {
            "type": "string",
            "nullable": true,
            "description": "System message to set the assistant behaviour"
          },
          "model": {
            "type": "string",
            "nullable": true,
            "description": "Model override; defaults to the AI provider's default model"
          }
        }
      },
      "ConversationResourceProperties": {
        "description": "Creates a conversation within the formation's project.",
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "name": {
            "type": "string",
            "nullable": true,
            "description": "Human-readable label for the conversation"
          },
          "status": {
            "type": "string",
            "description": "Initial status of the conversation (open or closed)"
          },
          "actor_id": {
            "x-soat-ref": "actors",
            "type": "string",
            "nullable": true,
            "description": "Public ID of an actor to associate with this conversation"
          }
        }
      },
      "FileResourceProperties": {
        "description": "Registers a file record within the formation's project.",
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "prefix": {
            "type": "string",
            "nullable": true,
            "description": "Directory within the project. Optional; defaults to / (root). Combined with filename to form the file's key (path)."
          },
          "filename": {
            "type": "string",
            "nullable": true,
            "description": "Original / download name and the key's leaf segment."
          },
          "content_type": {
            "type": "string",
            "nullable": true,
            "description": "MIME type of the file"
          },
          "size": {
            "type": "integer",
            "nullable": true,
            "description": "File size in bytes"
          },
          "metadata": {
            "type": "string",
            "nullable": true,
            "description": "JSON string with additional metadata"
          }
        }
      },
      "PolicyResourceProperties": {
        "description": "Creates an access-control policy within the formation's project.",
        "type": "object",
        "additionalProperties": false,
        "required": [
          "document"
        ],
        "properties": {
          "name": {
            "type": "string",
            "nullable": true,
            "description": "Human-readable label for the policy"
          },
          "description": {
            "type": "string",
            "nullable": true,
            "description": "Description of what the policy grants"
          },
          "document": {
            "type": "object",
            "additionalProperties": true,
            "description": "Policy document containing an array of statements"
          }
        }
      },
      "SecretResourceProperties": {
        "description": "Creates an encrypted secret within the formation's project.",
        "type": "object",
        "additionalProperties": false,
        "required": [
          "name",
          "value"
        ],
        "properties": {
          "name": {
            "type": "string",
            "description": "Human-readable label for the secret"
          },
          "value": {
            "type": "string",
            "description": "The secret value to encrypt and store"
          }
        }
      },
      "ProjectPriceResourceProperties": {
        "description": "Upserts a project-scoped price row so a deployed stack produces billing-grade usage cost with no out-of-band pricing step. The row is keyed on (provider, model, component, effective_from) within the formation's project — the middle pricing tier that covers every one of the project's instances of a given provider slug. When `effective_from` is omitted the price takes effect at deploy time, so generations run right after deploy are priced.",
        "type": "object",
        "additionalProperties": false,
        "required": [
          "provider",
          "model",
          "component",
          "unit",
          "unit_price"
        ],
        "properties": {
          "provider": {
            "type": "string",
            "description": "SKU vendor slug the price applies to (e.g. openai, anthropic, soat)"
          },
          "model": {
            "type": "string",
            "description": "SKU identifier — the model id for LLM SKUs, the platform unit otherwise"
          },
          "component": {
            "type": "string",
            "description": "The billable component this row prices (input_tokens, output_tokens, cached_tokens, compute_second, …)"
          },
          "unit": {
            "type": "string",
            "description": "Unit the unit_price is denominated in (token, compute_second, …); must match the metered component's unit"
          },
          "unit_price": {
            "type": "number",
            "description": "USD per unit. Must be a non-negative number"
          },
          "meter_type": {
            "type": "string",
            "description": "Meter type this SKU belongs to (defaults to llm_tokens)"
          },
          "effective_from": {
            "type": "string",
            "format": "date-time",
            "description": "Timestamp from which this price applies. Omit to take effect at deploy time. The row with the latest effective_from at or before now() prices a call."
          }
        }
      },
      "SessionResourceProperties": {
        "description": "Creates a session attached to an agent within the formation's project.",
        "type": "object",
        "additionalProperties": false,
        "required": [
          "agent_id"
        ],
        "properties": {
          "agent_id": {
            "x-soat-ref": "agents",
            "type": "string",
            "description": "Public ID of the agent that owns this session"
          },
          "name": {
            "type": "string",
            "nullable": true,
            "description": "Human-readable label for the session"
          },
          "actor_id": {
            "x-soat-ref": "actors",
            "type": "string",
            "nullable": true,
            "description": "Public ID of an actor to associate with this session"
          },
          "auto_generate": {
            "type": "boolean",
            "description": "Whether to automatically generate a response when messages are sent"
          },
          "inactivity_ttl_seconds": {
            "type": "integer",
            "description": "Number of seconds of inactivity after which the session expires. 0 means never expires."
          },
          "tool_context": {
            "type": "object",
            "additionalProperties": true,
            "nullable": true,
            "description": "Optional context object passed to tool calls"
          }
        }
      },
      "IngestionRuleResourceProperties": {
        "description": "Routes a file content_type to a converter (tool or agent) so ingestion can turn non-native files (images, audio, scanned PDFs) into Documents. See the Ingestion Rules module docs for the matching and converter-invocation model.",
        "type": "object",
        "additionalProperties": false,
        "required": [
          "content_type_glob"
        ],
        "properties": {
          "content_type_glob": {
            "type": "string",
            "description": "MIME type glob matched against a file's content_type (e.g. image/*, audio/mpeg, application/pdf)"
          },
          "tool_id": {
            "x-soat-ref": "tools",
            "type": "string",
            "nullable": true,
            "description": "Converter tool ID (mutually exclusive with agent_id)"
          },
          "agent_id": {
            "x-soat-ref": "agents",
            "type": "string",
            "nullable": true,
            "description": "Converter agent ID (mutually exclusive with tool_id)"
          },
          "action": {
            "type": "string",
            "nullable": true,
            "description": "Operation id, required for builtin/mcp tool converters"
          },
          "preset_parameters": {
            "type": "object",
            "nullable": true,
            "description": "Merged into the tool input before invocation (tool converters only)"
          },
          "native_extraction": {
            "type": "string",
            "nullable": true,
            "description": "For native types (PDF/text): `first` (default) converts only when native extraction yields no text; `skip` always converts."
          },
          "file_delivery": {
            "type": "string",
            "nullable": true,
            "description": "How the file reaches a tool converter — base64 (default) or download_url"
          },
          "chunk_strategy": {
            "type": "string",
            "nullable": true,
            "description": "Default chunk strategy (page/whole/size), overridable per ingest request"
          },
          "chunk_size": {
            "type": "integer",
            "nullable": true,
            "description": "Default window size in characters for the size strategy"
          },
          "chunk_overlap": {
            "type": "integer",
            "nullable": true,
            "description": "Default overlap in characters for the size strategy"
          },
          "metadata": {
            "type": "object",
            "nullable": true,
            "description": "Arbitrary JSON metadata"
          }
        }
      },
      "OrchestrationResourceProperties": {
        "description": "Creates a DAG orchestration that wires agents, tools, and knowledge lookups into a repeatable pipeline within the formation's project. Node resource references (`agent_id`, `tool_id`, `memory_id`, `orchestration_id`) accept `{ \"ref\": \"LogicalId\" }` expressions to point at other resources declared in the same template — the basis for deploying an agent \"squad\" (a team of agents plus the flow that coordinates them) as a single stack.",
        "type": "object",
        "additionalProperties": false,
        "required": [
          "name",
          "nodes",
          "edges"
        ],
        "properties": {
          "name": {
            "type": "string",
            "description": "Human-readable name for the orchestration"
          },
          "description": {
            "type": "string",
            "nullable": true,
            "description": "Optional description of what the orchestration does"
          },
          "nodes": {
            "type": "array",
            "description": "Ordered list of node definitions. A node's resource references (`agent_id`, `tool_id`, `memory_id`, `orchestration_id`) may use `{ \"ref\": \"LogicalId\" }` to bind to other resources in the template.",
            "items": {
              "type": "object",
              "additionalProperties": true
            }
          },
          "edges": {
            "type": "array",
            "description": "Directed connections between nodes",
            "items": {
              "type": "object",
              "additionalProperties": true
            }
          },
          "state_schema": {
            "type": "object",
            "nullable": true,
            "additionalProperties": true,
            "description": "Optional JSON Schema describing the run state"
          },
          "input_schema": {
            "type": "object",
            "nullable": true,
            "additionalProperties": true,
            "description": "Optional JSON Schema describing the run input"
          }
        }
      },
      "WorkflowResourceProperties": {
        "description": "Creates a workflow — a state-machine definition (named states, allowed transitions, guards, and per-state automation) that tasks live in. State and transition dispatch references (`agent_id`, `orchestration_id`, `tool_id` inside an `on_enter` block) accept `{ \"ref\": \"LogicalId\" }` expressions to point at agents, orchestrations or tools declared in the same template, so a workflow plus the agents and tools that service its states can deploy as one stack. Mirrors the workflows REST contract (`states`, `transitions`, `payload_schema`).",
        "type": "object",
        "additionalProperties": false,
        "required": [
          "name",
          "states",
          "transitions"
        ],
        "properties": {
          "name": {
            "type": "string",
            "description": "Human-readable name for the workflow, unique within the project"
          },
          "description": {
            "type": "string",
            "nullable": true,
            "description": "Optional description of what the workflow models"
          },
          "states": {
            "type": "array",
            "description": "Named states. Exactly one must be `initial: true`; any number may be `terminal: true`. A `kind: human` state parks the task until a transition fires; an `on_enter` block dispatches one agent generation or orchestration run on entry.",
            "items": {
              "type": "object",
              "additionalProperties": true
            }
          },
          "transitions": {
            "type": "array",
            "description": "Named, directional moves between states. Each has `from` (source states) and `to` (one target), an optional JSON Logic `guard`, and an optional `requires_approval` gate.",
            "items": {
              "type": "object",
              "additionalProperties": true
            }
          },
          "payload_schema": {
            "type": "object",
            "nullable": true,
            "additionalProperties": true,
            "description": "Optional JSON Schema describing a task's payload"
          }
        }
      },
      "QuotaResourceProperties": {
        "description": "Creates a quota — a project-scoped cap that blocks (`enforce`) or reports (`monitor`) when a windowed aggregate is exceeded. `requests` quotas are enforced by the request middleware; `tokens`/`cost_usd` quotas at the pre-generation check. Mirrors the quotas REST contract; `scope`, `metric`, and `window` are immutable after creation (only `limit`, `mode`, and `on_unpriced` update).",
        "type": "object",
        "additionalProperties": false,
        "required": [
          "scope",
          "metric",
          "window",
          "limit"
        ],
        "properties": {
          "scope": {
            "type": "string",
            "enum": [
              "project",
              "api_key",
              "agent",
              "actor"
            ],
            "description": "The scope the quota applies to"
          },
          "scope_ref": {
            "type": "string",
            "nullable": true,
            "description": "Public id of the api key / agent / actor the quota applies to. For `api_key` and `agent` scope, NULL means all entities of that scope type in the project. For `actor` scope, NULL means one budget *per* actor rather than a pooled total across all actors."
          },
          "metric": {
            "type": "string",
            "enum": [
              "requests",
              "tokens",
              "cost_usd"
            ],
            "description": "The metric being capped"
          },
          "window": {
            "type": "string",
            "enum": [
              "rolling_1m",
              "rolling_1h",
              "rolling_24h",
              "calendar_month"
            ],
            "description": "The window over which the metric is aggregated"
          },
          "limit": {
            "type": "number",
            "description": "The cap. Positive integer for requests/tokens; fractional allowed for cost_usd."
          },
          "mode": {
            "type": "string",
            "enum": [
              "enforce",
              "monitor"
            ],
            "description": "enforce blocks with 429; monitor fires the webhook only"
          },
          "on_unpriced": {
            "type": "string",
            "enum": [
              "block",
              "allow"
            ],
            "description": "Only for metric cost_usd. What an enforce quota does over a pricing blackout — block (the default) refuses generations with 409 QUOTA_UNENFORCEABLE, allow accepts the unmeasurable spend. See the quotas REST contract."
          }
        }
      },
      "GuardrailResourceProperties": {
        "description": "Creates a guardrail — an action-class document (`class`/`guard`) that gates tool-call autonomy. Attach it to a tool or agent via that resource's `guardrail_ids` (a `{ \"ref\": … }` to this resource in the same template resolves to its physical id at deploy time). Mirrors the guardrails REST contract; `class`/`default_class`/`guard`/`escalate` are flattened here from the REST API's single `document` object.",
        "type": "object",
        "additionalProperties": false,
        "required": [
          "name",
          "class"
        ],
        "properties": {
          "name": {
            "type": "string",
            "description": "Human-readable name"
          },
          "description": {
            "type": "string",
            "nullable": true,
            "description": "Optional description"
          },
          "class": {
            "description": "A class literal (`A` / `B` / `C` / `D`) or a JSON Logic expression returning one. An invalid result resolves to `default_class`.",
            "oneOf": [
              {
                "type": "string",
                "enum": [
                  "A",
                  "B",
                  "C",
                  "D"
                ]
              },
              {
                "type": "object"
              }
            ]
          },
          "default_class": {
            "type": "string",
            "enum": [
              "A",
              "B",
              "C",
              "D"
            ],
            "description": "Applied when the `class` expression returns anything other than a valid class. Defaults to `C` (fail-closed)."
          },
          "guard": {
            "type": "object",
            "nullable": true,
            "description": "A single JSON Logic expression; when the call classifies as `B` it executes only if this evaluates truthy."
          },
          "escalate": {
            "type": "boolean",
            "nullable": true,
            "description": "When true, a passing guard still files an approval item."
          },
          "context_tool_id": {
            "x-soat-ref": "tools",
            "type": "string",
            "nullable": true,
            "description": "Optional tool the platform calls at evaluation time to fetch fresh guardrail context."
          },
          "context_mode": {
            "type": "string",
            "nullable": true,
            "enum": [
              "merge",
              "replace",
              null
            ],
            "description": "How tool-fetched context combines with the caller-supplied context."
          }
        }
      },
      "ResourceDeclaration": {
        "type": "object",
        "required": [
          "type",
          "properties"
        ],
        "properties": {
          "type": {
            "type": "string",
            "pattern": "^[a-z][a-z0-9_]*$",
            "description": "Resource type. The built-in types are `ai_provider`, `tool`, `agent`, `actor`, `api_key`, `chat`, `conversation`, `dataset`, `dataset_item`, `document`, `file`, `guardrail`, `ingestion_rule`, `memory`, `memory_entry`, `model_route`, `eval`, `orchestration`, `policy`, `project_price`, `quota`, `secret`, `session`, `webhook`, `trigger` and `workflow`.\n\nThis is deliberately not an enum: a deployment operator can register additional resource types backed by their own handler, and those are declared here exactly like a built-in one. The set a given deployment accepts is authoritative in the server, which rejects an unregistered type with `VALIDATION_FAILED` and lists what it does support.\n"
          },
          "properties": {
            "type": "object",
            "additionalProperties": true,
            "description": "Resource properties, as authored in the template and echoed back verbatim. The allowed fields, required fields, and field types for each resource `type` are defined by the corresponding `<Type>ResourceProperties` schema in this document (e.g. `model_route` → `ModelRouteResourceProperties`), which the server enforces at validate/deploy time. The declaration itself is free-form here because property values may be substitution expressions rather than final values: `{ \"ref\": \"logicalId\" }` references another resource's physical ID, `{ \"param\": \"ParamName\" }` substitutes a parameter value, and `{ \"sub\": \"text ${ParamName}\" }` interpolates parameters into a string.\n"
          },
          "depends_on": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Explicit dependency list. In addition to implicit `ref` dependencies.",
            "nullable": true
          },
          "deletion_policy": {
            "type": "string",
            "enum": [
              "delete",
              "retain"
            ],
            "description": "Controls what happens to the physical resource when it is removed from the stack. `delete` (default) deletes the physical resource. `retain` keeps the physical resource alive and only removes the formation record. Omit it to get `delete`; an explicit `null` is rejected.\n"
          },
          "metadata": {
            "type": "object",
            "additionalProperties": true,
            "nullable": true
          }
        }
      },
      "FormationResource": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "Public ID of the resource record"
          },
          "logical_id": {
            "type": "string",
            "description": "Logical identifier from the template"
          },
          "resource_type": {
            "type": "string",
            "description": "Resource type (e.g. agent, memory)"
          },
          "physical_resource_id": {
            "type": "string",
            "nullable": true,
            "description": "Public ID of the physical SOAT resource"
          },
          "status": {
            "type": "string",
            "enum": [
              "pending",
              "created",
              "updated",
              "deleted",
              "failed"
            ],
            "description": "Current resource status"
          }
        }
      },
      "Formation": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "Public ID of the formation",
            "example": "form_V1StGXR8Z5jdHi6B"
          },
          "project_id": {
            "x-soat-ref": "projects",
            "type": "string",
            "description": "Project public ID"
          },
          "name": {
            "type": "string",
            "description": "Human-readable formation name"
          },
          "template": {
            "$ref": "#/components/schemas/FormationTemplate"
          },
          "outputs": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            },
            "nullable": true,
            "description": "Resolved output values after stack deployment"
          },
          "status": {
            "type": "string",
            "enum": [
              "creating",
              "active",
              "updating",
              "failed",
              "deleting",
              "deleted",
              "delete_failed"
            ],
            "description": "Formation status"
          },
          "metadata": {
            "type": "object",
            "additionalProperties": true,
            "nullable": true,
            "description": "Static annotations stored on the formation record (supplied at create/update). Not a substitution site — `sub`/`param`/`ref` expressions are rejected. Use the template's top-level `metadata` block for deploy-time substitution (see `resolved_metadata`).\n"
          },
          "resolved_metadata": {
            "type": "object",
            "additionalProperties": true,
            "nullable": true,
            "description": "The template's top-level `metadata` block after parameter (`sub`/`param`) and resource (`ref`) substitution at the last deploy. Null when the template declares no metadata.\n"
          },
          "resolved_parameters": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            },
            "nullable": true,
            "description": "Parameter values applied at the last deploy, for auditability. `no_echo` parameters are masked (`***`). Null when the template declares no parameters.\n"
          },
          "error": {
            "allOf": [
              {
                "$ref": "#/components/schemas/FormationError"
              }
            ],
            "nullable": true,
            "description": "Why the formation is `failed` or `delete_failed`, in the same `{ code, message, meta }` shape as an error response. Null in every other status, and cleared by the next successful deploy. This is the reason a `2xx` deploy response can report `status: \"failed\"` without a second call to `list-formation-events`.\n\nOne case carries an error while the formation is `active`: `FORMATION_REPLACE_CLEANUP_FAILED`, when a deploy replaced a resource and the superseded one could not be deleted. The desired state is realised, so the deploy succeeded — but the old resource is still live, and `meta.failures` names it. It stays on the formation as pending cleanup and is retried on the next deploy or teardown, which clears the error once it is gone.\n"
          },
          "resources": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/FormationResource"
            },
            "description": "Resources managed by this formation (present on get/create/update)"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "ValidationError": {
        "type": "object",
        "properties": {
          "path": {
            "type": "string",
            "description": "Location of the issue (e.g. nodes[1].input_mapping.val)."
          },
          "message": {
            "type": "string",
            "description": "Human-readable description of the issue."
          }
        }
      },
      "ValidationResult": {
        "type": "object",
        "required": [
          "valid",
          "errors",
          "warnings"
        ],
        "properties": {
          "valid": {
            "type": "boolean",
            "description": "True when there are no blocking errors."
          },
          "errors": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ValidationError"
            }
          },
          "warnings": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ValidationError"
            }
          }
        }
      },
      "PlanChange": {
        "type": "object",
        "properties": {
          "logical_id": {
            "type": "string"
          },
          "resource_type": {
            "type": "string"
          },
          "action": {
            "type": "string",
            "enum": [
              "create",
              "update",
              "delete",
              "no-op"
            ]
          },
          "physical_resource_id": {
            "type": "string",
            "description": "The existing resource's physical ID. Present for update / no-op / delete actions, absent for create."
          },
          "diff": {
            "type": "object",
            "description": "Resolved desired-state properties (post parameter/ref substitution) and, when available, the current live or last-applied properties they were compared against. Omitted when neither side could be computed (e.g. an unregistered resource type).",
            "properties": {
              "desired": {
                "type": "object",
                "additionalProperties": true
              },
              "current": {
                "type": "object",
                "additionalProperties": true,
                "nullable": true
              }
            }
          }
        }
      },
      "PlanResult": {
        "type": "object",
        "properties": {
          "changes": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/PlanChange"
            }
          },
          "unauthorized_actions": {
            "type": "array",
            "description": "The per-resource actions the caller may not perform. A formation may only do what the caller could do directly, so applying this template would be refused while any of these remain. Absent when the caller may perform every action the plan implies. A plan itself changes nothing, so it reports them rather than failing.",
            "items": {
              "$ref": "#/components/schemas/UnauthorizedFormationAction"
            }
          }
        }
      },
      "UnauthorizedFormationAction": {
        "type": "object",
        "required": [
          "logical_id",
          "resource_type",
          "action"
        ],
        "properties": {
          "logical_id": {
            "type": "string",
            "description": "The template's own name for the resource.",
            "example": "MyGuardrail"
          },
          "resource_type": {
            "type": "string",
            "description": "The declared resource type.",
            "example": "guardrail"
          },
          "action": {
            "type": "string",
            "description": "The action the caller lacks.",
            "example": "guardrails:CreateGuardrail"
          }
        }
      },
      "FormationError": {
        "type": "object",
        "description": "Why a deploy or teardown failed, in the one error shape the API has. Carried on the formation itself and on the operation that failed.",
        "required": [
          "code",
          "message"
        ],
        "properties": {
          "code": {
            "type": "string",
            "description": "The failing operation's error code (`VALIDATION_FAILED`, `RESOURCE_NOT_FOUND`, `FORMATION_DELETE_FAILED`, `FORMATION_REPLACE_CLEANUP_FAILED`, …), or `UNKNOWN` when the underlying failure carried no code.",
            "example": "VALIDATION_FAILED"
          },
          "message": {
            "type": "string",
            "description": "The failure, as reported by the resource that raised it.",
            "example": "dataset_id is immutable: item 'dsit_V1StGXR8Z5jdHi6B' belongs to 'dset_V1StGXR8Z5jdHi6B'. Declare a new dataset_item instead."
          },
          "meta": {
            "type": "object",
            "additionalProperties": true,
            "description": "Context for the failure. A failed apply names the resource that broke it (`logical_id`, `resource_type`); a failed teardown lists every blocker under `failures`, and so does a succeeded deploy that could not dispose of a replaced resource — there each entry adds the `physical_resource_id` still live.",
            "example": {
              "logical_id": "case1",
              "resource_type": "dataset_item"
            }
          }
        }
      },
      "FormationEvent": {
        "type": "object",
        "properties": {
          "timestamp": {
            "type": "string",
            "format": "date-time"
          },
          "logical_id": {
            "type": "string"
          },
          "resource_type": {
            "type": "string"
          },
          "action": {
            "type": "string",
            "description": "What the deploy did to the resource: `create`, `update`, `delete`, `no-op`, `rollback` (a resource created earlier in this deploy that was walked back after a later failure), or `rollback-skipped` (a `deletion_policy: retain` resource left standing by that unwind).",
            "example": "rollback"
          },
          "status": {
            "type": "string",
            "enum": [
              "succeeded",
              "failed"
            ]
          },
          "physical_resource_id": {
            "type": "string",
            "nullable": true
          },
          "error": {
            "type": "string",
            "nullable": true
          }
        }
      },
      "FormationOperation": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "Public ID of the operation"
          },
          "operation_type": {
            "type": "string",
            "enum": [
              "validate",
              "plan",
              "create",
              "update",
              "delete"
            ]
          },
          "status": {
            "type": "string",
            "enum": [
              "pending",
              "running",
              "succeeded",
              "failed"
            ]
          },
          "events": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/FormationEvent"
            },
            "nullable": true
          },
          "plan": {
            "allOf": [
              {
                "$ref": "#/components/schemas/PlanResult"
              }
            ],
            "nullable": true
          },
          "error": {
            "allOf": [
              {
                "$ref": "#/components/schemas/FormationError"
              }
            ],
            "nullable": true,
            "description": "Why this operation failed. Null for a succeeded or running operation. The same bag the formation itself carries while that failure is its current state."
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "Generation": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "Public ID of the generation",
            "example": "gen_V1StGXR8Z5jdHi6B"
          },
          "project_id": {
            "x-soat-ref": "projects",
            "type": "string",
            "description": "Public ID of the project"
          },
          "agent_id": {
            "x-soat-ref": "agents",
            "type": "string",
            "description": "Public ID of the agent that ran this generation"
          },
          "trace_id": {
            "x-soat-ref": "traces",
            "type": "string",
            "description": "Public ID of the trace this generation belongs to"
          },
          "initiator_generation_id": {
            "x-soat-ref": "generations",
            "type": "string",
            "nullable": true,
            "description": "Public ID of the generation that triggered this one. Set for sub-agent invocations. Null for top-level generations.\n"
          },
          "chain_id": {
            "x-soat-ref": "chains",
            "type": "string",
            "nullable": true,
            "description": "Public ID of the continuation chain this generation belongs to. Set on every member of a chain — the continuations and the root they descend from — and null on a generation that is not part of one.\n"
          },
          "started_by_principal_type": {
            "type": "string",
            "nullable": true,
            "description": "Type of the principal that started the generation"
          },
          "started_by_principal_id": {
            "type": "string",
            "nullable": true,
            "description": "ID of the principal that started the generation"
          },
          "status": {
            "type": "string",
            "description": "Lifecycle status of the generation",
            "enum": [
              "in_progress",
              "requires_action",
              "completed",
              "failed"
            ],
            "example": "failed"
          },
          "started_at": {
            "type": "string",
            "format": "date-time"
          },
          "completed_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true,
            "description": "When the generation reached a terminal state"
          },
          "last_activity_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "stop_reason": {
            "type": "string",
            "nullable": true,
            "description": "Why the generation stopped. Either the model provider's own finish reason relayed unchanged ('stop', 'tool-calls', 'length', …) or one the platform names itself: 'max_steps' when the turn spent its whole step budget on tool calls, 'depth_guard' when a nested call exceeded the call depth, 'chain_limit' when a continuation chain reached its generation budget, or 'error' when the turn failed.\n",
            "example": "error"
          },
          "error": {
            "type": "object",
            "nullable": true,
            "description": "Structured error payload recorded when the generation failed. Contains at least `message`; `code` is set for mapped errors (e.g. AI_PROVIDER_ERROR for upstream provider failures).\n",
            "properties": {
              "code": {
                "type": "string",
                "example": "AI_PROVIDER_ERROR"
              },
              "message": {
                "type": "string",
                "example": "Provider returned 402: insufficient credits"
              },
              "meta": {
                "type": "object"
              }
            }
          },
          "action_id": {
            "type": "string",
            "nullable": true,
            "description": "Logical action label supplied on the generate request. Recorded on the generation's usage event for per-action spend rollups.\n"
          },
          "trigger_id": {
            "x-soat-ref": "triggers",
            "type": "string",
            "nullable": true,
            "description": "Trigger that initiated the generation, when applicable"
          },
          "orchestration_run_id": {
            "x-soat-ref": "orchestration-runs",
            "type": "string",
            "nullable": true,
            "description": "Orchestration run that dispatched the generation. Null for a standalone generation.\n"
          },
          "node_id": {
            "type": "string",
            "nullable": true,
            "description": "Node within `orchestration_run_id` that dispatched the generation. Together with the run it forms the usage event's replay identity.\n"
          },
          "node_attempt": {
            "type": "integer",
            "nullable": true,
            "description": "The node's 1-based retry attempt, completing the run + node + attempt replay identity. A retried node produces one generation per attempt; this is what tells them apart. Null for a generation no orchestration node dispatched.\n"
          },
          "agent_version": {
            "type": "integer",
            "nullable": true,
            "description": "Agent config version that served this generation, resolved by the served-version resolver (see [agent versions](/docs/modules/agents#versions-and-releases)).\n"
          },
          "extraction": {
            "type": "object",
            "nullable": true,
            "description": "Memory-extraction summary recorded for this generation. Set when the agent's `knowledge_config.extraction` produced one for this turn.\n",
            "properties": {
              "candidates": {
                "type": "integer",
                "description": "Number of extraction candidates considered"
              },
              "created": {
                "type": "integer",
                "description": "Number of new memory entries created"
              },
              "updated": {
                "type": "integer",
                "description": "Number of existing memory entries updated"
              },
              "skipped": {
                "type": "integer",
                "description": "Number of candidates skipped (e.g. duplicates)"
              }
            }
          },
          "routing": {
            "type": "object",
            "nullable": true,
            "description": "What the model route did for this generation. Present only when the agent resolves its model through a `model_route_id`.\n",
            "properties": {
              "route_id": {
                "x-soat-ref": "model-routes",
                "type": "string",
                "description": "The route that resolved the model"
              },
              "target_index": {
                "type": "integer",
                "nullable": true,
                "description": "Position in the route's `targets` of the target that served the last LLM call; null when every attempt failed.\n"
              },
              "fallbacks": {
                "type": "integer",
                "description": "How many times the route moved past a target during this generation (cumulative across a multi-step run).\n"
              },
              "attempts": {
                "type": "array",
                "description": "Every attempt, in order, across every LLM call of the run. An attempt with no `error_class` succeeded.\n",
                "items": {
                  "type": "object",
                  "properties": {
                    "target_index": {
                      "type": "integer"
                    },
                    "ai_provider_id": {
                      "x-soat-ref": "ai-providers",
                      "type": "string"
                    },
                    "model": {
                      "type": "string"
                    },
                    "error_class": {
                      "type": "string",
                      "enum": [
                        "provider_error",
                        "timeout",
                        "rate_limited"
                      ],
                      "description": "Why the attempt failed. Absent on the serving attempt, and absent on a deterministic failure (which fails the generation instead of failing over).\n"
                    }
                  }
                }
              }
            }
          },
          "metadata": {
            "type": "object",
            "nullable": true,
            "additionalProperties": true,
            "description": "Caller-owned key/value annotations, attached at create time (via the `metadata` field on the create-agent-generation request) or afterwards (via the update-generation request), and returned verbatim. The server writes nothing here: every piece of state it owns — usage attribution, the served agent version, the route's record, the extraction summary, internal recovery state — is a field of its own, so no key written here can reach platform state. Keys are never transformed, and no key is reserved.\n",
            "example": {
              "team": "payments",
              "ticket_id": "OPS-4821"
            }
          },
          "content_redacted_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true,
            "description": "When the generation's content was purged. Non-null means `metadata`, `error`, `extraction` and the internal recovery state have been cleared, while the usage/audit skeleton — ids, timestamps, status, stop reason and the attribution fields — is preserved.\n"
          },
          "content_redacted_by_principal_type": {
            "type": "string",
            "nullable": true,
            "description": "Principal kind that purged the content ('user' or 'api_key')",
            "example": "user"
          },
          "content_redacted_by_principal_id": {
            "type": "string",
            "nullable": true,
            "description": "Public ID of the principal that purged the content — the API key's own id for key auth, so the record names which key acted.\n"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "UpdateGenerationRequest": {
        "type": "object",
        "required": [
          "metadata"
        ],
        "properties": {
          "metadata": {
            "type": "object",
            "additionalProperties": true,
            "description": "Caller-supplied key/value metadata to shallow-merge into the generation record's caller-owned `metadata` bag. No key is reserved: server-owned state lives in its own top-level fields and cannot be written from here.\n",
            "example": {
              "team": "payments",
              "ticket_id": "OPS-4821"
            }
          }
        }
      },
      "TranscriptUsage": {
        "type": "object",
        "description": "Token counts for one step. A field is null when the provider did not report it — null rather than 0, so \"not reported\" stays distinguishable from \"none used\".\n",
        "properties": {
          "input_tokens": {
            "type": "integer",
            "nullable": true
          },
          "output_tokens": {
            "type": "integer",
            "nullable": true
          },
          "total_tokens": {
            "type": "integer",
            "nullable": true
          }
        }
      },
      "TranscriptToolCall": {
        "type": "object",
        "description": "One tool call the model made during a step.",
        "properties": {
          "id": {
            "type": "string",
            "nullable": true,
            "description": "The call's ID, as the model provider issued it (e.g. `call_…`), used to correlate it with an entry in `tool_results`. Null when the stored step did not record one.\n"
          },
          "tool_name": {
            "type": "string",
            "nullable": true,
            "example": "weather"
          },
          "args": {
            "description": "The arguments the model supplied, as a value. This payload is tool-owned: its keys are passed through exactly as they were recorded and are never inspected or rewritten by SOAT.\n",
            "nullable": true,
            "example": {
              "cityName": "Paris"
            }
          }
        }
      },
      "TranscriptToolResult": {
        "type": "object",
        "description": "One tool's answer to a call in the same step. A call that failed is reported here too, with `result` null and `error` set — so a reader sees successes and failures in one ordered list keyed by the call they answer.\n",
        "properties": {
          "tool_call_id": {
            "type": "string",
            "nullable": true,
            "description": "The `id` of the `tool_calls` entry this answers."
          },
          "tool_name": {
            "type": "string",
            "nullable": true,
            "example": "weather"
          },
          "result": {
            "description": "What the tool returned, as a value. Tool-owned: keys are passed through verbatim. Null when the call errored.\n",
            "nullable": true,
            "example": {
              "tempC": 18
            }
          },
          "error": {
            "description": "The tool's failure, when the step recorded one.",
            "nullable": true,
            "example": null
          }
        }
      },
      "TranscriptStep": {
        "type": "object",
        "description": "One model step. Projected from the stored step at read time — the stored shape is provider- and SDK-specific and is never put on the wire.\n",
        "properties": {
          "index": {
            "type": "integer",
            "description": "Zero-based position of this step in the turn. Positional rather than the model's own step number, which restarts at zero when a paused turn resumes.\n",
            "example": 0
          },
          "text": {
            "type": "string",
            "description": "The text this step produced. Empty for a step that only called tools.\n",
            "example": ""
          },
          "finish_reason": {
            "type": "string",
            "nullable": true,
            "description": "Why this step stopped.",
            "example": "tool-calls"
          },
          "tool_calls": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/TranscriptToolCall"
            }
          },
          "tool_results": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/TranscriptToolResult"
            }
          },
          "usage": {
            "allOf": [
              {
                "$ref": "#/components/schemas/TranscriptUsage"
              }
            ],
            "nullable": true,
            "description": "Null when the step recorded no usage."
          }
        }
      },
      "GenerationTranscript": {
        "type": "object",
        "description": "One generation's turn, read back step by step. Assembled at read time from the generation record and the trace's steps object — never stored, so it dies with the content it projects.\n",
        "properties": {
          "generation_id": {
            "x-soat-ref": "generations",
            "type": "string",
            "example": "gen_V1StGXR8Z5jdHi6B"
          },
          "trace_id": {
            "x-soat-ref": "traces",
            "type": "string",
            "nullable": true,
            "example": "trace_V1StGXR8Z5jdHi6B"
          },
          "project_id": {
            "x-soat-ref": "projects",
            "type": "string"
          },
          "agent_id": {
            "x-soat-ref": "agents",
            "type": "string"
          },
          "agent_version": {
            "type": "integer",
            "nullable": true,
            "description": "Agent config version that served the turn."
          },
          "status": {
            "type": "string",
            "description": "Lifecycle status of the generation. Disambiguates an empty `steps` caused by a run still in flight from one caused by erased content.\n",
            "enum": [
              "in_progress",
              "requires_action",
              "completed",
              "failed"
            ],
            "example": "completed"
          },
          "stop_reason": {
            "type": "string",
            "nullable": true,
            "description": "Why the generation stopped — the provider's finish reason, or one of the platform's own ('max_steps', 'depth_guard', 'chain_limit', 'error').\n",
            "example": "stop"
          },
          "started_at": {
            "type": "string",
            "format": "date-time"
          },
          "completed_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "step_count": {
            "type": "integer",
            "description": "Number of steps this turn recorded. A counter rather than content, so it survives a purge and still reports the size of a turn whose steps are gone. Scoped to the generation, not the trace: a trace that groups several generations counts them all in its own `step_count`, while each transcript reports only its own.\n",
            "example": 2
          },
          "input": {
            "type": "array",
            "nullable": true,
            "description": "The messages the turn was asked, as recorded. Message content is caller-owned and passed through verbatim. Null when the content was never stored or has been purged.\n",
            "items": {
              "type": "object"
            }
          },
          "steps": {
            "type": "array",
            "description": "The turn's steps in order. Empty for a run still in progress, and for one whose content is unavailable — `status` and `content_redacted_at` say which.\n",
            "items": {
              "$ref": "#/components/schemas/TranscriptStep"
            }
          },
          "output": {
            "type": "object",
            "nullable": true,
            "description": "The turn's final answer. Null when there are no steps to derive it from.\n",
            "properties": {
              "content": {
                "type": "string",
                "nullable": true,
                "description": "The last step that produced text. Null for a turn that only called tools.\n",
                "example": "It's 18°C in Paris right now."
              },
              "finish_reason": {
                "type": "string",
                "nullable": true,
                "description": "The finish reason of the actual last step.",
                "example": "stop"
              }
            }
          },
          "error": {
            "type": "object",
            "nullable": true,
            "description": "Structured error payload when the generation failed."
          },
          "content_redacted_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true,
            "description": "When the generation's content was erased; null while it is intact.\n"
          },
          "content_redacted_by_principal_type": {
            "type": "string",
            "nullable": true,
            "description": "Principal kind that erased the content.",
            "example": "system"
          },
          "content_redacted_by_principal_id": {
            "type": "string",
            "nullable": true,
            "description": "Public ID of that principal. `zero_retention` when the content was never stored, distinguishing it from content erased later.\n"
          }
        }
      },
      "GuardrailDocument": {
        "type": "object",
        "required": [
          "class"
        ],
        "additionalProperties": true,
        "description": "The action-class document. `class` maps a call to an action class; `guard` gates class-B autonomy. Both are single JSON Logic expressions over the `args.*` / `context.*` / `runtime.*` namespaces.\n",
        "properties": {
          "class": {
            "description": "A class literal (`A` / `B` / `C` / `D`) or a JSON Logic expression returning one. An invalid result resolves to `default_class`.\n",
            "oneOf": [
              {
                "type": "string",
                "enum": [
                  "A",
                  "B",
                  "C",
                  "D"
                ]
              },
              {
                "type": "object"
              }
            ]
          },
          "default_class": {
            "type": "string",
            "enum": [
              "A",
              "B",
              "C",
              "D"
            ],
            "description": "Applied when the `class` expression returns anything other than a valid class. Defaults to `C` (fail-closed).\n"
          },
          "guard": {
            "type": "object",
            "description": "A single JSON Logic expression; when the call classifies as `B` it must evaluate truthy to execute autonomously. Compose multiple conditions with `{ \"and\": [...] }`.\n"
          },
          "escalate": {
            "type": "boolean",
            "description": "When `true`, a failing guard routes to approval instead of tripping fail-closed.\n"
          },
          "expires_in": {
            "type": "integer",
            "minimum": 1,
            "description": "Default approval window in seconds for a class-C approval this guardrail files. Omitted → the platform's 24h default. When several guardrails apply, the governing (strictest-matching) one's value is used.\n"
          }
        }
      },
      "Guardrail": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "Public ID of the guardrail",
            "example": "guard_V1StGXR8Z5jdHi6B"
          },
          "project_id": {
            "x-soat-ref": "projects",
            "type": "string",
            "description": "Public ID of the owning project",
            "example": "proj_V1StGXR8Z5jdHi6B"
          },
          "name": {
            "type": "string",
            "description": "Human-readable name",
            "example": "Budget Update Guardrail"
          },
          "description": {
            "type": "string",
            "nullable": true,
            "description": "Optional description"
          },
          "version": {
            "type": "integer",
            "description": "Incremented on every document write; prior versions are archived",
            "example": 1
          },
          "document": {
            "$ref": "#/components/schemas/GuardrailDocument"
          },
          "context_tool_id": {
            "x-soat-ref": "tools",
            "type": "string",
            "nullable": true,
            "description": "Optional tool the platform calls at evaluation time to fetch fresh guardrail context.\n"
          },
          "context_mode": {
            "type": "string",
            "nullable": true,
            "enum": [
              "merge",
              "replace",
              null
            ],
            "description": "How tool-fetched context combines with the caller-supplied context.\n"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "GuardrailEvaluation": {
        "type": "object",
        "description": "The record produced by evaluating a guardrail against one call — written to the audit trail at dispatch time (one per applying guardrail) and returned verbatim by the dry-run endpoint.\n",
        "properties": {
          "kind": {
            "type": "string",
            "description": "Always `guardrail_evaluation`."
          },
          "guardrail_id": {
            "x-soat-ref": "guardrails",
            "type": "string",
            "example": "guard_V1StGXR8Z5jdHi6B"
          },
          "guardrail_version": {
            "type": "integer",
            "nullable": true,
            "description": "The governing version; null for a dangling reference (fail-closed C)."
          },
          "scope": {
            "type": "string",
            "enum": [
              "project",
              "agent",
              "tool"
            ]
          },
          "tool": {
            "type": "string",
            "nullable": true
          },
          "action": {
            "type": "string",
            "nullable": true
          },
          "class": {
            "type": "string",
            "description": "The resolved class (or the applied default_class).",
            "enum": [
              "A",
              "B",
              "C",
              "D"
            ]
          },
          "decision": {
            "type": "string",
            "enum": [
              "execute",
              "route_to_approval",
              "blocked",
              "tripwire"
            ]
          },
          "guard_result": {
            "type": "boolean",
            "nullable": true,
            "description": "The guard outcome; null when the call did not classify as B."
          },
          "context_source": {
            "type": "string",
            "enum": [
              "caller",
              "tool",
              "merged",
              "none"
            ]
          },
          "context_snapshot": {
            "type": "object",
            "additionalProperties": true,
            "description": "Flat map of only the vars the class/guard expressions referenced, keyed by fully-qualified path, frozen at evaluation-time values.\n"
          },
          "agent_id": {
            "type": "string",
            "nullable": true
          },
          "orchestration_run_id": {
            "type": "string",
            "nullable": true
          },
          "generation_id": {
            "type": "string",
            "nullable": true
          }
        }
      },
      "GuardrailVersion": {
        "type": "object",
        "description": "An immutable archive of a guardrail's configuration at one version.",
        "properties": {
          "id": {
            "type": "string",
            "description": "Public ID of the archived version",
            "example": "guard_ver_V1StGXR8Z5jdHi6B"
          },
          "guardrail_id": {
            "x-soat-ref": "guardrails",
            "type": "string",
            "description": "Public ID of the guardrail this version belongs to",
            "example": "guard_V1StGXR8Z5jdHi6B"
          },
          "version": {
            "type": "integer",
            "description": "The archived version number",
            "example": 1
          },
          "config": {
            "type": "object",
            "additionalProperties": true,
            "description": "The guardrail's versioned surface as it stood at this version. Today that is the policy `document` and nothing else: name, description and the context binding are metadata, and bumping the version when one of them changes would make two version numbers denote the same policy — which is exactly what an evaluation record cites.\n\nDeliberately open rather than a fixed schema: an archive written by an earlier release of SOAT reflects the guardrail surface **of its own time**, so it may carry fields the current API no longer documents.",
            "properties": {
              "document": {
                "$ref": "#/components/schemas/GuardrailDocument"
              }
            }
          },
          "label": {
            "type": "string",
            "nullable": true,
            "description": "Optional human tag for this version, e.g. `pre-tightening`. Set from the `label` field of a restore, or generated for one.",
            "example": "restored from v2"
          },
          "created_by": {
            "x-soat-ref": "users",
            "type": "string",
            "nullable": true,
            "description": "Public ID of the user whose action produced this version. Null for writes with no request user behind them."
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "RestoreGuardrailVersionRequest": {
        "type": "object",
        "properties": {
          "label": {
            "type": "string",
            "description": "Optional tag for the version the restore creates. Defaults to `restored from v<version>`.",
            "example": "rollback to pre-incident policy"
          }
        }
      },
      "CreateGuardrailRequest": {
        "type": "object",
        "required": [
          "name",
          "document"
        ],
        "properties": {
          "project_id": {
            "x-soat-ref": "projects",
            "type": "string",
            "description": "Public ID of the project"
          },
          "name": {
            "type": "string",
            "description": "Human-readable name",
            "example": "Budget Update Guardrail"
          },
          "description": {
            "type": "string",
            "nullable": true
          },
          "document": {
            "$ref": "#/components/schemas/GuardrailDocument"
          },
          "context_tool_id": {
            "x-soat-ref": "tools",
            "type": "string",
            "nullable": true
          },
          "context_mode": {
            "type": "string",
            "enum": [
              "merge",
              "replace"
            ]
          },
          "version_label": {
            "type": "string",
            "description": "Optional tag for the config version this write archives (e.g. `initial`). Annotates the version only — it is not stored on the guardrail and is not part of the config, so labelling a change is never itself a change.",
            "example": "initial"
          }
        }
      },
      "UpdateGuardrailRequest": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string"
          },
          "description": {
            "type": "string",
            "nullable": true
          },
          "document": {
            "$ref": "#/components/schemas/GuardrailDocument"
          },
          "context_tool_id": {
            "x-soat-ref": "tools",
            "type": "string",
            "nullable": true
          },
          "context_mode": {
            "type": "string",
            "nullable": true,
            "enum": [
              "merge",
              "replace",
              null
            ]
          },
          "version_label": {
            "type": "string",
            "description": "Optional tag for the config version this write archives (e.g. `pre-tightening`). Annotates the version only — it is not stored on the guardrail and is not part of the config, so labelling a change is never itself a change. Ignored when the write changes no policy, since no version is created.",
            "example": "pre-tightening"
          }
        }
      },
      "IngestionRule": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "example": "igr_V1StGXR8Z5jdHi6B"
          },
          "project_id": {
            "x-soat-ref": "projects",
            "type": "string"
          },
          "content_type_glob": {
            "type": "string",
            "example": "image/*"
          },
          "tool_id": {
            "x-soat-ref": "tools",
            "type": "string",
            "nullable": true
          },
          "agent_id": {
            "x-soat-ref": "agents",
            "type": "string",
            "nullable": true
          },
          "action": {
            "type": "string",
            "nullable": true
          },
          "preset_parameters": {
            "type": "object",
            "nullable": true
          },
          "native_extraction": {
            "type": "string",
            "enum": [
              "first",
              "skip"
            ]
          },
          "file_delivery": {
            "type": "string",
            "enum": [
              "base64",
              "download_url"
            ]
          },
          "chunk_strategy": {
            "type": "string",
            "nullable": true
          },
          "chunk_size": {
            "type": "integer",
            "nullable": true
          },
          "chunk_overlap": {
            "type": "integer",
            "nullable": true
          },
          "metadata": {
            "type": "object",
            "nullable": true
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "KnowledgeResult": {
        "oneOf": [
          {
            "$ref": "#/components/schemas/DocumentKnowledgeResult"
          },
          {
            "$ref": "#/components/schemas/MemoryKnowledgeResult"
          }
        ],
        "discriminator": {
          "propertyName": "source_type",
          "mapping": {
            "document": "#/components/schemas/DocumentKnowledgeResult",
            "memory": "#/components/schemas/MemoryKnowledgeResult"
          }
        }
      },
      "DocumentKnowledgeResult": {
        "type": "object",
        "required": [
          "source_type",
          "document_id",
          "content",
          "created_at",
          "updated_at"
        ],
        "properties": {
          "source_type": {
            "type": "string",
            "enum": [
              "document"
            ],
            "description": "The type of knowledge source this result comes from",
            "example": "document"
          },
          "document_id": {
            "x-soat-ref": "documents",
            "type": "string",
            "description": "Public ID of the document",
            "example": "doc_V1StGXR8Z5jdHi6B"
          },
          "chunk_id": {
            "type": "string",
            "description": "Public ID of the document chunk that matched the query",
            "example": "dchunk_V1StGXR8Z5jdHi6B"
          },
          "page": {
            "type": "integer",
            "nullable": true,
            "description": "Page number within the source PDF (1-indexed). Null for plain-text documents.",
            "example": 3
          },
          "file_id": {
            "x-soat-ref": "files",
            "type": "string",
            "description": "Public ID of the underlying file",
            "example": "file_V1StGXR8Z5jdHi6B"
          },
          "project_id": {
            "x-soat-ref": "projects",
            "type": "string",
            "description": "Public ID of the project the document belongs to",
            "example": "proj_V1StGXR8Z5jdHi6B"
          },
          "path": {
            "type": "string",
            "description": "Logical path of the file within the project",
            "example": "/sales/policies.txt"
          },
          "filename": {
            "type": "string",
            "description": "Filename of the underlying file",
            "example": "policies.txt"
          },
          "size": {
            "type": "integer",
            "description": "File size in bytes",
            "example": 1024
          },
          "title": {
            "type": "string",
            "description": "Document title",
            "example": "Sales Communication Policy"
          },
          "metadata": {
            "type": "object",
            "description": "Arbitrary metadata attached to the document, returned verbatim in the casing it was written with at create/update time (e.g. a key written as `strapiDocumentId` is returned as `strapiDocumentId`, not `strapi_document_id`) — it is not converted between snake_case and camelCase like other response fields."
          },
          "tags": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            },
            "description": "Key-value tags",
            "example": {
              "department": "sales"
            }
          },
          "content": {
            "type": "string",
            "nullable": true,
            "description": "Full text content of the document"
          },
          "score": {
            "type": "number",
            "description": "Implementation-defined relevance ranking — higher is better. The **ordering** it produces is the contract; the absolute value is not, and the formula behind it may change (a future hybrid ranking would fuse several signals here). It is the field `min_score` filters on and the field results are sorted by. Only present when `query` was provided. Use `similarity_score` when you need the raw cosine value.",
            "example": 0.82
          },
          "similarity_score": {
            "type": "number",
            "description": "Raw cosine similarity (0–1) between the query and this result. Pinned to that meaning — unlike `score`, it is never redefined. Only present when `query` was provided.",
            "minimum": 0,
            "maximum": 1,
            "example": 0.82
          },
          "created_at": {
            "type": "string",
            "format": "date-time",
            "description": "Creation timestamp"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time",
            "description": "Last updated timestamp"
          }
        }
      },
      "MemoryKnowledgeResult": {
        "type": "object",
        "required": [
          "source_type",
          "entry_id",
          "memory_id",
          "memory_name",
          "content",
          "created_at",
          "updated_at"
        ],
        "properties": {
          "source_type": {
            "type": "string",
            "enum": [
              "memory"
            ],
            "description": "The type of knowledge source this result comes from",
            "example": "memory"
          },
          "entry_id": {
            "type": "string",
            "description": "Public ID of the memory entry",
            "example": "mem_entry_V1StGXR8Z5jdHi6B"
          },
          "memory_id": {
            "x-soat-ref": "memories",
            "type": "string",
            "description": "Public ID of the parent memory",
            "example": "mem_V1StGXR8Z5jdHi6B"
          },
          "memory_name": {
            "type": "string",
            "description": "Human-readable name of the parent memory",
            "example": "Customer Preferences"
          },
          "content": {
            "type": "string",
            "description": "Text content of the memory entry"
          },
          "score": {
            "type": "number",
            "description": "Implementation-defined relevance ranking — higher is better. The **ordering** it produces is the contract; the absolute value is not, and the formula behind it may change (a future hybrid ranking would fuse several signals here). It is the field `min_score` filters on and the field results are sorted by. Only present when `query` was provided. Use `similarity_score` when you need the raw cosine value.",
            "example": 0.79
          },
          "similarity_score": {
            "type": "number",
            "description": "Raw cosine similarity (0–1) between the query and this result. Pinned to that meaning — unlike `score`, it is never redefined. Only present when `query` was provided.",
            "minimum": 0,
            "maximum": 1,
            "example": 0.79
          },
          "created_at": {
            "type": "string",
            "format": "date-time",
            "description": "Creation timestamp"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time",
            "description": "Last updated timestamp"
          }
        }
      },
      "Memory": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "example": "mem_V1StGXR8Z5jdHi6B"
          },
          "project_id": {
            "x-soat-ref": "projects",
            "type": "string",
            "example": "proj_V1StGXR8Z5jdHi6B"
          },
          "name": {
            "type": "string",
            "example": "Product Documentation"
          },
          "description": {
            "type": "string",
            "nullable": true,
            "example": "Retrieves product docs for support queries"
          },
          "tags": {
            "type": "array",
            "nullable": true,
            "items": {
              "type": "string"
            },
            "description": "List of tags for filtering in knowledge search",
            "example": [
              "projectA",
              "customer-support"
            ]
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "MemoryEntry": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "example": "mem_entry_V1StGXR8Z5jdHi6B"
          },
          "memory_id": {
            "x-soat-ref": "memories",
            "type": "string",
            "example": "mem_V1StGXR8Z5jdHi6B"
          },
          "content": {
            "type": "string",
            "example": "The customer prefers email communication over phone calls"
          },
          "source_type": {
            "type": "string",
            "enum": [
              "manual",
              "agent",
              "extraction",
              "orchestration"
            ],
            "example": "manual"
          },
          "tags": {
            "type": "array",
            "nullable": true,
            "items": {
              "type": "string"
            },
            "description": "Per-entry tag strings",
            "example": [
              "role:traffic-manager",
              "source:rejected_approval"
            ]
          },
          "metadata": {
            "type": "object",
            "nullable": true,
            "additionalProperties": true,
            "description": "Arbitrary structured metadata attached to the entry",
            "example": {
              "evidence": "high"
            }
          },
          "source_generation_id": {
            "x-soat-ref": "generations",
            "type": "string",
            "nullable": true,
            "description": "The generation whose turn produced this entry. Set for entries written by the `write_memory` tool and by automatic extraction; null for manual and orchestration writes. Recorded when the entry is created and never rewritten by a later merge.",
            "example": "gen_V1StGXR8Z5jdHi6B"
          },
          "source_conversation_id": {
            "x-soat-ref": "conversations",
            "type": "string",
            "nullable": true,
            "description": "The conversation the producing turn belonged to. Null when the entry did not come from a conversation (a direct agent generation, a manual write, or an orchestration write).",
            "example": "conv_V1StGXR8Z5jdHi6B"
          },
          "invalidated_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true,
            "description": "When the entry was superseded. Null means the entry is currently valid. Invalidated entries are excluded from listing, from write deduplication, and from knowledge search, but remain readable by ID for audit."
          },
          "superseded_by_entry_id": {
            "type": "string",
            "nullable": true,
            "description": "The entry that replaced this one, when it was superseded. Null for valid entries.",
            "example": "mem_entry_V1StGXR8Z5jdHi6B"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "MemoryEntryWriteResult": {
        "allOf": [
          {
            "$ref": "#/components/schemas/MemoryEntry"
          },
          {
            "type": "object",
            "properties": {
              "action": {
                "type": "string",
                "enum": [
                  "created",
                  "updated",
                  "skipped",
                  "superseded"
                ],
                "description": "The outcome of the write operation. `updated` means an existing entry was rewritten to absorb the incoming fact — it is produced only by the LLM consolidation on agent write paths (the `write_memory` tool and automatic extraction), never by this endpoint, which creates instead of merging. `superseded` means the incoming content contradicted an existing entry, which was invalidated and replaced — it is produced by the LLM-arbitrated write path and does not occur until that ships."
              }
            }
          }
        ]
      },
      "ModelRouteTarget": {
        "type": "object",
        "required": [
          "ai_provider_id",
          "model"
        ],
        "properties": {
          "ai_provider_id": {
            "x-soat-ref": "ai-providers",
            "type": "string",
            "description": "AI provider in the route's project",
            "example": "aip_V1StGXR8Z5jdHi6B"
          },
          "model": {
            "type": "string",
            "description": "Model name to call on that provider",
            "example": "gpt-4o-mini"
          },
          "timeout_seconds": {
            "type": "integer",
            "description": "Per-attempt deadline, enforced with an AbortSignal composed with the caller's signal. A timeout classifies as `timeout`; the caller's own signal firing aborts the run without failover. Omitted means no per-target deadline.",
            "example": 30
          },
          "max_retries": {
            "type": "integer",
            "default": 0,
            "description": "Retries on this target before falling through to the next one. The route is the only retry authority — routed calls pass `maxRetries: 0` to the AI SDK so its own retry loop cannot multiply these.",
            "example": 1
          }
        }
      },
      "ModelRoute": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "example": "route_V1StGXR8Z5jdHi6B"
          },
          "project_id": {
            "x-soat-ref": "projects",
            "type": "string"
          },
          "name": {
            "type": "string",
            "example": "primary-with-fallback"
          },
          "targets": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ModelRouteTarget"
            }
          },
          "retry_on": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "provider_error",
                "timeout",
                "rate_limited"
              ]
            }
          },
          "failure_threshold": {
            "type": "integer",
            "example": 3
          },
          "cooldown_seconds": {
            "type": "integer",
            "example": 60
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "OauthAuthorizationServerMetadata": {
        "type": "object",
        "description": "RFC 8414 Authorization Server Metadata.",
        "required": [
          "issuer",
          "authorization_endpoint",
          "token_endpoint",
          "response_types_supported",
          "grant_types_supported",
          "code_challenge_methods_supported",
          "token_endpoint_auth_methods_supported"
        ],
        "properties": {
          "issuer": {
            "type": "string",
            "format": "uri",
            "description": "The deployment's base URL, which every endpoint below is relative to.",
            "example": "http://localhost:5047"
          },
          "authorization_endpoint": {
            "type": "string",
            "format": "uri",
            "example": "http://localhost:5047/authorize"
          },
          "token_endpoint": {
            "type": "string",
            "format": "uri",
            "example": "http://localhost:5047/token"
          },
          "registration_endpoint": {
            "type": "string",
            "format": "uri",
            "description": "RFC 7591 dynamic client registration.",
            "example": "http://localhost:5047/register"
          },
          "response_types_supported": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "example": [
              "code"
            ]
          },
          "grant_types_supported": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "example": [
              "authorization_code",
              "refresh_token"
            ]
          },
          "code_challenge_methods_supported": {
            "type": "array",
            "description": "PKCE is mandatory in OAuth 2.1, so `plain` is not offered.",
            "items": {
              "type": "string"
            },
            "example": [
              "S256"
            ]
          },
          "token_endpoint_auth_methods_supported": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "example": [
              "client_secret_basic",
              "client_secret_post",
              "none"
            ]
          },
          "scopes_supported": {
            "type": "array",
            "description": "The scopes a client may request. `mcp:access` grants use of the MCP endpoint.",
            "items": {
              "type": "string"
            },
            "example": [
              "mcp:access"
            ]
          }
        }
      },
      "OauthProtectedResourceMetadata": {
        "type": "object",
        "description": "RFC 9728 Protected Resource Metadata.",
        "required": [
          "resource",
          "authorization_servers"
        ],
        "properties": {
          "resource": {
            "type": "string",
            "format": "uri",
            "description": "The protected resource's identifier.",
            "example": "http://localhost:5047"
          },
          "authorization_servers": {
            "type": "array",
            "description": "Issuers of the authorization servers that guard this resource.",
            "items": {
              "type": "string",
              "format": "uri"
            },
            "example": [
              "http://localhost:5047"
            ]
          }
        }
      },
      "OauthClientRegistrationRequest": {
        "type": "object",
        "description": "RFC 7591 client metadata. Fields beyond those listed are stored as sent.",
        "required": [
          "redirect_uris"
        ],
        "properties": {
          "redirect_uris": {
            "type": "array",
            "description": "At least one redirect URI. `/authorize` accepts only an exact match.",
            "items": {
              "type": "string",
              "format": "uri"
            },
            "example": [
              "https://client.example.com/callback"
            ]
          },
          "client_name": {
            "type": "string",
            "description": "Human-readable name, shown on the consent screen.",
            "example": "My MCP Client"
          },
          "token_endpoint_auth_method": {
            "type": "string",
            "description": "`none` registers a public client, which receives no secret. Defaults to `client_secret_basic`.",
            "example": "none"
          },
          "grant_types": {
            "type": "array",
            "description": "Defaults to `authorization_code` and `refresh_token`.",
            "items": {
              "type": "string"
            }
          },
          "response_types": {
            "type": "array",
            "description": "Defaults to `code`.",
            "items": {
              "type": "string"
            }
          }
        }
      },
      "OauthClientRegistrationResponse": {
        "type": "object",
        "description": "The registered client, echoing the metadata sent plus the server-assigned fields.",
        "required": [
          "client_id",
          "redirect_uris",
          "token_endpoint_auth_method",
          "grant_types",
          "response_types",
          "client_id_issued_at"
        ],
        "properties": {
          "client_id": {
            "type": "string",
            "example": "8f14e45fceea167a"
          },
          "client_secret": {
            "type": "string",
            "description": "Absent for a public client (`token_endpoint_auth_method: none`)."
          },
          "client_secret_expires_at": {
            "type": "integer",
            "description": "`0` means the secret does not expire. Absent for a public client.",
            "example": 0
          },
          "client_id_issued_at": {
            "type": "integer",
            "description": "Unix time, in seconds, at which the client was registered.",
            "example": 1735689600
          },
          "redirect_uris": {
            "type": "array",
            "items": {
              "type": "string",
              "format": "uri"
            }
          },
          "client_name": {
            "type": "string"
          },
          "token_endpoint_auth_method": {
            "type": "string",
            "example": "none"
          },
          "grant_types": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "example": [
              "authorization_code",
              "refresh_token"
            ]
          },
          "response_types": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "example": [
              "code"
            ]
          }
        }
      },
      "OauthTokenRequest": {
        "type": "object",
        "description": "Form-encoded token request. `code`, `redirect_uri` and `code_verifier` belong to the\n`authorization_code` grant; `refresh_token` to the `refresh_token` grant.\n",
        "required": [
          "grant_type"
        ],
        "properties": {
          "grant_type": {
            "type": "string",
            "enum": [
              "authorization_code",
              "refresh_token"
            ],
            "example": "authorization_code"
          },
          "code": {
            "type": "string",
            "description": "The single-use authorization code from the `/authorize` redirect."
          },
          "redirect_uri": {
            "type": "string",
            "format": "uri",
            "description": "Must match the `redirect_uri` the code was issued for."
          },
          "code_verifier": {
            "type": "string",
            "description": "The PKCE verifier whose SHA-256 is the `code_challenge` sent to `/authorize`."
          },
          "refresh_token": {
            "type": "string",
            "description": "The refresh token to exchange. Single-use — a new one is returned."
          },
          "scope": {
            "type": "string",
            "description": "Space-separated scopes, to narrow those of the refreshed token."
          },
          "client_id": {
            "type": "string",
            "description": "Required for a public client, which sends no secret."
          },
          "client_secret": {
            "type": "string",
            "description": "For `client_secret_post`. With `client_secret_basic`, send it in the `Authorization` header instead."
          }
        }
      },
      "OauthTokenResponse": {
        "type": "object",
        "required": [
          "access_token",
          "token_type",
          "scope"
        ],
        "properties": {
          "access_token": {
            "type": "string",
            "description": "A SOAT-issued JWT. Send it as `Authorization: Bearer <token>`."
          },
          "token_type": {
            "type": "string",
            "example": "Bearer"
          },
          "expires_in": {
            "type": "integer",
            "description": "Lifetime of the access token, in seconds.",
            "example": 3600
          },
          "refresh_token": {
            "type": "string",
            "description": "Single-use; rotated on every exchange."
          },
          "scope": {
            "type": "string",
            "description": "Space-separated scopes actually granted.",
            "example": "mcp:access"
          }
        }
      },
      "OauthErrorResponse": {
        "type": "object",
        "description": "RFC 6749 error. These endpoints answer in the OAuth error shape rather than SOAT's\n`{ code, message, hint, docs_url }`, because an OAuth client parses `error` to decide what to\ndo next.\n",
        "required": [
          "error"
        ],
        "properties": {
          "error": {
            "type": "string",
            "description": "RFC 6749 error code.",
            "enum": [
              "invalid_request",
              "invalid_client",
              "invalid_grant",
              "invalid_redirect_uri",
              "unsupported_grant_type",
              "unsupported_response_type",
              "access_denied",
              "server_error"
            ]
          },
          "error_description": {
            "type": "string",
            "description": "Human-readable detail.",
            "example": "PKCE verification failed"
          }
        }
      },
      "QueueStats": {
        "type": "object",
        "description": "A point-in-time snapshot of the orchestration run queue.",
        "properties": {
          "driver": {
            "type": "string",
            "enum": [
              "postgres",
              "sqs"
            ],
            "description": "The active queue driver (`ORCHESTRATION_QUEUE_DRIVER`). Under `sqs`, `oldest_queued_age_seconds` is always `null` and `per_project` is always empty — SQS exposes neither.",
            "example": "postgres"
          },
          "queue_depth": {
            "type": "integer",
            "description": "Tasks waiting to be claimed now (unclaimed and past their `available_at`). Backoff-delayed tasks are excluded.",
            "example": 12
          },
          "claimed_tasks": {
            "type": "integer",
            "description": "Tasks currently claimed with a valid (unexpired) lease.",
            "example": 3
          },
          "oldest_queued_age_seconds": {
            "type": "number",
            "nullable": true,
            "description": "Age in seconds of the oldest claimable-now task, or `null` when none are waiting.",
            "example": 4.2
          },
          "claim_latency_ms": {
            "type": "object",
            "description": "Claim-latency percentiles (time from a task becoming available to being claimed) over a rolling in-process window. `p50`/`p95` are `null` when no claim happened in the window.",
            "properties": {
              "p50": {
                "type": "number",
                "nullable": true,
                "example": 18
              },
              "p95": {
                "type": "number",
                "nullable": true,
                "example": 240
              },
              "window_seconds": {
                "type": "integer",
                "example": 300
              }
            }
          },
          "per_project": {
            "type": "array",
            "description": "One row per project with any queued or claimed task.",
            "items": {
              "type": "object",
              "properties": {
                "project_id": {
                  "type": "string",
                  "description": "Public project ID (proj_ prefix).",
                  "example": "proj_V1StGXR8Z5jdHi6B"
                },
                "queued": {
                  "type": "integer",
                  "example": 5
                },
                "claimed": {
                  "type": "integer",
                  "example": 1
                }
              }
            }
          }
        }
      },
      "OrchestrationNode": {
        "type": "object",
        "description": "A single execution unit in the orchestration graph.",
        "required": [
          "id",
          "type"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "Unique node identifier within this orchestration."
          },
          "type": {
            "type": "string",
            "description": "Node execution type. Known types: agent, tool, transform, knowledge, memory_write, condition, human, approval, loop, poll, delay, webhook, emit_event, sub_orchestration. Open set — new types may be added in minor releases, and an unrecognized type is accepted at create time (the run fails when the node dispatches), so clients must tolerate unknown values."
          },
          "agent_id": {
            "x-soat-ref": "agents",
            "type": "string",
            "description": "For agent nodes — public ID of the agent to invoke."
          },
          "tool_id": {
            "x-soat-ref": "tools",
            "type": "string",
            "description": "For tool and poll nodes — public ID of the tool to call."
          },
          "operation_id": {
            "type": "string",
            "description": "For tool and poll nodes — specific operation/action on MCP/SOAT tools."
          },
          "expression": {
            "description": "For transform/condition nodes — JSON Logic rule (https://jsonlogic.com) evaluated against the run state. A rule may be any JSON value (object, string, number, boolean, array), so no type is constrained."
          },
          "exit_condition": {
            "description": "For poll nodes — JSON Logic stop condition, evaluated each attempt against the run state augmented with `response` (the latest tool result) and `attempt` (1-based count); a truthy result stops polling.\n"
          },
          "prompt": {
            "type": "string",
            "description": "For human nodes — prompt shown to the human reviewer."
          },
          "options": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "For human nodes — constrained choices."
          },
          "memory_id": {
            "x-soat-ref": "memories",
            "type": "string",
            "description": "For memory_write nodes — public ID of the target memory."
          },
          "arguments": {
            "type": "object",
            "additionalProperties": true,
            "description": "For approval nodes — input-mapping-style object (JSON Logic values) resolved against run state into the proposed tool call's arguments, frozen onto the created approval item.\n"
          },
          "expires_in": {
            "type": "integer",
            "description": "For approval nodes — seconds until the created approval item expires. Defaults to 86400 (24h) when omitted. An expired item can never execute; the run routes down its `on_expired` edge.\n"
          },
          "instructions": {
            "type": "string",
            "description": "For approval nodes — optional guidance shown to the approver."
          },
          "reasoning": {
            "description": "For approval nodes — JSON Logic (any JSON value) resolved into the item's reasoning."
          },
          "evidence": {
            "description": "For approval nodes — JSON Logic (any JSON value) resolved into the item's evidence."
          },
          "predicted_impact": {
            "description": "For approval nodes — JSON Logic (any JSON value) resolved into the item's predicted impact."
          },
          "input_mapping": {
            "type": "object",
            "additionalProperties": true,
            "description": "Maps node input keys to values. Each value is JSON Logic (https://jsonlogic.com), the same evaluator used by transform and condition nodes. A single-key object is evaluated against the run state — `{\"var\": \"key\"}` reads `state.key`, `{\"cat\": [...]}` and `{\">\": [...]}` compute derived values. Any other value (string, number, boolean, array, multi-key object) is passed through as a literal.\n"
          },
          "state_mapping": {
            "type": "object",
            "additionalProperties": true,
            "description": "Maps state write paths to values. Each key is a `state.<path>` destination (the `state.` prefix is optional); each value is JSON Logic (https://jsonlogic.com) evaluated against `{ \"output\": <node artifact>, \"state\": <run state> }` — e.g. `{ \"summary\": {\"var\": \"output.content\"} }` writes the artifact's `content` field to `state.summary`. The same evaluator as input_mapping/transform/condition; only the context differs.\n"
          },
          "output_schema": {
            "type": "object",
            "description": "For agent nodes — JSON Schema for structured output parsing."
          },
          "collection": {
            "type": "string",
            "description": "For loop nodes — state path to the collection to iterate over."
          },
          "item_variable": {
            "type": "string",
            "description": "For loop nodes — variable name injected into state for each item."
          },
          "parallelism": {
            "type": "integer",
            "description": "For loop nodes — number of items to process in parallel."
          },
          "context_keys": {
            "type": "array",
            "nullable": true,
            "items": {
              "type": "string"
            },
            "description": "For loop and sub_orchestration nodes — allowlist of the run's `tool_context` keys the child run inherits. When `null` (the default), the child inherits the parent's whole bag — the behavior of every graph authored before this field existed. When set, only the listed keys are handed down, so a run holding a broad credential can delegate one step to a shared sub-graph without passing on what that sub-graph does not need; `[]` hands down nothing. Matching is case-insensitive, since an entry names a key that becomes an HTTP header name; an entry outside that grammar is rejected at write time with `INVALID_TOOL_CONTEXT_KEY`. The server-derived identity keys (`session_id`, `actor_id`, `actor_external_id`) are unaffected — they are re-derived per generation in the child regardless of this list. Ignored for other node types."
          },
          "interval": {
            "type": "string",
            "description": "For poll nodes — wait between attempts. Accepts a friendly suffix form (`5s`, `30s`, `5m`, `2h`, `500ms`) or ISO 8601 (e.g. PT5S).\n"
          },
          "fail_on_timeout": {
            "type": "boolean",
            "description": "For poll nodes — when max_iterations is reached without the exit condition becoming true, fail the run (true) instead of completing with condition_met=false (default false).\n"
          },
          "duration": {
            "type": "string",
            "description": "For delay nodes — how long to wait. Accepts a friendly suffix form (`5s`, `30s`, `5m`, `2h`, `500ms`) or ISO 8601 (e.g. PT5S).\n"
          },
          "mode": {
            "type": "string",
            "enum": [
              "receive"
            ],
            "description": "For webhook nodes — parks the run awaiting an inbound callback. `receive` is the only mode; to send a notification out of a graph, use an `emit_event` node instead.\n"
          },
          "event_type": {
            "type": "string",
            "description": "For emit_event nodes — the internal event type to emit (e.g. `guardrail.exception`). The node's input_mapping becomes the event `data`. Any Webhook subscribed to this event type in the run's project then delivers it — signed, retried, and tracked by the Webhooks module — so the graph holds no URL or secret of its own.\n"
          },
          "orchestration_id": {
            "x-soat-ref": "orchestrations",
            "type": "string",
            "description": "Public ID of the orchestration this node runs — the child orchestration for sub_orchestration nodes, and the orchestration run once per item for loop nodes.\n"
          },
          "max_iterations": {
            "type": "integer",
            "description": "Maximum iterations before the node is aborted. For poll nodes this is the maximum number of attempts (default 10, ceiling 1000).\n"
          },
          "retry": {
            "type": "object",
            "description": "Retry-on-failure policy. When the node throws a transient error (unexpected/infrastructure errors and upstream 5xx) and attempts remain, the run parks as `sleeping` and re-executes the node after the backoff delay. Terminal errors (4xx business errors) fail immediately. Absent or `max_attempts <= 1` means fail-fast.\n",
            "properties": {
              "max_attempts": {
                "type": "integer",
                "description": "Total attempts including the first (default 1, ceiling 20).\n"
              },
              "backoff": {
                "type": "object",
                "properties": {
                  "strategy": {
                    "type": "string",
                    "enum": [
                      "fixed",
                      "exponential"
                    ],
                    "description": "`fixed` waits `delay_ms` between every attempt; `exponential` doubles per prior attempt. Default `fixed`.\n"
                  },
                  "delay_ms": {
                    "type": "integer",
                    "description": "Base delay between attempts in ms (default 1000)."
                  },
                  "max_delay_ms": {
                    "type": "integer",
                    "description": "Cap on the computed backoff delay in ms (default 300000).\n"
                  }
                }
              }
            }
          }
        }
      },
      "OrchestrationEdge": {
        "type": "object",
        "description": "A directed connection between two nodes.",
        "required": [
          "from",
          "to"
        ],
        "properties": {
          "from": {
            "type": "string",
            "description": "Source node ID."
          },
          "to": {
            "type": "string",
            "description": "Target node ID."
          },
          "condition": {
            "type": "string",
            "description": "For condition node routing — label to match against condition output."
          },
          "activation_group": {
            "type": "string",
            "description": "Groups edges for join semantics."
          },
          "activation_condition": {
            "type": "string",
            "enum": [
              "all",
              "any"
            ],
            "description": "Whether all or any edges in the activation group must fire."
          }
        }
      },
      "Orchestration": {
        "type": "object",
        "required": [
          "id",
          "project_id",
          "name",
          "version",
          "nodes",
          "edges",
          "created_at",
          "updated_at"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "Public ID (orch_...)."
          },
          "project_id": {
            "x-soat-ref": "projects",
            "type": "string",
            "description": "Public ID of the owning project."
          },
          "name": {
            "type": "string",
            "description": "Human-readable name."
          },
          "description": {
            "type": "string",
            "nullable": true,
            "description": "Optional description."
          },
          "version": {
            "type": "integer",
            "description": "Incremented on every write that changes the graph; prior versions are archived. A run pins the version it started on, so these fields are a draft for runs started from now on rather than a live rewrite of the ones already executing.\n",
            "example": 1
          },
          "nodes": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/OrchestrationNode"
            }
          },
          "edges": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/OrchestrationEdge"
            }
          },
          "state_schema": {
            "type": "object",
            "nullable": true,
            "description": "Optional JSON Schema for state validation."
          },
          "input_schema": {
            "type": "object",
            "nullable": true,
            "description": "Schema for run inputs (initial state)."
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "CreateOrchestrationRequest": {
        "type": "object",
        "required": [
          "name",
          "nodes",
          "edges"
        ],
        "properties": {
          "project_id": {
            "x-soat-ref": "projects",
            "type": "string",
            "description": "Public ID of the project. Optional when authenticating with a project-scoped API key, which defaults to the key's project; required otherwise."
          },
          "name": {
            "type": "string",
            "description": "Human-readable name."
          },
          "description": {
            "type": "string",
            "nullable": true
          },
          "nodes": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/OrchestrationNode"
            }
          },
          "edges": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/OrchestrationEdge"
            }
          },
          "state_schema": {
            "type": "object",
            "nullable": true
          },
          "input_schema": {
            "type": "object",
            "nullable": true
          },
          "version_label": {
            "type": "string",
            "description": "Optional tag for the version this create archives, e.g. `initial`.",
            "example": "initial"
          }
        }
      },
      "UpdateOrchestrationRequest": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string"
          },
          "description": {
            "type": "string",
            "nullable": true
          },
          "nodes": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/OrchestrationNode"
            }
          },
          "edges": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/OrchestrationEdge"
            }
          },
          "state_schema": {
            "type": "object",
            "nullable": true
          },
          "input_schema": {
            "type": "object",
            "nullable": true
          },
          "version_label": {
            "type": "string",
            "description": "Optional tag for the version this write archives, e.g. `pre-rewire`. Ignored when the write changes no graph field, since no version is archived.",
            "example": "pre-rewire"
          }
        }
      },
      "OrchestrationVersion": {
        "type": "object",
        "description": "An immutable archive of an orchestration's graph at one version.",
        "properties": {
          "id": {
            "type": "string",
            "description": "Public ID of the archived version",
            "example": "orch_ver_V1StGXR8Z5jdHi6B"
          },
          "orchestration_id": {
            "x-soat-ref": "orchestrations",
            "type": "string",
            "description": "Public ID of the orchestration this version belongs to",
            "example": "orch_V1StGXR8Z5jdHi6B"
          },
          "version": {
            "type": "integer",
            "description": "The archived version number",
            "example": 1
          },
          "config": {
            "type": "object",
            "additionalProperties": true,
            "description": "The orchestration's versioned surface as it stood at this version: `nodes`, `edges`, `state_schema` and `input_schema`. Name and description are metadata — bumping the version when one of them changes would make two version numbers denote the same topology, which is exactly what a run cites.\n\nDeliberately open rather than a fixed schema: an archive written by an earlier release of SOAT reflects the orchestration surface **of its own time**, so it may carry fields the current API no longer documents.",
            "properties": {
              "nodes": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/OrchestrationNode"
                }
              },
              "edges": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/OrchestrationEdge"
                }
              },
              "state_schema": {
                "type": "object",
                "nullable": true
              },
              "input_schema": {
                "type": "object",
                "nullable": true
              }
            }
          },
          "label": {
            "type": "string",
            "nullable": true,
            "description": "Optional human tag for this version, e.g. `pre-rewire`. Set from the `version_label` field of a write, the `label` field of a restore, or generated for one.",
            "example": "restored from v2"
          },
          "created_by": {
            "x-soat-ref": "users",
            "type": "string",
            "nullable": true,
            "description": "Public ID of the user whose action produced this version. Null for writes with no request user behind them."
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "RestoreOrchestrationVersionRequest": {
        "type": "object",
        "properties": {
          "label": {
            "type": "string",
            "description": "Optional tag for the version the restore creates. Defaults to `restored from v<version>`.",
            "example": "rollback to pre-incident graph"
          }
        }
      },
      "OrchestrationRun": {
        "type": "object",
        "required": [
          "id",
          "orchestration_id",
          "project_id",
          "status",
          "state",
          "active_nodes",
          "artifacts",
          "created_at",
          "updated_at"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "Public ID (run_...)."
          },
          "orchestration_id": {
            "x-soat-ref": "orchestrations",
            "type": "string",
            "description": "Public ID of the parent orchestration."
          },
          "orchestration_version": {
            "type": "integer",
            "nullable": true,
            "description": "The orchestration version this run executes, fixed when the run started. Every later step of the run — the first drive, a wake from `sleeping`, a human or approval resume, a redrive after a crash — resolves the graph from this version, so editing the orchestration never re-shapes a run already in flight. Fetch the graph it names at `GET /api/v1/orchestrations/{orchestration_id}/versions/{version}`.\n\nNull for runs created before pinning existed, which execute the live graph.\n",
            "example": 3
          },
          "project_id": {
            "x-soat-ref": "projects",
            "type": "string",
            "description": "Public ID of the owning project."
          },
          "status": {
            "type": "string",
            "description": "Run lifecycle state. `queued` awaits a worker; `running` is actively executing; `sleeping` is parked on a delay/poll wait (no worker); `awaiting_input` is parked on a human node; `succeeded`/`failed`/ `cancelled` are terminal; `expired` is a wait that passed its deadline.",
            "enum": [
              "queued",
              "running",
              "sleeping",
              "awaiting_input",
              "succeeded",
              "failed",
              "cancelled",
              "expired"
            ]
          },
          "state": {
            "type": "object",
            "description": "Current accumulated state."
          },
          "active_nodes": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Node IDs currently active."
          },
          "artifacts": {
            "type": "object",
            "description": "Map of node ID to output artifact."
          },
          "error": {
            "type": "object",
            "nullable": true,
            "description": "Error details when status is failed."
          },
          "trace_id": {
            "x-soat-ref": "traces",
            "type": "string",
            "nullable": true
          },
          "input": {
            "type": "object",
            "nullable": true,
            "description": "Initial input provided at run creation."
          },
          "tool_context": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            },
            "nullable": true,
            "description": "The `tool_context` supplied at run creation, forwarded as `X-Soat-Context-<key>` headers on every tool call the run's agent nodes make. Null when the run was started without one."
          },
          "metadata": {
            "type": "object",
            "additionalProperties": true,
            "nullable": true,
            "description": "The caller-owned key/value metadata supplied at run creation, returned verbatim. Null when the run was started without any. The server writes nothing here and no key is reserved; the bag is never merged into `state`, so nothing in it reaches the graph.",
            "example": {
              "tenant_account_id": "42",
              "dispatch_batch": "nightly-2026-08-25"
            }
          },
          "parent_orchestration_run_id": {
            "x-soat-ref": "orchestration-runs",
            "type": "string",
            "nullable": true,
            "description": "The run whose node started this one — set only on a child a `loop` or `sub_orchestration` node spawned, null for a run a caller started. A child is its own run with its own usage events, so this is what makes a delegated run's spend attributable to the run that ordered it."
          },
          "parent_node_id": {
            "type": "string",
            "nullable": true,
            "description": "The node within `parent_orchestration_run_id` that started this run. Null when `parent_orchestration_run_id` is null."
          },
          "run_depth": {
            "type": "integer",
            "minimum": 0,
            "description": "`loop` / `sub_orchestration` edges between this run and the run a caller started: `0` for a caller-started run, one more than its parent's for a child. Starting a child past the effective bound — the smaller of the deployment's `MAX_ORCHESTRATION_RUN_DEPTH` (default 10) and the project's `max_run_depth` — is refused with `ORCHESTRATION_RUN_DEPTH_LIMIT`, which fails the run that tried to descend. That bounds a graph whose `sub_orchestration` node names itself, directly or through a cycle of two graphs, which the intra-graph cycle check cannot see.",
            "example": 0
          },
          "output": {
            "type": "object",
            "nullable": true,
            "description": "Terminal node artifact(s) when the run has succeeded."
          },
          "node_executions": {
            "type": "array",
            "description": "Per-node execution records in chronological order. Each entry captures the resolved input, output, status, and error for a single node execution — the orchestration analogue of an LLM trace.",
            "items": {
              "$ref": "#/components/schemas/NodeExecution"
            }
          },
          "usage": {
            "allOf": [
              {
                "$ref": "#/components/schemas/RunUsageTotals"
              }
            ],
            "description": "What the run cost: token counts and `cost_usd` summed across every metered generation it produced **and every run it started** through `loop` / `sub_orchestration` nodes, at any depth. Present on the single-run read; omitted from run list responses.\n\nA nested child is a run record of its own, so this figure spans several of them. Two consequences: summing `usage` across a list that mixes parents and children double-counts (filter with `nested=false`), and the per-event receipt at `/api/v1/usage/receipt` stays scoped to one run — its line items carry a `node_id` from one graph only."
          },
          "usage_own": {
            "allOf": [
              {
                "$ref": "#/components/schemas/RunUsageTotals"
              }
            ],
            "description": "The same roll-up restricted to **this run's own nodes**, excluding every nested run it started. Equal to `usage` for a run with no children; below it for a run that delegates. Present on the single-run read; omitted from run list responses.\n\nThis is the field to read to see where cost sits in a run tree — own versus subtree — without walking the children."
          },
          "required_action": {
            "allOf": [
              {
                "$ref": "#/components/schemas/RequiredAction"
              }
            ],
            "nullable": true
          },
          "started_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "completed_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "RunUsageTotals": {
        "type": "object",
        "description": "Token and cost roll-up for an orchestration run, summed across every usage event the run's generations produced.",
        "properties": {
          "total_input_tokens": {
            "type": "integer"
          },
          "total_output_tokens": {
            "type": "integer"
          },
          "total_cached_tokens": {
            "type": "integer"
          },
          "total_reasoning_tokens": {
            "type": "integer"
          },
          "total_cost_usd": {
            "type": "number",
            "nullable": true,
            "description": "Sum of the run's priced component costs in USD. Null when nothing on the run was priced."
          }
        }
      },
      "NodeExecution": {
        "type": "object",
        "description": "Record of a single node execution within a run, used to debug which node failed, what input it received, and what it produced.",
        "required": [
          "node_id",
          "attempt",
          "status",
          "created_at"
        ],
        "properties": {
          "node_id": {
            "type": "string",
            "description": "ID of the executed node."
          },
          "node_type": {
            "type": "string",
            "nullable": true,
            "description": "Type of the executed node (e.g. agent, transform)."
          },
          "attempt": {
            "type": "integer",
            "description": "1-based attempt number. A node with a retry policy produces one record per attempt (failed attempts followed by a final record).\n"
          },
          "status": {
            "type": "string",
            "enum": [
              "running",
              "completed",
              "failed",
              "requires_action",
              "skipped"
            ],
            "description": "Node execution status. `running` marks an execution record whose node is still in flight. Open set — new statuses may be added in minor releases; clients must tolerate unknown values."
          },
          "input": {
            "type": "object",
            "nullable": true,
            "description": "Resolved input_mapping the node received."
          },
          "output": {
            "type": "object",
            "nullable": true,
            "description": "Output artifact the node produced (null when failed)."
          },
          "error": {
            "type": "object",
            "nullable": true,
            "description": "Error details when status is failed."
          },
          "started_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "completed_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "RequiredAction": {
        "type": "object",
        "description": "Details for an awaiting_input run waiting for human input.",
        "required": [
          "type",
          "node_id",
          "prompt",
          "context"
        ],
        "properties": {
          "type": {
            "type": "string",
            "enum": [
              "human_input",
              "webhook_receive",
              "approval"
            ],
            "description": "Discriminator identifying the kind of pause. Open enum — new pause kinds may be added in minor releases; clients must tolerate unknown values."
          },
          "node_id": {
            "type": "string"
          },
          "prompt": {
            "type": "string"
          },
          "context": {
            "type": "object"
          },
          "options": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "nullable": true
          },
          "approval_spec": {
            "type": "object",
            "description": "Present only for `approval` pauses — the frozen tool proposal the engine emits as an ApprovalItem when the run parks. Copied as a value; inner keys stay exactly as authored."
          },
          "approval_id": {
            "x-soat-ref": "approvals",
            "type": "string",
            "description": "Present once the approval item is emitted."
          },
          "expires_at": {
            "type": "string",
            "format": "date-time",
            "description": "Present for `approval` pauses — when the item expires."
          }
        }
      },
      "HumanInputRequest": {
        "type": "object",
        "required": [
          "node_id"
        ],
        "properties": {
          "node_id": {
            "type": "string",
            "description": "ID of the human node to satisfy."
          },
          "output": {
            "type": "object",
            "description": "Output/response provided by the human reviewer."
          }
        }
      },
      "StartRunRequest": {
        "type": "object",
        "required": [
          "orchestration_id"
        ],
        "properties": {
          "orchestration_id": {
            "x-soat-ref": "orchestrations",
            "type": "string",
            "description": "Orchestration to run (orch_...).",
            "example": "orch_V1StGXR8Z5jdHi6B"
          },
          "input": {
            "type": "object",
            "description": "Initial state for the run (merged with orchestration defaults)."
          },
          "tool_context": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            },
            "description": "Key-value pairs forwarded as `X-Soat-Context-<key>` headers on every `http`, `mcp` and `builtin` tool call made by an agent node of this run — including the agents of any child run a `loop` or `sub_orchestration` node starts. The header name is `X-Soat-Context-` plus the key verbatim; no character is re-cased.\n\nThe bag is stored on the run and re-read on every step, so it survives an `awaiting_input` pause, a `sleeping` wait, a background worker drive and a crash redrive. A key that is not a valid HTTP header name, or two keys that map to the same header, are rejected with `400 INVALID_TOOL_CONTEXT_KEY` and no run is created.\n\nThe reserved identity keys (`session_id`, `actor_id`, `actor_external_id`) are stripped at generation time — a caller cannot address them from here.",
            "example": {
              "ocaToken": "eyJhbGciOiJIUzI1NiJ9.abc"
            }
          },
          "metadata": {
            "type": "object",
            "additionalProperties": true,
            "description": "Caller-supplied key/value metadata attached to the run record for per-run attribution (e.g. which of your own tenants this run belongs to, or the dispatch batch that started it). Round-trips verbatim on every read of the run, on the list as well as the single read.\n\nThe bag is caller-owned and no key is reserved: server-owned state (status, the pinned orchestration version, the trace, usage, artifacts, the run's own `input` and accumulated `state`) lives in its own top-level field and cannot be written from here.\n\nIt is **not** merged into run state: no graph node sees it, and an `input_schema` never has to tolerate it — which is what makes it the place for an infrastructural label, rather than `input`. Keys are never transformed. It is not inherited by the child runs a `loop` or `sub_orchestration` node starts; each child carries whatever the graph gives it, which today is nothing.",
            "example": {
              "tenant_account_id": "42",
              "dispatch_batch": "nightly-2026-08-25"
            }
          },
          "wait": {
            "type": "boolean",
            "default": false,
            "description": "When true, block until the run reaches a terminal (succeeded/failed) or awaiting_input state and return the settled run. When false (default), return immediately with status \"queued\" and execute the run in the background."
          }
        }
      },
      "ValidateOrchestrationRequest": {
        "type": "object",
        "properties": {
          "nodes": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/OrchestrationNode"
            }
          },
          "edges": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/OrchestrationEdge"
            }
          },
          "input_schema": {
            "type": "object",
            "nullable": true,
            "description": "Optional JSON Schema for run inputs; its top-level properties seed state."
          }
        }
      },
      "PolicyStatement": {
        "type": "object",
        "required": [
          "effect",
          "action"
        ],
        "properties": {
          "effect": {
            "type": "string",
            "enum": [
              "Allow",
              "Deny"
            ],
            "example": "Allow"
          },
          "action": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "example": [
              "files:ListFiles",
              "files:CreateFile"
            ]
          },
          "resource": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "example": [
              "srn:proj_abc:files:*"
            ]
          },
          "condition": {
            "type": "object",
            "additionalProperties": true,
            "description": "Optional condition block. Keys are condition operators (e.g. StringEquals) mapping to context-key/value maps; evaluated by the policy compiler. Free-form — keys are dynamic, not validated. Operator and context keys are matched by exact string and are never case-converted, so they round-trip exactly as written: `soat:ResourceTag/cost_center` selects the tag `cost_center`, not `costCenter`.\n",
            "example": {
              "StringEquals": {
                "soat:ResourceTag/env": "prod"
              }
            }
          }
        }
      },
      "PolicyDocument": {
        "type": "object",
        "required": [
          "statement"
        ],
        "properties": {
          "statement": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/PolicyStatement"
            }
          }
        }
      },
      "PolicyRecord": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "Public policy ID (pol_ prefix)",
            "example": "pol_V1StGXR8Z5jdHi6B"
          },
          "name": {
            "type": "string",
            "nullable": true,
            "example": "ReadOnlyAccess"
          },
          "description": {
            "type": "string",
            "nullable": true
          },
          "document": {
            "$ref": "#/components/schemas/PolicyDocument"
          },
          "created_at": {
            "type": "string",
            "format": "date-time",
            "example": "2024-01-01T00:00:00.000Z"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time",
            "example": "2024-01-01T00:00:00.000Z"
          }
        }
      },
      "ProjectRecord": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "Public project ID (proj_ prefix)",
            "example": "proj_V1StGXR8Z5jdHi6B"
          },
          "name": {
            "type": "string",
            "example": "My Project"
          },
          "guardrail_ids": {
            "x-soat-ref": "guardrails",
            "type": "array",
            "nullable": true,
            "items": {
              "type": "string"
            },
            "description": "Guardrails attached at the project scope — the baseline governing every tool call by every agent in the project.",
            "example": []
          },
          "max_concurrent_runs": {
            "type": "integer",
            "nullable": true,
            "minimum": 1,
            "description": "Maximum orchestration runs of this project driven at once. `null` means unlimited (the default). Enforced at queue claim time.",
            "example": null
          },
          "max_chain_generations": {
            "type": "integer",
            "nullable": true,
            "minimum": 1,
            "description": "Generations one continuation chain in this project may hold before the platform stops resuming it. `null` means no project ceiling (the default), leaving the deployment-wide one. The effective budget is the smallest of the deployment's ceiling, this one, and the agent's own `max_chain_generations` stop condition.",
            "example": null
          },
          "max_run_depth": {
            "type": "integer",
            "nullable": true,
            "minimum": 1,
            "description": "`loop` / `sub_orchestration` nesting levels a run tree in this project may reach before the engine refuses to start the next child. `null` means no project bound (the default), leaving the deployment-wide one. The effective bound is the smaller of the two.",
            "example": null
          },
          "default_model_route_id": {
            "x-soat-ref": "model-routes",
            "type": "string",
            "nullable": true,
            "description": "Model route inherited by consumers in this project that bind neither `model_route_id` nor `ai_provider_id`. `null` means no default, so every consumer must bind explicitly.",
            "example": null
          },
          "audit_reads_enabled": {
            "type": "boolean",
            "description": "Whether `GET` requests naming this project are recorded in the audit log. `false` by default — v1 audits mutations only.",
            "example": false
          },
          "trace_content_retention_days": {
            "type": "integer",
            "nullable": true,
            "minimum": 1,
            "description": "Days of trace/generation content retention before the daily sweep purges it. `null` means retention is disabled (the default) and content is kept until purged on demand.",
            "example": null
          },
          "trace_content_mode": {
            "type": "string",
            "enum": [
              "full",
              "none"
            ],
            "description": "Whether trace/generation content is persisted. `full` by default; `none` is zero-retention — content is never written for any agent in this project.",
            "example": "full"
          },
          "created_at": {
            "type": "string",
            "format": "date-time",
            "example": "2024-01-01T00:00:00.000Z"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time",
            "example": "2024-01-01T00:00:00.000Z"
          }
        }
      },
      "ProjectPrice": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "Public ID of the price row",
            "example": "price_V1StGXR8Z5jdHi6B"
          },
          "project_id": {
            "x-soat-ref": "projects",
            "type": "string",
            "description": "The project this price is scoped to",
            "example": "proj_V1StGXR8Z5jdHi6B"
          },
          "ai_provider_id": {
            "type": "string",
            "nullable": true,
            "description": "Always null for a project + provider-slug price"
          },
          "meter_type": {
            "type": "string",
            "description": "Always `llm_tokens` for project prices"
          },
          "provider": {
            "type": "string",
            "example": "openai"
          },
          "model": {
            "type": "string",
            "example": "gpt-4o"
          },
          "component": {
            "type": "string",
            "description": "The token component this row prices (`input_tokens`, `output_tokens`, `cached_tokens`)"
          },
          "unit": {
            "type": "string",
            "description": "Always `token` for token pricing"
          },
          "unit_price": {
            "type": "number",
            "description": "USD per token for this component"
          },
          "effective_from": {
            "type": "string",
            "format": "date-time",
            "description": "The row with the latest effective_from <= now() prices a call"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "ProjectPricesResponse": {
        "type": "object",
        "properties": {
          "prices": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ProjectPrice"
            }
          }
        }
      },
      "UpsertProjectPricesRequest": {
        "type": "object",
        "required": [
          "prices"
        ],
        "properties": {
          "prices": {
            "type": "array",
            "items": {
              "type": "object",
              "required": [
                "provider",
                "model",
                "component",
                "unit",
                "unit_price",
                "effective_from"
              ],
              "properties": {
                "provider": {
                  "type": "string",
                  "example": "openai"
                },
                "model": {
                  "type": "string",
                  "example": "gpt-4o"
                },
                "component": {
                  "type": "string",
                  "description": "The token component this row prices (`input_tokens`, `output_tokens`, `cached_tokens`)"
                },
                "unit": {
                  "type": "string",
                  "description": "Always `token` for token pricing"
                },
                "unit_price": {
                  "type": "number",
                  "description": "USD per token for this component"
                },
                "effective_from": {
                  "type": "string",
                  "format": "date-time",
                  "description": "Must be in the future; past prices are immutable"
                }
              }
            }
          }
        }
      },
      "Quota": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "example": "quota_V1StGXR8Z5jdHi6B"
          },
          "project_id": {
            "x-soat-ref": "projects",
            "type": "string"
          },
          "scope": {
            "type": "string",
            "enum": [
              "project",
              "api_key",
              "agent",
              "actor"
            ]
          },
          "scope_ref": {
            "type": "string",
            "nullable": true,
            "description": "Public id of the api key / agent / actor the quota applies to. For `api_key` and `agent` scope, NULL means all entities of that scope type in the project. For `actor` scope, NULL means one budget *per* actor rather than a pooled total across all actors."
          },
          "metric": {
            "type": "string",
            "enum": [
              "requests",
              "tokens",
              "cost_usd"
            ]
          },
          "window": {
            "type": "string",
            "enum": [
              "rolling_1m",
              "rolling_1h",
              "rolling_24h",
              "calendar_month"
            ]
          },
          "limit": {
            "type": "number"
          },
          "mode": {
            "type": "string",
            "enum": [
              "enforce",
              "monitor"
            ]
          },
          "on_unpriced": {
            "type": "string",
            "enum": [
              "block",
              "allow",
              null
            ],
            "nullable": true,
            "description": "Pricing posture of a cost_usd quota over an unpriced blackout — block refuses generations, allow lets them through (the quota_unpriced exception is filed either way). Null for metrics with no pricing dependency."
          },
          "current_usage": {
            "type": "object",
            "nullable": true,
            "description": "Current fixed-window usage for the requests metric. Null for token/cost quotas (which aggregate the usage meter at check time rather than keeping a counter) and in list responses.",
            "properties": {
              "window_key": {
                "type": "string",
                "example": "2026-07-07T12:31Z"
              },
              "count": {
                "type": "integer",
                "example": 42
              },
              "resets_at": {
                "type": "string",
                "format": "date-time"
              }
            }
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "SessionRecord": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "Session public ID",
            "example": "sess_V1StGXR8Z5jdHi6B"
          },
          "agent_id": {
            "x-soat-ref": "agents",
            "type": "string",
            "description": "Agent public ID",
            "example": "agent_V1StGXR8Z5jdHi6B"
          },
          "conversation_id": {
            "x-soat-ref": "conversations",
            "type": "string",
            "description": "Underlying conversation public ID",
            "example": "conv_V1StGXR8Z5jdHi6B"
          },
          "status": {
            "type": "string",
            "enum": [
              "open",
              "closed",
              "expired"
            ],
            "example": "open"
          },
          "name": {
            "type": "string",
            "nullable": true,
            "example": "Support chat"
          },
          "actor_id": {
            "x-soat-ref": "actors",
            "type": "string",
            "nullable": true,
            "description": "Public ID of the user actor, or null when the session was created without one\n",
            "example": "actor_V1StGXR8Z5jdHi6B"
          },
          "tags": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            }
          },
          "auto_generate": {
            "type": "boolean",
            "default": false,
            "description": "When true, automatically triggers generation after each user message (if no generation is in progress)."
          },
          "generating_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true,
            "description": "Timestamp when the current generation started, or null if not generating."
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          },
          "tool_context": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            },
            "nullable": true,
            "description": "Key-value pairs forwarded as `X-Soat-Context-<key>` headers on every `http`, `mcp` and `builtin` tool call in this session. The header name is the deployment's configured context prefix (`X-Soat-Context-` by default) plus the key verbatim — no character is re-cased. Keys are never case-converted — they round-trip exactly as sent. A key that is not a valid HTTP header name, or two keys that map to the same header, are rejected with `400 INVALID_TOOL_CONTEXT_KEY`."
          },
          "inactivity_ttl_seconds": {
            "type": "integer",
            "default": 0,
            "description": "Number of seconds of inactivity after which the session expires. 0 means the session never expires.",
            "example": 300
          },
          "message_delay_seconds": {
            "type": "integer",
            "nullable": true,
            "default": null,
            "description": "Number of seconds to wait after the last user message before sending to the LLM. Acts as a debounce: each new message resets the timer. null or absent means no delay (immediate processing).\n",
            "example": 3
          },
          "last_activity_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true,
            "description": "Timestamp of the last activity on the session (message added or response generated)."
          },
          "forked_from_session_id": {
            "x-soat-ref": "sessions",
            "type": "string",
            "nullable": true,
            "description": "Public ID of the session this one was forked from, or null when it was not forked. Also null once that parent is deleted — a fork survives its parent and keeps its own history.\n",
            "example": "sess_V1StGXR8Z5jdHi6B"
          },
          "forked_from_position": {
            "type": "integer",
            "nullable": true,
            "description": "The parent conversation position this session branched after, or null when it is not a fork or was forked at the tip.\n",
            "example": 7
          }
        }
      },
      "ForkSessionRequest": {
        "type": "object",
        "properties": {
          "fork_at_position": {
            "type": "integer",
            "minimum": 0,
            "description": "The parent conversation `position` to branch after. Messages at positions 0..N are carried into the fork. Omit it to branch at the tip (the whole history).\n",
            "example": 7
          },
          "agent_id": {
            "x-soat-ref": "agents",
            "type": "string",
            "description": "Agent the fork runs against. Defaults to the parent session's agent; overriding it is the point of forking — same context, a different agent or agent version. Must belong to the same project as the session being forked.\n",
            "example": "agent_V1StGXR8Z5jdHi6B"
          },
          "name": {
            "type": "string",
            "description": "Optional name for the forked session",
            "example": "retry with stricter system prompt"
          },
          "tags": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            },
            "description": "Optional tags for the forked session"
          },
          "tool_context": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            },
            "nullable": true,
            "description": "Overrides the parent's `tool_context` on the fork. Omit it and the fork inherits the parent's, so the branch is faithful to the run it came from.\n"
          }
        }
      },
      "CreateSessionRequest": {
        "type": "object",
        "required": [
          "agent_id"
        ],
        "properties": {
          "agent_id": {
            "x-soat-ref": "agents",
            "type": "string",
            "description": "Agent this session belongs to",
            "example": "agent_V1StGXR8Z5jdHi6B"
          },
          "name": {
            "type": "string",
            "description": "Optional session name",
            "example": "Support chat"
          },
          "actor_id": {
            "x-soat-ref": "actors",
            "type": "string",
            "description": "Optional public ID of an existing actor to use as the user actor. Actors are created separately (POST /actors); this field only links one. Omit it and the session has no end user, so its generations match no actor-scoped quota.\n",
            "example": "actor_V1StGXR8Z5jdHi6B"
          },
          "auto_generate": {
            "type": "boolean",
            "default": false,
            "description": "When true, automatically triggers generation after each user message."
          },
          "tool_context": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            },
            "nullable": true,
            "description": "Key-value pairs forwarded as `X-Soat-Context-<key>` headers on every `http`, `mcp` and `builtin` tool call in this session. The header name is the deployment's configured context prefix (`X-Soat-Context-` by default) plus the key verbatim — no character is re-cased. Keys are never case-converted — they round-trip exactly as sent. A key that is not a valid HTTP header name, or two keys that map to the same header, are rejected with `400 INVALID_TOOL_CONTEXT_KEY`."
          },
          "inactivity_ttl_seconds": {
            "type": "integer",
            "default": 0,
            "description": "Number of seconds of inactivity after which the session expires. 0 means the session never expires.",
            "example": 300
          },
          "message_delay_seconds": {
            "type": "integer",
            "nullable": true,
            "default": null,
            "description": "Number of seconds to wait after the last user message before sending to the LLM. Acts as a debounce: each new message resets the timer. null or absent means no delay (immediate processing).\n",
            "example": 3
          }
        }
      },
      "UpdateSessionRequest": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string",
            "nullable": true,
            "description": "Session name (set to null to clear)"
          },
          "status": {
            "type": "string",
            "enum": [
              "open",
              "closed",
              "expired"
            ],
            "description": "Session status"
          },
          "auto_generate": {
            "type": "boolean",
            "description": "Enable or disable automatic generation after user messages."
          },
          "tool_context": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            },
            "nullable": true,
            "description": "Key-value pairs forwarded as `X-Soat-Context-<key>` headers on every `http`, `mcp` and `builtin` tool call in this session. The header name is the deployment's configured context prefix (`X-Soat-Context-` by default) plus the key verbatim — no character is re-cased. Keys are never case-converted — they round-trip exactly as sent. A key that is not a valid HTTP header name, or two keys that map to the same header, are rejected with `400 INVALID_TOOL_CONTEXT_KEY`."
          },
          "inactivity_ttl_seconds": {
            "type": "integer",
            "description": "Number of seconds of inactivity after which the session expires. 0 means the session never expires. Updates the stored TTL; the inactivity clock continues from the last activity timestamp.\n",
            "example": 300
          },
          "message_delay_seconds": {
            "type": "integer",
            "nullable": true,
            "description": "Number of seconds to wait after the last user message before sending to the LLM. Acts as a debounce: each new message resets the timer. Set to null to disable the delay.\n",
            "example": 3
          }
        }
      },
      "AddSessionMessageRequest": {
        "oneOf": [
          {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "message"
            ],
            "properties": {
              "message": {
                "type": "string",
                "description": "User message text",
                "example": "Hello, how can I deploy my app?"
              },
              "tool_context": {
                "type": "object",
                "additionalProperties": {
                  "type": "string"
                },
                "nullable": true,
                "description": "Key-value pairs forwarded as `X-Soat-Context-<key>` headers on every `http`, `mcp` and `builtin` tool call in this generation. The header name is the deployment's configured context prefix (`X-Soat-Context-` by default) plus the key verbatim — no character is re-cased. Keys are never case-converted — they round-trip exactly as sent. An invalid or colliding key is rejected with `400 INVALID_TOOL_CONTEXT_KEY`."
              },
              "idempotency_key": {
                "type": "string",
                "description": "Optional deduplication key scoped to this session. If a message with the same key already exists in the session, the original message is returned with HTTP 200 and no new message or generation is triggered.\n",
                "example": "wamid.HBgLNTUxMTk4..."
              }
            }
          },
          {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "document_id"
            ],
            "properties": {
              "document_id": {
                "x-soat-ref": "documents",
                "type": "string",
                "description": "Public ID of a document used as the user message content."
              },
              "tool_context": {
                "type": "object",
                "additionalProperties": {
                  "type": "string"
                },
                "nullable": true,
                "description": "Key-value pairs forwarded as `X-Soat-Context-<key>` headers on every `http`, `mcp` and `builtin` tool call in this generation. The header name is the deployment's configured context prefix (`X-Soat-Context-` by default) plus the key verbatim — no character is re-cased. Keys are never case-converted — they round-trip exactly as sent. An invalid or colliding key is rejected with `400 INVALID_TOOL_CONTEXT_KEY`."
              },
              "idempotency_key": {
                "type": "string",
                "description": "Optional deduplication key scoped to this session. If a message with the same key already exists in the session, the original message is returned with HTTP 200 and no new message or generation is triggered.\n",
                "example": "wamid.HBgLNTUxMTk4..."
              }
            }
          }
        ]
      },
      "AddSessionMessageSaved": {
        "type": "object",
        "description": "Message saved; auto-generate is off or a generation is already in progress.",
        "properties": {
          "role": {
            "type": "string",
            "enum": [
              "user"
            ]
          },
          "content": {
            "type": "string"
          },
          "document_id": {
            "x-soat-ref": "documents",
            "type": "string",
            "nullable": true
          }
        }
      },
      "AddSessionMessageResponse": {
        "anyOf": [
          {
            "$ref": "#/components/schemas/AddSessionMessageSaved"
          },
          {
            "$ref": "#/components/schemas/GenerateSessionResponse"
          }
        ]
      },
      "GenerateSessionRequest": {
        "type": "object",
        "properties": {
          "model": {
            "type": "string",
            "description": "Optional model override",
            "example": "gpt-4o"
          },
          "tool_context": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            },
            "nullable": true,
            "description": "Key-value pairs forwarded as `X-Soat-Context-<key>` headers on every `http`, `mcp` and `builtin` tool call in this generation. The header name is the deployment's configured context prefix (`X-Soat-Context-` by default) plus the key verbatim — no character is re-cased. Keys are never case-converted — they round-trip exactly as sent. An invalid or colliding key is rejected with `400 INVALID_TOOL_CONTEXT_KEY`."
          }
        }
      },
      "GenerateSessionResponse": {
        "type": "object",
        "properties": {
          "status": {
            "type": "string",
            "enum": [
              "completed",
              "requires_action"
            ]
          },
          "message": {
            "type": "object",
            "properties": {
              "role": {
                "type": "string"
              },
              "content": {
                "type": "string"
              },
              "model": {
                "type": "string"
              }
            }
          },
          "generation_id": {
            "x-soat-ref": "generations",
            "type": "string"
          },
          "trace_id": {
            "x-soat-ref": "traces",
            "type": "string"
          },
          "required_action": {
            "type": "object",
            "description": "Present when status is requires_action",
            "properties": {
              "tool_calls": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string"
                    },
                    "tool_name": {
                      "type": "string"
                    },
                    "args": {
                      "type": "object"
                    }
                  }
                }
              }
            }
          }
        }
      },
      "SendSessionMessageResponse": {
        "type": "object",
        "properties": {
          "status": {
            "type": "string",
            "enum": [
              "completed",
              "requires_action"
            ]
          },
          "message": {
            "type": "object",
            "properties": {
              "role": {
                "type": "string"
              },
              "content": {
                "type": "string"
              },
              "model": {
                "type": "string"
              }
            }
          },
          "generation_id": {
            "x-soat-ref": "generations",
            "type": "string"
          },
          "trace_id": {
            "x-soat-ref": "traces",
            "type": "string"
          },
          "required_action": {
            "type": "object",
            "description": "Present when status is requires_action",
            "properties": {
              "tool_calls": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string"
                    },
                    "tool_name": {
                      "type": "string"
                    },
                    "args": {
                      "type": "object"
                    }
                  }
                }
              }
            }
          }
        }
      },
      "SubmitSessionToolOutputsRequest": {
        "type": "object",
        "required": [
          "generation_id",
          "tool_outputs"
        ],
        "properties": {
          "generation_id": {
            "x-soat-ref": "generations",
            "type": "string",
            "description": "The generation ID from the requires_action response"
          },
          "tool_outputs": {
            "type": "array",
            "items": {
              "type": "object",
              "required": [
                "tool_call_id",
                "output"
              ],
              "properties": {
                "tool_call_id": {
                  "type": "string"
                },
                "output": {
                  "description": "The tool output value"
                }
              }
            }
          }
        }
      },
      "Task": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "project_id": {
            "type": "string"
          },
          "workflow_id": {
            "type": "string"
          },
          "workflow_version": {
            "type": "integer",
            "nullable": true,
            "description": "The workflow version this task runs on, fixed when the task was created. Transitions, approval gates and payload validation all resolve through it, so editing the workflow never re-shapes a task already in flight. `null` for tasks created before pinning existed, which run on the live definition.",
            "example": 1
          },
          "title": {
            "type": "string"
          },
          "state": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": [
              "open",
              "closed"
            ]
          },
          "payload": {
            "type": "object",
            "description": "Caller-owned task data; input to guards (as `task.payload`) and dispatch mappings. The engine never writes into it except the workflow's declared `payload_writes`."
          },
          "metadata": {
            "type": "object",
            "additionalProperties": true,
            "nullable": true,
            "description": "The caller-owned key/value metadata supplied when the task was created, returned verbatim. Null when the task was created without any. Unlike `payload` it is invisible to guards and to `payload_writes`, so it is the place for an attribution label rather than task data.",
            "example": {
              "tenant_account_id": "42",
              "source": "zendesk"
            }
          },
          "last_result": {
            "nullable": true,
            "description": "Server-owned. The result of the current state's last completed dispatch, overwritten on every dispatch. Read-only — exposed to transition guards and `on_complete`/`payload_writes` expressions as `task.last_result`, a namespace a caller cannot write."
          },
          "assignee": {
            "type": "string",
            "nullable": true
          },
          "active_dispatch": {
            "type": "object",
            "nullable": true,
            "description": "{ kind, id, status } of the current state's dispatch, if any. `kind` is `generation`, `orchestration_run` or `tool_call`; a `tool_call` always carries a null `id`, since a direct tool call leaves no addressable record. Carries an additional `attempt` (1-based) while the state's `on_enter.retry` policy is in effect."
          },
          "automation_status": {
            "type": "string",
            "nullable": true,
            "enum": [
              "running",
              "completed",
              "failed",
              "unrouted",
              null
            ],
            "description": "Status of the current state's dispatch. `null` until a state with an automation is entered."
          },
          "automation_chain_depth": {
            "type": "integer",
            "description": "Server-owned. How many machine-driven transitions have run back-to-back with no outside intervention — a dispatch outcome routed through `on_complete`/`on_failure`, or a `transition-task` call made by a dispatched run or agent with its run-as token. Any move by a person, a plain API key, or an approval resolution resets it to `0`. Once it would exceed the server's limit (`TASK_AUTOMATION_CHAIN_LIMIT`, default 50) the next such transition is refused with `TASK_AUTOMATION_CHAIN_LIMIT`, bounding a cycle composed across workflows and orchestrations."
          },
          "pending_transition": {
            "type": "string",
            "nullable": true,
            "description": "The name of a `requires_approval` transition parked awaiting a human decision. Non-null while an ApprovalItem gates the move; the task stays in its current state and no other transition may fire until the approval resolves."
          },
          "entered_state_at": {
            "type": "string",
            "format": "date-time"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "TaskTransition": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "task_id": {
            "type": "string"
          },
          "from_state": {
            "type": "string",
            "nullable": true
          },
          "to_state": {
            "type": "string"
          },
          "transition": {
            "type": "string",
            "nullable": true
          },
          "principal_kind": {
            "type": "string",
            "description": "Who made the move. `user` and `api_key` are authenticated principals; `automation` (the engine acting on an `on_enter` dispatch outcome) and `approval` (an approval resolution) are system principals. Named `principal_*`, not `actor_*`: these ids never reference the Actors module.\n",
            "enum": [
              "user",
              "api_key",
              "automation",
              "approval"
            ]
          },
          "principal_id": {
            "type": "string",
            "nullable": true,
            "description": "Public id of the principal that made the move — the user (`user_...`), or for `api_key` auth the key's own id (`key_...`), distinguishing which key acted. Null for `automation`, which has no principal: the cause is carried by `generation_id` / `orchestration_run_id` / `tool_id`, one per dispatch kind — exactly one of which is set on an automation move.\n"
          },
          "generation_id": {
            "type": "string",
            "nullable": true,
            "description": "Set when an `agent` dispatch's generation caused the move."
          },
          "orchestration_run_id": {
            "type": "string",
            "nullable": true,
            "description": "Set when an `orchestration` dispatch's run caused the move."
          },
          "tool_id": {
            "type": "string",
            "nullable": true,
            "description": "Set when a `tool` dispatch caused the move. A tool call produces no addressable record of its own, so the tool it called is what records why the task moved.\n"
          },
          "note": {
            "type": "string",
            "nullable": true
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "CreateTaskRequest": {
        "type": "object",
        "required": [
          "workflow_id",
          "title"
        ],
        "properties": {
          "project_id": {
            "type": "string"
          },
          "workflow_id": {
            "type": "string"
          },
          "title": {
            "type": "string"
          },
          "payload": {
            "type": "object"
          },
          "assignee": {
            "type": "string",
            "nullable": true
          },
          "state": {
            "type": "string",
            "description": "Name of a declared workflow state to create the task in directly, instead of the workflow's `initial` state. Must name a state declared on the workflow, or the request is rejected with `TASK_STATE_NOT_FOUND` (400). Defaults to the `initial` state."
          },
          "tool_context": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            },
            "description": "Key-value pairs forwarded as `X-Soat-Context-<key>` headers on every `http`, `mcp` and `builtin` tool call made by this task's automation dispatches — the agent generations a state's `on_enter` starts, and the agent nodes of any orchestration run it starts. The header name is `X-Soat-Context-` plus the key verbatim; no character is re-cased.\nCreation is the task's first move, so this is the bag the entry state's `on_enter` runs with. Each transition may replace it (see `TransitionTaskRequest.tool_context`).\nThe reserved identity keys (`session_id`, `actor_id`, `actor_external_id`) are stripped in any casing and re-derived server-side, so a task-dispatched generation cannot forge them. A key outside the HTTP header-name grammar is rejected with `INVALID_TOOL_CONTEXT_KEY` (400).\nWrite-only: the stored bag is never returned by any task read, and it is cleared when the task reaches a terminal state."
          },
          "metadata": {
            "type": "object",
            "additionalProperties": true,
            "description": "Caller-supplied key/value metadata attached to the task record for attribution — which of your own tenants the task belongs to, the ticket that raised it, the import batch that created it. Round-trips verbatim on every read of the task, the list included, and survives every transition (a transition supplies no metadata of its own).\n\nThe bag is caller-owned and no key is reserved: everything the engine decides about a task (`state`, `status`, `workflow_version`, `last_result`, `active_dispatch`, the automation fields) is a field of its own and cannot be written from here.\n\nPrefer this over `payload` for anything that is not task data: `payload` is read by every guard as `task.payload` and may be written by the workflow's declared `payload_writes`, so a label parked there is neither invisible to the state machine nor safe from it. A non-object is rejected with `400 VALIDATION_FAILED` and no task is created.",
            "example": {
              "tenant_account_id": "42",
              "source": "zendesk"
            }
          }
        }
      },
      "UpdateTaskRequest": {
        "type": "object",
        "properties": {
          "title": {
            "type": "string"
          },
          "payload": {
            "type": "object",
            "description": "Partial payload, shallow-merged over the existing payload. Omitted keys are preserved; provided keys overwrite. The merged result must satisfy the workflow's payload_schema."
          },
          "assignee": {
            "type": "string",
            "nullable": true
          }
        }
      },
      "TransitionTaskRequest": {
        "type": "object",
        "required": [
          "transition"
        ],
        "properties": {
          "transition": {
            "type": "string"
          },
          "note": {
            "type": "string",
            "nullable": true
          },
          "tool_context": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            },
            "description": "Caller context for the automation dispatches the task makes from here on, forwarded as `X-Soat-Context-<key>` headers on their tool calls.\nSupplying it **replaces** the task's stored bag wholesale; omitting it keeps the current one, so the context follows whoever last moved the task and survives every move that does not speak about it — including an approval gate, a retry, and an automation hop. Send an empty object to clear it without closing the task.\nThe reserved identity keys (`session_id`, `actor_id`, `actor_external_id`) are stripped in any casing and re-derived server-side. A key outside the HTTP header-name grammar is rejected with `INVALID_TOOL_CONTEXT_KEY` (400).\nWrite-only: never returned by a task read, and cleared when the transition closes the task."
          }
        }
      },
      "Tool": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "Public ID of the tool",
            "example": "tool_V1StGXR8Z5jdHi6B"
          },
          "project_id": {
            "x-soat-ref": "projects",
            "type": "string",
            "description": "Public ID of the owning project",
            "example": "proj_V1StGXR8Z5jdHi6B"
          },
          "name": {
            "type": "string",
            "description": "Tool name",
            "example": "get-weather"
          },
          "type": {
            "type": "string",
            "enum": [
              "http",
              "client",
              "mcp",
              "builtin",
              "pipeline"
            ],
            "description": "Tool type",
            "example": "http"
          },
          "description": {
            "type": "string",
            "nullable": true,
            "description": "What the tool does (sent to the model)"
          },
          "parameters": {
            "type": "object",
            "nullable": true,
            "description": "JSON Schema for tool input"
          },
          "execute": {
            "type": "object",
            "nullable": true,
            "description": "Execution config for http tools. Supported fields: `url` (required), `method` (default `POST`), `headers`, and `body_mode`. The `url` may contain `{paramName}` placeholders (e.g. `/users/{userId}`) that are replaced at call time with the corresponding tool argument value (URL-encoded). Arguments consumed as path parameters are excluded from the query string and request body. `body_mode` is `json` (default) or `multipart`. In `multipart` mode the merged tool arguments are sent as a `multipart/form-data` body: scalar fields become plain form fields and a field shaped like `{ content_type, filename, data_base64 }` is decoded from base64 and attached as a file part (the hardcoded `Content-Type: application/json` is dropped so `fetch` sets the multipart boundary itself).\n\n`auth` adds a computed request credential, for targets whose `Authorization` value cannot be expressed as a static header. Supported `auth.type` values:\n\n- `aws_sigv4` — signs the request with AWS Signature Version 4. Requires `region`, `service`, `access_key_id` and `secret_access_key`; `session_token` is optional (temporary credentials). Incompatible with `body_mode: multipart`, whose body bytes are not known at signing time.\n- `gcp_service_account` — mints a Google OAuth 2.0 access token from a signed service account assertion and sends it as a bearer token. Requires `credentials` (the service account key file JSON, as a string) and `scopes` (a non-empty array). Tokens are cached per service account and scope set until shortly before they expire.\n\nCredential fields accept `{{secret:...}}` references and should use them — a tool is readable by anyone who can `GET /tools`, and the stored reference is what is echoed back, never the resolved value.\n\n`headers` values additionally accept `{{context:<key>}}` references, resolved per call from the caller's `tool_context`, so a per-user credential can be placed in the real header the target expects (`Authorization: Bearer {{context:ocaToken}}`) instead of only in a prefixed context header. Valid **only** inside `headers` — a context value is caller-supplied, so it may not steer the `url` — and a key missing from the `tool_context` at call time fails the tool call with `MISSING_TOOL_CONTEXT_KEY` rather than sending an empty credential. See the Tool Context reference.\n"
          },
          "mcp": {
            "type": "object",
            "nullable": true,
            "description": "MCP server config (`url`, `headers`). `headers` values accept `{{secret:...}}` and `{{context:<key>}}` references, resolved right before the outbound MCP request; `url` accepts `{{secret:...}}` only."
          },
          "actions": {
            "type": "array",
            "nullable": true,
            "items": {
              "type": "string"
            },
            "description": "Allowlist of actions to expose. For `builtin` tools: SOAT platform action names (required). For `mcp` tools: an optional allowlist of MCP tool names — when set, only those tools are exposed to the model and callable via `/call`; when `null`, the entire MCP server surface is exposed. Ignored for other tool types."
          },
          "denied_actions": {
            "type": "array",
            "nullable": true,
            "items": {
              "type": "string"
            },
            "description": "For `mcp` tools: an optional denylist of MCP tool names to hide. Applied after `actions` and taking precedence over it — a name in both lists is denied. This is the ergonomic way to scope a read+write MCP server read-only: deny just the write tools instead of enumerating every read tool in `actions`. Names not listed are exposed. `null` (default) denies nothing. Ignored for other tool types."
          },
          "context_keys": {
            "type": "array",
            "nullable": true,
            "items": {
              "type": "string"
            },
            "description": "Optional allowlist of `tool_context` keys that may be forwarded to this tool as prefixed context headers (`X-Soat-Context-<key>` by default). When `null`, every key in the caller's `tool_context` is forwarded — the behavior of every tool created before this field existed. When set, only the listed keys are, so a per-user credential in `tool_context` can be confined to the tools that need it; `[]` forwards none. The server-pinned identity keys (`session_id`, `actor_id`, `actor_external_id`) are always forwarded. A key consumed by a `{{context:<key>}}` token in this tool's own headers is substituted regardless of this list — the tool declared that header itself."
          },
          "preset_parameters": {
            "type": "object",
            "nullable": true,
            "description": "Fixed parameters pinned on every call this tool makes, whatever its type. Keys matching fields in the input schema are removed from the schema shown to the model, and a pinned value wins over one the model or a direct caller supplies for the same key.\n\nValues accept `{{context:<key>}}` references, resolved per call from the caller's `tool_context`, so a pin can be the run's own value — the one account this run may act on — rather than one fixed when the tool was created. A resolved value is retyped to the parameter's declared schema type; a key missing from the call's `tool_context` fails the call with `MISSING_TOOL_CONTEXT_KEY` rather than sending the literal placeholder. `{{secret:...}}` is not resolved here. See the Tool Context reference."
          },
          "pipeline": {
            "type": "object",
            "nullable": true,
            "description": "Pipeline definition for `pipeline` tools: an ordered `steps` array, each step invoking a tool (optional `action`) and building its `input` from earlier results via JSON Logic evaluated over `{ input, steps }`. A step references its tool either by `tool_id` (an existing, persisted tool) or by an inline `tool` definition — the same shape as `CreateToolRequest` minus `project_id` — executed directly without a Tool row, but never both. An inline step `tool` cannot itself be of type `pipeline`. An optional `output` maps the final result."
          },
          "output_mapping": {
            "type": "object",
            "nullable": true,
            "description": "Universal JSON Logic mapping applied to the tool's raw result, for every tool type (`http`, `mcp`, `builtin`, `pipeline`, `client`). Evaluated over `{ output: <raw result>, input: <merged input> }`, so `{ \"var\": \"output.text\" }` extracts a bare scalar field instead of requiring a wrapping `pipeline` tool, and `{ \"var\": \"input.title\" }` echoes back a field of the request that produced the response. For `pipeline` tools this runs *after* the pipeline's own `output` mapping, over that mapping's result."
          },
          "guardrail_ids": {
            "x-soat-ref": "guardrails",
            "type": "array",
            "nullable": true,
            "items": {
              "type": "string"
            },
            "description": "Guardrails attached at the tool scope, governing this tool wherever it is used, by any agent."
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "CreateToolRequest": {
        "type": "object",
        "required": [
          "name"
        ],
        "properties": {
          "project_id": {
            "x-soat-ref": "projects",
            "type": "string",
            "description": "Public ID of the project"
          },
          "name": {
            "type": "string",
            "description": "Tool name"
          },
          "type": {
            "type": "string",
            "enum": [
              "http",
              "client",
              "mcp",
              "builtin",
              "pipeline"
            ],
            "description": "Tool type (default http)"
          },
          "description": {
            "type": "string",
            "description": "What the tool does"
          },
          "parameters": {
            "type": "object",
            "description": "JSON Schema for tool input"
          },
          "execute": {
            "type": "object",
            "description": "Execution config for http tools. Supported fields: `url` (required), `method` (default `POST`), `headers`, and `body_mode`. The `url` may contain `{paramName}` placeholders (e.g. `/users/{userId}`) that are replaced at call time with the corresponding tool argument value (URL-encoded). Arguments consumed as path parameters are excluded from the query string and request body. `body_mode` is `json` (default) or `multipart`. In `multipart` mode the merged tool arguments are sent as a `multipart/form-data` body: scalar fields become plain form fields and a field shaped like `{ content_type, filename, data_base64 }` is decoded from base64 and attached as a file part (the hardcoded `Content-Type: application/json` is dropped so `fetch` sets the multipart boundary itself).\n\n`auth` adds a computed request credential, for targets whose `Authorization` value cannot be expressed as a static header. Supported `auth.type` values:\n\n- `aws_sigv4` — signs the request with AWS Signature Version 4. Requires `region`, `service`, `access_key_id` and `secret_access_key`; `session_token` is optional (temporary credentials). Incompatible with `body_mode: multipart`, whose body bytes are not known at signing time.\n- `gcp_service_account` — mints a Google OAuth 2.0 access token from a signed service account assertion and sends it as a bearer token. Requires `credentials` (the service account key file JSON, as a string) and `scopes` (a non-empty array). Tokens are cached per service account and scope set until shortly before they expire.\n\nCredential fields accept `{{secret:...}}` references and should use them — a tool is readable by anyone who can `GET /tools`, and the stored reference is what is echoed back, never the resolved value.\n\n`headers` values additionally accept `{{context:<key>}}` references, resolved per call from the caller's `tool_context`, so a per-user credential can be placed in the real header the target expects (`Authorization: Bearer {{context:ocaToken}}`) instead of only in an `X-Soat-Context-<key>` header. Valid **only** inside `headers` — a context value is caller-supplied, so it may not steer the `url` — and a key missing from the `tool_context` at call time fails the tool call with `MISSING_TOOL_CONTEXT_KEY` rather than sending an empty credential. See the Tool Context reference.\n"
          },
          "mcp": {
            "type": "object",
            "description": "MCP server config (`url`, `headers`). `headers` values accept `{{secret:...}}` and `{{context:<key>}}` references, resolved right before the outbound MCP request; `url` accepts `{{secret:...}}` only."
          },
          "actions": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Allowlist of actions. For `builtin` tools: SOAT platform action names (required). For `mcp` tools: an optional allowlist of MCP tool names to scope the server surface — omit or set `null` to expose every tool the MCP server offers. Ignored for other tool types."
          },
          "denied_actions": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "For `mcp` tools: an optional denylist of MCP tool names to hide. Applied after `actions` and taking precedence over it. Use it to scope a read+write MCP server read-only by denying just the write tools. Omit or set `null` to deny nothing. Ignored for other tool types."
          },
          "context_keys": {
            "type": "array",
            "nullable": true,
            "items": {
              "type": "string"
            },
            "description": "Optional allowlist of `tool_context` keys that may be forwarded to this tool as prefixed context headers (`X-Soat-Context-<key>` by default). When `null` or omitted, every key in the caller's `tool_context` is forwarded — the behavior of every tool created before this field existed. When set, only the listed keys are, so a per-user credential in `tool_context` can be confined to the tools that need it; `[]` forwards none. The server-pinned identity keys (`session_id`, `actor_id`, `actor_external_id`) are always forwarded. A key consumed by a `{{context:<key>}}` token in this tool's own headers is substituted regardless of this list — the tool declared that header itself."
          },
          "preset_parameters": {
            "type": "object",
            "description": "Fixed parameters pinned on every call this tool makes, whatever its type. Keys matching fields in the input schema are removed from the schema shown to the model, and a pinned value wins over one the model or a direct caller supplies for the same key.\n\nValues accept `{{context:<key>}}` references, resolved per call from the caller's `tool_context`, so a pin can be the run's own value — the one account this run may act on — rather than one fixed when the tool was created. A resolved value is retyped to the parameter's declared schema type; a key missing from the call's `tool_context` fails the call with `MISSING_TOOL_CONTEXT_KEY` rather than sending the literal placeholder. `{{secret:...}}` is not resolved here. See the Tool Context reference."
          },
          "pipeline": {
            "type": "object",
            "description": "Pipeline definition for `pipeline` tools. See the `pipeline` field on the Tool schema for the full structure."
          },
          "output_mapping": {
            "type": "object",
            "description": "Universal JSON Logic mapping applied to the tool's raw result. See the `output_mapping` field on the Tool schema for details."
          },
          "guardrail_ids": {
            "x-soat-ref": "guardrails",
            "type": "array",
            "nullable": true,
            "items": {
              "type": "string"
            },
            "description": "Guardrails attached at the tool scope."
          }
        }
      },
      "UpdateToolRequest": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string"
          },
          "type": {
            "type": "string",
            "enum": [
              "http",
              "client",
              "mcp",
              "builtin",
              "pipeline"
            ]
          },
          "description": {
            "type": "string",
            "nullable": true
          },
          "parameters": {
            "type": "object",
            "nullable": true
          },
          "execute": {
            "type": "object",
            "nullable": true,
            "description": "Execution config for http tools. Supported fields: `url` (required), `method` (default `POST`), `headers`, and `body_mode`. The `url` may contain `{paramName}` placeholders (e.g. `/users/{userId}`) that are replaced at call time with the corresponding tool argument value (URL-encoded). Arguments consumed as path parameters are excluded from the query string and request body. `body_mode` is `json` (default) or `multipart`. In `multipart` mode the merged tool arguments are sent as a `multipart/form-data` body: scalar fields become plain form fields and a field shaped like `{ content_type, filename, data_base64 }` is decoded from base64 and attached as a file part (the hardcoded `Content-Type: application/json` is dropped so `fetch` sets the multipart boundary itself).\n\n`auth` adds a computed request credential, for targets whose `Authorization` value cannot be expressed as a static header. Supported `auth.type` values:\n\n- `aws_sigv4` — signs the request with AWS Signature Version 4. Requires `region`, `service`, `access_key_id` and `secret_access_key`; `session_token` is optional (temporary credentials). Incompatible with `body_mode: multipart`, whose body bytes are not known at signing time.\n- `gcp_service_account` — mints a Google OAuth 2.0 access token from a signed service account assertion and sends it as a bearer token. Requires `credentials` (the service account key file JSON, as a string) and `scopes` (a non-empty array). Tokens are cached per service account and scope set until shortly before they expire.\n\nCredential fields accept `{{secret:...}}` references and should use them — a tool is readable by anyone who can `GET /tools`, and the stored reference is what is echoed back, never the resolved value.\n\n`headers` values additionally accept `{{context:<key>}}` references, resolved per call from the caller's `tool_context`, so a per-user credential can be placed in the real header the target expects (`Authorization: Bearer {{context:ocaToken}}`) instead of only in an `X-Soat-Context-<key>` header. Valid **only** inside `headers` — a context value is caller-supplied, so it may not steer the `url` — and a key missing from the `tool_context` at call time fails the tool call with `MISSING_TOOL_CONTEXT_KEY` rather than sending an empty credential. See the Tool Context reference.\n"
          },
          "mcp": {
            "type": "object",
            "nullable": true,
            "description": "MCP server config (`url`, `headers`). `headers` values accept `{{secret:...}}` and `{{context:<key>}}` references, resolved right before the outbound MCP request; `url` accepts `{{secret:...}}` only."
          },
          "actions": {
            "type": "array",
            "nullable": true,
            "items": {
              "type": "string"
            },
            "description": "Allowlist of actions. For `builtin` tools: SOAT platform action names. For `mcp` tools: an optional allowlist of MCP tool names to scope the server surface (`null` exposes every tool). Ignored for other tool types."
          },
          "denied_actions": {
            "type": "array",
            "nullable": true,
            "items": {
              "type": "string"
            },
            "description": "For `mcp` tools: an optional denylist of MCP tool names to hide. Applied after `actions` and taking precedence over it. Use it to scope a read+write MCP server read-only by denying just the write tools. `null` denies nothing. Ignored for other tool types."
          },
          "context_keys": {
            "type": "array",
            "nullable": true,
            "items": {
              "type": "string"
            },
            "description": "Optional allowlist of `tool_context` keys that may be forwarded to this tool as prefixed context headers (`X-Soat-Context-<key>` by default). When `null` or omitted, every key in the caller's `tool_context` is forwarded — the behavior of every tool created before this field existed. When set, only the listed keys are, so a per-user credential in `tool_context` can be confined to the tools that need it; `[]` forwards none. The server-pinned identity keys (`session_id`, `actor_id`, `actor_external_id`) are always forwarded. A key consumed by a `{{context:<key>}}` token in this tool's own headers is substituted regardless of this list — the tool declared that header itself."
          },
          "preset_parameters": {
            "type": "object",
            "nullable": true,
            "description": "Fixed parameters pinned on every call this tool makes, whatever its type. Keys matching fields in the input schema are removed from the schema shown to the model, and a pinned value wins over one the model or a direct caller supplies for the same key.\n\nValues accept `{{context:<key>}}` references, resolved per call from the caller's `tool_context`, so a pin can be the run's own value — the one account this run may act on — rather than one fixed when the tool was created. A resolved value is retyped to the parameter's declared schema type; a key missing from the call's `tool_context` fails the call with `MISSING_TOOL_CONTEXT_KEY` rather than sending the literal placeholder. `{{secret:...}}` is not resolved here. See the Tool Context reference."
          },
          "pipeline": {
            "type": "object",
            "nullable": true,
            "description": "Pipeline definition for `pipeline` tools. See the `pipeline` field on the Tool schema for the full structure."
          },
          "output_mapping": {
            "type": "object",
            "nullable": true,
            "description": "Universal JSON Logic mapping applied to the tool's raw result. See the `output_mapping` field on the Tool schema for details."
          },
          "guardrail_ids": {
            "x-soat-ref": "guardrails",
            "type": "array",
            "nullable": true,
            "items": {
              "type": "string"
            },
            "description": "Guardrails attached at the tool scope."
          }
        }
      },
      "CallToolRequest": {
        "type": "object",
        "properties": {
          "action": {
            "type": "string",
            "description": "For `builtin` tools: the action name (must be in the tool's `actions` list). For `mcp` tools: the MCP tool name to invoke (must be in the tool's `actions` allowlist when one is set, and must not be in its `denied_actions` denylist). Ignored for `http` tools.\n"
          },
          "input": {
            "type": "object",
            "description": "Input parameters for the tool call. These are merged with the tool's `preset_parameters` before execution; a preset value wins over the same key sent here.\n",
            "additionalProperties": true
          },
          "tool_context": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            },
            "description": "Key/value context for this call, forwarded to the tool as `X-Soat-Context-<key>` request headers and resolving any `{{context:<key>}}` token in the tool's `execute.headers`, `mcp.headers` or `preset_parameters`. Narrowed by the tool's `context_keys` allowlist when it sets one.\nThis route has no session, so it stamps no server-derived identity: the reserved keys `session_id`, `actor_id` and `actor_external_id` are dropped from this bag (in any casing) rather than forwarded, so a downstream tool can still trust that a context header naming one is server-derived. Every other key becomes an HTTP header name and must match that grammar, or the call fails with `INVALID_TOOL_CONTEXT_KEY`.\n",
            "example": {
              "tenantId": "acme",
              "userToken": "tok_abc123"
            }
          }
        }
      },
      "Trace": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "Public ID of the trace",
            "example": "trace_V1StGXR8Z5jdHi6B"
          },
          "project_id": {
            "x-soat-ref": "projects",
            "type": "string",
            "description": "Public ID of the project"
          },
          "agent_id": {
            "x-soat-ref": "agents",
            "type": "string",
            "description": "Public ID of the agent that produced this trace"
          },
          "file_id": {
            "x-soat-ref": "files",
            "type": "string",
            "nullable": true,
            "description": "Public ID of the File containing the full serialized steps JSON. Null if the trace has not been saved yet (save is fire-and-forget).\n",
            "example": "file_xyz789"
          },
          "step_count": {
            "type": "integer",
            "description": "Number of steps recorded in this trace",
            "example": 2
          },
          "parent_trace_id": {
            "x-soat-ref": "traces",
            "type": "string",
            "nullable": true,
            "description": "Public ID of the parent trace. Null if this trace is the root (i.e., it was not triggered by a sub-agent call from another trace).\n"
          },
          "root_trace_id": {
            "x-soat-ref": "traces",
            "type": "string",
            "nullable": true,
            "description": "Public ID of the root trace for the entire execution tree. Null if this trace is itself the root.\n"
          },
          "error": {
            "type": "object",
            "nullable": true,
            "description": "Structured error payload recorded when a generation in this trace failed (e.g. an upstream AI provider error). Null if no failure has been recorded.\n",
            "properties": {
              "code": {
                "type": "string",
                "example": "AI_PROVIDER_ERROR"
              },
              "message": {
                "type": "string",
                "example": "Provider returned 402: insufficient credits"
              },
              "meta": {
                "type": "object"
              }
            }
          },
          "content_redacted_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true,
            "description": "When the trace's content was purged. Non-null means the steps object has been deleted from storage and the content columns cleared, while this row survives as an auditable skeleton (ids, timestamps, step count). A purged trace still reads back as a skeleton with this marker set rather than as a 404, so the erasure is provable.\n"
          },
          "content_redacted_by_principal_type": {
            "type": "string",
            "nullable": true,
            "description": "Principal kind that purged the content ('user' or 'api_key')",
            "example": "user"
          },
          "content_redacted_by_principal_id": {
            "type": "string",
            "nullable": true,
            "description": "Public ID of the principal that purged the content — the API key's own id for key auth, so the record names which key acted.\n"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "TraceTreeNode": {
        "type": "object",
        "description": "A trace node in the execution tree, with nested children.",
        "properties": {
          "id": {
            "type": "string",
            "description": "Public ID of the trace"
          },
          "project_id": {
            "x-soat-ref": "projects",
            "type": "string"
          },
          "agent_id": {
            "x-soat-ref": "agents",
            "type": "string"
          },
          "file_id": {
            "x-soat-ref": "files",
            "type": "string",
            "nullable": true
          },
          "step_count": {
            "type": "integer"
          },
          "parent_trace_id": {
            "x-soat-ref": "traces",
            "type": "string",
            "nullable": true
          },
          "root_trace_id": {
            "x-soat-ref": "traces",
            "type": "string",
            "nullable": true
          },
          "error": {
            "type": "object",
            "nullable": true,
            "description": "Structured error payload recorded when a generation in this trace failed"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "content_redacted_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true,
            "description": "When the trace's content was purged. Non-null means the steps object has been deleted from storage and the content columns cleared, while this row survives as an auditable skeleton (ids, timestamps, step count). A purged trace still reads back as a skeleton with this marker set rather than as a 404, so the erasure is provable.\n"
          },
          "content_redacted_by_principal_type": {
            "type": "string",
            "nullable": true,
            "description": "Principal kind that purged the content ('user' or 'api_key')",
            "example": "user"
          },
          "content_redacted_by_principal_id": {
            "type": "string",
            "nullable": true,
            "description": "Public ID of the principal that purged the content — the API key's own id for key auth, so the record names which key acted.\n"
          },
          "children": {
            "type": "array",
            "description": "Child traces triggered by sub-agent calls from this trace",
            "items": {
              "$ref": "#/components/schemas/TraceTreeNode"
            }
          },
          "generations": {
            "type": "array",
            "description": "Generations that belong to this trace node. Only present when `include=generations` is requested. Includes top-level generations and sub-agent child generations linked via `initiator_generation_id`.\n",
            "items": {
              "$ref": "#/components/schemas/Generation"
            }
          }
        }
      },
      "Trigger": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "project_id": {
            "x-soat-ref": "projects",
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "description": {
            "type": "string",
            "nullable": true
          },
          "type": {
            "type": "string",
            "enum": [
              "manual",
              "webhook",
              "schedule",
              "event"
            ]
          },
          "target_type": {
            "type": "string",
            "enum": [
              "orchestration",
              "agent",
              "tool",
              "eval"
            ]
          },
          "target_id": {
            "type": "string",
            "description": "Public ID of the target resource (orchestration, agent, tool, or eval)"
          },
          "action": {
            "type": "string",
            "nullable": true,
            "description": "Tool targets only — the action for builtin/mcp tools"
          },
          "input": {
            "type": "object",
            "nullable": true,
            "description": "Static input, shallow-merged under fire-time input. For `eval` targets the effective input may carry `agent_version` and `baseline_run_id`, which are passed to the queued run."
          },
          "cron": {
            "type": "string",
            "nullable": true,
            "description": "5-field cron expression (UTC). Present only for schedule triggers"
          },
          "event_pattern": {
            "type": "string",
            "nullable": true,
            "description": "Internal-event subscription pattern. Present only for event triggers: `*`, `prefix.*`, or an exact event name"
          },
          "active": {
            "type": "boolean"
          },
          "policy_id": {
            "x-soat-ref": "policies",
            "type": "string",
            "nullable": true,
            "description": "Optional boundary policy that further restricts firings"
          },
          "next_fire_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true,
            "description": "Read-only, schedule triggers only. Server-computed next fire time"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "TriggerWithSecret": {
        "allOf": [
          {
            "$ref": "#/components/schemas/Trigger"
          },
          {
            "type": "object",
            "properties": {
              "secret": {
                "type": "string",
                "description": "Webhook triggers only. Returned only on create and rotate"
              }
            }
          }
        ]
      },
      "CreateTriggerRequest": {
        "type": "object",
        "required": [
          "name",
          "type",
          "target_type",
          "target_id"
        ],
        "properties": {
          "project_id": {
            "x-soat-ref": "projects",
            "type": "string",
            "description": "Public ID of the project. Optional when authenticating with a project-scoped API key, which defaults to the key's project; required otherwise."
          },
          "name": {
            "type": "string"
          },
          "description": {
            "type": "string"
          },
          "type": {
            "type": "string",
            "enum": [
              "manual",
              "webhook",
              "schedule",
              "event"
            ]
          },
          "target_type": {
            "type": "string",
            "enum": [
              "orchestration",
              "agent",
              "tool",
              "eval"
            ]
          },
          "target_id": {
            "type": "string"
          },
          "action": {
            "type": "string",
            "description": "Tool targets only — the action for builtin/mcp tools"
          },
          "input": {
            "type": "object"
          },
          "cron": {
            "type": "string",
            "description": "5-field cron expression (UTC). Required when type is schedule"
          },
          "event_pattern": {
            "type": "string",
            "description": "Internal-event subscription pattern. Required when type is event, rejected otherwise. `*` matches every event, `prefix.*` a namespace, or give an exact event name such as `documents.ingested`"
          },
          "active": {
            "type": "boolean",
            "default": true
          },
          "policy_id": {
            "x-soat-ref": "policies",
            "type": "string"
          }
        }
      },
      "UpdateTriggerRequest": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string"
          },
          "description": {
            "type": "string",
            "nullable": true
          },
          "target_type": {
            "type": "string",
            "enum": [
              "orchestration",
              "agent",
              "tool",
              "eval"
            ]
          },
          "target_id": {
            "type": "string"
          },
          "action": {
            "type": "string",
            "nullable": true
          },
          "input": {
            "type": "object",
            "nullable": true
          },
          "cron": {
            "type": "string",
            "nullable": true
          },
          "event_pattern": {
            "type": "string",
            "nullable": true
          },
          "active": {
            "type": "boolean"
          },
          "policy_id": {
            "x-soat-ref": "policies",
            "type": "string",
            "nullable": true
          }
        }
      },
      "FireTriggerRequest": {
        "type": "object",
        "properties": {
          "input": {
            "type": "object",
            "description": "Fire-time input, shallow-merged over the trigger's static input. For `eval` targets it may carry `agent_version` and `baseline_run_id`."
          }
        }
      },
      "TriggerSecretResponse": {
        "type": "object",
        "properties": {
          "secret": {
            "type": "string"
          }
        }
      },
      "TriggerFiring": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "trigger_id": {
            "x-soat-ref": "triggers",
            "type": "string"
          },
          "project_id": {
            "x-soat-ref": "projects",
            "type": "string"
          },
          "source": {
            "type": "string",
            "enum": [
              "manual",
              "webhook",
              "schedule",
              "event"
            ]
          },
          "status": {
            "type": "string",
            "enum": [
              "pending",
              "running",
              "succeeded",
              "failed"
            ]
          },
          "input": {
            "type": "object",
            "nullable": true
          },
          "result": {
            "type": "object",
            "nullable": true,
            "description": "{ target_type, result_id, status, output } — output truncated"
          },
          "error": {
            "type": "object",
            "nullable": true,
            "description": "{ code, message, meta }"
          },
          "started_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "completed_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "TriggerFiringListResponse": {
        "type": "object",
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/TriggerFiring"
            }
          },
          "total": {
            "type": "integer"
          },
          "limit": {
            "type": "integer"
          },
          "offset": {
            "type": "integer"
          }
        }
      },
      "UsageComponent": {
        "type": "object",
        "description": "One priced dimension of a usage event. Every meter type is expressed as components, so tokens and infra are uniform: an `llm_tokens` event has `input_tokens` / `output_tokens` (+ `cached_tokens`, and a non-billable `reasoning_tokens` detail), a `compute_execution` event has one `compute_second` component.\n",
        "properties": {
          "component": {
            "type": "string",
            "description": "The measured dimension (e.g. `input_tokens`, `compute_second`)"
          },
          "quantity": {
            "type": "number",
            "description": "The measured amount, expressed in `unit`"
          },
          "unit": {
            "type": "string",
            "description": "Unit `quantity` is measured in (`token`, `compute_second`, …)"
          },
          "billable": {
            "type": "boolean",
            "description": "Whether this component contributes to cost. Non-billable details (e.g. `reasoning_tokens`, a subset of `output_tokens`) are never priced and never double-counted into billable totals.\n"
          },
          "unit_price": {
            "type": "number",
            "nullable": true,
            "description": "USD per `unit`, frozen at write time; null when unpriced"
          },
          "cost_usd": {
            "type": "number",
            "nullable": true,
            "description": "quantity × unit_price, frozen at write time; null when unpriced"
          },
          "price_id": {
            "type": "string",
            "nullable": true,
            "description": "Public ID of the price-book row that priced this component"
          }
        }
      },
      "UsageEvent": {
        "type": "object",
        "description": "One metered occurrence (a completed LLM call, a node execution, …). Attribution and total cost live here; the measured quantities live in `components`.\n",
        "properties": {
          "id": {
            "type": "string",
            "description": "Public ID of the usage event",
            "example": "ue_V1StGXR8Z5jdHi6B"
          },
          "project_id": {
            "x-soat-ref": "projects",
            "type": "string",
            "description": "Public ID of the project the usage is attributed to"
          },
          "orchestration_run_id": {
            "type": "string",
            "nullable": true,
            "description": "Public ID of the orchestration run that initiated the occurrence. Null for standalone events.\n"
          },
          "node_id": {
            "type": "string",
            "nullable": true,
            "description": "Orchestration node ID within the run, when applicable"
          },
          "agent_id": {
            "x-soat-ref": "agents",
            "type": "string",
            "nullable": true,
            "description": "Public ID of the agent that ran the generation"
          },
          "generation_id": {
            "x-soat-ref": "generations",
            "type": "string",
            "nullable": true,
            "description": "Public ID of the generation this usage was recorded for"
          },
          "trace_id": {
            "x-soat-ref": "traces",
            "type": "string",
            "nullable": true,
            "description": "Public ID of the trace this usage belongs to"
          },
          "actor_id": {
            "x-soat-ref": "actors",
            "type": "string",
            "nullable": true,
            "description": "Public ID of the actor (end user) the metered occurrence was produced for, frozen at write time. Null when no end user is behind the work — orchestration runs, triggers, direct API generations.\n"
          },
          "session_id": {
            "x-soat-ref": "sessions",
            "type": "string",
            "nullable": true,
            "description": "Public ID of the session the metered occurrence ran in, frozen at write time. Null for work not dispatched through a session.\n"
          },
          "ai_provider_id": {
            "x-soat-ref": "ai-providers",
            "type": "string",
            "nullable": true,
            "description": "Public ID of the AI provider instance billed — the target a model route picked for the turn, or the agent's pinned provider. Null if the provider was later deleted; the `provider`/`model` snapshot still records what was billed.\n"
          },
          "trigger_id": {
            "x-soat-ref": "triggers",
            "type": "string",
            "nullable": true,
            "description": "Public ID of the trigger that initiated the generation (agent-target triggers). Null when not started by a trigger.\n"
          },
          "action_id": {
            "type": "string",
            "nullable": true,
            "description": "Caller-supplied logical action label, for rolling spend up per action. Null when the caller did not label the action.\n"
          },
          "meter_type": {
            "type": "string",
            "description": "What the event measures: `llm_tokens`, `compute_execution`, `api_request`, or `storage`.\n"
          },
          "source": {
            "type": "string",
            "nullable": true,
            "description": "What the spend was incurred for. `eval` is an eval run's item generations and `eval_judge` an `llm_judge` scorer's own completion, so verification spend is `source` in (`eval`, `eval_judge`) and the two are distinct so running a suite can be priced apart from grading it. Null for ordinary agent traffic.\n",
            "example": "eval"
          },
          "provider": {
            "type": "string",
            "description": "As-billed SKU vendor slug, retained even if the AI provider is deleted. `soat` for platform meter types.\n",
            "example": "openai"
          },
          "model": {
            "type": "string",
            "description": "Model id, or the billable SKU for platform meter types",
            "example": "gpt-4o"
          },
          "cost_usd": {
            "type": "number",
            "nullable": true,
            "description": "Total USD cost — the sum of the priced component costs, frozen at write time. Null when no component was priced.\n"
          },
          "components": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/UsageComponent"
            }
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "UsageAggregateComponent": {
        "type": "object",
        "description": "One measured dimension of a bucket, summed across its events. This is what makes the rollup uniform over meter types: the token fields only describe `llm_tokens`, so an infra meter's amount is read here.\n",
        "properties": {
          "component": {
            "type": "string",
            "description": "The measured dimension: `input_tokens`, `cached_tokens`, `output_tokens`, `reasoning_tokens`, `compute_second`, `request`, `gb_day`, …\n"
          },
          "unit": {
            "type": "string",
            "description": "Unit `quantity` is measured in: `token`, `compute_second`, `request`, `gb_day`.\n"
          },
          "quantity": {
            "type": "number",
            "description": "Summed measured amount in `unit`. Non-integer for fractional measures (GB-days, compute seconds).\n",
            "example": 0.4
          },
          "cost_usd": {
            "type": "number",
            "nullable": true,
            "description": "Sum of the priced component costs; null when no price row covered the component — the quantity is still captured, it does not mean the usage was free.\n"
          }
        }
      },
      "UsageAggregateTotals": {
        "type": "object",
        "description": "Summed token counts, measured component quantities, and cost for a bucket (or the grand total).\n",
        "properties": {
          "cost_usd": {
            "type": "number",
            "nullable": true,
            "description": "Sum of priced event costs in the bucket; null when nothing is priced"
          },
          "input_tokens": {
            "type": "integer",
            "description": "Full prompt tokens (uncached input + cached), reconstructed from components"
          },
          "output_tokens": {
            "type": "integer"
          },
          "cached_tokens": {
            "type": "integer"
          },
          "reasoning_tokens": {
            "type": "integer"
          },
          "components": {
            "type": "array",
            "description": "Every component measured in the bucket, sorted by `component` then `unit`. The token fields above cover `llm_tokens` only, so this is where a `storage` / `api_request` / `compute_execution` bucket reports what it actually measured.\n",
            "items": {
              "$ref": "#/components/schemas/UsageAggregateComponent"
            }
          }
        }
      },
      "UsageAggregate": {
        "type": "object",
        "properties": {
          "project_id": {
            "x-soat-ref": "projects",
            "type": "string"
          },
          "from": {
            "type": "string",
            "format": "date-time",
            "nullable": true,
            "description": "Lower bound applied, echoed back; null when unbounded"
          },
          "to": {
            "type": "string",
            "format": "date-time",
            "nullable": true,
            "description": "Upper bound applied, echoed back; null when unbounded"
          },
          "group_by": {
            "type": "string",
            "enum": [
              "model",
              "ai_provider",
              "agent",
              "run",
              "day",
              "meter_type",
              "actor",
              "session",
              "source"
            ]
          },
          "meter_type": {
            "type": "string",
            "nullable": true,
            "description": "Meter-type filter applied, echoed back; null when unfiltered"
          },
          "groups": {
            "type": "array",
            "description": "One entry per distinct value in the chosen dimension.",
            "items": {
              "allOf": [
                {
                  "type": "object",
                  "properties": {
                    "key": {
                      "type": "string",
                      "nullable": true,
                      "description": "The bucket's value in the chosen dimension (a model id, meter type, AI provider / agent / run / actor / session public id, or `YYYY-MM-DD` UTC day). Null when the dimension does not apply to an event — under `actor`/`session` this is the bucket holding everything with no end user behind it.\n"
                    },
                    "ai_provider_id": {
                      "type": "string",
                      "x-soat-ref": "ai-providers",
                      "nullable": true,
                      "description": "Under `group_by=model`, the provider that served the bucket's model; null on every other dimension. A model id does not identify its provider on its own — one project can hold two providers serving byte-identical model names — so the model dimension buckets on (model, `ai_provider_id`) and two groups may repeat one `key` with different providers. The groups still sum to `totals`.\n"
                    }
                  }
                },
                {
                  "$ref": "#/components/schemas/UsageAggregateTotals"
                }
              ]
            }
          },
          "totals": {
            "$ref": "#/components/schemas/UsageAggregateTotals"
          }
        }
      },
      "UsageThreshold": {
        "type": "object",
        "description": "A per-project alert rule on windowed usage. When the project's `metric` over `window` crosses `threshold`, a `usage.threshold_crossed` webhook fires. Fire state is enforced with once-per-window (calendar) / 10% re-arm (rolling) hysteresis.\n",
        "properties": {
          "id": {
            "type": "string",
            "description": "Public ID of the threshold",
            "example": "uthr_V1StGXR8Z5jdHi6B"
          },
          "project_id": {
            "x-soat-ref": "projects",
            "type": "string"
          },
          "metric": {
            "type": "string",
            "enum": [
              "cost_usd",
              "tokens"
            ],
            "description": "What is measured: `cost_usd` (across all meter types) or `tokens` (input + output + cached).\n"
          },
          "window": {
            "type": "string",
            "enum": [
              "calendar_month",
              "rolling_24h"
            ],
            "description": "The evaluation window: the current UTC calendar month, or the trailing 24 hours.\n"
          },
          "threshold": {
            "type": "number",
            "description": "The value the windowed aggregate must cross to fire"
          },
          "last_fired_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true,
            "description": "When the threshold last fired; null until first fire"
          },
          "fired_window_key": {
            "type": "string",
            "nullable": true,
            "description": "The `YYYY-MM` window key of the last fire (calendar_month hysteresis); null for rolling_24h and before the first fire.\n"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "CreateUsageThresholdRequest": {
        "type": "object",
        "required": [
          "project_id",
          "metric",
          "window",
          "threshold"
        ],
        "properties": {
          "project_id": {
            "x-soat-ref": "projects",
            "type": "string"
          },
          "metric": {
            "type": "string",
            "enum": [
              "cost_usd",
              "tokens"
            ]
          },
          "window": {
            "type": "string",
            "enum": [
              "calendar_month",
              "rolling_24h"
            ]
          },
          "threshold": {
            "type": "number",
            "description": "Must be greater than 0"
          }
        }
      },
      "UsageReceipt": {
        "type": "object",
        "properties": {
          "generation_id": {
            "description": "Present on a per-generation receipt; absent on a per-run receipt.\n",
            "x-soat-ref": "generations",
            "type": "string"
          },
          "orchestration_run_id": {
            "description": "Present on a per-run receipt (summed across the run's meters); absent on a per-generation receipt.\n",
            "x-soat-ref": "orchestration-runs",
            "type": "string"
          },
          "currency": {
            "type": "string",
            "example": "USD"
          },
          "line_items": {
            "type": "array",
            "description": "One line per usage event — for a generation receipt, the events on that generation; for a run receipt, every event across the run.\n",
            "items": {
              "type": "object",
              "properties": {
                "event_id": {
                  "type": "string"
                },
                "meter_type": {
                  "type": "string"
                },
                "provider": {
                  "type": "string"
                },
                "model": {
                  "type": "string"
                },
                "node_id": {
                  "type": "string",
                  "nullable": true,
                  "description": "Orchestration node that produced the event. On a run receipt every line carries it — an `agent` node's `llm_tokens` line and the `compute_execution` line of each node execution alike — so grouping the lines by `node_id` gives the per-node cost the receipt total alone hides. A retried node contributes one line per attempt, all under the same `node_id` — the event records no attempt number — which is the intended reading for spend: a retry is real money. Null when no node produced the event (a standalone generation, a run-level meter).\n"
                },
                "cost_usd": {
                  "type": "number",
                  "nullable": true
                },
                "components": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/UsageComponent"
                  }
                }
              }
            }
          },
          "by_meter_type": {
            "type": "array",
            "description": "Per-meter-type cost rollup — the \"tokens + infra\" split. A single-type receipt has one entry whose cost equals the receipt total.\n",
            "items": {
              "type": "object",
              "properties": {
                "meter_type": {
                  "type": "string"
                },
                "cost_usd": {
                  "type": "number",
                  "nullable": true
                }
              }
            }
          },
          "total_input_tokens": {
            "type": "integer",
            "description": "Full prompt tokens (uncached input + cached), reconstructed from components"
          },
          "total_output_tokens": {
            "type": "integer"
          },
          "total_cached_tokens": {
            "type": "integer"
          },
          "total_reasoning_tokens": {
            "type": "integer"
          },
          "total_cost_usd": {
            "type": "number",
            "nullable": true,
            "description": "Sum of priced component costs; null when nothing is priced"
          }
        }
      },
      "Price": {
        "type": "object",
        "description": "A versioned unit price for one billable component of a SKU. Cost is uniform across meter types — quantity × unit_price.\n",
        "properties": {
          "id": {
            "type": "string",
            "description": "Public ID of the price row",
            "example": "price_V1StGXR8Z5jdHi6B"
          },
          "ai_provider_id": {
            "x-soat-ref": "ai-providers",
            "type": "string",
            "nullable": true,
            "description": "Null for a global default price. Set when this row is a per-provider override for a specific AI provider instance.\n"
          },
          "meter_type": {
            "type": "string",
            "description": "Meter type this SKU belongs to (`llm_tokens`, `compute_execution`, …)"
          },
          "provider": {
            "type": "string",
            "description": "SKU vendor slug (`openai`, or `soat` for platform SKUs)",
            "example": "openai"
          },
          "model": {
            "type": "string",
            "description": "Model id, or the billable SKU for platform meter types",
            "example": "gpt-4o"
          },
          "component": {
            "type": "string",
            "description": "The component this row prices (`input_tokens`, `compute_second`, …)"
          },
          "unit": {
            "type": "string",
            "description": "Unit `unit_price` is denominated in (`token`, `compute_second`, …)"
          },
          "unit_price": {
            "type": "number",
            "description": "USD per `unit` (for token components, USD per token)"
          },
          "effective_from": {
            "type": "string",
            "format": "date-time",
            "description": "The row with the latest effective_from <= now() prices a call"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "PriceBookResponse": {
        "type": "object",
        "properties": {
          "prices": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Price"
            }
          }
        }
      },
      "UpsertPricesRequest": {
        "type": "object",
        "required": [
          "prices"
        ],
        "properties": {
          "prices": {
            "type": "array",
            "items": {
              "type": "object",
              "required": [
                "provider",
                "model",
                "component",
                "unit",
                "unit_price",
                "effective_from"
              ],
              "description": "A price row for one component of a SKU. For LLM SKUs supply the `input_tokens` / `output_tokens` (and optionally `cached_tokens`) components with unit `token`; for platform SKUs supply the platform component (e.g. `compute_second`).\n",
              "properties": {
                "ai_provider_id": {
                  "x-soat-ref": "ai-providers",
                  "type": "string",
                  "nullable": true,
                  "description": "Omit for a global default price; set to a provider public ID to record a per-provider override.\n"
                },
                "meter_type": {
                  "type": "string",
                  "default": "llm_tokens",
                  "description": "Defaults to `llm_tokens`. Set to a platform meter type (e.g. `compute_execution`) for a platform SKU.\n"
                },
                "provider": {
                  "type": "string"
                },
                "model": {
                  "type": "string"
                },
                "component": {
                  "type": "string",
                  "description": "The component this row prices (`input_tokens`, `compute_second`, …)"
                },
                "unit": {
                  "type": "string",
                  "description": "Unit `unit_price` is denominated in (`token`, `compute_second`, …)"
                },
                "unit_price": {
                  "type": "number",
                  "description": "USD per `unit`"
                },
                "effective_from": {
                  "type": "string",
                  "format": "date-time",
                  "description": "Must be in the future; past prices are immutable"
                }
              }
            }
          }
        }
      },
      "UserRecord": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "Public user ID (user_ prefix)",
            "example": "user_V1StGXR8Z5jdHi6B"
          },
          "username": {
            "type": "string",
            "example": "johndoe"
          },
          "role": {
            "type": "string",
            "enum": [
              "admin",
              "user"
            ],
            "example": "user"
          },
          "created_at": {
            "type": "string",
            "format": "date-time",
            "example": "2024-01-01T00:00:00.000Z"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time",
            "example": "2024-01-01T00:00:00.000Z"
          }
        }
      },
      "LoginResponse": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "Public user ID (user_ prefix)",
            "example": "user_V1StGXR8Z5jdHi6B"
          },
          "username": {
            "type": "string",
            "example": "admin"
          },
          "role": {
            "type": "string",
            "enum": [
              "admin",
              "user"
            ],
            "example": "admin"
          },
          "token": {
            "type": "string",
            "description": "JWT bearer token",
            "example": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9"
          }
        }
      },
      "Webhook": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "project_id": {
            "x-soat-ref": "projects",
            "type": "string"
          },
          "policy_id": {
            "x-soat-ref": "policies",
            "type": "string",
            "nullable": true
          },
          "name": {
            "type": "string"
          },
          "description": {
            "type": "string",
            "nullable": true
          },
          "url": {
            "type": "string"
          },
          "events": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "active": {
            "type": "boolean"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "WebhookWithSecret": {
        "allOf": [
          {
            "$ref": "#/components/schemas/Webhook"
          },
          {
            "type": "object",
            "properties": {
              "secret": {
                "type": "string"
              }
            }
          }
        ]
      },
      "CreateWebhookRequest": {
        "type": "object",
        "required": [
          "name",
          "url",
          "events"
        ],
        "properties": {
          "project_id": {
            "x-soat-ref": "projects",
            "type": "string",
            "description": "Public ID of the project. Optional when authenticating with a project-scoped API key, which defaults to the key's project; required otherwise."
          },
          "name": {
            "type": "string"
          },
          "description": {
            "type": "string"
          },
          "url": {
            "type": "string"
          },
          "events": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "policy_id": {
            "x-soat-ref": "policies",
            "type": "string"
          }
        }
      },
      "UpdateWebhookRequest": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string"
          },
          "description": {
            "type": "string"
          },
          "url": {
            "type": "string"
          },
          "events": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "active": {
            "type": "boolean"
          },
          "policy_id": {
            "x-soat-ref": "policies",
            "type": "string",
            "nullable": true
          }
        }
      },
      "WebhookSecretResponse": {
        "type": "object",
        "properties": {
          "secret": {
            "type": "string"
          }
        }
      },
      "Delivery": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "webhook_id": {
            "x-soat-ref": "webhooks",
            "type": "string",
            "description": "Public ID of the webhook this delivery belongs to"
          },
          "event_type": {
            "type": "string"
          },
          "payload": {
            "type": "object"
          },
          "status": {
            "type": "string",
            "enum": [
              "pending",
              "success",
              "failed"
            ]
          },
          "status_code": {
            "type": "integer",
            "nullable": true
          },
          "attempts": {
            "type": "integer"
          },
          "last_attempt_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "next_attempt_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true,
            "description": "When the delivery becomes eligible for its next attempt. Set while\nthe delivery is pending and a retry is still owed; null once it has\nsucceeded or exhausted its attempts.\n"
          },
          "response_body": {
            "type": "string",
            "nullable": true
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "DeliveryListResponse": {
        "type": "object",
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Delivery"
            }
          },
          "total": {
            "type": "integer"
          },
          "limit": {
            "type": "integer"
          },
          "offset": {
            "type": "integer"
          }
        }
      },
      "WorkflowState": {
        "type": "object",
        "description": "A named state. Exactly one state must be `initial: true`; any number may be `terminal: true`. A `kind: human` state never dispatches — the task parks until a transition fires. `on_enter` (§5) dispatches exactly one of an agent generation (`kind: agent`, `agent_id`), an orchestration run (`kind: orchestration`, `orchestration_id`) or a tool call (`kind: tool`, `tool_id`, optional `operation_id`) on entry, optionally under a `retry` policy (`max_attempts` 1-10, `backoff_seconds`, `backoff_multiplier`) that re-runs execution failures before `on_failure` applies. A `tool` dispatch settles within the dispatch and is adjudicated by the same guardrails as an orchestration `tool` node; for anything that must wait (a delay, a poll, a multi-step pipeline, or an approval-gated tool), dispatch an orchestration instead.",
        "additionalProperties": true,
        "required": [
          "name"
        ],
        "properties": {
          "name": {
            "type": "string"
          },
          "initial": {
            "type": "boolean"
          },
          "terminal": {
            "type": "boolean"
          },
          "kind": {
            "type": "string",
            "description": "`human` marks a human-in-the-loop parking state: the state never dispatches (declaring `on_enter` on it is rejected at validation) and the task parks until a principal fires a transition."
          },
          "stalled_after": {
            "type": "integer",
            "nullable": true,
            "description": "Seconds a task may sit in this state before the stall sweeper emits a `tasks.stalled` event (once per stall episode, re-armed on the next transition). Must be a positive integer when set. Omit or null to never stall. The event does not move the task — route on it with a webhook/trigger."
          },
          "on_enter": {
            "type": "object",
            "nullable": true,
            "additionalProperties": true
          }
        }
      },
      "WorkflowTransition": {
        "type": "object",
        "description": "A named, directional move. `from` is a list of source states; `to` is one target state. `guard` is a JSON Logic expression over `{task, transition, principal}` that must be truthy for the move to apply.",
        "additionalProperties": true,
        "required": [
          "name",
          "from",
          "to"
        ],
        "properties": {
          "name": {
            "type": "string"
          },
          "from": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "to": {
            "type": "string"
          },
          "guard": {
            "type": "object",
            "nullable": true
          },
          "requires_approval": {
            "type": "boolean",
            "description": "Gate the transition behind a human approval. When `true`, firing the transition (by anyone other than the approval resolution itself) parks a pending `ApprovalItem` instead of moving the task; the task exposes `pending_transition` until the item resolves. Approval fires the transition as the `approval` principal (its guard re-evaluated at resolution time); rejection or expiry clears the gate and appends a history note."
          }
        }
      },
      "Workflow": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "project_id": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "description": {
            "type": "string",
            "nullable": true
          },
          "version": {
            "type": "integer",
            "description": "Incremented on every write that changes the state machine; prior versions are archived. A task pins the version it entered on, so these fields are a draft for tasks created from now on rather than a live rewrite of the ones already in flight.\n",
            "example": 1
          },
          "states": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/WorkflowState"
            }
          },
          "transitions": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/WorkflowTransition"
            }
          },
          "payload_schema": {
            "type": "object",
            "nullable": true
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "CreateWorkflowRequest": {
        "type": "object",
        "required": [
          "name",
          "states",
          "transitions"
        ],
        "properties": {
          "project_id": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "description": {
            "type": "string",
            "nullable": true
          },
          "states": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/WorkflowState"
            }
          },
          "transitions": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/WorkflowTransition"
            }
          },
          "payload_schema": {
            "type": "object",
            "nullable": true
          },
          "version_label": {
            "type": "string",
            "description": "Optional tag for the version this create archives, e.g. `initial`.",
            "example": "initial"
          }
        }
      },
      "UpdateWorkflowRequest": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string"
          },
          "description": {
            "type": "string",
            "nullable": true
          },
          "states": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/WorkflowState"
            }
          },
          "transitions": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/WorkflowTransition"
            }
          },
          "payload_schema": {
            "type": "object",
            "nullable": true
          },
          "version_label": {
            "type": "string",
            "description": "Optional tag for the version this write archives, e.g. `pre-rewire`. Ignored when the write changes no definition field, since no version is archived.",
            "example": "pre-rewire"
          }
        }
      },
      "WorkflowVersion": {
        "type": "object",
        "description": "An immutable archive of a workflow's state machine at one version.",
        "properties": {
          "id": {
            "type": "string",
            "description": "Public ID of the archived version",
            "example": "wfl_ver_V1StGXR8Z5jdHi6B"
          },
          "workflow_id": {
            "x-soat-ref": "workflows",
            "type": "string",
            "description": "Public ID of the workflow this version belongs to",
            "example": "wfl_V1StGXR8Z5jdHi6B"
          },
          "version": {
            "type": "integer",
            "description": "The archived version number",
            "example": 1
          },
          "config": {
            "type": "object",
            "additionalProperties": true,
            "description": "The workflow's versioned surface as it stood at this version: `states`, `transitions` and `payload_schema`. Name and description are metadata — bumping the version when one of them changes would make two version numbers denote the same state machine, which is exactly what a task cites.\n\nDeliberately open rather than a fixed schema: an archive written by an earlier release of SOAT reflects the workflow surface **of its own time**, so it may carry fields the current API no longer documents.",
            "properties": {
              "states": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/WorkflowState"
                }
              },
              "transitions": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/WorkflowTransition"
                }
              },
              "payload_schema": {
                "type": "object",
                "nullable": true
              }
            }
          },
          "label": {
            "type": "string",
            "nullable": true,
            "description": "Optional human tag for this version, e.g. `pre-rewire`. Set from the `version_label` field of a write, the `label` field of a restore, or generated for one.",
            "example": "restored from v2"
          },
          "created_by": {
            "x-soat-ref": "users",
            "type": "string",
            "nullable": true,
            "description": "Public ID of the user whose action produced this version. Null for writes with no request user behind them."
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "RestoreWorkflowVersionRequest": {
        "type": "object",
        "properties": {
          "label": {
            "type": "string",
            "description": "Optional tag for the new version the restore archives. Defaults to `restored from vN`.",
            "example": "rollback to pre-rewire"
          }
        }
      }
    },
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "JWT"
      }
    },
    "parameters": {
      "approval_id": {
        "name": "approval_id",
        "in": "path",
        "required": true,
        "description": "Approval item ID",
        "schema": {
          "type": "string",
          "example": "apr_V1StGXR8Z5jdHi6B"
        }
      },
      "chain_id": {
        "name": "chain_id",
        "in": "path",
        "required": true,
        "description": "Continuation chain ID",
        "schema": {
          "type": "string",
          "example": "chain_V1StGXR8Z5jdHi6B"
        }
      },
      "exception_id": {
        "name": "exception_id",
        "in": "path",
        "required": true,
        "description": "Exception item ID",
        "schema": {
          "type": "string",
          "example": "exc_V1StGXR8Z5jdHi6B"
        }
      },
      "orchestration_id": {
        "in": "path",
        "name": "orchestration_id",
        "required": true,
        "schema": {
          "type": "string"
        },
        "description": "Public ID of the orchestration (orch_...)"
      },
      "orchestration_run_id": {
        "in": "path",
        "name": "orchestration_run_id",
        "required": true,
        "schema": {
          "type": "string"
        },
        "description": "Public ID of the run (run_...)"
      },
      "SessionId": {
        "name": "session_id",
        "in": "path",
        "required": true,
        "description": "Session public ID",
        "schema": {
          "type": "string",
          "example": "sess_V1StGXR8Z5jdHi6B"
        }
      }
    },
    "responses": {
      "Unauthorized": {
        "description": "Unauthorized",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorResponse"
            }
          }
        }
      },
      "Forbidden": {
        "description": "Forbidden",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorResponse"
            }
          }
        }
      },
      "NotFound": {
        "description": "Not found",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorResponse"
            }
          }
        }
      }
    }
  },
  "x-error-codes": {
    "ACTIVITY_INVALID_CURSOR": {
      "http_status": 400,
      "description": "The activity feed cursor is malformed or does not decode to a valid (created_at, id) position.",
      "resolution": "Fix the request and retry — the payload, parameters, or referenced ids are invalid. Compare the request against the operation in `/openapi.json`.",
      "docs_url": "https://soat.ttoss.dev/docs/error-codes#activity_invalid_cursor"
    },
    "ACTOR_HAS_MESSAGES": {
      "http_status": 409,
      "description": "The actor has linked session messages and cannot be deleted.",
      "resolution": "The request is well-formed but the resource is not in a state that allows it. Resolve the conflicting state named in the message, then retry.",
      "docs_url": "https://soat.ttoss.dev/docs/error-codes#actor_has_messages"
    },
    "ACTOR_NOT_FOUND": {
      "http_status": 400,
      "description": "A referenced actor does not exist.",
      "resolution": "Fix the request and retry — the payload, parameters, or referenced ids are invalid. Compare the request against the operation in `/openapi.json`.",
      "docs_url": "https://soat.ttoss.dev/docs/error-codes#actor_not_found"
    },
    "AGENT_AND_CHAT_EXCLUSIVE": {
      "http_status": 400,
      "description": "An actor cannot have both an agent_id and a chat_id assigned simultaneously. Set one or the other, not both.",
      "resolution": "Fix the request and retry — the payload, parameters, or referenced ids are invalid. Compare the request against the operation in `/openapi.json`.",
      "docs_url": "https://soat.ttoss.dev/docs/error-codes#agent_and_chat_exclusive"
    },
    "AGENT_HAS_DEPENDENTS": {
      "http_status": 409,
      "description": "The agent is referenced by one or more generations or traces and cannot be deleted. Delete the dependent records first.",
      "resolution": "The request is well-formed but the resource is not in a state that allows it. Resolve the conflicting state named in the message, then retry.",
      "docs_url": "https://soat.ttoss.dev/docs/error-codes#agent_has_dependents"
    },
    "AGENT_NOT_FOUND": {
      "http_status": 400,
      "description": "A referenced agent does not exist.",
      "resolution": "Fix the request and retry — the payload, parameters, or referenced ids are invalid. Compare the request against the operation in `/openapi.json`.",
      "docs_url": "https://soat.ttoss.dev/docs/error-codes#agent_not_found"
    },
    "AI_PROVIDER_ERROR": {
      "http_status": 502,
      "description": "The upstream AI provider returned an error (e.g. insufficient credits, rate limit, or the provider is unreachable).",
      "resolution": "The upstream model provider failed, not this server. Check the provider credential and the account behind it (credits, rate limits, model access), then retry with backoff.",
      "docs_url": "https://soat.ttoss.dev/docs/error-codes#ai_provider_error"
    },
    "AI_PROVIDER_HAS_DEPENDENTS": {
      "http_status": 409,
      "description": "The AI provider still has dependents. Live references (chats, agents, model-route targets) always block deletion and must be deleted or repointed first. Soft dependents (price overrides, usage records) block only until force=true, which drops the overrides and unlinks usage history. The error meta reports the counts, a sample of offending IDs, and a `forcible` flag.",
      "resolution": "The request is well-formed but the resource is not in a state that allows it. Resolve the conflicting state named in the message, then retry.",
      "docs_url": "https://soat.ttoss.dev/docs/error-codes#ai_provider_has_dependents"
    },
    "AI_PROVIDER_MISCONFIGURED": {
      "http_status": 400,
      "description": "The AI provider record is missing configuration the provider type requires — for example a `vertex` provider with no Google Cloud project in `config.project` and no service-account key file linked as its secret.",
      "resolution": "The provider record is missing something it needs to make a call — usually the API key secret or the base URL. Update the AI provider and retry.",
      "docs_url": "https://soat.ttoss.dev/docs/error-codes#ai_provider_misconfigured"
    },
    "AI_PROVIDER_NOT_FOUND": {
      "http_status": 400,
      "description": "A referenced AI provider does not exist.",
      "resolution": "Fix the request and retry — the payload, parameters, or referenced ids are invalid. Compare the request against the operation in `/openapi.json`.",
      "docs_url": "https://soat.ttoss.dev/docs/error-codes#ai_provider_not_found"
    },
    "API_KEY_PROJECT_SCOPE": {
      "http_status": 403,
      "description": "The credential (API key or OAuth token) is bound to one project and the request targets a different one. This project binding is a hard boundary that the owner’s admin role does not lift — admin only relaxes the project create/delete gate, never the per-credential project scope for resource operations. Mint a key scoped to the target project (or an unscoped key) to operate there. The error meta reports `scoped_project` and `requested_project`.",
      "resolution": "This API key is scoped to a single project and the request targets another one (or none). Omit `project_id` to use the key's own project, or use an unscoped credential.",
      "docs_url": "https://soat.ttoss.dev/docs/error-codes#api_key_project_scope"
    },
    "APPROVAL_ALREADY_RESOLVED": {
      "http_status": 409,
      "description": "The approval item has already been resolved (approved, rejected, or expired) and cannot be resolved again.",
      "resolution": "The request is well-formed but the resource is not in a state that allows it. Resolve the conflicting state named in the message, then retry.",
      "docs_url": "https://soat.ttoss.dev/docs/error-codes#approval_already_resolved"
    },
    "APPROVAL_EXPIRED": {
      "http_status": 409,
      "description": "The approval item has expired and can never execute. Its supporting evidence is stale; a fresh proposal is required.",
      "resolution": "The request is well-formed but the resource is not in a state that allows it. Resolve the conflicting state named in the message, then retry.",
      "docs_url": "https://soat.ttoss.dev/docs/error-codes#approval_expired"
    },
    "APPROVAL_INVALID_EDIT": {
      "http_status": 400,
      "description": "The edited arguments supplied on edit-then-approve must be a JSON object.",
      "resolution": "Fix the request and retry — the payload, parameters, or referenced ids are invalid. Compare the request against the operation in `/openapi.json`.",
      "docs_url": "https://soat.ttoss.dev/docs/error-codes#approval_invalid_edit"
    },
    "APPROVAL_NOT_FOUND": {
      "http_status": 404,
      "description": "The approval item does not exist or is not accessible.",
      "resolution": "Check the id and the project the credential can see, then retry. Listing the collection confirms whether the resource exists.",
      "docs_url": "https://soat.ttoss.dev/docs/error-codes#approval_not_found"
    },
    "APPROVAL_REASON_REQUIRED": {
      "http_status": 400,
      "description": "A reason is required when rejecting an approval item.",
      "resolution": "Fix the request and retry — the payload, parameters, or referenced ids are invalid. Compare the request against the operation in `/openapi.json`.",
      "docs_url": "https://soat.ttoss.dev/docs/error-codes#approval_reason_required"
    },
    "BOOTSTRAP_ALREADY_COMPLETED": {
      "http_status": 409,
      "description": "The instance already has at least one user, so the one-time bootstrap of the first admin cannot run again.",
      "resolution": "The first admin already exists, so bootstrap is closed for good. Sign in instead, and create further users with `POST /api/v1/users`.",
      "docs_url": "https://soat.ttoss.dev/docs/error-codes#bootstrap_already_completed"
    },
    "CHAIN_NOT_FOUND": {
      "http_status": 404,
      "description": "The continuation chain does not exist or is not accessible.",
      "resolution": "Check the id and the project the credential can see, then retry. Listing the collection confirms whether the resource exists.",
      "docs_url": "https://soat.ttoss.dev/docs/error-codes#chain_not_found"
    },
    "CHAT_NOT_FOUND": {
      "http_status": 400,
      "description": "A referenced chat does not exist.",
      "resolution": "Fix the request and retry — the payload, parameters, or referenced ids are invalid. Compare the request against the operation in `/openapi.json`.",
      "docs_url": "https://soat.ttoss.dev/docs/error-codes#chat_not_found"
    },
    "CONVERTER_FAILED": {
      "http_status": 422,
      "description": "The ingestion converter (tool or agent) failed to run, an agent converter returned an async deferral (unsupported — agent converters are always awaited inline), or a tool converter returned an async deferral during synchronous ingestion (`?wait=true`, which cannot wait for a callback).",
      "resolution": "The request was accepted and then could not be carried out. The failure is in the work, not the request shape — read `meta` and the trace for which step failed before retrying.",
      "docs_url": "https://soat.ttoss.dev/docs/error-codes#converter_failed"
    },
    "CONVERTER_OUTPUT_INVALID": {
      "http_status": 422,
      "description": "The ingestion converter returned an unrecognized output shape. Expected a string, `{ pages: [{ text, page_number }] }`, or `{ status: \"pending\" }`.",
      "resolution": "The request was accepted and then could not be carried out. The failure is in the work, not the request shape — read `meta` and the trace for which step failed before retrying.",
      "docs_url": "https://soat.ttoss.dev/docs/error-codes#converter_output_invalid"
    },
    "EMBEDDING_NOT_CONFIGURED": {
      "http_status": 503,
      "description": "The embedding service is not configured. Set EMBEDDING_PROVIDER and EMBEDDING_MODEL environment variables.",
      "resolution": "Knowledge search needs an embedding model. Configure one on the project or the AI provider before ingesting or searching.",
      "docs_url": "https://soat.ttoss.dev/docs/error-codes#embedding_not_configured"
    },
    "EXCEPTION_ALREADY_RESOLVED": {
      "http_status": 409,
      "description": "The exception item has already been resolved and cannot be acknowledged or resolved again.",
      "resolution": "The request is well-formed but the resource is not in a state that allows it. Resolve the conflicting state named in the message, then retry.",
      "docs_url": "https://soat.ttoss.dev/docs/error-codes#exception_already_resolved"
    },
    "EXCEPTION_NOT_FOUND": {
      "http_status": 404,
      "description": "The exception item does not exist or is not accessible.",
      "resolution": "Check the id and the project the credential can see, then retry. Listing the collection confirms whether the resource exists.",
      "docs_url": "https://soat.ttoss.dev/docs/error-codes#exception_not_found"
    },
    "FILE_ALREADY_INGESTED": {
      "http_status": 409,
      "description": "The file already backs a Document (a file can only be ingested once). Use POST /documents/{document_id}/ingest to re-process the existing document, or upload a new copy of the file to ingest it under a different path/strategy.",
      "resolution": "The request is well-formed but the resource is not in a state that allows it. Resolve the conflicting state named in the message, then retry.",
      "docs_url": "https://soat.ttoss.dev/docs/error-codes#file_already_ingested"
    },
    "FILE_DOWNLOAD_URL_NOT_CONFIGURED": {
      "http_status": 500,
      "description": "An ingestion rule with file_delivery: download_url requires SOAT_BASE_URL to be set — the URL is fetched by an external converter that cannot resolve a relative or localhost address.",
      "resolution": "Retry with backoff. If it persists, the server log carries the detail the response deliberately omits.",
      "docs_url": "https://soat.ttoss.dev/docs/error-codes#file_download_url_not_configured"
    },
    "FILE_HAS_DEPENDENTS": {
      "http_status": 409,
      "description": "The file is referenced by one or more records and cannot be deleted. Delete the dependent records first.",
      "resolution": "The request is well-formed but the resource is not in a state that allows it. Resolve the conflicting state named in the message, then retry.",
      "docs_url": "https://soat.ttoss.dev/docs/error-codes#file_has_dependents"
    },
    "FILE_NOT_FOUND": {
      "http_status": 400,
      "description": "The referenced file does not exist.",
      "resolution": "Fix the request and retry — the payload, parameters, or referenced ids are invalid. Compare the request against the operation in `/openapi.json`.",
      "docs_url": "https://soat.ttoss.dev/docs/error-codes#file_not_found"
    },
    "FILE_PARSE_FAILED": {
      "http_status": 400,
      "description": "The referenced file contains no extractable text.",
      "resolution": "Fix the request and retry — the payload, parameters, or referenced ids are invalid. Compare the request against the operation in `/openapi.json`.",
      "docs_url": "https://soat.ttoss.dev/docs/error-codes#file_parse_failed"
    },
    "FILE_STORAGE_MISCONFIGURED": {
      "http_status": 500,
      "description": "The file storage backend is not configured correctly — an unknown FILES_STORAGE_PROVIDER, an unsupported storage type, or a missing required variable (e.g. FILES_STORAGE_DIR for local, FILES_S3_BUCKET for s3).",
      "resolution": "Retry with backoff. If it persists, the server log carries the detail the response deliberately omits.",
      "docs_url": "https://soat.ttoss.dev/docs/error-codes#file_storage_misconfigured"
    },
    "FILE_TOO_LARGE_FOR_SYNC": {
      "http_status": 413,
      "description": "The file is too large to ingest synchronously. Retry without `?wait=true` to run ingestion in the background and poll the document status.",
      "resolution": "The file is too large to parse inside the request. Call the same operation without `wait=true` and poll the handle it returns.",
      "docs_url": "https://soat.ttoss.dev/docs/error-codes#file_too_large_for_sync"
    },
    "FORBIDDEN": {
      "http_status": 403,
      "description": "The authenticated user does not have permission to perform this action.",
      "resolution": "The credential authenticated but is not allowed this action. Attach an IAM policy granting the action to the caller, or use a credential that has it.",
      "docs_url": "https://soat.ttoss.dev/docs/error-codes#forbidden"
    },
    "FORCED_TOOL_CHOICE_CANNOT_STOP": {
      "http_status": 400,
      "description": "The agent forces tool use on every step (`tool_choice: \"required\"`, or the `{ type: \"tool\" }` object form) but declares no `has_tool_call` stop condition, so no turn it runs could ever end by answering — only by exhausting `max_steps`. Declare the terminal condition, or drop the forcing to `\"auto\"` and force a specific step with `step_rules` instead.",
      "resolution": "Add `{ \"type\": \"has_tool_call\", \"tool_name\": \"<your done tool>\" }` to `stop_conditions`, or set `tool_choice` to `\"auto\"` and force the step you actually care about with `step_rules`. `max_chain_generations` does not satisfy this — it bounds a chain, it never ends a turn.",
      "docs_url": "https://soat.ttoss.dev/docs/error-codes#forced_tool_choice_cannot_stop"
    },
    "FORMATION_DELETE_FAILED": {
      "http_status": 409,
      "description": "One or more of the formation's resources could not be deleted, so the stack is left in `delete_failed`. `meta.failures` names each blocking resource (its logical id, type, and the underlying error) — the common case is a resource the platform refuses to delete on its own, such as an agent that has generation or trace history. Resolve those, then delete the formation again.",
      "resolution": "The request is well-formed but the resource is not in a state that allows it. Resolve the conflicting state named in the message, then retry.",
      "docs_url": "https://soat.ttoss.dev/docs/error-codes#formation_delete_failed"
    },
    "FORMATION_HANDLER_FAILED": {
      "http_status": 502,
      "description": "A resource type registered by the deployment operator delegates its lifecycle to an external HTTP handler, and that handler did not answer successfully — it returned a non-2xx status, timed out, was unreachable, or replied with a body the protocol does not allow (a create with no `physical_resource_id`, for instance). `meta.resource_type` and `meta.request_type` name the type and the operation, and the handler's own message is relayed when it sent one. This is an operator-side failure: the template is not at fault and re-deploying it unchanged will fail the same way until the handler is fixed.",
      "resolution": "A resource type this deployment registered delegates to an external handler, and that handler did not answer. The template is not at fault, so re-deploying it unchanged will fail identically — the deployment operator has to fix the handler (`meta.resource_type` names the type, `meta.request_type` the operation it failed on).",
      "docs_url": "https://soat.ttoss.dev/docs/error-codes#formation_handler_failed"
    },
    "FORMATION_INVALID_METADATA": {
      "http_status": 400,
      "description": "The formation-level `metadata` field contains a `sub`/`param`/`ref`/`ref_attr` substitution expression. That field is a static annotation bag and is never resolved; put deploy-time substitutions in the template top-level `metadata` block instead.",
      "resolution": "Fix the request and retry — the payload, parameters, or referenced ids are invalid. Compare the request against the operation in `/openapi.json`.",
      "docs_url": "https://soat.ttoss.dev/docs/error-codes#formation_invalid_metadata"
    },
    "FORMATION_MISSING_PARAMETERS": {
      "http_status": 400,
      "description": "The formation template declares one or more required parameters (no default, not satisfied by use_previous_value) that were not supplied or were provided as an empty string.",
      "resolution": "Fix the request and retry — the payload, parameters, or referenced ids are invalid. Compare the request against the operation in `/openapi.json`.",
      "docs_url": "https://soat.ttoss.dev/docs/error-codes#formation_missing_parameters"
    },
    "FORMATION_REPLACE_CLEANUP_FAILED": {
      "http_status": 500,
      "description": "A deploy replaced a resource — the handler answered a new `physical_resource_id` — and the superseded resource could not be deleted, so it is still live while the formation's ledger already points at the replacement. The deploy itself succeeded: the desired state is realised, and this is carried on the formation (and on its operation) rather than thrown, so it never appears as a response status. `meta.failures` names each un-deleted resource as `{ logical_id, resource_type, physical_resource_id, error }`. Each one stays on the formation as pending cleanup and is retried on the next deploy and on teardown; resolve whatever refused the delete and deploy again.",
      "resolution": "Retry with backoff. If it persists, the server log carries the detail the response deliberately omits.",
      "docs_url": "https://soat.ttoss.dev/docs/error-codes#formation_replace_cleanup_failed"
    },
    "GENERATION_ALREADY_IN_PROGRESS": {
      "http_status": 409,
      "description": "A generation is already in progress for this session. Wait for it to complete before starting a new one.",
      "resolution": "A generation is already running for this target. Poll it to completion, or cancel it, before starting another.",
      "docs_url": "https://soat.ttoss.dev/docs/error-codes#generation_already_in_progress"
    },
    "GENERATION_CONTENT_UNAVAILABLE": {
      "http_status": 409,
      "description": "The generation's content was never stored (the agent or project runs with trace_content_mode 'none') or has since been purged, so the turn cannot be replayed or curated.",
      "resolution": "The request is well-formed but the resource is not in a state that allows it. Resolve the conflicting state named in the message, then retry.",
      "docs_url": "https://soat.ttoss.dev/docs/error-codes#generation_content_unavailable"
    },
    "GENERATION_FAILED": {
      "http_status": 500,
      "description": "An unexpected error occurred during agent generation. Check the trace for details.",
      "resolution": "Retry with backoff. If it persists, the server log carries the detail the response deliberately omits.",
      "docs_url": "https://soat.ttoss.dev/docs/error-codes#generation_failed"
    },
    "GENERATION_NOT_COMPLETED": {
      "http_status": 409,
      "description": "The generation has not completed, so it has no finished turn to promote. Only a completed generation can be curated into a dataset item.",
      "resolution": "The request is well-formed but the resource is not in a state that allows it. Resolve the conflicting state named in the message, then retry.",
      "docs_url": "https://soat.ttoss.dev/docs/error-codes#generation_not_completed"
    },
    "GENERATION_NOT_FOUND": {
      "http_status": 404,
      "description": "The generation does not exist or is not in a pending state for tool output submission.",
      "resolution": "Check the id and the project the credential can see, then retry. Listing the collection confirms whether the resource exists.",
      "docs_url": "https://soat.ttoss.dev/docs/error-codes#generation_not_found"
    },
    "GUARDRAIL_HAS_REFERENCES": {
      "http_status": 409,
      "description": "The guardrail is still attached to one or more tools, agents, or projects (`guardrail_ids`) and cannot be deleted. Detach every reference first (requires guardrails:DetachGuardrail). The error meta lists the referencing resources by scope.",
      "resolution": "The request is well-formed but the resource is not in a state that allows it. Resolve the conflicting state named in the message, then retry.",
      "docs_url": "https://soat.ttoss.dev/docs/error-codes#guardrail_has_references"
    },
    "GUARDRAIL_NOT_FOUND": {
      "http_status": 400,
      "description": "A guardrail referenced by a `guardrail_ids` attachment does not exist in the project.",
      "resolution": "Fix the request and retry — the payload, parameters, or referenced ids are invalid. Compare the request against the operation in `/openapi.json`.",
      "docs_url": "https://soat.ttoss.dev/docs/error-codes#guardrail_not_found"
    },
    "HOOK_INVALID_JSON": {
      "http_status": 400,
      "description": "The inbound hook request body is not valid JSON.",
      "resolution": "Fix the request and retry — the payload, parameters, or referenced ids are invalid. Compare the request against the operation in `/openapi.json`.",
      "docs_url": "https://soat.ttoss.dev/docs/error-codes#hook_invalid_json"
    },
    "HOOK_PAYLOAD_TOO_LARGE": {
      "http_status": 413,
      "description": "The inbound hook request body exceeds the 1 MiB limit.",
      "resolution": "The payload exceeds the accepted size. Send less in one request, or use the upload flow for large bodies.",
      "docs_url": "https://soat.ttoss.dev/docs/error-codes#hook_payload_too_large"
    },
    "INGESTION_CALLBACK_CONFLICT": {
      "http_status": 409,
      "description": "The document is no longer awaiting this conversion attempt — it already completed, timed out, or was superseded by a re-ingest.",
      "resolution": "The request is well-formed but the resource is not in a state that allows it. Resolve the conflicting state named in the message, then retry.",
      "docs_url": "https://soat.ttoss.dev/docs/error-codes#ingestion_callback_conflict"
    },
    "INGESTION_CALLBACK_INVALID_TOKEN": {
      "http_status": 401,
      "description": "The ingestion-callback token is missing, malformed, expired, or does not match the target document.",
      "resolution": "Authenticate the request: send a valid bearer token or project API key in the `Authorization` header.",
      "docs_url": "https://soat.ttoss.dev/docs/error-codes#ingestion_callback_invalid_token"
    },
    "INGESTION_RULE_GLOB_CONFLICT": {
      "http_status": 409,
      "description": "An ingestion rule for this content_type_glob already exists in the project.",
      "resolution": "The request is well-formed but the resource is not in a state that allows it. Resolve the conflicting state named in the message, then retry.",
      "docs_url": "https://soat.ttoss.dev/docs/error-codes#ingestion_rule_glob_conflict"
    },
    "INGESTION_RULE_VALIDATION_FAILED": {
      "http_status": 400,
      "description": "The ingestion rule configuration is invalid (e.g. tool_id and agent_id are both set or both missing, the converter tool is a client tool, a soat/mcp converter tool is missing an action, content_type_glob is not a valid MIME type glob, or preset_parameters contains the reserved key \"file\" or \"callback\").",
      "resolution": "Fix the request and retry — the payload, parameters, or referenced ids are invalid. Compare the request against the operation in `/openapi.json`.",
      "docs_url": "https://soat.ttoss.dev/docs/error-codes#ingestion_rule_validation_failed"
    },
    "INTERNAL_ERROR": {
      "http_status": 500,
      "description": "An unhandled server-side failure. The message is always the constant \"Internal Server Error\" — the underlying exception is logged, never returned, so this error carries no detail a caller can act on beyond retrying.",
      "resolution": "Retry with backoff. The underlying exception is logged server-side and deliberately never forwarded, so the response carries nothing further to act on.",
      "docs_url": "https://soat.ttoss.dev/docs/error-codes#internal_error"
    },
    "INVALID_CRON_EXPRESSION": {
      "http_status": 400,
      "description": "The cron expression is invalid. Triggers require a strict 5-field cron expression evaluated in UTC.",
      "resolution": "Fix the request and retry — the payload, parameters, or referenced ids are invalid. Compare the request against the operation in `/openapi.json`.",
      "docs_url": "https://soat.ttoss.dev/docs/error-codes#invalid_cron_expression"
    },
    "INVALID_EVENT_PATTERN": {
      "http_status": 400,
      "description": "The event pattern is invalid. An event trigger subscribes with '*', 'prefix.*', or an exact event name, and a pattern in a platform namespace must match a registered event.",
      "resolution": "Fix the request and retry — the payload, parameters, or referenced ids are invalid. Compare the request against the operation in `/openapi.json`.",
      "docs_url": "https://soat.ttoss.dev/docs/error-codes#invalid_event_pattern"
    },
    "INVALID_OUTPUT_SCHEMA": {
      "http_status": 400,
      "description": "The agent output_schema is invalid — it must be a JSON Schema object.",
      "resolution": "Fix the request and retry — the payload, parameters, or referenced ids are invalid. Compare the request against the operation in `/openapi.json`.",
      "docs_url": "https://soat.ttoss.dev/docs/error-codes#invalid_output_schema"
    },
    "INVALID_TEMPLATE_TOKEN": {
      "http_status": 400,
      "description": "A {{...}} double-curly token was found that is not a {{secret:sec_...}} or {{context:<key>}} reference, or a {{context:<key>}} token was found outside execute.headers / mcp.headers. Double curly braces are reserved for those two reference kinds; use single braces ({param}) for URL path parameters.",
      "resolution": "Fix the request and retry — the payload, parameters, or referenced ids are invalid. Compare the request against the operation in `/openapi.json`.",
      "docs_url": "https://soat.ttoss.dev/docs/error-codes#invalid_template_token"
    },
    "INVALID_TOOL_CONTEXT_KEY": {
      "http_status": 400,
      "description": "A tool_context key cannot be used as an HTTP header name. Keys are forwarded verbatim as <prefix><key> headers, where the prefix is the deployment's TOOL_CONTEXT_HEADER_PREFIX (X-Soat-Context- by default), so a key may only contain letters, digits and the characters !#$%&'*+-.^_`|~, and two keys must not map to the same header name (header names are case-insensitive).",
      "resolution": "Fix the request and retry — the payload, parameters, or referenced ids are invalid. Compare the request against the operation in `/openapi.json`.",
      "docs_url": "https://soat.ttoss.dev/docs/error-codes#invalid_tool_context_key"
    },
    "MISSING_TOOL_CONTEXT_KEY": {
      "http_status": 400,
      "description": "A tool header references a {{context:<key>}} token whose key is not present in the tool_context for this call. The call is failed rather than sending the header with an empty value.",
      "resolution": "Fix the request and retry — the payload, parameters, or referenced ids are invalid. Compare the request against the operation in `/openapi.json`.",
      "docs_url": "https://soat.ttoss.dev/docs/error-codes#missing_tool_context_key"
    },
    "MODEL_LISTING_FAILED": {
      "http_status": 502,
      "description": "The provider rejected the model listing request, or answered it with something other than JSON. The provider's own status and message are carried in the error message.",
      "resolution": "An upstream dependency failed rather than this server itself. Retry with backoff and check that dependency.",
      "docs_url": "https://soat.ttoss.dev/docs/error-codes#model_listing_failed"
    },
    "MODEL_LISTING_UNSUPPORTED": {
      "http_status": 400,
      "description": "The AI provider type cannot enumerate the models it can run. Azure lists deployments an operator named rather than models, and Ollama lists whatever was pulled onto that host, so neither answers the question.",
      "resolution": "Fix the request and retry — the payload, parameters, or referenced ids are invalid. Compare the request against the operation in `/openapi.json`.",
      "docs_url": "https://soat.ttoss.dev/docs/error-codes#model_listing_unsupported"
    },
    "MODEL_ROUTE_HAS_DEPENDENTS": {
      "http_status": 409,
      "description": "The model route is referenced by one or more agents, or is a project's default_model_route_id, and cannot be deleted. Repoint or delete the referencing consumers first; the error meta reports the counts and a sample of agent IDs.",
      "resolution": "The request is well-formed but the resource is not in a state that allows it. Resolve the conflicting state named in the message, then retry.",
      "docs_url": "https://soat.ttoss.dev/docs/error-codes#model_route_has_dependents"
    },
    "MODEL_ROUTE_NOT_FOUND": {
      "http_status": 400,
      "description": "A referenced model route does not exist in the project.",
      "resolution": "Fix the request and retry — the payload, parameters, or referenced ids are invalid. Compare the request against the operation in `/openapi.json`.",
      "docs_url": "https://soat.ttoss.dev/docs/error-codes#model_route_not_found"
    },
    "NAME_CONFLICT": {
      "http_status": 409,
      "description": "A resource with this name already exists in the project. Use a different name.",
      "resolution": "A resource with this name already exists in the project. Pick another name, or update the existing resource instead of creating a second one.",
      "docs_url": "https://soat.ttoss.dev/docs/error-codes#name_conflict"
    },
    "NOT_IMPLEMENTED": {
      "http_status": 501,
      "description": "The request is well-formed but names an option this API version does not implement.",
      "resolution": "The operation exists in the contract but is not implemented on this deployment. Retrying will not change the outcome.",
      "docs_url": "https://soat.ttoss.dev/docs/error-codes#not_implemented"
    },
    "NO_ACTIVE_RELEASE": {
      "http_status": 409,
      "description": "The agent has no active release to promote or abort. Set one with PUT /agents/{agent_id}/release first.",
      "resolution": "The request is well-formed but the resource is not in a state that allows it. Resolve the conflicting state named in the message, then retry.",
      "docs_url": "https://soat.ttoss.dev/docs/error-codes#no_active_release"
    },
    "ORCHESTRATION_CYCLE_DETECTED": {
      "http_status": 422,
      "description": "The orchestration graph contains a cycle. Cycles are not supported.",
      "resolution": "The graph has a cycle, so no topological order exists. Break the cycle named in `meta` — a loop belongs in a workflow, not a DAG.",
      "docs_url": "https://soat.ttoss.dev/docs/error-codes#orchestration_cycle_detected"
    },
    "ORCHESTRATION_DISPATCH_FAILED": {
      "http_status": 422,
      "description": "A workflow on_enter dispatch's orchestration run settled in a non-success terminal status (failed, cancelled, or expired).",
      "resolution": "The request was accepted and then could not be carried out. The failure is in the work, not the request shape — read `meta` and the trace for which step failed before retrying.",
      "docs_url": "https://soat.ttoss.dev/docs/error-codes#orchestration_dispatch_failed"
    },
    "ORCHESTRATION_HUMAN_NODE_MISMATCH": {
      "http_status": 400,
      "description": "The specified node is not the active human node waiting for input.",
      "resolution": "Fix the request and retry — the payload, parameters, or referenced ids are invalid. Compare the request against the operation in `/openapi.json`.",
      "docs_url": "https://soat.ttoss.dev/docs/error-codes#orchestration_human_node_mismatch"
    },
    "ORCHESTRATION_MAX_ITERATIONS_EXCEEDED": {
      "http_status": 422,
      "description": "The orchestration run exceeded the maximum number of iterations.",
      "resolution": "The request was accepted and then could not be carried out. The failure is in the work, not the request shape — read `meta` and the trace for which step failed before retrying.",
      "docs_url": "https://soat.ttoss.dev/docs/error-codes#orchestration_max_iterations_exceeded"
    },
    "ORCHESTRATION_NESTED_RUN_FAILED": {
      "http_status": 422,
      "description": "A `loop` / `sub_orchestration` child run settled in a non-success terminal status (failed, cancelled, or expired) carrying no code of its own, so the node that started it failed too. A child that carries a code fails its parent under that code instead, so the cause reaches the run a caller reads (#1185). Matches ORCHESTRATION_DISPATCH_FAILED, the same rule for a workflow on_enter dispatch's run.",
      "resolution": "The request was accepted and then could not be carried out. The failure is in the work, not the request shape — read `meta` and the trace for which step failed before retrying.",
      "docs_url": "https://soat.ttoss.dev/docs/error-codes#orchestration_nested_run_failed"
    },
    "ORCHESTRATION_NODE_FAILED": {
      "http_status": 422,
      "description": "A node in the orchestration run failed to execute.",
      "resolution": "The request was accepted and then could not be carried out. The failure is in the work, not the request shape — read `meta` and the trace for which step failed before retrying.",
      "docs_url": "https://soat.ttoss.dev/docs/error-codes#orchestration_node_failed"
    },
    "ORCHESTRATION_NOT_FOUND": {
      "http_status": 404,
      "description": "The orchestration does not exist or is not accessible.",
      "resolution": "Check the id and the project the credential can see, then retry. Listing the collection confirms whether the resource exists.",
      "docs_url": "https://soat.ttoss.dev/docs/error-codes#orchestration_not_found"
    },
    "ORCHESTRATION_POLL_EXHAUSTED": {
      "http_status": 422,
      "description": "A poll node reached its attempt limit without its exit condition becoming true (only raised when fail_on_timeout is set).",
      "resolution": "The request was accepted and then could not be carried out. The failure is in the work, not the request shape — read `meta` and the trace for which step failed before retrying.",
      "docs_url": "https://soat.ttoss.dev/docs/error-codes#orchestration_poll_exhausted"
    },
    "ORCHESTRATION_RUN_DEPTH_LIMIT": {
      "http_status": 409,
      "description": "Starting the next `loop` / `sub_orchestration` child run would nest past the depth bound, so it was refused. This bounds a cycle no intra-graph validator can see — a graph whose `sub_orchestration` node names itself, directly or through a cycle of two graphs — which the orchestration cycle validator (intra-graph, and excluding loop nodes deliberately) cannot detect. The bound is the smaller of MAX_ORCHESTRATION_RUN_DEPTH (default 10) and the project's `max_run_depth`; the error meta names the depth reached, the limit, and which of the two set it (#1185).",
      "resolution": "Nesting is recursing: follow `parent_orchestration_run_id` up from the failed run to find the `sub_orchestration` (or `loop`) node that names a graph already in the chain, and repoint it. If the composition is legitimately this deep, raise the project bound with `PATCH /api/v1/projects/{project_id}` (`max_run_depth`) — `meta.limit_source` says whether the project or the deployment set the number that refused it.",
      "docs_url": "https://soat.ttoss.dev/docs/error-codes#orchestration_run_depth_limit"
    },
    "ORCHESTRATION_RUN_NOT_AWAITING_INPUT": {
      "http_status": 409,
      "description": "The orchestration run is not awaiting input.",
      "resolution": "The request is well-formed but the resource is not in a state that allows it. Resolve the conflicting state named in the message, then retry.",
      "docs_url": "https://soat.ttoss.dev/docs/error-codes#orchestration_run_not_awaiting_input"
    },
    "ORCHESTRATION_RUN_NOT_CANCELLABLE": {
      "http_status": 409,
      "description": "The orchestration run is already in a terminal state and cannot be cancelled.",
      "resolution": "The request is well-formed but the resource is not in a state that allows it. Resolve the conflicting state named in the message, then retry.",
      "docs_url": "https://soat.ttoss.dev/docs/error-codes#orchestration_run_not_cancellable"
    },
    "ORCHESTRATION_RUN_NOT_FOUND": {
      "http_status": 404,
      "description": "The orchestration run does not exist or is not accessible.",
      "resolution": "Check the id and the project the credential can see, then retry. Listing the collection confirms whether the resource exists.",
      "docs_url": "https://soat.ttoss.dev/docs/error-codes#orchestration_run_not_found"
    },
    "ORCHESTRATION_VALIDATION_FAILED": {
      "http_status": 400,
      "description": "The orchestration graph is invalid (e.g. a dangling edge, a node missing a required field, a cycle, or an input_mapping that references a state key no upstream node writes).",
      "resolution": "The orchestration graph is not runnable as declared. `meta` names the failing node or edge; `POST /api/v1/orchestrations/{orchestration_id}/validate` reports every problem at once.",
      "docs_url": "https://soat.ttoss.dev/docs/error-codes#orchestration_validation_failed"
    },
    "OUTPUT_SCHEMA_STREAMING_UNSUPPORTED": {
      "http_status": 400,
      "description": "Streaming generation does not support output_schema. Set stream to false, or remove output_schema from the agent/request.",
      "resolution": "Fix the request and retry — the payload, parameters, or referenced ids are invalid. Compare the request against the operation in `/openapi.json`.",
      "docs_url": "https://soat.ttoss.dev/docs/error-codes#output_schema_streaming_unsupported"
    },
    "OUTPUT_SCHEMA_VALIDATION_FAILED": {
      "http_status": 502,
      "description": "The model returned an object that does not satisfy the agent output_schema (or was not valid JSON at all). Upstream-caused like AI_PROVIDER_ERROR: the request was well-formed, the model output was not.",
      "resolution": "An upstream dependency failed rather than this server itself. Retry with backoff and check that dependency.",
      "docs_url": "https://soat.ttoss.dev/docs/error-codes#output_schema_validation_failed"
    },
    "PDF_PARSE_FAILED": {
      "http_status": 400,
      "description": "The uploaded file could not be parsed as a PDF. Ensure the file is a valid PDF document.",
      "resolution": "Fix the request and retry — the payload, parameters, or referenced ids are invalid. Compare the request against the operation in `/openapi.json`.",
      "docs_url": "https://soat.ttoss.dev/docs/error-codes#pdf_parse_failed"
    },
    "PIPELINE_DEPTH_EXCEEDED": {
      "http_status": 422,
      "description": "The pipeline tool exceeded the maximum nested execution depth (pipelines calling pipelines).",
      "resolution": "The request was accepted and then could not be carried out. The failure is in the work, not the request shape — read `meta` and the trace for which step failed before retrying.",
      "docs_url": "https://soat.ttoss.dev/docs/error-codes#pipeline_depth_exceeded"
    },
    "PIPELINE_INVALID_STEP": {
      "http_status": 400,
      "description": "The pipeline tool configuration is invalid (e.g. no steps, a duplicate or malformed step id, a missing tool reference, a forward reference to a later step, or a step that targets a client tool).",
      "resolution": "Fix the request and retry — the payload, parameters, or referenced ids are invalid. Compare the request against the operation in `/openapi.json`.",
      "docs_url": "https://soat.ttoss.dev/docs/error-codes#pipeline_invalid_step"
    },
    "PIPELINE_STEP_FAILED": {
      "http_status": 422,
      "description": "A step in the pipeline tool failed to execute.",
      "resolution": "The request was accepted and then could not be carried out. The failure is in the work, not the request shape — read `meta` and the trace for which step failed before retrying.",
      "docs_url": "https://soat.ttoss.dev/docs/error-codes#pipeline_step_failed"
    },
    "POLICY_NOT_FOUND": {
      "http_status": 400,
      "description": "A referenced policy does not exist.",
      "resolution": "Fix the request and retry — the payload, parameters, or referenced ids are invalid. Compare the request against the operation in `/openapi.json`.",
      "docs_url": "https://soat.ttoss.dev/docs/error-codes#policy_not_found"
    },
    "PROJECT_DEFAULT_ROUTE_INHERITED": {
      "http_status": 409,
      "description": "A project's default_model_route_id cannot be cleared while consumers that bind neither an AI provider nor a model route inherit it — clearing it would leave them with no resolvable model. Repointing the default to another route is always allowed; the error meta reports the count and a sample of inheriting resource IDs.",
      "resolution": "The request is well-formed but the resource is not in a state that allows it. Resolve the conflicting state named in the message, then retry.",
      "docs_url": "https://soat.ttoss.dev/docs/error-codes#project_default_route_inherited"
    },
    "PROJECT_HAS_DEPENDENTS": {
      "http_status": 409,
      "description": "The project has one or more dependent resources (agents, ai providers, tools, etc.) and cannot be deleted. Use force=true to delete dependents as well.",
      "resolution": "The request is well-formed but the resource is not in a state that allows it. Resolve the conflicting state named in the message, then retry.",
      "docs_url": "https://soat.ttoss.dev/docs/error-codes#project_has_dependents"
    },
    "PROMOTION_GATE_UNMET": {
      "http_status": 409,
      "description": "The release's promotion_gate has no passing eval run against the canary version. Run the eval with agent_version pinned to the canary, or abort the rollout.",
      "resolution": "The request is well-formed but the resource is not in a state that allows it. Resolve the conflicting state named in the message, then retry.",
      "docs_url": "https://soat.ttoss.dev/docs/error-codes#promotion_gate_unmet"
    },
    "QUEUE_DRIVER_MISCONFIGURED": {
      "http_status": 500,
      "description": "The orchestration queue driver is not configured correctly — an unknown ORCHESTRATION_QUEUE_DRIVER, or a missing required variable for the selected driver (e.g. ORCHESTRATION_QUEUE_SQS_QUEUE_URL for sqs).",
      "resolution": "Retry with backoff. If it persists, the server log carries the detail the response deliberately omits.",
      "docs_url": "https://soat.ttoss.dev/docs/error-codes#queue_driver_misconfigured"
    },
    "QUOTA_CONFLICT": {
      "http_status": 409,
      "description": "A quota with the same (project, scope, scope_ref, metric, window) already exists. The all-enforce precedence rule makes duplicates pure redundancy, so a duplicate is rejected instead of stored.",
      "resolution": "The request is well-formed but the resource is not in a state that allows it. Resolve the conflicting state named in the message, then retry.",
      "docs_url": "https://soat.ttoss.dev/docs/error-codes#quota_conflict"
    },
    "QUOTA_EXCEEDED": {
      "http_status": 429,
      "description": "An enforced quota has been exceeded for the request scope. The response carries a `Retry-After` header (seconds until the window resets) and a `meta` block naming the breached quota, metric, limit, window, and reset time.",
      "resolution": "Wait until the window resets — `Retry-After` (seconds) and `meta.resets_at` both carry the time — or raise the quota with `PATCH /api/v1/quotas/{quota_id}`.",
      "docs_url": "https://soat.ttoss.dev/docs/error-codes#quota_exceeded"
    },
    "QUOTA_UNENFORCEABLE": {
      "http_status": 409,
      "description": "An enforced `cost_usd` quota with `on_unpriced: \"block\"` (the default) cannot be evaluated: the current window holds a pricing blackout — several metered events, none of them priced — so the aggregate is `0` however much was actually spent. The cap refuses the generation rather than waving through spend it cannot measure. No `Retry-After` is sent — the window resetting changes nothing; configure pricing for the models in use, or set the quota's `on_unpriced` to `\"allow\"` to accept unmeasurable spend explicitly.",
      "resolution": "Add price book entries covering the models this project runs (`POST /api/v1/prices`), so the cost quota has something to aggregate. To accept unmeasurable spend instead, set `on_unpriced: \"allow\"` on the quota with `PATCH /api/v1/quotas/{quota_id}` (or switch it to `monitor` mode to stop blocking entirely).",
      "docs_url": "https://soat.ttoss.dev/docs/error-codes#quota_unenforceable"
    },
    "REQUEST_REJECTED": {
      "http_status": 400,
      "description": "The HTTP framework rejected the request before it reached a handler — a payload over the size limit, an unsupported method, a malformed request line. The accompanying message carries the specific reason, and the response status is the one the framework chose rather than this default.",
      "resolution": "The HTTP layer rejected the request before it reached a route — method, content type, or size. The message names the reason.",
      "docs_url": "https://soat.ttoss.dev/docs/error-codes#request_rejected"
    },
    "RESOURCE_NOT_FOUND": {
      "http_status": 404,
      "description": "The requested resource does not exist or is not accessible.",
      "resolution": "Check the id, and check that the credential can see the project that owns the resource — a resource in another project is indistinguishable from one that does not exist. List the collection to confirm.",
      "docs_url": "https://soat.ttoss.dev/docs/error-codes#resource_not_found"
    },
    "SECRET_HAS_DEPENDENTS": {
      "http_status": 409,
      "description": "The secret is referenced by one or more AI providers and cannot be deleted. Use force=true to delete dependents as well.",
      "resolution": "The request is well-formed but the resource is not in a state that allows it. Resolve the conflicting state named in the message, then retry.",
      "docs_url": "https://soat.ttoss.dev/docs/error-codes#secret_has_dependents"
    },
    "SECRET_NOT_DECRYPTABLE": {
      "http_status": 500,
      "description": "A stored trigger or webhook secret could not be decrypted — SECRETS_ENCRYPTION_KEY has changed since it was written. Rotating the secret replaces it; restoring the original key recovers every secret written under it.",
      "resolution": "Retry with backoff. If it persists, the server log carries the detail the response deliberately omits.",
      "docs_url": "https://soat.ttoss.dev/docs/error-codes#secret_not_decryptable"
    },
    "SECRET_NOT_FOUND": {
      "http_status": 400,
      "description": "A secret referenced by a {{secret:...}} token does not exist in this project.",
      "resolution": "Fix the request and retry — the payload, parameters, or referenced ids are invalid. Compare the request against the operation in `/openapi.json`.",
      "docs_url": "https://soat.ttoss.dev/docs/error-codes#secret_not_found"
    },
    "SESSION_CLOSED": {
      "http_status": 409,
      "description": "The session is closed and does not accept new messages or generation requests. Open a new session to continue.",
      "resolution": "The session is closed and accepts no further messages. Open a new one, or fork this one to keep its history.",
      "docs_url": "https://soat.ttoss.dev/docs/error-codes#session_closed"
    },
    "SESSION_EXPIRED": {
      "http_status": 410,
      "description": "The session has expired due to inactivity. Open a new session to continue.",
      "resolution": "Open a new session; an expired one cannot be revived. Raise the agent's session TTL if this happens sooner than the workload needs.",
      "docs_url": "https://soat.ttoss.dev/docs/error-codes#session_expired"
    },
    "SINGLE_SESSION_CONFLICT": {
      "http_status": 409,
      "description": "An open session already exists for this actor. Use the existing session or close it first.",
      "resolution": "The agent allows one open session per actor. Reuse the session named in `meta.session_id`, or close it first.",
      "docs_url": "https://soat.ttoss.dev/docs/error-codes#single_session_conflict"
    },
    "SYSTEM_MESSAGE_NOT_ALLOWED": {
      "http_status": 400,
      "description": "A `role: \"system\"` entry was supplied in `messages`. System content never travels as a message on any SOAT surface — it goes in the `instructions` field instead — the same name on every surface (a completion request, a Chat, an Agent). Mirrors the AI SDK, whose `allowSystemInMessages` defaults to false because a system entry in a caller-supplied array is a prompt-injection vector.",
      "resolution": "Fix the request and retry — the payload, parameters, or referenced ids are invalid. Compare the request against the operation in `/openapi.json`.",
      "docs_url": "https://soat.ttoss.dev/docs/error-codes#system_message_not_allowed"
    },
    "TASK_AUTOMATION_CHAIN_LIMIT": {
      "http_status": 409,
      "description": "The task has run too many machine-driven transitions back-to-back with no outside intervention, and the next one was refused. This bounds a cycle composed across modules — a workflow state dispatching work that transitions the task back into that same state — which neither the orchestration cycle validator (intra-graph) nor the workflow itself (where revisiting states is deliberate) can see. Any move by a person, a plain API key, or an approval resolution resets the chain. The ceiling is TASK_AUTOMATION_CHAIN_LIMIT (default 50) (#885).",
      "resolution": "The request is well-formed but the resource is not in a state that allows it. Resolve the conflicting state named in the message, then retry.",
      "docs_url": "https://soat.ttoss.dev/docs/error-codes#task_automation_chain_limit"
    },
    "TASK_AUTOMATION_PROVENANCE_MISSING": {
      "http_status": 500,
      "description": "An automation-caused transition was about to be persisted with no recorded cause: principal_id, generation_id, and orchestration_run_id would all be null. This is a writer bug, not a valid degraded record — the transition is rejected instead of being silently written (#792).",
      "resolution": "Retry with backoff. If it persists, the server log carries the detail the response deliberately omits.",
      "docs_url": "https://soat.ttoss.dev/docs/error-codes#task_automation_provenance_missing"
    },
    "TASK_GUARD_REJECTED": {
      "http_status": 400,
      "description": "The transition guard evaluated to false for this task.",
      "resolution": "Fix the request and retry — the payload, parameters, or referenced ids are invalid. Compare the request against the operation in `/openapi.json`.",
      "docs_url": "https://soat.ttoss.dev/docs/error-codes#task_guard_rejected"
    },
    "TASK_NOT_FOUND": {
      "http_status": 404,
      "description": "The task does not exist or is not accessible.",
      "resolution": "Check the id and the project the credential can see, then retry. Listing the collection confirms whether the resource exists.",
      "docs_url": "https://soat.ttoss.dev/docs/error-codes#task_not_found"
    },
    "TASK_PAYLOAD_INVALID": {
      "http_status": 400,
      "description": "The task payload does not satisfy the workflow's payload_schema.",
      "resolution": "Fix the request and retry — the payload, parameters, or referenced ids are invalid. Compare the request against the operation in `/openapi.json`.",
      "docs_url": "https://soat.ttoss.dev/docs/error-codes#task_payload_invalid"
    },
    "TASK_STATE_NOT_FOUND": {
      "http_status": 400,
      "description": "A task create request's `state` does not name a declared state of the workflow.",
      "resolution": "Fix the request and retry — the payload, parameters, or referenced ids are invalid. Compare the request against the operation in `/openapi.json`.",
      "docs_url": "https://soat.ttoss.dev/docs/error-codes#task_state_not_found"
    },
    "TASK_TRANSITION_CONFLICT": {
      "http_status": 409,
      "description": "A concurrent change made the requested transition invalid from the current state, or the task is already closed.",
      "resolution": "The request is well-formed but the resource is not in a state that allows it. Resolve the conflicting state named in the message, then retry.",
      "docs_url": "https://soat.ttoss.dev/docs/error-codes#task_transition_conflict"
    },
    "TASK_TRANSITION_NOT_FOUND": {
      "http_status": 400,
      "description": "The named transition does not exist in the workflow.",
      "resolution": "Fix the request and retry — the payload, parameters, or referenced ids are invalid. Compare the request against the operation in `/openapi.json`.",
      "docs_url": "https://soat.ttoss.dev/docs/error-codes#task_transition_not_found"
    },
    "TEXT_ENCODED_TOOL_CALL": {
      "http_status": 502,
      "description": "The model wrote a tool invocation as plain assistant text (e.g. a ```json block containing {\"name\": \"<a bound tool>\", \"arguments\": {…}}) instead of making a structured tool call, so the tool never ran. Upstream-caused like AI_PROVIDER_ERROR: returning that text as the answer would be a silent data-integrity failure, so the generation fails instead.",
      "resolution": "An upstream dependency failed rather than this server itself. Retry with backoff and check that dependency.",
      "docs_url": "https://soat.ttoss.dev/docs/error-codes#text_encoded_tool_call"
    },
    "TOOL_AUTH_FAILED": {
      "http_status": 502,
      "description": "An http-type tool's `execute.auth` credentials could not be turned into a request credential — malformed service account JSON, an unusable private key, or a token endpoint that rejected the assertion. The error `meta` carries the token endpoint's status and body when it responded. Distinct from `TOOL_HTTP_ERROR`, which is the tool's own target rejecting the call.",
      "resolution": "An upstream dependency failed rather than this server itself. Retry with backoff and check that dependency.",
      "docs_url": "https://soat.ttoss.dev/docs/error-codes#tool_auth_failed"
    },
    "TOOL_CALL_NOT_SUPPORTED": {
      "http_status": 422,
      "description": "This tool type cannot be invoked server-side. Client tools must be executed by the calling client.",
      "resolution": "The request was accepted and then could not be carried out. The failure is in the work, not the request shape — read `meta` and the trace for which step failed before retrying.",
      "docs_url": "https://soat.ttoss.dev/docs/error-codes#tool_call_not_supported"
    },
    "TOOL_DISPATCH_FAILED": {
      "http_status": 422,
      "description": "A workflow on_enter `tool` dispatch did not return a result. The tool call was settled before it ran — blocked by a guardrail (class D or a class-B tripwire), or routed to human approval (class C), which a task dispatch cannot park on. The error `meta` carries the `tool_id` and the `outcome` that settled it. Dispatch an orchestration instead when the tool is approval-gated: its engine can park and resume.",
      "resolution": "The request was accepted and then could not be carried out. The failure is in the work, not the request shape — read `meta` and the trace for which step failed before retrying.",
      "docs_url": "https://soat.ttoss.dev/docs/error-codes#tool_dispatch_failed"
    },
    "TOOL_EGRESS_BLOCKED": {
      "http_status": 403,
      "description": "An http- or mcp-type tool's target is not publicly routable — a loopback, private, link-local (cloud metadata), CGNAT or IPv6 ULA address — and the deployment's TOOL_EGRESS_ALLOWED_HOSTS does not list it. Also returned when a hostname resolves to such an address, when a redirect leads to one, when the scheme is not http/https, or when the redirect chain is too long. The error `meta` carries `tool_url` and, when known, the offending `tool_address`.",
      "resolution": "The credential is authenticated but lacks this action, or is scoped to another project. Grant the action in an IAM policy, or use a credential that has it.",
      "docs_url": "https://soat.ttoss.dev/docs/error-codes#tool_egress_blocked"
    },
    "TOOL_HTTP_ERROR": {
      "http_status": 502,
      "description": "An http-type tool call returned a non-2xx response. The error `meta` carries the real upstream status code, response body, URL, and method.",
      "resolution": "An upstream dependency failed rather than this server itself. Retry with backoff and check that dependency.",
      "docs_url": "https://soat.ttoss.dev/docs/error-codes#tool_http_error"
    },
    "TOOL_NOT_FOUND": {
      "http_status": 400,
      "description": "A referenced tool does not exist.",
      "resolution": "Fix the request and retry — the payload, parameters, or referenced ids are invalid. Compare the request against the operation in `/openapi.json`.",
      "docs_url": "https://soat.ttoss.dev/docs/error-codes#tool_not_found"
    },
    "TRIGGER_ACTION_NOT_ALLOWED": {
      "http_status": 400,
      "description": "A trigger field is incompatible with its type: `action` is only valid for tool targets (and required for soat/mcp tools), and `cron` is required for schedule triggers and rejected otherwise.",
      "resolution": "Fix the request and retry — the payload, parameters, or referenced ids are invalid. Compare the request against the operation in `/openapi.json`.",
      "docs_url": "https://soat.ttoss.dev/docs/error-codes#trigger_action_not_allowed"
    },
    "TRIGGER_CAUSATION_LIMIT": {
      "http_status": 409,
      "description": "An event trigger refused to extend the causal chain that reached it — it is already in the chain, or the chain has run too deep.",
      "resolution": "The request is well-formed but the resource is not in a state that allows it. Resolve the conflicting state named in the message, then retry.",
      "docs_url": "https://soat.ttoss.dev/docs/error-codes#trigger_causation_limit"
    },
    "TRIGGER_CREATOR_UNAVAILABLE": {
      "http_status": 409,
      "description": "The trigger creator no longer exists, so the firing identity cannot be resolved.",
      "resolution": "The request is well-formed but the resource is not in a state that allows it. Resolve the conflicting state named in the message, then retry.",
      "docs_url": "https://soat.ttoss.dev/docs/error-codes#trigger_creator_unavailable"
    },
    "TRIGGER_INPUT_INVALID": {
      "http_status": 400,
      "description": "The effective input is invalid for the target (empty agent input, or an orchestration input_schema violation).",
      "resolution": "Fix the request and retry — the payload, parameters, or referenced ids are invalid. Compare the request against the operation in `/openapi.json`.",
      "docs_url": "https://soat.ttoss.dev/docs/error-codes#trigger_input_invalid"
    },
    "TRIGGER_NOT_ACTIVE": {
      "http_status": 409,
      "description": "The trigger is inactive and cannot fire.",
      "resolution": "The request is well-formed but the resource is not in a state that allows it. Resolve the conflicting state named in the message, then retry.",
      "docs_url": "https://soat.ttoss.dev/docs/error-codes#trigger_not_active"
    },
    "TRIGGER_RECURSION_FORBIDDEN": {
      "http_status": 403,
      "description": "A trigger-scoped credential cannot fire a trigger, preventing unbounded trigger→trigger loops.",
      "resolution": "The credential is authenticated but lacks this action, or is scoped to another project. Grant the action in an IAM policy, or use a credential that has it.",
      "docs_url": "https://soat.ttoss.dev/docs/error-codes#trigger_recursion_forbidden"
    },
    "TRIGGER_TARGET_NOT_FOUND": {
      "http_status": 400,
      "description": "The trigger target does not exist in the project, or its type does not match target_type.",
      "resolution": "Fix the request and retry — the payload, parameters, or referenced ids are invalid. Compare the request against the operation in `/openapi.json`.",
      "docs_url": "https://soat.ttoss.dev/docs/error-codes#trigger_target_not_found"
    },
    "TRIGGER_TYPE_IMMUTABLE": {
      "http_status": 400,
      "description": "A trigger’s type cannot be changed after creation.",
      "resolution": "Fix the request and retry — the payload, parameters, or referenced ids are invalid. Compare the request against the operation in `/openapi.json`.",
      "docs_url": "https://soat.ttoss.dev/docs/error-codes#trigger_type_immutable"
    },
    "UNAUTHORIZED": {
      "http_status": 401,
      "description": "Authentication is required or the token is invalid.",
      "resolution": "Send `Authorization: Bearer <token>` — a user token from `POST /api/v1/users/login`, a project API key (`sk_…`), or an OAuth access token. Discovery metadata for the OAuth flow is at `/.well-known/oauth-authorization-server`.",
      "docs_url": "https://soat.ttoss.dev/docs/error-codes#unauthorized"
    },
    "UNSUPPORTED_FILE_TYPE": {
      "http_status": 400,
      "description": "The referenced file has a content type that cannot be ingested. Supported types: application/pdf, text/plain, text/markdown.",
      "resolution": "The file's media type has no parser on this deployment. Convert it to a supported type, or add an ingestion rule that does.",
      "docs_url": "https://soat.ttoss.dev/docs/error-codes#unsupported_file_type"
    },
    "UPLOAD_TOKEN_EXPIRED": {
      "http_status": 410,
      "description": "The upload token has expired. Request a new one.",
      "resolution": "Request a fresh upload token; tokens are single-use and short-lived by design.",
      "docs_url": "https://soat.ttoss.dev/docs/error-codes#upload_token_expired"
    },
    "UPLOAD_TOKEN_NOT_FOUND": {
      "http_status": 404,
      "description": "The upload token does not exist.",
      "resolution": "Check the id and the project the credential can see, then retry. Listing the collection confirms whether the resource exists.",
      "docs_url": "https://soat.ttoss.dev/docs/error-codes#upload_token_not_found"
    },
    "UPLOAD_TOKEN_USED": {
      "http_status": 409,
      "description": "The upload token has already been used.",
      "resolution": "This upload token has already been redeemed. Request a new one for another upload.",
      "docs_url": "https://soat.ttoss.dev/docs/error-codes#upload_token_used"
    },
    "VALIDATION_FAILED": {
      "http_status": 400,
      "description": "The request body failed validation.",
      "resolution": "Fix the request and retry. Unknown fields are rejected outright, so compare the payload against the operation in `/openapi.json`; `meta` names the offending field when the check can identify one.",
      "docs_url": "https://soat.ttoss.dev/docs/error-codes#validation_failed"
    },
    "WORKFLOW_HAS_OPEN_TASKS": {
      "http_status": 409,
      "description": "The workflow has one or more open tasks and cannot be deleted.",
      "resolution": "The request is well-formed but the resource is not in a state that allows it. Resolve the conflicting state named in the message, then retry.",
      "docs_url": "https://soat.ttoss.dev/docs/error-codes#workflow_has_open_tasks"
    },
    "WORKFLOW_NOT_FOUND": {
      "http_status": 404,
      "description": "The workflow does not exist or is not accessible.",
      "resolution": "Check the id and the project the credential can see, then retry. Listing the collection confirms whether the resource exists.",
      "docs_url": "https://soat.ttoss.dev/docs/error-codes#workflow_not_found"
    },
    "WORKFLOW_VALIDATION_FAILED": {
      "http_status": 400,
      "description": "The workflow definition is invalid (e.g. duplicate state names, not exactly one initial state, a transition referencing an unknown state, a malformed guard, or an on_enter dispatch missing its target).",
      "resolution": "Fix the request and retry — the payload, parameters, or referenced ids are invalid. Compare the request against the operation in `/openapi.json`.",
      "docs_url": "https://soat.ttoss.dev/docs/error-codes#workflow_validation_failed"
    }
  }
}
