openapi: 3.1.0
info:
  title: Forgium Agent Public API
  version: 7.0.0
  description: |
    Stable public contract for sending messages to Forgium Agent, managing
    account policies, and registering read-only host capabilities. Mutations
    and candidate-resolution workflows are owned by the host application.
  contact:
    name: Forgium platform administrator
  license:
    name: Forgium public documentation terms
    identifier: LicenseRef-Forgium-Public-Docs
servers:
  - url: https://{apiHost}
    description: Use the host from FORGIUM_AGENT_BASE_URL. Do not infer a host from this document.
    variables:
      apiHost:
        default: api.forgium.dev
        description: Hostname only, copied from FORGIUM_AGENT_BASE_URL.
security:
  - bearerAuth: []
tags:
  - name: System
    description: Availability checks.
  - name: HTTP channel
    description: Synchronous text messaging.
  - name: Capabilities
    description: Self-service host-application capability lifecycle.
  - name: Agent policy
    description: Optional account-wide conversational instructions and topic scope.
  - name: Diagnostics
    description: Account-scoped capability-gap diagnostics and privacy settings.
  - name: Prompt integrations
    description: Browser-only, operator-provisioned synchronous JSON forms.
paths:
  /health:
    get:
      operationId: getHealth
      summary: Check public API availability
      security: []
      tags: [System]
      responses:
        '200':
          description: Public API is available.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/HealthResponse'
              example:
                status: ok
                service: agent-api
        '400':
          description: Invalid health-check request.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '503':
          description: Public API is temporarily unavailable.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'

  /.well-known/forgium/capability-context/v1/jwks.json:
    get:
      operationId: getCapabilityContextJwks
      summary: Retrieve public keys for Forgium capability contexts
      security: []
      tags: [System]
      responses:
        '200':
          description: Public Ed25519 verification keys.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/JwksResponse'
        '404':
          $ref: '#/components/responses/NotFound'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'

  /v1/channels/http/messages:
    post:
      operationId: sendHttpMessage
      summary: Send a text message with an optional image and receive a synchronous response
      tags: [HTTP channel]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/HttpChannelRequest'
            example:
              user_id: mobile_user_123
              message:
                type: text
                body: Hello, can you help me?
              metadata:
                platform: ios
                app_version: 1.0.0
          multipart/form-data:
            schema:
              $ref: '#/components/schemas/HttpChannelMultipartRequest'
            encoding:
              payload:
                contentType: application/json
      responses:
        '200':
          description: Agent response.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/HttpChannelResponse'
              examples:
                answer:
                  value:
                    conversation_id: conv_abc123def456
                    message:
                      id: msg_abc123def456
                      role: assistant
                      type: text
                      body: Hello, how can I help?
                host_interaction:
                  summary: Response requiring a host-owned confirmation or selection
                  value:
                    conversation_id: conv_abc123def456
                    message:
                      id: msg_abc123def456
                      role: assistant
                      type: text
                      body: Encontré la operación. Confirmá si querés continuar.
                    interaction:
                      interaction_id: int_abc123def456
                      owner: host
                      type: confirm_operation
                      operation: cancel_expense
                      operation_ref: opaque-host-value
                      expires_at: '2026-08-02T16:05:00Z'
                      preview:
                        title: Cancelar gasto
                        fields:
                          - label: Concepto
                            value: Servicio de internet
                with_capability_trace:
                  summary: Response with capability trace, benchmark observation, and timing.
                  value:
                    conversation_id: conv_abc123def456
                    message:
                      id: msg_abc123def456
                      role: assistant
                      type: text
                      body: "El consorcio tiene 12 unidades funcionales."
                    capability_trace:
                      version: v1
                      run_id: run_abc123def456
                      message_id: msg_abc123def456
                      events:
                        - ordinal: 1
                          stage: selection
                          status: selected
                          capability: list_unidades
                          revision: 3
                        - ordinal: 2
                          stage: execution
                          status: result_received
                          capability: list_unidades
                          revision: 3
                        - ordinal: 3
                          stage: response_generation
                          status: response_generated
                          reason_code: grounded
                      timing:
                        preparation_ms: 12
                        selection_inference_ms: 840
                        selection_inference_attempts: 1
                        selection_retry_delay_ms: 0
                        execution_ms: 90
                        executor_attempts: 1
                        grounded_inference_ms: 510
                        e2e_processing_ms: 1490
                    benchmark_observation:
                      capability: list_unidades
                      argument_category: known
                      fixture_result: non_empty
                      status: 200
                      model: "@cf/meta/llama-3.3-70b-instruct-fp8-fast"
                      correlation_id: run_abc123def456
        '401':
          $ref: '#/components/responses/Unauthorized'
        '405':
          $ref: '#/components/responses/HttpMethodNotAllowed'
        '409':
          $ref: '#/components/responses/HttpDuplicateEvent'
        '413':
          $ref: '#/components/responses/HttpRequestTooLarge'
        '422':
          $ref: '#/components/responses/HttpValidationError'
        '500':
          $ref: '#/components/responses/HttpRuntimeError'

  /v1/conversations/{conversationId}/operation-results:
    post:
      operationId: reportHostOperationResult
      summary: Report a terminal host-owned operation result
      description: >-
        Optional host callback for keeping the Forgium conversation current.
        The host performs authorization and mutation locally; Forgium only
        validates the bounded terminal status and may append deterministic text.
        Do not send operation references, mutation arguments, result bodies, or
        user-authored messages.
      tags: [HTTP channel]
      parameters:
        - name: conversationId
          in: path
          required: true
          schema:
            type: string
            minLength: 1
            maxLength: 128
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/HostOperationResultRequest'
            example:
              interaction_id: int_abc123def456
              operation: cancel_expense
              status: succeeded
      responses:
        '200':
          description: Terminal result accepted or replayed.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/HostOperationResultResponse'
        '400':
          $ref: '#/components/responses/ValidationError'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '409':
          $ref: '#/components/responses/Conflict'
        '410':
          $ref: '#/components/responses/Gone'
        '413':
          $ref: '#/components/responses/RequestTooLarge'

  /v1/webhooks/meta:
    get:
      operationId: verifyMetaWebhook
      summary: Verify the Meta WhatsApp webhook callback
      description: Meta uses this unauthenticated challenge request when configuring the callback URL.
      security: []
      tags: [Webhooks]
      parameters:
        - name: hub.mode
          in: query
          required: true
          schema: { type: string, enum: [subscribe] }
        - name: hub.verify_token
          in: query
          required: true
          schema: { type: string, maxLength: 256 }
        - name: hub.challenge
          in: query
          required: true
          schema: { type: string, maxLength: 2048 }
      responses:
        '200':
          description: The challenge value is returned as plain text.
          content:
            text/plain:
              schema: { type: string }
        '403':
          $ref: '#/components/responses/MetaWebhookForbidden'
    post:
      operationId: receiveMetaWebhook
      summary: Receive a native Meta WhatsApp event
      description: Validates Meta's X-Hub-Signature-256 header, routes by the registered phone_number_id, and queues inbound text messages.
      security: []
      tags: [Webhooks]
      parameters:
        - name: X-Hub-Signature-256
          in: header
          required: true
          schema:
            type: string
            pattern: '^sha256=[0-9a-f]{64}$'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              additionalProperties: true
            example:
              object: whatsapp_business_account
              entry: []
      responses:
        '200':
          description: Event accepted, duplicated, or intentionally ignored.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MetaWebhookResponse'
        '400':
          $ref: '#/components/responses/MetaWebhookBadRequest'
        '401':
          $ref: '#/components/responses/MetaWebhookUnauthorized'
        '404':
          $ref: '#/components/responses/MetaWebhookPhoneNotRegistered'
        '413':
          $ref: '#/components/responses/MetaWebhookTooLarge'

        '503':
          $ref: '#/components/responses/ServiceUnavailable'

  /v1/prompt-integrations/{publicId}/execute:
    options:
      operationId: preflightPromptIntegration
      summary: Preflight a browser prompt integration request
      security: []
      tags: [Prompt integrations]
      parameters:
        - $ref: '#/components/parameters/PromptIntegrationPublicId'
      responses:
        '204':
          description: Preflight accepted for the configured origin.
        '403':
          $ref: '#/components/responses/PublicPromptForbidden'
        '404':
          $ref: '#/components/responses/NotFound'
    post:
      operationId: executePromptIntegration
      summary: Execute a browser prompt integration
      description: |
        Sends schema-declared non-sensitive input to the operator-configured
        prompt and returns schema-valid JSON. This route has no API-key
        authentication; the exact configured Origin and a single-use
        Turnstile token are required. The integration URL and output schema
        remain stable across compatible administrative revisions. Usage is
        attributed to the account that owns the integration; this route does
        not accept or require a user_id for usage attribution.
      security: []
      tags: [Prompt integrations]
      parameters:
        - $ref: '#/components/parameters/PromptIntegrationPublicId'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/PromptIntegrationExecuteRequest'
      responses:
        '200':
          description: Schema-valid JSON result.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PromptIntegrationExecuteResponse'
        '400':
          $ref: '#/components/responses/PublicPromptValidation'
        '403':
          $ref: '#/components/responses/PublicPromptForbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '413':
          $ref: '#/components/responses/PublicPromptTooLarge'
        '422':
          $ref: '#/components/responses/PublicPromptValidation'
        '429':
          $ref: '#/components/responses/PublicPromptRateLimited'
        '502':
          $ref: '#/components/responses/PublicPromptModelFailure'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'

  /v1/agent-policy:
    get:
      operationId: getAgentPolicy
      summary: Read the account's current agent policy
      description: The bearer API key determines which account policy is returned. A missing policy is not an error in agent runtime behavior, but this management endpoint returns 404.
      tags: [Agent policy]
      responses:
        '200':
          description: The validated policy and its informational revision.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AgentPolicyResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/PolicyNotFound'
        '405':
          $ref: '#/components/responses/PolicyMethodNotAllowed'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
    put:
      operationId: replaceAgentPolicy
      summary: Create or fully replace the account's agent policy
      description: >-
        Defines how the agent should behave, not how users should express themselves.
        Covers language, topic scope, natural interpretation, ambiguity handling,
        no invention, and privacy. Full replacement applied to the account selected
        by the bearer API key. Limited to 16 KiB. Topic matching is best-effort
        model behavior, not a deterministic content filter or safety boundary.
      tags: [Agent policy]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/AgentPolicy'
            example:
              version: 1
              instructions: >-
                Respondé en español rioplatense. Interpreta las consultas de forma
                natural: "listar consorcios", "mis consorcios", "qué consorcios tengo"
                significan lo mismo. Nunca inventes datos no devueltos por capabilities.
                Si es ambiguo, pedí aclaración. No expongas IDs internos.
              allowed_topics:
                - name: consorcios
                  description: 'Gestión de consorcios: listado, unidades, gastos, pagos y liquidaciones.'
                - name: cuenta_corriente
                  description: Saldos, deudas y movimientos de cuenta corriente.
              out_of_scope_response: No puedo ayudarte con esa consulta.
      responses:
        '200':
          description: Existing policy fully replaced.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AgentPolicyResponse'
        '201':
          description: Policy created for the first time.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AgentPolicyResponse'
        '400':
          description: Invalid JSON, invalid policy fields, or an empty policy.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              examples:
                invalidJson:
                  value: { error: INVALID_JSON, code: INVALID_JSON }
                invalidPolicy:
                  value: { error: POLICY_INVALID, code: POLICY_INVALID }
                emptyPolicy:
                  value: { error: POLICY_EMPTY, code: POLICY_EMPTY }
        '401':
          $ref: '#/components/responses/Unauthorized'
        '405':
          $ref: '#/components/responses/PolicyMethodNotAllowed'
        '413':
          $ref: '#/components/responses/PolicyRequestTooLarge'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
    delete:
      operationId: deleteAgentPolicy
      summary: Remove the account's agent policy
      description: Removes only the policy selected by the bearer API key. Subsequent runs use the platform defaults.
      tags: [Agent policy]
      responses:
        '204':
          description: Policy removed.
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/PolicyNotFound'
        '405':
          $ref: '#/components/responses/PolicyMethodNotAllowed'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'

  /v1/diagnostics/settings:
    get:
      operationId: getDiagnosticSettings
      summary: Read account diagnostic privacy settings
      description: The bearer API key determines which account's settings are returned. The default mode is metadata when no explicit setting exists.
      tags: [Diagnostics]
      responses:
        '200':
          description: Current account diagnostic settings.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DiagnosticSettingsResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '405':
          $ref: '#/components/responses/DiagnosticsMethodNotAllowed'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
    put:
      operationId: replaceDiagnosticSettings
      summary: Set account diagnostic message mode
      description: Controls whether the account's capability-gap report is disabled, returns only metadata, or includes the original message text. This setting applies only to that account and does not change the core conversation storage policy or internal audit retention.
      tags: [Diagnostics]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/DiagnosticSettings'
            example:
              diagnostic_message_mode: text
      responses:
        '200':
          description: Existing settings replaced.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DiagnosticSettingsResponse'
        '201':
          description: Settings created for the first time.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DiagnosticSettingsResponse'
        '400':
          description: Invalid JSON or diagnostic settings.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '405':
          $ref: '#/components/responses/DiagnosticsMethodNotAllowed'
        '413':
          $ref: '#/components/responses/DiagnosticsRequestTooLarge'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'

  /v1/diagnostics/capability-gaps:
    get:
      operationId: listCapabilityGaps
      summary: List unresolved capability diagnostics for the account
      description: Returns account-scoped coverage-gap and agent-dead-end events. Message text is included only when diagnostic_message_mode is text and remains subject to the platform's message retention policy. Results never include data from another account.
      tags: [Diagnostics]
      parameters:
        - name: limit
          in: query
          required: false
          schema: { type: integer, minimum: 1, maximum: 100, default: 50 }
        - name: before
          in: query
          required: false
          schema: { type: string, format: date-time }
          description: RFC3339 cursor timestamp ending in Z, used with before_id.
        - name: before_id
          in: query
          required: false
          schema: { type: string, minLength: 1, maxLength: 128 }
          description: Event ID cursor used with before.
      responses:
        '200':
          description: Account-scoped unresolved capability diagnostics in reverse chronological order.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CapabilityGapResponse'
        '400':
          description: Invalid pagination parameters.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '405':
          $ref: '#/components/responses/DiagnosticsMethodNotAllowed'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'

  /v1/capability-credentials/{credentialRef}:
    delete:
      operationId: revokeCapabilityCredential
      summary: Revoke a stored host-endpoint credential
      tags: [Capabilities]
      parameters:
        - $ref: '#/components/parameters/CredentialRef'
      responses:
        '200':
          description: Credential revoked and no longer usable.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CredentialResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
    put:
      operationId: configureCapabilityCredential
      summary: Store or rotate a host-endpoint credential
      description: The secret is encrypted at rest and is never returned or included in a capability manifest.
      tags: [Capabilities]
      parameters:
        - $ref: '#/components/parameters/CredentialRef'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CredentialRequest'
            example:
              auth_type: bearer
              secret: endpoint-token-from-secret-manager
      responses:
        '201':
          description: Credential configured. The secret is not returned.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CredentialResponse'
        '400':
          $ref: '#/components/responses/ValidationError'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '413':
          description: Request exceeds the credential size limit.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '503':
          description: Credential vault is unavailable.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'

  /v1/capabilities:
    get:
      operationId: listCapabilities
      summary: List capabilities available to the API key
      tags: [Capabilities]
      responses:
        '200':
          description: Capability definitions scoped to the API key.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CapabilityListResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
    post:
      operationId: createCapability
      summary: Validate and publish a single capability
      description: |
        Creates one capability from a single-definition payload. Use this
        instead of `/v1/capabilities/import` when adding a single capability
        without wrapping it in an array. Schema and credential validation happen
        before the capability is published atomically in `active` status.
      tags: [Capabilities]
      parameters:
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CapabilityCreateRequest'
            example:
              domain: customers
              capability:
                name: lookup_customer
                description: Looks up one customer by email.
                when_to_use: Use when a customer asks for their account details.
                method: POST
                url: https://customer.example.com/v1/customers/lookup
                auth:
                  type: bearer
                  credential_ref: cred_customer_api
                safe_headers:
                  accept: application/json
                  content-type: application/json
                input_schema:
                  type: object
                  additionalProperties: false
                  required: [email]
                  properties:
                    email:
                      type: string
                      maxLength: 254
                output_schema:
                  type: object
                  additionalProperties: false
                  required: [customer_id, email]
                  properties:
                    customer_id:
                      type: string
                      maxLength: 100
                    email:
                      type: string
                      maxLength: 254
                business_rules:
                  - The backend system is authoritative.
                examples: []
                side_effect: read
                requires_approval: false
                timeout_ms: 5000
                retry_count: 1
      responses:
        '201':
          description: Capability validated and published as active.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CapabilityLifecycleResponse'
        '400':
          $ref: '#/components/responses/ValidationError'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '409':
          $ref: '#/components/responses/Conflict'

  /v1/capabilities/import:
    post:
      operationId: importCapabilities
      summary: Validate and publish capabilities
      description: Schema and credential validation happen before all capabilities are published atomically in active status. Contract tests are independent diagnostics and do not change lifecycle state or revision.
      tags: [Capabilities]
      parameters:
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CapabilityManifest'
      responses:
        '201':
          description: Capabilities validated and published as active.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CapabilityImportResponse'
        '400':
          $ref: '#/components/responses/ValidationError'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '409':
          $ref: '#/components/responses/Conflict'

  /v1/capabilities/{capabilityId}:
    get:
      operationId: getCapability
      summary: Get one capability
      tags: [Capabilities]
      parameters:
        - $ref: '#/components/parameters/CapabilityId'
      responses:
        '200':
          description: Capability definition and revision history.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CapabilityDetailResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
    patch:
      operationId: reviseCapability
      summary: Validate and publish a replacement revision
      description: If-Match must contain the current numeric revision. The complete replacement is schema- and credential-validated before an atomic write. The current active or disabled state is preserved; the immutable capability ID does not change.
      tags: [Capabilities]
      parameters:
        - $ref: '#/components/parameters/CapabilityId'
        - $ref: '#/components/parameters/IfMatch'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CapabilityPatchRequest'
      responses:
        '200':
          description: Replacement revision validated and published.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CapabilityLifecycleResponse'
        '400':
          $ref: '#/components/responses/ValidationError'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '409':
          $ref: '#/components/responses/Conflict'

  /v1/capabilities/{capabilityId}/test:
    post:
      operationId: testCapability
      summary: Run a contract test for a capability revision
      description: Calls the registered active read-only endpoint at the selected revision with the supplied bounded arguments. The result is stored as an independent contract-test record and does not change lifecycle state or revision.
      tags: [Capabilities]
      parameters:
        - $ref: '#/components/parameters/CapabilityId'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CapabilityTestRequest'
            example:
              arguments:
                name: Juan Pérez
      responses:
        '200':
          description: Contract test result.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CapabilityTestResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '409':
          $ref: '#/components/responses/Conflict'

  /v1/capabilities/{capabilityId}/enable:
    post:
      operationId: enableCapability
      summary: Explicitly enable a capability
      description: Requires the current If-Match revision. Enabling rechecks the credential reference and creates a new active revision. It never happens implicitly through PATCH.
      tags: [Capabilities]
      parameters:
        - $ref: '#/components/parameters/CapabilityId'
        - $ref: '#/components/parameters/IfMatch'
      responses:
        '200':
          description: Capability is active.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CapabilityLifecycleResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '409':
          $ref: '#/components/responses/Conflict'

  /v1/capabilities/{capabilityId}/disable:
    post:
      operationId: disableCapability
      summary: Disable a capability
      tags: [Capabilities]
      parameters:
        - $ref: '#/components/parameters/CapabilityId'
        - $ref: '#/components/parameters/IfMatch'
      responses:
        '200':
          description: Capability is disabled.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CapabilityLifecycleResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '409':
          $ref: '#/components/responses/Conflict'

components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      description: API key issued out of band by the Forgium administrator.

  parameters:
    PromptIntegrationPublicId:
      name: publicId
      in: path
      required: true
      schema:
        type: string
        pattern: '^pi_public_[A-Za-z0-9_-]{16,80}$'
        minLength: 27
        maxLength: 90
    CredentialRef:
      name: credentialRef
      in: path
      required: true
      schema:
        type: string
        pattern: '^cred_[A-Za-z0-9_-]{3,120}$'
        minLength: 7
        maxLength: 128
    CapabilityId:
      name: capabilityId
      in: path
      required: true
      schema:
        type: string
        pattern: '^cap_[a-z0-9]+$'
        minLength: 8
        maxLength: 80
    IdempotencyKey:
      name: Idempotency-Key
      in: header
      required: true
      schema:
        type: string
        minLength: 1
        maxLength: 200
    IfMatch:
      name: If-Match
      in: header
      required: true
      schema:
        type: string
        minLength: 1
        maxLength: 80

  schemas:
    PromptIntegrationExecuteRequest:
      type: object
      additionalProperties: false
      required: [input, turnstile_token]
      properties:
        input:
          type: object
          additionalProperties: true
          description: Object validated against the integration's operator-configured input schema.
        turnstile_token:
          type: string
          minLength: 1
          maxLength: 2048
          description: >-
            Single-use token returned by the Turnstile widget.
            The widget must be rendered with the integration's
            turnstile_action value — the server rejects tokens
            whose action does not match.
    PromptIntegrationExecuteResponse:
      type: object
      additionalProperties: false
      required: [data]
      properties:
        data:
          type: object
          additionalProperties: true
          description: Object validated against the integration's operator-configured output schema.
    HealthResponse:
      type: object
      additionalProperties: false
      required: [status, service]
      properties:
        status:
          type: string
          enum: [ok]
          maxLength: 16
        service:
          type: string
          maxLength: 64

    AgentPolicy:
      type: object
      additionalProperties: false
      required: [version]
      description: At least one of instructions or allowed_topics is required. out_of_scope_response is valid only with allowed_topics. Omitted fields keep their platform defaults.
      anyOf:
        - required: [instructions]
        - required: [allowed_topics]
      dependentRequired:
        out_of_scope_response: [allowed_topics]
      properties:
        version:
          type: integer
          const: 1
        instructions:
          type: string
          minLength: 1
          maxLength: 2000
          pattern: '\S'
          description: Optional account-authored style or behavior instructions.
        allowed_topics:
          type: array
          minItems: 1
          maxItems: 20
          items:
            $ref: '#/components/schemas/AgentPolicyTopic'
          description: Optional allowlist of topics. Matching is best-effort model behavior.
        out_of_scope_response:
          type: string
          minLength: 1
          maxLength: 500
          pattern: '\S'
          description: Optional fallback for requests outside allowed_topics. Defaults to No puedo ayudarte con esa consulta.
    AgentPolicyTopic:
      type: object
      additionalProperties: false
      required: [name, description]
      properties:
        name:
          type: string
          minLength: 1
          maxLength: 80
          pattern: '\S'
          description: Unique topic label, compared case-insensitively after trimming.
        description:
          type: string
          minLength: 1
          maxLength: 500
          pattern: '\S'
          description: Boundary description shown to the model.
    AgentPolicyResponse:
      type: object
      additionalProperties: false
      required: [revision, updated_at, policy]
      properties:
        revision:
          type: integer
          minimum: 1
        updated_at:
          type: string
          format: date-time
          maxLength: 64
        policy:
          $ref: '#/components/schemas/AgentPolicy'

    DiagnosticSettings:
      type: object
      additionalProperties: false
      required: [diagnostic_message_mode]
      description: Account-owned privacy setting for the capability-gap report. Metadata is the safe default; text is opt-in.
      properties:
        diagnostic_message_mode:
          type: string
          enum: [metadata, text, disabled]
          description: Whether capability-gap diagnostics are disabled, return only metadata, or also return the original message text.

    DiagnosticSettingsResponse:
      type: object
      additionalProperties: false
      required: [revision, updated_at, settings]
      properties:
        revision:
          type: integer
          minimum: 0
        updated_at:
          type: [string, 'null']
          format: date-time
        settings:
          $ref: '#/components/schemas/DiagnosticSettings'

    CapabilityGapResponse:
      type: object
      additionalProperties: false
      required: [message_mode, has_more, events]
      properties:
        message_mode:
          type: string
          enum: [metadata, text, disabled]
        has_more:
          type: boolean
        events:
          type: array
          maxItems: 100
          items:
            $ref: '#/components/schemas/CapabilityGapEvent'

    CapabilityGapEvent:
      type: object
      additionalProperties: false
      required: [id, agent_run_id, category, capability, revision, created_at]
      properties:
        id:
          type: string
          description: Opaque diagnostic event ID owned by the authenticated account.
        agent_run_id:
          type: string
          description: Opaque agent-run correlation ID.
        message_id:
          type: string
          description: Opaque user-message correlation ID, when available.
        category:
          type: string
          enum: [coverage_gap, agent_dead_end]
        capability:
          type: [string, 'null']
        revision:
          type: [integer, 'null']
        available_capabilities:
          type: array
          maxItems: 20
          description: Existing eligible capability names considered during the run, when available.
          items:
            type: string
            minLength: 1
            maxLength: 128
        conversation_id:
          type: string
          description: Present only when message text mode is enabled and the source message is retained.
        message:
          type: string
          maxLength: 10000
          description: Original user message, returned only when message text mode is enabled and with sensitive token-shaped values redacted.
        redacted:
          type: boolean
          description: Whether sensitive token-shaped content was replaced before returning the message.
        truncated:
          type: boolean
          description: Whether the returned message was bounded to the maximum diagnostic message length.
        created_at:
          type: string
          format: date-time

    HttpChannelRequest:
      type: object
      additionalProperties: false
      required: [message]
      properties:
        conversation_id:
          type: string
          minLength: 1
          maxLength: 128
        user_id:
          type: string
          minLength: 1
          maxLength: 200
          description: >-
            Opaque external user identifier recommended for per-user usage
            attribution. Optional during client migration; if omitted, usage
            remains attributed to the account without an individual user. No
            mandatory migration date is announced.
        message:
          $ref: '#/components/schemas/TextMessageRequest'
        metadata:
          type: object
          additionalProperties:
            type: string
            maxLength: 500
          maxProperties: 30
    TextMessageRequest:
      type: object
      additionalProperties: false
      required: [type, body]
      properties:
        type:
          type: string
          enum: [text]
          maxLength: 16
        body:
          type: string
          minLength: 1
          maxLength: 10000
    HttpChannelMultipartRequest:
      type: object
      additionalProperties: false
      required: [payload, image]
      properties:
        payload:
          $ref: '#/components/schemas/HttpChannelMultipartPayload'
        image:
          type: string
          format: binary
          description: One JPEG, PNG, or WebP image, at most 4 MiB. The binary is processed in memory and not persisted.
    HttpChannelMultipartPayload:
      type: object
      additionalProperties: false
      required: [message]
      description: JSON payload part. It has the same fields as HttpChannelRequest; its message body may be empty because the image supplies the content.
      properties:
        conversation_id:
          type: string
          minLength: 1
          maxLength: 128
        user_id:
          type: string
          minLength: 1
          maxLength: 200
        message:
          type: object
          additionalProperties: false
          required: [type, body]
          properties:
            type:
              type: string
              enum: [text]
            body:
              type: string
              minLength: 0
              maxLength: 10000
        metadata:
          type: object
          additionalProperties:
            type: string
            maxLength: 500
          maxProperties: 30
    HttpChannelResponse:
      type: object
      additionalProperties: false
      required: [conversation_id, message]
      properties:
        conversation_id:
          type: string
          minLength: 1
          maxLength: 128
        message:
          $ref: '#/components/schemas/AssistantMessage'
        interaction:
          $ref: '#/components/schemas/HostInteraction'
        capability_trace:
          $ref: '#/components/schemas/CapabilityTrace'
        benchmark_observation:
          $ref: '#/components/schemas/BenchmarkObservation'
    CapabilityTrace:
      type: object
      additionalProperties: false
      required: [version, run_id, message_id, events]
      properties:
        version:
          type: string
          enum: [v1]
        run_id:
          type: string
          minLength: 1
          maxLength: 128
          description: Opaque identifier for the current agent run.
        message_id:
          type: string
          minLength: 1
          maxLength: 128
          description: Opaque identifier for the user message that initiated the run.
        events:
          type: array
          maxItems: 8
          items:
            $ref: '#/components/schemas/CapabilityTraceEvent'
        timing:
          type: object
          additionalProperties: false
          description: |
            Internal timing breakdown for the agent run. All values are
            non-negative integers in milliseconds unless noted. Omitted when
            the worker does not return timing data or when the sanitizer
            rejects unknown keys.
          properties:
            preparation_ms:
              type: integer
              minimum: 0
              maximum: 300000
              description: Time spent preparing the run context.
            selection_inference_ms:
              type: integer
              minimum: 0
              maximum: 300000
              description: Inference time for capability selection.
            selection_inference_attempts:
              type: integer
              minimum: 1
              maximum: 10
              description: Number of selection inference attempts.
            selection_retry_delay_ms:
              type: integer
              minimum: 0
              maximum: 300000
              description: Cumulative delay between selection retries.
            execution_ms:
              type: integer
              minimum: 0
              maximum: 300000
              description: Time spent executing the selected capability.
            executor_attempts:
              type: integer
              minimum: 1
              maximum: 10
              description: Number of executor attempts.
            grounded_inference_ms:
              type: integer
              minimum: 0
              maximum: 300000
              description: Inference time for grounded response generation.
            e2e_processing_ms:
              type: integer
              minimum: 0
              maximum: 300000
              description: Total wall-clock time from request receipt to response.
    CapabilityTraceEvent:
      type: object
      additionalProperties: false
      required: [ordinal, stage, status]
      properties:
        ordinal:
          type: integer
          minimum: 1
        stage:
          type: string
          enum: [selection, argument_validation, execution, response_generation, run]
        status:
          type: string
          description: Sanitized current-run state; this is not model reasoning.
          enum: [not_selected, selected, arguments_invalid, execution_failed, empty_result, result_received, response_generated, unresolved, runtime_failed]
        reason_code:
          type: string
          description: Bounded technical category. `safe_fallback` is terminal, not causal; inspect the preceding event.
          enum: [no_tool_call, no_active_capabilities, policy_out_of_scope, schema_required_missing, schema_invalid, input_invalid, capability_not_active, approval_required, upstream_timeout, upstream_unavailable, upstream_http_error, response_invalid, business_outcome_failed, structurally_empty, grounded, answer_only, safe_fallback, run_deadline_exceeded, coverage_gap, out_of_scope, agent_dead_end, selection_inference_failed, grounded_inference_failed]
        capability:
          type: string
          minLength: 1
          maxLength: 128
        revision:
          type: integer
          minimum: 1
        interaction_type:
          type: string
          enum: [confirm_operation, select_option]
          description: Bounded host interaction discriminator; opaque references are never included in traces.
    BenchmarkObservation:
      type: object
      additionalProperties: false
      required: [capability, argument_category, fixture_result, status, model, correlation_id]
      properties:
        capability:
          type: [string, 'null']
          maxLength: 128
        argument_category:
          type: string
          enum: [known, unknown, missing]
        fixture_result:
          type: string
          enum: [non_empty, empty, error, ambiguous]
        status:
          type: integer
          enum: [200]
        model:
          type: string
          minLength: 1
          maxLength: 256
        correlation_id:
          type: string
          minLength: 1
          maxLength: 128
    HostInteractionPreparationResult:
      description: >-
        Exact result returned by a capability endpoint whose result_handling is
        host_interaction. Forgium sanitizes it against the declared canonical
        output schema and then enforces this discriminated union. This call is a
        just-in-time host preflight after the user expresses an operation
        intent. The host resolves resources and current eligibility during the
        call and must revalidate before any later mutation.
      oneOf:
        - type: object
          additionalProperties: false
          required: [status, confirmation]
          properties:
            status:
              type: string
              enum: [confirmation_required]
            confirmation:
              type: object
              additionalProperties: false
              required: [operation, operation_ref, expires_at, preview]
              properties:
                operation:
                  type: string
                  pattern: '^[a-z][a-z0-9_]{1,63}$'
                  maxLength: 64
                operation_ref:
                  type: string
                  minLength: 1
                  maxLength: 512
                expires_at:
                  type: string
                  format: date-time
                  maxLength: 35
                preview:
                  $ref: '#/components/schemas/HostInteractionPreview'
        - type: object
          additionalProperties: false
          required: [status, selection]
          properties:
            status:
              type: string
              enum: [selection_required]
            selection:
              type: object
              additionalProperties: false
              required: [operation, selection_ref, expires_at, options]
              properties:
                operation:
                  type: string
                  pattern: '^[a-z][a-z0-9_]{1,63}$'
                  maxLength: 64
                selection_ref:
                  type: string
                  minLength: 1
                  maxLength: 512
                expires_at:
                  type: string
                  format: date-time
                  maxLength: 35
                options:
                  type: array
                  minItems: 2
                  maxItems: 8
                  items:
                    $ref: '#/components/schemas/HostInteractionOption'
        - type: object
          description: >-
            No operation is currently eligible. This covers both no matching
            resource and matching resources whose state makes the requested
            operation ineligible; it does not assert that the resource is
            absent and does not distinguish those causes.
          additionalProperties: false
          required: [status]
          properties:
            status:
              type: string
              enum: [not_found]
    HostInteraction:
      oneOf:
        - type: object
          additionalProperties: false
          required: [interaction_id, owner, type, operation, operation_ref, expires_at, preview]
          properties:
            interaction_id:
              type: string
              minLength: 1
              maxLength: 128
            owner:
              type: string
              enum: [host]
            type:
              type: string
              enum: [confirm_operation]
            operation:
              type: string
              pattern: '^[a-z][a-z0-9_]{1,63}$'
              minLength: 2
              maxLength: 64
            operation_ref:
              type: string
              minLength: 1
              maxLength: 512
            expires_at:
              type: string
              format: date-time
              maxLength: 64
            preview:
              $ref: '#/components/schemas/HostInteractionPreview'
        - type: object
          additionalProperties: false
          required: [interaction_id, owner, type, operation, selection_ref, expires_at, options]
          properties:
            interaction_id:
              type: string
              minLength: 1
              maxLength: 128
            owner:
              type: string
              enum: [host]
            type:
              type: string
              enum: [select_option]
            operation:
              type: string
              pattern: '^[a-z][a-z0-9_]{1,63}$'
              minLength: 2
              maxLength: 64
            selection_ref:
              type: string
              minLength: 1
              maxLength: 512
            expires_at:
              type: string
              format: date-time
              maxLength: 64
            options:
              type: array
              minItems: 2
              maxItems: 8
              items:
                $ref: '#/components/schemas/HostInteractionOption'
    HostInteractionPreview:
      type: object
      additionalProperties: false
      required: [title, fields]
      properties:
        title:
          type: string
          minLength: 1
          maxLength: 120
        fields:
          type: array
          minItems: 1
          maxItems: 8
          items:
            $ref: '#/components/schemas/HostInteractionField'
    HostInteractionField:
      type: object
      additionalProperties: false
      required: [label, value]
      properties:
        label:
          type: string
          minLength: 1
          maxLength: 80
        value:
          type: string
          minLength: 1
          maxLength: 500
    HostInteractionOption:
      type: object
      additionalProperties: false
      required: [option_ref, label]
      properties:
        option_ref:
          type: string
          minLength: 1
          maxLength: 512
        label:
          type: string
          minLength: 1
          maxLength: 200
    HostOperationResultRequest:
      type: object
      additionalProperties: false
      required: [interaction_id, operation, status]
      properties:
        interaction_id:
          type: string
          minLength: 1
          maxLength: 128
        operation:
          type: string
          pattern: '^[a-z][a-z0-9_]{1,63}$'
          minLength: 2
          maxLength: 64
        status:
          type: string
          enum: [succeeded, resource_changed, rejected, failed]
    HostOperationResultResponse:
      allOf:
        - $ref: '#/components/schemas/HostOperationResultRequest'
    AssistantMessage:
      type: object
      additionalProperties: false
      required: [id, role, type, body]
      properties:
        id:
          type: string
          minLength: 1
          maxLength: 128
        role:
          type: string
          enum: [assistant]
          maxLength: 16
        type:
          type: string
          enum: [text]
          maxLength: 16
        body:
          type: string
          maxLength: 20000

    CredentialRequest:
      type: object
      additionalProperties: false
      required: [auth_type, secret]
      properties:
        auth_type:
          type: string
          enum: [bearer, api_key, basic]
          maxLength: 16
        header_name:
          type: string
          enum: [x-api-key, x-api-token]
          maxLength: 32
        secret:
          type: string
          minLength: 1
          maxLength: 4096
    CredentialResponse:
      type: object
      additionalProperties: false
      required: [credential_ref, status]
      properties:
        credential_ref:
          type: string
          maxLength: 128
        status:
          type: string
          enum: [configured, revoked]
          maxLength: 32

    JwksResponse:
      type: object
      additionalProperties: false
      required: [keys]
      properties:
        keys:
          type: array
          minItems: 1
          maxItems: 10
          items:
            $ref: '#/components/schemas/PublicSigningKey'
    PublicSigningKey:
      type: object
      additionalProperties: false
      required: [kty, crv, x, kid, use, alg]
      properties:
        kty:
          type: string
          enum: [OKP]
        crv:
          type: string
          enum: [Ed25519]
        x:
          type: string
          maxLength: 128
        kid:
          type: string
          minLength: 1
          maxLength: 128
        use:
          type: string
          enum: [sig]
        alg:
          type: string
          enum: [EdDSA]

    CapabilityManifest:
      type: object
      additionalProperties: false
      required: [domain, capabilities]
      properties:
        domain:
          type: string
          pattern: '^[a-z][a-z0-9_-]{1,63}$'
          minLength: 2
          maxLength: 64
        capabilities:
          type: array
          minItems: 1
          maxItems: 50
          items:
            $ref: '#/components/schemas/CapabilityDefinition'
    CapabilityCreateRequest:
      type: object
      additionalProperties: false
      required: [domain, capability]
      description: Single-capability creation payload. Use instead of CapabilityManifest when adding one capability without an array wrapper.
      properties:
        domain:
          type: string
          pattern: '^[a-z][a-z0-9_-]{1,63}$'
          minLength: 2
          maxLength: 64
        capability:
          $ref: '#/components/schemas/CapabilityDefinition'
    CapabilityDefinition:
      type: object
      additionalProperties: false
      required: [name, description, when_to_use, method, url, auth, safe_headers, input_schema, output_schema, side_effect, requires_approval, timeout_ms, retry_count]
      properties:
        name:
          type: string
          pattern: '^[a-z][a-z0-9_]{1,63}$'
          minLength: 2
          maxLength: 64
        description:
          type: string
          minLength: 1
          maxLength: 1000
        when_to_use:
          type: string
          minLength: 1
          maxLength: 1000
        method:
          type: string
          enum: [GET, POST, PUT, PATCH, DELETE]
          maxLength: 6
        url:
          type: string
          format: uri
          pattern: '^https://'
          maxLength: 2048
        allowed_host:
          type: string
          maxLength: 253
        auth:
          $ref: '#/components/schemas/CapabilityAuth'
        safe_headers:
          type: object
          additionalProperties: false
          properties:
            accept:
              type: string
              maxLength: 100
            content-type:
              type: string
              enum: [application/json]
              maxLength: 100
        input_schema:
          $ref: '#/components/schemas/JsonSchemaDocument'
          description: >-
            Bounded JSON Schema for capability arguments. Supported keywords:
            type, properties, required, additionalProperties, items, maxItems,
            minLength, maxLength, enum, const, description, anyOf, oneOf, not,
            and pattern. pattern is accepted only for bounded string schemas
            and uses ECMAScript regular-expression syntax. Use oneOf to require
            exactly one alternative. allOf, $ref, numeric bounds, minItems,
            format, and every other keyword are rejected at import.
        output_schema:
          $ref: '#/components/schemas/JsonSchemaDocument'
          description: >-
            Bounded JSON Schema for capability results. Supported keywords:
            type, properties, required, additionalProperties, items, maxItems,
            minLength, maxLength, enum, const, and description. Composition
            keywords (anyOf, oneOf, not, allOf) and every other keyword are
            rejected so the executor can safely sanitize one explicit shape.
            A host_interaction capability must declare the complete canonical
            status/confirmation/selection schema published in
            capability-handoff; Forgium then validates the sanitized result
            against HostInteractionPreparationResult as well.
        image_interpretation:
          $ref: '#/components/schemas/ImageInterpretation'
          description: >-
            Optional image-assisted extraction contract. It provides partial
            values for input_schema fields when a multipart image is sent;
            input_schema.required remains the source of truth for missing
            fields.
        context_bindings:
          type: object
          maxProperties: 16
          patternProperties:
            '^[a-z][a-z0-9_]{0,127}$':
              $ref: '#/components/schemas/CapabilityContextBinding'
          additionalProperties: false
          description: >-
            Optional declarative handoff metadata for validated conversational
            references. Each key must be a string property in input_schema;
            Forgium uses the accepted reference fields and producer capability
            names only as a bounded provenance check. It never grants
            authorization or bypasses executor validation.
        business_rules:
          type: array
          description: >-
            Bounded routing guidance. Explicit `Do not use for ...` clauses
            are enforced as fail-closed exclusions before executor I/O; they
            cannot authorize or expand a capability.
          maxItems: 50
          items:
            type: string
            maxLength: 1000
        examples:
          type: array
          maxItems: 20
          items:
            type: object
            additionalProperties: true
        side_effect:
          type: string
          enum: [read]
          description: 'Contract 5 capabilities are read-only. Use result_handling: host_interaction for bounded preparation of host-owned operations.'
        requires_approval:
          type: boolean
          const: false
        result_handling:
          type: string
          enum: [model_response, host_interaction]
          description: >-
            `host_interaction` is a read-only, non-retrying, uncached
            preparation capability. Its output schema must declare the closed
            canonical status object with complete confirmation and selection
            properties. The endpoint result is additionally validated against
            the discriminated confirmation/selection/not-found union.
        timeout_ms:
          type: integer
          minimum: 1000
          maximum: 10000
        retry_count:
          type: integer
          minimum: 0
          maximum: 1
        cache_ttl_seconds:
          type: integer
          minimum: 0
          maximum: 86400
    CapabilityAuth:
      type: object
      additionalProperties: false
      required: [type]
      properties:
        type:
          type: string
          enum: [bearer, api_key, basic, none]
          maxLength: 16
        credential_ref:
          type: string
          pattern: '^cred_[A-Za-z0-9_-]{3,120}$'
          maxLength: 128
        header_name:
          type: string
          enum: [x-api-key, x-api-token]
          maxLength: 32
    CapabilityContextBinding:
      type: object
      additionalProperties: false
      required: [entity, accepted_reference_fields, producer_capabilities]
      properties:
        entity:
          type: string
          pattern: '^[a-z][a-z0-9_-]{1,63}$'
          maxLength: 64
        accepted_reference_fields:
          type: array
          minItems: 1
          maxItems: 8
          uniqueItems: true
          items:
            type: string
            pattern: '(^|_)(name|nombre|title|titulo|label|etiqueta)$'
            maxLength: 128
        producer_capabilities:
          type: array
          minItems: 1
          maxItems: 8
          uniqueItems: true
          items:
            type: string
            pattern: '^[a-z][a-z0-9_]{1,63}$'
            maxLength: 64
    JsonSchemaDocument:
      type: object
      description: Bounded JSON Schema document used to validate capability arguments or results; see the field-specific descriptions for the exact keyword matrix.
      additionalProperties: true
    ImageInterpretation:
      type: object
      additionalProperties: false
      required: [document_kinds, extraction_schema]
      properties:
        document_kinds:
          type: array
          minItems: 1
          maxItems: 3
          uniqueItems: true
          description: Supported document categories for image-assisted capability selection.
          items:
            type: string
            enum: [bank_transfer_receipt, invoice, receipt]
        extraction_schema:
          $ref: '#/components/schemas/JsonSchemaDocument'
          description: >-
            Partial object schema for image extraction. It must use
            additionalProperties false, must not declare required fields, and
            every declared field must also exist compatibly in input_schema.
    CapabilityImportResponse:
      type: object
      additionalProperties: false
      required: [capabilities]
      properties:
        capabilities:
          type: array
          maxItems: 50
          items:
            $ref: '#/components/schemas/CapabilityLifecycleResponse'
    CapabilityListResponse:
      type: object
      additionalProperties: false
      required: [capabilities]
      properties:
        capabilities:
          type: array
          maxItems: 1000
          items:
            $ref: '#/components/schemas/CapabilitySummary'
    CapabilitySummary:
      type: object
      unevaluatedProperties: false
      required: [id, name, description, when_to_use, input_schema, output_schema, business_rules, examples, side_effect, requires_approval, revision, lifecycle_status]
      properties:
        id:
          type: string
          maxLength: 80
        name:
          type: string
          maxLength: 64
        description:
          type: string
          maxLength: 1000
        when_to_use:
          type: string
          maxLength: 1000
        input_schema:
          $ref: '#/components/schemas/JsonSchemaDocument'
        output_schema:
          $ref: '#/components/schemas/JsonSchemaDocument'
        image_interpretation:
          $ref: '#/components/schemas/ImageInterpretation'
          description: Optional image-assisted extraction contract published with this capability.
        context_bindings:
          type: object
          maxProperties: 16
          patternProperties:
            '^[a-z][a-z0-9_]{0,127}$':
              $ref: '#/components/schemas/CapabilityContextBinding'
          additionalProperties: false
          description: Optional provenance metadata for validated conversational reference handoff.
        business_rules:
          type: array
          description: >-
            Bounded routing guidance. Explicit `Do not use for ...` clauses
            are enforced as fail-closed exclusions before executor I/O; they
            cannot authorize or expand a capability.
          maxItems: 50
          items:
            type: string
            maxLength: 1000
        examples:
          type: array
          maxItems: 20
          items:
            type: object
            additionalProperties: true
        side_effect:
          type: string
          enum: [read]
          description: Contract 5 capabilities are read-only.
        requires_approval:
          type: boolean
          const: false
        result_handling:
          type: string
          enum: [model_response, host_interaction]
          description: The deterministic result handling mode persisted for this capability.
        revision:
          type: integer
          minimum: 1
        lifecycle_status:
          type: string
          enum: [active, disabled]
          maxLength: 16
        credential_configured:
          type: boolean
        updated_at:
          type: string
          format: date-time
          maxLength: 64
    CapabilityDetailResponse:
      allOf:
        - $ref: '#/components/schemas/CapabilitySummary'
        - type: object
          unevaluatedProperties: false
          required: [method, url, revisions]
          properties:
            method:
              type: string
              maxLength: 6
            url:
              type: string
              format: uri
              maxLength: 2048
            revisions:
              type: array
              maxItems: 100
              items:
                $ref: '#/components/schemas/CapabilityRevision'
    CapabilityRevision:
      type: object
      additionalProperties: false
      required: [revision, change_type, actor_type, actor_id, created_at]
      properties:
        revision:
          type: integer
          minimum: 1
        change_type:
          type: string
          maxLength: 32
        actor_type:
          type: string
          maxLength: 32
        actor_id:
          type: string
          maxLength: 128
        created_at:
          type: string
          format: date-time
          maxLength: 64
    CapabilityPatchRequest:
      type: object
      additionalProperties: false
      required: [capability]
      properties:
        domain:
          type: string
          maxLength: 64
        capability:
          $ref: '#/components/schemas/CapabilityDefinition'
    CapabilityTestRequest:
      type: object
      additionalProperties: false
      required: [arguments]
      properties:
        arguments:
          type: object
          additionalProperties: true
          description: >-
            Arguments sent to the real active endpoint at the current revision. Forgium signs a
            deterministic test scope with subject_id forgium-contract-test and
            conversation_id contract-test:{capabilityId}. Contract tests have no
            declarative expectation block; any valid output branch passes.
    CapabilityTestResponse:
      type: object
      additionalProperties: false
      required: [status, capability_id, revision]
      properties:
        status:
          type: string
          enum: [passed, failed]
          maxLength: 16
        capability_id:
          type: string
          maxLength: 80
        revision:
          type: integer
          minimum: 1
        error_code:
          type: string
          maxLength: 100
    CapabilityLifecycleResponse:
      type: object
      additionalProperties: false
      required: [id, lifecycle_status, revision]
      properties:
        id:
          type: string
          maxLength: 80
        lifecycle_status:
          type: string
          enum: [active, disabled]
          maxLength: 16
        revision:
          type: integer
          minimum: 1

    PublicPromptErrorResponse:
      type: object
      additionalProperties: false
      required: [error]
      properties:
        error:
          type: object
          additionalProperties: false
          required: [code, message]
          properties:
            code:
              type: string
              maxLength: 100
            message:
              type: string
              maxLength: 100
    ErrorResponse:
      type: object
      additionalProperties: false
      required: [error, code]
      properties:
        error:
          type: string
          maxLength: 500
        code:
          type: string
          maxLength: 100
    HttpErrorResponse:
      type: object
      additionalProperties: false
      required: [error]
      properties:
        error:
          type: object
          additionalProperties: false
          required: [code, message]
          properties:
            code:
              type: string
              maxLength: 100
            message:
              type: string
              maxLength: 500
            details:
              type: object
              additionalProperties: true

    MetaWebhookResponse:
      type: object
      additionalProperties: false
      required: [status, accepted, duplicates, ignored]
      properties:
        status:
          type: string
          enum: [accepted, ignored]
        accepted:
          type: integer
          minimum: 0
        duplicates:
          type: integer
          minimum: 0
        ignored:
          type: integer
          minimum: 0

  responses:
    PublicPromptValidation:
      description: The JSON body or schema-declared input is invalid.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/PublicPromptErrorResponse'
    PublicPromptForbidden:
      description: The browser origin or Turnstile verification is not accepted.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/PublicPromptErrorResponse'
    PublicPromptTooLarge:
      description: The request body exceeds the configured boundary.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/PublicPromptErrorResponse'
    PublicPromptRateLimited:
      description: The integration/IP burst limit or daily quota was exceeded.
      headers:
        Retry-After:
          schema:
            type: string
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/PublicPromptErrorResponse'
    PublicPromptModelFailure:
      description: Workers AI was unavailable or returned schema-invalid JSON.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/PublicPromptErrorResponse'
    Unauthorized:
      description: API key is missing, invalid, expired, or revoked.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          example:
            error: Invalid API key
            code: UNAUTHORIZED
    ValidationError:
      description: Request body or parameter is invalid.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          example:
            error: VALIDATION_ERROR
            code: VALIDATION_ERROR
    HttpMethodNotAllowed:
      description: HTTP method is not supported for this route.
      headers:
        Allow:
          schema:
            type: string
            maxLength: 100
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/HttpErrorResponse'
    HttpDuplicateEvent:
      description: The message event was already processed.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/HttpErrorResponse'
    HttpValidationError:
      description: The HTTP channel request is invalid.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/HttpErrorResponse'
    HttpRequestTooLarge:
      description: The HTTP channel body exceeds the boundary limit.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/HttpErrorResponse'
    HttpRuntimeError:
      description: The agent runtime could not produce a response.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/HttpErrorResponse'
    MetaWebhookForbidden:
      description: The Meta webhook verification token is invalid.
      content:
        text/plain:
          schema: { type: string }
    MetaWebhookBadRequest:
      description: The Meta webhook body is invalid JSON or has an unsupported shape.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
    MetaWebhookUnauthorized:
      description: The Meta webhook signature is missing or invalid.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
    MetaWebhookPhoneNotRegistered:
      description: The phone number in the event is not registered to a Forgium account.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
    MetaWebhookTooLarge:
      description: The Meta webhook body exceeds the 1 MiB boundary.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
    NotFound:
      description: Capability does not exist for this API key.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
    PolicyNotFound:
      description: No agent policy exists for this API key.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          example:
            error: POLICY_NOT_FOUND
            code: POLICY_NOT_FOUND
    PolicyMethodNotAllowed:
      description: The agent policy endpoint accepts only GET, PUT, and DELETE.
      headers:
        Allow:
          schema:
            type: string
            enum: [GET, PUT, DELETE]
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          example:
            error: METHOD_NOT_ALLOWED
            code: METHOD_NOT_ALLOWED
    PolicyRequestTooLarge:
      description: The agent policy JSON body exceeds 16 KiB.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          example:
            error: REQUEST_TOO_LARGE
            code: REQUEST_TOO_LARGE
    DiagnosticsMethodNotAllowed:
      description: The diagnostics settings endpoint accepts only GET and PUT; the capability-gap endpoint accepts only GET.
      headers:
        Allow:
          schema:
            type: string
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          example:
            error: METHOD_NOT_ALLOWED
            code: METHOD_NOT_ALLOWED
    DiagnosticsRequestTooLarge:
      description: The diagnostic settings JSON body exceeds 1 KiB.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          example:
            error: REQUEST_TOO_LARGE
            code: REQUEST_TOO_LARGE
    Conflict:
      description: The requested operation conflicts with the current capability state.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
    Gone:
      description: The host interaction expired before the report was accepted.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
    RequestTooLarge:
      description: The request body exceeds the bounded operation-result limit.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
    UpstreamFailure:
      description: The capability endpoint failed; no automatic mutation retry is performed.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
    ServiceUnavailable:
      description: The requested service is temporarily unavailable.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
