openapi: 3.1.0
info:
  title: Weirgate API
  version: 2026-07-18
  description: >-
    Frozen Wave-1 contract for the Weirgate data and management planes. The
    Weirgate-Api-Version response header is the calendar-date contract version.
    Capability-named features are the default; provider/model fields appear in the
    catalog only for features that deliberately opt into provider visibility.
servers:
  - url: https://api.weirgate.com
    description: Production
tags:
  - name: Data plane
  - name: Configuration
  - name: Keys
  - name: Team
  - name: Webhooks
  - name: Money
  - name: Billing
  - name: Rules
  - name: Privacy
  - name: Operations
  - name: Docs
x-weirgate-api-version: 2026-07-18
x-weirgate-id-formats:
  request_id: req_<uuid-v4>
  proposal_id: prop_<uuid-v4>
  management_key_value: wgk_<64-lowercase-hex>; reveal once and never log
  session_grant_code: wgc_<64-lowercase-hex>; single use, five-minute lifetime
x-weirgate-dev-auth:
  internal: true
  production: false
  format: Bearer dev:<external-user-id>
  description: Accepted only by app configurations whose auth.mode is dev; tests and sandbox only.
x-weirgate-error-registry:
  invalid_request: { status: 400, response: '#/components/responses/ErrorInvalidRequest' }
  invalid_token: { status: 401, response: '#/components/responses/ErrorInvalidToken' }
  user_provider_key_required: { status: 422, response: '#/components/responses/ErrorUserProviderKeyRequired' }
  user_provider_key_invalid: { status: 422, response: '#/components/responses/ErrorUserProviderKeyInvalid' }
  insufficient_scope: { status: 403, response: '#/components/responses/ErrorInsufficientScope' }
  out_of_allowance: { status: 402, response: '#/components/responses/ErrorOutOfAllowance' }
  abuse_blocked: { status: 403, response: '#/components/responses/ErrorAbuseBlocked' }
  feature_disabled: { status: 403, response: '#/components/responses/ErrorFeatureDisabled' }
  feature_not_found: { status: 404, response: '#/components/responses/ErrorFeatureNotFound' }
  resource_not_found: { status: 404, response: '#/components/responses/ErrorResourceNotFound' }
  provider_policy_blocked: { status: 403, response: '#/components/responses/ErrorProviderPolicyBlocked' }
  output_contract_unsupported: { status: 422, response: '#/components/responses/ErrorOutputContractUnsupported' }
  output_contract_violation: { status: 502, response: '#/components/responses/ErrorOutputContractViolation' }
  proposal_stale: { status: 409, response: '#/components/responses/ErrorProposalStale' }
  rate_limited: { status: 429, response: '#/components/responses/ErrorRateLimited' }
  telemetry_request_unavailable: { status: 404, response: '#/components/responses/ErrorTelemetryRequestUnavailable' }
  provider_unavailable: { status: 502, response: '#/components/responses/ErrorProviderUnavailable' }
  internal: { status: 500, response: '#/components/responses/ErrorInternal' }
x-weirgate-webhook-signature:
  algorithm: HMAC-SHA256
  header: Weirgate-Signature
  format: v1=<lowercase-hex>
  signed_payload: <Weirgate-Timestamp>.<exact raw request body>
  delivery: at-least-once
  ordering: best-effort per user

paths:
  /healthz:
    get:
      tags: [Operations]
      operationId: getHealth
      security: []
      summary: Read process health and runtime mode
      responses:
        '200':
          description: Healthy
          headers:
            Weirgate-Api-Version: { $ref: '#/components/headers/WeirgateApiVersion' }
            X-Weirgate-Request-Id: { $ref: '#/components/headers/RequestId' }
          content:
            application/json:
              schema: { $ref: '#/components/schemas/Health' }

  /v1/chat/completions:
    post:
      tags: [Data plane]
      operationId: createChatCompletion
      summary: Create a server-routed chat completion
      description: >-
        OpenAI-compatible request/response envelope. Client-supplied model and token
        ceilings are ignored; X-Feature-Id owns routing and output limits. When stream
        is true the response is SSE and follows x-weirgate-sse.
      security:
        - EndUserBearer: []
        - EndUserBearer: []
          UserProviderKey: []
      parameters:
        - { $ref: '#/components/parameters/XAppId' }
        - { $ref: '#/components/parameters/XFeatureId' }
        - { $ref: '#/components/parameters/XIdempotencyKey' }
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/ChatCompletionRequest' }
      x-weirgate-sse:
        media_type: text/event-stream
        data_schema: { $ref: '#/components/schemas/ChatCompletionChunk' }
        final_chunk:
          usage_required: true
          finish_reason_required: true
        terminator: 'data: [DONE]'
        mid_stream_error: >-
          A premature upstream termination closes/errors the stream without a guessed
          usage chunk; the reservation is refunded. Typed HTTP errors are available
          only before response headers are committed.
      responses:
        '200':
          description: Completion or completion stream
          headers:
            Weirgate-Api-Version: { $ref: '#/components/headers/WeirgateApiVersion' }
            X-Weirgate-Request-Id: { $ref: '#/components/headers/RequestId' }
            X-Credits-Remaining: { $ref: '#/components/headers/CreditsRemaining' }
          content:
            application/json:
              schema: { $ref: '#/components/schemas/ChatCompletion' }
            text/event-stream:
              schema:
                type: string
                description: SSE frames described by x-weirgate-sse.
        '400': { $ref: '#/components/responses/InvalidRequest' }
        '401': { $ref: '#/components/responses/InvalidToken' }
        '402': { $ref: '#/components/responses/OutOfAllowance' }
        '403': { $ref: '#/components/responses/ForbiddenDataPlane' }
        '404': { $ref: '#/components/responses/NotFoundDataPlane' }
        '422': { $ref: '#/components/responses/UnprocessableDataPlane' }
        '429': { $ref: '#/components/responses/RateLimited' }
        '500': { $ref: '#/components/responses/Internal' }
        '502': { $ref: '#/components/responses/UpstreamFailure' }

  /v1/embeddings:
    post:
      tags: [Data plane]
      operationId: createEmbedding
      summary: Create server-routed embeddings
      security:
        - EndUserBearer: []
        - EndUserBearer: []
          UserProviderKey: []
      parameters:
        - { $ref: '#/components/parameters/XAppId' }
        - { $ref: '#/components/parameters/XFeatureId' }
        - { $ref: '#/components/parameters/XIdempotencyKey' }
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/EmbeddingRequest' }
      responses:
        '200':
          description: Embeddings
          headers:
            Weirgate-Api-Version: { $ref: '#/components/headers/WeirgateApiVersion' }
            X-Weirgate-Request-Id: { $ref: '#/components/headers/RequestId' }
            X-Credits-Remaining: { $ref: '#/components/headers/CreditsRemaining' }
          content:
            application/json:
              schema: { $ref: '#/components/schemas/EmbeddingResponse' }
        '400': { $ref: '#/components/responses/InvalidRequest' }
        '401': { $ref: '#/components/responses/InvalidToken' }
        '402': { $ref: '#/components/responses/OutOfAllowance' }
        '403': { $ref: '#/components/responses/ForbiddenDataPlane' }
        '404': { $ref: '#/components/responses/NotFoundDataPlane' }
        '422': { $ref: '#/components/responses/UnprocessableDataPlane' }
        '429': { $ref: '#/components/responses/RateLimited' }
        '500': { $ref: '#/components/responses/Internal' }
        '502': { $ref: '#/components/responses/UpstreamFailure' }

  /v1/features:
    get:
      tags: [Data plane]
      operationId: listFeatures
      summary: Read the authenticated app's enabled feature catalog
      security: [{ EndUserBearer: [] }]
      parameters:
        - { $ref: '#/components/parameters/XAppId' }
        - name: If-None-Match
          in: header
          schema: { type: string }
      responses:
        '200':
          description: Private app catalog
          headers:
            Weirgate-Api-Version: { $ref: '#/components/headers/WeirgateApiVersion' }
            X-Weirgate-Request-Id: { $ref: '#/components/headers/RequestId' }
            ETag: { schema: { type: string } }
            Cache-Control: { schema: { type: string, const: 'private, max-age=60, must-revalidate' } }
            Vary: { schema: { type: string, const: 'Authorization, X-App-Id' } }
          content:
            application/json:
              schema: { $ref: '#/components/schemas/FeatureCatalog' }
        '304':
          description: Catalog is unchanged
          headers:
            Weirgate-Api-Version: { $ref: '#/components/headers/WeirgateApiVersion' }
            X-Weirgate-Request-Id: { $ref: '#/components/headers/RequestId' }
            ETag: { schema: { type: string } }
        '400': { $ref: '#/components/responses/InvalidRequest' }
        '401': { $ref: '#/components/responses/InvalidToken' }
        '404': { $ref: '#/components/responses/ResourceNotFound' }

  /v1/balance:
    get:
      tags: [Data plane]
      operationId: getBalance
      summary: Read balance, lazily provisioning the app/user row
      description: The monthly allowance grant remains lazy until the first metered call.
      security: [{ EndUserBearer: [] }]
      parameters:
        - { $ref: '#/components/parameters/XAppId' }
      responses:
        '200':
          description: Current balance
          headers:
            Weirgate-Api-Version: { $ref: '#/components/headers/WeirgateApiVersion' }
            X-Weirgate-Request-Id: { $ref: '#/components/headers/RequestId' }
          content:
            application/json:
              schema: { $ref: '#/components/schemas/Balance' }
        '400': { $ref: '#/components/responses/InvalidRequest' }
        '401': { $ref: '#/components/responses/InvalidToken' }
        '404': { $ref: '#/components/responses/ResourceNotFound' }

  /v1/telemetry/client:
    post:
      tags: [Data plane]
      operationId: ingestClientTelemetry
      summary: Accept optional user-bound client timing
      description: Created, duplicate, and cap-dropped events intentionally share the same 202 response.
      security: [{ EndUserBearer: [] }]
      parameters:
        - { $ref: '#/components/parameters/XAppId' }
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/ClientTelemetryInput' }
      responses:
        '202':
          description: Accepted without disclosing deduplication or cap state
          headers:
            Weirgate-Api-Version: { $ref: '#/components/headers/WeirgateApiVersion' }
            X-Weirgate-Request-Id: { $ref: '#/components/headers/RequestId' }
          content:
            application/json:
              schema: { $ref: '#/components/schemas/Accepted' }
        '400': { $ref: '#/components/responses/InvalidRequest' }
        '401': { $ref: '#/components/responses/InvalidToken' }
        '404': { $ref: '#/components/responses/TelemetryRequestUnavailable' }

  /v1/stripe/webhook:
    post:
      tags: [Billing]
      operationId: receiveStripeWebhook
      summary: Receive a signature-verified idempotent Stripe billing event
      description: >-
        The exact raw body is verified before parsing. Durable event-id
        deduplication precedes audited tenant subscription state changes.
        This path never invokes app-user grant or reversal primitives.
      security: []
      parameters:
        - name: Stripe-Signature
          in: header
          required: true
          x-sensitive: true
          schema: { type: string }
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/GenericObject' }
      responses:
        '200':
          description: Event received or already processed
          headers:
            Weirgate-Api-Version: { $ref: '#/components/headers/WeirgateApiVersion' }
            X-Weirgate-Request-Id: { $ref: '#/components/headers/RequestId' }
          content:
            application/json:
              schema:
                type: object
                additionalProperties: false
                required: [received, duplicate]
                properties:
                  received: { type: boolean, const: true }
                  duplicate: { type: boolean }
        '400': { $ref: '#/components/responses/InvalidRequest' }
        '500': { $ref: '#/components/responses/Internal' }
        '502': { $ref: '#/components/responses/UpstreamFailure' }

  /v1/admin/whoami:
    get:
      tags: [Keys]
      operationId: getManagementIdentity
      summary: Inspect the authenticated management grant
      security: [{ AdminKey: [] }, { ClerkSession: [] }]
      responses:
        '200': { $ref: '#/components/responses/ManagementIdentityOk' }
        '401': { $ref: '#/components/responses/InvalidToken' }

  /v1/admin/session:
    post:
      tags: [Keys]
      operationId: provisionDeveloperSession
      summary: Idempotently provision and read the Clerk organization tenant
      description: Developer identity is distinct from every app's end-user auth configuration.
      security: [{ ClerkSession: [] }]
      responses:
        '200': { $ref: '#/components/responses/DeveloperSessionOk' }
        '201': { $ref: '#/components/responses/DeveloperSessionOk' }
        '401': { $ref: '#/components/responses/InvalidToken' }
        '403': { $ref: '#/components/responses/InsufficientScope' }

  /v1/admin/billing:
    get:
      tags: [Billing]
      operationId: getTenantBilling
      summary: Read tenant tier, provisional usage, invoices, and append-only statements
      security: [{ AdminKey: [] }, { ClerkSession: [] }]
      parameters:
        - { $ref: '#/components/parameters/TenantIdQuery' }
      responses:
        '200':
          description: Tenant billing summary
          headers:
            Weirgate-Api-Version: { $ref: '#/components/headers/WeirgateApiVersion' }
            X-Weirgate-Request-Id: { $ref: '#/components/headers/RequestId' }
          content:
            application/json:
              schema: { $ref: '#/components/schemas/BillingSummary' }
        '401': { $ref: '#/components/responses/InvalidToken' }
        '403': { $ref: '#/components/responses/InsufficientScope' }
        '404': { $ref: '#/components/responses/ResourceNotFound' }

  /v1/admin/billing/checkout:
    post:
      tags: [Billing]
      operationId: createBillingCheckout
      summary: Create a Stripe-hosted subscription checkout session
      description: Requires a tenant owner with a recent second factor. Tier state changes only from verified Stripe webhooks.
      security: [{ ClerkSession: [] }]
      parameters:
        - { $ref: '#/components/parameters/XIdempotencyKey' }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              additionalProperties: false
              required: [tier]
              properties:
                tier: { type: string, enum: [pro, scale] }
      responses:
        '201':
          description: Stripe Checkout redirect
          headers:
            Weirgate-Api-Version: { $ref: '#/components/headers/WeirgateApiVersion' }
            X-Weirgate-Request-Id: { $ref: '#/components/headers/RequestId' }
          content:
            application/json:
              schema: { $ref: '#/components/schemas/BillingRedirect' }
        '400': { $ref: '#/components/responses/InvalidRequest' }
        '401': { $ref: '#/components/responses/InvalidToken' }
        '403': { $ref: '#/components/responses/InsufficientScope' }
        '502': { $ref: '#/components/responses/UpstreamFailure' }

  /v1/admin/billing/portal:
    post:
      tags: [Billing]
      operationId: createBillingPortal
      summary: Create a Stripe-hosted customer portal session
      description: Requires a tenant owner with a recent second factor.
      security: [{ ClerkSession: [] }]
      responses:
        '201':
          description: Stripe Portal redirect
          headers:
            Weirgate-Api-Version: { $ref: '#/components/headers/WeirgateApiVersion' }
            X-Weirgate-Request-Id: { $ref: '#/components/headers/RequestId' }
          content:
            application/json:
              schema: { $ref: '#/components/schemas/BillingPortalRedirect' }
        '401': { $ref: '#/components/responses/InvalidToken' }
        '403': { $ref: '#/components/responses/InsufficientScope' }
        '502': { $ref: '#/components/responses/UpstreamFailure' }

  /v1/admin/billing/catalog/setup:
    post:
      tags: [Billing]
      operationId: setupStripeTestCatalog
      summary: Idempotently create the approved Stripe test-mode catalog
      security: [{ AdminKey: [] }]
      responses:
        '201':
          description: Test-mode products, prices, coupons, and portal configuration
          headers:
            Weirgate-Api-Version: { $ref: '#/components/headers/WeirgateApiVersion' }
            X-Weirgate-Request-Id: { $ref: '#/components/headers/RequestId' }
          content:
            application/json:
              schema: { $ref: '#/components/schemas/StripeTestCatalog' }
        '401': { $ref: '#/components/responses/InvalidToken' }
        '403': { $ref: '#/components/responses/InsufficientScope' }
        '500': { $ref: '#/components/responses/Internal' }
        '502': { $ref: '#/components/responses/UpstreamFailure' }

  /v1/admin/billing/design-partner:
    post:
      tags: [Billing]
      operationId: enrollBillingDesignPartner
      summary: Enroll a tenant in the approved six-month design-partner schedule
      security: [{ AdminKey: [] }]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              additionalProperties: false
              required: [tenant_id, started_at]
              properties:
                tenant_id: { $ref: '#/components/schemas/TenantId' }
                started_at: { type: string, format: date-time }
      responses:
        '200':
          description: Enrollment timestamp
          headers:
            Weirgate-Api-Version: { $ref: '#/components/headers/WeirgateApiVersion' }
            X-Weirgate-Request-Id: { $ref: '#/components/headers/RequestId' }
          content:
            application/json:
              schema:
                type: object
                additionalProperties: false
                required: [tenant_id, started_at]
                properties:
                  tenant_id: { $ref: '#/components/schemas/TenantId' }
                  started_at: { type: string, format: date-time }
        '400': { $ref: '#/components/responses/InvalidRequest' }
        '401': { $ref: '#/components/responses/InvalidToken' }
        '403': { $ref: '#/components/responses/InsufficientScope' }
        '404': { $ref: '#/components/responses/ResourceNotFound' }

  /v1/admin/billing/statements/close:
    post:
      tags: [Billing]
      operationId: closeTenantBillingStatement
      summary: Close one UTC month and post paid-tier overage invoice items
      security: [{ AdminKey: [] }]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              additionalProperties: false
              required: [tenant_id, period_start, period_end, reason, reconciliation_case_id]
              properties:
                tenant_id: { $ref: '#/components/schemas/TenantId' }
                period_start: { type: string, format: date-time }
                period_end: { type: string, format: date-time }
                reason: { type: string, minLength: 1 }
                reconciliation_case_id: { type: string, minLength: 1 }
      responses:
        '201':
          description: Append-only draft/final records and any Stripe invoice record
          headers:
            Weirgate-Api-Version: { $ref: '#/components/headers/WeirgateApiVersion' }
            X-Weirgate-Request-Id: { $ref: '#/components/headers/RequestId' }
          content:
            application/json:
              schema: { $ref: '#/components/schemas/BillingStatementClose' }
        '400': { $ref: '#/components/responses/InvalidRequest' }
        '401': { $ref: '#/components/responses/InvalidToken' }
        '403': { $ref: '#/components/responses/InsufficientScope' }
        '404': { $ref: '#/components/responses/ResourceNotFound' }
        '500': { $ref: '#/components/responses/Internal' }
        '502': { $ref: '#/components/responses/UpstreamFailure' }

  /v1/admin/billing/statements/correct:
    post:
      tags: [Billing]
      operationId: correctFinalizedTenantBillingStatement
      summary: Append a closed-month correction and credit matching finalized Stripe lines
      security: [{ AdminKey: [] }]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              additionalProperties: false
              required:
                - tenant_id
                - period_start
                - period_end
                - corrected_calls
                - corrected_tokens
                - credit_calls
                - credit_tokens
                - reason
                - reconciliation_case_id
              properties:
                tenant_id: { $ref: '#/components/schemas/TenantId' }
                period_start: { type: string, format: date-time }
                period_end: { type: string, format: date-time }
                corrected_calls: { type: integer, minimum: 0 }
                corrected_tokens: { type: integer, minimum: 0 }
                credit_calls: { type: integer, minimum: 0 }
                credit_tokens: { type: integer, minimum: 0 }
                reason: { type: string, minLength: 1 }
                reconciliation_case_id: { type: string, minLength: 1 }
      responses:
        '201':
          description: Append-only correction and any same-line Stripe credit note record
          headers:
            Weirgate-Api-Version: { $ref: '#/components/headers/WeirgateApiVersion' }
            X-Weirgate-Request-Id: { $ref: '#/components/headers/RequestId' }
          content:
            application/json:
              schema: { $ref: '#/components/schemas/BillingStatementCorrection' }
        '400': { $ref: '#/components/responses/InvalidRequest' }
        '401': { $ref: '#/components/responses/InvalidToken' }
        '403': { $ref: '#/components/responses/InsufficientScope' }
        '404': { $ref: '#/components/responses/ResourceNotFound' }
        '500': { $ref: '#/components/responses/Internal' }
        '502': { $ref: '#/components/responses/UpstreamFailure' }

  /v1/admin/session-grants:
    post:
      tags: [Keys]
      operationId: authorizeSessionGrant
      summary: Authorize a bounded MCP or CLI grant from a developer session
      description: Defaults to propose scope; apply requires a recent Clerk second factor.
      security: [{ ClerkSession: [] }]
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/SessionGrantInput' }
      responses:
        '201': { $ref: '#/components/responses/SessionGrantAuthorized' }
        '400': { $ref: '#/components/responses/InvalidRequest' }
        '401': { $ref: '#/components/responses/InvalidToken' }
        '403': { $ref: '#/components/responses/InsufficientScope' }
        '404': { $ref: '#/components/responses/ResourceNotFound' }

  /v1/admin/members:
    get:
      tags: [Team]
      operationId: listDeveloperMembers
      summary: List Weirgate-owned tenant memberships and roles
      security: [{ ClerkSession: [] }]
      responses:
        '200': { $ref: '#/components/responses/DeveloperMemberListOk' }
        '401': { $ref: '#/components/responses/InvalidToken' }
        '403': { $ref: '#/components/responses/InsufficientScope' }

  /v1/admin/members/{developerIdentityId}:
    patch:
      tags: [Team]
      operationId: updateDeveloperMemberRole
      summary: Change a Weirgate-owned tenant role
      description: Owner-only. Requires a recent second factor and preserves at least one tenant owner.
      security: [{ ClerkSession: [] }]
      parameters:
        - name: developerIdentityId
          in: path
          required: true
          schema: { type: string }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              additionalProperties: false
              required: [role]
              properties:
                role: { type: string, enum: [owner, member] }
      responses:
        '200': { $ref: '#/components/responses/DeveloperMemberOk' }
        '400': { $ref: '#/components/responses/InvalidRequest' }
        '401': { $ref: '#/components/responses/InvalidToken' }
        '403': { $ref: '#/components/responses/InsufficientScope' }
        '404': { $ref: '#/components/responses/ResourceNotFound' }

  /v1/session-grants/exchange:
    post:
      tags: [Keys]
      operationId: exchangeSessionGrant
      summary: Exchange a single-use consent code for a reveal-once bounded key
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              additionalProperties: false
              required: [code]
              properties:
                code: { type: string, pattern: '^wgc_[a-f0-9]{64}$' }
      responses:
        '201':
          description: Bounded key metadata plus reveal-once value
          headers:
            Weirgate-Api-Version: { $ref: '#/components/headers/WeirgateApiVersion' }
            X-Weirgate-Request-Id: { $ref: '#/components/headers/RequestId' }
          content:
            application/json:
              schema: { $ref: '#/components/schemas/MintedManagementKey' }
        '400': { $ref: '#/components/responses/InvalidRequest' }
        '401': { $ref: '#/components/responses/InvalidToken' }

  /v1/admin/docs/search:
    get:
      tags: [Docs]
      operationId: searchAgentDocs
      summary: Search the authenticated canonical Markdown corpus
      security: [{ AdminKey: [] }]
      parameters:
        - name: query
          in: query
          required: true
          schema: { type: string, minLength: 1 }
        - name: section
          in: query
          schema: { type: string }
        - { $ref: '#/components/parameters/LimitQuery' }
      responses:
        '200': { $ref: '#/components/responses/AgentDocSearchOk' }
        '400': { $ref: '#/components/responses/InvalidRequest' }
        '401': { $ref: '#/components/responses/InvalidToken' }
        '403': { $ref: '#/components/responses/InsufficientScope' }

  /v1/admin/docs/{pageId}:
    get:
      tags: [Docs]
      operationId: getAgentDoc
      summary: Read one canonical Markdown page
      security: [{ AdminKey: [] }]
      parameters:
        - name: pageId
          in: path
          required: true
          schema: { type: string, pattern: '^[a-z0-9-]+$' }
      responses:
        '200': { $ref: '#/components/responses/AgentDocOk' }
        '401': { $ref: '#/components/responses/InvalidToken' }
        '403': { $ref: '#/components/responses/InsufficientScope' }
        '404': { $ref: '#/components/responses/ResourceNotFound' }

  /v1/admin/auth-presets:
    get:
      tags: [Configuration]
      operationId: listAuthPresets
      summary: List non-secret Firebase, Supabase, Clerk, and custom JWKS templates
      security: [{ AdminKey: [] }, { ClerkSession: [] }]
      responses:
        '200': { $ref: '#/components/responses/AuthPresetListOk' }
        '401': { $ref: '#/components/responses/InvalidToken' }
        '403': { $ref: '#/components/responses/InsufficientScope' }

  /v1/admin/provider-policy:
    get:
      tags: [Operations]
      operationId: getProviderPolicy
      summary: Read the platform-owned provider policy registry
      x-weirgate-internal: true
      security: [{ AdminKey: [] }]
      responses:
        '200': { $ref: '#/components/responses/ProviderPolicyOk' }
        '401': { $ref: '#/components/responses/InvalidToken' }
        '403': { $ref: '#/components/responses/InsufficientScope' }
    put:
      tags: [Operations]
      operationId: replaceProviderPolicy
      summary: Replace the platform-owned policy registry
      x-weirgate-internal: true
      security: [{ AdminKey: [] }]
      requestBody: { $ref: '#/components/requestBodies/ProviderPolicyBody' }
      responses:
        '200': { $ref: '#/components/responses/ProviderPolicyOk' }
        '400': { $ref: '#/components/responses/InvalidRequest' }
        '401': { $ref: '#/components/responses/InvalidToken' }
        '403': { $ref: '#/components/responses/InsufficientScope' }

  /v1/admin/provider-keys:
    get:
      tags: [Keys]
      operationId: listProviderKeys
      summary: List write-only provider-key slot metadata
      security: [{ AdminKey: [] }, { ClerkSession: [] }]
      parameters:
        - { $ref: '#/components/parameters/TenantIdQuery' }
        - { $ref: '#/components/parameters/AppIdQuery' }
      responses:
        '200': { $ref: '#/components/responses/ProviderKeyListOk' }
        '400': { $ref: '#/components/responses/InvalidRequest' }
        '401': { $ref: '#/components/responses/InvalidToken' }
        '403': { $ref: '#/components/responses/InsufficientScope' }
        '404': { $ref: '#/components/responses/ResourceNotFound' }

  /v1/admin/provider-keys/{provider}:
    parameters:
      - { $ref: '#/components/parameters/ProviderPath' }
      - { $ref: '#/components/parameters/TenantIdQuery' }
    put:
      tags: [Keys]
      operationId: putProviderKey
      summary: Create or replace a write-only provider key
      description: Clerk developer sessions require a recent second factor.
      security: [{ AdminKey: [] }, { ClerkSession: [] }]
      requestBody: { $ref: '#/components/requestBodies/ProviderKeyBody' }
      responses:
        '200': { $ref: '#/components/responses/ProviderKeyOk' }
        '201': { $ref: '#/components/responses/ProviderKeyOk' }
        '400': { $ref: '#/components/responses/InvalidRequest' }
        '401': { $ref: '#/components/responses/InvalidToken' }
        '403': { $ref: '#/components/responses/InsufficientScope' }
        '404': { $ref: '#/components/responses/ResourceNotFound' }
    delete:
      tags: [Keys]
      operationId: revokeProviderKey
      summary: Soft-revoke a provider key
      description: Clerk developer sessions require a recent second factor.
      security: [{ AdminKey: [] }, { ClerkSession: [] }]
      responses:
        '200': { $ref: '#/components/responses/ProviderKeyOk' }
        '400': { $ref: '#/components/responses/InvalidRequest' }
        '401': { $ref: '#/components/responses/InvalidToken' }
        '403': { $ref: '#/components/responses/InsufficientScope' }
        '404': { $ref: '#/components/responses/ResourceNotFound' }

  /v1/admin/provider-keys/{provider}/rotate:
    parameters:
      - { $ref: '#/components/parameters/ProviderPath' }
      - { $ref: '#/components/parameters/TenantIdQuery' }
    post:
      tags: [Keys]
      operationId: rotateProviderKey
      summary: Rotate a write-only provider key
      description: Clerk developer sessions require a recent second factor.
      security: [{ AdminKey: [] }, { ClerkSession: [] }]
      requestBody: { $ref: '#/components/requestBodies/ProviderKeyBody' }
      responses:
        '200': { $ref: '#/components/responses/ProviderKeyOk' }
        '400': { $ref: '#/components/responses/InvalidRequest' }
        '401': { $ref: '#/components/responses/InvalidToken' }
        '403': { $ref: '#/components/responses/InsufficientScope' }
        '404': { $ref: '#/components/responses/ResourceNotFound' }

  /v1/admin/provider-keys/{provider}/health:
    parameters:
      - { $ref: '#/components/parameters/ProviderPath' }
      - { $ref: '#/components/parameters/TenantIdQuery' }
      - { $ref: '#/components/parameters/AppIdQuery' }
    post:
      tags: [Keys]
      operationId: probeProviderKey
      summary: Run a non-billable provider metadata probe
      security: [{ AdminKey: [] }, { ClerkSession: [] }]
      responses:
        '200': { $ref: '#/components/responses/ProviderKeyOk' }
        '400': { $ref: '#/components/responses/InvalidRequest' }
        '401': { $ref: '#/components/responses/InvalidToken' }
        '403': { $ref: '#/components/responses/InsufficientScope' }
        '404': { $ref: '#/components/responses/ResourceNotFound' }
        '500': { $ref: '#/components/responses/Internal' }

  /v1/admin/keys:
    post:
      tags: [Keys]
      operationId: mintManagementKey
      summary: Mint and reveal a bounded management key once
      security: [{ AdminKey: [] }, { ClerkSession: [] }]
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/ManagementKeyMintInput' }
      responses:
        '201':
          description: Key metadata plus reveal-once value
          headers:
            Weirgate-Api-Version: { $ref: '#/components/headers/WeirgateApiVersion' }
            X-Weirgate-Request-Id: { $ref: '#/components/headers/RequestId' }
          content:
            application/json:
              schema: { $ref: '#/components/schemas/MintedManagementKey' }
        '400': { $ref: '#/components/responses/InvalidRequest' }
        '401': { $ref: '#/components/responses/InvalidToken' }
        '403': { $ref: '#/components/responses/InsufficientScope' }
        '404': { $ref: '#/components/responses/ResourceNotFound' }
    get:
      tags: [Keys]
      operationId: listManagementKeys
      summary: List bounded management-key metadata
      security: [{ AdminKey: [] }, { ClerkSession: [] }]
      parameters:
        - { $ref: '#/components/parameters/TenantIdQuery' }
      responses:
        '200': { $ref: '#/components/responses/ManagementKeyListOk' }
        '400': { $ref: '#/components/responses/InvalidRequest' }
        '401': { $ref: '#/components/responses/InvalidToken' }
        '403': { $ref: '#/components/responses/InsufficientScope' }
        '404': { $ref: '#/components/responses/ResourceNotFound' }

  /v1/admin/keys/{keyId}:
    delete:
      tags: [Keys]
      operationId: revokeManagementKey
      summary: Revoke a management key immediately
      security: [{ AdminKey: [] }, { ClerkSession: [] }]
      parameters:
        - { $ref: '#/components/parameters/KeyIdPath' }
        - { $ref: '#/components/parameters/TenantIdQuery' }
      responses:
        '200': { $ref: '#/components/responses/ManagementKeyOk' }
        '400': { $ref: '#/components/responses/InvalidRequest' }
        '401': { $ref: '#/components/responses/InvalidToken' }
        '403': { $ref: '#/components/responses/InsufficientScope' }
        '404': { $ref: '#/components/responses/ResourceNotFound' }

  /v1/admin/apps/{appId}/webhook-proposals:
    post:
      tags: [Webhooks]
      operationId: createWebhookProposal
      summary: Propose a durable webhook endpoint change without revealing a secret
      security: [{ AdminKey: [] }]
      parameters: [{ $ref: '#/components/parameters/AppIdPath' }]
      requestBody: { $ref: '#/components/requestBodies/WebhookProposalBody' }
      responses:
        '201': { $ref: '#/components/responses/WebhookProposalCreatedOk' }
        '400': { $ref: '#/components/responses/InvalidRequest' }
        '401': { $ref: '#/components/responses/InvalidToken' }
        '403': { $ref: '#/components/responses/InsufficientScope' }
        '404': { $ref: '#/components/responses/ResourceNotFound' }
        '409': { $ref: '#/components/responses/ProposalStale' }

  /v1/admin/webhook-proposals:
    get:
      tags: [Webhooks]
      operationId: listWebhookProposals
      summary: List visible durable webhook proposals
      security: [{ AdminKey: [] }]
      parameters:
        - name: app_id
          in: query
          schema: { $ref: '#/components/schemas/AppId' }
        - name: status
          in: query
          schema: { type: string, enum: [pending, applied, cancelled, expired] }
        - name: cursor
          in: query
          schema: { $ref: '#/components/schemas/ProposalId' }
        - { $ref: '#/components/parameters/LimitQuery' }
      responses:
        '200': { $ref: '#/components/responses/WebhookProposalListOk' }
        '400': { $ref: '#/components/responses/InvalidRequest' }
        '401': { $ref: '#/components/responses/InvalidToken' }
        '403': { $ref: '#/components/responses/InsufficientScope' }

  /v1/admin/webhook-proposals/{proposalId}:
    get:
      tags: [Webhooks]
      operationId: getWebhookProposal
      summary: Preview a durable webhook proposal
      security: [{ AdminKey: [] }]
      parameters: [{ $ref: '#/components/parameters/ProposalIdPath' }]
      responses:
        '200': { $ref: '#/components/responses/WebhookProposalOk' }
        '401': { $ref: '#/components/responses/InvalidToken' }
        '403': { $ref: '#/components/responses/InsufficientScope' }
        '404': { $ref: '#/components/responses/ResourceNotFound' }

  /v1/admin/webhook-proposals/{proposalId}/apply:
    post:
      tags: [Webhooks]
      operationId: applyWebhookProposal
      summary: Apply a webhook proposal by reference without returning signing material
      security: [{ AdminKey: [] }]
      parameters:
        - { $ref: '#/components/parameters/ProposalIdPath' }
        - { $ref: '#/components/parameters/XIdempotencyKey' }
      requestBody:
        required: false
        content:
          application/json:
            schema:
              type: object
              additionalProperties: false
              properties:
                expected_base_revision: { type: integer, minimum: 0 }
      responses:
        '200': { $ref: '#/components/responses/ConfigProposalAppliedOk' }
        '400': { $ref: '#/components/responses/InvalidRequest' }
        '401': { $ref: '#/components/responses/InvalidToken' }
        '403': { $ref: '#/components/responses/InsufficientScope' }
        '404': { $ref: '#/components/responses/ResourceNotFound' }
        '409': { $ref: '#/components/responses/ProposalStale' }

  /v1/admin/webhook-proposals/{proposalId}/cancel:
    post:
      tags: [Webhooks]
      operationId: cancelWebhookProposal
      summary: Cancel a pending webhook proposal
      security: [{ AdminKey: [] }]
      parameters: [{ $ref: '#/components/parameters/ProposalIdPath' }]
      responses:
        '200': { $ref: '#/components/responses/ProposalCancelledOk' }
        '400': { $ref: '#/components/responses/InvalidRequest' }
        '401': { $ref: '#/components/responses/InvalidToken' }
        '403': { $ref: '#/components/responses/InsufficientScope' }
        '404': { $ref: '#/components/responses/ResourceNotFound' }

  /v1/admin/apps/{appId}/webhooks:
    parameters: [{ $ref: '#/components/parameters/AppIdPath' }]
    post:
      tags: [Webhooks]
      operationId: createWebhook
      summary: Register a webhook and reveal its signing secret once
      security: [{ AdminKey: [] }]
      requestBody: { $ref: '#/components/requestBodies/WebhookCreateBody' }
      responses:
        '201': { $ref: '#/components/responses/WebhookSecretOk' }
        '400': { $ref: '#/components/responses/InvalidRequest' }
        '401': { $ref: '#/components/responses/InvalidToken' }
        '403': { $ref: '#/components/responses/InsufficientScope' }
        '404': { $ref: '#/components/responses/ResourceNotFound' }
    get:
      tags: [Webhooks]
      operationId: listWebhooks
      summary: List webhook metadata without secrets
      security: [{ AdminKey: [] }]
      responses:
        '200': { $ref: '#/components/responses/WebhookListOk' }
        '401': { $ref: '#/components/responses/InvalidToken' }
        '403': { $ref: '#/components/responses/InsufficientScope' }
        '404': { $ref: '#/components/responses/ResourceNotFound' }

  /v1/admin/apps/{appId}/webhooks/deliveries:
    get:
      tags: [Webhooks]
      operationId: listWebhookDeliveries
      summary: List recent webhook delivery attempts
      security: [{ AdminKey: [] }]
      parameters:
        - { $ref: '#/components/parameters/AppIdPath' }
        - { $ref: '#/components/parameters/LimitQuery' }
      responses:
        '200': { $ref: '#/components/responses/WebhookDeliveryListOk' }
        '401': { $ref: '#/components/responses/InvalidToken' }
        '403': { $ref: '#/components/responses/InsufficientScope' }
        '404': { $ref: '#/components/responses/ResourceNotFound' }

  /v1/admin/apps/{appId}/webhooks/deliveries/{deliveryId}/replay:
    post:
      tags: [Webhooks]
      operationId: replayWebhookDelivery
      summary: Replay a delivery while retaining the event id
      security: [{ AdminKey: [] }]
      parameters:
        - { $ref: '#/components/parameters/AppIdPath' }
        - { $ref: '#/components/parameters/DeliveryIdPath' }
      responses:
        '200': { $ref: '#/components/responses/WebhookReplayOk' }
        '401': { $ref: '#/components/responses/InvalidToken' }
        '403': { $ref: '#/components/responses/InsufficientScope' }
        '404': { $ref: '#/components/responses/ResourceNotFound' }

  /v1/admin/apps/{appId}/webhooks/{endpointId}:
    parameters:
      - { $ref: '#/components/parameters/AppIdPath' }
      - { $ref: '#/components/parameters/EndpointIdPath' }
    patch:
      tags: [Webhooks]
      operationId: updateWebhook
      summary: Update webhook URL, event registry, or enabled state
      security: [{ AdminKey: [] }]
      requestBody: { $ref: '#/components/requestBodies/WebhookUpdateBody' }
      responses:
        '200': { $ref: '#/components/responses/WebhookOk' }
        '400': { $ref: '#/components/responses/InvalidRequest' }
        '401': { $ref: '#/components/responses/InvalidToken' }
        '403': { $ref: '#/components/responses/InsufficientScope' }
        '404': { $ref: '#/components/responses/ResourceNotFound' }
    delete:
      tags: [Webhooks]
      operationId: deleteWebhook
      summary: Delete a webhook endpoint
      security: [{ AdminKey: [] }]
      responses:
        '200': { $ref: '#/components/responses/WebhookDeleteOk' }
        '401': { $ref: '#/components/responses/InvalidToken' }
        '403': { $ref: '#/components/responses/InsufficientScope' }
        '404': { $ref: '#/components/responses/ResourceNotFound' }

  /v1/admin/apps/{appId}/webhooks/{endpointId}/rotate-secret:
    post:
      tags: [Webhooks]
      operationId: rotateWebhookSecret
      summary: Rotate and reveal a webhook signing secret once
      security: [{ AdminKey: [] }]
      parameters:
        - { $ref: '#/components/parameters/AppIdPath' }
        - { $ref: '#/components/parameters/EndpointIdPath' }
      responses:
        '200': { $ref: '#/components/responses/WebhookSecretOk' }
        '401': { $ref: '#/components/responses/InvalidToken' }
        '403': { $ref: '#/components/responses/InsufficientScope' }
        '404': { $ref: '#/components/responses/ResourceNotFound' }
        '500': { $ref: '#/components/responses/Internal' }

  /v1/admin/apps/{appId}/users/{externalId}/grants:
    post:
      tags: [Money]
      operationId: createGrant
      summary: Grant units idempotently
      description: X-Idempotency-Key is canonical; body idempotency_key is a deprecated compatibility alias.
      security: [{ AdminKey: [] }]
      parameters:
        - { $ref: '#/components/parameters/AppIdPath' }
        - { $ref: '#/components/parameters/ExternalIdPath' }
        - { $ref: '#/components/parameters/XIdempotencyKey' }
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/GrantInput' }
      responses:
        '200': { $ref: '#/components/responses/GrantOk' }
        '400': { $ref: '#/components/responses/InvalidRequest' }
        '401': { $ref: '#/components/responses/InvalidToken' }
        '403': { $ref: '#/components/responses/InsufficientScope' }
        '404': { $ref: '#/components/responses/ResourceNotFound' }

  /v1/admin/apps/{appId}/grants/{grantId}/reverse:
    post:
      tags: [Money]
      operationId: reverseGrant
      summary: Reverse a grant idempotently
      security: [{ AdminKey: [] }]
      parameters:
        - { $ref: '#/components/parameters/AppIdPath' }
        - { $ref: '#/components/parameters/GrantIdPath' }
        - { $ref: '#/components/parameters/XIdempotencyKey' }
      responses:
        '200': { $ref: '#/components/responses/GrantReversalOk' }
        '401': { $ref: '#/components/responses/InvalidToken' }
        '403': { $ref: '#/components/responses/InsufficientScope' }
        '404': { $ref: '#/components/responses/ResourceNotFound' }

  /v1/admin/apps/{appId}/usage:
    get:
      tags: [Money]
      operationId: getUsage
      summary: Query windowed usage rollups
      security: [{ AdminKey: [] }, { ClerkSession: [] }]
      parameters:
        - { $ref: '#/components/parameters/AppIdPath' }
        - { $ref: '#/components/parameters/SinceQuery' }
        - { $ref: '#/components/parameters/UntilQuery' }
        - { $ref: '#/components/parameters/LimitQuery' }
        - name: group_by
          in: query
          schema: { type: string, enum: [feature, user], default: feature }
      responses:
        '200': { $ref: '#/components/responses/UsageOk' }
        '400': { $ref: '#/components/responses/InvalidRequest' }
        '401': { $ref: '#/components/responses/InvalidToken' }
        '403': { $ref: '#/components/responses/InsufficientScope' }
        '404': { $ref: '#/components/responses/ResourceNotFound' }

  /v1/admin/apps/{appId}/pricing-insights:
    get:
      tags: [Money]
      operationId: getPricingInsights
      summary: Query closed-window per-user pricing distributions
      description: Percentiles are null below 20 users with typed insufficient_samples evidence.
      security: [{ AdminKey: [] }, { ClerkSession: [] }]
      parameters:
        - { $ref: '#/components/parameters/AppIdPath' }
        - { $ref: '#/components/parameters/SinceQuery' }
        - { $ref: '#/components/parameters/UntilQuery' }
      responses:
        '200': { $ref: '#/components/responses/PricingInsightsOk' }
        '400': { $ref: '#/components/responses/InvalidRequest' }
        '401': { $ref: '#/components/responses/InvalidToken' }
        '403': { $ref: '#/components/responses/InsufficientScope' }
        '404': { $ref: '#/components/responses/ResourceNotFound' }

  /v1/admin/apps/{appId}/rules:
    get:
      tags: [Rules]
      operationId: getRules
      summary: Read the current revisioned ruleset
      security: [{ AdminKey: [] }, { ClerkSession: [] }]
      parameters: [{ $ref: '#/components/parameters/AppIdPath' }]
      responses:
        '200': { $ref: '#/components/responses/RulesOk' }
        '401': { $ref: '#/components/responses/InvalidToken' }
        '403': { $ref: '#/components/responses/InsufficientScope' }
        '404': { $ref: '#/components/responses/ResourceNotFound' }

  /v1/admin/apps/{appId}/rules:preview:
    post:
      tags: [Rules]
      operationId: previewRules
      summary: Preview rules against recent traffic and bind a content hash
      security: [{ AdminKey: [] }, { ClerkSession: [] }]
      parameters: [{ $ref: '#/components/parameters/AppIdPath' }]
      requestBody: { $ref: '#/components/requestBodies/RulesPreviewBody' }
      responses:
        '201': { $ref: '#/components/responses/RulesPreviewCreated' }
        '400': { $ref: '#/components/responses/InvalidRequest' }
        '401': { $ref: '#/components/responses/InvalidToken' }
        '403': { $ref: '#/components/responses/InsufficientScope' }
        '404': { $ref: '#/components/responses/ResourceNotFound' }

  /v1/admin/apps/{appId}/rules/proposals:
    post:
      tags: [Rules]
      operationId: proposeRules
      summary: Create a preview-hash-bound rules proposal
      security: [{ AdminKey: [] }, { ClerkSession: [] }]
      parameters: [{ $ref: '#/components/parameters/AppIdPath' }]
      requestBody: { $ref: '#/components/requestBodies/RulesProposalBody' }
      responses:
        '201': { $ref: '#/components/responses/RulesProposalCreated' }
        '400': { $ref: '#/components/responses/InvalidRequest' }
        '401': { $ref: '#/components/responses/InvalidToken' }
        '403': { $ref: '#/components/responses/InsufficientScope' }
        '404': { $ref: '#/components/responses/ResourceNotFound' }

  /v1/admin/apps/{appId}/users:
    get:
      tags: [Privacy]
      operationId: listUsers
      summary: List active users for an app
      security: [{ AdminKey: [] }, { ClerkSession: [] }]
      parameters:
        - { $ref: '#/components/parameters/AppIdPath' }
        - name: cursor
          in: query
          schema: { type: string }
        - { $ref: '#/components/parameters/LimitQuery' }
      responses:
        '200': { $ref: '#/components/responses/UserListOk' }
        '400': { $ref: '#/components/responses/InvalidRequest' }
        '401': { $ref: '#/components/responses/InvalidToken' }
        '403': { $ref: '#/components/responses/InsufficientScope' }
        '404': { $ref: '#/components/responses/ResourceNotFound' }

  /v1/admin/apps/{appId}/users/{externalId}:
    parameters:
      - { $ref: '#/components/parameters/AppIdPath' }
      - { $ref: '#/components/parameters/ExternalIdPath' }
    get:
      tags: [Privacy]
      operationId: getUser
      summary: Read an existing user, balance, and recent usage
      security: [{ AdminKey: [] }, { ClerkSession: [] }]
      responses:
        '200': { $ref: '#/components/responses/UserOk' }
        '401': { $ref: '#/components/responses/InvalidToken' }
        '403': { $ref: '#/components/responses/InsufficientScope' }
        '404': { $ref: '#/components/responses/ResourceNotFound' }
    delete:
      tags: [Privacy]
      operationId: anonymizeUser
      summary: Anonymize identity while retaining money history
      description: A Clerk developer session requires a recent second factor.
      security: [{ AdminKey: [] }, { ClerkSession: [] }]
      responses:
        '200': { $ref: '#/components/responses/UserDeleteOk' }
        '401': { $ref: '#/components/responses/InvalidToken' }
        '403': { $ref: '#/components/responses/InsufficientScope' }
        '404': { $ref: '#/components/responses/ResourceNotFound' }

  /v1/admin/apps/{appId}/users/{externalId}/export:
    get:
      tags: [Privacy]
      operationId: exportUser
      summary: Export retained user identity, money, usage, and timing data
      security: [{ AdminKey: [] }, { ClerkSession: [] }]
      parameters:
        - { $ref: '#/components/parameters/AppIdPath' }
        - { $ref: '#/components/parameters/ExternalIdPath' }
      responses:
        '200': { $ref: '#/components/responses/UserExportOk' }
        '401': { $ref: '#/components/responses/InvalidToken' }
        '403': { $ref: '#/components/responses/InsufficientScope' }
        '404': { $ref: '#/components/responses/ResourceNotFound' }

  /v1/admin/config:
    get:
      tags: [Configuration]
      operationId: getConfig
      summary: Read visible tenant configuration and current revisions
      security: [{ AdminKey: [] }, { ClerkSession: [] }]
      responses:
        '200': { $ref: '#/components/responses/ConfigReadOk' }
        '401': { $ref: '#/components/responses/InvalidToken' }
        '403': { $ref: '#/components/responses/InsufficientScope' }
        '404': { $ref: '#/components/responses/ResourceNotFound' }
    put:
      tags: [Configuration]
      operationId: applyConfigDirect
      summary: Apply a config document directly with apply scope
      description: Ruleset changes are rejected and must use preview-bound proposals.
      security: [{ AdminKey: [] }]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              oneOf:
                - { $ref: '#/components/schemas/ConfigDocument' }
                - { $ref: '#/components/schemas/TenantConfig' }
      responses:
        '200': { $ref: '#/components/responses/ConfigAppliedOk' }
        '400': { $ref: '#/components/responses/InvalidRequest' }
        '401': { $ref: '#/components/responses/InvalidToken' }
        '403': { $ref: '#/components/responses/InsufficientScope' }
        '404': { $ref: '#/components/responses/ResourceNotFound' }

  /v1/admin/config/proposals:
    get:
      tags: [Configuration]
      operationId: listConfigProposals
      summary: List visible durable proposals across config, rules, and webhooks
      security: [{ AdminKey: [] }]
      parameters:
        - name: app_id
          in: query
          schema: { $ref: '#/components/schemas/AppId' }
        - name: kind
          in: query
          schema: { type: string, enum: [config, rules] }
        - name: status
          in: query
          schema: { type: string, enum: [pending, applied, cancelled, expired] }
        - name: cursor
          in: query
          schema: { $ref: '#/components/schemas/ProposalId' }
        - { $ref: '#/components/parameters/LimitQuery' }
      responses:
        '200': { $ref: '#/components/responses/ConfigProposalListOk' }
        '400': { $ref: '#/components/responses/InvalidRequest' }
        '401': { $ref: '#/components/responses/InvalidToken' }
        '403': { $ref: '#/components/responses/InsufficientScope' }
    post:
      tags: [Configuration]
      operationId: createConfigProposal
      summary: Propose a tenant config against its current base revision
      security: [{ AdminKey: [] }, { ClerkSession: [] }]
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/TenantConfig' }
      responses:
        '201':
          description: Durable proposal
          headers:
            Weirgate-Api-Version: { $ref: '#/components/headers/WeirgateApiVersion' }
            X-Weirgate-Request-Id: { $ref: '#/components/headers/RequestId' }
          content:
            application/json:
              schema: { $ref: '#/components/schemas/ConfigProposalCreated' }
        '400': { $ref: '#/components/responses/InvalidRequest' }
        '401': { $ref: '#/components/responses/InvalidToken' }
        '403': { $ref: '#/components/responses/InsufficientScope' }
        '404': { $ref: '#/components/responses/ResourceNotFound' }

  /v1/admin/config/proposals/{proposalId}:
    get:
      tags: [Configuration]
      operationId: getConfigProposal
      summary: Preview proposal document, attribution, expiry, and status
      security: [{ AdminKey: [] }, { ClerkSession: [] }]
      parameters: [{ $ref: '#/components/parameters/ProposalIdPath' }]
      responses:
        '200':
          description: Proposal preview
          headers:
            Weirgate-Api-Version: { $ref: '#/components/headers/WeirgateApiVersion' }
            X-Weirgate-Request-Id: { $ref: '#/components/headers/RequestId' }
          content:
            application/json:
              schema: { $ref: '#/components/schemas/ConfigProposal' }
        '401': { $ref: '#/components/responses/InvalidToken' }
        '403': { $ref: '#/components/responses/InsufficientScope' }
        '404': { $ref: '#/components/responses/ResourceNotFound' }

  /v1/admin/config/proposals/{proposalId}/apply:
    post:
      tags: [Configuration]
      operationId: applyConfigProposal
      summary: Apply a proposal by reference with base-revision CAS
      description: A Clerk developer session requires a recent second factor.
      security: [{ AdminKey: [] }, { ClerkSession: [] }]
      parameters:
        - { $ref: '#/components/parameters/ProposalIdPath' }
        - { $ref: '#/components/parameters/XIdempotencyKey' }
      requestBody:
        required: false
        content:
          application/json:
            schema:
              type: object
              additionalProperties: false
              properties:
                expected_base_revision: { type: integer, minimum: 0 }
      responses:
        '200':
          description: Applied revision or idempotent replay
          headers:
            Weirgate-Api-Version: { $ref: '#/components/headers/WeirgateApiVersion' }
            X-Weirgate-Request-Id: { $ref: '#/components/headers/RequestId' }
          content:
            application/json:
              schema: { $ref: '#/components/schemas/ConfigProposalApplied' }
        '400': { $ref: '#/components/responses/InvalidRequest' }
        '401': { $ref: '#/components/responses/InvalidToken' }
        '403': { $ref: '#/components/responses/InsufficientScope' }
        '404': { $ref: '#/components/responses/ResourceNotFound' }
        '409': { $ref: '#/components/responses/ProposalStale' }

  /v1/admin/config/proposals/{proposalId}/cancel:
    post:
      tags: [Configuration]
      operationId: cancelConfigProposal
      summary: Cancel a pending proposal as its creator or with apply scope
      security: [{ AdminKey: [] }]
      parameters: [{ $ref: '#/components/parameters/ProposalIdPath' }]
      responses:
        '200': { $ref: '#/components/responses/ProposalCancelledOk' }
        '400': { $ref: '#/components/responses/InvalidRequest' }
        '401': { $ref: '#/components/responses/InvalidToken' }
        '403': { $ref: '#/components/responses/InsufficientScope' }
        '404': { $ref: '#/components/responses/ResourceNotFound' }

  /v1/admin/audit:
    get:
      tags: [Operations]
      operationId: queryAudit
      summary: Query tenant-scoped actor-attributed management audit events
      security: [{ AdminKey: [] }, { ClerkSession: [] }]
      parameters:
        - { $ref: '#/components/parameters/TenantIdQuery' }
        - { $ref: '#/components/parameters/LimitQuery' }
        - name: action
          in: query
          schema: { type: string }
        - name: resource_type
          in: query
          schema: { type: string }
        - name: actor
          in: query
          schema: { type: string }
        - name: since
          in: query
          schema: { type: string, format: date-time }
        - name: until
          in: query
          schema: { type: string, format: date-time }
      responses:
        '200':
          description: Audit events, newest first
          headers:
            Weirgate-Api-Version: { $ref: '#/components/headers/WeirgateApiVersion' }
            X-Weirgate-Request-Id: { $ref: '#/components/headers/RequestId' }
          content:
            application/json:
              schema: { $ref: '#/components/schemas/AuditPage' }
        '400': { $ref: '#/components/responses/InvalidRequest' }
        '401': { $ref: '#/components/responses/InvalidToken' }
        '403': { $ref: '#/components/responses/InsufficientScope' }
        '404': { $ref: '#/components/responses/ResourceNotFound' }

  /v1/admin/apps/{appId}/doctor:
    post:
      tags: [Operations]
      operationId: runDoctor
      summary: Run non-billable auth, provider, or webhook probes
      security: [{ AdminKey: [] }, { ClerkSession: [] }]
      parameters: [{ $ref: '#/components/parameters/AppIdPath' }]
      requestBody: { $ref: '#/components/requestBodies/DoctorBody' }
      responses:
        '200': { $ref: '#/components/responses/DoctorOk' }
        '400': { $ref: '#/components/responses/InvalidRequest' }
        '401': { $ref: '#/components/responses/InvalidToken' }
        '403': { $ref: '#/components/responses/InsufficientScope' }
        '404': { $ref: '#/components/responses/ResourceNotFound' }

  /v1/admin/feature-templates:
    get:
      tags: [Configuration]
      operationId: listFeatureTemplates
      summary: List built-in mock-first feature templates
      security: [{ AdminKey: [] }, { ClerkSession: [] }]
      responses:
        '200': { $ref: '#/components/responses/FeatureTemplateListOk' }
        '401': { $ref: '#/components/responses/InvalidToken' }
        '403': { $ref: '#/components/responses/InsufficientScope' }

  /v1/admin/apps/{appId}/feature-templates/{templateId}/instantiate:
    post:
      tags: [Configuration]
      operationId: instantiateFeatureTemplate
      summary: Instantiate a template as an ordinary config revision
      security: [{ AdminKey: [] }]
      parameters:
        - { $ref: '#/components/parameters/AppIdPath' }
        - { $ref: '#/components/parameters/TemplateIdPath' }
      requestBody: { $ref: '#/components/requestBodies/TemplateInstantiateBody' }
      responses:
        '201': { $ref: '#/components/responses/TemplateInstantiated' }
        '400': { $ref: '#/components/responses/InvalidRequest' }
        '401': { $ref: '#/components/responses/InvalidToken' }
        '403': { $ref: '#/components/responses/InsufficientScope' }
        '404': { $ref: '#/components/responses/ResourceNotFound' }

  /v1/admin/apps/{appId}/sandbox/fixtures:
    get:
      tags: [Operations]
      operationId: listSandboxFixtures
      summary: List deterministic success and typed-error fixtures
      security: [{ AdminKey: [] }, { ClerkSession: [] }]
      parameters: [{ $ref: '#/components/parameters/AppIdPath' }]
      responses:
        '200': { $ref: '#/components/responses/SandboxFixturesOk' }
        '401': { $ref: '#/components/responses/InvalidToken' }
        '403': { $ref: '#/components/responses/InsufficientScope' }
        '404': { $ref: '#/components/responses/ResourceNotFound' }

  /v1/admin/apps/{appId}/sandbox/sessions:
    post:
      tags: [Operations]
      operationId: createSandboxSession
      summary: Create a named deterministic sandbox session
      description: Clerk developer sessions require a recent second factor.
      security: [{ AdminKey: [] }, { ClerkSession: [] }]
      parameters: [{ $ref: '#/components/parameters/AppIdPath' }]
      requestBody: { $ref: '#/components/requestBodies/SandboxSessionBody' }
      responses:
        '201': { $ref: '#/components/responses/SandboxSessionCreated' }
        '400': { $ref: '#/components/responses/InvalidRequest' }
        '401': { $ref: '#/components/responses/InvalidToken' }
        '403': { $ref: '#/components/responses/InsufficientScope' }
        '404': { $ref: '#/components/responses/ResourceNotFound' }

  /v1/admin/apps/{appId}/sandbox/sessions/{sessionId}:
    get:
      tags: [Operations]
      operationId: getSandboxSession
      summary: Read a sandbox session and its durable runs
      security: [{ AdminKey: [] }, { ClerkSession: [] }]
      parameters:
        - { $ref: '#/components/parameters/AppIdPath' }
        - { $ref: '#/components/parameters/SessionIdPath' }
      responses:
        '200': { $ref: '#/components/responses/SandboxSessionOk' }
        '401': { $ref: '#/components/responses/InvalidToken' }
        '403': { $ref: '#/components/responses/InsufficientScope' }
        '404': { $ref: '#/components/responses/ResourceNotFound' }

  /v1/admin/apps/{appId}/sandbox/sessions/{sessionId}/runs:
    post:
      tags: [Operations]
      operationId: runSandboxFixture
      summary: Run a named deterministic fixture
      description: Clerk developer sessions require a recent second factor.
      security: [{ AdminKey: [] }, { ClerkSession: [] }]
      parameters:
        - { $ref: '#/components/parameters/AppIdPath' }
        - { $ref: '#/components/parameters/SessionIdPath' }
      requestBody: { $ref: '#/components/requestBodies/SandboxRunBody' }
      responses:
        '201': { $ref: '#/components/responses/SandboxRunCreated' }
        '400': { $ref: '#/components/responses/InvalidRequest' }
        '401': { $ref: '#/components/responses/InvalidToken' }
        '403': { $ref: '#/components/responses/InsufficientScope' }
        '404': { $ref: '#/components/responses/ResourceNotFound' }

  /v1/admin/apps/{appId}/readiness:
    get:
      tags: [Operations]
      operationId: getReadiness
      summary: Read durable onboarding readiness evidence
      security: [{ AdminKey: [] }, { ClerkSession: [] }]
      parameters: [{ $ref: '#/components/parameters/AppIdPath' }]
      responses:
        '200': { $ref: '#/components/responses/ReadinessOk' }
        '401': { $ref: '#/components/responses/InvalidToken' }
        '403': { $ref: '#/components/responses/InsufficientScope' }
        '404': { $ref: '#/components/responses/ResourceNotFound' }

  /v1/admin/apps/{appId}/request-traces/{requestId}:
    get:
      tags: [Operations]
      operationId: getRequestTrace
      summary: Join reservation, settlement, usage, and optional client timing by request id
      security: [{ AdminKey: [] }, { ClerkSession: [] }]
      parameters:
        - { $ref: '#/components/parameters/AppIdPath' }
        - { $ref: '#/components/parameters/RequestIdPath' }
      responses:
        '200': { $ref: '#/components/responses/RequestTraceOk' }
        '401': { $ref: '#/components/responses/InvalidToken' }
        '403': { $ref: '#/components/responses/InsufficientScope' }
        '404': { $ref: '#/components/responses/ResourceNotFound' }

  /v1/admin/apps/{appId}/integration-snippet:
    get:
      tags: [Operations]
      operationId: getIntegrationSnippet
      summary: Generate an HTTP/curl-only integration snippet
      security: [{ AdminKey: [] }, { ClerkSession: [] }]
      parameters:
        - { $ref: '#/components/parameters/AppIdPath' }
        - name: mode
          in: query
          schema: { type: string, enum: [http, curl], default: http }
        - name: feature_id
          in: query
          schema: { type: string }
      responses:
        '200': { $ref: '#/components/responses/IntegrationSnippetOk' }
        '400': { $ref: '#/components/responses/InvalidRequest' }
        '401': { $ref: '#/components/responses/InvalidToken' }
        '403': { $ref: '#/components/responses/InsufficientScope' }
        '404': { $ref: '#/components/responses/NotFoundDataPlane' }

components:
  securitySchemes:
    EndUserBearer:
      type: http
      scheme: bearer
      bearerFormat: JWT
      description: End-user JWT. Internal dev tokens are documented by x-weirgate-dev-auth and are never production auth.
    AdminKey:
      type: apiKey
      in: header
      name: X-Admin-Key
      description: Hash-stored tenant key or fenced platform super-admin key.
    ClerkSession:
      type: http
      scheme: bearer
      bearerFormat: JWT
      description: Clerk developer-identity session JWT with an active organization; distinct from app end-user auth.
    UserProviderKey:
      type: apiKey
      in: header
      name: X-User-Provider-Key
      description: Ephemeral end-user provider credential; passthrough only, never stored or logged.
      x-sensitive: true
      x-never-logged: true

  parameters:
    XAppId:
      name: X-App-Id
      in: header
      required: true
      schema: { $ref: '#/components/schemas/AppId' }
    XFeatureId:
      name: X-Feature-Id
      in: header
      required: true
      schema: { $ref: '#/components/schemas/FeatureId' }
    XIdempotencyKey:
      name: X-Idempotency-Key
      in: header
      required: false
      description: Canonical idempotency carrier for retried mutations.
      schema: { type: string, minLength: 1, maxLength: 255 }
    TenantIdQuery:
      name: tenant_id
      in: query
      required: false
      description: Required for platform-key access; tenant keys are pinned to their own tenant.
      schema: { $ref: '#/components/schemas/TenantId' }
    AppIdQuery:
      name: app_id
      in: query
      required: false
      description: Required to bind app-scoped provider metadata reads and probes.
      schema: { $ref: '#/components/schemas/AppId' }
    LimitQuery:
      name: limit
      in: query
      schema: { type: integer, minimum: 1, maximum: 500 }
    SinceQuery:
      name: since
      in: query
      schema: { type: string, format: date-time }
    UntilQuery:
      name: until
      in: query
      schema: { type: string, format: date-time }
    AppIdPath:
      name: appId
      in: path
      required: true
      schema: { $ref: '#/components/schemas/AppId' }
    FeatureIdPath:
      name: featureId
      in: path
      required: true
      schema: { $ref: '#/components/schemas/FeatureId' }
    ExternalIdPath:
      name: externalId
      in: path
      required: true
      schema: { type: string, minLength: 1 }
    ProviderPath:
      name: provider
      in: path
      required: true
      schema: { $ref: '#/components/schemas/Provider' }
    KeyIdPath:
      name: keyId
      in: path
      required: true
      schema: { type: string }
    EndpointIdPath:
      name: endpointId
      in: path
      required: true
      schema: { type: string }
    DeliveryIdPath:
      name: deliveryId
      in: path
      required: true
      schema: { type: string }
    GrantIdPath:
      name: grantId
      in: path
      required: true
      schema: { type: string }
    ProposalIdPath:
      name: proposalId
      in: path
      required: true
      schema: { $ref: '#/components/schemas/ProposalId' }
    TemplateIdPath:
      name: templateId
      in: path
      required: true
      schema: { type: string }
    SessionIdPath:
      name: sessionId
      in: path
      required: true
      schema: { type: string, pattern: '^sbox_' }
    RequestIdPath:
      name: requestId
      in: path
      required: true
      schema: { $ref: '#/components/schemas/RequestId' }

  headers:
    WeirgateApiVersion:
      description: Frozen calendar-date API contract version.
      required: true
      schema: { type: string, const: '2026-07-18' }
    RequestId:
      description: Correlation id with UUIDv4 randomness.
      required: true
      schema: { $ref: '#/components/schemas/RequestId' }
    CreditsRemaining:
      description: Available units after pending reservations.
      schema: { type: integer }
    ErrorType:
      description: Stable enumerable error discriminator; never parse message strings.
      required: true
      schema: { $ref: '#/components/schemas/ErrorType' }

  requestBodies:
    GenericJsonBody:
      required: true
      content:
        application/json:
          schema: { $ref: '#/components/schemas/GenericObject' }
    OptionalGenericJsonBody:
      required: false
      content:
        application/json:
          schema: { $ref: '#/components/schemas/GenericObject' }
    ProviderKeyBody:
      required: true
      content:
        application/json:
          schema:
            type: object
            additionalProperties: false
            required: [value]
            properties:
              value: { type: string, minLength: 1, maxLength: 8192, writeOnly: true, x-sensitive: true }
              label: { type: string, maxLength: 120 }
    ProviderPolicyBody:
      required: true
      content:
        application/json:
          schema: { $ref: '#/components/schemas/ProviderPolicyDocument' }
    WebhookCreateBody:
      required: true
      content:
        application/json:
          schema:
            type: object
            additionalProperties: false
            required: [url]
            properties:
              url: { type: string, format: uri, pattern: '^https://' }
              event_types: { type: array, minItems: 1, items: { $ref: '#/components/schemas/WebhookEventType' } }
              enabled: { type: boolean, default: true }
    WebhookUpdateBody:
      required: true
      content:
        application/json:
          schema:
            type: object
            additionalProperties: false
            minProperties: 1
            properties:
              url: { type: string, format: uri, pattern: '^https://' }
              event_types: { type: array, minItems: 1, items: { $ref: '#/components/schemas/WebhookEventType' } }
              enabled: { type: boolean }
    WebhookProposalBody:
      required: true
      content:
        application/json:
          schema:
            type: object
            additionalProperties: false
            required: [operation, base_revision]
            properties:
              operation: { type: string, enum: [create, update, delete] }
              endpoint_id: { type: string }
              url: { type: string, format: uri, pattern: '^https://' }
              event_types: { type: array, minItems: 1, items: { $ref: '#/components/schemas/WebhookEventType' } }
              enabled: { type: boolean }
              base_revision: { type: integer, minimum: 0 }
    RulesPreviewBody:
      required: true
      content:
        application/json:
          schema:
            type: object
            additionalProperties: false
            required: [ruleset, since, until]
            properties:
              ruleset: { $ref: '#/components/schemas/RulesetDocument' }
              since: { type: string, format: date-time }
              until: { type: string, format: date-time }
    RulesProposalBody:
      required: true
      content:
        application/json:
          schema:
            type: object
            additionalProperties: false
            required: [ruleset, preview_id]
            properties:
              ruleset: { $ref: '#/components/schemas/RulesetDocument' }
              preview_id: { type: string, pattern: '^rprev_' }
    DoctorBody:
      required: true
      content:
        application/json:
          schema:
            type: object
            additionalProperties: false
            required: [checks]
            properties:
              checks:
                type: array
                minItems: 1
                uniqueItems: true
                items: { type: string, enum: [auth, provider, webhook] }
    TemplateInstantiateBody:
      required: false
      content:
        application/json:
          schema:
            type: object
            additionalProperties: false
            properties:
              feature_id: { $ref: '#/components/schemas/FeatureId' }
    SandboxSessionBody:
      required: false
      content:
        application/json:
          schema:
            type: object
            additionalProperties: false
            properties:
              name: { type: string, minLength: 1, maxLength: 120, default: Sandbox session }
    SandboxRunBody:
      required: true
      content:
        application/json:
          schema:
            type: object
            additionalProperties: false
            required: [fixture]
            properties:
              fixture: { type: string, description: success or error.<ErrorType> }

  schemas:
    GenericObject:
      type: object
      additionalProperties: true
    ManagementIdentity:
      oneOf:
        - type: object
          additionalProperties: false
          required: [actor, scope]
          properties:
            actor:
              type: object
              additionalProperties: false
              required: [type, key_id]
              properties:
                type: { type: string, const: platform_super_admin }
                key_id: { type: 'null' }
            scope: { type: string, const: apply }
        - type: object
          additionalProperties: false
          required: [actor, tenant_id, app_ids, scope, tool_groups, environment, expires_at]
          properties:
            actor:
              type: object
              additionalProperties: false
              required: [type, key_id]
              properties:
                type: { type: string, const: management_key }
                key_id: { type: string }
            tenant_id: { $ref: '#/components/schemas/TenantId' }
            app_ids: { type: array, items: { type: string } }
            scope: { type: string, enum: [read, propose, apply] }
            tool_groups: { type: array, items: { type: string } }
            environment: { type: string, enum: [test, live] }
            expires_at: { type: [string, 'null'], format: date-time }
        - type: object
          additionalProperties: false
          required: [actor, tenant_id, app_ids, scope, tool_groups, environment, expires_at, second_factor_age_minutes]
          properties:
            actor:
              type: object
              additionalProperties: false
              required: [type, user_id, session_id]
              properties:
                type: { type: string, const: clerk_session }
                user_id: { type: string }
                session_id: { type: string }
            tenant_id: { $ref: '#/components/schemas/TenantId' }
            app_ids: { type: array, items: { type: string } }
            scope: { type: string, const: apply }
            tool_groups: { type: array, items: { type: string } }
            environment: { type: string, const: live }
            expires_at: { type: string, format: date-time }
            second_factor_age_minutes: { type: [number, 'null'], minimum: 0 }
    AgentDocSummary:
      type: object
      additionalProperties: false
      required: [page_id, section, title, excerpt, last_updated, api_version]
      properties:
        page_id: { type: string }
        section: { type: string }
        title: { type: string }
        excerpt: { type: string }
        last_updated: { type: string, format: date }
        api_version: { type: string, const: '2026-07-18' }
    AgentDoc:
      type: object
      additionalProperties: false
      required: [page_id, section, title, last_updated, api_version, markdown]
      properties:
        page_id: { type: string }
        section: { type: string }
        title: { type: string }
        last_updated: { type: string, format: date }
        api_version: { type: string, const: '2026-07-18' }
        markdown: { type: string }
    BillingRedirect:
      type: object
      additionalProperties: false
      required: [url, sessionId]
      properties:
        url: { type: string, format: uri, pattern: '^https://checkout\.stripe\.com/' }
        sessionId: { type: string, pattern: '^cs_test_' }
    BillingPortalRedirect:
      type: object
      additionalProperties: false
      required: [url]
      properties:
        url: { type: string, format: uri }
    BillingStatement:
      type: object
      additionalProperties: false
      required:
        - id
        - period_start
        - period_end
        - kind
        - draft
        - final
        - credit
        - reason
        - reconciliation_case_id
        - stripe_idempotency_key
        - stripe_invoice_item_ids
        - stripe_invoice_id
        - stripe_credit_note_id
        - created_at
      properties:
        id: { type: string }
        period_start: { type: string, format: date-time }
        period_end: { type: string, format: date-time }
        kind: { type: string, enum: [statement_draft, statement_final, correction, credit, stripe_invoice_items] }
        draft: { $ref: '#/components/schemas/BillingQuantity' }
        final: { $ref: '#/components/schemas/BillingQuantity' }
        credit: { $ref: '#/components/schemas/BillingQuantity' }
        reason: { type: string }
        reconciliation_case_id: { type: string }
        stripe_idempotency_key: { type: string }
        stripe_invoice_item_ids: { type: array, items: { type: string } }
        stripe_invoice_id: { type: [string, 'null'] }
        stripe_credit_note_id: { type: [string, 'null'] }
        created_at: { type: string, format: date-time }
    BillingQuantity:
      type: object
      additionalProperties: false
      required: [calls, tokens]
      properties:
        calls: { type: integer, minimum: 0 }
        tokens: { type: integer, minimum: 0 }
    BillingSummary:
      type: object
      additionalProperties: false
      required: [tier, subscription, design_partner, usage, tiers, invoices, statements]
      properties:
        tier: { type: string, enum: [free, pro, scale] }
        subscription: { $ref: '#/components/schemas/BillingSubscription' }
        design_partner: { $ref: '#/components/schemas/BillingDesignPartner' }
        usage: { $ref: '#/components/schemas/BillingUsage' }
        tiers:
          type: object
          additionalProperties: { $ref: '#/components/schemas/BillingTierPosture' }
        invoices:
          type: array
          items: { $ref: '#/components/schemas/BillingInvoice' }
        statements:
          type: array
          items: { $ref: '#/components/schemas/BillingStatement' }
    BillingSubscription:
      type: object
      additionalProperties: false
      required: [status, current_period_end, cancel_at_period_end]
      properties:
        status: { type: string }
        current_period_end: { type: [string, 'null'], format: date-time }
        cancel_at_period_end: { type: boolean }
    BillingDesignPartner:
      type: object
      additionalProperties: false
      required: [enrolled, phase, started_at, free_until, discount_until]
      properties:
        enrolled: { type: boolean }
        phase: { type: string, enum: [none, free, half, complete] }
        started_at: { type: [string, 'null'], format: date-time }
        free_until: { type: [string, 'null'], format: date-time }
        discount_until: { type: [string, 'null'], format: date-time }
    BillingUsage:
      type: object
      additionalProperties: false
      required: [period_start, period_end, provisional, calls, tokens, blocked_at_cap]
      properties:
        period_start: { type: string, format: date-time }
        period_end: { type: string, format: date-time }
        provisional: { type: boolean, const: true }
        calls: { $ref: '#/components/schemas/BillingMeterConsumption' }
        tokens: { $ref: '#/components/schemas/BillingMeterConsumption' }
        blocked_at_cap: { type: boolean }
    BillingMeterConsumption:
      type: object
      additionalProperties: false
      required: [used, included, warning_at]
      properties:
        used: { type: integer, minimum: 0 }
        included: { type: integer, minimum: 0 }
        warning_at: { type: integer, minimum: 0 }
    BillingTierPosture:
      type: object
      additionalProperties: false
      required: [monthlyFeeUsd, includedCalls, includedTokens, blocksAtCap]
      properties:
        monthlyFeeUsd: { type: number, minimum: 0 }
        includedCalls: { type: integer, minimum: 0 }
        includedTokens: { type: integer, minimum: 0 }
        blocksAtCap: { type: boolean }
    BillingInvoice:
      type: object
      additionalProperties: false
      required: [id, status, total, currency, created_at, hosted_invoice_url, invoice_pdf]
      properties:
        id: { type: string }
        status: { type: [string, 'null'] }
        total: { type: [integer, 'null'] }
        currency: { type: [string, 'null'] }
        created_at: { type: [string, 'null'], format: date-time }
        hosted_invoice_url: { type: [string, 'null'], format: uri }
        invoice_pdf: { type: [string, 'null'], format: uri }
    StripeTestCatalog:
      type: object
      additionalProperties: false
      required: [mode, version, products, prices, coupons, portalConfigurationId]
      properties:
        mode: { type: string, const: test }
        version: { type: string }
        products: { $ref: '#/components/schemas/GenericObject' }
        prices: { $ref: '#/components/schemas/GenericObject' }
        coupons: { $ref: '#/components/schemas/GenericObject' }
        portalConfigurationId: { type: string }
    BillingStatementClose:
      type: object
      additionalProperties: false
      required: [draft, final, overage, invoice]
      properties:
        draft: { $ref: '#/components/schemas/BillingStatement' }
        final: { $ref: '#/components/schemas/BillingStatement' }
        overage: { $ref: '#/components/schemas/BillingQuantity' }
        invoice:
          oneOf:
            - { $ref: '#/components/schemas/BillingStatement' }
            - { type: 'null' }
    BillingStatementCorrection:
      type: object
      additionalProperties: false
      required: [correction, credit]
      properties:
        correction: { $ref: '#/components/schemas/BillingStatement' }
        credit:
          oneOf:
            - { $ref: '#/components/schemas/BillingStatement' }
            - { type: 'null' }
    AuthPreset:
      type: object
      additionalProperties: false
      required: [id, label, mode, required_fields, issuer_template, jwks_url_template]
      properties:
        id: { type: string, enum: [firebase, supabase, clerk, custom] }
        label: { type: string }
        mode: { type: string, enum: [firebase, jwks] }
        required_fields: { type: array, items: { type: string } }
        issuer_template: { type: string }
        jwks_url_template: { type: string }
        audience_default: { type: string }
    RequestId:
      type: string
      pattern: '^req_[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$'
    ProposalId:
      type: string
      pattern: '^prop_[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$'
    ManagementKeyValue:
      type: string
      pattern: '^wgk_[a-f0-9]{64}$'
      writeOnly: true
      x-sensitive: true
    TenantId:
      type: string
      pattern: '^[a-z0-9-]+$'
    AppId:
      type: string
      pattern: '^[a-z0-9-]+$'
    FeatureId:
      type: string
      pattern: '^[a-z0-9-]+$'
    Provider:
      type: string
      enum: [openrouter, openai, anthropic, google, xai]
    ErrorType:
      type: string
      enum:
        - invalid_request
        - invalid_token
        - user_provider_key_required
        - user_provider_key_invalid
        - insufficient_scope
        - out_of_allowance
        - abuse_blocked
        - feature_disabled
        - feature_not_found
        - resource_not_found
        - provider_policy_blocked
        - output_contract_unsupported
        - output_contract_violation
        - proposal_stale
        - rate_limited
        - telemetry_request_unavailable
        - provider_unavailable
        - internal
    ErrorEnvelope:
      type: object
      additionalProperties: false
      required: [error]
      properties:
        error:
          type: object
          additionalProperties: false
          required: [type, message, request_id]
          properties:
            type: { $ref: '#/components/schemas/ErrorType' }
            message: { type: string }
            request_id: { $ref: '#/components/schemas/RequestId' }
            detail: { $ref: '#/components/schemas/GenericObject' }
    Health:
      type: object
      additionalProperties: false
      required: [ok, mode]
      properties:
        ok: { type: boolean, const: true }
        mode: { type: string, enum: [mock, live] }
    Accepted:
      type: object
      additionalProperties: false
      required: [accepted]
      properties:
        accepted: { type: boolean, const: true }
    ChatMessage:
      type: object
      required: [role, content]
      properties:
        role: { type: string }
        content: {}
      additionalProperties: true
    ChatCompletionRequest:
      type: object
      required: [messages]
      properties:
        messages:
          type: array
          minItems: 1
          items: { $ref: '#/components/schemas/ChatMessage' }
        stream: { type: boolean, default: false }
        model:
          type: string
          deprecated: true
          description: Accepted for OpenAI compatibility but ignored; X-Feature-Id selects the model.
      additionalProperties: true
    Usage:
      type: object
      required: [prompt_tokens, completion_tokens, total_tokens]
      properties:
        prompt_tokens: { type: integer, minimum: 0 }
        completion_tokens: { type: integer, minimum: 0 }
        total_tokens: { type: integer, minimum: 0 }
        cost: { type: number, minimum: 0 }
        model: { type: string }
    ChatCompletion:
      type: object
      required: [id, object, choices]
      properties:
        id: { type: string }
        object: { type: string, const: chat.completion }
        model: { type: string }
        choices: { type: array, items: { $ref: '#/components/schemas/GenericObject' } }
        usage: { $ref: '#/components/schemas/Usage' }
      additionalProperties: true
    ChatCompletionChunk:
      type: object
      required: [id, object, choices]
      properties:
        id: { type: string }
        object: { type: string, const: chat.completion.chunk }
        model: { type: string }
        choices: { type: array, items: { $ref: '#/components/schemas/GenericObject' } }
        usage: { $ref: '#/components/schemas/Usage' }
      additionalProperties: true
    EmbeddingRequest:
      type: object
      additionalProperties: true
      required: [input]
      properties:
        input:
          oneOf:
            - { type: string }
            - { type: array, minItems: 1, items: { type: string } }
        model:
          type: string
          deprecated: true
          description: Accepted but ignored; X-Feature-Id selects the model.
    EmbeddingResponse:
      type: object
      required: [object, data, model, usage]
      properties:
        object: { type: string, const: list }
        data:
          type: array
          items:
            type: object
            required: [object, index, embedding]
            properties:
              object: { type: string, const: embedding }
              index: { type: integer }
              embedding: { type: array, items: { type: number } }
        model: { type: string }
        usage: { $ref: '#/components/schemas/Usage' }
    FeatureCatalog:
      type: object
      additionalProperties: false
      required: [catalog_version, data]
      properties:
        catalog_version: { type: string, pattern: '^cat_[0-9]+_[a-f0-9]{16}$' }
        data:
          type: array
          items: { $ref: '#/components/schemas/FeatureCatalogEntry' }
    FeatureCatalogEntry:
      type: object
      additionalProperties: false
      required: [feature_id, modality, key_policy, display_label, availability, provider_policy]
      properties:
        feature_id: { $ref: '#/components/schemas/FeatureId' }
        modality: { type: string, enum: [chat, embedding, image, audio] }
        key_policy: { type: string, enum: [developer, user, user_or_developer] }
        display_label: { type: string }
        availability:
          type: object
          additionalProperties: false
          required: [available, reason]
          properties:
            available: { type: boolean }
            reason: { type: [string, 'null'], enum: [provider_policy_blocked, null] }
        provider_policy:
          type: object
          additionalProperties: false
          required: [effective_state]
          properties:
            effective_state: { type: string, enum: [allowed, warning, blocked] }
        provider:
          $ref: '#/components/schemas/Provider'
          description: Present only when provider_visible is true.
        model:
          type: string
          description: Present only when provider_visible is true. Capability-only entries omit it.
    Balance:
      type: object
      additionalProperties: false
      required: [units_available, units_pending, tier]
      properties:
        units_available: { type: number }
        units_pending: { type: number }
        tier: { type: string }
    ClientTelemetryInput:
      type: object
      additionalProperties: false
      required: [request_id, event_id, ttft_ms]
      properties:
        request_id: { $ref: '#/components/schemas/RequestId' }
        event_id: { type: string, minLength: 8, maxLength: 128 }
        event_type: { type: string, const: timing, default: timing }
        ttft_ms: { type: integer, minimum: 0, maximum: 300000 }
        content_complete_ms: { type: integer, minimum: 0, maximum: 600000 }
        sdk:
          type: object
          additionalProperties: false
          required: [name, version]
          properties:
            name: { type: string, minLength: 1, maxLength: 64 }
            version: { type: string, minLength: 1, maxLength: 64 }
    OutputContract:
      type: object
      additionalProperties: false
      required: [max_visible_output_tokens]
      properties:
        max_visible_output_tokens: { type: integer, minimum: 1 }
        min_visible_output_tokens: { type: integer, minimum: 1 }
        reasoning:
          oneOf:
            - { type: object, additionalProperties: false, required: [mode], properties: { mode: { const: none } } }
            - { type: object, additionalProperties: false, required: [mode, max_tokens], properties: { mode: { const: bounded }, max_tokens: { type: integer, minimum: 1 } } }
        accepted_finish_reasons:
          type: array
          minItems: 1
          items: { type: string, enum: [stop, length, content_filter, tool_calls] }
        on_unsupported_reasoning: { type: string, enum: [fail, use_compatible_route], default: use_compatible_route }
    FeatureConfig:
      type: object
      additionalProperties: false
      required: [model]
      properties:
        display_label: { type: string, minLength: 1 }
        modality: { type: string, enum: [chat, embedding, image, audio], default: chat }
        upstream: { $ref: '#/components/schemas/Provider' }
        model: { type: string }
        fallback_models: { type: array, items: { type: string }, default: [] }
        max_output_tokens: { type: integer, minimum: 1, description: Legacy compatibility output ceiling. }
        output_contract: { $ref: '#/components/schemas/OutputContract' }
        unit_cost: { type: number, minimum: 0, exclusiveMinimum: 0, default: 1 }
        enabled: { type: boolean, default: true }
        key_policy: { type: string, enum: [developer, user, user_or_developer], default: developer }
        provider_visible: { type: boolean, default: false }
        mode: { type: string, enum: [mock, live] }
        provider_policy:
          readOnly: true
          type: object
          required: [effective_state, providers]
          properties:
            effective_state: { type: string, enum: [allowed, warning, blocked] }
            providers: { type: object, additionalProperties: { $ref: '#/components/schemas/ProviderPolicyEntry' } }
    AppConfig:
      type: object
      additionalProperties: false
      required: [app_id, name, auth, default_tier, tiers, features]
      properties:
        app_id: { $ref: '#/components/schemas/AppId' }
        name: { type: string }
        auth:
          oneOf:
            - type: object
              additionalProperties: false
              required: [mode, project_id]
              properties: { mode: { const: firebase }, project_id: { type: string } }
            - type: object
              additionalProperties: false
              required: [mode]
              properties: { mode: { const: dev, x-internal: true } }
        default_tier: { type: string }
        tiers:
          type: object
          additionalProperties:
            type: object
            additionalProperties: false
            required: [monthly_allowance_units]
            properties: { monthly_allowance_units: { type: number, minimum: 0 } }
        features:
          type: object
          additionalProperties: { $ref: '#/components/schemas/FeatureConfig' }
        ruleset: { $ref: '#/components/schemas/GenericObject' }
    TenantConfig:
      type: object
      additionalProperties: false
      required: [tenant_id, apps]
      properties:
        tenant_id: { $ref: '#/components/schemas/TenantId' }
        name: { type: string }
        apps: { type: array, items: { $ref: '#/components/schemas/AppConfig' } }
    ConfigDocument:
      type: object
      additionalProperties: false
      required: [tenants]
      properties:
        tenants: { type: array, minItems: 1, items: { $ref: '#/components/schemas/TenantConfig' } }
    ManagementKeyMintInput:
      type: object
      additionalProperties: false
      required: [app_ids, tool_groups, environment]
      properties:
        tenant_id: { $ref: '#/components/schemas/TenantId' }
        scope: { type: string, enum: [read, propose, apply], default: read }
        app_ids: { type: array, minItems: 1, items: { type: string } }
        tool_groups: { type: array, minItems: 1, items: { type: string } }
        environment: { type: string, enum: [test, live] }
        expires_at: { type: string, format: date-time }
        step_up: { type: boolean, description: Must be true for apply scope. }
    SessionGrantInput:
      type: object
      additionalProperties: false
      required: [app_ids, tool_groups]
      properties:
        scope: { type: string, enum: [read, propose, apply], default: propose }
        app_ids: { type: array, minItems: 1, items: { type: string } }
        tool_groups: { type: array, minItems: 1, items: { type: string } }
        environment: { type: string, enum: [test, live], default: test }
        expires_at: { type: string, format: date-time }
    DeveloperSession:
      type: object
      additionalProperties: false
      required: [actor, tenant, apps, provisioned, second_factor_age_minutes, api_version]
      properties:
        actor:
          type: object
          additionalProperties: false
          required: [type, user_id, session_id]
          properties:
            type: { type: string, const: clerk_session }
            user_id: { type: string }
            session_id: { type: string }
        tenant:
          type: object
          additionalProperties: false
          required: [id, name, named, created_at]
          properties:
            id: { $ref: '#/components/schemas/TenantId' }
            name: { type: string }
            named: { type: boolean, description: Whether the developer completed explicit tenant naming. }
            created_at: { type: string, format: date-time }
        apps:
          type: array
          items:
            type: object
            additionalProperties: false
            required: [id, name]
            properties:
              id: { $ref: '#/components/schemas/AppId' }
              name: { type: string }
              created_at: { type: string, format: date-time }
        provisioned: { type: boolean }
        second_factor_age_minutes: { type: [number, 'null'], minimum: 0 }
        api_version: { type: string, const: '2026-07-18' }
    DeveloperMember:
      type: object
      additionalProperties: false
      required: [id, auth_subject, role, created_at, last_seen_at]
      properties:
        id: { type: string }
        auth_subject: { type: string }
        role: { type: string, enum: [owner, member] }
        created_at: { type: string, format: date-time }
        last_seen_at: { type: string, format: date-time }
    SessionGrantAuthorization:
      type: object
      additionalProperties: false
      required: [code, code_expires_at, grant]
      properties:
        code: { type: string, pattern: '^wgc_[a-f0-9]{64}$', writeOnly: true }
        code_expires_at: { type: string, format: date-time }
        grant:
          type: object
          additionalProperties: false
          required: [tenant_id, scope, app_ids, tool_groups, environment, expires_at]
          properties:
            tenant_id: { $ref: '#/components/schemas/TenantId' }
            scope: { type: string, enum: [read, propose, apply] }
            app_ids: { type: array, items: { type: string } }
            tool_groups: { type: array, items: { type: string } }
            environment: { type: string, enum: [test, live] }
            expires_at: { type: string, format: date-time }
    ManagementKeyMetadata:
      type: object
      required: [id, tenant_id, scope, app_ids, tool_groups, environment, expires_at, revoked_at, last_used_at, created_at]
      properties:
        id: { type: string }
        tenant_id: { $ref: '#/components/schemas/TenantId' }
        scope: { type: string, enum: [read, propose, apply] }
        app_ids: { type: array, items: { type: string } }
        tool_groups: { type: array, items: { type: string } }
        environment: { type: string, enum: [test, live] }
        expires_at: { type: [string, 'null'], format: date-time }
        revoked_at: { type: [string, 'null'], format: date-time }
        last_used_at: { type: [string, 'null'], format: date-time }
        created_at: { type: string, format: date-time }
    MintedManagementKey:
      allOf:
        - { $ref: '#/components/schemas/ManagementKeyMetadata' }
        - type: object
          required: [value]
          properties:
            value: { $ref: '#/components/schemas/ManagementKeyValue' }
    GrantInput:
      type: object
      additionalProperties: false
      required: [units]
      properties:
        units: { type: number, exclusiveMinimum: 0 }
        source: { type: string }
        idempotency_key:
          type: string
          deprecated: true
          description: Deprecated body alias; use X-Idempotency-Key. Removal requires a future breaking version.
    ConfigProposalCreated:
      type: object
      additionalProperties: false
      required: [proposal_id, tenant_id, base_revision, status, expires_at]
      properties:
        proposal_id: { $ref: '#/components/schemas/ProposalId' }
        tenant_id: { $ref: '#/components/schemas/TenantId' }
        base_revision: { type: integer, minimum: 0 }
        status: { type: string, const: pending }
        expires_at: { type: string, format: date-time }
    WebhookProposalDocument:
      type: object
      additionalProperties: false
      required: [proposal_kind, tenant_id, app_id, operation, endpoint_id]
      properties:
        proposal_kind: { type: string, const: webhook }
        tenant_id: { $ref: '#/components/schemas/TenantId' }
        app_id: { $ref: '#/components/schemas/AppId' }
        operation: { type: string, enum: [create, update, delete] }
        endpoint_id: { type: string }
        url: { type: string, format: uri }
        event_types: { type: array, items: { $ref: '#/components/schemas/WebhookEventType' } }
        enabled: { type: boolean }
    WebhookProposalCreated:
      type: object
      additionalProperties: false
      required: [proposal_id, tenant_id, app_id, kind, operation, endpoint_id, base_revision, content_hash, status, expires_at, validation]
      properties:
        proposal_id: { $ref: '#/components/schemas/ProposalId' }
        tenant_id: { $ref: '#/components/schemas/TenantId' }
        app_id: { $ref: '#/components/schemas/AppId' }
        kind: { type: string, const: webhook }
        operation: { type: string, enum: [create, update, delete] }
        endpoint_id: { type: string }
        base_revision: { type: integer, minimum: 0 }
        content_hash: { type: string, pattern: '^[a-f0-9]{64}$' }
        status: { type: string, const: pending }
        expires_at: { type: string, format: date-time }
        validation:
          type: object
          additionalProperties: false
          required: [ok, warnings]
          properties:
            ok: { type: boolean, const: true }
            warnings: { type: array, items: { type: string } }
    ConfigProposal:
      type: object
      required: [proposal_id, tenant_id, base_revision, content_hash, status, document, created_by_key, applied_by_key, applied_revision, preview_id, created_at, expires_at, applied_at, cancelled_at]
      properties:
        proposal_id: { $ref: '#/components/schemas/ProposalId' }
        tenant_id: { $ref: '#/components/schemas/TenantId' }
        app_id: { type: [string, 'null'] }
        kind: { type: string, enum: [config, rules] }
        base_revision: { type: integer, minimum: 0 }
        content_hash: { type: string, pattern: '^[a-f0-9]{64}$' }
        status: { type: string, enum: [pending, applied, cancelled, expired] }
        document: { $ref: '#/components/schemas/TenantConfig' }
        created_by_key: { type: string }
        applied_by_key: { type: [string, 'null'] }
        applied_revision: { type: [integer, 'null'] }
        preview_id: { type: [string, 'null'] }
        created_at: { type: string, format: date-time }
        expires_at: { type: string, format: date-time }
        applied_at: { type: [string, 'null'], format: date-time }
        cancelled_at: { type: [string, 'null'], format: date-time }
    WebhookProposal:
      type: object
      additionalProperties: false
      required: [proposal_id, tenant_id, app_id, kind, base_revision, content_hash, status, document, created_by_key, applied_by_key, applied_revision, preview_id, created_at, expires_at, applied_at, cancelled_at]
      properties:
        proposal_id: { $ref: '#/components/schemas/ProposalId' }
        tenant_id: { $ref: '#/components/schemas/TenantId' }
        app_id: { $ref: '#/components/schemas/AppId' }
        kind: { type: string, const: webhook }
        base_revision: { type: integer, minimum: 0 }
        content_hash: { type: string, pattern: '^[a-f0-9]{64}$' }
        status: { type: string, enum: [pending, applied, cancelled, expired] }
        document: { $ref: '#/components/schemas/WebhookProposalDocument' }
        created_by_key: { type: string }
        applied_by_key: { type: [string, 'null'] }
        applied_revision: { type: [integer, 'null'] }
        preview_id: { type: [string, 'null'] }
        created_at: { type: string, format: date-time }
        expires_at: { type: string, format: date-time }
        applied_at: { type: [string, 'null'], format: date-time }
        cancelled_at: { type: [string, 'null'], format: date-time }
    ConfigProposalApplied:
      type: object
      additionalProperties: false
      required: [applied, proposal_id, revision, idempotent]
      properties:
        applied: { type: boolean, const: true }
        proposal_id: { $ref: '#/components/schemas/ProposalId' }
        revision: { type: [integer, 'null'] }
        idempotent: { type: boolean }
        result: { $ref: '#/components/schemas/GenericObject' }
    ProviderPolicyEntry:
      type: object
      additionalProperties: false
      required: [state, checked_at]
      properties:
        state: { type: string, enum: [allowed, warning, blocked] }
        checked_at: { type: string, format: date-time }
        reason_code: { type: [string, 'null'] }
        grace_until: { type: [string, 'null'], format: date-time }
        user_state:
          type: string
          enum: [allowed, warning, blocked]
          description: Optional key_source=user override; developer routes continue to use state.
        user_checked_at:
          type: string
          format: date-time
          description: Required when user_state is present.
        user_reason_code: { type: [string, 'null'] }
    ProviderPolicyDocument:
      type: object
      additionalProperties: false
      required: [providers]
      properties:
        providers:
          type: object
          required: [openrouter, openai, anthropic, google, xai]
          additionalProperties: false
          properties:
            openrouter: { $ref: '#/components/schemas/ProviderPolicyEntry' }
            openai: { $ref: '#/components/schemas/ProviderPolicyEntry' }
            anthropic: { $ref: '#/components/schemas/ProviderPolicyEntry' }
            google: { $ref: '#/components/schemas/ProviderPolicyEntry' }
            xai: { $ref: '#/components/schemas/ProviderPolicyEntry' }
    WebhookEventType:
      type: string
      enum: [allowance.low, allowance.exhausted, rule.breached, provider_policy.changed, grant.created, grant.reversed]
    WebhookEvent:
      type: object
      additionalProperties: false
      required: [id, type, created_at, app_id, user_id, data]
      properties:
        id: { type: string }
        type: { $ref: '#/components/schemas/WebhookEventType' }
        created_at: { type: string, format: date-time }
        app_id: { $ref: '#/components/schemas/AppId' }
        user_id: { type: [string, 'null'] }
        data: { $ref: '#/components/schemas/GenericObject' }
    ProviderPolicyWithRevision:
      allOf:
        - { $ref: '#/components/schemas/ProviderPolicyDocument' }
        - type: object
          required: [revision]
          properties: { revision: { type: integer, minimum: 0 } }
    ProviderKeyMetadata:
      type: object
      additionalProperties: false
      required: [id, tenant_id, provider, label, health, last_checked_at, revoked_at, created_at, updated_at]
      properties:
        id: { type: string }
        tenant_id: { $ref: '#/components/schemas/TenantId' }
        provider: { $ref: '#/components/schemas/Provider' }
        label: { type: string }
        health: { type: string, enum: [unknown, healthy, unhealthy] }
        last_checked_at: { type: [string, 'null'], format: date-time }
        revoked_at: { type: [string, 'null'], format: date-time }
        created_at: { type: string, format: date-time }
        updated_at: { type: string, format: date-time }
    WebhookMetadata:
      type: object
      additionalProperties: false
      required: [id, app_id, url, event_types, enabled, created_at, updated_at]
      properties:
        id: { type: string }
        app_id: { $ref: '#/components/schemas/AppId' }
        url: { type: string, format: uri }
        event_types: { type: array, items: { $ref: '#/components/schemas/WebhookEventType' } }
        enabled: { type: boolean }
        created_at: { type: string, format: date-time }
        updated_at: { type: string, format: date-time }
    WebhookWithSecret:
      allOf:
        - { $ref: '#/components/schemas/WebhookMetadata' }
        - type: object
          required: [signing_secret]
          properties:
            signing_secret: { type: string, pattern: '^whsec_', writeOnly: true, x-sensitive: true }
    WebhookDelivery:
      type: object
      additionalProperties: false
      required: [id, event_id, event_type, endpoint_id, status, attempts, next_attempt_at, last_attempt_at, delivered_at, last_http_status, last_error, created_at]
      properties:
        id: { type: string }
        event_id: { type: string }
        event_type: { $ref: '#/components/schemas/WebhookEventType' }
        endpoint_id: { type: string }
        status: { type: string, enum: [pending, delivering, delivered, dead_letter] }
        attempts: { type: integer, minimum: 0, maximum: 8 }
        next_attempt_at: { type: string, format: date-time }
        last_attempt_at: { type: [string, 'null'], format: date-time }
        delivered_at: { type: [string, 'null'], format: date-time }
        last_http_status: { type: [integer, 'null'] }
        last_error: { type: [string, 'null'] }
        created_at: { type: string, format: date-time }
    GrantRow:
      type: object
      additionalProperties: false
      required: [id, appId, userId, units, source, idempotencyKey, reversed, createdAt]
      properties:
        id: { type: string }
        appId: { $ref: '#/components/schemas/AppId' }
        userId: { type: string }
        units: { type: number }
        source: { type: string }
        idempotencyKey: { type: string }
        reversed: { type: boolean }
        createdAt: { type: integer, description: Unix epoch milliseconds }
    StoreBalance:
      type: object
      additionalProperties: false
      required: [available, pending]
      properties:
        available: { type: number }
        pending: { type: number }
    UsageRollup:
      type: object
      additionalProperties: false
      required: [key, requests, developer_requests, user_key_requests, units, prompt_tokens, completion_tokens, provider_cost_usd, user_cost_usd]
      properties:
        key: { type: string }
        requests: { type: integer }
        developer_requests: { type: integer }
        user_key_requests: { type: integer }
        units: { type: number }
        prompt_tokens: { type: integer }
        completion_tokens: { type: integer }
        provider_cost_usd: { type: number }
        user_cost_usd: { type: number }
    UsageRollupPage:
      type: object
      additionalProperties: false
      required: [group_by, window_events, window, pagination, groups]
      properties:
        group_by: { type: string, enum: [feature, user] }
        window_events: { type: integer }
        window: { $ref: '#/components/schemas/QueryWindow' }
        pagination: { $ref: '#/components/schemas/Pagination' }
        groups: { type: array, items: { $ref: '#/components/schemas/UsageRollup' } }
    QueryWindow:
      type: object
      additionalProperties: false
      required: [since, until]
      properties:
        since: { type: [string, 'null'], format: date-time }
        until: { type: [string, 'null'], format: date-time }
    Pagination:
      type: object
      additionalProperties: false
      required: [limit, returned, truncated]
      properties:
        limit: { type: integer }
        returned: { type: integer }
        truncated: { type: boolean }
    PricingInsights:
      type: object
      additionalProperties: false
      required: [app_id, window, minimum_sample_users, percentile_method, groups]
      properties:
        app_id: { $ref: '#/components/schemas/AppId' }
        window:
          type: object
          required: [since, until, closed]
          properties:
            since: { type: string, format: date-time }
            until: { type: string, format: date-time }
            closed: { type: boolean, const: true }
        minimum_sample_users: { type: integer, const: 20 }
        percentile_method: { type: string, const: nearest_rank }
        groups:
          type: array
          items:
            type: object
            required: [feature_id, tier, sample_users, completeness, percentiles_usd, suppression]
            properties:
              feature_id: { $ref: '#/components/schemas/FeatureId' }
              tier: { type: string }
              sample_users: { type: integer }
              completeness: { $ref: '#/components/schemas/GenericObject' }
              percentiles_usd:
                oneOf:
                  - type: object
                    required: [p50, p95, p99]
                    properties: { p50: { type: number }, p95: { type: number }, p99: { type: number } }
                  - { type: 'null' }
              suppression:
                oneOf:
                  - type: object
                    required: [reason, required_users, observed_users]
                    properties:
                      reason: { type: string, const: insufficient_samples }
                      required_users: { type: integer }
                      observed_users: { type: integer }
                  - { type: 'null' }
    VelocityLimit:
      type: object
      additionalProperties: false
      required: [window_seconds]
      properties:
        window_seconds: { type: integer, minimum: 1, maximum: 86400 }
        max_requests: { type: integer, minimum: 1 }
        max_units: { type: number, exclusiveMinimum: 0 }
        expires_at: { type: string, format: date-time }
      anyOf: [{ required: [max_requests] }, { required: [max_units] }]
    RulesetDocument:
      type: object
      additionalProperties: false
      properties:
        velocity:
          type: object
          additionalProperties: false
          properties:
            tiers: { type: object, additionalProperties: { $ref: '#/components/schemas/VelocityLimit' } }
            user_overrides: { type: object, additionalProperties: { $ref: '#/components/schemas/VelocityLimit' } }
    Ruleset:
      allOf:
        - { $ref: '#/components/schemas/RulesetDocument' }
        - type: object
          required: [revision]
          properties: { revision: { type: integer, minimum: 0 } }
    RulesPreview:
      type: object
      additionalProperties: false
      required: [preview_id, app_id, ruleset_revision, content_hash, window, impact, expires_at]
      properties:
        preview_id: { type: string, pattern: '^rprev_' }
        app_id: { $ref: '#/components/schemas/AppId' }
        ruleset_revision: { type: integer }
        content_hash: { type: string, pattern: '^[a-f0-9]{64}$' }
        window:
          type: object
          required: [since, until]
          properties: { since: { type: string, format: date-time }, until: { type: string, format: date-time } }
        impact:
          type: object
          required: [traffic_calls, evaluated_calls, unevaluable_calls, traffic_complete, would_limit_users, would_limit_calls, would_limit_units, would_limit_provider_cost_usd, provider_cost_known_limited_calls]
          additionalProperties: false
          properties:
            traffic_calls: { type: integer }
            evaluated_calls: { type: integer }
            unevaluable_calls: { type: integer }
            traffic_complete: { type: boolean }
            would_limit_users: { type: integer }
            would_limit_calls: { type: integer }
            would_limit_units: { type: number }
            would_limit_provider_cost_usd: { type: number }
            provider_cost_known_limited_calls: { type: integer }
        expires_at: { type: string, format: date-time }
    RulesProposalCreated:
      allOf:
        - { $ref: '#/components/schemas/ConfigProposalCreated' }
        - type: object
          required: [preview_id, app_id, ruleset_revision]
          properties:
            preview_id: { type: string, pattern: '^rprev_' }
            app_id: { $ref: '#/components/schemas/AppId' }
            ruleset_revision: { type: integer }
    UserRow:
      type: object
      additionalProperties: false
      required: [id, appId, externalId, tier, anonymous, anonymizedAt]
      properties:
        id: { type: string }
        appId: { $ref: '#/components/schemas/AppId' }
        externalId: { type: [string, 'null'] }
        tier: { type: string }
        anonymous: { type: boolean }
        anonymizedAt: { type: [integer, 'null'], description: Unix epoch milliseconds }
    ConfigRead:
      description: Platform reads return tenants plus a revision map; tenant reads return one visible tenant plus revision.
      oneOf:
        - type: object
          required: [tenants, revisions]
          properties:
            tenants: { type: array, items: { $ref: '#/components/schemas/TenantConfig' } }
            revisions: { type: object, additionalProperties: { type: integer } }
        - allOf:
            - { $ref: '#/components/schemas/TenantConfig' }
            - type: object
              required: [revision]
              properties: { revision: { type: integer } }
    ConfigApplied:
      type: object
      additionalProperties: false
      required: [applied, apps, revisions]
      properties:
        applied: { type: boolean, const: true }
        apps: { type: array, items: { $ref: '#/components/schemas/AppId' } }
        revisions: { type: object, additionalProperties: { type: integer } }
    DoctorResult:
      type: object
      required: [check, status, code, billable]
      properties:
        check: { type: string, enum: [auth, provider, webhook] }
        status: { type: string, enum: [passed, blocked, skipped] }
        code: { type: string }
        billable: { type: boolean, const: false }
        remediation: { $ref: '#/components/schemas/GenericObject' }
        probes: { type: array, items: { $ref: '#/components/schemas/GenericObject' } }
    FeatureTemplate:
      type: object
      additionalProperties: false
      required: [id, name, description, feature]
      properties:
        id: { type: string }
        name: { type: string }
        description: { type: string }
        feature: { $ref: '#/components/schemas/FeatureConfig' }
    SandboxFixture:
      type: object
      required: [id, outcome, http_status, billable]
      properties:
        id: { type: string }
        outcome: { type: string, enum: [success, error] }
        error_type: { $ref: '#/components/schemas/ErrorType' }
        http_status: { type: integer }
        billable: { type: boolean, const: false }
    SandboxSession:
      type: object
      required: [id, appId, name, createdAt]
      properties:
        id: { type: string, pattern: '^sbox_' }
        appId: { $ref: '#/components/schemas/AppId' }
        name: { type: string }
        createdAt: { type: integer, description: Unix epoch milliseconds }
        created_at: { type: string, format: date-time }
        runs: { type: array, items: { $ref: '#/components/schemas/GenericObject' } }
    Readiness:
      type: object
      required: [app_id, target, status, steps]
      properties:
        app_id: { $ref: '#/components/schemas/AppId' }
        target: { type: string, const: first_metered_call }
        status: { type: string, enum: [passed, blocked] }
        steps:
          type: array
          items:
            type: object
            required: [id, status, updated_at]
            additionalProperties: true
            properties:
              id: { type: string, enum: [auth, provider_key, feature, sandbox, integration] }
              status: { type: string, enum: [not_started, in_progress, blocked, passed, skipped] }
              updated_at: { type: string, format: date-time }
    RequestTrace:
      type: object
      additionalProperties: false
      required: [request_id, app_id, auth, server, money, usage_event, client_telemetry]
      properties:
        request_id: { $ref: '#/components/schemas/RequestId' }
        app_id: { $ref: '#/components/schemas/AppId' }
        auth: { $ref: '#/components/schemas/GenericObject' }
        server: { oneOf: [{ $ref: '#/components/schemas/GenericObject' }, { type: 'null' }] }
        money: { oneOf: [{ $ref: '#/components/schemas/GenericObject' }, { type: 'null' }] }
        usage_event: { oneOf: [{ $ref: '#/components/schemas/GenericObject' }, { type: 'null' }] }
        client_telemetry: { oneOf: [{ $ref: '#/components/schemas/GenericObject' }, { type: 'null' }] }
    IntegrationSnippet:
      type: object
      additionalProperties: false
      required: [mode, format, app_id, feature_id, snippet]
      properties:
        mode: { type: string, const: http }
        format: { type: string, const: curl }
        app_id: { $ref: '#/components/schemas/AppId' }
        feature_id: { $ref: '#/components/schemas/FeatureId' }
        snippet: { type: string }
    AuditEvent:
      type: object
      additionalProperties: false
      required: [id, tenant_id, actor_key_id, action, resource_type, resource_id, detail, request_id, created_at]
      properties:
        id: { type: string }
        tenant_id: { $ref: '#/components/schemas/TenantId' }
        actor_key_id: { type: string }
        action: { type: string }
        resource_type: { type: string }
        resource_id: { type: string }
        detail: { $ref: '#/components/schemas/GenericObject' }
        request_id: { type: [string, 'null'] }
        created_at: { type: string, format: date-time }
    AuditPage:
      type: object
      additionalProperties: false
      required: [data, pagination]
      properties:
        data: { type: array, items: { $ref: '#/components/schemas/AuditEvent' } }
        pagination:
          type: object
          additionalProperties: false
          required: [limit, returned, truncated]
          properties:
            limit: { type: integer }
            returned: { type: integer }
            truncated: { type: boolean }

  responses:
    DeveloperSessionOk:
      description: Current developer identity and provisioned tenant context
      headers: &developerSessionHeaders
        Weirgate-Api-Version: { $ref: '#/components/headers/WeirgateApiVersion' }
        X-Weirgate-Request-Id: { $ref: '#/components/headers/RequestId' }
      content: { application/json: { schema: { $ref: '#/components/schemas/DeveloperSession' } } }
    DeveloperMemberOk:
      description: Weirgate-owned developer membership and role
      headers: *developerSessionHeaders
      content: { application/json: { schema: { $ref: '#/components/schemas/DeveloperMember' } } }
    DeveloperMemberListOk:
      description: Weirgate-owned developer memberships and roles
      headers: *developerSessionHeaders
      content:
        application/json:
          schema:
            type: object
            additionalProperties: false
            required: [data]
            properties:
              data: { type: array, items: { $ref: '#/components/schemas/DeveloperMember' } }
    SessionGrantAuthorized:
      description: Single-use consent code and bounded grant metadata
      headers: *developerSessionHeaders
      content: { application/json: { schema: { $ref: '#/components/schemas/SessionGrantAuthorization' } } }
    ManagementIdentityOk:
      description: Authenticated management identity and grant bounds
      headers: &standardResponseHeaders
        Weirgate-Api-Version: { $ref: '#/components/headers/WeirgateApiVersion' }
        X-Weirgate-Request-Id: { $ref: '#/components/headers/RequestId' }
      content: { application/json: { schema: { $ref: '#/components/schemas/ManagementIdentity' } } }
    AgentDocSearchOk:
      description: Ranked canonical Markdown excerpts
      headers: *standardResponseHeaders
      content:
        application/json:
          schema:
            type: object
            additionalProperties: false
            required: [data, pagination]
            properties:
              data: { type: array, items: { $ref: '#/components/schemas/AgentDocSummary' } }
              pagination: { $ref: '#/components/schemas/Pagination' }
    AgentDocOk:
      description: Canonical Markdown page
      headers: *standardResponseHeaders
      content: { application/json: { schema: { $ref: '#/components/schemas/AgentDoc' } } }
    AuthPresetListOk:
      description: Non-secret authentication presets and custom JWKS template
      headers: *standardResponseHeaders
      content:
        application/json:
          schema:
            type: object
            additionalProperties: false
            required: [data]
            properties: { data: { type: array, items: { $ref: '#/components/schemas/AuthPreset' } } }
    ProviderPolicyOk:
      description: Current policy registry and revision
      headers: *standardResponseHeaders
      content: { application/json: { schema: { $ref: '#/components/schemas/ProviderPolicyWithRevision' } } }
    ProviderKeyOk:
      description: Provider-key metadata; plaintext and ciphertext are never returned
      headers: *standardResponseHeaders
      content: { application/json: { schema: { $ref: '#/components/schemas/ProviderKeyMetadata' } } }
    ProviderKeyListOk:
      description: Provider-key metadata list
      headers: *standardResponseHeaders
      content:
        application/json:
          schema:
            type: object
            additionalProperties: false
            required: [data]
            properties: { data: { type: array, items: { $ref: '#/components/schemas/ProviderKeyMetadata' } } }
    ManagementKeyOk:
      description: Management-key metadata without key material
      headers: *standardResponseHeaders
      content: { application/json: { schema: { $ref: '#/components/schemas/ManagementKeyMetadata' } } }
    ManagementKeyListOk:
      description: Bounded management-key metadata
      headers: *standardResponseHeaders
      content:
        application/json:
          schema:
            type: object
            additionalProperties: false
            required: [data]
            properties: { data: { type: array, items: { $ref: '#/components/schemas/ManagementKeyMetadata' } } }
    WebhookOk:
      description: Webhook metadata without signing material
      headers: *standardResponseHeaders
      content: { application/json: { schema: { $ref: '#/components/schemas/WebhookMetadata' } } }
    WebhookSecretOk:
      description: Webhook metadata plus reveal-once signing secret
      headers: *standardResponseHeaders
      content: { application/json: { schema: { $ref: '#/components/schemas/WebhookWithSecret' } } }
    WebhookListOk:
      description: Webhook metadata list
      headers: *standardResponseHeaders
      content:
        application/json:
          schema:
            type: object
            additionalProperties: false
            required: [data]
            properties: { data: { type: array, items: { $ref: '#/components/schemas/WebhookMetadata' } } }
    WebhookDeliveryListOk:
      description: Delivery attempts
      headers: *standardResponseHeaders
      content:
        application/json:
          schema:
            type: object
            additionalProperties: false
            required: [data]
            properties: { data: { type: array, items: { $ref: '#/components/schemas/WebhookDelivery' } } }
    WebhookReplayOk:
      description: Requeued delivery retaining its event id
      headers: *standardResponseHeaders
      content:
        application/json:
          schema:
            type: object
            additionalProperties: false
            required: [id, status, event_id]
            properties: { id: { type: string }, status: { type: string, const: pending }, event_id: { type: string } }
    WebhookDeleteOk:
      description: Deleted webhook id
      headers: *standardResponseHeaders
      content:
        application/json:
          schema:
            type: object
            additionalProperties: false
            required: [id, deleted]
            properties: { id: { type: string }, deleted: { type: boolean, const: true } }
    WebhookProposalCreatedOk:
      description: Durable webhook proposal without signing material
      headers: *standardResponseHeaders
      content: { application/json: { schema: { $ref: '#/components/schemas/WebhookProposalCreated' } } }
    WebhookProposalOk:
      description: Durable webhook proposal without signing material
      headers: *standardResponseHeaders
      content: { application/json: { schema: { $ref: '#/components/schemas/WebhookProposal' } } }
    WebhookProposalListOk:
      description: Visible durable webhook proposal summaries
      headers: *standardResponseHeaders
      content:
        application/json:
          schema:
            type: object
            additionalProperties: false
            required: [data, pagination]
            properties:
              data: { type: array, items: { $ref: '#/components/schemas/GenericObject' } }
              pagination:
                type: object
                additionalProperties: false
                required: [limit, returned, truncated, next_cursor]
                properties:
                  limit: { type: integer }
                  returned: { type: integer }
                  truncated: { type: boolean }
                  next_cursor: { type: [string, 'null'] }
    GrantOk:
      description: Idempotent grant and resulting balance
      headers: *standardResponseHeaders
      content:
        application/json:
          schema:
            type: object
            additionalProperties: false
            required: [grant, balance]
            properties: { grant: { $ref: '#/components/schemas/GrantRow' }, balance: { $ref: '#/components/schemas/StoreBalance' } }
    GrantReversalOk:
      description: Reversed grant and resulting balance
      headers: *standardResponseHeaders
      content:
        application/json:
          schema:
            type: object
            additionalProperties: false
            required: [grant, balance, idempotency_key]
            properties:
              grant: { $ref: '#/components/schemas/GrantRow' }
              balance: { $ref: '#/components/schemas/StoreBalance' }
              idempotency_key: { type: [string, 'null'] }
    UsageOk:
      description: Windowed usage rollup
      headers: *standardResponseHeaders
      content: { application/json: { schema: { $ref: '#/components/schemas/UsageRollupPage' } } }
    PricingInsightsOk:
      description: Closed-window pricing evidence
      headers: *standardResponseHeaders
      content: { application/json: { schema: { $ref: '#/components/schemas/PricingInsights' } } }
    RulesOk:
      description: Current app ruleset and revision
      headers: *standardResponseHeaders
      content:
        application/json:
          schema:
            type: object
            additionalProperties: false
            required: [app_id, revision, ruleset]
            properties:
              app_id: { $ref: '#/components/schemas/AppId' }
              revision: { type: integer, minimum: 0 }
              ruleset: { $ref: '#/components/schemas/Ruleset' }
    RulesPreviewCreated:
      description: Durable hash-bound traffic preview
      headers: *standardResponseHeaders
      content: { application/json: { schema: { $ref: '#/components/schemas/RulesPreview' } } }
    RulesProposalCreated:
      description: Durable preview-bound rules proposal
      headers: *standardResponseHeaders
      content: { application/json: { schema: { $ref: '#/components/schemas/RulesProposalCreated' } } }
    UserListOk:
      description: Active app users
      headers: *standardResponseHeaders
      content:
        application/json:
          schema:
            type: object
            additionalProperties: false
            required: [data, pagination]
            properties:
              data: { type: array, items: { $ref: '#/components/schemas/UserRow' } }
              pagination:
                type: object
                additionalProperties: false
                required: [limit, returned, truncated, next_cursor]
                properties:
                  limit: { type: integer }
                  returned: { type: integer }
                  truncated: { type: boolean }
                  next_cursor: { type: [string, 'null'] }
    UserOk:
      description: Existing user, balance, grants, and recent usage
      headers: *standardResponseHeaders
      content:
        application/json:
          schema:
            type: object
            additionalProperties: false
            required: [user, balance, grants, recent_events, recent_events_pagination]
            properties:
              user: { $ref: '#/components/schemas/UserRow' }
              balance: { $ref: '#/components/schemas/StoreBalance' }
              grants: { type: array, items: { $ref: '#/components/schemas/GrantRow' } }
              recent_events: { type: array, items: { $ref: '#/components/schemas/GenericObject' } }
              recent_events_pagination: { $ref: '#/components/schemas/Pagination' }
    UserExportOk:
      description: DSAR export
      headers: *standardResponseHeaders
      content:
        application/json:
          schema:
            type: object
            additionalProperties: false
            required: [identity, balance, grants, usage, client_telemetry, exported_at]
            properties:
              identity: { $ref: '#/components/schemas/GenericObject' }
              balance: { $ref: '#/components/schemas/StoreBalance' }
              grants: { type: array, items: { $ref: '#/components/schemas/GrantRow' } }
              usage: { type: array, items: { $ref: '#/components/schemas/GenericObject' } }
              client_telemetry: { type: array, items: { $ref: '#/components/schemas/GenericObject' } }
              exported_at: { type: string, format: date-time }
    UserDeleteOk:
      description: Idempotent anonymization result
      headers: *standardResponseHeaders
      content:
        application/json:
          schema:
            type: object
            required: [deleted, idempotent]
            properties:
              deleted: { type: boolean, const: true }
              idempotent: { type: boolean }
              user_id: { type: string }
              anonymized_at: { type: string, format: date-time }
    ConfigReadOk:
      description: Visible config and durable revision metadata
      headers: *standardResponseHeaders
      content: { application/json: { schema: { $ref: '#/components/schemas/ConfigRead' } } }
    ConfigAppliedOk:
      description: Applied direct config revisions
      headers: *standardResponseHeaders
      content: { application/json: { schema: { $ref: '#/components/schemas/ConfigApplied' } } }
    ConfigProposalListOk:
      description: Visible durable proposal summaries
      headers: *standardResponseHeaders
      content:
        application/json:
          schema:
            type: object
            additionalProperties: false
            required: [data, pagination]
            properties:
              data: { type: array, items: { $ref: '#/components/schemas/GenericObject' } }
              pagination:
                type: object
                additionalProperties: false
                required: [limit, returned, truncated, next_cursor]
                properties:
                  limit: { type: integer }
                  returned: { type: integer }
                  truncated: { type: boolean }
                  next_cursor: { type: [string, 'null'] }
    ConfigProposalAppliedOk:
      description: Applied revision or idempotent replay
      headers: *standardResponseHeaders
      content: { application/json: { schema: { $ref: '#/components/schemas/ConfigProposalApplied' } } }
    ProposalCancelledOk:
      description: Cancelled proposal
      headers: *standardResponseHeaders
      content:
        application/json:
          schema:
            type: object
            additionalProperties: false
            required: [proposal_id, status]
            properties: { proposal_id: { $ref: '#/components/schemas/ProposalId' }, status: { type: string, const: cancelled } }
    DoctorOk:
      description: Non-billable probe evidence
      headers: *standardResponseHeaders
      content:
        application/json:
          schema:
            type: object
            additionalProperties: false
            required: [app_id, billable, results]
            properties:
              app_id: { $ref: '#/components/schemas/AppId' }
              billable: { type: boolean, const: false }
              results: { type: array, items: { $ref: '#/components/schemas/DoctorResult' } }
    FeatureTemplateListOk:
      description: Built-in feature templates
      headers: *standardResponseHeaders
      content:
        application/json:
          schema:
            type: object
            additionalProperties: false
            required: [data]
            properties: { data: { type: array, items: { $ref: '#/components/schemas/FeatureTemplate' } } }
    TemplateInstantiated:
      description: Instantiated template and config revision
      headers: *standardResponseHeaders
      content:
        application/json:
          schema:
            type: object
            required: [instantiated, app_id, feature_id, template_id, config_revision, feature]
            properties:
              instantiated: { type: boolean, const: true }
              app_id: { $ref: '#/components/schemas/AppId' }
              feature_id: { $ref: '#/components/schemas/FeatureId' }
              template_id: { type: string }
              config_revision: { type: integer }
              feature: { $ref: '#/components/schemas/FeatureConfig' }
    SandboxFixturesOk:
      description: Complete deterministic fixture registry
      headers: *standardResponseHeaders
      content:
        application/json:
          schema:
            type: object
            additionalProperties: false
            required: [app_id, deterministic, data]
            properties:
              app_id: { $ref: '#/components/schemas/AppId' }
              deterministic: { type: boolean, const: true }
              data: { type: array, items: { $ref: '#/components/schemas/SandboxFixture' } }
    SandboxSessionCreated:
      description: Durable sandbox session
      headers: *standardResponseHeaders
      content: { application/json: { schema: { $ref: '#/components/schemas/SandboxSession' } } }
    SandboxSessionOk:
      description: Sandbox session and runs
      headers: *standardResponseHeaders
      content: { application/json: { schema: { $ref: '#/components/schemas/SandboxSession' } } }
    SandboxRunCreated:
      description: Deterministic fixture outcome
      headers: *standardResponseHeaders
      content:
        application/json:
          schema:
            type: object
            additionalProperties: true
            required: [id, sessionId, appId, fixture, requestId, status, providerCostUsd, unitsDebited, createdAt, billable, outcome]
            properties:
              id: { type: string, pattern: '^srun_' }
              sessionId: { type: string, pattern: '^sbox_' }
              appId: { $ref: '#/components/schemas/AppId' }
              fixture: { type: string }
              requestId: { $ref: '#/components/schemas/RequestId' }
              status: { type: string, const: passed }
              providerCostUsd: { type: number, const: 0 }
              unitsDebited: { type: number, const: 0 }
              createdAt: { type: integer }
              billable: { type: boolean, const: false }
              outcome: { $ref: '#/components/schemas/GenericObject' }
    ReadinessOk:
      description: Durable onboarding readiness
      headers: *standardResponseHeaders
      content: { application/json: { schema: { $ref: '#/components/schemas/Readiness' } } }
    RequestTraceOk:
      description: Correlated request trace
      headers: *standardResponseHeaders
      content: { application/json: { schema: { $ref: '#/components/schemas/RequestTrace' } } }
    IntegrationSnippetOk:
      description: HTTP/curl integration snippet
      headers: *standardResponseHeaders
      content: { application/json: { schema: { $ref: '#/components/schemas/IntegrationSnippet' } } }
    GenericOk:
      description: Successful JSON response; route-specific shape is stable runtime behavior described by its operation.
      headers:
        Weirgate-Api-Version: { $ref: '#/components/headers/WeirgateApiVersion' }
        X-Weirgate-Request-Id: { $ref: '#/components/headers/RequestId' }
      content:
        application/json:
          schema: { $ref: '#/components/schemas/GenericObject' }
    GenericCreated:
      description: Created JSON resource
      headers:
        Weirgate-Api-Version: { $ref: '#/components/headers/WeirgateApiVersion' }
        X-Weirgate-Request-Id: { $ref: '#/components/headers/RequestId' }
      content:
        application/json:
          schema: { $ref: '#/components/schemas/GenericObject' }
    InvalidRequest: { $ref: '#/components/responses/ErrorInvalidRequest' }
    InvalidToken: { $ref: '#/components/responses/ErrorInvalidToken' }
    OutOfAllowance: { $ref: '#/components/responses/ErrorOutOfAllowance' }
    InsufficientScope: { $ref: '#/components/responses/ErrorInsufficientScope' }
    ResourceNotFound: { $ref: '#/components/responses/ErrorResourceNotFound' }
    ProposalStale: { $ref: '#/components/responses/ErrorProposalStale' }
    RateLimited: { $ref: '#/components/responses/ErrorRateLimited' }
    TelemetryRequestUnavailable: { $ref: '#/components/responses/ErrorTelemetryRequestUnavailable' }
    ForbiddenDataPlane:
      description: abuse_blocked, feature_disabled, or provider_policy_blocked
      headers:
        X-Weirgate-Error-Type: { $ref: '#/components/headers/ErrorType' }
        X-Weirgate-Request-Id: { $ref: '#/components/headers/RequestId' }
        Weirgate-Api-Version: { $ref: '#/components/headers/WeirgateApiVersion' }
      content: { application/json: { schema: { $ref: '#/components/schemas/ErrorEnvelope' } } }
    NotFoundDataPlane:
      description: feature_not_found or resource_not_found
      headers:
        X-Weirgate-Error-Type: { $ref: '#/components/headers/ErrorType' }
        X-Weirgate-Request-Id: { $ref: '#/components/headers/RequestId' }
        Weirgate-Api-Version: { $ref: '#/components/headers/WeirgateApiVersion' }
      content: { application/json: { schema: { $ref: '#/components/schemas/ErrorEnvelope' } } }
    UnprocessableDataPlane:
      description: user-provider-key or output-contract validation failure
      headers:
        X-Weirgate-Error-Type: { $ref: '#/components/headers/ErrorType' }
        X-Weirgate-Request-Id: { $ref: '#/components/headers/RequestId' }
        Weirgate-Api-Version: { $ref: '#/components/headers/WeirgateApiVersion' }
      content: { application/json: { schema: { $ref: '#/components/schemas/ErrorEnvelope' } } }
    UpstreamFailure:
      description: provider_unavailable or output_contract_violation
      headers:
        X-Weirgate-Error-Type: { $ref: '#/components/headers/ErrorType' }
        X-Weirgate-Request-Id: { $ref: '#/components/headers/RequestId' }
        Weirgate-Api-Version: { $ref: '#/components/headers/WeirgateApiVersion' }
      content: { application/json: { schema: { $ref: '#/components/schemas/ErrorEnvelope' } } }
    Internal: { $ref: '#/components/responses/ErrorInternal' }
    ErrorInvalidRequest: { $ref: '#/components/responses/TypedError' }
    ErrorInvalidToken: { $ref: '#/components/responses/TypedError' }
    ErrorUserProviderKeyRequired: { $ref: '#/components/responses/TypedError' }
    ErrorUserProviderKeyInvalid: { $ref: '#/components/responses/TypedError' }
    ErrorInsufficientScope: { $ref: '#/components/responses/TypedError' }
    ErrorOutOfAllowance: { $ref: '#/components/responses/TypedError' }
    ErrorAbuseBlocked: { $ref: '#/components/responses/TypedError' }
    ErrorFeatureDisabled: { $ref: '#/components/responses/TypedError' }
    ErrorFeatureNotFound: { $ref: '#/components/responses/TypedError' }
    ErrorResourceNotFound: { $ref: '#/components/responses/TypedError' }
    ErrorProviderPolicyBlocked: { $ref: '#/components/responses/TypedError' }
    ErrorOutputContractUnsupported: { $ref: '#/components/responses/TypedError' }
    ErrorOutputContractViolation: { $ref: '#/components/responses/TypedError' }
    ErrorProposalStale: { $ref: '#/components/responses/TypedError' }
    ErrorRateLimited: { $ref: '#/components/responses/TypedError' }
    ErrorTelemetryRequestUnavailable: { $ref: '#/components/responses/TypedError' }
    ErrorProviderUnavailable: { $ref: '#/components/responses/TypedError' }
    ErrorInternal: { $ref: '#/components/responses/TypedError' }
    TypedError:
      description: Typed Weirgate error. The component name selects the registry member and its documented status.
      headers:
        X-Weirgate-Error-Type: { $ref: '#/components/headers/ErrorType' }
        X-Weirgate-Request-Id: { $ref: '#/components/headers/RequestId' }
        Weirgate-Api-Version: { $ref: '#/components/headers/WeirgateApiVersion' }
      content:
        application/json:
          schema: { $ref: '#/components/schemas/ErrorEnvelope' }
