openapi: 3.1.0
info:
  title: AGRouter API
  description: |
    AGRouter is an agent-native platform for connecting Agents to the real world.
    Use one account and one key to call Models, ready-made APIs, and Agent Services.

    ## Authentication
    Public API requests require a AGRouter API key. Use `Authorization: Bearer sk-YOUR_KEY` by default.
    Anthropic Messages and Google Gemini-compatible operations also document their native SDK-compatible key locations.

    ## SDK Compatibility
    - **LLM Gateway** (`/v1/chat/completions`): Use OpenAI SDK with `base_url="https://api.agrouter.ai/v1"`
    - **Embeddings** (`/v1/embeddings`): Use OpenAI SDK with `base_url="https://api.agrouter.ai/v1"`
    - **Images** (`/v1/images/generations`, `/v1/images/edits`): Use OpenAI SDK with `base_url="https://api.agrouter.ai/v1"`
    - **Anthropic Messages** (`/v1/messages`): Use Anthropic SDK with `base_url="https://api.agrouter.ai"`
    - **Google Gemini GenerateContent** (`/v1beta/models/{model}:generateContent`): Configure the Google Gen AI SDK to use `https://api.agrouter.ai`
    - **Agents and Sessions** (`/v1/agents`, `/v1/sessions`): Use the documented AGRouter HTTP resource APIs
  version: 1.0.0
  x-agrouter-session-contract:
    status: active
    feature: persistent public Session identity
    description: >-
      Public session_id identifies persistent Agent interaction across Direct Session, Service, and Schedule
      (Deployment) entry points. Service invocation creates or continues a Session and sends one event. Every manual or
      scheduled Deployment trigger creates a public drun_* DeploymentRun and attempts to create one new Session.
      Internal Runtime Session identities are never exposed.
  contact:
    name: AGRouter
    url: https://www.agrouter.ai
  license:
    name: Proprietary

servers:
  - url: https://api.agrouter.ai
    description: Production

security:
  - BearerAuth: []

tags:
  - name: Chat Completions
    description: OpenAI-compatible LLM Gateway
  - name: Responses
    description: OpenAI-compatible Responses API
  - name: Embeddings
    description: OpenAI-compatible text embeddings
  - name: Images
    description: OpenAI-compatible image generation and editing
  - name: Messages
    description: Anthropic-compatible Messages API
  - name: Gemini
    description: Google Gemini-compatible GenerateContent and Interactions APIs
  - name: Official Native API
    description: Provider-native model protocols, currently ByteDance Seedance content-generation tasks
  - name: Agents
    description: Build and manage Agent definitions
  - name: Sessions
    description: Create stateful Agent executions and exchange events
  - name: Services
    description: Publish and invoke tested Agents as Services
  - name: Schedules
    description: Create and manage Schedules through Deployment resources
  - name: Events
    description: Run event streaming
  - name: Models
    description: Model catalog
  - name: Tasks
    description: Asynchronous model task status, settlement, and usage
  - name: Account
    description: Organization balance and API execution history
  - name: Skills
    description: Reusable Agent instruction and resource bundles
  - name: Credentials
    description: Private values injected into Agent runtimes

paths:
  # --- LLM Gateway ------------------------------------------------
  /v1/chat/completions:
    post:
      operationId: createChatCompletion
      tags: [Chat Completions]
      summary: Create chat completion
      description: >-
        OpenAI-compatible chat completions endpoint. AGRouter uses model and stream for routing and preserves
        additional provider-compatible fields when the selected route uses the same protocol. Support for
        provider-specific fields can vary when AGRouter translates between protocols.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ChatCompletionRequest'
      responses:
        '200':
          description: Chat completion response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ChatCompletionResponse'
            text/event-stream:
              schema:
                type: string
                description: "SSE stream of chat.completion.chunk events, followed by data: [DONE]."
        '400': { description: "Invalid JSON, missing model, or an invalid provider request", content: { application/json: { schema: { $ref: '#/components/schemas/ChatCompletionError' } } } }
        '401': { description: Missing or invalid API key, content: { application/json: { schema: { $ref: '#/components/schemas/TaskCostError' } } } }
        '402':
          description: Organization spending limit reached
          content:
            application/json:
              schema:
                oneOf:
                  - $ref: '#/components/schemas/ChatCompletionError'
                  - $ref: '#/components/schemas/TaskCostError'
        '403':
          description: Organization is disabled, or the API key scope does not authorize this endpoint
          content:
            application/json:
              schema:
                oneOf:
                  - $ref: '#/components/schemas/ChatCompletionError'
                  - $ref: '#/components/schemas/APIKeyScopeError'
        '404': { description: Model not found or unavailable, content: { application/json: { schema: { $ref: '#/components/schemas/ChatCompletionError' } } } }
        '429': { description: Rate limit exceeded, content: { application/json: { schema: { $ref: '#/components/schemas/TaskCostError' } } } }
        '500': { description: Request persistence or organization lookup failed, content: { application/json: { schema: { $ref: '#/components/schemas/ChatCompletionError' } } } }
        '502': { description: Invalid response from the selected provider, content: { application/json: { schema: { $ref: '#/components/schemas/ChatCompletionError' } } } }
        '503': { description: No provider route succeeded, content: { application/json: { schema: { $ref: '#/components/schemas/ChatCompletionError' } } } }

  /v1/responses:
    post:
      operationId: createResponse
      tags: [Responses]
      summary: Create a model response
      description: >-
        OpenAI-compatible Responses API endpoint. AGRouter validates model and stream for routing, rewrites model
        to the selected provider mapping, and preserves other request fields. Responses are rebuilt from public
        OpenAI fields: unknown provider, routing, account, and billing extensions are removed. Unsafe responses fail
        closed instead of being forwarded.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ResponsesRequest'
      responses:
        '200':
          description: OpenAI-compatible response object or event stream from the selected provider.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ResponsesResponse'
            text/event-stream:
              schema:
                type: string
                description: >-
                  Sanitized OpenAI Responses events. Public response.* and error events are forwarded; unknown
                  private events and non-public event fields are removed. A malformed stream closes without
                  forwarding unsafe data.
        '400': { description: "Invalid JSON, missing model, no provider, or upstream request rejected", content: { application/json: { schema: { $ref: '#/components/schemas/ChatCompletionError' } } } }
        '401':
          description: Missing or invalid API key, or upstream authentication rejection
          content:
            application/json:
              schema:
                oneOf:
                  - $ref: '#/components/schemas/TaskCostError'
                  - $ref: '#/components/schemas/ChatCompletionError'
        '402':
          description: Organization or API Key spending limit reached
          content:
            application/json:
              schema:
                oneOf:
                  - $ref: '#/components/schemas/TaskCostError'
                  - $ref: '#/components/schemas/ChatCompletionError'
        '403':
          description: API key scope or upstream permission rejection
          content:
            application/json:
              schema:
                oneOf:
                  - $ref: '#/components/schemas/TaskCostError'
                  - $ref: '#/components/schemas/APIKeyScopeError'
                  - $ref: '#/components/schemas/ChatCompletionError'
        '413': { description: Upstream provider rejected the request as too large, content: { application/json: { schema: { $ref: '#/components/schemas/ChatCompletionError' } } } }
        '500': { description: Prediction lifecycle or organization lookup failed, content: { application/json: { schema: { oneOf: [{ $ref: '#/components/schemas/ChatCompletionError' }, { $ref: '#/components/schemas/TaskCostError' }] } } } }
        '502': { description: Upstream response could not be safely parsed and sanitized, content: { application/json: { schema: { $ref: '#/components/schemas/ChatCompletionError' } } } }
        '503': { description: Routing failed or every provider candidate was exhausted, content: { application/json: { schema: { $ref: '#/components/schemas/ChatCompletionError' } } } }

  /v1/embeddings:
    post:
      operationId: createEmbedding
      tags: [Embeddings]
      summary: Create embeddings
      description: OpenAI-compatible embeddings endpoint for semantic search, RAG, clustering, and classification. AGRouter validates the logical model and forwards provider-compatible embedding fields; model-specific constraints are defined by the selected model and upstream provider.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/EmbeddingRequest'
      responses:
        '200':
          description: Embedding response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EmbeddingResponse'
        '400': { description: Invalid request or non-embedding model, content: { application/json: { schema: { $ref: '#/components/schemas/EmbeddingError' } } } }
        '401': { description: Missing or invalid API key, content: { application/json: { schema: { $ref: '#/components/schemas/TaskCostError' } } } }
        '402':
          description: Organization or API Key spending limit reached
          content:
            application/json:
              schema:
                oneOf:
                  - $ref: '#/components/schemas/EmbeddingError'
                  - $ref: '#/components/schemas/TaskCostError'
        '403':
          description: Organization is disabled, or the API key scope does not authorize this endpoint
          content:
            application/json:
              schema:
                oneOf:
                  - $ref: '#/components/schemas/EmbeddingError'
                  - $ref: '#/components/schemas/APIKeyScopeError'
        '404': { description: Logical model not found, content: { application/json: { schema: { $ref: '#/components/schemas/EmbeddingError' } } } }
        '500': { description: Request persistence or organization lookup failed, content: { application/json: { schema: { $ref: '#/components/schemas/EmbeddingError' } } } }
        '503': { description: No provider route succeeded, content: { application/json: { schema: { $ref: '#/components/schemas/EmbeddingError' } } } }

  /v1/images/generations:
    post:
      operationId: createImage
      tags: [Images]
      summary: Generate an image
      description: >-
        Generate images through the synchronous OpenAI Images-compatible endpoint. Supported public model names are
        gpt-image-2, gpt-image-2.5-flare, and gpt-image-2.5-sunburst. Additional compatible JSON fields are forwarded
        unchanged. Streaming and asynchronous mode are not supported on this endpoint.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ImageGenerationRequest'
      responses:
        '200':
          description: Generated images and authoritative token usage
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ImagesResponse'
        '400': { description: "Invalid body, unsupported model, streaming request, or provider request", content: { application/json: { schema: { $ref: '#/components/schemas/OpenAIImagesError' } } } }
        '401': { description: Missing or invalid API key, content: { application/json: { schema: { $ref: '#/components/schemas/TaskCostError' } } } }
        '402':
          description: Organization or API Key spending limit reached
          content:
            application/json:
              schema:
                oneOf:
                  - $ref: '#/components/schemas/OpenAIImagesError'
                  - $ref: '#/components/schemas/TaskCostError'
        '403':
          description: Organization is disabled, or the API key scope does not authorize this endpoint
          content:
            application/json:
              schema:
                oneOf:
                  - $ref: '#/components/schemas/TaskCostError'
                  - $ref: '#/components/schemas/APIKeyScopeError'
        '404': { description: Routed image model is unavailable, content: { application/json: { schema: { $ref: '#/components/schemas/OpenAIImagesError' } } } }
        '429': { description: Rate limit exceeded, content: { application/json: { schema: { $ref: '#/components/schemas/TaskCostError' } } } }
        '500': { description: Request persistence or response processing failed, content: { application/json: { schema: { oneOf: [{ $ref: '#/components/schemas/OpenAIImagesError' }, { $ref: '#/components/schemas/TaskCostError' }] } } } }
        '502': { description: Provider returned an invalid native response, content: { application/json: { schema: { $ref: '#/components/schemas/OpenAIImagesError' } } } }
        '503': { description: No provider route succeeded, content: { application/json: { schema: { $ref: '#/components/schemas/OpenAIImagesError' } } } }

  /v1/images/edits:
    post:
      operationId: createImageEdit
      tags: [Images]
      summary: Edit an image
      description: >-
        Edit one or more uploaded images through the synchronous OpenAI Images-compatible multipart endpoint.
        Supported public model names are gpt-image-2, gpt-image-2.5-flare, and gpt-image-2.5-sunburst. Repeating the
        image field is supported. Additional compatible multipart fields are forwarded unchanged. Streaming and
        asynchronous mode are not supported on this endpoint.
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              $ref: '#/components/schemas/ImageEditRequest'
      responses:
        '200':
          description: Edited images and authoritative token usage
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ImagesResponse'
        '400': { description: "Invalid multipart body, unsupported model, streaming request, or provider request", content: { application/json: { schema: { $ref: '#/components/schemas/OpenAIImagesError' } } } }
        '401': { description: Missing or invalid API key, content: { application/json: { schema: { $ref: '#/components/schemas/TaskCostError' } } } }
        '402':
          description: Organization or API Key spending limit reached
          content:
            application/json:
              schema:
                oneOf:
                  - $ref: '#/components/schemas/OpenAIImagesError'
                  - $ref: '#/components/schemas/TaskCostError'
        '403':
          description: Organization is disabled, or the API key scope does not authorize this endpoint
          content:
            application/json:
              schema:
                oneOf:
                  - $ref: '#/components/schemas/TaskCostError'
                  - $ref: '#/components/schemas/APIKeyScopeError'
        '404': { description: Routed image-edit model is unavailable, content: { application/json: { schema: { $ref: '#/components/schemas/OpenAIImagesError' } } } }
        '429': { description: Rate limit exceeded, content: { application/json: { schema: { $ref: '#/components/schemas/TaskCostError' } } } }
        '500': { description: Request persistence or response processing failed, content: { application/json: { schema: { oneOf: [{ $ref: '#/components/schemas/OpenAIImagesError' }, { $ref: '#/components/schemas/TaskCostError' }] } } } }
        '502': { description: Provider returned an invalid native response, content: { application/json: { schema: { $ref: '#/components/schemas/OpenAIImagesError' } } } }
        '503': { description: No provider route succeeded, content: { application/json: { schema: { $ref: '#/components/schemas/OpenAIImagesError' } } } }

  /v1/messages:
    post:
      operationId: createMessage
      tags: [Messages]
      summary: Create message (Anthropic compatible)
      description: >-
        Anthropic Messages-compatible endpoint. AGRouter uses model and stream for routing, maps the public model
        name to the selected provider, and otherwise preserves the compatible request and response protocol.
        Provider compatibility rules may remove or reject unsupported beta fields before an upstream attempt.
      security:
        - BearerAuth: []
        - AnthropicApiKey: []
      parameters:
        - name: anthropic-version
          in: header
          required: false
          description: Anthropic API version. Defaults to 2023-06-01 when omitted.
          schema:
            type: string
            default: "2023-06-01"
            example: "2023-06-01"
        - name: anthropic-beta
          in: header
          required: false
          description: Comma-separated Anthropic beta feature identifiers. Availability is provider-specific.
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [model, max_tokens, messages]
              additionalProperties: true
              description: Anthropic-compatible request. Additional fields are preserved unless a selected provider's compatibility rules remove or reject them.
              properties:
                model:
                  type: string
                  example: "claude-sonnet-5"
                max_tokens:
                  type: integer
                  example: 4096
                messages:
                  type: array
                  items:
                    type: object
                    required: [role, content]
                    additionalProperties: true
                    properties:
                      role:
                        type: string
                        enum: [user, assistant]
                      content:
                        oneOf:
                          - type: string
                          - type: array
                            items:
                              type: object
                              additionalProperties: true
                system:
                  oneOf:
                    - type: string
                    - type: array
                      items:
                        type: object
                        additionalProperties: true
                temperature:
                  type: number
                  minimum: 0
                  maximum: 1
                top_p:
                  type: number
                  minimum: 0
                  maximum: 1
                top_k:
                  type: integer
                  minimum: 0
                stop_sequences:
                  type: array
                  items: { type: string }
                stream:
                  type: boolean
                  default: false
                metadata:
                  type: object
                  additionalProperties: true
                tools:
                  type: array
                  items:
                    type: object
                    additionalProperties: true
                tool_choice:
                  type: object
                  additionalProperties: true
                thinking:
                  type: object
                  description: Extended thinking configuration when supported by the selected model.
                  additionalProperties: true
      responses:
        '200':
          description: Anthropic message response or event stream from the selected provider
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AnthropicMessageResponse'
            text/event-stream:
              schema:
                type: string
                description: Anthropic SSE events, including provider-defined beta events and possible in-stream error events.
        '400': { description: "Invalid JSON, missing model, unsupported feature, or invalid provider request", content: { application/json: { schema: { $ref: '#/components/schemas/AnthropicError' } } } }
        '401': { description: "Missing, invalid, revoked, or expired API key", content: { application/json: { schema: { $ref: '#/components/schemas/AnthropicError' } } } }
        '402': { description: Organization balance and credit are exhausted, content: { application/json: { schema: { $ref: '#/components/schemas/AnthropicError' } } } }
        '403': { description: 'API Key spending limit reached, organization disabled, or upstream permission rejected', content: { application/json: { schema: { $ref: '#/components/schemas/AnthropicError' } } } }
        '404': { description: Model or upstream resource not found, content: { application/json: { schema: { $ref: '#/components/schemas/AnthropicError' } } } }
        '413': { description: Provider rejected the request as too large, content: { application/json: { schema: { $ref: '#/components/schemas/AnthropicError' } } } }
        '429': { description: Provider rate limit exceeded, content: { application/json: { schema: { $ref: '#/components/schemas/AnthropicError' } } } }
        '500': { description: Prediction lifecycle or provider error, content: { application/json: { schema: { $ref: '#/components/schemas/AnthropicError' } } } }
        '502': { description: Upstream stream ended before usable content, content: { application/json: { schema: { $ref: '#/components/schemas/AnthropicError' } } } }
        '503': { description: Routing failed or all provider candidates were unavailable, content: { application/json: { schema: { $ref: '#/components/schemas/AnthropicError' } } } }

  # --- Google Gemini GenerateContent -----------------------------
  /v1beta/models/{model}:generateContent:
    post:
      operationId: generateGeminiContent
      tags: [Gemini]
      summary: Generate content with the Google Gemini protocol
      description: |
        Accepts a Google Gemini GenerateContent request and returns a Gemini-compatible response. A bare model name,
        such as gemini-3.5-flash, is normalized to the AGRouter catalog name google/gemini-3.5-flash. This endpoint
        also accepts x-goog-api-key and the key query parameter for Google SDK compatibility, although headers are safer.
        Models with a native passthrough mapping preserve provider-defined request and response fields, including image
        response modalities. Other mappings may use the translated capability subset documented in the schema below.
      security:
        - GoogleApiKey: []
        - BearerAuth: []
        - GoogleQueryApiKey: []
      parameters:
        - $ref: '#/components/parameters/GeminiModel'
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/GeminiGenerateContentRequest' }
      responses:
        '200': { description: Gemini GenerateContent response, content: { application/json: { schema: { $ref: '#/components/schemas/GeminiGenerateContentResponse' } } } }
        '400': { description: Invalid JSON or a request rejected by the selected native or translated mapping, content: { application/json: { schema: { $ref: '#/components/schemas/GeminiError' } } } }
        '401': { description: 'Missing, invalid, revoked, or expired API key', content: { application/json: { schema: { $ref: '#/components/schemas/GeminiError' } } } }
        '402': { description: Organization balance and credit are exhausted, content: { application/json: { schema: { $ref: '#/components/schemas/GeminiError' } } } }
        '403': { description: 'API Key spending limit reached, organization disabled, or upstream permission rejected', content: { application/json: { schema: { $ref: '#/components/schemas/GeminiError' } } } }
        '404': { description: 'Model, method, or upstream resource not found', content: { application/json: { schema: { $ref: '#/components/schemas/GeminiError' } } } }
        '429': { description: Provider rate limit exceeded, content: { application/json: { schema: { $ref: '#/components/schemas/GeminiError' } } } }
        '500': { description: Prediction lifecycle or provider error, content: { application/json: { schema: { $ref: '#/components/schemas/GeminiError' } } } }
        '502': { description: Upstream response failed or could not be processed, content: { application/json: { schema: { $ref: '#/components/schemas/GeminiError' } } } }
        '503': { description: Routing failed or all provider candidates were unavailable, content: { application/json: { schema: { $ref: '#/components/schemas/GeminiError' } } } }
        '504': { description: Upstream request timed out, content: { application/json: { schema: { $ref: '#/components/schemas/GeminiError' } } } }

  /v1beta/models/{model}:streamGenerateContent:
    post:
      operationId: streamGeminiContent
      tags: [Gemini]
      summary: Stream content with the Google Gemini protocol
      description: Returns a streamed JSON array by default. Add alt=sse to receive Server-Sent Events instead.
      security:
        - GoogleApiKey: []
        - BearerAuth: []
        - GoogleQueryApiKey: []
      parameters:
        - $ref: '#/components/parameters/GeminiModel'
        - name: alt
          in: query
          schema: { type: string, enum: [sse] }
          description: Set to sse for Server-Sent Events. Any other or omitted value uses the Google-compatible streamed JSON array.
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/GeminiGenerateContentRequest' }
      responses:
        '200':
          description: Gemini response frames
          content:
            application/json:
              schema:
                type: array
                items: { $ref: '#/components/schemas/GeminiGenerateContentResponse' }
            text/event-stream:
              schema:
                type: string
                description: SSE data frames containing Gemini GenerateContentResponse objects.
        '400': { description: Invalid JSON or an input field that AGRouter cannot safely translate, content: { application/json: { schema: { $ref: '#/components/schemas/GeminiError' } } } }
        '401': { description: 'Missing, invalid, revoked, or expired API key', content: { application/json: { schema: { $ref: '#/components/schemas/GeminiError' } } } }
        '402': { description: Organization balance and credit are exhausted, content: { application/json: { schema: { $ref: '#/components/schemas/GeminiError' } } } }
        '403': { description: 'API Key spending limit reached, organization disabled, or upstream permission rejected', content: { application/json: { schema: { $ref: '#/components/schemas/GeminiError' } } } }
        '404': { description: 'Model, method, or upstream resource not found', content: { application/json: { schema: { $ref: '#/components/schemas/GeminiError' } } } }
        '429': { description: Provider rate limit exceeded before streaming starts, content: { application/json: { schema: { $ref: '#/components/schemas/GeminiError' } } } }
        '500': { description: Prediction lifecycle or provider error before streaming starts, content: { application/json: { schema: { $ref: '#/components/schemas/GeminiError' } } } }
        '502': { description: Upstream stream could not be translated, content: { application/json: { schema: { $ref: '#/components/schemas/GeminiError' } } } }
        '503': { description: Routing failed or all provider candidates were unavailable, content: { application/json: { schema: { $ref: '#/components/schemas/GeminiError' } } } }
        '504': { description: Upstream request timed out before streaming starts, content: { application/json: { schema: { $ref: '#/components/schemas/GeminiError' } } } }

  # --- Google Gemini Interactions --------------------------------
  /v1beta/interactions:
    post:
      operationId: createGeminiInteraction
      tags: [Gemini]
      summary: Create a Google Gemini Interaction
      description: |
        Proxies the Google Interactions protocol while routing the logical model through AGRouter. Successful requests
        always return HTTP 200. Check status: an in_progress response is pollable and is not a completed result.
        Set stream=true for SSE, or background=true for a durable background submission; they cannot both be true.
      security:
        - GoogleApiKey: []
        - BearerAuth: []
        - GoogleQueryApiKey: []
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/GeminiInteractionRequest' }
      responses:
        '200':
          description: Current or terminal Interaction, or an Interaction event stream
          headers:
            Location:
              schema: { type: string }
              description: Polling path when the returned Interaction is still in_progress. This convenience header may be omitted for a terminal response.
          content:
            application/json:
              schema: { $ref: '#/components/schemas/GeminiInteraction' }
            text/event-stream:
              schema:
                type: string
                description: Google Interactions SSE events ending with a done event and data [DONE]. In-stream failures use an error event after headers are written.
        '400': { description: 'Invalid JSON, missing model, unsupported Agent interaction, incompatible background and stream settings, stale conversation affinity, or upstream request rejection', content: { application/json: { schema: { $ref: '#/components/schemas/GeminiError' } } } }
        '401': { description: 'Missing, invalid, revoked, or expired API key', content: { application/json: { schema: { $ref: '#/components/schemas/GeminiError' } } } }
        '402': { description: Organization balance and credit are exhausted, content: { application/json: { schema: { $ref: '#/components/schemas/GeminiError' } } } }
        '403': { description: 'API Key spending limit reached, organization disabled, or upstream permission rejected', content: { application/json: { schema: { $ref: '#/components/schemas/GeminiError' } } } }
        '404': { description: Logical model or usable provider was not found, content: { application/json: { schema: { $ref: '#/components/schemas/GeminiError' } } } }
        '422': { description: Upstream rejected otherwise valid Interaction fields, content: { application/json: { schema: { $ref: '#/components/schemas/GeminiError' } } } }
        '429': { description: Provider rate limit exceeded after failover attempts, content: { application/json: { schema: { $ref: '#/components/schemas/GeminiError' } } } }
        '500': { description: Prediction lifecycle or internal persistence failed, content: { application/json: { schema: { $ref: '#/components/schemas/GeminiError' } } } }
        '502': { description: Upstream response exceeded the gateway size limit, content: { application/json: { schema: { $ref: '#/components/schemas/GeminiError' } } } }
        '503': { description: 'Routing, upstream connection, or durable background handoff failed', content: { application/json: { schema: { $ref: '#/components/schemas/GeminiError' } } } }
        '504': { description: Upstream request timed out after failover attempts, content: { application/json: { schema: { $ref: '#/components/schemas/GeminiError' } } } }

  /v1beta/interactions/{id}:
    get:
      operationId: getGeminiInteraction
      tags: [Gemini]
      summary: Get a Google Gemini Interaction
      description: |
        Retrieves the Interaction created by the same AGRouter API key. Non-terminal responses omit partial output and
        usage. When upstream media has expired, a terminal fallback can preserve structure and metadata without the
        original media bytes; download terminal media promptly rather than treating this endpoint as durable storage.
      security:
        - GoogleApiKey: []
        - BearerAuth: []
        - GoogleQueryApiKey: []
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: string, minLength: 1 }
          description: Upstream Interaction ID returned by POST. It is not a AGRouter prediction ID.
      responses:
        '200': { description: Current or terminal Interaction, content: { application/json: { schema: { $ref: '#/components/schemas/GeminiInteraction' } } } }
        '400': { description: Empty Interaction ID, content: { application/json: { schema: { $ref: '#/components/schemas/GeminiError' } } } }
        '401': { description: 'Missing, invalid, revoked, or expired API key', content: { application/json: { schema: { $ref: '#/components/schemas/GeminiError' } } } }
        '402': { description: Organization balance and credit are exhausted, content: { application/json: { schema: { $ref: '#/components/schemas/GeminiError' } } } }
        '403': { description: API Key spending limit reached or organization disabled, content: { application/json: { schema: { $ref: '#/components/schemas/GeminiError' } } } }
        '404': { description: 'Interaction is unknown, expired, or not owned by the current API key', content: { application/json: { schema: { $ref: '#/components/schemas/GeminiError' } } } }
        '500': { description: Organization admission lookup failed, content: { application/json: { schema: { $ref: '#/components/schemas/GeminiError' } } } }
        '502': { description: Upstream Interaction response exceeded the gateway size limit, content: { application/json: { schema: { $ref: '#/components/schemas/GeminiError' } } } }
        '503': { description: 'Affinity storage, provider lookup, credentials, or upstream retrieval is temporarily unavailable', content: { application/json: { schema: { $ref: '#/components/schemas/GeminiError' } } } }

  # --- Volcengine Ark Contents Generations ----------------------
  /api/v3/contents/generations/tasks:
    post:
      operationId: createVolcengineContentsGenerationTask
      tags: [Official Native API]
      summary: Create a content generation task
      description: Creates an asynchronous Seedance video generation task using the Volcengine Ark native request shape.
      security:
        - BearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/VolcengineContentsGenerationRequest' }
      responses:
        '200': { description: Task accepted, content: { application/json: { schema: { $ref: '#/components/schemas/VolcengineTaskCreated' } } } }
        '400': { description: 'Invalid JSON, unsupported model or content mode, or rejected generation parameters', content: { application/json: { schema: { $ref: '#/components/schemas/VolcengineError' } } } }
        '401': { description: 'Missing, invalid, revoked, or expired API key', content: { application/json: { schema: { $ref: '#/components/schemas/VolcengineError' } } } }
        '402': { description: Organization balance and credit are exhausted, content: { application/json: { schema: { $ref: '#/components/schemas/VolcengineError' } } } }
        '403': { description: 'API Key spending limit reached, organization disabled, or upstream permission rejected', content: { application/json: { schema: { $ref: '#/components/schemas/VolcengineError' } } } }
        '404': { description: Model or upstream resource not found, content: { application/json: { schema: { $ref: '#/components/schemas/VolcengineError' } } } }
        '429': { description: Provider rate limit exceeded, content: { application/json: { schema: { $ref: '#/components/schemas/VolcengineError' } } } }
        '500': { description: Prediction lifecycle or provider error, content: { application/json: { schema: { $ref: '#/components/schemas/VolcengineError' } } } }
    get:
      operationId: listVolcengineContentsGenerationTasks
      tags: [Official Native API]
      summary: List content generation tasks
      description: Lists non-deleted native video tasks created by the same organization and API key, newest first.
      security:
        - BearerAuth: []
      parameters:
        - { name: page_num, in: query, schema: { type: integer, minimum: 1, maximum: 500, default: 1 } }
        - { name: page_size, in: query, schema: { type: integer, minimum: 1, maximum: 500, default: 10 } }
        - name: filter.task_ids
          in: query
          description: Task IDs, supplied as repeated parameters or a comma-separated value.
          schema: { type: array, items: { type: string } }
          style: form
          explode: true
        - { name: filter.model, in: query, schema: { type: string } }
        - { name: filter.status, in: query, schema: { type: string, enum: [queued, running, cancelled, succeeded, failed, expired] } }
      responses:
        '200': { description: Task page, content: { application/json: { schema: { $ref: '#/components/schemas/VolcengineTaskList' } } } }
        '401': { description: 'Missing, invalid, revoked, or expired API key', content: { application/json: { schema: { $ref: '#/components/schemas/VolcengineError' } } } }
        '402': { description: Organization balance and credit are exhausted, content: { application/json: { schema: { $ref: '#/components/schemas/VolcengineError' } } } }
        '403': { description: API Key spending limit reached or organization disabled, content: { application/json: { schema: { $ref: '#/components/schemas/VolcengineError' } } } }
        '500': { description: Task lookup failed, content: { application/json: { schema: { $ref: '#/components/schemas/VolcengineError' } } } }

  /api/v3/contents/generations/tasks/{task_id}:
    parameters:
      - name: task_id
        in: path
        required: true
        schema: { type: string, minLength: 1 }
        description: Task ID returned by the create operation.
    get:
      operationId: getVolcengineContentsGenerationTask
      tags: [Official Native API]
      summary: Get a content generation task
      security:
        - BearerAuth: []
      responses:
        '200': { description: Current task state, content: { application/json: { schema: { $ref: '#/components/schemas/VolcengineTask' } } } }
        '401': { description: 'Missing, invalid, revoked, or expired API key', content: { application/json: { schema: { $ref: '#/components/schemas/VolcengineError' } } } }
        '402': { description: Organization balance and credit are exhausted, content: { application/json: { schema: { $ref: '#/components/schemas/VolcengineError' } } } }
        '403': { description: API Key spending limit reached or organization disabled, content: { application/json: { schema: { $ref: '#/components/schemas/VolcengineError' } } } }
        '404': { description: 'Task is unknown, deleted, not native, or not owned by the current API key', content: { application/json: { schema: { $ref: '#/components/schemas/VolcengineError' } } } }
        '500': { description: Task lookup failed, content: { application/json: { schema: { $ref: '#/components/schemas/VolcengineError' } } } }
    delete:
      operationId: deleteVolcengineContentsGenerationTask
      tags: [Official Native API]
      summary: Cancel or delete a content generation task
      description: Cancels a queued task or hides a terminal task. Running and already-cancelled tasks cannot be deleted.
      security:
        - BearerAuth: []
      responses:
        '204': { description: Task cancelled or deleted }
        '400': { description: 'Task is running, already cancelled, or otherwise cannot be deleted in its current state', content: { application/json: { schema: { $ref: '#/components/schemas/VolcengineError' } } } }
        '401': { description: 'Missing, invalid, revoked, or expired API key', content: { application/json: { schema: { $ref: '#/components/schemas/VolcengineError' } } } }
        '402': { description: Organization balance and credit are exhausted, content: { application/json: { schema: { $ref: '#/components/schemas/VolcengineError' } } } }
        '403': { description: API Key spending limit reached or organization disabled, content: { application/json: { schema: { $ref: '#/components/schemas/VolcengineError' } } } }
        '404': { description: 'Task is unknown, deleted, not native, or not owned by the current API key', content: { application/json: { schema: { $ref: '#/components/schemas/VolcengineError' } } } }
        '500': { description: Task update failed, content: { application/json: { schema: { $ref: '#/components/schemas/VolcengineError' } } } }

  # --- Account ----------------------------------------------------
  /v1/account/balance:
    get:
      operationId: getAccountBalance
      tags: [Account]
      summary: Get account balance
      description: Returns the balance and optional credit settings for the organization that owns the API key.
      responses:
        '200':
          description: Organization balance
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AccountBalance'
        '401':
          description: The API key did not resolve an organization
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TaskCostError'
        '402':
          description: API key spending limit exceeded
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TaskCostError'
        '403':
          description: API key scope does not authorize this endpoint
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/APIKeyScopeError'
        '404':
          description: Organization not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TaskCostError'

  /v1/account/history:
    get:
      operationId: listAccountHistory
      tags: [Account]
      summary: List execution history
      description: Lists recent execution records for the API key's organization, newest first. Input and output snapshots are included only when stored JSON can be decoded. Error messages are sanitized before they are returned.
      parameters:
        - { name: model, in: query, schema: { type: string }, description: Exact logical model name. }
        - { name: api_key_id, in: query, schema: { type: string }, description: Exact API Key resource ID within the organization. }
        - { name: type, in: query, schema: { type: string }, description: 'Execution category. Supported filters are multimodal (image, video, and audio), llm, and api; any other value applies no type filter.' }
        - { name: status, in: query, schema: { type: string, enum: [completed, failure, in_progress] }, description: Status group. failure includes failed and timeout; in_progress includes pending and running. }
        - { name: error_type, in: query, schema: { type: string, maxLength: 50 }, description: Exact error type. Use unknown for records without an error type. }
        - { name: range, in: query, schema: { type: string, default: 7d }, description: 'Rolling created-at range when an explicit analytics window is not used. Supported values are 1h, 24h, 7d, and 30d; every other value falls back to 7d.' }
        - { name: page, in: query, schema: { type: integer, minimum: 1, default: 1 } }
        - { name: page_size, in: query, schema: { type: integer, minimum: 1, maximum: 100, default: 20 } }
        - { name: request_id, in: query, schema: { type: string, maxLength: 64 }, description: "Exact organization-scoped execution ID lookup. When set, other filters are ignored and the response is forced to page 1 with page_size 1." }
        - { name: time_basis, in: query, schema: { type: string, enum: [created_at, settled_at], default: created_at }, description: Timestamp basis for an explicit start_at/end_at window. }
        - { name: billing_scope, in: query, schema: { type: string, enum: [any, positive, token], default: any }, description: Settlement filter. positive and token are valid only with time_basis=settled_at. }
        - { name: start_at, in: query, schema: { type: string, format: date-time }, description: Inclusive RFC 3339 window start. Must be paired with end_at; the window cannot exceed 32 days. }
        - { name: end_at, in: query, schema: { type: string, format: date-time }, description: Exclusive RFC 3339 window end. Must be paired with start_at. }
        - { name: latency_sample, in: query, schema: { type: string, enum: [positive, terminal_positive] }, description: "Restrict latency samples to positive values, optionally only for terminal executions." }
        - { name: min_latency_ms, in: query, schema: { type: integer, minimum: 0 }, description: Minimum recorded latency in milliseconds. }
        - { name: timezone, in: query, schema: { type: string, maxLength: 64 }, description: 'Compatibility parameter. Without request_id, a valid IANA time-zone name is required when supplied, but raw response timestamps remain unchanged. Exact request_id mode ignores it.' }
      responses:
        '200':
          description: Paginated execution history
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AccountHistoryPage'
        '400':
          description: Invalid filter or analytics window
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TaskCostError'
        '401':
          description: The API key did not resolve an organization
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TaskCostError'
        '402':
          description: API key spending limit exceeded
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TaskCostError'
        '403':
          description: API key scope does not authorize this endpoint
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/APIKeyScopeError'
        '500':
          description: History lookup failed
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TaskCostError'

  # --- Tasks ------------------------------------------------------
  /v1/tasks/{task_id}/cost:
    get:
      operationId: getTaskCost
      tags: [Tasks]
      summary: Get task cost and usage
      description: |
        Returns the settlement status, final user cost, estimated cost, and token usage for a recent task.
        Public API keys can retrieve only tasks originally created by that same API key, even when another key belongs to the same organization.
        This lookup remains available after the key reaches its spending limit, but expired, revoked, or scoped keys are still rejected.
        When `settled` is false, `cost` is not final and must not be used for billing or reconciliation.
        Use `estimated_cost` only as a display estimate until final settlement completes.
        Older task execution records may be archived after the retention window and then return 404.
        Use billing records or billing exports for long-term reconciliation.
      parameters:
        - name: task_id
          in: path
          required: true
          description: Opaque task ID returned by a billable operation, including an x-task-id response header when present.
          schema:
            type: string
      responses:
        '200':
          description: Task cost and usage
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TaskCostResponse'
        '401':
          description: Missing or invalid API key
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TaskCostError'
        '403':
          description: API key scope does not authorize this endpoint
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/APIKeyScopeError'
        '404':
          description: Task does not exist or is not accessible with the current API key
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TaskCostError'

  # --- Unified model and API execution ---------------------------
  /v1/run:
    post:
      operationId: runCapability
      tags: [Models]
      summary: Run a model or API capability
      description: |
        Executes the selected catalog capability. Capability-specific fields must match the live schema returned by
        the model detail endpoint or Store. Asynchronous capabilities can be polled with GET /v1/run/{id}.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/RunRequest'
      responses:
        '200':
          description: Capability completed synchronously, or a streaming response started
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RunResponse'
            text/event-stream:
              schema:
                type: string
                description: Server-Sent Events when stream is true or mode is stream and the selected model supports streaming.
        '202':
          description: Capability accepted for asynchronous execution
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RunResponse'
        '400':
          description: Invalid JSON, unsupported execution mode, invalid capability input, or invalid webhook URL
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TaskCostError'
        '401':
          description: Missing or invalid API key
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TaskCostError'
        '402':
          description: Insufficient balance or spending limit reached
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TaskCostError'
        '403':
          description: The current API key cannot use the selected capability
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TaskCostError'
        '404':
          description: Model or capability not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TaskCostError'
        '500':
          description: Internal execution error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TaskCostError'
        '502':
          description: Upstream provider returned an invalid response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TaskCostError'
        '503':
          description: No provider is currently available
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TaskCostError'

  /v1/run/{id}:
    get:
      operationId: getRun
      tags: [Models]
      summary: Get an asynchronous run
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: string }
          description: Opaque ID returned by POST /v1/run. Do not assume a prefix or derive it independently.
      responses:
        '200':
          description: Current or terminal run state
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RunResponse'
        '404':
          description: Run not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TaskCostError'
        '401':
          description: Missing or invalid API key
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TaskCostError'
        '402':
          description: API key spending limit exceeded
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TaskCostError'
        '403':
          description: API key scope does not authorize this endpoint
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/APIKeyScopeError'

  /v1/api/{vendor}/{upstream_path}:
    parameters:
      - name: vendor
        in: path
        required: true
        schema: { type: string, minLength: 1 }
        description: First segment of the enabled catalog capability name.
      - name: upstream_path
        in: path
        required: true
        schema: { type: string, minLength: 1 }
        description: Remaining capability-name path. It may contain multiple literal slash-separated segments; do not percent-encode those slashes.
    get:
      operationId: getAPICapability
      tags: [Models]
      summary: Invoke a catalog API capability with GET
      description: Resolves the enabled catalog model named vendor/upstream_path and executes it through the unified pipeline. The request body is empty; capability-specific query parameters remain on the incoming request and are available only when supported by that capability.
      responses:
        '200': { description: Capability-specific synchronous response, content: { application/json: { schema: { $ref: '#/components/schemas/APIPassthroughResponse' } } } }
        '202': { description: Capability accepted for asynchronous execution, content: { application/json: { schema: { $ref: '#/components/schemas/APIPassthroughResponse' } } } }
        '400': { description: Capability input is invalid, content: { application/json: { schema: { $ref: '#/components/schemas/APIPassthroughError' } } } }
        '401': { description: Missing or invalid API key, content: { application/json: { schema: { $ref: '#/components/schemas/TaskCostError' } } } }
        '402': { description: Insufficient balance or spending limit reached, content: { application/json: { schema: { $ref: '#/components/schemas/TaskCostError' } } } }
        '403': { description: API key or capability access is forbidden, content: { application/json: { schema: { $ref: '#/components/schemas/APIPassthroughError' } } } }
        '404': { description: No enabled catalog capability matches the vendor and path, content: { application/json: { schema: { $ref: '#/components/schemas/APIPassthroughError' } } } }
        '500': { description: Internal execution error, content: { application/json: { schema: { $ref: '#/components/schemas/APIPassthroughError' } } } }
        '502': { description: Upstream provider returned an invalid response, content: { application/json: { schema: { $ref: '#/components/schemas/APIPassthroughError' } } } }
        '503': { description: No provider is currently available, content: { application/json: { schema: { $ref: '#/components/schemas/APIPassthroughError' } } } }
    post:
      operationId: postAPICapability
      tags: [Models]
      summary: Invoke a catalog API capability with POST
      description: Resolves the enabled catalog model named vendor/upstream_path and executes a JSON object through the unified pipeline. When model is omitted, the URL-derived capability name is injected. A supplied model field is preserved and remains authoritative.
      requestBody:
        required: false
        content:
          application/json:
            schema:
              type: object
              additionalProperties: true
      responses:
        '200': { description: Capability-specific synchronous response, content: { application/json: { schema: { $ref: '#/components/schemas/APIPassthroughResponse' } } } }
        '202': { description: Capability accepted for asynchronous execution, content: { application/json: { schema: { $ref: '#/components/schemas/APIPassthroughResponse' } } } }
        '400': { description: Body is not a JSON object or capability input is invalid, content: { application/json: { schema: { $ref: '#/components/schemas/APIPassthroughError' } } } }
        '401': { description: Missing or invalid API key, content: { application/json: { schema: { $ref: '#/components/schemas/TaskCostError' } } } }
        '402': { description: Insufficient balance or spending limit reached, content: { application/json: { schema: { $ref: '#/components/schemas/TaskCostError' } } } }
        '403': { description: API key or capability access is forbidden, content: { application/json: { schema: { $ref: '#/components/schemas/APIPassthroughError' } } } }
        '404': { description: No enabled catalog capability matches the vendor and path, content: { application/json: { schema: { $ref: '#/components/schemas/APIPassthroughError' } } } }
        '500': { description: Internal execution error, content: { application/json: { schema: { $ref: '#/components/schemas/APIPassthroughError' } } } }
        '502': { description: Upstream provider returned an invalid response, content: { application/json: { schema: { $ref: '#/components/schemas/APIPassthroughError' } } } }
        '503': { description: No provider is currently available, content: { application/json: { schema: { $ref: '#/components/schemas/APIPassthroughError' } } } }

  /v1/upload:
    post:
      operationId: uploadMedia
      tags: [Models]
      summary: Upload media for model input
      description: |
        Uploads one image, audio, or video file and returns its stored URL. The server detects the actual MIME type
        from the file bytes and filename; the optional type form field is only a hint and does not override detection.
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              $ref: '#/components/schemas/UploadMediaRequest'
      responses:
        '200':
          description: Media uploaded
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UploadMediaResponse'
        '400':
          description: Missing file, invalid multipart data, unsupported MIME type, or file too large
          content:
            application/json:
              schema: { $ref: '#/components/schemas/TaskCostError' }
        '401':
          description: Missing or invalid API key
          content:
            application/json:
              schema: { $ref: '#/components/schemas/TaskCostError' }
        '402':
          description: API key spending limit exceeded
          content:
            application/json:
              schema: { $ref: '#/components/schemas/TaskCostError' }
        '403':
          description: API key scope does not authorize this endpoint
          content:
            application/json:
              schema: { $ref: '#/components/schemas/APIKeyScopeError' }
        '500':
          description: File could not be read or stored
          content:
            application/json:
              schema: { $ref: '#/components/schemas/TaskCostError' }
        '503':
          description: File storage is not configured
          content:
            application/json:
              schema: { $ref: '#/components/schemas/TaskCostError' }

  # --- Assets -----------------------------------------------------
  /v1/assets:
    post:
      operationId: createAsset
      tags: [Models]
      summary: Register a media asset
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateAssetRequest'
      responses:
        '200':
          description: Asset registered
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CreateAssetResponse'
        '400':
          description: Invalid JSON, missing URL, or unsupported asset type
          content:
            application/json:
              schema: { $ref: '#/components/schemas/TaskCostError' }
        '401':
          description: Missing or invalid API key
          content:
            application/json:
              schema: { $ref: '#/components/schemas/TaskCostError' }
        '402':
          description: API key spending limit exceeded
          content:
            application/json:
              schema: { $ref: '#/components/schemas/TaskCostError' }
        '403':
          description: API key scope does not authorize this endpoint
          content:
            application/json:
              schema: { $ref: '#/components/schemas/APIKeyScopeError' }
        '500':
          description: Asset provider credentials are unavailable or the asset record could not be saved
          content:
            application/json:
              schema: { $ref: '#/components/schemas/TaskCostError' }
        '502':
          description: Upstream asset registration failed
          content:
            application/json:
              schema: { $ref: '#/components/schemas/TaskCostError' }

  /v1/assets/{id}:
    get:
      operationId: getAsset
      tags: [Models]
      summary: Get asset metadata and a signed download URL
      parameters:
        - name: id
          in: path
          required: true
          description: External Asset ID without the asset:// prefix.
          schema: { type: string }
      responses:
        '200':
          description: Asset metadata
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/GetAssetResponse'
        '401':
          description: Missing or invalid API key
          content:
            application/json:
              schema: { $ref: '#/components/schemas/TaskCostError' }
        '402':
          description: API key spending limit exceeded
          content:
            application/json:
              schema: { $ref: '#/components/schemas/TaskCostError' }
        '403':
          description: API key scope does not authorize this endpoint
          content:
            application/json:
              schema: { $ref: '#/components/schemas/APIKeyScopeError' }
        '404':
          description: Asset not found
          content:
            application/json:
              schema: { $ref: '#/components/schemas/TaskCostError' }
        '500':
          description: Internal asset lookup error or provider credentials are unavailable
          content:
            application/json:
              schema: { $ref: '#/components/schemas/TaskCostError' }
        '502':
          description: Upstream asset query failed
          content:
            application/json:
              schema: { $ref: '#/components/schemas/TaskCostError' }

  # --- Services (Endpoint resources) -----------------------------
  /v1/endpoints:
    post:
      operationId: createEndpoint
      tags: [Services]
      summary: Create a Service
      description: |
        Creates a Service from either a declarative runtime definition or an existing Agent.
        The two creation modes are mutually exclusive. Declarative YAML uses the same schema as JSON.
        The Agent runtime binding must use an enabled provider for the organization.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              oneOf:
                - $ref: '#/components/schemas/CreateDeclarativeEndpointRequest'
                - $ref: '#/components/schemas/CreateAdvancedEndpointRequest'
          application/yaml:
            schema:
              $ref: '#/components/schemas/CreateDeclarativeEndpointRequest'
          application/x-yaml:
            schema:
              $ref: '#/components/schemas/CreateDeclarativeEndpointRequest'
      responses:
        '201':
          description: Service created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Endpoint'
        '400':
          description: Invalid definition or creation mode
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/APIError'
        '401':
          description: Missing or invalid API key
          content:
            application/json:
              schema: { $ref: '#/components/schemas/TaskCostError' }
        '402': { description: API key spending limit exceeded, content: { application/json: { schema: { $ref: '#/components/schemas/TaskCostError' } } } }
        '403': { description: API key scope does not authorize this endpoint, content: { application/json: { schema: { $ref: '#/components/schemas/APIKeyScopeError' } } } }
        '404':
          description: Referenced Skill or Agent version was not found
          content:
            application/json:
              schema: { $ref: '#/components/schemas/APIError' }
        '409':
          description: Referenced Agent is archived
          content:
            application/json:
              schema: { $ref: '#/components/schemas/APIError' }
        '415':
          description: Content-Type is not application/json, application/yaml, or application/x-yaml
          content:
            application/json:
              schema: { $ref: '#/components/schemas/APIError' }
        '422':
          description: Runtime or Skill is incompatible
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/APIError'
        '500':
          description: Service creation or declarative compilation failed
          content:
            application/json:
              schema: { $ref: '#/components/schemas/APIError' }
    get:
      operationId: listEndpoints
      tags: [Services]
      summary: List Services
      parameters:
        - name: q
          in: query
          schema: { type: string }
          description: Search by ID, name, slug, runtime, or Agent ID. `search` is accepted as a compatibility alias.
        - name: limit
          in: query
          schema: { type: integer, minimum: 1, maximum: 100, default: 20 }
        - name: page
          in: query
          schema: { type: string }
          description: Opaque cursor from a previous response's next_page field.
      responses:
        '200':
          description: Service list
          content:
            application/json:
              schema:
                type: object
                required: [data]
                properties:
                  data:
                    type: array
                    items:
                      $ref: '#/components/schemas/Endpoint'
                  next_page:
                    type: string
                    description: Opaque cursor included only when another page is available.
        '400':
          description: Invalid page cursor
          content:
            application/json:
              schema: { $ref: '#/components/schemas/APIError' }
        '401':
          description: Missing or invalid API key
          content:
            application/json:
              schema: { $ref: '#/components/schemas/TaskCostError' }
        '402': { description: API key spending limit exceeded, content: { application/json: { schema: { $ref: '#/components/schemas/TaskCostError' } } } }
        '403': { description: API key scope does not authorize this endpoint, content: { application/json: { schema: { $ref: '#/components/schemas/APIKeyScopeError' } } } }
        '500':
          description: Service list could not be loaded
          content:
            application/json:
              schema: { $ref: '#/components/schemas/APIError' }

  /v1/endpoints/{id}:
    get:
      operationId: getEndpoint
      tags: [Services]
      summary: Get a Service
      parameters:
        - $ref: '#/components/parameters/EndpointId'
      responses:
        '200':
          description: Service details
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Endpoint'
        '404':
          description: Service not found
          content:
            application/json:
              schema: { $ref: '#/components/schemas/APIError' }
        '401':
          description: Missing or invalid API key
          content:
            application/json:
              schema: { $ref: '#/components/schemas/TaskCostError' }
        '402': { description: API key spending limit exceeded, content: { application/json: { schema: { $ref: '#/components/schemas/TaskCostError' } } } }
        '403': { description: API key scope does not authorize this endpoint, content: { application/json: { schema: { $ref: '#/components/schemas/APIKeyScopeError' } } } }
    patch:
      operationId: updateEndpoint
      tags: [Services]
      summary: Update a Service
      description: Declarative definition fields are immutable in this release; operational fields such as name, status, and protocols may be updated.
      parameters:
        - $ref: '#/components/parameters/EndpointId'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UpdateEndpointRequest'
      responses:
        '200':
          description: Service updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Endpoint'
        '409':
          description: Agent version conflict, archived Agent, or immutable declarative definition
          content:
            application/json:
              schema: { $ref: '#/components/schemas/APIError' }
        '400': { description: 'Invalid request, status, protocols, Agent binding, or required expected version', content: { application/json: { schema: { $ref: '#/components/schemas/APIError' } } } }
        '401': { description: Missing or invalid API key, content: { application/json: { schema: { $ref: '#/components/schemas/TaskCostError' } } } }
        '402': { description: API key spending limit exceeded, content: { application/json: { schema: { $ref: '#/components/schemas/TaskCostError' } } } }
        '403': { description: API key scope does not authorize this endpoint, content: { application/json: { schema: { $ref: '#/components/schemas/APIKeyScopeError' } } } }
        '404': { description: 'Service or Agent version not found', content: { application/json: { schema: { $ref: '#/components/schemas/APIError' } } } }
        '422': { description: Runtime provider is unsupported, content: { application/json: { schema: { $ref: '#/components/schemas/APIError' } } } }
        '500': { description: Service update failed, content: { application/json: { schema: { $ref: '#/components/schemas/APIError' } } } }
    post:
      operationId: updateEndpointCompatibility
      tags: [Services]
      summary: Update a Service (compatibility alias)
      description: Compatibility alias for PATCH. New integrations should use PATCH.
      parameters:
        - $ref: '#/components/parameters/EndpointId'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UpdateEndpointRequest'
      responses:
        '200':
          description: Service updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Endpoint'
        '409':
          description: Agent version conflict, archived Agent, or immutable declarative definition
          content:
            application/json:
              schema: { $ref: '#/components/schemas/APIError' }
        '400': { description: 'Invalid request, status, protocols, Agent binding, or required expected version', content: { application/json: { schema: { $ref: '#/components/schemas/APIError' } } } }
        '401': { description: Missing or invalid API key, content: { application/json: { schema: { $ref: '#/components/schemas/TaskCostError' } } } }
        '402': { description: API key spending limit exceeded, content: { application/json: { schema: { $ref: '#/components/schemas/TaskCostError' } } } }
        '403': { description: API key scope does not authorize this endpoint, content: { application/json: { schema: { $ref: '#/components/schemas/APIKeyScopeError' } } } }
        '404': { description: 'Service or Agent version not found', content: { application/json: { schema: { $ref: '#/components/schemas/APIError' } } } }
        '422': { description: Runtime provider is unsupported, content: { application/json: { schema: { $ref: '#/components/schemas/APIError' } } } }
        '500': { description: Service update failed, content: { application/json: { schema: { $ref: '#/components/schemas/APIError' } } } }
    delete:
      operationId: deleteEndpoint
      tags: [Services]
      summary: Delete a Service
      parameters:
        - $ref: '#/components/parameters/EndpointId'
      responses:
        '200':
          description: Service deleted
          content:
            application/json:
              schema:
                type: object
                required: [id, deleted]
                properties:
                  id: { type: string }
                  deleted: { type: boolean, const: true }
        '404': { description: Service not found, content: { application/json: { schema: { $ref: '#/components/schemas/APIError' } } } }
        '401': { description: Missing or invalid API key, content: { application/json: { schema: { $ref: '#/components/schemas/TaskCostError' } } } }
        '402': { description: API key spending limit exceeded, content: { application/json: { schema: { $ref: '#/components/schemas/TaskCostError' } } } }
        '403': { description: API key scope does not authorize this endpoint, content: { application/json: { schema: { $ref: '#/components/schemas/APIKeyScopeError' } } } }
        '500': { description: Service deletion failed, content: { application/json: { schema: { $ref: '#/components/schemas/APIError' } } } }

  /v1/endpoints/{id}/run:
    post:
      operationId: runEndpoint
      tags: [Services]
      summary: Invoke a Service over REST
      description: |
        Creates or continues a persistent Session and sends one standard event. Omit `session_id` to create a
        Session fixed to the Service's Agent version and runtime binding. Provide `session_id` to
        continue that Session. Returns the persistent Session ID and accepted events; no public execution Run or
        internal Runtime Session identity is exposed. When both input and content are supplied, content takes precedence.
      parameters:
        - $ref: '#/components/parameters/EndpointId'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/EndpointInvokeRequest'
      responses:
        '202':
          description: Session event accepted
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EndpointInvokeAccepted'
        '400':
          description: Invalid JSON or missing usable input/content
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/APIError'
        '401':
          description: Missing or invalid API key
          content:
            application/json:
              schema: { $ref: '#/components/schemas/TaskCostError' }
        '402': { description: API key spending limit exceeded, content: { application/json: { schema: { $ref: '#/components/schemas/TaskCostError' } } } }
        '403':
          description: Service is disabled, REST is not enabled, or the API key scope does not authorize this endpoint
          content:
            application/json:
              schema:
                oneOf:
                  - $ref: '#/components/schemas/APIError'
                  - $ref: '#/components/schemas/APIKeyScopeError'
        '404':
          description: Service or supplied Session not found
          content:
            application/json:
              schema: { $ref: '#/components/schemas/APIError' }
        '409':
          description: Session is busy, archived, terminal, or bound to another Service, Agent, or version
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/APIError'
        '500':
          description: Session creation or turn acceptance failed
          content:
            application/json:
              schema: { $ref: '#/components/schemas/APIError' }
        '503':
          description: Stateful persistence or recovery is unavailable
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/APIError'

  /v1/endpoints/{id}/acp:
    post:
      operationId: callEndpointAcp
      tags: [Services]
      summary: Invoke a Service over experimental ACP
      description: |
        Experimental ACP-over-HTTP JSON-RPC transport supporting initialize, session/new, session/prompt, and
        session/cancel. Prompt output is newline-delimited JSON. Runtime failures use redacted public error messages;
        the remote transport may change while ACP remains experimental.
      parameters:
        - $ref: '#/components/parameters/EndpointId'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ACPRequest'
      responses:
        '200':
          description: ACP JSON-RPC response or newline-delimited prompt stream
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ACPResponse'
            application/x-ndjson:
              schema:
                type: string
                description: ACP session/update notifications followed by the final JSON-RPC result.
        '400':
          description: Invalid JSON-RPC request
          content:
            application/json:
              schema: { $ref: '#/components/schemas/ACPResponse' }
        '401':
          description: Missing or invalid API key
          content:
            application/json:
              schema: { $ref: '#/components/schemas/TaskCostError' }
        '402': { description: API key spending limit exceeded, content: { application/json: { schema: { $ref: '#/components/schemas/TaskCostError' } } } }
        '403': { description: API key scope does not authorize this endpoint, content: { application/json: { schema: { $ref: '#/components/schemas/APIKeyScopeError' } } } }
        '404':
          description: Service or ACP session not found
          content:
            application/json:
              schema: { $ref: '#/components/schemas/ACPResponse' }
        '500':
          description: ACP Session could not be created
          content:
            application/json:
              schema: { $ref: '#/components/schemas/ACPResponse' }

  /v1/endpoints/{id}/mcp:
    post:
      operationId: callEndpointMcp
      tags: [Services]
      summary: Invoke a Service over MCP
      description: Handle MCP JSON-RPC for a Service whose protocols include mcp. Responses may be newline-delimited when the transport emits a stream.
      parameters:
        - $ref: '#/components/parameters/EndpointId'
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/MCPRequest' }
      responses:
        '200':
          description: MCP JSON-RPC response or newline-delimited stream
          content:
            application/json:
              schema: { $ref: '#/components/schemas/MCPResponse' }
            application/x-ndjson:
              schema: { type: string, description: Newline-delimited MCP JSON-RPC messages. }
        '400': { description: Invalid MCP JSON-RPC request, content: { application/json: { schema: { $ref: '#/components/schemas/MCPResponse' } } } }
        '401': { description: Missing or invalid API key, content: { application/json: { schema: { $ref: '#/components/schemas/TaskCostError' } } } }
        '402': { description: API key spending limit exceeded, content: { application/json: { schema: { $ref: '#/components/schemas/TaskCostError' } } } }
        '403': { description: 'Service is disabled, MCP is not enabled, or the API key scope does not authorize this endpoint', content: { application/json: { schema: { $ref: '#/components/schemas/APIKeyScopeError' } } } }
        '404': { description: Service not found, content: { application/json: { schema: { $ref: '#/components/schemas/APIError' } } } }
        '500': { description: MCP transport failed, content: { application/json: { schema: { $ref: '#/components/schemas/APIError' } } } }
    delete:
      operationId: closeEndpointMcp
      tags: [Services]
      summary: Close a Service MCP transport
      description: Close and clean up MCP transport state without deleting or disabling the Service.
      parameters:
        - $ref: '#/components/parameters/EndpointId'
      responses:
        '204': { description: MCP transport state closed }
        '401': { description: Missing or invalid API key, content: { application/json: { schema: { $ref: '#/components/schemas/TaskCostError' } } } }
        '402': { description: API key spending limit exceeded, content: { application/json: { schema: { $ref: '#/components/schemas/TaskCostError' } } } }
        '403': { description: API key scope does not authorize this endpoint, content: { application/json: { schema: { $ref: '#/components/schemas/APIKeyScopeError' } } } }
        '404': { description: Service not found, content: { application/json: { schema: { $ref: '#/components/schemas/APIError' } } } }
        '500': { description: MCP transport cleanup failed, content: { application/json: { schema: { $ref: '#/components/schemas/APIError' } } } }

  # --- Agents -----------------------------------------------------
  /v1/agents:
    post:
      operationId: createAgent
      tags: [Agents]
      summary: Create agent
      description: Create a reusable, versioned Agent configuration. Model IDs must use the public vendor/model identity. Unknown request fields are accepted and ignored. Omitted tools default to the built-in Agent toolset; an explicit null leaves tools null.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateAgentRequest'
      responses:
        '200':
          description: Agent created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Agent'
        '400': { description: 'Missing or invalid name, model, visibility, multiagent configuration, or MCP headers', content: { application/json: { schema: { $ref: '#/components/schemas/SessionAPIError' } } } }
        '401': { description: Missing or invalid API key, content: { application/json: { schema: { $ref: '#/components/schemas/TaskCostError' } } } }
        '402': { description: API key spending limit exceeded, content: { application/json: { schema: { $ref: '#/components/schemas/TaskCostError' } } } }
        '403': { description: API key scope does not authorize this endpoint, content: { application/json: { schema: { $ref: '#/components/schemas/APIKeyScopeError' } } } }
        '422': { description: Model identity is invalid or unavailable, content: { application/json: { schema: { $ref: '#/components/schemas/SessionAPIError' } } } }
        '500': { description: Agent could not be created or validated, content: { application/json: { schema: { $ref: '#/components/schemas/SessionAPIError' } } } }
    get:
      operationId: listAgents
      tags: [Agents]
      summary: List agents
      parameters:
        - { name: 'created_at[gte]', in: query, schema: { type: string, format: date-time } }
        - { name: 'created_at[lte]', in: query, schema: { type: string, format: date-time } }
        - { name: include_archived, in: query, schema: { type: boolean, default: false } }
        - { name: limit, in: query, schema: { type: integer, minimum: 1, maximum: 100, default: 20 } }
        - { name: page, in: query, schema: { type: string } }
      responses:
        '200':
          description: Agent list
          content:
            application/json:
              schema: { $ref: '#/components/schemas/AgentListPage' }
        '400': { description: Invalid page cursor, content: { application/json: { schema: { $ref: '#/components/schemas/SessionAPIError' } } } }
        '401': { description: Missing or invalid API key, content: { application/json: { schema: { $ref: '#/components/schemas/TaskCostError' } } } }
        '402': { description: API key spending limit exceeded, content: { application/json: { schema: { $ref: '#/components/schemas/TaskCostError' } } } }
        '403': { description: API key scope does not authorize this endpoint, content: { application/json: { schema: { $ref: '#/components/schemas/APIKeyScopeError' } } } }
        '500': { description: Agents could not be listed, content: { application/json: { schema: { $ref: '#/components/schemas/SessionAPIError' } } } }

  /v1/agents/{id}:
    get:
      operationId: getAgent
      tags: [Agents]
      summary: Get agent
      parameters:
        - $ref: '#/components/parameters/AgentId'
        - name: version
          in: query
          schema: { type: integer, minimum: 1 }
          description: Return the immutable Agent snapshot for this numeric version. Omit for the current version.
      responses:
        '200':
          description: Agent details
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Agent'
        '400': { description: Invalid numeric version selector, content: { application/json: { schema: { $ref: '#/components/schemas/SessionAPIError' } } } }
        '401': { description: Missing or invalid API key, content: { application/json: { schema: { $ref: '#/components/schemas/TaskCostError' } } } }
        '402': { description: API key spending limit exceeded, content: { application/json: { schema: { $ref: '#/components/schemas/TaskCostError' } } } }
        '403': { description: API key scope does not authorize this endpoint, content: { application/json: { schema: { $ref: '#/components/schemas/APIKeyScopeError' } } } }
        '404': { description: Agent or requested version not found, content: { application/json: { schema: { $ref: '#/components/schemas/SessionAPIError' } } } }
        '500': { description: Agent or snapshot could not be loaded, content: { application/json: { schema: { $ref: '#/components/schemas/SessionAPIError' } } } }
    post:
      operationId: updateAgent
      tags: [Agents]
      summary: Update agent
      description: Update Agent configuration. Effective changes create a new version; a no-op returns the current Agent without incrementing it. Omitted fields and null scalar fields preserve their current values. Array null values clear those arrays. Unknown fields are accepted and ignored.
      parameters:
        - $ref: '#/components/parameters/AgentId'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UpdateAgentRequest'
      responses:
        '200':
          description: Agent updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Agent'
        '400': { description: 'Invalid request, model, multiagent configuration, or MCP headers', content: { application/json: { schema: { $ref: '#/components/schemas/SessionAPIError' } } } }
        '401': { description: Missing or invalid API key, content: { application/json: { schema: { $ref: '#/components/schemas/TaskCostError' } } } }
        '402': { description: API key spending limit exceeded, content: { application/json: { schema: { $ref: '#/components/schemas/TaskCostError' } } } }
        '403':
          description: Agent is archived or the API key scope does not authorize this endpoint
          content:
            application/json:
              schema:
                oneOf:
                  - $ref: '#/components/schemas/SessionAPIError'
                  - $ref: '#/components/schemas/APIKeyScopeError'
        '404': { description: Agent not found, content: { application/json: { schema: { $ref: '#/components/schemas/SessionAPIError' } } } }
        '409': { description: Supplied version does not match the current Agent version, content: { application/json: { schema: { $ref: '#/components/schemas/SessionAPIError' } } } }
        '422': { description: Model identity is invalid or unavailable, content: { application/json: { schema: { $ref: '#/components/schemas/SessionAPIError' } } } }
        '500': { description: Agent could not be updated or validated, content: { application/json: { schema: { $ref: '#/components/schemas/SessionAPIError' } } } }

  /v1/agents/{id}/archive:
    post:
      operationId: archiveAgent
      tags: [Agents]
      summary: Archive agent
      description: Archive an agent, making it read-only.
      parameters:
        - $ref: '#/components/parameters/AgentId'
      responses:
        '200':
          description: Agent archived
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Agent'
        '401': { description: Missing or invalid API key, content: { application/json: { schema: { $ref: '#/components/schemas/TaskCostError' } } } }
        '402': { description: API key spending limit exceeded, content: { application/json: { schema: { $ref: '#/components/schemas/TaskCostError' } } } }
        '403': { description: API key scope does not authorize this endpoint, content: { application/json: { schema: { $ref: '#/components/schemas/APIKeyScopeError' } } } }
        '404': { description: Agent not found, content: { application/json: { schema: { $ref: '#/components/schemas/SessionAPIError' } } } }
        '409': { description: Agent is already archived, content: { application/json: { schema: { $ref: '#/components/schemas/SessionAPIError' } } } }
        '500': { description: Agent could not be archived, content: { application/json: { schema: { $ref: '#/components/schemas/SessionAPIError' } } } }

  /v1/agents/{id}/versions:
    get:
      operationId: listAgentVersions
      tags: [Agents]
      summary: List agent versions
      parameters:
        - $ref: '#/components/parameters/AgentId'
        - { name: limit, in: query, schema: { type: integer, minimum: 1, maximum: 100, default: 20 } }
        - { name: page, in: query, schema: { type: string } }
      responses:
        '200':
          description: Version history
          content:
            application/json:
              schema:
                type: object
                required: [data]
                properties:
                  data:
                    type: array
                    items:
                      $ref: '#/components/schemas/Agent'
                  next_page: { type: string, description: Opaque cursor included only when another page is available. }
        '400': { description: Invalid page cursor, content: { application/json: { schema: { $ref: '#/components/schemas/SessionAPIError' } } } }
        '401': { description: Missing or invalid API key, content: { application/json: { schema: { $ref: '#/components/schemas/TaskCostError' } } } }
        '402': { description: API key spending limit exceeded, content: { application/json: { schema: { $ref: '#/components/schemas/TaskCostError' } } } }
        '403': { description: API key scope does not authorize this endpoint, content: { application/json: { schema: { $ref: '#/components/schemas/APIKeyScopeError' } } } }
        '404': { description: Agent not found, content: { application/json: { schema: { $ref: '#/components/schemas/SessionAPIError' } } } }
        '500': { description: Agent versions or a stored snapshot could not be loaded, content: { application/json: { schema: { $ref: '#/components/schemas/SessionAPIError' } } } }

  /v1/agents/{id}/versions/{version}:
    get:
      operationId: getAgentVersion
      tags: [Agents]
      summary: Get agent version
      description: Return one immutable Agent snapshot by version resource ID or positive numeric version.
      parameters:
        - $ref: '#/components/parameters/AgentId'
        - name: version
          in: path
          required: true
          schema:
            oneOf:
              - type: string
              - type: integer
                minimum: 1
      responses:
        '200':
          description: Agent version
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Agent'
        '401': { description: Missing or invalid API key, content: { application/json: { schema: { $ref: '#/components/schemas/TaskCostError' } } } }
        '402': { description: API key spending limit exceeded, content: { application/json: { schema: { $ref: '#/components/schemas/TaskCostError' } } } }
        '403': { description: API key scope does not authorize this endpoint, content: { application/json: { schema: { $ref: '#/components/schemas/APIKeyScopeError' } } } }
        '404': { description: Agent or version not found, content: { application/json: { schema: { $ref: '#/components/schemas/SessionAPIError' } } } }
        '500': { description: Agent version or stored snapshot could not be loaded, content: { application/json: { schema: { $ref: '#/components/schemas/SessionAPIError' } } } }

  # --- Sessions ---------------------------------------------------
  /v1/sessions:
    post:
      operationId: createSession
      tags: [Sessions]
      summary: Create session
      description: Unknown top-level fields are rejected. resources and vault_ids are reserved and any non-null value is rejected.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateSessionRequest'
      responses:
        '200':
          description: Session created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Session'
        '400': { description: 'Invalid body, Agent reference, initial Event, or initial Event batch', content: { application/json: { schema: { $ref: '#/components/schemas/SessionAPIError' } } } }
        '401': { description: Missing or invalid API key, content: { application/json: { schema: { $ref: '#/components/schemas/TaskCostError' } } } }
        '402': { description: API key spending limit exceeded, content: { application/json: { schema: { $ref: '#/components/schemas/TaskCostError' } } } }
        '403':
          description: The Agent is archived or the API key scope does not authorize this endpoint
          content:
            application/json:
              schema:
                oneOf:
                  - $ref: '#/components/schemas/SessionAPIError'
                  - $ref: '#/components/schemas/APIKeyScopeError'
        '404': { description: 'Agent or Agent version not found', content: { application/json: { schema: { $ref: '#/components/schemas/SessionAPIError' } } } }
        '422':
          description: A reserved resources or vault_ids value was supplied, or initial_events used unsupported multi-Agent or runtime-resume behavior. When rejection occurs after Session creation, Events are not persisted and Session cleanup follows the documented best-effort compensation path.
          content:
            application/json:
              schema:
                type: object
                required: [error]
                properties:
                  error:
                    type: object
                    required: [type, message]
                    properties:
                      type:
                        type: string
                        enum: [unsupported_event_type, unsupported_multiagent, unsupported_feature, agent_runtime_environment_mismatch]
                      message: { type: string }
        '500': { description: Session could not be created, content: { application/json: { schema: { $ref: '#/components/schemas/SessionAPIError' } } } }
        '503': { description: Initial Event delivery is not configured, content: { application/json: { schema: { $ref: '#/components/schemas/SessionAPIError' } } } }
        '502':
          description: Native runtime delivery outcome is unknown. Recover the returned Session and inspect Events before retrying.
          content:
            application/json:
              schema:
                type: object
                required: [error, session_id]
                properties:
                  error:
                    type: object
                    required: [type, message]
                    properties:
                      type: { type: string, enum: [delivery_outcome_unknown] }
                      message: { type: string }
                  session_id:
                    type: string
                    pattern: '^sess_'
                  delivery_status:
                    type: string
                    enum: [unknown]
                  recovery:
                    type: object
                    required: [recoverable, retry_create, action]
                    properties:
                      recoverable: { type: boolean, enum: [true] }
                      retry_create: { type: boolean, enum: [false] }
                      action: { type: string, enum: [get_session_and_list_events] }
    get:
      operationId: listSessions
      tags: [Sessions]
      summary: List sessions
      parameters:
        - name: agent_id
          in: query
          schema:
            type: string
          description: Filter by agent ID
        - name: agent_version
          in: query
          schema: { type: integer, minimum: 1 }
        - name: statuses
          in: query
          style: form
          explode: true
          schema:
            type: array
            items: { type: string, enum: [idle, running, rescheduling, terminated] }
          description: Repeat the parameter to filter by one or more Session statuses.
        - name: include_archived
          in: query
          schema: { type: boolean, default: false }
        - name: 'created_at[gt]'
          in: query
          schema: { type: string, format: date-time }
        - name: 'created_at[gte]'
          in: query
          schema: { type: string, format: date-time }
        - name: 'created_at[lt]'
          in: query
          schema: { type: string, format: date-time }
        - name: 'created_at[lte]'
          in: query
          schema: { type: string, format: date-time }
        - { name: limit, in: query, schema: { type: integer, minimum: 1, maximum: 100, default: 20 } }
        - { name: page, in: query, schema: { type: string } }
      responses:
        '200':
          description: Session list
          content:
            application/json:
              schema:
                type: object
                required: [data]
                properties:
                  data:
                    type: array
                    items:
                      $ref: '#/components/schemas/Session'
                  next_page: { type: string, description: Opaque cursor included only when another page is available. }
        '400': { description: Invalid created-at filter or page cursor, content: { application/json: { schema: { $ref: '#/components/schemas/SessionAPIError' } } } }
        '401': { description: Missing or invalid API key, content: { application/json: { schema: { $ref: '#/components/schemas/TaskCostError' } } } }
        '402': { description: API key spending limit exceeded, content: { application/json: { schema: { $ref: '#/components/schemas/TaskCostError' } } } }
        '403': { description: API key scope does not authorize this endpoint, content: { application/json: { schema: { $ref: '#/components/schemas/APIKeyScopeError' } } } }
        '500': { description: Sessions could not be listed, content: { application/json: { schema: { $ref: '#/components/schemas/SessionAPIError' } } } }

  /v1/sessions/{id}:
    get:
      operationId: getSession
      tags: [Sessions]
      summary: Get session
      parameters:
        - $ref: '#/components/parameters/SessionId'
      responses:
        '200':
          description: Session details
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Session'
        '401': { description: Missing or invalid API key, content: { application/json: { schema: { $ref: '#/components/schemas/TaskCostError' } } } }
        '402': { description: API key spending limit exceeded, content: { application/json: { schema: { $ref: '#/components/schemas/TaskCostError' } } } }
        '403': { description: API key scope does not authorize this endpoint, content: { application/json: { schema: { $ref: '#/components/schemas/APIKeyScopeError' } } } }
        '404': { description: Session not found, content: { application/json: { schema: { $ref: '#/components/schemas/SessionAPIError' } } } }
        '500': { description: Session could not be loaded, content: { application/json: { schema: { $ref: '#/components/schemas/SessionAPIError' } } } }
    post:
      operationId: updateSession
      tags: [Sessions]
      summary: Update session metadata or Agent overrides
      description: Update title, shallow-merge metadata, or fully replace Session-local tool and MCP server overrides. In metadata, null removes a key. Omitted fields and a null title preserve the current value. The pinned Agent version remains immutable. Unknown fields are accepted and ignored.
      parameters:
        - $ref: '#/components/parameters/SessionId'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              additionalProperties: true
              properties:
                title: { type: [string, 'null'] }
                metadata:
                  type: object
                  additionalProperties: true
                  description: Shallow merge patch. Null property values delete existing keys; the metadata field itself cannot be null.
                agent:
                  $ref: '#/components/schemas/SessionAgentUpdate'
                vault_ids:
                  type: array
                  items: { type: string, pattern: '^vlt_' }
                  description: Reserved for Claude compatibility; rejected until vault attachment is supported.
      responses:
        '200':
          description: Session updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Session'
        '400': { description: 'Invalid JSON, metadata, Agent override, or reserved vault_ids', content: { application/json: { schema: { $ref: '#/components/schemas/SessionAPIError' } } } }
        '401': { description: Missing or invalid API key, content: { application/json: { schema: { $ref: '#/components/schemas/TaskCostError' } } } }
        '402': { description: API key spending limit exceeded, content: { application/json: { schema: { $ref: '#/components/schemas/TaskCostError' } } } }
        '403': { description: API key scope does not authorize this endpoint, content: { application/json: { schema: { $ref: '#/components/schemas/APIKeyScopeError' } } } }
        '404': { description: Session not found, content: { application/json: { schema: { $ref: '#/components/schemas/SessionAPIError' } } } }
        '500': { description: Session could not be updated, content: { application/json: { schema: { $ref: '#/components/schemas/SessionAPIError' } } } }
    delete:
      operationId: deleteSession
      tags: [Sessions]
      summary: Permanently delete a non-running session
      description: Allowed only when the Session is neither running nor rescheduling. Deletion is permanent; use archive to retain history.
      parameters:
        - $ref: '#/components/parameters/SessionId'
      responses:
        '200':
          description: Session deleted
          content:
            application/json:
              schema:
                type: object
                required: [id, type]
                properties:
                  id: { type: string, pattern: '^sess_' }
                  type: { type: string, const: session_deleted }
        '404': { description: Session not found, content: { application/json: { schema: { $ref: '#/components/schemas/SessionAPIError' } } } }
        '409': { description: Running or rescheduling Session cannot be deleted, content: { application/json: { schema: { $ref: '#/components/schemas/SessionAPIError' } } } }
        '401': { description: Missing or invalid API key, content: { application/json: { schema: { $ref: '#/components/schemas/TaskCostError' } } } }
        '402': { description: API key spending limit exceeded, content: { application/json: { schema: { $ref: '#/components/schemas/TaskCostError' } } } }
        '403': { description: API key scope does not authorize this endpoint, content: { application/json: { schema: { $ref: '#/components/schemas/APIKeyScopeError' } } } }
        '500': { description: Session could not be deleted, content: { application/json: { schema: { $ref: '#/components/schemas/SessionAPIError' } } } }
  /v1/sessions/{id}/archive:
    post:
      operationId: archiveSession
      tags: [Sessions]
      summary: Archive session
      parameters:
        - $ref: '#/components/parameters/SessionId'
      responses:
        '200':
          description: Session archived
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Session'
        '401': { description: Missing or invalid API key, content: { application/json: { schema: { $ref: '#/components/schemas/TaskCostError' } } } }
        '402': { description: API key spending limit exceeded, content: { application/json: { schema: { $ref: '#/components/schemas/TaskCostError' } } } }
        '403': { description: API key scope does not authorize this endpoint, content: { application/json: { schema: { $ref: '#/components/schemas/APIKeyScopeError' } } } }
        '404': { description: Session not found or already archived, content: { application/json: { schema: { $ref: '#/components/schemas/SessionAPIError' } } } }
        '500': { description: Session could not be archived, content: { application/json: { schema: { $ref: '#/components/schemas/SessionAPIError' } } } }

  /v1/sessions/{id}/events:
    post:
      operationId: sendEvents
      tags: [Events]
      summary: Send events to session
      parameters:
        - $ref: '#/components/parameters/SessionId'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SendEventsRequest'
      responses:
        '200':
          description: Implemented event types accepted. Schema-recognized event types without runtime behavior are never returned as accepted.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AcceptedSessionEvents'
        '400': { description: 'Invalid body, Event type, payload, or server-owned field', content: { application/json: { schema: { $ref: '#/components/schemas/SessionAPIError' } } } }
        '401': { description: Missing or invalid API key, content: { application/json: { schema: { $ref: '#/components/schemas/TaskCostError' } } } }
        '402': { description: API key spending limit exceeded, content: { application/json: { schema: { $ref: '#/components/schemas/TaskCostError' } } } }
        '403': { description: API key scope does not authorize this endpoint, content: { application/json: { schema: { $ref: '#/components/schemas/APIKeyScopeError' } } } }
        '404': { description: Session not found, content: { application/json: { schema: { $ref: '#/components/schemas/SessionAPIError' } } } }
        '409': { description: Event conflicts with Session state or the Session is archived or terminated, content: { application/json: { schema: { $ref: '#/components/schemas/SessionAPIError' } } } }
        '422':
          description: The batch was rejected without persisting any event. Tool confirmation, custom tool result, tool result, and define outcome currently return `unsupported_event_type`; any `session_thread_id` returns `unsupported_multiagent`.
          content:
            application/json:
              schema:
                type: object
                required: [error]
                properties:
                  error:
                    type: object
                    required: [type, message]
                    properties:
                      type:
                        type: string
                        enum: [unsupported_event_type, unsupported_multiagent]
                      message: { type: string }
        '500': { description: Events could not be accepted, content: { application/json: { schema: { $ref: '#/components/schemas/SessionAPIError' } } } }
    get:
      operationId: listEvents
      tags: [Events]
      summary: List session events
      parameters:
        - $ref: '#/components/parameters/SessionId'
        - name: types
          in: query
          schema: { type: array, items: { type: string } }
          style: form
          explode: true
          description: Repeat the parameter or provide a comma-separated list of event types.
        - name: order
          in: query
          schema: { type: string, enum: [asc, desc], default: asc }
        - name: limit
          in: query
          schema: { type: integer, minimum: 1, maximum: 100 }
        - name: page
          in: query
          schema: { type: string }
        - name: 'created_at[gt]'
          in: query
          schema: { type: string, format: date-time }
        - name: 'created_at[gte]'
          in: query
          schema: { type: string, format: date-time }
        - name: 'created_at[lt]'
          in: query
          schema: { type: string, format: date-time }
        - name: 'created_at[lte]'
          in: query
          schema: { type: string, format: date-time }
      responses:
        '200':
          description: Event list
          content:
            application/json:
              schema:
                type: object
                required: [data]
                properties:
                  data:
                    type: array
                    items:
                      $ref: '#/components/schemas/SessionEvent'
                  next_page:
                    type: string
                    description: Opaque cursor present when another page is available.
        '400': { description: 'Invalid order, limit, page cursor, or created-at filter', content: { application/json: { schema: { $ref: '#/components/schemas/SessionAPIError' } } } }
        '401': { description: Missing or invalid API key, content: { application/json: { schema: { $ref: '#/components/schemas/TaskCostError' } } } }
        '402': { description: API key spending limit exceeded, content: { application/json: { schema: { $ref: '#/components/schemas/TaskCostError' } } } }
        '403': { description: API key scope does not authorize this endpoint, content: { application/json: { schema: { $ref: '#/components/schemas/APIKeyScopeError' } } } }
        '404': { description: Session not found or no longer stream-accessible, content: { application/json: { schema: { $ref: '#/components/schemas/SessionAPIError' } } } }
        '500': { description: Events could not be listed, content: { application/json: { schema: { $ref: '#/components/schemas/SessionAPIError' } } } }

  /v1/sessions/{id}/events/stream:
    get:
      operationId: streamEvents
      tags: [Events]
      summary: Stream session events (SSE)
      parameters:
        - $ref: '#/components/parameters/SessionId'
        - name: cursor
          in: query
          schema: { type: string }
          description: Resume after the last delivered event ID. Last-Event-ID is also accepted.
        - name: event_delta
          in: query
          style: form
          explode: true
          schema:
            type: array
            items: { type: string, enum: [agent.message, agent.thinking] }
          description: Compatibility parameter. Accepted values are validated, but the current stream emits persisted canonical Events only and does not synthesize preview deltas.
      responses:
        '200':
          description: SSE event stream
          content:
            text/event-stream:
              schema:
                type: string
        '400': { description: Invalid event_delta or stream cursor, content: { application/json: { schema: { $ref: '#/components/schemas/SessionAPIError' } } } }
        '401': { description: Missing or invalid API key, content: { application/json: { schema: { $ref: '#/components/schemas/TaskCostError' } } } }
        '402': { description: API key spending limit exceeded, content: { application/json: { schema: { $ref: '#/components/schemas/TaskCostError' } } } }
        '403': { description: API key scope does not authorize this endpoint, content: { application/json: { schema: { $ref: '#/components/schemas/APIKeyScopeError' } } } }
        '404': { description: Session not found, content: { application/json: { schema: { $ref: '#/components/schemas/SessionAPIError' } } } }
        '500': { description: Event history could not be loaded before streaming, content: { application/json: { schema: { $ref: '#/components/schemas/SessionAPIError' } } } }


  # --- Models -----------------------------------------------------
  /v1/models:
    get:
      operationId: listModels
      tags: [Models]
      summary: List models
      description: List enabled logical models in the OpenAI-compatible model-list shape. Use the detail endpoint for capabilities, schemas, and pricing.
      parameters:
        - name: q
          in: query
          schema:
            type: string
          description: Fuzzy search over model name, display name, vendor, and description.
        - name: vendor
          in: query
          schema:
            type: string
          description: Exact, case-sensitive vendor filter.
        - name: type
          in: query
          schema:
            type: string
            default: llm
          description: Filter by model type. Omission defaults to llm; an explicitly empty value removes the type filter.
        - name: order
          in: query
          schema:
            type: string
            enum: [sort_order, most-popular, newest, name]
            default: sort_order
          description: Sort order.
      responses:
        '200':
          description: Model list
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ModelListResponse'
        '500':
          description: Model catalog lookup failed
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TaskCostError'
        '401':
          description: Missing or invalid API key
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TaskCostError'
        '402':
          description: API key spending limit exceeded
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TaskCostError'
        '403':
          description: API key scope does not authorize this endpoint
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/APIKeyScopeError'

  /v1/models/{id_or_name}:
    get:
      operationId: getModel
      tags: [Models]
      summary: Get model details
      description: Get model details by UUID or full logical model name. Model names containing slashes are supported as path segments, for example /v1/models/openai/gpt-5.6-luna. Name lookup returns enabled catalog models; UUID lookup can return a disabled catalog record with enabled set to false. Protocol-ingress-only models are never returned.
      parameters:
        - name: id_or_name
          in: path
          required: true
          schema:
            type: string
          description: Model ID or full model name.
      responses:
        '200':
          description: Model details
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ModelDetail'
        '400':
          description: Missing model ID or name
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TaskCostError'
        '404':
          description: Model not found or not publicly discoverable
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TaskCostError'
        '401':
          description: Missing or invalid API key
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TaskCostError'
        '402':
          description: API key spending limit exceeded
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TaskCostError'
        '403':
          description: API key scope does not authorize this endpoint
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/APIKeyScopeError'

  /v1/deployments:
    post:
      operationId: createDeployment
      tags: [Schedules]
      summary: Create a Schedule
      description: Create either a declarative REST Deployment from a runtime definition or an advanced Deployment bound to an existing Agent.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              oneOf:
                - $ref: '#/components/schemas/CreateDeclarativeDeploymentRequest'
                - $ref: '#/components/schemas/CreateDeploymentRequest'
          application/yaml:
            schema:
              oneOf:
                - $ref: '#/components/schemas/CreateDeclarativeDeploymentRequest'
                - $ref: '#/components/schemas/CreateDeploymentRequest'
          application/x-yaml:
            schema:
              oneOf:
                - $ref: '#/components/schemas/CreateDeclarativeDeploymentRequest'
                - $ref: '#/components/schemas/CreateDeploymentRequest'
      responses:
        '200':
          description: Deployment created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Deployment'
        '400':
          description: Invalid definition, creation mode, initial events, schedule, timeout policy, Agent, or reserved fields
          content:
            application/json:
              schema: { $ref: '#/components/schemas/APIError' }
        '401':
          description: Missing or invalid API key
          content:
            application/json:
              schema: { $ref: '#/components/schemas/TaskCostError' }
        '402': { description: API key spending limit exceeded, content: { application/json: { schema: { $ref: '#/components/schemas/TaskCostError' } } } }
        '403': { description: API key scope does not authorize this endpoint, content: { application/json: { schema: { $ref: '#/components/schemas/APIKeyScopeError' } } } }
        '404':
          description: Referenced Skill not found
          content:
            application/json:
              schema: { $ref: '#/components/schemas/APIError' }
        '415':
          description: Content-Type is not application/json, application/yaml, or application/x-yaml
          content:
            application/json:
              schema: { $ref: '#/components/schemas/APIError' }
        '422':
          description: Runtime or Skill is incompatible
          content:
            application/json:
              schema: { $ref: '#/components/schemas/APIError' }
        '500':
          description: Deployment creation or declarative compilation failed
          content:
            application/json:
              schema: { $ref: '#/components/schemas/APIError' }
    get:
      operationId: listDeployments
      tags: [Schedules]
      summary: List Schedules
      parameters:
        - { name: agent_id, in: query, schema: { type: string } }
        - { name: 'created_at[gte]', in: query, schema: { type: string, format: date-time } }
        - { name: 'created_at[lte]', in: query, schema: { type: string, format: date-time } }
        - { name: include_archived, in: query, schema: { type: boolean, default: false } }
        - name: status
          in: query
          style: form
          explode: true
          schema:
            type: array
            items: { type: string, enum: [active, paused] }
          description: Repeat the parameter to filter by one or more Deployment statuses.
        - { name: limit, in: query, schema: { type: integer, minimum: 1, maximum: 100, default: 20 } }
        - { name: page, in: query, schema: { type: string } }
      responses:
        '200':
          description: Deployment list
          content:
            application/json:
              schema:
                type: object
                required: [data]
                properties:
                  data:
                    type: array
                    items:
                      $ref: '#/components/schemas/Deployment'
                  next_page: { type: string, description: Opaque cursor included only when another page is available. }
        '400': { description: Invalid status filter or page cursor, content: { application/json: { schema: { $ref: '#/components/schemas/APIError' } } } }
        '401': { description: Missing or invalid API key, content: { application/json: { schema: { $ref: '#/components/schemas/TaskCostError' } } } }
        '402': { description: API key spending limit exceeded, content: { application/json: { schema: { $ref: '#/components/schemas/TaskCostError' } } } }
        '403': { description: API key scope does not authorize this endpoint, content: { application/json: { schema: { $ref: '#/components/schemas/APIKeyScopeError' } } } }
        '500': { description: Deployment list could not be loaded, content: { application/json: { schema: { $ref: '#/components/schemas/APIError' } } } }

  /v1/deployments/{id}:
    get:
      operationId: getDeployment
      tags: [Schedules]
      summary: Get a Schedule
      parameters:
        - $ref: '#/components/parameters/DeploymentId'
      responses:
        '200':
          description: Deployment details
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Deployment'
        '401': { description: Missing or invalid API key, content: { application/json: { schema: { $ref: '#/components/schemas/TaskCostError' } } } }
        '402': { description: API key spending limit exceeded, content: { application/json: { schema: { $ref: '#/components/schemas/TaskCostError' } } } }
        '403': { description: API key scope does not authorize this endpoint, content: { application/json: { schema: { $ref: '#/components/schemas/APIKeyScopeError' } } } }
        '404': { description: Deployment not found, content: { application/json: { schema: { $ref: '#/components/schemas/APIError' } } } }
        '500': { description: Deployment detail could not be loaded, content: { application/json: { schema: { $ref: '#/components/schemas/APIError' } } } }
    patch:
      operationId: updateDeployment
      tags: [Schedules]
      summary: Update a Schedule
      parameters:
        - $ref: '#/components/parameters/DeploymentId'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UpdateDeploymentRequest'
      responses:
        '200':
          description: Deployment updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Deployment'
        '400': { description: 'Invalid field, events, schedule, timeout, binding, Agent config, or notification target', content: { application/json: { schema: { $ref: '#/components/schemas/APIError' } } } }
        '401': { description: Missing or invalid API key, content: { application/json: { schema: { $ref: '#/components/schemas/TaskCostError' } } } }
        '402': { description: API key spending limit exceeded, content: { application/json: { schema: { $ref: '#/components/schemas/TaskCostError' } } } }
        '403': { description: API key scope does not authorize this endpoint, content: { application/json: { schema: { $ref: '#/components/schemas/APIKeyScopeError' } } } }
        '404': { description: 'Deployment or Skill not found', content: { application/json: { schema: { $ref: '#/components/schemas/APIError' } } } }
        '409': { description: 'Archived, active, running, or version-conflicted Deployment', content: { application/json: { schema: { $ref: '#/components/schemas/APIError' } } } }
        '422': { description: Reserved field supplied or Runtime/Skill is incompatible, content: { application/json: { schema: { $ref: '#/components/schemas/APIError' } } } }
        '500': { description: Deployment update or detail reload failed, content: { application/json: { schema: { $ref: '#/components/schemas/APIError' } } } }
    post:
      operationId: updateDeploymentClaude
      tags: [Schedules]
      summary: Update a Schedule (compatibility alias)
      parameters:
        - $ref: '#/components/parameters/DeploymentId'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UpdateDeploymentRequest'
      responses:
        '200':
          description: Deployment updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Deployment'
        '400': { description: 'Invalid field, events, schedule, timeout, binding, Agent config, or notification target', content: { application/json: { schema: { $ref: '#/components/schemas/APIError' } } } }
        '401': { description: Missing or invalid API key, content: { application/json: { schema: { $ref: '#/components/schemas/TaskCostError' } } } }
        '402': { description: API key spending limit exceeded, content: { application/json: { schema: { $ref: '#/components/schemas/TaskCostError' } } } }
        '403': { description: API key scope does not authorize this endpoint, content: { application/json: { schema: { $ref: '#/components/schemas/APIKeyScopeError' } } } }
        '404': { description: 'Deployment or Skill not found', content: { application/json: { schema: { $ref: '#/components/schemas/APIError' } } } }
        '409': { description: 'Archived, active, running, or version-conflicted Deployment', content: { application/json: { schema: { $ref: '#/components/schemas/APIError' } } } }
        '422': { description: Reserved field supplied or Runtime/Skill is incompatible, content: { application/json: { schema: { $ref: '#/components/schemas/APIError' } } } }
        '500': { description: Deployment update or detail reload failed, content: { application/json: { schema: { $ref: '#/components/schemas/APIError' } } } }
    delete:
      operationId: deleteDeployment
      tags: [Schedules]
      summary: Delete a Schedule
      description: Permanently delete an eligible Deployment.
      parameters:
        - $ref: '#/components/parameters/DeploymentId'
      responses:
        '200':
          description: Deployment deleted
          content:
            application/json:
              schema:
                type: object
                required: [id, deleted]
                properties:
                  id: { type: string }
                  deleted: { type: boolean, const: true }
        '404': { description: Deployment not found, content: { application/json: { schema: { $ref: '#/components/schemas/APIError' } } } }
        '409': { description: Deployment cannot be deleted from its current state, content: { application/json: { schema: { $ref: '#/components/schemas/APIError' } } } }
        '401': { description: Missing or invalid API key, content: { application/json: { schema: { $ref: '#/components/schemas/TaskCostError' } } } }
        '402': { description: API key spending limit exceeded, content: { application/json: { schema: { $ref: '#/components/schemas/TaskCostError' } } } }
        '403': { description: API key scope does not authorize this endpoint, content: { application/json: { schema: { $ref: '#/components/schemas/APIKeyScopeError' } } } }
        '500': { description: Deployment deletion failed, content: { application/json: { schema: { $ref: '#/components/schemas/APIError' } } } }

  /v1/deployments/{id}/run:
    post:
      operationId: runDeployment
      tags: [Schedules]
      summary: Trigger a Schedule (compatibility alias)
      parameters:
        - $ref: '#/components/parameters/DeploymentId'
        - name: Idempotency-Key
          in: header
          required: false
          schema: { type: string }
      requestBody:
        required: false
        content:
          application/json:
            schema: { $ref: '#/components/schemas/EmptyObjectRequest' }
      responses:
        '200':
          description: Existing or newly created DeploymentRun result
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DeploymentRun'
        '400': { description: 'Request body is not absent or an empty JSON object, or stored initial events are invalid', content: { application/json: { schema: { $ref: '#/components/schemas/APIError' } } } }
        '401': { description: Missing or invalid API key, content: { application/json: { schema: { $ref: '#/components/schemas/TaskCostError' } } } }
        '402': { description: API key spending limit exceeded, content: { application/json: { schema: { $ref: '#/components/schemas/TaskCostError' } } } }
        '403': { description: API key scope does not authorize this endpoint, content: { application/json: { schema: { $ref: '#/components/schemas/APIKeyScopeError' } } } }
        '404': { description: Deployment or a required resolved binding was not found, content: { application/json: { schema: { $ref: '#/components/schemas/APIError' } } } }
        '409': { description: Deployment is archived or Idempotency-Key conflicts with a different trigger, content: { application/json: { schema: { $ref: '#/components/schemas/APIError' } } } }
        '500': { description: 'Trigger, Session, or execution binding could not be created', content: { application/json: { schema: { $ref: '#/components/schemas/APIError' } } } }
        '503': { description: DeploymentRun or execution runtime service is unavailable, content: { application/json: { schema: { $ref: '#/components/schemas/APIError' } } } }

  /v1/deployments/{id}/pause:
    post:
      operationId: pauseDeployment
      tags: [Schedules]
      summary: Pause scheduled triggers
      parameters:
        - $ref: '#/components/parameters/DeploymentId'
      responses:
        '200':
          description: Deployment paused
          content:
            application/json:
              schema: { $ref: '#/components/schemas/Deployment' }
        '404': { description: Deployment not found, content: { application/json: { schema: { $ref: '#/components/schemas/APIError' } } } }
        '409': { description: Deployment cannot be paused from its current state, content: { application/json: { schema: { $ref: '#/components/schemas/APIError' } } } }
        '401': { description: Missing or invalid API key, content: { application/json: { schema: { $ref: '#/components/schemas/TaskCostError' } } } }
        '402': { description: API key spending limit exceeded, content: { application/json: { schema: { $ref: '#/components/schemas/TaskCostError' } } } }
        '403': { description: API key scope does not authorize this endpoint, content: { application/json: { schema: { $ref: '#/components/schemas/APIKeyScopeError' } } } }
        '500': { description: Deployment pause failed, content: { application/json: { schema: { $ref: '#/components/schemas/APIError' } } } }

  /v1/deployments/{id}/unpause:
    post:
      operationId: unpauseDeployment
      tags: [Schedules]
      summary: Resume scheduled triggers
      parameters:
        - $ref: '#/components/parameters/DeploymentId'
      responses:
        '200':
          description: Deployment resumed
          content:
            application/json:
              schema: { $ref: '#/components/schemas/Deployment' }
        '404': { description: Deployment not found, content: { application/json: { schema: { $ref: '#/components/schemas/APIError' } } } }
        '409': { description: Deployment cannot be resumed from its current state, content: { application/json: { schema: { $ref: '#/components/schemas/APIError' } } } }
        '401': { description: Missing or invalid API key, content: { application/json: { schema: { $ref: '#/components/schemas/TaskCostError' } } } }
        '402': { description: API key spending limit exceeded, content: { application/json: { schema: { $ref: '#/components/schemas/TaskCostError' } } } }
        '403': { description: API key scope does not authorize this endpoint, content: { application/json: { schema: { $ref: '#/components/schemas/APIKeyScopeError' } } } }
        '500': { description: Deployment resume failed, content: { application/json: { schema: { $ref: '#/components/schemas/APIError' } } } }

  /v1/deployments/{id}/archive:
    post:
      operationId: archiveDeployment
      tags: [Schedules]
      summary: Archive a Schedule
      parameters:
        - $ref: '#/components/parameters/DeploymentId'
      responses:
        '200':
          description: Deployment archived
          content:
            application/json:
              schema: { $ref: '#/components/schemas/Deployment' }
        '404': { description: Deployment not found, content: { application/json: { schema: { $ref: '#/components/schemas/APIError' } } } }
        '409': { description: Deployment cannot be archived from its current state, content: { application/json: { schema: { $ref: '#/components/schemas/APIError' } } } }
        '401': { description: Missing or invalid API key, content: { application/json: { schema: { $ref: '#/components/schemas/TaskCostError' } } } }
        '402': { description: API key spending limit exceeded, content: { application/json: { schema: { $ref: '#/components/schemas/TaskCostError' } } } }
        '403': { description: API key scope does not authorize this endpoint, content: { application/json: { schema: { $ref: '#/components/schemas/APIKeyScopeError' } } } }
        '500': { description: Deployment archive or reload failed, content: { application/json: { schema: { $ref: '#/components/schemas/APIError' } } } }

  /v1/deployments/{id}/notifications/feishu/test:
    post:
      operationId: testDeploymentFeishuNotification
      tags: [Schedules]
      summary: Test the saved Feishu notification target
      description: |
        Sends a fixed AGRouter test notification to the Feishu webhook already saved on this Deployment.
        The request body must be absent or an empty JSON object. This endpoint never accepts a webhook URL or custom message.
      parameters:
        - $ref: '#/components/parameters/DeploymentId'
      requestBody:
        required: false
        content:
          application/json:
            schema:
              type: object
              maxProperties: 0
              additionalProperties: false
      responses:
        '200':
          description: Test notification delivered
          content:
            application/json:
              schema:
                type: object
                required: [delivered]
                properties:
                  delivered: { type: boolean, const: true }
        '400':
          description: Request body is not absent or an empty JSON object
          content:
            application/json:
              schema: { $ref: '#/components/schemas/APIError' }
        '401':
          description: Missing or invalid API key
          content:
            application/json:
              schema: { $ref: '#/components/schemas/TaskCostError' }
        '402': { description: API key spending limit exceeded, content: { application/json: { schema: { $ref: '#/components/schemas/TaskCostError' } } } }
        '403': { description: API key scope does not authorize this endpoint, content: { application/json: { schema: { $ref: '#/components/schemas/APIKeyScopeError' } } } }
        '404':
          description: Deployment not found for the current organization
          content:
            application/json:
              schema: { $ref: '#/components/schemas/APIError' }
        '409':
          description: Deployment is archived or no Feishu webhook is configured
          content:
            application/json:
              schema: { $ref: '#/components/schemas/APIError' }
        '502':
          description: Feishu rejected the test notification or delivery failed
          content:
            application/json:
              schema: { $ref: '#/components/schemas/APIError' }

  /v1/deployments/{id}/runs:
    post:
      operationId: triggerDeploymentRun
      tags: [Schedules]
      summary: Trigger a Schedule
      description: Creates one durable trigger record and attempts to create exactly one new Session.
      parameters:
        - $ref: '#/components/parameters/DeploymentId'
        - name: Idempotency-Key
          in: header
          required: false
          schema: { type: string }
      requestBody:
        required: false
        content:
          application/json:
            schema: { $ref: '#/components/schemas/EmptyObjectRequest' }
      responses:
        '200':
          description: Existing or newly created DeploymentRun result
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DeploymentRun'
        '409':
          description: The same idempotency key was used with a different request
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/APIError'
        '400': { description: 'Request body is not absent or an empty JSON object, or stored initial events are invalid', content: { application/json: { schema: { $ref: '#/components/schemas/APIError' } } } }
        '401': { description: Missing or invalid API key, content: { application/json: { schema: { $ref: '#/components/schemas/TaskCostError' } } } }
        '402': { description: API key spending limit exceeded, content: { application/json: { schema: { $ref: '#/components/schemas/TaskCostError' } } } }
        '403': { description: API key scope does not authorize this endpoint, content: { application/json: { schema: { $ref: '#/components/schemas/APIKeyScopeError' } } } }
        '404': { description: Deployment or a required resolved binding was not found, content: { application/json: { schema: { $ref: '#/components/schemas/APIError' } } } }
        '500': { description: 'Trigger, Session, or execution binding could not be created', content: { application/json: { schema: { $ref: '#/components/schemas/APIError' } } } }
        '503': { description: DeploymentRun or execution runtime service is unavailable, content: { application/json: { schema: { $ref: '#/components/schemas/APIError' } } } }
    get:
      operationId: listDeploymentRuns
      tags: [Schedules]
      summary: List runs for a Schedule
      parameters:
        - $ref: '#/components/parameters/DeploymentId'
        - name: limit
          in: query
          schema: { type: integer, minimum: 1, maximum: 1000, default: 20 }
          description: Page size. Pass this parameter explicitly when following nested-list cursors.
        - name: page
          in: query
          schema: { type: string }
          description: Opaque cursor returned in a previous response's next_page field.
      responses:
        '200':
          description: DeploymentRun list
          content:
            application/json:
              schema:
                type: object
                required: [data]
                properties:
                  data:
                    type: array
                    items:
                      $ref: '#/components/schemas/DeploymentRun'
                  next_page:
                    type: string
                    description: Opaque cursor included when the current page reaches the requested limit.
        '400': { description: Invalid page cursor, content: { application/json: { schema: { $ref: '#/components/schemas/APIError' } } } }
        '401': { description: Missing or invalid API key, content: { application/json: { schema: { $ref: '#/components/schemas/TaskCostError' } } } }
        '402': { description: API key spending limit exceeded, content: { application/json: { schema: { $ref: '#/components/schemas/TaskCostError' } } } }
        '403': { description: API key scope does not authorize this endpoint, content: { application/json: { schema: { $ref: '#/components/schemas/APIKeyScopeError' } } } }
        '500': { description: DeploymentRun list could not be loaded, content: { application/json: { schema: { $ref: '#/components/schemas/APIError' } } } }
        '503': { description: DeploymentRun service is unavailable, content: { application/json: { schema: { $ref: '#/components/schemas/APIError' } } } }

  /v1/deployments/{id}/runs/{drun_id}:
    get:
      operationId: getDeploymentRun
      tags: [Schedules]
      summary: Get a run for a Schedule
      parameters:
        - $ref: '#/components/parameters/DeploymentId'
        - name: drun_id
          in: path
          required: true
          schema: { type: string, pattern: '^drun_' }
      responses:
        '200':
          description: DeploymentRun details
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DeploymentRun'
        '409':
          description: The DeploymentRun is still pending and has not resolved to a Session creation result
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/APIError'
        '401': { description: Missing or invalid API key, content: { application/json: { schema: { $ref: '#/components/schemas/TaskCostError' } } } }
        '402': { description: API key spending limit exceeded, content: { application/json: { schema: { $ref: '#/components/schemas/TaskCostError' } } } }
        '403': { description: API key scope does not authorize this endpoint, content: { application/json: { schema: { $ref: '#/components/schemas/APIKeyScopeError' } } } }
        '404': { description: DeploymentRun not found or belongs to another Deployment, content: { application/json: { schema: { $ref: '#/components/schemas/APIError' } } } }
        '503': { description: DeploymentRun service is unavailable, content: { application/json: { schema: { $ref: '#/components/schemas/APIError' } } } }

  /v1/deployment_runs:
    get:
      operationId: listAllDeploymentRuns
      tags: [Schedules]
      summary: List runs across all Schedules
      parameters:
        - name: deployment_id
          in: query
          schema: { type: string }
        - name: agent_id
          in: query
          schema: { type: string }
        - { name: 'created_at[gt]', in: query, schema: { type: string, format: date-time } }
        - { name: 'created_at[gte]', in: query, schema: { type: string, format: date-time } }
        - { name: 'created_at[lt]', in: query, schema: { type: string, format: date-time } }
        - { name: 'created_at[lte]', in: query, schema: { type: string, format: date-time } }
        - { name: created_at_gt, in: query, deprecated: true, description: 'Compatibility alias for created_at[gt].', schema: { type: string, format: date-time } }
        - { name: created_at_gte, in: query, deprecated: true, description: 'Compatibility alias for created_at[gte].', schema: { type: string, format: date-time } }
        - { name: created_at_lt, in: query, deprecated: true, description: 'Compatibility alias for created_at[lt].', schema: { type: string, format: date-time } }
        - { name: created_at_lte, in: query, deprecated: true, description: 'Compatibility alias for created_at[lte].', schema: { type: string, format: date-time } }
        - { name: has_error, in: query, schema: { type: boolean } }
        - name: trigger_type
          in: query
          style: form
          explode: true
          schema:
            type: array
            items: { type: string, enum: [schedule, manual] }
          description: Repeat the parameter, or use comma-separated values, to filter by trigger type.
        - name: status
          in: query
          description: Repeat to select one or more DeploymentRun creation outcomes. The current response object does not expose this internal status field.
          style: form
          explode: true
          schema:
            type: array
            items: { type: string, enum: [pending, succeeded, failed] }
        - name: limit
          in: query
          schema: { type: integer, minimum: 1, maximum: 1000, default: 20 }
        - name: page
          in: query
          schema: { type: string }
      responses:
        '200':
          description: DeploymentRun list
          content:
            application/json:
              schema:
                type: object
                required: [data]
                properties:
                  data:
                    type: array
                    items: { $ref: '#/components/schemas/DeploymentRun' }
                  next_page: { type: string, description: Opaque cursor included when the current page reaches the requested limit. }
        '400': { description: 'Invalid filter, limit, timestamp, boolean, status, trigger type, or page cursor', content: { application/json: { schema: { $ref: '#/components/schemas/APIError' } } } }
        '401': { description: Missing or invalid API key, content: { application/json: { schema: { $ref: '#/components/schemas/TaskCostError' } } } }
        '402': { description: API key spending limit exceeded, content: { application/json: { schema: { $ref: '#/components/schemas/TaskCostError' } } } }
        '403': { description: API key scope does not authorize this endpoint, content: { application/json: { schema: { $ref: '#/components/schemas/APIKeyScopeError' } } } }
        '500': { description: DeploymentRun list could not be loaded, content: { application/json: { schema: { $ref: '#/components/schemas/APIError' } } } }
        '503': { description: DeploymentRun service is unavailable, content: { application/json: { schema: { $ref: '#/components/schemas/APIError' } } } }

  /v1/deployment_runs/{id}:
    get:
      operationId: getGlobalDeploymentRun
      tags: [Schedules]
      summary: Get a Schedule run by ID
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: string, pattern: '^drun_' }
      responses:
        '200':
          description: DeploymentRun details
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DeploymentRun'
        '401': { description: Missing or invalid API key, content: { application/json: { schema: { $ref: '#/components/schemas/TaskCostError' } } } }
        '402': { description: API key spending limit exceeded, content: { application/json: { schema: { $ref: '#/components/schemas/TaskCostError' } } } }
        '403': { description: API key scope does not authorize this endpoint, content: { application/json: { schema: { $ref: '#/components/schemas/APIKeyScopeError' } } } }
        '404': { description: DeploymentRun not found, content: { application/json: { schema: { $ref: '#/components/schemas/APIError' } } } }
        '503': { description: DeploymentRun service is unavailable, content: { application/json: { schema: { $ref: '#/components/schemas/APIError' } } } }

  # --- Skills -----------------------------------------------------
  /v1/skills/files:
    post:
      operationId: uploadSkillFile
      tags: [Skills]
      summary: Upload a Skill bundle or preview image
      description: Archives are limited to 50 MB. JPEG, PNG, WebP, and GIF preview images are limited to 10 MB.
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              type: object
              required: [file]
              properties:
                file: { type: string, format: binary }
                skill_id:
                  type: string
                  format: uuid
                  description: Optional Skill ID used to organize the uploaded file path.
      responses:
        '201':
          description: Skill file uploaded
          content:
            application/json:
              schema: { $ref: '#/components/schemas/SkillFile' }
        '400': { description: "Missing file, unsupported file type, or file over its category limit", content: { application/json: { schema: { $ref: '#/components/schemas/TaskCostError' } } } }
        '401': { description: Missing API identity or organization context, content: { application/json: { schema: { $ref: '#/components/schemas/TaskCostError' } } } }
        '402': { description: API key spending limit exceeded, content: { application/json: { schema: { $ref: '#/components/schemas/TaskCostError' } } } }
        '403': { description: API key scope does not authorize this endpoint, content: { application/json: { schema: { $ref: '#/components/schemas/APIKeyScopeError' } } } }
        '500': { description: File could not be read or stored, content: { application/json: { schema: { $ref: '#/components/schemas/TaskCostError' } } } }
        '503': { description: Persistent file storage is unavailable, content: { application/json: { schema: { $ref: '#/components/schemas/TaskCostError' } } } }

  /v1/skills:
    post:
      operationId: createSkill
      tags: [Skills]
      summary: Create a Skill
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/CreateSkillRequest' }
      responses:
        '201':
          description: Skill created
          content:
            application/json:
              schema: { $ref: '#/components/schemas/SkillCreated' }
        '400': { description: Invalid Skill source or metadata, content: { application/json: { schema: { $ref: '#/components/schemas/APIError' } } } }
        '401': { description: Missing API identity or organization context, content: { application/json: { schema: { oneOf: [{ $ref: '#/components/schemas/TaskCostError' }, { $ref: '#/components/schemas/APIError' }] } } } }
        '402': { description: API key spending limit exceeded, content: { application/json: { schema: { $ref: '#/components/schemas/TaskCostError' } } } }
        '403': { description: API key scope does not authorize this endpoint, content: { application/json: { schema: { $ref: '#/components/schemas/APIKeyScopeError' } } } }
        '500': { description: Skill could not be created, content: { application/json: { schema: { $ref: '#/components/schemas/APIError' } } } }
    get:
      operationId: listSkills
      tags: [Skills]
      summary: List Skills
      parameters:
        - { name: page, in: query, schema: { type: integer, minimum: 1, default: 1 } }
        - { name: page_size, in: query, schema: { type: integer, minimum: 1, maximum: 1000, default: 20 } }
      responses:
        '200':
          description: Skill list
          content:
            application/json:
              schema:
                type: object
                required: [data, total, page, page_size]
                properties:
                  data: { type: array, items: { $ref: '#/components/schemas/SkillListItem' } }
                  total: { type: integer }
                  page: { type: integer }
                  page_size: { type: integer }
        '401': { description: Missing or invalid API key, content: { application/json: { schema: { $ref: '#/components/schemas/TaskCostError' } } } }
        '402': { description: API key spending limit exceeded, content: { application/json: { schema: { $ref: '#/components/schemas/TaskCostError' } } } }
        '403': { description: API key scope does not authorize this endpoint, content: { application/json: { schema: { $ref: '#/components/schemas/APIKeyScopeError' } } } }
        '500': { description: Skill list could not be loaded, content: { application/json: { schema: { $ref: '#/components/schemas/APIError' } } } }

  /v1/skills/{id}:
    get:
      operationId: getSkill
      tags: [Skills]
      summary: Get a Skill
      parameters:
        - $ref: '#/components/parameters/SkillId'
      responses:
        '200':
          description: Skill details
          content:
            application/json:
              schema: { $ref: '#/components/schemas/Skill' }
        '404': { description: Skill not found, content: { application/json: { schema: { $ref: '#/components/schemas/APIError' } } } }
        '401': { description: Missing or invalid API key, content: { application/json: { schema: { $ref: '#/components/schemas/TaskCostError' } } } }
        '402': { description: API key spending limit exceeded, content: { application/json: { schema: { $ref: '#/components/schemas/TaskCostError' } } } }
        '403': { description: API key scope does not authorize this endpoint, content: { application/json: { schema: { $ref: '#/components/schemas/APIKeyScopeError' } } } }
        '500': { description: Skill could not be loaded, content: { application/json: { schema: { $ref: '#/components/schemas/APIError' } } } }
    put:
      operationId: updateSkill
      tags: [Skills]
      summary: Update a Skill
      description: Replacement-style update for display fields. Omitted name, description, and categories are cleared; omitted source URL, preview URLs, metadata, and unified schema are preserved.
      parameters:
        - $ref: '#/components/parameters/SkillId'
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/UpdateSkillRequest' }
      responses:
        '200':
          description: Skill updated
          content:
            application/json:
              schema: { $ref: '#/components/schemas/Skill' }
        '404': { description: Skill not found, content: { application/json: { schema: { $ref: '#/components/schemas/APIError' } } } }
        '400': { description: 'Invalid JSON body, field length, or unified schema', content: { application/json: { schema: { $ref: '#/components/schemas/APIError' } } } }
        '401': { description: Missing or invalid API key, content: { application/json: { schema: { $ref: '#/components/schemas/TaskCostError' } } } }
        '402': { description: API key spending limit exceeded, content: { application/json: { schema: { $ref: '#/components/schemas/TaskCostError' } } } }
        '403': { description: API key scope does not authorize this endpoint, content: { application/json: { schema: { $ref: '#/components/schemas/APIKeyScopeError' } } } }
        '500': { description: Skill could not be updated, content: { application/json: { schema: { $ref: '#/components/schemas/APIError' } } } }
    delete:
      operationId: deleteSkill
      tags: [Skills]
      summary: Soft-delete a Skill
      parameters:
        - $ref: '#/components/parameters/SkillId'
      responses:
        '200':
          description: Skill deleted
          content:
            application/json:
              schema:
                type: object
                required: [id, deleted]
                properties:
                  id: { type: string, format: uuid }
                  deleted: { type: boolean, const: true }
        '404': { description: Skill not found, content: { application/json: { schema: { $ref: '#/components/schemas/APIError' } } } }
        '401': { description: Missing or invalid API key, content: { application/json: { schema: { $ref: '#/components/schemas/TaskCostError' } } } }
        '402': { description: API key spending limit exceeded, content: { application/json: { schema: { $ref: '#/components/schemas/TaskCostError' } } } }
        '403': { description: API key scope does not authorize this endpoint, content: { application/json: { schema: { $ref: '#/components/schemas/APIKeyScopeError' } } } }
        '500': { description: Skill could not be deleted, content: { application/json: { schema: { $ref: '#/components/schemas/APIError' } } } }

  # --- Credentials -----------------------------------------------
  /v1/credentials:
    post:
      operationId: createCredential
      tags: [Credentials]
      summary: Create credential
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateCredentialRequest'
      responses:
        '201':
          description: Credential created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CredentialResponse'
        '400': { description: 'Missing, blank, oversized, unsupported, or otherwise invalid credential field', content: { application/json: { schema: { $ref: '#/components/schemas/LegacyResourceError' } } } }
        '401': { description: Missing or invalid API key, content: { application/json: { schema: { $ref: '#/components/schemas/TaskCostError' } } } }
        '402': { description: API key spending limit exceeded, content: { application/json: { schema: { $ref: '#/components/schemas/TaskCostError' } } } }
        '403': { description: API key scope does not authorize this endpoint, content: { application/json: { schema: { $ref: '#/components/schemas/APIKeyScopeError' } } } }
        '500': { description: Credential could not be encrypted or created, content: { application/json: { schema: { $ref: '#/components/schemas/LegacyResourceError' } } } }
    get:
      operationId: listCredentials
      tags: [Credentials]
      summary: List credentials
      responses:
        '200':
          description: Credential list
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CredentialListResponse'
        '401': { description: Missing or invalid API key, content: { application/json: { schema: { $ref: '#/components/schemas/TaskCostError' } } } }
        '402': { description: API key spending limit exceeded, content: { application/json: { schema: { $ref: '#/components/schemas/TaskCostError' } } } }
        '403': { description: API key scope does not authorize this endpoint, content: { application/json: { schema: { $ref: '#/components/schemas/APIKeyScopeError' } } } }
        '500': { description: Credential list could not be loaded, content: { application/json: { schema: { $ref: '#/components/schemas/LegacyResourceError' } } } }
  /v1/credentials/{id}:
    get:
      operationId: getCredential
      tags: [Credentials]
      summary: Get credential (masked value)
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: string, pattern: '^sec_' }
      responses:
        '200':
          description: Credential details (secret masked)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CredentialResponse'
        '401': { description: Missing or invalid API key, content: { application/json: { schema: { $ref: '#/components/schemas/TaskCostError' } } } }
        '402': { description: API key spending limit exceeded, content: { application/json: { schema: { $ref: '#/components/schemas/TaskCostError' } } } }
        '403': { description: API key scope does not authorize this endpoint, content: { application/json: { schema: { $ref: '#/components/schemas/APIKeyScopeError' } } } }
        '404': { description: Credential not found or belongs to another organization, content: { application/json: { schema: { $ref: '#/components/schemas/LegacyResourceError' } } } }
        '500': { description: Credential could not be loaded, content: { application/json: { schema: { $ref: '#/components/schemas/LegacyResourceError' } } } }
    patch:
      operationId: updateCredential
      tags: [Credentials]
      summary: Update credential status or selection settings
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: string, pattern: '^sec_' }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UpdateCredentialRequest'
      responses:
        '200':
          description: Updated credential
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CredentialResponse'
        '400': { description: 'Invalid JSON body, unsupported status or strategy, non-positive weight, or no mutable fields', content: { application/json: { schema: { $ref: '#/components/schemas/LegacyResourceError' } } } }
        '401': { description: Missing or invalid API key, content: { application/json: { schema: { $ref: '#/components/schemas/TaskCostError' } } } }
        '402': { description: API key spending limit exceeded, content: { application/json: { schema: { $ref: '#/components/schemas/TaskCostError' } } } }
        '403': { description: API key scope does not authorize this endpoint, content: { application/json: { schema: { $ref: '#/components/schemas/APIKeyScopeError' } } } }
        '404': { description: Credential not found or belongs to another organization, content: { application/json: { schema: { $ref: '#/components/schemas/LegacyResourceError' } } } }
        '500': { description: Credential could not be updated, content: { application/json: { schema: { $ref: '#/components/schemas/LegacyResourceError' } } } }
  /v1/credentials/{id}/rotate:
    post:
      operationId: rotateCredential
      tags: [Credentials]
      summary: Rotate credential value
      description: Replace the encrypted value and masked hint, then reset failure_count to 0 and clear cooldown_until.
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: string, pattern: '^sec_' }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [value]
              properties:
                value:
                  type: string
                  minLength: 1
                  maxLength: 8192
                  writeOnly: true
                  description: New plaintext value. Blank or whitespace-only values are rejected.
      responses:
        '200':
          description: Rotated credential
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CredentialResponse'
        '400': { description: 'Missing, blank, oversized, or invalid replacement value', content: { application/json: { schema: { $ref: '#/components/schemas/LegacyResourceError' } } } }
        '401': { description: Missing or invalid API key, content: { application/json: { schema: { $ref: '#/components/schemas/TaskCostError' } } } }
        '402': { description: API key spending limit exceeded, content: { application/json: { schema: { $ref: '#/components/schemas/TaskCostError' } } } }
        '403': { description: API key scope does not authorize this endpoint, content: { application/json: { schema: { $ref: '#/components/schemas/APIKeyScopeError' } } } }
        '404': { description: Credential not found or belongs to another organization, content: { application/json: { schema: { $ref: '#/components/schemas/LegacyResourceError' } } } }
        '500': { description: Credential value could not be encrypted or stored, content: { application/json: { schema: { $ref: '#/components/schemas/LegacyResourceError' } } } }
components:
  securitySchemes:
    BearerAuth:
      type: http
      scheme: bearer
      description: AGRouter API key. Treat the complete credential as an opaque secret.
    AnthropicApiKey:
      type: apiKey
      in: header
      name: x-api-key
      description: "Supported only by `POST /v1/messages` for Anthropic SDK compatibility"
    GoogleApiKey:
      type: apiKey
      in: header
      name: x-goog-api-key
      description: Supported by Google Gemini-compatible endpoints. Takes precedence over the other key locations.
    GoogleQueryApiKey:
      type: apiKey
      in: query
      name: key
      description: Google SDK compatibility fallback. Prefer x-goog-api-key or Authorization because URLs can be logged.

  parameters:
    GeminiModel:
      name: model
      in: path
      required: true
      schema: { type: string, minLength: 1 }
      description: Bare Gemini model name, such as gemini-3.5-flash. AGRouter adds the google/ catalog prefix.
    EndpointId:
      name: id
      in: path
      required: true
      schema:
        type: string
      description: Service ID (`ep_` prefix)
    AgentId:
      name: id
      in: path
      required: true
      schema:
        type: string
        pattern: '^agent_'
      description: Agent ID
    SessionId:
      name: id
      in: path
      required: true
      schema:
        type: string
      description: Session ID
    DeploymentId:
      name: id
      in: path
      required: true
      schema:
        type: string
        pattern: '^depl_'
      description: Deployment ID
    SkillId:
      name: id
      in: path
      required: true
      schema:
        type: string
        format: uuid
      description: Skill UUID
  schemas:
    # --- Skills ---------------------------------------------------
    SkillFile:
      type: object
      required: [url, key, filename, size, content_type]
      properties:
        url: { type: string, format: uri }
        key: { type: string }
        filename: { type: string }
        size: { type: integer }
        content_type: { type: string }

    CreateSkillRequest:
      type: object
      required: [name]
      properties:
        name: { type: string, minLength: 1, maxLength: 100, pattern: '.*\S.*' }
        description: { type: [string, 'null'], maxLength: 1000 }
        categories: { type: [array, 'null'], items: { type: string } }
        skill_file_url: { type: string, minLength: 1, pattern: '.*\S.*' }
        git_url: { type: string, minLength: 1, pattern: '.*\S.*' }
        preview_image_urls: { type: [array, 'null'], items: { type: string } }
        metadata: { type: [object, 'null'], additionalProperties: true }
        unified_schema: { type: [object, 'null'], additionalProperties: true }
      anyOf:
        - required: [skill_file_url]
        - required: [git_url]

    UpdateSkillRequest:
      type: object
      description: Omitted name, description, and categories are cleared. Omitted preview_image_urls, skill_file_url, metadata, and unified_schema preserve their stored values.
      properties:
        name: { type: [string, 'null'], maxLength: 100, description: 'New display name. Omitted, null, or blank clears display_name; the immutable full Skill ref in name is unchanged.' }
        description: { type: [string, 'null'], maxLength: 1000, description: Omitted or null clears the description. }
        categories: { type: [array, 'null'], items: { type: string }, description: 'Omitted or null clears categories; the detail response normalizes the result to an empty array.' }
        skill_file_url: { type: [string, 'null'], description: 'A non-blank value replaces the bundle URL; omitted, null, or blank preserves it.' }
        preview_image_urls: { type: [array, 'null'], items: { type: string }, description: 'Omitted or null preserves previews; an empty array clears them.' }
        metadata: { type: [object, 'null'], additionalProperties: true, description: 'Supplied non-reserved keys merge into stored metadata; omitted or null preserves them.' }
        unified_schema: { type: [object, 'null'], additionalProperties: true, description: 'Supplied value replaces the stored schema after validation; omitted or null preserves it.' }

    SkillCreated:
      type: object
      required: [id, name, display_name, vendor_slug, plugin_slug, icon_url, preview_urls, created_at]
      properties:
        id: { type: string, format: uuid }
        name: { type: string, description: Full vendor_slug/plugin_slug reference. }
        display_name: { type: string }
        vendor_slug: { type: string }
        plugin_slug: { type: string }
        icon_url: { type: string }
        preview_urls: { type: array, items: { type: string } }
        created_at: { type: string, format: date-time }

    Skill:
      type: object
      required: [id, name, display_name, vendor_slug, plugin_slug, description, categories, icon_url, preview_urls, skill_file_url, git_url, created_at, updated_at]
      properties:
        id: { type: string, format: uuid }
        name: { type: string, description: Full vendor_slug/plugin_slug reference. }
        display_name: { type: string }
        vendor_slug: { type: string }
        plugin_slug: { type: string }
        description: { type: string }
        categories: { type: array, items: { type: string } }
        icon_url: { type: string }
        preview_urls: { type: array, items: { type: string } }
        skill_file_url: { type: string }
        git_url: { type: string }
        created_at: { type: string, format: date-time }
        updated_at: { type: string, format: date-time }

    SkillListItem:
      type: object
      required: [id, name, display_name, vendor_slug, plugin_slug, description, icon_url, preview_urls, created_at, updated_at]
      properties:
        id: { type: string, format: uuid }
        name: { type: string, description: Full vendor_slug/plugin_slug reference. }
        display_name: { type: string }
        vendor_slug: { type: string }
        plugin_slug: { type: string }
        description: { type: string }
        icon_url: { type: string }
        preview_urls: { type: array, items: { type: string } }
        created_at: { type: string, format: date-time }
        updated_at: { type: string, format: date-time }

    # --- Account --------------------------------------------------
    AccountBalance:
      type: object
      required: [org_id, balance, credit_limit, alert_threshold]
      properties:
        org_id:
          type: string
          format: uuid
        balance:
          type: string
          description: Current organization balance in USD, serialized as an exact decimal string.
        credit_limit:
          oneOf:
            - type: string
            - type: 'null'
          description: Optional USD credit limit. Null means no credit limit.
        alert_threshold:
          oneOf:
            - type: string
            - type: 'null'
          description: Optional USD balance alert threshold.

    AccountHistoryPage:
      type: object
      required: [items, total, page, page_size]
      properties:
        items:
          type: array
          items:
            $ref: '#/components/schemas/AccountHistoryItem'
        total: { type: integer, minimum: 0 }
        page: { type: integer, minimum: 1 }
        page_size: { type: integer, minimum: 1, maximum: 100 }

    AccountHistoryItem:
      type: object
      required: [id, model, type, execution_mode, status, latency_ms, user_cost, prompt_tokens, completion_tokens, total_tokens, cached_tokens, cache_creation_tokens, created_at, api_key_prefix]
      properties:
        id: { type: string, description: Opaque execution ID. }
        model: { type: string }
        type: { type: string }
        execution_mode: { type: string }
        status: { type: string, enum: [pending, running, completed, failed, timeout] }
        latency_ms: { type: integer, minimum: 0 }
        user_cost:
          type: string
          description: USD cost serialized as an exact decimal string.
        prompt_tokens: { type: integer, minimum: 0 }
        completion_tokens: { type: integer, minimum: 0 }
        total_tokens: { type: integer, minimum: 0 }
        cached_tokens: { type: integer, minimum: 0 }
        cache_creation_tokens: { type: integer, minimum: 0 }
        created_at: { type: string, format: date-time }
        api_key_prefix: { type: string }
        error_message: { type: string, description: Sanitized failure message when recorded. }
        error_type: { type: string }
        completed_at: { type: string, format: date-time }
        period_settled_cost:
          type: string
          pattern: '^-?\d+\.\d{6}$'
          description: Settlement cost attributed to the selected settlement window when available.
        input_params:
          description: Parsed stored request parameters; omitted when absent or invalid JSON.
        output_result:
          description: Parsed stored output; numbers retain their JSON precision. Omitted when absent or invalid JSON.

    # --- Tasks ----------------------------------------------------
    RunRequest:
      type: object
      required: [model]
      description: Capability-specific request. Resolve the selected model's live input schema before sending fields.
      properties:
        model:
          type: string
          description: Current model or API capability identifier from the AGRouter catalog.
        mode:
          type: string
          enum: [auto, sync, async, stream]
          default: auto
          description: Execution mode when supported by the selected capability. Auto uses the capability's configured default.
        stream:
          type: boolean
          default: false
          description: Requests streaming output. Equivalent to mode stream and valid only for a capability that supports streaming.
        webhook_url:
          type: string
          format: uri
          description: Optional public HTTPS callback URL for asynchronous image, video, or audio tasks. It is validated only for those tasks and ignored for other execution types.
      additionalProperties: true

    RunResponse:
      type: object
      required: [id, status, model]
      properties:
        id:
          type: string
          description: Opaque execution identifier. Use exactly as returned; no prefix is guaranteed.
        model: { type: string }
        status:
          type: string
          enum: [pending, running, completed, failed, timeout]
          description: A stored completed task can temporarily be reported as running while output transfer is still pending.
        outputs:
          type: array
          description: Extracted generated outputs when available. Omitted from non-terminal responses and also from completed responses whose output is empty, malformed, unsupported, or not yet transferred.
          items:
            type: object
            properties:
              url: { type: string, description: 'Provider output string, commonly an HTTP URL or data URI; syntax is not revalidated by this projection.' }
              content_type: { type: string }
            additionalProperties: true
        error:
          type: object
          description: Structured sanitized error for failed or timed-out runs when the stored error message is non-empty; otherwise omitted.
          required: [type, message]
          properties:
            type: { type: string }
            message: { type: string }
        usage:
          type: object
          additionalProperties: true
          description: Model-specific public usage details when available; omitted for models that do not report them.

    UploadMediaRequest:
      type: object
      required: [file]
      properties:
        file:
          type: string
          format: binary
          description: Image (up to 20 MB), audio (up to 50 MB), or video (up to 500 MB).
        type:
          type: string
          enum: [image, audio, video]
          description: Optional category hint. The server still uses the detected MIME type for validation and storage.

    UploadMediaResponse:
      type: object
      required: [url, filename, size, type, content_type]
      properties:
        url:
          type: string
          description: Stored media URL to pass to a supported model input.
        filename:
          type: string
          description: Original multipart filename.
        size:
          type: integer
          minimum: 0
          description: Uploaded file size in bytes.
        type:
          type: string
          description: Detected MIME type. This currently matches content_type.
        content_type:
          type: string
          description: Detected MIME type used when storing the file.

    CreateAssetRequest:
      type: object
      required: [url, asset_type]
      properties:
        url:
          type: string
          minLength: 1
          description: Source URL forwarded to the asset provider. AGRouter requires a non-empty string; the provider must be able to fetch it.
        asset_type:
          type: string
          enum: [Image, Video, Audio]
        name: { type: string }

    CreateAssetResponse:
      type: object
      required: [id, asset_url, asset_type, created_at]
      properties:
        id: { type: string }
        asset_url:
          type: string
          description: Persistent asset:// reference used in generation requests.
        asset_type:
          type: string
          enum: [Image, Video, Audio]
        name: { type: string }
        created_at: { type: string, format: date-time }

    GetAssetResponse:
      type: object
      required: [id, external_id, asset_url, status, asset_type, download_url, created_at]
      properties:
        id: { type: string }
        external_id: { type: string }
        asset_url:
          type: string
          description: Persistent asset:// reference used in generation requests.
        asset_type:
          type: string
          enum: [Image, Video, Audio]
        name: { type: string }
        status:
          type: string
          description: Provider-reported asset status, returned without normalization.
        download_url:
          type: string
          description: Provider-returned download URL. It can be empty until the provider makes a URL available.
        created_at: { type: string, format: date-time }

    TaskCostResponse:
      type: object
      required: [id, status, settled, currency, cost, estimated_cost, usage]
      properties:
        id:
          type: string
          description: AGRouter task ID.
        status:
          type: string
          enum: [pending, running, completed, failed, timeout]
        settled:
          type: boolean
          description: Whether final cost settlement has completed.
        currency:
          type: string
          enum: [USD]
        cost:
          type: string
          pattern: '^\d+\.\d{6}$'
          description: USD amount with six decimal places. Not final while settled is false.
          example: '0.001234'
        estimated_cost:
          type: string
          pattern: '^\d+\.\d{6}$'
          description: Best-effort USD estimate with six decimal places. Informational while settled is false; matches cost once settled is true.
          example: '0.001234'
        usage:
          $ref: '#/components/schemas/TaskCostUsage'

    TaskCostUsage:
      type: object
      required: [prompt_tokens, completion_tokens, total_tokens, cached_tokens, cache_creation_tokens, reasoning_tokens]
      properties:
        prompt_tokens:
          type: integer
          minimum: 0
        completion_tokens:
          type: integer
          minimum: 0
        total_tokens:
          type: integer
          minimum: 0
        cached_tokens:
          type: integer
          minimum: 0
        cache_creation_tokens:
          type: integer
          minimum: 0
        reasoning_tokens:
          type: integer
          minimum: 0

    TaskCostError:
      type: object
      required: [error]
      properties:
        error:
          type: string

    APIPassthroughResponse:
      description: Capability-specific response. Its shape is defined by the selected live catalog capability and may be an object, array, or scalar.

    APIPassthroughError:
      description: Route-resolution, authentication, admission, or unified-pipeline error.
      anyOf:
        - $ref: '#/components/schemas/TaskCostError'
        - $ref: '#/components/schemas/APIKeyScopeError'
        - type: object
          required: [error, message]
          properties:
            error: { type: string }
            message: { type: string }

    # --- Models ---------------------------------------------------
    ModelListResponse:
      type: object
      required: [object, data]
      properties:
        object: { type: string, const: list }
        data:
          type: array
          items:
            $ref: '#/components/schemas/OpenAIModelListItem'

    OpenAIModelListItem:
      type: object
      required: [id, object, created, owned_by]
      properties:
        id:
          type: string
          description: Logical model name used in inference requests.
        object: { type: string, const: model }
        created: { type: integer, description: Unix creation timestamp. }
        owned_by: { type: string, description: Representative enabled provider or agrouter. }

    ModelDetail:
      type: object
      description: Detailed model response. Pricing is returned only in the optional model_card object.
      required: [id, name, display_name, vendor, description, type, capability_tags, skills, unified_schema, execution_mode, supported_modes, run_count, sort_order, published_at, vendor_slug, model_slug, enabled, featured, created_at, examples]
      properties:
        id:
          type: string
          format: uuid
          description: Internal model resource ID. Use name in inference requests.
        name:
          type: string
          example: openai/gpt-5.6-luna
        display_name:
          type: string
          example: GPT-5.6 Luna
        vendor:
          type: string
        description:
          type: string
        type:
          type: string
        capability_tags:
          type: [array, 'null']
          items:
            type: string
        skills:
          type: array
          description: String capability tags derived from capability_tags; empty when tags are absent or invalid.
          items:
            type: string
        unified_schema:
          type: [object, 'null']
          additionalProperties: true
        execution_mode:
          type: string
        supported_modes:
          type: [array, 'null']
          items:
            type: string
        run_count:
          type: integer
        sort_order:
          type: integer
        published_at:
          type: [string, 'null']
          format: date-time
        vendor_slug:
          type: string
        model_slug:
          type: string
        enabled:
          type: boolean
        featured:
          type: boolean
        created_at:
          type: string
          format: date-time
        model_card:
          $ref: '#/components/schemas/ModelCard'
        examples:
          type: array
          items:
            $ref: '#/components/schemas/ModelExample'

    ModelExample:
      type: object
      required: [id, is_default, created_at]
      properties:
        id: { type: string }
        is_default: { type: boolean }
        created_at: { type: string, format: date-time }
        input_snapshot:
          description: Parsed JSON input snapshot. Omitted when absent or invalid.
        output_snapshot:
          description: Parsed JSON output snapshot. Omitted when absent or invalid.

    ModelCard:
      type: object
      description: Model pricing and presentation data.
      required: [base_price, prompt_token_price, completion_token_price, cache_read_multiplier, cache_write_multiplier, cache_write_1h_multiplier, reasoning_price, price_formula, markup_ratio, readme, cover_url]
      properties:
        base_price:
          oneOf:
            - type: string
            - type: number
            - type: 'null'
        prompt_token_price:
          oneOf:
            - type: string
            - type: number
            - type: 'null'
        completion_token_price:
          oneOf:
            - type: string
            - type: number
            - type: 'null'
        cache_read_multiplier:
          oneOf:
            - type: string
            - type: number
            - type: 'null'
        cache_write_multiplier:
          oneOf:
            - type: string
            - type: number
            - type: 'null'
        cache_write_1h_multiplier:
          oneOf:
            - type: string
            - type: number
            - type: 'null'
        reasoning_price:
          oneOf:
            - type: string
            - type: number
            - type: 'null'
        price_formula:
          type: string
        markup_ratio:
          oneOf:
            - type: string
            - type: number
            - type: 'null'
        readme:
          type: string
        cover_url:
          type: string

    # --- Responses ------------------------------------------------
    ResponsesRequest:
      type: object
      required: [model]
      additionalProperties: true
      description: OpenAI-compatible request. AGRouter rewrites model for the selected provider and preserves other fields.
      properties:
        model:
          type: string
          description: Model identifier from the Models API.
        input:
          description: Text, message items, or other input supported by the selected model and provider.
        instructions:
          type: string
        stream:
          type: boolean
          default: false
          description: Return sanitized OpenAI Responses Server-Sent Events.
        background:
          type: boolean
        include:
          type: array
          items: { type: string }
        max_output_tokens:
          type: integer
          minimum: 1
        max_tool_calls:
          type: integer
          minimum: 1
        metadata:
          type: object
          additionalProperties: true
        parallel_tool_calls:
          type: boolean
        previous_response_id:
          type: string
        reasoning:
          type: object
          additionalProperties: true
        service_tier:
          type: string
        store:
          type: boolean
        temperature:
          type: number
        text:
          type: object
          additionalProperties: true
        tool_choice:
          oneOf:
            - type: string
            - type: object
              additionalProperties: true
        tools:
          type: array
          items:
            type: object
            additionalProperties: true
        top_logprobs:
          type: integer
        top_p:
          type: number
        truncation:
          type: string
        user:
          type: string

    ResponsesResponse:
      type: object
      additionalProperties: false
      description: >-
        Public OpenAI Responses fields retained by AGRouter. Provider-specific top-level fields are removed;
        output items remain provider-compatible free-form objects.
      properties:
        id: { type: string }
        object: { type: string }
        created_at: { type: integer }
        status: { type: string }
        error:
          type: [object, 'null']
          additionalProperties: true
        incomplete_details:
          type: [object, 'null']
          additionalProperties: true
        instructions:
          type: [string, 'null']
        max_output_tokens: { type: [integer, 'null'] }
        max_tool_calls: { type: [integer, 'null'] }
        model: { type: string }
        output:
          type: array
          items:
            type: object
            additionalProperties: true
        parallel_tool_calls: { type: boolean }
        previous_response_id: { type: [string, 'null'] }
        reasoning:
          type: [object, 'null']
          additionalProperties: true
        store: { type: boolean }
        temperature: { type: [number, 'null'] }
        text:
          type: object
          additionalProperties: true
        tool_choice: {}
        tools:
          type: array
          items:
            type: object
            additionalProperties: true
        top_logprobs: { type: integer }
        top_p: { type: [number, 'null'] }
        truncation: { type: string }
        usage:
          type: object
          additionalProperties: false
          required: [input_tokens, output_tokens, total_tokens, input_tokens_details, output_tokens_details]
          properties:
            input_tokens: { type: integer, minimum: 0 }
            output_tokens: { type: integer, minimum: 0 }
            total_tokens: { type: integer, minimum: 0 }
            input_tokens_details:
              type: object
              additionalProperties: false
              required: [cached_tokens]
              properties:
                cached_tokens: { type: integer, minimum: 0 }
            output_tokens_details:
              type: object
              additionalProperties: false
              required: [reasoning_tokens]
              properties:
                reasoning_tokens: { type: integer, minimum: 0 }
        metadata:
          type: object
          additionalProperties: true
        service_tier: { type: string }
        user: { type: string }
        background: { type: boolean }

    # --- Chat Completions -----------------------------------------
    ChatCompletionRequest:
      type: object
      required: [model, messages]
      additionalProperties: true
      description: OpenAI-compatible request. Additional provider-compatible fields are forwarded when supported by the selected route.
      properties:
        model:
          type: string
          description: Model identifier
          example: "openai/gpt-5.4"
        messages:
          type: array
          items:
            type: object
            required: [role]
            additionalProperties: true
            properties:
              role:
                type: string
                description: Message role supported by the selected provider, such as system, developer, user, assistant, or tool.
              content:
                oneOf:
                  - type: string
                  - type: array
                    items:
                      type: object
                      additionalProperties: true
                  - type: 'null'
              name:
                type: string
              tool_calls:
                type: array
              tool_call_id:
                type: string
        temperature:
          type: number
          minimum: 0
          maximum: 2
          default: 1
        top_p:
          type: number
        max_tokens:
          type: integer
        stream:
          type: boolean
          default: false
        stop:
          oneOf:
            - type: string
            - type: array
              items:
                type: string
        tools:
          type: array
          items:
            type: object
        mcp_servers:
          type: array
          maxItems: 20
          items: { $ref: '#/components/schemas/MCPServer' }
        tool_choice:
          oneOf:
            - type: string
            - type: object
        parallel_tool_calls:
          type: boolean
        user:
          type: string
        response_format:
          type: object
        reasoning_effort:
          type: string
          description: Provider-compatible reasoning effort setting.
        reasoning:
          type: object
          additionalProperties: true
          description: Reasoning configuration; common fields include effort and max_tokens.
        thinking:
          type: object
          additionalProperties: true
          description: Thinking configuration; common fields include type and budget_tokens.
        extra_body:
          type: object
          additionalProperties: true
          description: Provider-specific parameters used when AGRouter translates the request between protocols.
        n:
          type: integer
          default: 1
        presence_penalty:
          type: number
        frequency_penalty:
          type: number
        stream_options:
          type: object
          properties:
            include_usage:
              type: boolean

    ChatCompletionResponse:
      type: object
      required: [id, object, created, model, choices, usage]
      additionalProperties: true
      properties:
        id:
          type: string
        object:
          type: string
          enum: [chat.completion]
        created:
          type: integer
        model:
          type: string
        choices:
          type: array
          items:
            type: object
            required: [index, message, finish_reason, logprobs]
            additionalProperties: true
            properties:
              index:
                type: integer
              message:
                type: object
                required: [role, content]
                additionalProperties: true
                properties:
                  role:
                    type: string
                  content:
                    type: [string, 'null']
                  tool_calls:
                    type: array
                  reasoning_content:
                    type: [string, 'null']
              finish_reason:
                type: [string, 'null']
                description: Provider finish reason. Values can vary by model and provider.
              logprobs:
                description: Token log probabilities when requested and supported; otherwise null.
        usage:
          type: object
          required: [prompt_tokens, completion_tokens, total_tokens, prompt_tokens_details, completion_tokens_details]
          additionalProperties: true
          properties:
            prompt_tokens:
              type: integer
            completion_tokens:
              type: integer
            total_tokens:
              type: integer
            prompt_tokens_details:
              type: object
              required: [cached_tokens, cache_write_tokens]
              properties:
                cached_tokens: { type: integer }
                cache_write_tokens: { type: integer }
            completion_tokens_details:
              type: object
              required: [reasoning_tokens]
              properties:
                reasoning_tokens: { type: integer }

    ChatCompletionError:
      type: object
      required: [error]
      properties:
        error:
          type: object
          required: [message, type, code, param]
          properties:
            message: { type: string }
            type: { type: string }
            code: { type: ['null', string] }
            param: { type: ['null', string] }

    AnthropicMessageResponse:
      type: object
      additionalProperties: true
      description: Anthropic-compatible response forwarded from the selected provider. Fields can vary with beta features.
      properties:
        id: { type: string }
        type: { type: string, const: message }
        role: { type: string, const: assistant }
        model: { type: string }
        content:
          type: array
          items:
            type: object
            additionalProperties: true
        stop_reason:
          type: [string, 'null']
        stop_sequence:
          type: [string, 'null']
        usage:
          type: object
          additionalProperties: true
          properties:
            input_tokens: { type: integer, minimum: 0 }
            output_tokens: { type: integer, minimum: 0 }
            cache_read_input_tokens: { type: integer, minimum: 0 }
            cache_creation_input_tokens: { type: integer, minimum: 0 }
            cache_creation:
              type: object
              additionalProperties: true
              properties:
                ephemeral_5m_input_tokens: { type: integer, minimum: 0 }
                ephemeral_1h_input_tokens: { type: integer, minimum: 0 }

    AnthropicError:
      type: object
      required: [type, error]
      properties:
        type: { type: string, const: error }
        error:
          type: object
          required: [type, message]
          additionalProperties: true
          properties:
            type:
              type: string
              description: Anthropic error type, such as invalid_request_error, authentication_error, permission_error, request_too_large, rate_limit_error, or api_error.
            message: { type: string }

    GeminiGenerateContentRequest:
      type: object
      additionalProperties: true
      description: Google Gemini GenerateContent request. Native passthrough mappings preserve provider-defined fields; translated mappings use the documented capability subset.
      properties:
        contents:
          type: array
          items: { $ref: '#/components/schemas/GeminiContent' }
        systemInstruction:
          $ref: '#/components/schemas/GeminiContent'
        generationConfig:
          type: object
          additionalProperties: true
          properties:
            temperature: { type: number }
            topP: { type: number }
            topK: { type: integer }
            maxOutputTokens: { type: integer }
            stopSequences: { type: array, items: { type: string } }
            candidateCount: { type: integer, description: Translated mappings accept only 1 when supplied; native passthrough mappings preserve the provider request. }
            seed: { type: integer }
            presencePenalty: { type: number }
            frequencyPenalty: { type: number }
            responseMimeType: { type: string, enum: [text/plain, application/json, text/x.enum] }
            responseSchema: { type: object, additionalProperties: true }
            responseModalities:
              type: array
              items: { type: string }
              description: Native image mappings support provider image modalities. Other translated mappings may support TEXT only.
            thinkingConfig: { type: object, additionalProperties: true }
        tools:
          type: array
          items: { type: object, additionalProperties: true }
        toolConfig: { type: object, additionalProperties: true }
        safetySettings:
          type: array
          items: { type: object, additionalProperties: true }
        cachedContent:
          type: string
          description: Passed through when the selected provider supports Gemini context caching.

    GeminiContent:
      type: object
      additionalProperties: true
      properties:
        role: { type: string, enum: [user, model] }
        parts:
          type: array
          items:
            type: object
            additionalProperties: true
            description: Text, image inlineData, public HTTP(S) image fileData, functionCall, or functionResponse part.

    GeminiGenerateContentResponse:
      type: object
      additionalProperties: true
      properties:
        candidates:
          type: array
          items:
            type: object
            additionalProperties: true
            properties:
              content: { $ref: '#/components/schemas/GeminiContent' }
              finishReason: { type: string }
              index: { type: integer }
              safetyRatings: { type: array, items: { type: object, additionalProperties: true } }
        usageMetadata:
          type: object
          additionalProperties: true
          properties:
            promptTokenCount: { type: integer, minimum: 0 }
            candidatesTokenCount: { type: integer, minimum: 0 }
            totalTokenCount: { type: integer, minimum: 0 }
            cachedContentTokenCount: { type: integer, minimum: 0 }
            thoughtsTokenCount: { type: integer, minimum: 0 }
        promptFeedback: { type: object, additionalProperties: true }
        modelVersion: { type: string }
        responseId: { type: string }

    GeminiInteractionRequest:
      type: object
      required: [model, input]
      additionalProperties: true
      description: |
        Google Interactions request. Except for fields used for routing and lifecycle control, fields are forwarded to
        the selected upstream. Agent interactions are not supported. background and stream cannot both be true.
      properties:
        model:
          type: string
          minLength: 1
          description: Logical AGRouter model name. The response echoes this name instead of an upstream model identifier.
        input:
          description: Google Interactions input; it can be text, an object, or an array of typed input items.
        stream:
          type: boolean
          default: false
          description: Requests an SSE event stream when the selected model supports it.
        background:
          type: boolean
          default: false
          description: Requests durable background execution and an immediately pollable in_progress Interaction.
        previous_interaction_id:
          type: string
          description: Previous Interaction ID. AGRouter keeps the request on the same upstream provider when the mapping is still available.
        store:
          type: boolean
          description: Forwarded upstream. Stored upstream state may be required for previous_interaction_id.
        system_instruction: { type: string }
        tools: { type: array, items: { type: object, additionalProperties: true } }
        generation_config: { type: object, additionalProperties: true }
        response_format:
          oneOf:
            - type: object
              additionalProperties: true
            - type: array
              items: {}
        response_modalities: { type: array, items: { type: string } }

    GeminiInteraction:
      type: object
      required: [id, object, model, status]
      additionalProperties: true
      properties:
        id:
          type: string
          description: Upstream Interaction ID used by GET and previous_interaction_id.
        object: { type: string, const: interaction }
        model:
          type: string
          description: Logical AGRouter model name supplied by the client.
        role: { type: string }
        status:
          type: string
          enum: [in_progress, requires_action, completed, failed, cancelled, incomplete]
        created: { type: string }
        updated: { type: string }
        steps:
          type: array
          description: Polymorphic Google Interaction steps. Unknown step and content types are preserved.
          items: { type: object, additionalProperties: true }
        usage:
          $ref: '#/components/schemas/GeminiInteractionUsage'

    GeminiInteractionUsage:
      type: object
      additionalProperties: true
      properties:
        total_input_tokens: { type: integer, minimum: 0 }
        total_output_tokens: { type: integer, minimum: 0 }
        total_cached_tokens: { type: integer, minimum: 0 }
        total_thought_tokens: { type: integer, minimum: 0 }
        total_tool_use_tokens: { type: integer, minimum: 0 }
        total_tokens: { type: integer, minimum: 0 }
        input_tokens_by_modality:
          type: array
          items: { $ref: '#/components/schemas/GeminiInteractionModalityUsage' }
        output_tokens_by_modality:
          type: array
          items: { $ref: '#/components/schemas/GeminiInteractionModalityUsage' }

    GeminiInteractionModalityUsage:
      type: object
      required: [modality, tokens]
      additionalProperties: true
      properties:
        modality: { type: string }
        tokens: { type: integer, minimum: 0 }

    GeminiError:
      type: object
      required: [error]
      properties:
        error:
          type: object
          required: [code, message, status]
          additionalProperties: true
          properties:
            code: { type: integer }
            message: { type: string }
            status:
              type: string
              description: Google canonical error status, such as INVALID_ARGUMENT, UNAUTHENTICATED, PERMISSION_DENIED, NOT_FOUND, RESOURCE_EXHAUSTED, INTERNAL, UNAVAILABLE, or DEADLINE_EXCEEDED.

    VolcengineContentsGenerationRequest:
      type: object
      required: [model, content]
      additionalProperties: true
      properties:
        model:
          type: string
          enum: [doubao-seedance-2-0-260128, doubao-seedance-2-0-fast-260128, doubao-seedance-2-5-260628]
        content:
          type: array
          minItems: 1
          description: Must include text. Media items select image-to-video or reference-to-video mode according to role.
          items: { $ref: '#/components/schemas/VolcengineContentItem' }
        callback_url: { type: string, minLength: 1, description: Receives the terminal task response. }
        duration: { type: integer }
        resolution: { type: string }
        ratio: { type: string }
        generate_audio: { type: boolean }
        seed: { type: integer }
        camera_fixed: { type: boolean }
        watermark: { type: boolean }
        draft: { type: boolean }
        return_last_frame: { type: boolean }
        service_tier: { type: string }
        execution_expires_after: { type: integer }
        frames: { type: integer }
        framespersecond: { type: integer }
        tools: { type: array, items: { type: object, additionalProperties: true } }

    VolcengineContentItem:
      type: object
      required: [type]
      additionalProperties: true
      properties:
        type: { type: string, enum: [text, image_url, video_url, audio_url] }
        text: { type: string }
        image_url:
          oneOf:
            - { type: string }
            - { type: object, required: [url], properties: { url: { type: string } } }
        video_url:
          oneOf:
            - { type: string }
            - { type: object, required: [url], properties: { url: { type: string } } }
        audio_url:
          oneOf:
            - { type: string }
            - { type: object, required: [url], properties: { url: { type: string } } }
        role: { type: string, enum: [first_frame, last_frame, reference_image, reference_video, reference_audio] }

    VolcengineTaskCreated:
      type: object
      required: [id]
      properties:
        id: { type: string }

    VolcengineTask:
      type: object
      required: [id, model, status, created_at, updated_at, content, usage]
      additionalProperties: true
      properties:
        id: { type: string }
        model: { type: string }
        status: { type: string, enum: [queued, running, cancelled, succeeded, failed, expired] }
        created_at: { type: integer, format: int64, description: Unix timestamp in seconds. }
        updated_at: { type: integer, format: int64, description: Unix timestamp in seconds. }
        content:
          type: object
          properties:
            video_url: { type: string }
            last_frame_url: { type: string }
            file_url: { type: string }
        usage:
          type: object
          required: [prompt_tokens, completion_tokens, total_tokens, prompt_tokens_details, completion_tokens_details]
          properties:
            prompt_tokens: { type: integer, minimum: 0 }
            completion_tokens: { type: integer, minimum: 0 }
            total_tokens: { type: integer, minimum: 0 }
            prompt_tokens_details: { type: object, additionalProperties: true }
            completion_tokens_details: { type: object, additionalProperties: true }
        error: { $ref: '#/components/schemas/VolcengineTaskFailure' }
        failure_reason: { $ref: '#/components/schemas/VolcengineTaskFailure' }

    VolcengineTaskFailure:
      type: object
      required: [code, message]
      properties:
        code: { type: string }
        message: { type: string }

    VolcengineTaskList:
      type: object
      required: [items, total]
      properties:
        items: { type: array, items: { $ref: '#/components/schemas/VolcengineTask' } }
        total: { type: integer, minimum: 0 }

    VolcengineError:
      type: object
      required: [error]
      properties:
        error:
          type: object
          required: [code, message, type]
          properties:
            code: { type: string, description: 'InvalidParameter, AuthenticationError, PermissionDenied, ResourceNotFound, RateLimitExceeded, or InternalServiceError.' }
            message: { type: string }
            type: { type: string, description: HTTP-style Volcengine error type. }

    # --- Embeddings -----------------------------------------------
    EmbeddingRequest:
      type: object
      required: [model, input]
      additionalProperties: true
      description: OpenAI-compatible request. Additional provider-compatible fields are forwarded unchanged.
      properties:
        model:
          type: string
          description: Embedding model identifier.
          example: "alibaba/text-embedding-v4"
        input:
          description: Text, token IDs, or a batch of texts or token-ID arrays to embed. Provider limits apply.
          oneOf:
            - type: string
              example: "AGRouter provides a unified API for AI models, agents, and developer tools."
            - type: array
              items:
                type: string
              example:
                - "First document"
                - "Second document"
            - type: array
              items:
                type: integer
            - type: array
              items:
                type: array
                items:
                  type: integer
        dimensions:
          type: integer
          minimum: 1
          description: Optional output dimension when supported by the selected model. AGRouter does not impose a universal dimension list.
          example: 1024
        encoding_format:
          type: string
          description: Requested embedding encoding. Support is provider- and model-specific.
          enum: [float, base64]
          default: float

    EmbeddingResponse:
      type: object
      required: [object, data, model, usage]
      additionalProperties: true
      properties:
        object:
          type: string
          enum: [list]
        data:
          type: array
          items:
            type: object
            required: [object, index, embedding]
            additionalProperties: true
            properties:
              object:
                type: string
                enum: [embedding]
              index:
                type: integer
              embedding:
                oneOf:
                  - type: array
                    items:
                      type: number
                      format: float
                  - type: string
                    description: Base64-encoded vector when encoding_format is base64.
        model:
          type: string
          example: "alibaba/text-embedding-v4"
        usage:
          type: object
          required: [prompt_tokens, total_tokens]
          additionalProperties: true
          properties:
            prompt_tokens:
              type: integer
            total_tokens:
              type: integer

    EmbeddingError:
      type: object
      required: [error]
      properties:
        error:
          type: object
          required: [message, type, code, param]
          properties:
            message: { type: string }
            type: { type: string }
            code: { type: ['null', string] }
            param: { type: ['null', string] }

    # --- OpenAI-compatible Images --------------------------------
    ImageGenerationRequest:
      type: object
      required: [model, prompt]
      additionalProperties: true
      properties:
        model:
          type: string
          enum: [gpt-image-2, gpt-image-2.5-flare, gpt-image-2.5-sunburst]
        prompt:
          type: string
          minLength: 1
        n:
          type: integer
          minimum: 1
        size:
          type: string
          description: Output size supported by the selected model, such as 1024x1024, 1024x1536, 1536x1024, or auto.
        quality:
          type: string
          description: Output quality supported by the selected model, such as low, medium, high, or auto.
        background:
          type: string
          description: Background setting such as transparent, opaque, or auto.
        output_format:
          type: string
          description: Output encoding such as png, webp, or jpeg.
        output_compression:
          type: integer
          minimum: 0
          maximum: 100
        moderation:
          type: string
        user:
          type: string
        stream:
          type: boolean
          const: false
          default: false
          description: Streaming is not supported by this AGRouter endpoint.
        mode:
          type: string
          const: sync
          default: sync

    ImageEditRequest:
      type: object
      required: [model, prompt, image]
      additionalProperties: true
      properties:
        model:
          type: string
          enum: [gpt-image-2, gpt-image-2.5-flare, gpt-image-2.5-sunburst]
        prompt:
          type: string
          minLength: 1
        image:
          oneOf:
            - type: string
              format: binary
            - type: array
              items:
                type: string
                format: binary
          description: Source image file. Repeat the multipart field to provide multiple images.
        mask:
          type: string
          format: binary
        n:
          type: integer
          minimum: 1
        size:
          type: string
        quality:
          type: string
        background:
          type: string
        input_fidelity:
          type: string
        output_format:
          type: string
        output_compression:
          type: integer
          minimum: 0
          maximum: 100
        user:
          type: string
        stream:
          type: boolean
          const: false
          default: false
          description: Streaming is not supported by this AGRouter endpoint.
        mode:
          type: string
          const: sync
          default: sync

    ImagesResponse:
      type: object
      required: [data, usage]
      additionalProperties: true
      properties:
        created:
          type: integer
        data:
          type: array
          minItems: 1
          items:
            type: object
            additionalProperties: true
            anyOf:
              - required: [b64_json]
              - required: [url]
            properties:
              b64_json:
                type: string
                description: Base64-encoded image data.
              url:
                type: string
                format: uri
              revised_prompt:
                type: string
        usage:
          type: object
          required: [input_tokens, output_tokens, total_tokens, input_tokens_details]
          additionalProperties: true
          properties:
            input_tokens:
              type: integer
              minimum: 0
            output_tokens:
              type: integer
              minimum: 0
            total_tokens:
              type: integer
              minimum: 0
            input_tokens_details:
              type: object
              required: [text_tokens, image_tokens]
              additionalProperties: true
              properties:
                text_tokens: { type: integer, minimum: 0 }
                image_tokens: { type: integer, minimum: 0 }
                cached_text_tokens: { type: integer, minimum: 0 }
                cached_image_tokens: { type: integer, minimum: 0 }
                cached_tokens: { type: integer, minimum: 0 }

    OpenAIImagesError:
      type: object
      required: [error]
      properties:
        error:
          type: object
          required: [message, type, code, param]
          properties:
            message: { type: string }
            type: { type: string }
            code: { type: ['null', string] }
            param: { type: ['null', string] }

    APIKeyScopeError:
      type: object
      required: [error]
      properties:
        error:
          type: object
          required: [code, message, request_id]
          properties:
            code: { type: string, const: insufficient_scope }
            message: { type: string }
            request_id: { type: string }

    LegacyResourceError:
      type: object
      required: [code, message]
      properties:
        code: { type: integer }
        message: { type: string }

    # --- Services (Endpoint resources) -----------------------------
    CreateDeclarativeEndpointRequest:
      type: object
      additionalProperties: false
      required: [name, runtime, protocols]
      properties:
        name:
          type: string
          maxLength: 200
        runtime:
          type: string
          description: Enabled runtime profile. This release supports hermes.
          example: hermes
        description:
          type: string
        model:
          oneOf:
            - type: string
            - type: object
              additionalProperties: true
        system:
          type: string
        skills:
          type: [array, 'null']
          maxItems: 20
          description: |
            Full vendor_slug/plugin_slug references. Hermes runtime identity and installation lookup use each
            bundle-root SKILL.md frontmatter name; the plugin slug and runtime name may differ.
          items:
            type: string
            pattern: '^[A-Za-z0-9][A-Za-z0-9._-]*/[A-Za-z0-9][A-Za-z0-9._-]*$'
        tools:
          type: [array, 'null']
          maxItems: 1
          items:
            $ref: '#/components/schemas/EndpointToolset'
        protocols:
          type: array
          minItems: 1
          uniqueItems: true
          description: Explicit public invocation transports. Always send this field; public clients may enable REST, MCP, experimental ACP, or any supported combination.
          items:
            type: string
            enum: [rest, mcp, acp]

    CreateAdvancedEndpointRequest:
      type: object
      additionalProperties: false
      required: [name, agent_id, protocols]
      properties:
        name:
          type: string
        slug:
          type: string
        agent_id:
          type: string
        agent_version:
          type: integer
          minimum: 1
        protocols:
          type: array
          minItems: 1
          uniqueItems: true
          description: Explicit public invocation transports. Always send this field; public clients may enable REST, experimental ACP, or both.
          items:
            type: string
            enum: [rest, mcp, acp]
        config:
          type: object
          additionalProperties: true
        session_metadata:
          type: object
          additionalProperties: true
          description: Metadata applied when an invocation creates a new persistent Session.
        memory_config:
          type: object
          additionalProperties: true
          description: Reserved configuration that is stored with the Service but is not currently applied to Session execution.
        resource_config:
          type: object
          additionalProperties: true
          description: Reserved configuration that is stored with the Service but is not currently applied to Session execution.
        vault_config:
          type: object
          additionalProperties: true
          description: Reserved configuration that is stored with the Service but is not currently applied to Session execution.

    EndpointToolset:
      type: object
      additionalProperties: false
      required: [type]
      properties:
        type:
          type: string
          const: agent_toolset_20260401

    UpdateEndpointRequest:
      type: object
      minProperties: 1
      properties:
        name:
          type: string
        slug:
          type: string
        agent_id:
          type: string
        agent_version:
          type: integer
          minimum: 1
        expected_agent_version:
          type: integer
          minimum: 1
          description: Current bound version required when upgrading agent_version.
        status:
          type: string
          enum: [active, disabled]
        protocols:
          type: array
          minItems: 1
          uniqueItems: true
          items:
            type: string
            enum: [rest, mcp, acp]
        config: { type: object, additionalProperties: true }
        session_metadata:
          type: object
          additionalProperties: true
          description: Metadata applied when a later invocation creates a new persistent Session.
        memory_config:
          type: object
          additionalProperties: true
          description: Reserved configuration that is stored with the Service but is not currently applied to Session execution.
        resource_config:
          type: object
          additionalProperties: true
          description: Reserved configuration that is stored with the Service but is not currently applied to Session execution.
        vault_config:
          type: object
          additionalProperties: true
          description: Reserved configuration that is stored with the Service but is not currently applied to Session execution.

    Endpoint:
      type: object
      required: [id, type, name, slug, agent_id, agent_version, protocols, config, session_metadata, memory_config, resource_config, vault_config, status, store_status, creation_mode, run_url, acp_url, created_at, updated_at]
      properties:
        id:
          type: string
          example: ep_01abc
        type:
          type: string
          const: endpoint
        name:
          type: string
        slug:
          type: string
        agent_id:
          type: string
        agent_version:
          type: [integer, 'null']
        protocols:
          type: array
          description: Enabled transport identifiers. Public values are rest, mcp, and experimental acp; other returned values are reserved.
          items:
            type: string
        config:
          type: [object, 'null']
          additionalProperties: true
          description: Advanced Service configuration. Declarative Service responses always return null here.
        session_metadata:
          type: [object, 'null']
          additionalProperties: true
          description: Metadata applied when an invocation creates a new persistent Session.
        memory_config:
          type: [object, 'null']
          additionalProperties: true
          description: Reserved configuration stored with the Service; it is not currently applied to Session execution.
        resource_config:
          type: [object, 'null']
          additionalProperties: true
          description: Reserved configuration stored with the Service; it is not currently applied to Session execution.
        vault_config:
          type: [object, 'null']
          additionalProperties: true
          description: Reserved configuration stored with the Service; it is not currently applied to Session execution.
        status:
          type: string
          enum: [active, disabled]
        store_status:
          type: string
          enum: [private, pending_review, public, suspended]
          description: Publication and review state of the Service in the Service store.
        creation_mode:
          type: string
          enum: [declarative, advanced]
        runtime:
          type: string
          description: Present for declarative Services.
        skills:
          type: array
          description: Present for declarative Services.
          items:
            type: string
        run_url:
          type: string
          format: uri
        acp_url:
          type: string
          format: uri
          description: Experimental ACP invocation URL. Its presence does not mean ACP is enabled; inspect protocols first.
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time

    EndpointInvokeRequest:
      type: object
      properties:
        input:
          oneOf:
            - type: string
            - type: array
              items:
                type: object
                additionalProperties: true
            - type: object
              additionalProperties: true
        content:
          oneOf:
            - type: array
              items:
                type: object
                additionalProperties: true
            - type: object
              additionalProperties: true
        session_id:
          type: string
          description: Persistent Session to continue. Omit to create a new Session.
      anyOf:
        - required: [input]
        - required: [content]

    EndpointInvokeAccepted:
      type: object
      description: Acceptance response for standard events sent to a persistent Session.
      required: [session_id, events]
      properties:
        session_id:
          type: string
          description: Persistent public Session identity.
        events:
          type: array
          items:
            $ref: '#/components/schemas/EndpointAcceptedEvent'

    EndpointAcceptedEvent:
      type: object
      required: [id, type, processed_at]
      properties:
        id: { type: string }
        type: { type: string, const: user.message }
        processed_at:
          type: [string, 'null']
          format: date-time
          description: Acceptance time, or null while a native delivery outcome is still unknown.

    APIError:
      type: object
      required: [error]
      properties:
        error:
          type: object
          required: [type, message]
          properties:
            type:
              type: string
            message:
              type: string

    ACPRequest:
      type: object
      required: [method]
      properties:
        jsonrpc:
          type: string
          description: JSON-RPC version supplied by the client. Use 2.0.
          example: '2.0'
        id:
          description: Client request identifier echoed in the response.
          oneOf:
            - type: string
            - type: number
            - type: 'null'
        method:
          type: string
          enum: [initialize, session/new, session/prompt, session/cancel]
        params:
          type: object
          additionalProperties: true
          description: Method-specific parameters. session/prompt and session/cancel require sessionId.
      additionalProperties: true

    ACPResponse:
      type: object
      required: [jsonrpc, id]
      properties:
        jsonrpc: { type: string, const: '2.0' }
        id:
          oneOf:
            - type: string
            - type: number
            - type: 'null'
        result:
          type: object
          additionalProperties: true
          description: Method-specific result, present on success.
        error:
          type: object
          required: [code, message]
          properties:
            code: { type: integer }
            message: { type: string }
            data:
              type: object
              additionalProperties: true
          description: JSON-RPC error, present when the method cannot be completed.
      oneOf:
        - required: [result]
        - required: [error]

    MCPRequest:
      type: object
      required: [method]
      properties:
        jsonrpc: { type: string, const: '2.0' }
        id:
          oneOf:
            - type: string
            - type: number
            - type: 'null'
        method: { type: string, description: MCP method name. }
        params: { type: object, additionalProperties: true }
      additionalProperties: true

    MCPResponse:
      type: object
      required: [jsonrpc]
      properties:
        jsonrpc: { type: string, const: '2.0' }
        id:
          oneOf:
            - type: string
            - type: number
            - type: 'null'
        result: { type: object, additionalProperties: true }
        error:
          type: object
          required: [code, message]
          properties:
            code: { type: integer }
            message: { type: string }
            data: { type: object, additionalProperties: true }
      oneOf:
        - required: [result]
        - required: [error]

    # --- Agents ---------------------------------------------------
    CreateAgentRequest:
      type: object
      additionalProperties: true
      required: [model, name]
      properties:
        model:
          oneOf:
            - type: string
              pattern: '^[A-Za-z0-9][A-Za-z0-9._-]*/[A-Za-z0-9][A-Za-z0-9._-]*$'
              example: "anthropic/claude-sonnet-5"
            - type: object
              additionalProperties: true
              required: [id]
              properties:
                id:
                  type: string
                  pattern: '^[A-Za-z0-9][A-Za-z0-9._-]*/[A-Za-z0-9][A-Za-z0-9._-]*$'
                speed:
                  type: string
                effort:
                  description: Optional model runtime hint preserved with the Agent configuration.
        name:
          type: string
        description:
          type: [string, 'null']
        system:
          type: [string, 'null']
        tools:
          type: [array, 'null']
          items:
            type: object
        mcp_servers:
          type: [array, 'null']
          description: MCP server configurations available to the Agent runtime.
        skills:
          type: [array, 'null']
          items:
            type: object
        handoffs:
          type: [array, 'null']
          description: Agent handoff configurations.
        multiagent:
          type: [object, 'null']
          description: Reserved for Claude compatibility. Omit, send null, or send an empty object; any non-empty object returns unsupported_feature.
        metadata:
          type: [object, 'null']
          additionalProperties: true
          description: Application-defined JSON metadata. The reserved _agrouter namespace is stripped from public requests.

    UpdateAgentRequest:
      type: object
      additionalProperties: true
      properties:
        version:
          type: integer
          minimum: 1
          description: Current version (optimistic lock)
        name:
          type: [string, 'null']
        description:
          type: [string, 'null']
        model:
          oneOf:
            - type: string
              pattern: '^[A-Za-z0-9][A-Za-z0-9._-]*/[A-Za-z0-9][A-Za-z0-9._-]*$'
            - type: object
              additionalProperties: true
              required: [id]
              properties:
                id: { type: string, pattern: '^[A-Za-z0-9][A-Za-z0-9._-]*/[A-Za-z0-9][A-Za-z0-9._-]*$' }
                speed: { type: string }
                effort:
                  description: Optional model runtime hint preserved with the Agent configuration.
        system:
          type: [string, 'null']
        tools:
          type: [array, 'null']
        skills:
          type: [array, 'null']
        mcp_servers:
          type: [array, 'null']
        handoffs:
          type: [array, 'null']
        multiagent:
          type: [object, 'null']
          description: Reserved. Omit, send null, or send an empty object; any non-empty object returns unsupported_feature.
        metadata:
          type: [object, 'null']
          description: Full replacement for caller-owned metadata. The reserved _agrouter namespace cannot be changed through this API.

    Agent:
      type: object
      required: [id, type, name, description, model, system, tools, mcp_servers, skills, handoffs, metadata, version, created_at, updated_at, archived_at]
      properties:
        id:
          type: string
          pattern: '^agent_[0-9a-f]{8}-[0-9a-f]{3}$'
        type:
          type: string
          enum: [agent]
        name:
          type: string
        description:
          type: string
        model:
          type: object
          required: [id]
          properties:
            id: { type: string }
            effort:
              description: Optional model runtime hint preserved from the submitted model object; absent for string model input.
            speed: { type: [string, 'null'], description: Optional model runtime hint preserved from the submitted model object. }
        system:
          type: string
        tools:
          type: [array, 'null']
        mcp_servers:
          type: [array, 'null']
        skills:
          type: [array, 'null']
        handoffs:
          type: [array, 'null']
        metadata:
          type: [object, 'null']
        version:
          type: integer
          minimum: 1
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
        archived_at:
          type: [string, 'null']
          format: date-time

    AgentListPage:
      type: object
      required: [data]
      properties:
        data:
          type: array
          items: { $ref: '#/components/schemas/Agent' }
        next_page:
          type: string
          description: Opaque cursor included only when another page is available.

    MCPServer:
      type: object
      required: [name, type, url]
      properties:
        name: { type: string, minLength: 1, maxLength: 255 }
        type: { type: string, enum: [url] }
        url: { type: string, format: uri }

    # --- Sessions -------------------------------------------------
    CreateSessionRequest:
      type: object
      additionalProperties: false
      required: [agent]
      properties:
        agent:
          oneOf:
            - type: string
              description: Agent ID (uses latest version)
            - type: object
              additionalProperties: false
              required: [type, id]
              properties:
                type:
                  type: string
                  enum: [agent]
                id:
                  type: string
                version:
                  type: integer
                  minimum: 1
        agent_version_id:
          type: string
          description: Immutable AgentVersion resource ID. Takes precedence over agent.version when supplied.
        initial_events:
          type: array
          maxItems: 50
          items:
            $ref: '#/components/schemas/SessionEventInput'
        initial_event:
          type: array
          deprecated: true
          description: AGRouter legacy alias for initial_events; do not combine both fields.
          maxItems: 50
          items:
            $ref: '#/components/schemas/SessionEventInput'
        title:
          type: string
        metadata:
          type: object
        resources:
          type: array
          description: Reserved for Claude compatibility; rejected until resource attachment is supported.
          items: { type: object, additionalProperties: true }
        vault_ids:
          type: array
          description: Reserved for Claude compatibility; rejected until vault attachment is supported.
          items: { type: string }

    Session:
      type: object
      required: [id, type, status, agent, agent_id, agent_version, agent_version_id, source, title, metadata, archived_at, created_at, updated_at]
      properties:
        id:
          type: string
          description: Persistent public Session identity.
        type:
          type: string
          enum: [session]
        agent_id:
          type: string
        agent:
          $ref: '#/components/schemas/SessionAgentProjection'
          readOnly: true
        agent_version:
          type: [integer, 'null']
        agent_version_id:
          type: string
        title:
          type: string
        status:
          type: string
          enum: [idle, running, rescheduling, terminated]
        source:
          type: string
          enum: [direct, endpoint, deployment, store_trial]
          description: Entry point that created the Session. store_trial identifies a Service store trial Session.
        metadata:
          type: [object, 'null']
        archived_at:
          type: [string, 'null']
          format: date-time
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time

    SessionAPIError:
      type: object
      required: [error]
      properties:
        error:
          type: object
          required: [type, message]
          properties:
            type: { type: string }
            message: { type: string }

    SessionAgentProjection:
      description: The immutable pinned Agent snapshot when available; legacy or unavailable snapshots fall back to the minimal identity projection.
      anyOf:
        - $ref: '#/components/schemas/Agent'
        - type: object
          required: [id, type, version]
          properties:
            id: { type: string, pattern: '^agent_' }
            type: { type: string, const: agent }
            version: { type: [integer, 'null'] }

    SendEventsRequest:
      type: object
      required: [events]
      properties:
        events:
          type: array
          minItems: 1
          items:
            $ref: '#/components/schemas/SessionEventInput'

    SessionAgentUpdate:
      type: object
      description: Session-local full replacement override; omitted fields preserve the pinned Agent snapshot.
      properties:
        tools:
          type: array
          items: { type: object, additionalProperties: true }
        mcp_servers:
          type: array
          items: { $ref: '#/components/schemas/MCPServer' }

    SessionEventInput:
      description: >-
        Discriminated input schema. Recognition does not imply runtime support:
        user.tool_confirmation, user.custom_tool_result, user.tool_result, and
        user.define_outcome currently return HTTP 422 unsupported_event_type;
        any session_thread_id returns HTTP 422 unsupported_multiagent. The
        rejected batch is not persisted.
      discriminator:
        propertyName: type
        mapping:
          user.message: '#/components/schemas/UserMessageEventInput'
          user.interrupt: '#/components/schemas/UserInterruptEventInput'
          user.tool_confirmation: '#/components/schemas/UserToolConfirmationEventInput'
          user.custom_tool_result: '#/components/schemas/UserCustomToolResultEventInput'
          user.tool_result: '#/components/schemas/UserToolResultEventInput'
          user.define_outcome: '#/components/schemas/UserDefineOutcomeEventInput'
          interrupt.response: '#/components/schemas/AGRouterInterruptResponseEventInput'
          system.message: '#/components/schemas/AGRouterSystemMessageEventInput'
      oneOf:
        - $ref: '#/components/schemas/UserMessageEventInput'
        - $ref: '#/components/schemas/UserInterruptEventInput'
        - $ref: '#/components/schemas/UserToolConfirmationEventInput'
        - $ref: '#/components/schemas/UserCustomToolResultEventInput'
        - $ref: '#/components/schemas/UserToolResultEventInput'
        - $ref: '#/components/schemas/UserDefineOutcomeEventInput'
        - $ref: '#/components/schemas/AGRouterInterruptResponseEventInput'
        - $ref: '#/components/schemas/AGRouterSystemMessageEventInput'

    UserMessageEventInput:
      type: object
      additionalProperties: false
      required: [type, content]
      properties:
        type: { type: string, enum: [user.message] }
        content: { $ref: '#/components/schemas/UserMessageContent' }

    UserInterruptEventInput:
      type: object
      additionalProperties: false
      required: [type]
      properties:
        type: { type: string, enum: [user.interrupt] }
        session_thread_id: { type: string }

    UserToolConfirmationEventInput:
      type: object
      description: Schema-recognized but not runtime-supported; requests currently return HTTP 422 unsupported_event_type without persistence.
      additionalProperties: false
      required: [type, tool_use_id, result]
      properties:
        type: { type: string, enum: [user.tool_confirmation] }
        tool_use_id: { type: string }
        result: { type: string, enum: [allow, deny] }
        deny_message: { type: string, description: Allowed only when result is deny. }
        session_thread_id: { type: string }

    UserCustomToolResultEventInput:
      type: object
      description: Schema-recognized but not runtime-supported; requests currently return HTTP 422 unsupported_event_type without persistence.
      additionalProperties: false
      required: [type, custom_tool_use_id]
      properties:
        type: { type: string, enum: [user.custom_tool_result] }
        custom_tool_use_id: { type: string }
        content: { $ref: '#/components/schemas/ToolResultContent' }
        is_error: { type: boolean }
        session_thread_id: { type: string }

    UserToolResultEventInput:
      type: object
      description: Schema-recognized but not runtime-supported; requests currently return HTTP 422 unsupported_event_type without persistence.
      additionalProperties: false
      required: [type, tool_use_id]
      properties:
        type: { type: string, enum: [user.tool_result] }
        tool_use_id: { type: string }
        content: { $ref: '#/components/schemas/ToolResultContent' }
        is_error: { type: boolean }
        session_thread_id: { type: string }

    UserDefineOutcomeEventInput:
      type: object
      description: Schema-recognized but not runtime-supported; requests currently return HTTP 422 unsupported_event_type without persistence.
      additionalProperties: false
      required: [type, description, rubric]
      properties:
        type: { type: string, enum: [user.define_outcome] }
        description: { type: string }
        rubric: { $ref: '#/components/schemas/OutcomeRubric' }
        max_iterations: { type: integer, minimum: 1, maximum: 20, default: 3 }

    AGRouterInterruptResponseEventInput:
      type: object
      additionalProperties: false
      description: AGRouter extension; not part of the Claude Managed Agents event input union.
      required: [type, interrupt_id, response]
      properties:
        type: { type: string, enum: [interrupt.response] }
        interrupt_id: { type: string }
        response: { type: object, additionalProperties: true }

    AGRouterSystemMessageEventInput:
      type: object
      additionalProperties: false
      description: AGRouter extension for a privileged mid-conversation system message.
      required: [type, content]
      properties:
        type: { type: string, enum: [system.message] }
        content:
          type: array
          minItems: 1
          items: { $ref: '#/components/schemas/TextContentBlock' }

    UserMessageContent:
      type: array
      minItems: 1
      items:
        oneOf:
          - $ref: '#/components/schemas/TextContentBlock'
          - $ref: '#/components/schemas/ImageContentBlock'
          - $ref: '#/components/schemas/DocumentContentBlock'

    ToolResultContent:
      type: array
      minItems: 1
      items:
        oneOf:
          - $ref: '#/components/schemas/TextContentBlock'
          - $ref: '#/components/schemas/ImageContentBlock'
          - $ref: '#/components/schemas/DocumentContentBlock'
          - $ref: '#/components/schemas/SearchResultContentBlock'

    TextContentBlock:
      type: object
      additionalProperties: false
      required: [type, text]
      properties:
        type: { type: string, enum: [text] }
        text: { type: string }

    ImageContentBlock:
      type: object
      additionalProperties: false
      required: [type, source]
      properties:
        type: { type: string, enum: [image] }
        source: { type: object, additionalProperties: true }

    DocumentContentBlock:
      type: object
      additionalProperties: false
      required: [type, source]
      properties:
        type: { type: string, enum: [document] }
        source: { type: object, additionalProperties: true }
        context: { type: string }
        title: { type: string }

    SearchResultContentBlock:
      type: object
      additionalProperties: false
      required: [type, source, title, content]
      properties:
        type: { type: string, enum: [search_result] }
        source: { type: string }
        title: { type: string }
        content:
          type: array
          items: { $ref: '#/components/schemas/TextContentBlock' }
        citations: { type: object, additionalProperties: true }

    OutcomeRubric:
      oneOf:
        - $ref: '#/components/schemas/TextOutcomeRubric'
        - $ref: '#/components/schemas/FileOutcomeRubric'

    TextOutcomeRubric:
      type: object
      additionalProperties: false
      required: [type, content]
      properties:
        type: { type: string, enum: [text] }
        content: { type: string, maxLength: 262144 }

    FileOutcomeRubric:
      type: object
      additionalProperties: false
      required: [type, file_id]
      properties:
        type: { type: string, enum: [file] }
        file_id: { type: string }

    AcceptedSessionEvents:
      type: object
      required: [data]
      properties:
        data:
          type: array
          items:
            $ref: '#/components/schemas/SessionEvent'

    SessionEvent:
      type: object
      required: [id, type, processed_at, created_at]
      properties:
        id:
          type: string
          pattern: '^sevt_'
        type:
          type: string
        content:
          description: Canonical event content. Message events use an array; tool and status events may use an object.
          oneOf:
            - type: array
              items: { type: object, additionalProperties: true }
            - type: object
              additionalProperties: true
        data:
          type: object
          description: Present only for event types whose canonical payload is not content.
        stop_reason:
          type: [object, 'null']
        model_used:
          type: [string, 'null']
        tokens_in:
          type: integer
        tokens_out:
          type: integer
        duration_ms:
          type: [integer, 'null']
        processed_at:
          type: [string, 'null']
          format: date-time
          description: Null only while a native delivery outcome is still unknown.
        created_at:
          type: string
          format: date-time

    CreateCredentialRequest:
      type: object
      required: [scope, scope_name, secret_key, value]
      properties:
        agent_id: { type: string }
        scope: { type: string, minLength: 1, maxLength: 40, description: 'Caller-defined scope. Leading and trailing whitespace is removed; control characters are rejected.' }
        scope_name: { type: string, minLength: 1, maxLength: 100, description: 'Caller-defined name within the scope. Leading and trailing whitespace is removed; control characters are rejected.' }
        secret_key: { type: string, minLength: 1, maxLength: 200, description: 'Caller-defined key. Leading and trailing whitespace is removed; control characters are rejected.' }
        value: { type: string, minLength: 1, maxLength: 8192, writeOnly: true, description: Plaintext value encrypted at rest and never returned. Blank or whitespace-only values are rejected. }
        group_key: { type: string, description: Defaults to the submitted scope_name and secret_key joined by a colon. }
        strategy: { type: string, enum: [round_robin], default: round_robin }
        weight: { type: integer, minimum: 1, default: 1 }

    UpdateCredentialRequest:
      type: object
      minProperties: 1
      properties:
        status: { type: string, enum: [active, disabled] }
        strategy: { type: string, enum: [round_robin] }
        weight: { type: integer, minimum: 1 }

    Credential:
      type: object
      description: Credential metadata. The plaintext and encrypted payload are never returned.
      required: [id, org_id, agent_id, agent_version, scope, scope_name, secret_key, value_hint, group_key, strategy, weight, status, last_used_at, cooldown_until, failure_count, created_at, updated_at]
      properties:
        id: { type: string, pattern: '^sec_' }
        org_id: { type: string }
        agent_id: { type: string }
        agent_version: { type: [integer, 'null'] }
        scope: { type: string }
        scope_name: { type: string }
        secret_key: { type: string }
        value_hint: { type: string, description: Masked prefix/suffix hint. Treat it as sensitive metadata even though it is not the stored plaintext. }
        group_key: { type: string }
        strategy: { type: string }
        weight: { type: integer }
        status: { type: string, enum: [active, disabled] }
        last_used_at: { type: [string, 'null'], format: date-time }
        cooldown_until: { type: [string, 'null'], format: date-time }
        failure_count: { type: integer }
        created_at: { type: string, format: date-time }
        updated_at: { type: string, format: date-time }

    CredentialResponse:
      type: object
      description: Credential endpoints currently use the standard response envelope; the plaintext secret is never included.
      required: [code, data, message]
      properties:
        code: { type: integer, const: 0 }
        data: { $ref: '#/components/schemas/Credential' }
        message: { type: string, const: ok }

    CredentialListResponse:
      type: object
      required: [code, data, message]
      properties:
        code: { type: integer, const: 0 }
        data:
          type: array
          items: { $ref: '#/components/schemas/Credential' }
        message: { type: string, const: ok }

    CreateDeclarativeDeploymentRequest:
      type: object
      additionalProperties: false
      required: [name, runtime, initial_events]
      properties:
        name: { type: string, minLength: 1 }
        runtime:
          type: string
          description: Enabled declarative runtime. This release supports hermes.
          example: hermes
        description: { type: string }
        model:
          oneOf:
            - type: string
            - type: object
              additionalProperties: true
        system: { type: string }
        skills:
          type: [array, 'null']
          maxItems: 20
          items:
            type: string
            pattern: '^[A-Za-z0-9][A-Za-z0-9._-]*/[A-Za-z0-9][A-Za-z0-9._-]*$'
        tools:
          type: [array, 'null']
          maxItems: 1
          items: { $ref: '#/components/schemas/EndpointToolset' }
        initial_events:
          type: array
          minItems: 1
          description: Must contain at least one user.message event.
          items: { $ref: '#/components/schemas/SessionEventInput' }
        schedule: { type: [object, 'null'] }
        timeout_policy:
          type: object
          additionalProperties: false
          properties:
            hard_timeout_sec: { type: [integer, 'null'], minimum: 60, maximum: 604800 }
            inactivity_timeout_sec: { type: integer, minimum: 180, maximum: 3600 }
        resources: { type: array, maxItems: 0, description: Reserved; only an empty array is accepted. }
        vault_ids: { type: array, maxItems: 0, description: Reserved; only an empty array is accepted. }

    CreateDeploymentRequest:
      type: object
      required: [name, initial_events]
      anyOf:
        - required: [agent_id]
          properties:
            agent_id: {}
        - required: [agent]
          properties:
            agent: {}
      properties:
        name: { type: string }
        description: { type: string }
        agent_id:
          type: string
          description: Agent to run. This is the primary request field.
        agent:
          deprecated: true
          description: Compatibility alias for agent_id; do not send both fields.
          oneOf:
            - type: string
            - $ref: '#/components/schemas/AgentReference'
        agent_version: { type: integer, minimum: 1 }
        initial_events:
          type: array
          minItems: 1
          description: Must contain at least one user.message event.
          items: { $ref: '#/components/schemas/SessionEventInput' }
        schedule: { type: [object, 'null'] }
        timeout_policy:
          type: object
          additionalProperties: false
          properties:
            hard_timeout_sec: { type: [integer, 'null'], minimum: 60, maximum: 604800 }
            inactivity_timeout_sec: { type: integer, minimum: 180, maximum: 3600 }
        resources: { type: array, maxItems: 0, description: Reserved; only an empty array is accepted. }
        vault_ids: { type: array, maxItems: 0, description: Reserved; only an empty array is accepted. }
        metadata: { type: object, additionalProperties: true }
        config: { type: object }

    UpdateDeploymentRequest:
      type: object
      minProperties: 1
      additionalProperties: false
      properties:
        name: { type: string }
        description: { type: string }
        metadata: { type: object, additionalProperties: true }
        agent:
          oneOf:
            - type: string
            - $ref: '#/components/schemas/AgentReference'
        initial_events:
          type: array
          minItems: 1
          description: Must contain at least one user.message event.
          items: { $ref: '#/components/schemas/SessionEventInput' }
        schedule: { type: [object, 'null'] }
        timeout_policy:
          type: object
          additionalProperties: false
          properties:
            hard_timeout_sec: { type: [integer, 'null'], minimum: 60, maximum: 604800 }
            inactivity_timeout_sec: { type: integer, minimum: 180, maximum: 3600 }
        expected_version: { type: integer, minimum: 1 }
        agent_binding:
          type: object
          additionalProperties: false
          required: [agent_id]
          properties:
            agent_id: { type: string }
            agent_version: { type: integer, minimum: 1 }
        agent_config: { type: object, additionalProperties: true }
        notification_settings:
          type: [object, 'null']
          description: Set to null to remove the saved Feishu webhook. A configured webhook is encrypted and never returned.
          additionalProperties: false
          required: [feishu_webhook_url]
          properties:
            feishu_webhook_url:
              type: [string, 'null']
              format: uri
              pattern: '^https://open\.feishu\.cn/open-apis/bot/v2/hook/[A-Za-z0-9_-]+$'
              writeOnly: true
              description: Official Feishu custom-bot webhook. Set to null, or set notification_settings itself to null, to remove it.

    Deployment:
      type: object
      description: Deployment resource. List responses omit initial_events and config; detail responses may add resolved binding fields.
      required: [id, type, name, description, metadata, resources, vault_ids, agent, agent_id, agent_version, schedule, timeout_policy, status, version, next_run_at, creation_mode, created_at, updated_at]
      properties:
        id: { type: string, pattern: '^depl_' }
        type: { type: string, enum: [deployment] }
        name: { type: string }
        description: { type: string }
        metadata: { type: [object, 'null'], additionalProperties: true }
        resources: { type: array, items: { type: object, additionalProperties: true } }
        vault_ids: { type: array, items: { type: string } }
        agent: { $ref: '#/components/schemas/AgentReference' }
        agent_id: { type: string }
        agent_version: { type: [integer, 'null'] }
        initial_events:
          type: [array, 'null']
          items: { $ref: '#/components/schemas/SessionEventInput' }
        schedule: { type: [object, 'null'], additionalProperties: true }
        timeout_policy: { type: object, additionalProperties: true }
        config: { type: [object, 'null'], additionalProperties: true }
        status: { type: string, enum: [active, paused, archived] }
        version: { type: integer, minimum: 1 }
        next_run_at: { type: [string, 'null'], format: date-time }
        creation_mode: { type: string, enum: [declarative, advanced] }
        runtime: { type: string, description: Present for declarative Deployments. }
        skills: { type: array, items: { type: string }, description: Present for declarative Deployments. }
        agent_binding: { type: object, additionalProperties: true }
        agent_config: { type: object, additionalProperties: true }
        notification_settings:
          type: object
          properties:
            feishu_webhook_configured: { type: boolean }
        created_at: { type: string, format: date-time }
        updated_at: { type: string, format: date-time }

    EmptyObjectRequest:
      type: object
      maxProperties: 0
      additionalProperties: false

    DeploymentRun:
      type: object
      description: Durable result of one Deployment trigger's Session creation attempt; it never mirrors later Session state.
      required: [id, type, agent, deployment_id, session_id, error, trigger_context, created_at]
      properties:
        id: { type: string, pattern: '^drun_' }
        type: { type: string, enum: [deployment_run] }
        agent: { $ref: '#/components/schemas/AgentReference' }
        deployment_id: { type: string, pattern: '^depl_' }
        session_id: { type: [string, 'null'], pattern: '^sess_' }
        error:
          type: [object, 'null']
          required: [type, code, message]
          properties:
            type: { type: string }
            code: { type: string }
            message: { type: string }
        trigger_context:
          oneOf:
            - type: 'null'
            - type: object
              required: [type]
              properties: { type: { type: string, enum: [manual] } }
            - type: object
              required: [type, scheduled_at]
              properties:
                type: { type: string, enum: [schedule] }
                scheduled_at: { type: string, format: date-time }
        created_at: { type: string, format: date-time }

    AgentReference:
      type: object
      required: [id, type, version]
      properties:
        id: { type: string }
        type: { type: string, enum: [agent] }
        version: { type: integer, minimum: 1 }
