openapi: 3.0.3
info:
  title: Rustybin
  description: A high-performance, all-in-one HTTP stub service for API gateway testing. Built in Rust with axum. Designed to exercise every category of API gateway feature - auth, routing, transformation, rate limiting, AI proxying, and more.
  version: 0.6.0
  contact:
    name: Rustybin
servers:
- url: http://localhost
  description: HTTP
- url: https://localhost
  description: HTTPS / mTLS
tags:
- name: Utility
  description: Health, identity, image, and flaky endpoints
- name: Echo
  description: Echo and anything endpoints
- name: Status
  description: HTTP status code responses
- name: Response Shaping
  description: Delay, cache, and response-headers
- name: Redirects & Cookies
  description: Redirect chains and cookie management
- name: Info
  description: IP, date, and time information
- name: Random
  description: UUID, random numbers, and lorem ipsum
- name: Auth
  description: Authentication endpoints (basic, api-key, JWT, mTLS, OIDC)
- name: AI Gateway
  description: OpenAI-compatible AI endpoints
- name: GraphQL
  description: GraphQL API with playground
- name: Orchestration
  description: Multi-step orchestration pipeline
- name: SOAP
  description: SOAP/XML web service
- name: WebSocket
  description: WebSocket echo and server-push endpoints
- name: Control Plane
  description: Request inspector, configuration and version under /_rustybin
paths:
  /:
    get:
      tags:
      - Utility
      summary: Landing page
      description: HTML landing page with endpoint directory and quick-start examples.
      operationId: getLanding
      responses:
        '200':
          description: HTML landing page
          content:
            text/html: {}
  /health:
    get:
      tags:
      - Utility
      summary: Health check
      description: Returns service health status with version and instance info.
      operationId: getHealth
      responses:
        '200':
          description: Service is healthy
          content:
            application/json:
              schema:
                type: object
                properties:
                  status:
                    type: string
                    example: healthy
                  service:
                    type: string
                    example: rustybin
                  version:
                    type: string
                    example: 0.6.0
                  instance_id:
                    type: string
                    format: uuid
            application/xml:
              schema:
                type: object
                properties:
                  status:
                    type: string
                    example: healthy
                  service:
                    type: string
                    example: rustybin
                  version:
                    type: string
                    example: 0.6.0
                  instance_id:
                    type: string
                    format: uuid
  /identity:
    get:
      tags:
      - Utility
      summary: Instance identity
      description: Returns hostname, instance ID, uptime, request count, and port information. Useful for load-balancer testing.
      operationId: getIdentity
      responses:
        '200':
          description: Instance identity information
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/IdentityResponse'
            application/xml:
              schema:
                $ref: '#/components/schemas/IdentityResponse'
    post:
      tags:
      - Utility
      summary: Instance identity
      description: Returns hostname, instance ID, uptime, request count, and port information. Useful for load-balancer testing.
      operationId: getIdentity_post
      responses:
        '200':
          description: Instance identity information
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/IdentityResponse'
            application/xml:
              schema:
                $ref: '#/components/schemas/IdentityResponse'
    put:
      tags:
      - Utility
      summary: Instance identity
      description: Returns hostname, instance ID, uptime, request count, and port information. Useful for load-balancer testing.
      operationId: getIdentity_put
      responses:
        '200':
          description: Instance identity information
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/IdentityResponse'
            application/xml:
              schema:
                $ref: '#/components/schemas/IdentityResponse'
    patch:
      tags:
      - Utility
      summary: Instance identity
      description: Returns hostname, instance ID, uptime, request count, and port information. Useful for load-balancer testing.
      operationId: getIdentity_patch
      responses:
        '200':
          description: Instance identity information
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/IdentityResponse'
            application/xml:
              schema:
                $ref: '#/components/schemas/IdentityResponse'
    delete:
      tags:
      - Utility
      summary: Instance identity
      description: Returns hostname, instance ID, uptime, request count, and port information. Useful for load-balancer testing.
      operationId: getIdentity_delete
      responses:
        '200':
          description: Instance identity information
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/IdentityResponse'
            application/xml:
              schema:
                $ref: '#/components/schemas/IdentityResponse'
  /health/healthy:
    post:
      tags:
      - Utility
      summary: Mark instance healthy
      description: Sets the instance health state to healthy. Subsequent GET /health returns 200.
      operationId: markHealthy
      responses:
        '200':
          description: State changed; the body reports the new state (`status`, `healthy`)
          content:
            application/json:
              schema:
                type: object
            application/xml:
              schema:
                type: object
        '401':
          description: Admin token required
        '403':
          description: Disabled in public mode without an admin token
  /health/unhealthy:
    post:
      tags:
      - Utility
      summary: Mark instance unhealthy
      description: Sets the instance health state to unhealthy (200 confirms the change). GET /health then returns 503 and the gRPC grpc.health.v1 service reports NOT_SERVING - useful for gateway upstream active health-check failover demos.
      operationId: markUnhealthy
      responses:
        '200':
          description: State changed; the body reports the new state (`status`, `healthy`)
          content:
            application/json:
              schema:
                type: object
            application/xml:
              schema:
                type: object
        '401':
          description: Admin token required
        '403':
          description: Disabled in public mode without an admin token
  /health/toggle:
    post:
      tags:
      - Utility
      summary: Toggle health state
      description: Flips the current health state between healthy and unhealthy; 200 with the new state.
      operationId: toggleHealth
      responses:
        '200':
          description: State changed; the body reports the new state (`status`, `healthy`)
          content:
            application/json:
              schema:
                type: object
            application/xml:
              schema:
                type: object
        '401':
          description: Admin token required
        '403':
          description: Disabled in public mode without an admin token
  /ws:
    get:
      tags:
      - WebSocket
      summary: WebSocket echo
      description: 'Upgrade to a WebSocket connection that echoes every text and binary frame back to the client. The first subprotocol offered in Sec-WebSocket-Protocol is echoed back. Limits: 1 MiB messages (close 1009), 5 min idle and 1 h lifetime (close 1001); 256 KiB, 1 min and 10 min in public mode. Requires the standard WebSocket upgrade headers; a plain GET returns 426 Upgrade Required.'
      operationId: wsEcho
      responses:
        '101':
          description: Switching Protocols (WebSocket established)
        '426':
          description: Upgrade Required (missing WebSocket headers)
  /ws/time:
    get:
      tags:
      - WebSocket
      summary: WebSocket timestamp ticker
      description: Upgrade to a WebSocket that pushes the current timestamp on a fixed interval, then closes. Exercises server-initiated frames through the gateway.
      operationId: wsTime
      parameters:
      - name: interval_ms
        in: query
        required: false
        schema:
          type: integer
          default: 1000
          minimum: 100
          maximum: 60000
        description: Tick interval in milliseconds
      - name: count
        in: query
        required: false
        schema:
          type: integer
          default: 10
          minimum: 1
          maximum: 1000
        description: Number of ticks before close
      responses:
        '101':
          description: Switching Protocols (WebSocket established)
        '426':
          description: Upgrade Required (missing WebSocket headers)
  /image:
    get:
      tags:
      - Utility
      summary: Get an image in the format chosen by Accept
      description: Picks webp, svg, jpeg, png or gif from the Accept header (q-values honoured); PNG without Accept; 406 when nothing acceptable is offered.
      operationId: getImage
      responses:
        '200':
          description: Image
          content:
            image/webp:
              schema:
                type: string
                format: binary
            image/svg+xml:
              schema:
                type: string
            image/jpeg:
              schema:
                type: string
                format: binary
            image/png:
              schema:
                type: string
                format: binary
            image/gif:
              schema:
                type: string
                format: binary
        '406':
          description: No supported image type acceptable
  /image/png:
    get:
      tags:
      - Utility
      summary: Get a png image
      description: Returns a small, valid png test image.
      operationId: getImagePNG
      responses:
        '200':
          description: png image
          content:
            image/png:
              schema:
                type: string
                format: binary
  /image/jpeg:
    get:
      tags:
      - Utility
      summary: Get a jpeg image
      description: Returns a small, valid jpeg test image.
      operationId: getImageJPEG
      responses:
        '200':
          description: jpeg image
          content:
            image/jpeg:
              schema:
                type: string
                format: binary
  /image/gif:
    get:
      tags:
      - Utility
      summary: Get a gif image
      description: Returns a small, valid gif test image.
      operationId: getImageGIF
      responses:
        '200':
          description: gif image
          content:
            image/gif:
              schema:
                type: string
                format: binary
  /image/webp:
    get:
      tags:
      - Utility
      summary: Get a webp image
      description: Returns a small, valid webp test image.
      operationId: getImageWEBP
      responses:
        '200':
          description: webp image
          content:
            image/webp:
              schema:
                type: string
                format: binary
  /image/svg:
    get:
      tags:
      - Utility
      summary: Get a svg image
      description: Returns a small, valid svg test image.
      operationId: getImageSVG
      responses:
        '200':
          description: svg image
          content:
            image/svg+xml:
              schema:
                type: string
                format: binary
  /openapi.json:
    get:
      tags:
      - Utility
      summary: OpenAPI spec (JSON)
      description: Returns the full OpenAPI 3.0.3 specification as JSON.
      operationId: getOpenApiJson
      responses:
        '200':
          description: OpenAPI JSON spec
  /openapi.yaml:
    get:
      tags:
      - Utility
      summary: OpenAPI spec (YAML)
      description: Returns the full OpenAPI 3.0.3 specification as YAML.
      operationId: getOpenApiYaml
      responses:
        '200':
          description: OpenAPI YAML spec
  /docs:
    get:
      tags:
      - Utility
      summary: API documentation UI
      description: Serves a Scalar-powered interactive API documentation page.
      operationId: getDocs
      responses:
        '200':
          description: HTML documentation page
          content:
            text/html: {}
  /export/postman.json:
    get:
      tags:
      - Utility
      summary: Postman Collection export
      description: Downloads a Postman Collection v2.1 JSON file with pre-configured requests for all Rustybin endpoints.
      operationId: getExportPostman
      responses:
        '200':
          description: Postman Collection v2.1 JSON
          content:
            application/json: {}
          headers:
            Content-Disposition:
              schema:
                type: string
                example: attachment; filename="rustybin-postman.json"
        '400':
          description: Invalid base_url
          content:
            application/json: {}
      parameters:
      - name: base_url
        in: query
        required: false
        description: 'Base URL written into the export (absolute http(s) URL, optional path prefix). Default: the origin the request used (scheme, Host, and Forwarded / X-Forwarded-* with RUSTYBIN_TRUST_FORWARD).'
        schema:
          type: string
          format: uri
          example: https://gateway.example.com/rustybin
  /export/insomnia.json:
    get:
      tags:
      - Utility
      summary: Insomnia Collection export
      description: Downloads an Insomnia Export v4 JSON file with pre-configured requests for all Rustybin endpoints.
      operationId: getExportInsomnia
      responses:
        '200':
          description: Insomnia Export v4 JSON
          content:
            application/json: {}
          headers:
            Content-Disposition:
              schema:
                type: string
                example: attachment; filename="rustybin-insomnia.json"
        '400':
          description: Invalid base_url
          content:
            application/json: {}
      parameters:
      - name: base_url
        in: query
        required: false
        description: 'Base URL written into the export (absolute http(s) URL, optional path prefix). Default: the origin the request used (scheme, Host, and Forwarded / X-Forwarded-* with RUSTYBIN_TRUST_FORWARD).'
        schema:
          type: string
          format: uri
          example: https://gateway.example.com/rustybin
  /export/curl.sh:
    get:
      tags:
      - Utility
      summary: cURL shell script export
      description: Downloads a Bash script with curl commands for all Rustybin endpoints. Set BASE_URL env var to target your instance.
      operationId: getExportCurl
      responses:
        '200':
          description: Bash script with curl commands
          content:
            text/x-shellscript: {}
          headers:
            Content-Disposition:
              schema:
                type: string
                example: attachment; filename="rustybin-curl.sh"
        '400':
          description: Invalid base_url
          content:
            application/json: {}
      parameters:
      - name: base_url
        in: query
        required: false
        description: 'Base URL written into the export (absolute http(s) URL, optional path prefix). Default: the origin the request used (scheme, Host, and Forwarded / X-Forwarded-* with RUSTYBIN_TRUST_FORWARD).'
        schema:
          type: string
          format: uri
          example: https://gateway.example.com/rustybin
  /export/bruno.json:
    get:
      tags:
      - Utility
      summary: Bruno Collection export
      description: Downloads a Bruno collection JSON file with pre-configured requests for all Rustybin endpoints.
      operationId: getExportBruno
      responses:
        '200':
          description: Bruno Collection JSON
          content:
            application/json: {}
          headers:
            Content-Disposition:
              schema:
                type: string
                example: attachment; filename="rustybin-bruno.json"
        '400':
          description: Invalid base_url
          content:
            application/json: {}
      parameters:
      - name: base_url
        in: query
        required: false
        description: 'Base URL written into the export (absolute http(s) URL, optional path prefix). Default: the origin the request used (scheme, Host, and Forwarded / X-Forwarded-* with RUSTYBIN_TRUST_FORWARD).'
        schema:
          type: string
          format: uri
          example: https://gateway.example.com/rustybin
  /export/requests.http:
    get:
      tags:
      - Utility
      summary: .http file export
      description: Downloads an HTTP requests file compatible with JetBrains HTTP Client and VS Code REST Client.
      operationId: getExportHttpFile
      responses:
        '200':
          description: .http file with request definitions
          content:
            text/plain: {}
          headers:
            Content-Disposition:
              schema:
                type: string
                example: attachment; filename="rustybin.http"
        '400':
          description: Invalid base_url
          content:
            application/json: {}
      parameters:
      - name: base_url
        in: query
        required: false
        description: 'Base URL written into the export (absolute http(s) URL, optional path prefix). Default: the origin the request used (scheme, Host, and Forwarded / X-Forwarded-* with RUSTYBIN_TRUST_FORWARD).'
        schema:
          type: string
          format: uri
          example: https://gateway.example.com/rustybin
  /export/requests.hurl:
    get:
      tags:
      - Utility
      summary: Hurl file export
      description: Downloads a Hurl file with HTTP request definitions and response assertions for all Rustybin endpoints.
      operationId: getExportHurl
      responses:
        '200':
          description: Hurl file with request definitions
          content:
            text/plain: {}
          headers:
            Content-Disposition:
              schema:
                type: string
                example: attachment; filename="rustybin.hurl"
        '400':
          description: Invalid base_url
          content:
            application/json: {}
      parameters:
      - name: base_url
        in: query
        required: false
        description: 'Base URL written into the export (absolute http(s) URL, optional path prefix). Default: the origin the request used (scheme, Host, and Forwarded / X-Forwarded-* with RUSTYBIN_TRUST_FORWARD).'
        schema:
          type: string
          format: uri
          example: https://gateway.example.com/rustybin
  /export/k6.js:
    get:
      tags:
      - Utility
      summary: k6 load test script export
      description: Downloads a Grafana k6 JavaScript load test script for all Rustybin endpoints. Set BASE_URL env var to target your instance.
      operationId: getExportK6
      responses:
        '200':
          description: k6 JavaScript load test script
          content:
            application/javascript: {}
          headers:
            Content-Disposition:
              schema:
                type: string
                example: attachment; filename="rustybin-k6.js"
        '400':
          description: Invalid base_url
          content:
            application/json: {}
      parameters:
      - name: base_url
        in: query
        required: false
        description: 'Base URL written into the export (absolute http(s) URL, optional path prefix). Default: the origin the request used (scheme, Host, and Forwarded / X-Forwarded-* with RUSTYBIN_TRUST_FORWARD).'
        schema:
          type: string
          format: uri
          example: https://gateway.example.com/rustybin
  /export/har.json:
    get:
      tags:
      - Utility
      summary: HAR 1.2 archive export
      description: Downloads an HTTP Archive (HAR) 1.2 JSON file with request entries for all Rustybin endpoints.
      operationId: getExportHar
      responses:
        '200':
          description: HAR 1.2 JSON archive
          content:
            application/json: {}
          headers:
            Content-Disposition:
              schema:
                type: string
                example: attachment; filename="rustybin.har.json"
        '400':
          description: Invalid base_url
          content:
            application/json: {}
      parameters:
      - name: base_url
        in: query
        required: false
        description: 'Base URL written into the export (absolute http(s) URL, optional path prefix). Default: the origin the request used (scheme, Host, and Forwarded / X-Forwarded-* with RUSTYBIN_TRUST_FORWARD).'
        schema:
          type: string
          format: uri
          example: https://gateway.example.com/rustybin
  /flaky/{fail_rate}:
    get:
      tags:
      - Utility
      summary: Random failure by rate
      description: Fails fail_rate% of the time with 503. Useful for circuit-breaker and health-check testing.
      operationId: getFlakyRate
      parameters:
      - name: fail_rate
        in: path
        required: true
        schema:
          type: integer
          minimum: 0
          maximum: 100
        description: Failure percentage (0=never fail, 100=always fail)
      responses:
        '200':
          description: Request succeeded
          headers:
            X-Rustybin-Flaky:
              schema:
                type: string
                example: 'true'
            X-Rustybin-Fail-Rate:
              schema:
                type: integer
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/FlakySuccess'
            application/xml:
              schema:
                $ref: '#/components/schemas/FlakySuccess'
        '400':
          description: Invalid fail_rate
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
            application/xml:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '503':
          description: Simulated failure
          headers:
            X-Rustybin-Flaky:
              schema:
                type: string
                example: 'true'
            X-Rustybin-Fail-Rate:
              schema:
                type: integer
            Retry-After:
              schema:
                type: integer
                example: 1
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/FlakyFailure'
            application/xml:
              schema:
                $ref: '#/components/schemas/FlakyFailure'
    post:
      tags:
      - Utility
      summary: Random failure by rate
      description: Fails fail_rate% of the time with 503. Useful for circuit-breaker and health-check testing.
      operationId: getFlakyRate_post
      parameters:
      - name: fail_rate
        in: path
        required: true
        schema:
          type: integer
          minimum: 0
          maximum: 100
        description: Failure percentage (0=never fail, 100=always fail)
      responses:
        '200':
          description: Request succeeded
          headers:
            X-Rustybin-Flaky:
              schema:
                type: string
                example: 'true'
            X-Rustybin-Fail-Rate:
              schema:
                type: integer
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/FlakySuccess'
            application/xml:
              schema:
                $ref: '#/components/schemas/FlakySuccess'
        '400':
          description: Invalid fail_rate
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
            application/xml:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '503':
          description: Simulated failure
          headers:
            X-Rustybin-Flaky:
              schema:
                type: string
                example: 'true'
            X-Rustybin-Fail-Rate:
              schema:
                type: integer
            Retry-After:
              schema:
                type: integer
                example: 1
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/FlakyFailure'
            application/xml:
              schema:
                $ref: '#/components/schemas/FlakyFailure'
    put:
      tags:
      - Utility
      summary: Random failure by rate
      description: Fails fail_rate% of the time with 503. Useful for circuit-breaker and health-check testing.
      operationId: getFlakyRate_put
      parameters:
      - name: fail_rate
        in: path
        required: true
        schema:
          type: integer
          minimum: 0
          maximum: 100
        description: Failure percentage (0=never fail, 100=always fail)
      responses:
        '200':
          description: Request succeeded
          headers:
            X-Rustybin-Flaky:
              schema:
                type: string
                example: 'true'
            X-Rustybin-Fail-Rate:
              schema:
                type: integer
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/FlakySuccess'
            application/xml:
              schema:
                $ref: '#/components/schemas/FlakySuccess'
        '400':
          description: Invalid fail_rate
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
            application/xml:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '503':
          description: Simulated failure
          headers:
            X-Rustybin-Flaky:
              schema:
                type: string
                example: 'true'
            X-Rustybin-Fail-Rate:
              schema:
                type: integer
            Retry-After:
              schema:
                type: integer
                example: 1
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/FlakyFailure'
            application/xml:
              schema:
                $ref: '#/components/schemas/FlakyFailure'
    patch:
      tags:
      - Utility
      summary: Random failure by rate
      description: Fails fail_rate% of the time with 503. Useful for circuit-breaker and health-check testing.
      operationId: getFlakyRate_patch
      parameters:
      - name: fail_rate
        in: path
        required: true
        schema:
          type: integer
          minimum: 0
          maximum: 100
        description: Failure percentage (0=never fail, 100=always fail)
      responses:
        '200':
          description: Request succeeded
          headers:
            X-Rustybin-Flaky:
              schema:
                type: string
                example: 'true'
            X-Rustybin-Fail-Rate:
              schema:
                type: integer
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/FlakySuccess'
            application/xml:
              schema:
                $ref: '#/components/schemas/FlakySuccess'
        '400':
          description: Invalid fail_rate
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
            application/xml:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '503':
          description: Simulated failure
          headers:
            X-Rustybin-Flaky:
              schema:
                type: string
                example: 'true'
            X-Rustybin-Fail-Rate:
              schema:
                type: integer
            Retry-After:
              schema:
                type: integer
                example: 1
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/FlakyFailure'
            application/xml:
              schema:
                $ref: '#/components/schemas/FlakyFailure'
    delete:
      tags:
      - Utility
      summary: Random failure by rate
      description: Fails fail_rate% of the time with 503. Useful for circuit-breaker and health-check testing.
      operationId: getFlakyRate_delete
      parameters:
      - name: fail_rate
        in: path
        required: true
        schema:
          type: integer
          minimum: 0
          maximum: 100
        description: Failure percentage (0=never fail, 100=always fail)
      responses:
        '200':
          description: Request succeeded
          headers:
            X-Rustybin-Flaky:
              schema:
                type: string
                example: 'true'
            X-Rustybin-Fail-Rate:
              schema:
                type: integer
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/FlakySuccess'
            application/xml:
              schema:
                $ref: '#/components/schemas/FlakySuccess'
        '400':
          description: Invalid fail_rate
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
            application/xml:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '503':
          description: Simulated failure
          headers:
            X-Rustybin-Flaky:
              schema:
                type: string
                example: 'true'
            X-Rustybin-Fail-Rate:
              schema:
                type: integer
            Retry-After:
              schema:
                type: integer
                example: 1
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/FlakyFailure'
            application/xml:
              schema:
                $ref: '#/components/schemas/FlakyFailure'
  /flaky/pattern/{pattern}:
    get:
      tags:
      - Utility
      summary: Deterministic success/fail pattern
      description: 'Follows a repeating pattern of S (success/200) and F (fail/503). E.g. SSFSF cycles through: success, success, fail, success, fail.'
      operationId: getFlakyPattern
      parameters:
      - name: pattern
        in: path
        required: true
        schema:
          type: string
          pattern: ^[SsFf]+$
        example: SSFSF
        description: Pattern of S (success) and F (fail) characters
      responses:
        '200':
          description: Pattern position is S (success)
        '400':
          description: Invalid pattern (must contain only S and F)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
            application/xml:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '503':
          description: Pattern position is F (fail)
    post:
      tags:
      - Utility
      summary: Deterministic success/fail pattern
      description: 'Follows a repeating pattern of S (success/200) and F (fail/503). E.g. SSFSF cycles through: success, success, fail, success, fail.'
      operationId: getFlakyPattern_post
      parameters:
      - name: pattern
        in: path
        required: true
        schema:
          type: string
          pattern: ^[SsFf]+$
        example: SSFSF
        description: Pattern of S (success) and F (fail) characters
      responses:
        '200':
          description: Pattern position is S (success)
        '400':
          description: Invalid pattern (must contain only S and F)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
            application/xml:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '503':
          description: Pattern position is F (fail)
    put:
      tags:
      - Utility
      summary: Deterministic success/fail pattern
      description: 'Follows a repeating pattern of S (success/200) and F (fail/503). E.g. SSFSF cycles through: success, success, fail, success, fail.'
      operationId: getFlakyPattern_put
      parameters:
      - name: pattern
        in: path
        required: true
        schema:
          type: string
          pattern: ^[SsFf]+$
        example: SSFSF
        description: Pattern of S (success) and F (fail) characters
      responses:
        '200':
          description: Pattern position is S (success)
        '400':
          description: Invalid pattern (must contain only S and F)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
            application/xml:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '503':
          description: Pattern position is F (fail)
    patch:
      tags:
      - Utility
      summary: Deterministic success/fail pattern
      description: 'Follows a repeating pattern of S (success/200) and F (fail/503). E.g. SSFSF cycles through: success, success, fail, success, fail.'
      operationId: getFlakyPattern_patch
      parameters:
      - name: pattern
        in: path
        required: true
        schema:
          type: string
          pattern: ^[SsFf]+$
        example: SSFSF
        description: Pattern of S (success) and F (fail) characters
      responses:
        '200':
          description: Pattern position is S (success)
        '400':
          description: Invalid pattern (must contain only S and F)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
            application/xml:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '503':
          description: Pattern position is F (fail)
    delete:
      tags:
      - Utility
      summary: Deterministic success/fail pattern
      description: 'Follows a repeating pattern of S (success/200) and F (fail/503). E.g. SSFSF cycles through: success, success, fail, success, fail.'
      operationId: getFlakyPattern_delete
      parameters:
      - name: pattern
        in: path
        required: true
        schema:
          type: string
          pattern: ^[SsFf]+$
        example: SSFSF
        description: Pattern of S (success) and F (fail) characters
      responses:
        '200':
          description: Pattern position is S (success)
        '400':
          description: Invalid pattern (must contain only S and F)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
            application/xml:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '503':
          description: Pattern position is F (fail)
  /flaky/after/{n}:
    get:
      tags:
      - Utility
      summary: Succeed then fail after N requests
      description: Succeeds for the first N requests, then fails forever (until reset). Simulates an upstream that goes down.
      operationId: getFlakyAfter
      parameters:
      - name: n
        in: path
        required: true
        schema:
          type: integer
          minimum: 0
        description: Number of successful requests before failing
      responses:
        '200':
          description: Request succeeded (within first N requests)
        '503':
          description: Service unavailable (after N requests)
    post:
      tags:
      - Utility
      summary: Succeed then fail after N requests
      description: Succeeds for the first N requests, then fails forever (until reset). Simulates an upstream that goes down.
      operationId: getFlakyAfter_post
      parameters:
      - name: n
        in: path
        required: true
        schema:
          type: integer
          minimum: 0
        description: Number of successful requests before failing
      responses:
        '200':
          description: Request succeeded (within first N requests)
        '503':
          description: Service unavailable (after N requests)
    put:
      tags:
      - Utility
      summary: Succeed then fail after N requests
      description: Succeeds for the first N requests, then fails forever (until reset). Simulates an upstream that goes down.
      operationId: getFlakyAfter_put
      parameters:
      - name: n
        in: path
        required: true
        schema:
          type: integer
          minimum: 0
        description: Number of successful requests before failing
      responses:
        '200':
          description: Request succeeded (within first N requests)
        '503':
          description: Service unavailable (after N requests)
    patch:
      tags:
      - Utility
      summary: Succeed then fail after N requests
      description: Succeeds for the first N requests, then fails forever (until reset). Simulates an upstream that goes down.
      operationId: getFlakyAfter_patch
      parameters:
      - name: n
        in: path
        required: true
        schema:
          type: integer
          minimum: 0
        description: Number of successful requests before failing
      responses:
        '200':
          description: Request succeeded (within first N requests)
        '503':
          description: Service unavailable (after N requests)
    delete:
      tags:
      - Utility
      summary: Succeed then fail after N requests
      description: Succeeds for the first N requests, then fails forever (until reset). Simulates an upstream that goes down.
      operationId: getFlakyAfter_delete
      parameters:
      - name: n
        in: path
        required: true
        schema:
          type: integer
          minimum: 0
        description: Number of successful requests before failing
      responses:
        '200':
          description: Request succeeded (within first N requests)
        '503':
          description: Service unavailable (after N requests)
  /flaky/recover/{n}:
    get:
      tags:
      - Utility
      summary: Fail then recover after N requests
      description: Fails for the first N requests, then succeeds forever. Simulates an upstream recovering.
      operationId: getFlakyRecover
      parameters:
      - name: n
        in: path
        required: true
        schema:
          type: integer
          minimum: 0
        description: Number of failing requests before recovering
      responses:
        '200':
          description: Request succeeded (after N requests)
        '503':
          description: Service unavailable (within first N requests)
    post:
      tags:
      - Utility
      summary: Fail then recover after N requests
      description: Fails for the first N requests, then succeeds forever. Simulates an upstream recovering.
      operationId: getFlakyRecover_post
      parameters:
      - name: n
        in: path
        required: true
        schema:
          type: integer
          minimum: 0
        description: Number of failing requests before recovering
      responses:
        '200':
          description: Request succeeded (after N requests)
        '503':
          description: Service unavailable (within first N requests)
    put:
      tags:
      - Utility
      summary: Fail then recover after N requests
      description: Fails for the first N requests, then succeeds forever. Simulates an upstream recovering.
      operationId: getFlakyRecover_put
      parameters:
      - name: n
        in: path
        required: true
        schema:
          type: integer
          minimum: 0
        description: Number of failing requests before recovering
      responses:
        '200':
          description: Request succeeded (after N requests)
        '503':
          description: Service unavailable (within first N requests)
    patch:
      tags:
      - Utility
      summary: Fail then recover after N requests
      description: Fails for the first N requests, then succeeds forever. Simulates an upstream recovering.
      operationId: getFlakyRecover_patch
      parameters:
      - name: n
        in: path
        required: true
        schema:
          type: integer
          minimum: 0
        description: Number of failing requests before recovering
      responses:
        '200':
          description: Request succeeded (after N requests)
        '503':
          description: Service unavailable (within first N requests)
    delete:
      tags:
      - Utility
      summary: Fail then recover after N requests
      description: Fails for the first N requests, then succeeds forever. Simulates an upstream recovering.
      operationId: getFlakyRecover_delete
      parameters:
      - name: n
        in: path
        required: true
        schema:
          type: integer
          minimum: 0
        description: Number of failing requests before recovering
      responses:
        '200':
          description: Request succeeded (after N requests)
        '503':
          description: Service unavailable (within first N requests)
  /flaky/reset:
    post:
      tags:
      - Utility
      summary: Reset flaky counters
      description: 'Counters are kept per (session, route): the session is X-Rustybin-Session (else the client IP), the route the endpoint plus its parameter. Without `scope` the caller''s own counters are reset (open). `scope=all` resets every client''s counters and is admin-guarded.'
      operationId: resetFlaky
      parameters:
      - name: scope
        in: query
        required: false
        schema:
          type: string
          enum:
          - session
          - all
          default: session
      responses:
        '200':
          description: Counters reset
          content:
            application/json:
              schema:
                type: object
                properties:
                  status:
                    type: string
                    example: reset
                  scope:
                    type: string
                    example: session
                  session:
                    type: string
                    example: ip:203.0.113.9
                  counters_removed:
                    type: integer
                    example: 2
            application/xml:
              schema:
                type: object
                properties:
                  status:
                    type: string
                    example: reset
                  scope:
                    type: string
                    example: session
                  session:
                    type: string
                    example: ip:203.0.113.9
                  counters_removed:
                    type: integer
                    example: 2
        '401':
          description: scope=all without the admin token
  /flaky/status:
    get:
      tags:
      - Utility
      summary: Flaky endpoint counter status
      description: Returns the caller's flaky counters (per session and route).
      operationId: getFlakyStatus
      responses:
        '200':
          description: Current counter values
          content:
            application/json:
              schema:
                type: object
                properties:
                  session:
                    type: string
                  counters:
                    type: array
                    items:
                      type: object
                      properties:
                        route:
                          type: string
                          example: after:3
                        count:
                          type: integer
            application/xml:
              schema:
                type: object
                properties:
                  session:
                    type: string
                  counters:
                    type: array
                    items:
                      type: object
                      properties:
                        route:
                          type: string
                          example: after:3
                        count:
                          type: integer
  /echo:
    get:
      tags:
      - Echo
      summary: echo - echo request details
      description: 'Returns all details of the incoming request: method, headers, query params, body. Supports all HTTP methods.'
      operationId: getEcho
      responses:
        '200':
          description: Request details
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EchoResponse'
            application/xml:
              schema:
                $ref: '#/components/schemas/EchoResponse'
    post:
      tags:
      - Echo
      summary: echo - echo request with body
      operationId: postEcho
      requestBody:
        content:
          application/json:
            schema: {}
          text/plain:
            schema:
              type: string
      responses:
        '200':
          description: Request details with body
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EchoResponse'
            application/xml:
              schema:
                $ref: '#/components/schemas/EchoResponse'
    put:
      tags:
      - Echo
      summary: echo - PUT
      operationId: putEcho
      responses:
        '200':
          description: Request details
    delete:
      tags:
      - Echo
      summary: echo - DELETE
      operationId: deleteEcho
      responses:
        '200':
          description: Request details
    patch:
      tags:
      - Echo
      summary: echo - PATCH
      operationId: patchEcho
      responses:
        '200':
          description: Request details
  /echo/{subpath}:
    get:
      tags:
      - Echo
      summary: echo with sub-path
      operationId: getEchoSubPath
      parameters:
      - name: subpath
        in: path
        required: true
        schema:
          type: string
        description: Arbitrary sub-path (captured in response)
      responses:
        '200':
          description: Request details
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EchoResponse'
            application/xml:
              schema:
                $ref: '#/components/schemas/EchoResponse'
    post:
      tags:
      - Echo
      summary: echo with sub-path
      operationId: getEchoSubPath_post
      parameters:
      - name: subpath
        in: path
        required: true
        schema:
          type: string
        description: Arbitrary sub-path (captured in response)
      responses:
        '200':
          description: Request details
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EchoResponse'
            application/xml:
              schema:
                $ref: '#/components/schemas/EchoResponse'
    put:
      tags:
      - Echo
      summary: echo with sub-path
      operationId: getEchoSubPath_put
      parameters:
      - name: subpath
        in: path
        required: true
        schema:
          type: string
        description: Arbitrary sub-path (captured in response)
      responses:
        '200':
          description: Request details
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EchoResponse'
            application/xml:
              schema:
                $ref: '#/components/schemas/EchoResponse'
    patch:
      tags:
      - Echo
      summary: echo with sub-path
      operationId: getEchoSubPath_patch
      parameters:
      - name: subpath
        in: path
        required: true
        schema:
          type: string
        description: Arbitrary sub-path (captured in response)
      responses:
        '200':
          description: Request details
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EchoResponse'
            application/xml:
              schema:
                $ref: '#/components/schemas/EchoResponse'
    delete:
      tags:
      - Echo
      summary: echo with sub-path
      operationId: getEchoSubPath_delete
      parameters:
      - name: subpath
        in: path
        required: true
        schema:
          type: string
        description: Arbitrary sub-path (captured in response)
      responses:
        '200':
          description: Request details
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EchoResponse'
            application/xml:
              schema:
                $ref: '#/components/schemas/EchoResponse'
  /anything:
    get:
      tags:
      - Echo
      summary: anything - echo request details
      description: 'Returns all details of the incoming request: method, headers, query params, body. Supports all HTTP methods.'
      operationId: getAnything
      responses:
        '200':
          description: Request details
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EchoResponse'
            application/xml:
              schema:
                $ref: '#/components/schemas/EchoResponse'
    post:
      tags:
      - Echo
      summary: anything - echo request with body
      operationId: postAnything
      requestBody:
        content:
          application/json:
            schema: {}
          text/plain:
            schema:
              type: string
      responses:
        '200':
          description: Request details with body
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EchoResponse'
            application/xml:
              schema:
                $ref: '#/components/schemas/EchoResponse'
    put:
      tags:
      - Echo
      summary: anything - PUT
      operationId: putAnything
      responses:
        '200':
          description: Request details
    delete:
      tags:
      - Echo
      summary: anything - DELETE
      operationId: deleteAnything
      responses:
        '200':
          description: Request details
    patch:
      tags:
      - Echo
      summary: anything - PATCH
      operationId: patchAnything
      responses:
        '200':
          description: Request details
  /anything/{subpath}:
    get:
      tags:
      - Echo
      summary: anything with sub-path
      operationId: getAnythingSubPath
      parameters:
      - name: subpath
        in: path
        required: true
        schema:
          type: string
        description: Arbitrary sub-path (captured in response)
      responses:
        '200':
          description: Request details
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EchoResponse'
            application/xml:
              schema:
                $ref: '#/components/schemas/EchoResponse'
    post:
      tags:
      - Echo
      summary: anything with sub-path
      operationId: getAnythingSubPath_post
      parameters:
      - name: subpath
        in: path
        required: true
        schema:
          type: string
        description: Arbitrary sub-path (captured in response)
      responses:
        '200':
          description: Request details
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EchoResponse'
            application/xml:
              schema:
                $ref: '#/components/schemas/EchoResponse'
    put:
      tags:
      - Echo
      summary: anything with sub-path
      operationId: getAnythingSubPath_put
      parameters:
      - name: subpath
        in: path
        required: true
        schema:
          type: string
        description: Arbitrary sub-path (captured in response)
      responses:
        '200':
          description: Request details
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EchoResponse'
            application/xml:
              schema:
                $ref: '#/components/schemas/EchoResponse'
    patch:
      tags:
      - Echo
      summary: anything with sub-path
      operationId: getAnythingSubPath_patch
      parameters:
      - name: subpath
        in: path
        required: true
        schema:
          type: string
        description: Arbitrary sub-path (captured in response)
      responses:
        '200':
          description: Request details
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EchoResponse'
            application/xml:
              schema:
                $ref: '#/components/schemas/EchoResponse'
    delete:
      tags:
      - Echo
      summary: anything with sub-path
      operationId: getAnythingSubPath_delete
      parameters:
      - name: subpath
        in: path
        required: true
        schema:
          type: string
        description: Arbitrary sub-path (captured in response)
      responses:
        '200':
          description: Request details
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EchoResponse'
            application/xml:
              schema:
                $ref: '#/components/schemas/EchoResponse'
  /status/{code}:
    get:
      tags:
      - Status
      summary: Return a specific HTTP status code (or a weighted random choice)
      description: 'Returns the specified HTTP status code (200-599). 1xx codes return 400 with a JSON explanation: they are interim responses and cannot be sent as a final response. `200,500` picks uniformly, `200:0.9,500:0.1` by relative weight (up to 20 codes). Redirect codes (301, 302, 303, 307, 308) include `Location: /echo`; 401 adds WWW-Authenticate, 407 Proxy-Authenticate, 429 and 503 Retry-After. 204, 205 and 304 return empty bodies.'
      operationId: getStatus
      parameters:
      - name: code
        in: path
        required: true
        schema:
          type: string
          pattern: ^[0-9]{3}(:[0-9.]+)?(,[0-9]{3}(:[0-9.]+)?)*$
        example: 200:0.9,500:0.1
        description: Status code, or comma-separated codes with optional :weight
      responses:
        '200':
          description: Success response
          content:
            application/json:
              schema:
                type: object
                properties:
                  status:
                    type: integer
                  chosen_from:
                    type: string
            application/xml:
              schema:
                type: object
                properties:
                  status:
                    type: integer
                  chosen_from:
                    type: string
        '400':
          description: Invalid or informational (1xx) status code
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
            application/xml:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
    post:
      tags:
      - Status
      summary: Return a specific HTTP status code (or a weighted random choice)
      description: 'Returns the specified HTTP status code (200-599). 1xx codes return 400 with a JSON explanation: they are interim responses and cannot be sent as a final response. `200,500` picks uniformly, `200:0.9,500:0.1` by relative weight (up to 20 codes). Redirect codes (301, 302, 303, 307, 308) include `Location: /echo`; 401 adds WWW-Authenticate, 407 Proxy-Authenticate, 429 and 503 Retry-After. 204, 205 and 304 return empty bodies.'
      operationId: getStatus_post
      parameters:
      - name: code
        in: path
        required: true
        schema:
          type: string
          pattern: ^[0-9]{3}(:[0-9.]+)?(,[0-9]{3}(:[0-9.]+)?)*$
        example: 200:0.9,500:0.1
        description: Status code, or comma-separated codes with optional :weight
      responses:
        '200':
          description: Success response
          content:
            application/json:
              schema:
                type: object
                properties:
                  status:
                    type: integer
                  chosen_from:
                    type: string
            application/xml:
              schema:
                type: object
                properties:
                  status:
                    type: integer
                  chosen_from:
                    type: string
        '400':
          description: Invalid or informational (1xx) status code
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
            application/xml:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
    put:
      tags:
      - Status
      summary: Return a specific HTTP status code (or a weighted random choice)
      description: 'Returns the specified HTTP status code (200-599). 1xx codes return 400 with a JSON explanation: they are interim responses and cannot be sent as a final response. `200,500` picks uniformly, `200:0.9,500:0.1` by relative weight (up to 20 codes). Redirect codes (301, 302, 303, 307, 308) include `Location: /echo`; 401 adds WWW-Authenticate, 407 Proxy-Authenticate, 429 and 503 Retry-After. 204, 205 and 304 return empty bodies.'
      operationId: getStatus_put
      parameters:
      - name: code
        in: path
        required: true
        schema:
          type: string
          pattern: ^[0-9]{3}(:[0-9.]+)?(,[0-9]{3}(:[0-9.]+)?)*$
        example: 200:0.9,500:0.1
        description: Status code, or comma-separated codes with optional :weight
      responses:
        '200':
          description: Success response
          content:
            application/json:
              schema:
                type: object
                properties:
                  status:
                    type: integer
                  chosen_from:
                    type: string
            application/xml:
              schema:
                type: object
                properties:
                  status:
                    type: integer
                  chosen_from:
                    type: string
        '400':
          description: Invalid or informational (1xx) status code
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
            application/xml:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
    patch:
      tags:
      - Status
      summary: Return a specific HTTP status code (or a weighted random choice)
      description: 'Returns the specified HTTP status code (200-599). 1xx codes return 400 with a JSON explanation: they are interim responses and cannot be sent as a final response. `200,500` picks uniformly, `200:0.9,500:0.1` by relative weight (up to 20 codes). Redirect codes (301, 302, 303, 307, 308) include `Location: /echo`; 401 adds WWW-Authenticate, 407 Proxy-Authenticate, 429 and 503 Retry-After. 204, 205 and 304 return empty bodies.'
      operationId: getStatus_patch
      parameters:
      - name: code
        in: path
        required: true
        schema:
          type: string
          pattern: ^[0-9]{3}(:[0-9.]+)?(,[0-9]{3}(:[0-9.]+)?)*$
        example: 200:0.9,500:0.1
        description: Status code, or comma-separated codes with optional :weight
      responses:
        '200':
          description: Success response
          content:
            application/json:
              schema:
                type: object
                properties:
                  status:
                    type: integer
                  chosen_from:
                    type: string
            application/xml:
              schema:
                type: object
                properties:
                  status:
                    type: integer
                  chosen_from:
                    type: string
        '400':
          description: Invalid or informational (1xx) status code
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
            application/xml:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
    delete:
      tags:
      - Status
      summary: Return a specific HTTP status code (or a weighted random choice)
      description: 'Returns the specified HTTP status code (200-599). 1xx codes return 400 with a JSON explanation: they are interim responses and cannot be sent as a final response. `200,500` picks uniformly, `200:0.9,500:0.1` by relative weight (up to 20 codes). Redirect codes (301, 302, 303, 307, 308) include `Location: /echo`; 401 adds WWW-Authenticate, 407 Proxy-Authenticate, 429 and 503 Retry-After. 204, 205 and 304 return empty bodies.'
      operationId: getStatus_delete
      parameters:
      - name: code
        in: path
        required: true
        schema:
          type: string
          pattern: ^[0-9]{3}(:[0-9.]+)?(,[0-9]{3}(:[0-9.]+)?)*$
        example: 200:0.9,500:0.1
        description: Status code, or comma-separated codes with optional :weight
      responses:
        '200':
          description: Success response
          content:
            application/json:
              schema:
                type: object
                properties:
                  status:
                    type: integer
                  chosen_from:
                    type: string
            application/xml:
              schema:
                type: object
                properties:
                  status:
                    type: integer
                  chosen_from:
                    type: string
        '400':
          description: Invalid or informational (1xx) status code
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
            application/xml:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  /delay/{ms}:
    get:
      tags:
      - Response Shaping
      summary: Delay response by N milliseconds
      description: 'Waits before responding. The value is milliseconds (`1500`), or carries a unit: `250ms`, `1.5s`. Maximum 60 s (10 s in public mode) including jitter; larger values return 400.'
      operationId: getDelay
      parameters:
      - name: ms
        in: path
        required: true
        schema:
          type: string
          pattern: ^[0-9.]+(ms|s)?$
        example: '1500'
        description: 'Delay: milliseconds, or with an ms / s suffix'
      - name: jitter
        in: query
        required: false
        schema:
          type: boolean
        description: Apply +-20% random jitter to the delay (capped at the maximum)
      responses:
        '200':
          description: Delayed response with request details
        '400':
          description: Invalid delay value
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
            application/xml:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
    post:
      tags:
      - Response Shaping
      summary: Delay response by N milliseconds
      description: 'Waits before responding. The value is milliseconds (`1500`), or carries a unit: `250ms`, `1.5s`. Maximum 60 s (10 s in public mode) including jitter; larger values return 400.'
      operationId: getDelay_post
      parameters:
      - name: ms
        in: path
        required: true
        schema:
          type: string
          pattern: ^[0-9.]+(ms|s)?$
        example: '1500'
        description: 'Delay: milliseconds, or with an ms / s suffix'
      - name: jitter
        in: query
        required: false
        schema:
          type: boolean
        description: Apply +-20% random jitter to the delay (capped at the maximum)
      responses:
        '200':
          description: Delayed response with request details
        '400':
          description: Invalid delay value
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
            application/xml:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
    put:
      tags:
      - Response Shaping
      summary: Delay response by N milliseconds
      description: 'Waits before responding. The value is milliseconds (`1500`), or carries a unit: `250ms`, `1.5s`. Maximum 60 s (10 s in public mode) including jitter; larger values return 400.'
      operationId: getDelay_put
      parameters:
      - name: ms
        in: path
        required: true
        schema:
          type: string
          pattern: ^[0-9.]+(ms|s)?$
        example: '1500'
        description: 'Delay: milliseconds, or with an ms / s suffix'
      - name: jitter
        in: query
        required: false
        schema:
          type: boolean
        description: Apply +-20% random jitter to the delay (capped at the maximum)
      responses:
        '200':
          description: Delayed response with request details
        '400':
          description: Invalid delay value
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
            application/xml:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
    patch:
      tags:
      - Response Shaping
      summary: Delay response by N milliseconds
      description: 'Waits before responding. The value is milliseconds (`1500`), or carries a unit: `250ms`, `1.5s`. Maximum 60 s (10 s in public mode) including jitter; larger values return 400.'
      operationId: getDelay_patch
      parameters:
      - name: ms
        in: path
        required: true
        schema:
          type: string
          pattern: ^[0-9.]+(ms|s)?$
        example: '1500'
        description: 'Delay: milliseconds, or with an ms / s suffix'
      - name: jitter
        in: query
        required: false
        schema:
          type: boolean
        description: Apply +-20% random jitter to the delay (capped at the maximum)
      responses:
        '200':
          description: Delayed response with request details
        '400':
          description: Invalid delay value
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
            application/xml:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
    delete:
      tags:
      - Response Shaping
      summary: Delay response by N milliseconds
      description: 'Waits before responding. The value is milliseconds (`1500`), or carries a unit: `250ms`, `1.5s`. Maximum 60 s (10 s in public mode) including jitter; larger values return 400.'
      operationId: getDelay_delete
      parameters:
      - name: ms
        in: path
        required: true
        schema:
          type: string
          pattern: ^[0-9.]+(ms|s)?$
        example: '1500'
        description: 'Delay: milliseconds, or with an ms / s suffix'
      - name: jitter
        in: query
        required: false
        schema:
          type: boolean
        description: Apply +-20% random jitter to the delay (capped at the maximum)
      responses:
        '200':
          description: Delayed response with request details
        '400':
          description: Invalid delay value
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
            application/xml:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  /response-headers:
    get:
      tags:
      - Response Shaping
      summary: Set arbitrary response headers
      description: Any query parameters are set as response headers and returned in the JSON body (repeated names become several headers). Hop-by-hop and framing headers (Connection, Content-Length, Transfer-Encoding, Keep-Alive, TE, Trailer, Upgrade, ...) are refused with 400; Content-Type may be overridden.
      operationId: getResponseHeaders
      parameters:
      - name: X-Custom-Header
        in: query
        required: false
        schema:
          type: string
        description: 'Example: any query parameter becomes a response header'
      responses:
        '200':
          description: Response with custom headers
        '400':
          description: Forbidden or invalid header
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
            application/xml:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  /cache/{ttl}:
    get:
      tags:
      - Response Shaping
      summary: Cacheable response with TTL
      description: 'Returns a response with Cache-Control, ETag, Last-Modified and Vary: Accept. Conditional requests: If-None-Match (lists, weak tags, `*`; weak comparison) takes precedence over If-Modified-Since (second precision). JSON and XML representations have different ETags. 304 responses carry ETag, Cache-Control, Last-Modified and Vary.'
      operationId: getCache
      parameters:
      - name: ttl
        in: path
        required: true
        schema:
          type: integer
        description: Cache TTL in seconds
      - name: If-None-Match
        in: header
        required: false
        schema:
          type: string
        description: ETag for conditional request
      - name: If-Modified-Since
        in: header
        required: false
        schema:
          type: string
        description: Date for conditional request
      responses:
        '200':
          description: Cacheable response with ETag and Cache-Control headers
        '304':
          description: Not Modified - conditional request matched
  /redirect/{n}:
    get:
      tags:
      - Redirects & Cookies
      summary: Relative redirect chain
      description: Performs N relative 302 redirects, ending at /echo. Max 20 hops.
      operationId: getRedirect
      parameters:
      - name: n
        in: path
        required: true
        schema:
          type: integer
          minimum: 1
          maximum: 20
        description: Number of redirects
      responses:
        '302':
          description: Redirect
          headers:
            Location:
              schema:
                type: string
        '400':
          description: Invalid redirect count
  /cookies:
    get:
      tags:
      - Redirects & Cookies
      summary: Get cookies
      description: Returns all cookies sent with the request.
      operationId: getCookies
      responses:
        '200':
          description: Cookies from the request
          content:
            application/json:
              schema:
                type: object
                properties:
                  cookies:
                    type: object
                    additionalProperties:
                      type: string
            application/xml:
              schema:
                type: object
                properties:
                  cookies:
                    type: object
                    additionalProperties:
                      type: string
  /cookies/set:
    get:
      tags:
      - Redirects & Cookies
      summary: Set cookies via query parameters
      description: Sets cookies from query parameters and redirects to /cookies. Use _path (default /), _domain, _secure, _httponly, _samesite (Strict, Lax, None; None forces Secure), _maxage for cookie options. Names must be RFC 6265 tokens; values are percent-encoded where they contain characters outside cookie-octet (space, ;, comma, quotes, CR/LF, non-ASCII).
      operationId: setCookies
      parameters:
      - name: name
        in: query
        required: false
        schema:
          type: string
        description: Cookie name=value (any query param becomes a cookie)
      - name: _path
        in: query
        required: false
        schema:
          type: string
          default: /
        description: Cookie Path attribute
      - name: _domain
        in: query
        required: false
        schema:
          type: string
        description: Cookie Domain attribute
      - name: _secure
        in: query
        required: false
        schema:
          type: boolean
        description: Cookie Secure flag
      - name: _httponly
        in: query
        required: false
        schema:
          type: boolean
        description: Cookie HttpOnly flag
      - name: _samesite
        in: query
        required: false
        schema:
          type: string
          enum:
          - Strict
          - Lax
          - None
        description: Cookie SameSite attribute
      - name: _maxage
        in: query
        required: false
        schema:
          type: integer
        description: Cookie Max-Age in seconds
      responses:
        '302':
          description: Redirect to /cookies with Set-Cookie headers
        '400':
          description: Invalid cookie name or attribute
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
            application/xml:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  /cookies/set/{name}/{value}:
    get:
      tags:
      - Redirects & Cookies
      summary: Set a single cookie
      description: Sets a single cookie with the given name and value, then redirects to /cookies.
      operationId: setCookie
      parameters:
      - name: name
        in: path
        required: true
        schema:
          type: string
        description: Cookie name
      - name: value
        in: path
        required: true
        schema:
          type: string
        description: Cookie value
      responses:
        '302':
          description: Redirect to /cookies with Set-Cookie header
  /cookies/delete:
    get:
      tags:
      - Redirects & Cookies
      summary: Delete cookies
      description: Deletes cookies named in the query parameters by setting Max-Age=0 and an expired Expires, with Path (default /) and Domain matching how they were set (_path, _domain).
      operationId: deleteCookies
      parameters:
      - name: name
        in: query
        required: false
        schema:
          type: string
        description: Cookie name to delete (any query param name)
      - name: _path
        in: query
        required: false
        schema:
          type: string
          default: /
        description: Path the cookie was set with
      - name: _domain
        in: query
        required: false
        schema:
          type: string
        description: Domain the cookie was set with
      responses:
        '302':
          description: Redirect to /cookies with expired Set-Cookie headers
  /ip:
    get:
      tags:
      - Info
      summary: Client IP address
      description: Returns the client's IP address. Respects X-Forwarded-For when RUSTYBIN_TRUST_FORWARD=true.
      operationId: getIp
      responses:
        '200':
          description: IP address
          content:
            application/json:
              schema:
                type: object
                properties:
                  ipv4:
                    type: string
                    example: 127.0.0.1
                    nullable: true
                  ipv6:
                    type: string
                    nullable: true
            application/xml:
              schema:
                type: object
                properties:
                  ipv4:
                    type: string
                    example: 127.0.0.1
                    nullable: true
                  ipv6:
                    type: string
                    nullable: true
  /ip/v4:
    get:
      tags:
      - Info
      summary: Client IPv4 address
      operationId: getIpV4
      responses:
        '200':
          description: IPv4 address
  /ip/v6:
    get:
      tags:
      - Info
      summary: Client IPv6 address
      operationId: getIpV6
      responses:
        '200':
          description: IPv6 address
  /date:
    get:
      tags:
      - Info
      summary: Current date (UTC)
      description: Returns the current date in UTC.
      operationId: getDate
      responses:
        '200':
          description: Date
          content:
            application/json:
              schema:
                type: object
                properties:
                  date:
                    type: string
                    format: date
                    example: 2024-01-01
                  timezone:
                    type: string
                    example: UTC
            application/xml:
              schema:
                type: object
                properties:
                  date:
                    type: string
                    format: date
                    example: 2024-01-01
                  timezone:
                    type: string
                    example: UTC
  /date/{timezone}:
    get:
      tags:
      - Info
      summary: Current date in timezone
      operationId: getDateTz
      parameters:
      - name: timezone
        in: path
        required: true
        schema:
          type: string
        example: America/New_York
        description: IANA timezone name
      responses:
        '200':
          description: Date in timezone
        '404':
          description: Unknown timezone
  /time:
    get:
      tags:
      - Info
      summary: Current time (UTC)
      description: Returns the current date and time in UTC (RFC 3339).
      operationId: getTime
      responses:
        '200':
          description: Time
          content:
            application/json:
              schema:
                type: object
                properties:
                  time:
                    type: string
                    format: date-time
                  timezone:
                    type: string
                    example: UTC
            application/xml:
              schema:
                type: object
                properties:
                  time:
                    type: string
                    format: date-time
                  timezone:
                    type: string
                    example: UTC
  /time/{timezone}:
    get:
      tags:
      - Info
      summary: Current time in timezone
      operationId: getTimeTz
      parameters:
      - name: timezone
        in: path
        required: true
        schema:
          type: string
        example: Europe/London
        description: IANA timezone name
      responses:
        '200':
          description: Time in timezone
        '404':
          description: Unknown timezone
  /uuid:
    get:
      tags:
      - Random
      summary: Generate a UUID v4
      operationId: getUuid
      responses:
        '200':
          description: UUID
          content:
            application/json:
              schema:
                type: object
                properties:
                  uuid:
                    type: string
                    format: uuid
            application/xml:
              schema:
                type: object
                properties:
                  uuid:
                    type: string
                    format: uuid
  /guuid:
    get:
      tags:
      - Random
      summary: Generate a GUID (braced UUID)
      operationId: getGuuid
      responses:
        '200':
          description: GUID
          content:
            application/json:
              schema:
                type: object
                properties:
                  guuid:
                    type: string
                    example: '{550e8400-e29b-41d4-a716-446655440000}'
            application/xml:
              schema:
                type: object
                properties:
                  guuid:
                    type: string
                    example: '{550e8400-e29b-41d4-a716-446655440000}'
  /random:
    get:
      tags:
      - Random
      summary: Random data bundle
      description: Returns random int, uint, uuid, guuid, and lorem ipsum text.
      operationId: getRandom
      responses:
        '200':
          description: Random data
  /random/int:
    get:
      tags:
      - Random
      summary: Random integer (-32000 to 32000)
      operationId: getRandomInt
      responses:
        '200':
          description: Random integer
          content:
            application/json:
              schema:
                type: object
                properties:
                  value:
                    type: integer
            application/xml:
              schema:
                type: object
                properties:
                  value:
                    type: integer
  /random/int/{lower}/{upper}:
    get:
      tags:
      - Random
      summary: Random integer in range
      operationId: getRandomIntRange
      parameters:
      - name: lower
        in: path
        required: true
        schema:
          type: integer
        description: Lower bound (inclusive)
      - name: upper
        in: path
        required: true
        schema:
          type: integer
        description: Upper bound (inclusive)
      responses:
        '200':
          description: Random integer in range
        '400':
          description: Invalid range (lower >= upper)
  /random/uint:
    get:
      tags:
      - Random
      summary: Random unsigned integer (0-65535)
      operationId: getRandomUint
      responses:
        '200':
          description: Random unsigned integer
  /random/lorem-ipsum:
    get:
      tags:
      - Random
      summary: Lorem ipsum text (1 paragraph)
      operationId: getLoremIpsum
      responses:
        '200':
          description: Lorem ipsum paragraph(s)
          content:
            application/json:
              schema:
                type: object
                properties:
                  paragraphs:
                    type: array
                    items:
                      type: string
            application/xml:
              schema:
                type: object
                properties:
                  paragraphs:
                    type: array
                    items:
                      type: string
  /random/lorem-ipsum/{count}:
    get:
      tags:
      - Random
      summary: Lorem ipsum text (N paragraphs)
      operationId: getLoremIpsumCount
      parameters:
      - name: count
        in: path
        required: true
        schema:
          type: integer
          minimum: 1
          maximum: 32
        description: Number of paragraphs
      responses:
        '200':
          description: Lorem ipsum paragraphs
        '400':
          description: Invalid count
  /graphql:
    get:
      tags:
      - GraphQL
      summary: GraphQL query over GET (or GraphiQL for browsers)
      description: 'Runs `query` (with optional `variables` / `operationName` / `extensions` as JSON strings). Mutations over GET return 405. Without a query, `Accept: text/html` gets a GraphiQL page (pinned CDN versions); other clients get 400.'
      operationId: getGraphql
      parameters:
      - name: query
        in: query
        required: false
        schema:
          type: string
        example: '{ users { id name } }'
      - name: variables
        in: query
        required: false
        schema:
          type: string
        description: JSON object
      - name: operationName
        in: query
        required: false
        schema:
          type: string
      - name: extensions
        in: query
        required: false
        schema:
          type: string
        description: JSON object, e.g. persistedQuery
      responses:
        '200':
          description: GraphQL response, or the GraphiQL page
          content:
            application/json: {}
            application/graphql-response+json: {}
            text/html: {}
        '400':
          description: Request error (and parse/validation errors with application/graphql-response+json)
        '405':
          description: Mutation over GET
    post:
      tags:
      - GraphQL
      summary: Execute GraphQL query
      description: 'Executes a GraphQL operation against the hardcoded dataset (users, products, orders, reviews). Content-Type application/json (or application/graphql with the query as body; others 415). `Accept: application/graphql-response+json` enables GraphQL-over-HTTP status codes (400 for parse/validation/limit errors); legacy application/json answers 200 for well-formed requests. Limits: depth 10, complexity 500, 30 aliases. Automatic persisted queries via extensions.persistedQuery (version 1, sha256Hash); unknown hashes return PersistedQueryNotFound.'
      operationId: postGraphql
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                query:
                  type: string
                  example: '{ users { id name email } }'
                variables:
                  type: object
                operationName:
                  type: string
                extensions:
                  type: object
                  properties:
                    persistedQuery:
                      type: object
                      properties:
                        version:
                          type: integer
                          example: 1
                        sha256Hash:
                          type: string
          application/graphql:
            schema:
              type: string
      responses:
        '200':
          description: GraphQL response
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: object
                  errors:
                    type: array
                    items:
                      type: object
            application/graphql-response+json: {}
        '400':
          description: Malformed request (or document error with application/graphql-response+json)
        '415':
          description: Unsupported Content-Type
  /graphql/schema:
    get:
      tags:
      - GraphQL
      summary: GraphQL SDL schema
      description: Returns the GraphQL schema in SDL format.
      operationId: getGraphqlSchema
      responses:
        '200':
          description: GraphQL SDL
          content:
            text/plain:
              schema:
                type: string
  /orchestration/step/1:
    post:
      tags:
      - Orchestration
      summary: 'Step 1: authenticate'
      description: Validate API key and return merchant metadata with correlation token
      operationId: postOrchestrationStep1
      parameters:
      - name: X-Api-Key
        in: header
        required: true
        schema:
          type: string
        description: API key (any non-empty value)
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
      responses:
        '200':
          description: Step 1 result
        '400':
          description: Missing required headers or body fields
        '401':
          description: Missing API key (step 1 only)
        '403':
          description: Validation not approved (step 4 only)
  /orchestration/step/2:
    post:
      tags:
      - Orchestration
      summary: 'Step 2: enrich'
      description: Enrich the transaction with a deterministic risk score (0-99, stable hash of correlation id, merchant_id, amount and card BIN) and card details. Amounts are numbers in major units (99.99).
      operationId: postOrchestrationStep2
      parameters:
      - name: X-Correlation-Id
        in: header
        required: true
        schema:
          type: string
        description: Correlation ID from step 1
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
      responses:
        '200':
          description: Step 2 result
        '400':
          description: Missing required headers or body fields
        '401':
          description: Missing API key (step 1 only)
        '403':
          description: Validation not approved (step 4 only)
  /orchestration/step/3:
    post:
      tags:
      - Orchestration
      summary: 'Step 3: validate'
      description: 'Apply business rules: declined when the risk score (forwarded `risk_score`, else computed like step 2) is >= 70, the amount exceeds 50000 or card_payment is not permitted. validation.result is the X-Validation-Result value for step 4.'
      operationId: postOrchestrationStep3
      parameters:
      - name: X-Correlation-Id
        in: header
        required: true
        schema:
          type: string
        description: Correlation ID from step 1
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
      responses:
        '200':
          description: Step 3 result
        '400':
          description: Missing required headers or body fields
        '401':
          description: Missing API key (step 1 only)
        '403':
          description: Validation not approved (step 4 only)
  /orchestration/step/4:
    post:
      tags:
      - Orchestration
      summary: 'Step 4: process'
      description: Execute the payment and return transaction result
      operationId: postOrchestrationStep4
      parameters:
      - name: X-Correlation-Id
        in: header
        required: true
        schema:
          type: string
        description: Correlation ID from step 1
      - name: X-Validation-Result
        in: header
        required: true
        schema:
          type: string
          enum:
          - approved
        description: Must be 'approved'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
      responses:
        '200':
          description: Step 4 result
        '400':
          description: Missing required headers or body fields
        '401':
          description: Missing API key (step 1 only)
        '403':
          description: Validation not approved (step 4 only)
  /orchestration/status:
    get:
      tags:
      - Orchestration
      summary: Orchestration pipeline overview
      description: Returns documentation of all orchestration steps with required headers and example bodies.
      operationId: getOrchestrationStatus
      responses:
        '200':
          description: Pipeline documentation
  /soap:
    get:
      tags:
      - SOAP
      summary: WSDL (GET /soap?wsdl)
      description: With the `wsdl` query string returns the WSDL document; otherwise 405 with a SOAP fault.
      operationId: getSoapWsdlQuery
      responses:
        '200':
          description: WSDL document
          content:
            text/xml:
              schema:
                type: string
        '405':
          description: Use POST for SOAP calls
    post:
      tags:
      - SOAP
      summary: SOAP 1.1 / 1.2 web service
      description: 'Accepts SOAP 1.1 (text/xml, envelope http://schemas.xmlsoap.org/soap/envelope/) and SOAP 1.2 (application/soap+xml, envelope http://www.w3.org/2003/05/soap-envelope) and responds in kind. Operations: GetUser, CreateOrder, GetStatus, dispatched by SOAPAction (1.1) or the Content-Type action parameter (1.2), falling back to the first Body element. Faults: SOAP 1.1 faultcode/faultstring/detail with HTTP 500; SOAP 1.2 Code/Reason/Detail with HTTP 400 (env:Sender) or 500.'
      operationId: postSoap
      parameters:
      - name: SOAPAction
        in: header
        required: false
        schema:
          type: string
        description: SOAP 1.1 action URI, e.g. "http://rustybin.local/GetUser"
      requestBody:
        required: true
        content:
          text/xml:
            schema:
              type: string
            example: <soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"><soap:Body><GetUser xmlns="http://rustybin.local/users"><userId>123</userId></GetUser></soap:Body></soap:Envelope>
          application/soap+xml:
            schema:
              type: string
            example: <env:Envelope xmlns:env="http://www.w3.org/2003/05/soap-envelope"><env:Body><GetStatus xmlns="http://rustybin.local/status"><orderId>ORD-1</orderId></GetStatus></env:Body></env:Envelope>
      responses:
        '200':
          description: SOAP response
          content:
            text/xml:
              schema:
                type: string
            application/soap+xml:
              schema:
                type: string
        '400':
          description: SOAP 1.2 sender fault
          content:
            application/soap+xml:
              schema:
                type: string
        '500':
          description: 'SOAP fault (1.1: every fault; 1.2: receiver, version mismatch, mustUnderstand)'
          content:
            text/xml:
              schema:
                type: string
            application/soap+xml:
              schema:
                type: string
  /soap/wsdl:
    get:
      tags:
      - SOAP
      summary: WSDL service description
      description: Returns the WSDL document defining the SOAP service operations (GetUser, CreateOrder, GetStatus) with SOAP 1.1 and SOAP 1.2 bindings; soap:address is derived from the request (Host, or trusted X-Forwarded-* headers).
      operationId: getSoapWsdl
      responses:
        '200':
          description: WSDL document
          content:
            text/xml:
              schema:
                type: string
  /_rustybin/requests:
    get:
      tags:
      - Control Plane
      summary: List captured requests
      operationId: listCapturedRequests
      parameters:
      - name: session
        in: query
        required: false
        schema:
          type: string
        description: Only entries captured with this X-Rustybin-Session value (required in public mode)
      - name: path_prefix
        in: query
        required: false
        schema:
          type: string
      - name: limit
        in: query
        required: false
        schema:
          type: integer
          default: 100
      responses:
        '200':
          description: Captured requests, newest first
          content:
            application/json:
              schema:
                type: object
                properties:
                  count:
                    type: integer
                  capacity:
                    type: integer
                  public_mode:
                    type: boolean
                  requests:
                    type: array
                    items:
                      $ref: '#/components/schemas/CapturedRequest'
        '400':
          description: Missing ?session= in public mode
        '403':
          description: 'permission_required: the caller lacks traffic.read'
      x-rustybin-permission: traffic.read
    delete:
      tags:
      - Control Plane
      summary: Clear captured requests
      description: With ?session= clears that session only; otherwise requires the admin token.
      operationId: clearCapturedRequests
      parameters:
      - name: session
        in: query
        required: false
        schema:
          type: string
        description: Only entries captured with this X-Rustybin-Session value (required in public mode)
      responses:
        '200':
          description: Number of cleared entries
        '401':
          description: Admin token required
        '403':
          description: Disabled in public mode without an admin token
      x-rustybin-permission: traffic.read
  /_rustybin/requests/stream:
    get:
      tags:
      - Control Plane
      summary: Live captured-request feed (SSE)
      operationId: streamCapturedRequests
      parameters:
      - name: session
        in: query
        required: false
        schema:
          type: string
        description: Only entries captured with this X-Rustybin-Session value (required in public mode)
      - name: path_prefix
        in: query
        required: false
        schema:
          type: string
      responses:
        '200':
          description: text/event-stream of `request` events
          content:
            text/event-stream: {}
        '403':
          description: 'permission_required: the caller lacks traffic.read'
      x-rustybin-permission: traffic.read
  /_rustybin/requests/{id}:
    get:
      tags:
      - Control Plane
      summary: Get one captured request
      operationId: getCapturedRequest
      parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
      - name: session
        in: query
        required: false
        schema:
          type: string
        description: Only entries captured with this X-Rustybin-Session value (required in public mode)
      responses:
        '200':
          description: Captured request
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CapturedRequest'
        '404':
          description: Unknown id (or not visible to this session)
        '403':
          description: 'permission_required: the caller lacks traffic.read'
      x-rustybin-permission: traffic.read
  /_rustybin/audit:
    get:
      tags:
      - Control Plane
      summary: The audit log
      operationId: listAudit
      parameters:
      - name: actor
        in: query
        required: false
        schema:
          type: string
        description: Contained in the actor's kind, user id, name or agent (any case)
      - name: action
        in: query
        required: false
        schema:
          type: string
        description: Contained in the action (route template and method, or tool) or the event
      - name: target
        in: query
        required: false
        schema:
          type: string
        description: Contained in a target's name; kind:name for one kind (mock:petstore)
      - name: since
        in: query
        required: false
        schema:
          type: string
        description: RFC 3339, a UTC day (2026-11-03) or an age (15m, 2h, 7d)
      - name: until
        in: query
        required: false
        schema:
          type: string
        description: RFC 3339, a UTC day or an age
      - name: result
        in: query
        required: false
        schema:
          type: string
        description: ok, error, refused, asked, or a status code
      - name: cursor
        in: query
        required: false
        schema:
          type: string
        description: The next_cursor of the previous page
      - name: limit
        in: query
        required: false
        schema:
          type: integer
          minimum: 1
          maximum: 500
          default: 100
      responses:
        '200':
          description: '{entries (newest first), next_cursor, log: held, capacity, persisted, files, bytes, retention_days, max_bytes, not_written, suppressed}'
        '400':
          description: An invalid filter
        '403':
          description: 'permission_required: audit.read'
      x-rustybin-permission: audit.read
  /_rustybin/audit/stream:
    get:
      tags:
      - Control Plane
      summary: Live tail of the audit log (SSE)
      operationId: streamAudit
      parameters:
      - name: actor
        in: query
        required: false
        schema:
          type: string
        description: Contained in the actor's kind, user id, name or agent (any case)
      - name: action
        in: query
        required: false
        schema:
          type: string
        description: Contained in the action (route template and method, or tool) or the event
      - name: target
        in: query
        required: false
        schema:
          type: string
        description: Contained in a target's name; kind:name for one kind (mock:petstore)
      - name: since
        in: query
        required: false
        schema:
          type: string
        description: RFC 3339, a UTC day (2026-11-03) or an age (15m, 2h, 7d)
      - name: until
        in: query
        required: false
        schema:
          type: string
        description: RFC 3339, a UTC day or an age
      - name: result
        in: query
        required: false
        schema:
          type: string
        description: ok, error, refused, asked, or a status code
      responses:
        '200':
          description: 'text/event-stream: entry events'
        '400':
          description: An invalid filter
        '403':
          description: 'permission_required: audit.read'
        '429':
          description: Too many listeners
      x-rustybin-permission: audit.read
  /_rustybin/mcp:
    post:
      tags:
      - Control Plane
      summary: 'Management MCP: send a JSON-RPC message (tools/list, tools/call, ...)'
      operationId: manageMcpPost
      parameters:
      - name: MCP-Protocol-Version
        in: header
        required: false
        schema:
          type: string
          example: 2026-07-28
      - name: Mcp-Method
        in: header
        required: false
        schema:
          type: string
      - name: Mcp-Name
        in: header
        required: false
        schema:
          type: string
      - name: Mcp-Session-Id
        in: header
        required: false
        schema:
          type: string
      responses:
        '200':
          description: JSON-RPC response (application/json) or SSE stream (text/event-stream)
          content:
            application/json: {}
            text/event-stream: {}
        '202':
          description: Notification accepted
        '400':
          description: Invalid message or missing session
        '401':
          description: Control-plane credentials required
        '403':
          description: Origin not allowed
        '404':
          description: Unknown method or session
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/McpJsonRpcMessage'
      x-rustybin-permission: signed in
    get:
      tags:
      - Control Plane
      summary: 'Management MCP: open the server-to-client stream (2025-xx sessions)'
      operationId: manageMcpStream
      parameters:
      - name: MCP-Protocol-Version
        in: header
        required: false
        schema:
          type: string
          example: 2026-07-28
      - name: Mcp-Method
        in: header
        required: false
        schema:
          type: string
      - name: Mcp-Name
        in: header
        required: false
        schema:
          type: string
      - name: Mcp-Session-Id
        in: header
        required: false
        schema:
          type: string
      responses:
        '200':
          description: JSON-RPC response (application/json) or SSE stream (text/event-stream)
          content:
            application/json: {}
            text/event-stream: {}
        '202':
          description: Notification accepted
        '400':
          description: Invalid message or missing session
        '401':
          description: Control-plane credentials required
        '403':
          description: Origin not allowed
        '404':
          description: Unknown method or session
      x-rustybin-permission: signed in
    delete:
      tags:
      - Control Plane
      summary: 'Management MCP: terminate the session (2025-xx)'
      operationId: manageMcpDelete
      parameters:
      - name: MCP-Protocol-Version
        in: header
        required: false
        schema:
          type: string
          example: 2026-07-28
      - name: Mcp-Method
        in: header
        required: false
        schema:
          type: string
      - name: Mcp-Name
        in: header
        required: false
        schema:
          type: string
      - name: Mcp-Session-Id
        in: header
        required: false
        schema:
          type: string
      responses:
        '200':
          description: JSON-RPC response (application/json) or SSE stream (text/event-stream)
          content:
            application/json: {}
            text/event-stream: {}
        '202':
          description: Notification accepted
        '400':
          description: Invalid message or missing session
        '401':
          description: Control-plane credentials required
        '403':
          description: Origin not allowed
        '404':
          description: Unknown method or session
      x-rustybin-permission: signed in
  /_rustybin/agents:
    get:
      tags:
      - Control Plane
      summary: AI agents on the management MCP server
      operationId: listAgents
      parameters:
      - name: owner
        in: query
        required: false
        schema:
          type: string
        description: Only the agents acting for this user (an id or a sign-in name)
      responses:
        '200':
          description: '{agents (each with owner and owner_label), owner, policy, capacity, persisted}'
        '403':
          description: 'permission_required: agents.read (another user''s agent) or agents.manage (changing another user''s agent, or widening your own)'
      x-rustybin-permission: signed in
  /_rustybin/agents/events:
    get:
      tags:
      - Control Plane
      summary: Live feed of agent calls (SSE)
      operationId: streamAgentEvents
      responses:
        '200':
          description: 'text/event-stream: call and agent events'
        '403':
          description: 'permission_required: agents.read (another user''s agent) or agents.manage (changing another user''s agent, or widening your own)'
        '429':
          description: Too many listeners
      x-rustybin-permission: signed in
  /_rustybin/agents/{id}:
    get:
      tags:
      - Control Plane
      summary: One AI agent
      operationId: getAgent
      parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
      responses:
        '200':
          description: The agent with owner, tools, last_errors, sessions, override and available_tools
        '403':
          description: 'permission_required: agents.read (another user''s agent) or agents.manage (changing another user''s agent, or widening your own)'
        '404':
          description: No such agent
      x-rustybin-permission: signed in
    patch:
      tags:
      - Control Plane
      summary: Set an AI agent's rights and note
      operationId: setAgentRights
      parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              additionalProperties: false
              properties:
                rights:
                  type:
                  - object
                  - 'null'
                  additionalProperties: false
                  properties:
                    ceiling:
                      type: string
                      description: read-only, traffic, manage, full, or a role name
                    allow:
                      type: array
                      items:
                        type: string
                      description: Management tool names
                    deny:
                      type: array
                      items:
                        type: string
                      description: Management tool names
                note:
                  type:
                  - string
                  - 'null'
                  maxLength: 500
      responses:
        '200':
          description: The agent
        '400':
          description: Invalid rights (an unknown tool or role)
        '403':
          description: 'permission_required: agents.read (another user''s agent) or agents.manage (changing another user''s agent, or widening your own)'
        '404':
          description: No such agent
      x-rustybin-permission: signed in
  /_rustybin/agents/{id}/revoke:
    post:
      tags:
      - Control Plane
      summary: Revoke an AI agent
      operationId: revokeAgent
      parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
      responses:
        '200':
          description: '{revoked, sessions_ended, tokens_revoked, note}'
        '403':
          description: 'permission_required: agents.read (another user''s agent) or agents.manage (changing another user''s agent, or widening your own)'
        '404':
          description: No such agent
      x-rustybin-permission: signed in
  /_rustybin/agents/{id}/restore:
    post:
      tags:
      - Control Plane
      summary: Restore a revoked AI agent
      operationId: restoreAgent
      parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
      responses:
        '200':
          description: '{restored}'
        '403':
          description: 'permission_required: agents.read (another user''s agent) or agents.manage (changing another user''s agent, or widening your own)'
        '404':
          description: No such agent
      x-rustybin-permission: signed in
  /_rustybin/agents/{id}/sessions:
    delete:
      tags:
      - Control Plane
      summary: End an AI agent's MCP sessions
      operationId: endAgentSessions
      parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
      responses:
        '200':
          description: '{sessions_ended}'
        '403':
          description: 'permission_required: agents.read (another user''s agent) or agents.manage (changing another user''s agent, or widening your own)'
        '404':
          description: No such agent
      x-rustybin-permission: signed in
  /_rustybin/config/export:
    get:
      tags:
      - Control Plane
      summary: The whole instance as rustybin.yaml
      operationId: exportConfig
      parameters:
      - name: format
        in: query
        schema:
          type: string
          enum:
          - yaml
          - json
          - bundle
          default: yaml
      - name: secrets
        in: query
        schema:
          type: string
          enum:
          - refs
          - omit
          default: refs
        description: 'refs: ${VAR} and secret:stored references; omit: no secrets section, demo credentials redacted'
      responses:
        '200':
          description: The document (application/yaml or application/json) or the bundle (application/gzip)
        '401':
          description: Admin rights required
        '403':
          description: 'permission_required: the caller lacks config.export'
      x-rustybin-permission: config.export
  /_rustybin/config/apply:
    post:
      tags:
      - Control Plane
      summary: Apply a rustybin.yaml document
      operationId: applyConfig
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - document
              additionalProperties: false
              properties:
                document:
                  description: 'The document: YAML or JSON text, or a JSON object'
                  oneOf:
                  - type: string
                  - type: object
                prune:
                  type: boolean
                  default: false
                  description: A present section is the whole truth for its part of the instance
                dry_run:
                  type: boolean
                  default: false
      responses:
        '200':
          description: '{ok, dry_run, prune, sections, changes, summary, results, problems}'
        '400':
          description: Not a document
        '401':
          description: Admin rights required
        '403':
          description: 'permission_required: the caller lacks config.apply'
      x-rustybin-permission: config.apply
  /_rustybin/config/schema:
    get:
      tags:
      - Control Plane
      summary: JSON Schema of rustybin.yaml
      operationId: getConfigSchema
      responses:
        '200':
          description: The schema (application/schema+json)
        '403':
          description: 'permission_required: the caller lacks instance.read'
      x-rustybin-permission: instance.read
  /_rustybin/git:
    get:
      tags:
      - Control Plane
      summary: Source control status
      operationId: gitStatus
      parameters:
      - name: fetch
        in: query
        schema:
          type: boolean
        description: Ask the remote for ahead and behind
      responses:
        '200':
          description: '{git, remote, branch, path, auth, initialised, head, ahead, behind, drift, deploy_key, timeline}'
        '401':
          description: Admin rights required
        '403':
          description: 'permission_required: the caller lacks git.read'
      x-rustybin-permission: git.read
  /_rustybin/git/init:
    post:
      tags:
      - Control Plane
      summary: Connect the repository
      operationId: gitInit
      responses:
        '200':
          description: '{initialised, fresh, remote_branch, head}'
        '401':
          description: Admin rights required
        '409':
          description: Not configured, not initialised, busy, or a conflict to resolve
        '502':
          description: git failed (the remote, the network)
        '403':
          description: 'permission_required: the caller lacks git.write'
      x-rustybin-permission: git.write
  /_rustybin/git/commit:
    post:
      tags:
      - Control Plane
      summary: Commit the instance's configuration
      operationId: gitCommit
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - message
              properties:
                message:
                  type: string
                  maxLength: 2000
                checkpoints:
                  type: array
                  items:
                    type: string
                  maxItems: 20
                  default: []
                  description: 'Local checkpoints written to <git.path>/checkpoints/<name>.json in this commit (mock data: an explicit commit only)'
      responses:
        '200':
          description: '{committed, files, checkpoints, head}'
        '401':
          description: Admin rights required
        '409':
          description: Not configured, not initialised, busy, or a conflict to resolve
        '502':
          description: git failed (the remote, the network)
        '403':
          description: 'permission_required: the caller lacks git.write'
      x-rustybin-permission: git.write
  /_rustybin/git/push:
    post:
      tags:
      - Control Plane
      summary: Push
      operationId: gitPush
      responses:
        '200':
          description: '{pushed, head}'
        '401':
          description: Admin rights required
        '409':
          description: Not configured, not initialised, busy, or a conflict to resolve
        '502':
          description: git failed (the remote, the network)
        '403':
          description: 'permission_required: the caller lacks git.write'
      x-rustybin-permission: git.write
  /_rustybin/git/webhook:
    post:
      tags:
      - Control Plane
      summary: A signed push from a git host
      operationId: gitWebhook
      responses:
        '202':
          description: A pull is queued
        '200':
          description: Another branch, or a ping
        '401':
          description: The signature does not match
        '404':
          description: git.webhook_secret is not set
      x-rustybin-permission: open
  /_rustybin/git/pull:
    post:
      tags:
      - Control Plane
      summary: Pull and apply
      operationId: gitPull
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                apply:
                  type: boolean
                  default: false
                dry_run:
                  type: boolean
                  default: false
                prune:
                  type: boolean
                  default: false
      responses:
        '200':
          description: '{fast_forwarded, ahead, behind, head, applied, checkpoints (the repository''s, by name)}'
        '401':
          description: Admin rights required
        '409':
          description: Not configured, not initialised, busy, or a conflict to resolve
        '502':
          description: git failed (the remote, the network)
        '403':
          description: 'permission_required: the caller lacks git.write'
      x-rustybin-permission: git.write
  /_rustybin/git/resolve:
    post:
      tags:
      - Control Plane
      summary: Resolve a diverged branch
      operationId: gitResolve
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - with
              properties:
                with:
                  type: string
                  enum:
                  - instance
                  - repository
                message:
                  type: string
      responses:
        '200':
          description: '{resolved, commit, push} or {resolved, applied, head}'
        '401':
          description: Admin rights required
        '409':
          description: Not configured, not initialised, busy, or a conflict to resolve
        '502':
          description: git failed (the remote, the network)
        '403':
          description: 'permission_required: the caller lacks git.write'
      x-rustybin-permission: git.write
  /_rustybin/git/key:
    get:
      tags:
      - Control Plane
      summary: The deploy key's public half
      operationId: gitKey
      responses:
        '200':
          description: '{deploy_key: {public, path} | null}'
        '401':
          description: Admin rights required
        '403':
          description: 'permission_required: the caller lacks git.read'
      x-rustybin-permission: git.read
    post:
      tags:
      - Control Plane
      summary: Generate the deploy key
      operationId: gitGenerateKey
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                replace:
                  type: boolean
                  default: false
      responses:
        '200':
          description: '{public, path, generated}'
        '401':
          description: Admin rights required
        '409':
          description: Not configured, not initialised, busy, or a conflict to resolve
        '502':
          description: git failed (the remote, the network)
        '403':
          description: 'permission_required: the caller lacks git.write'
      x-rustybin-permission: git.write
  /_rustybin/git/history:
    get:
      tags:
      - Control Plane
      summary: Commit history
      operationId: gitHistory
      responses:
        '200':
          description: '{commits: [{sha, short, author, at, subject, stat}]}'
        '401':
          description: Admin rights required
        '409':
          description: Not configured, not initialised, busy, or a conflict to resolve
        '502':
          description: git failed (the remote, the network)
        '403':
          description: 'permission_required: the caller lacks git.read'
      x-rustybin-permission: git.read
  /_rustybin/mcp-servers:
    get:
      tags:
      - Control Plane
      summary: MCP servers of this instance
      operationId: listMcpServers
      responses:
        '200':
          description: '{servers, relay, checks}'
        '403':
          description: 'permission_required: the caller lacks mcp.inspect'
      x-rustybin-permission: mcp.inspect
  /_rustybin/mcp-relay:
    post:
      tags:
      - Control Plane
      summary: Relay one JSON-RPC message to an MCP server
      operationId: mcpRelay
      description: 'The answer streams as SSE: relay ({session, status, headers}), message per JSON-RPC message, body for a non-JSON-RPC answer, error, done. Relay sessions keep the upstream session id or the open legacy stream (at most 64, idle 10 minutes).'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                target:
                  type: string
                  description: A path on this instance (/mcp, /mcp/protected, /_rustybin/mcp, /mock/<name>/_rustybin/mcp) or an http(s) URL
                headers:
                  type: object
                  additionalProperties:
                    type: string
                  description: Headers sent upstream (MCP routing headers, credentials)
                connection:
                  type: string
                  description: A saved connection whose credentials fill in
                transport:
                  type: string
                  enum:
                  - streamable
                  - legacy
                message:
                  type: object
                  description: One JSON-RPC message
                session:
                  type: string
                  description: The relay session of an earlier answer
                op:
                  type: string
                  enum:
                  - delete
      responses:
        '200':
          description: text/event-stream
        '400':
          description: Invalid target or message
        '403':
          description: The egress policy refused the host
        '404':
          description: Unknown relay session
        '502':
          description: The server could not be reached
      x-rustybin-permission: mcp.inspect
  /_rustybin/mcp-relay/fetch:
    post:
      tags:
      - Control Plane
      summary: Fetch an OAuth discovery or token document
      operationId: mcpRelayFetch
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - url
              properties:
                url:
                  type: string
                method:
                  type: string
                  enum:
                  - GET
                  - POST
                headers:
                  type: object
                body:
                  type: string
      responses:
        '200':
          description: '{status, headers, body, json}'
        '403':
          description: The egress policy refused the host
      x-rustybin-permission: mcp.inspect
  /_rustybin/mcp-relay/{session}:
    delete:
      tags:
      - Control Plane
      summary: End a relay session
      operationId: endMcpRelaySession
      parameters:
      - name: session
        in: path
        required: true
        schema:
          type: string
      responses:
        '200':
          description: Ended
        '404':
          description: Unknown session
        '403':
          description: 'permission_required: the caller lacks mcp.inspect'
      x-rustybin-permission: mcp.inspect
  /_rustybin/mcp-checks:
    get:
      tags:
      - Control Plane
      summary: Saved MCP check runs
      operationId: listMcpCheckRuns
      responses:
        '200':
          description: '{runs, capacity}'
        '403':
          description: 'permission_required: the caller lacks mcp.inspect'
      x-rustybin-permission: mcp.inspect
    post:
      tags:
      - Control Plane
      summary: Run the MCP check suite
      operationId: runMcpChecks
      description: 'Connect, version, auth refusal, tools/list schemas, every tool called with generated arguments, unknown tool and method, bad arguments, resources, prompts, ping, routing headers (2026-07-28), the session stream and lifecycle (session transports). A report like a harness report; format junit (or Accept: application/xml) for JUnit. Saved unless save is false.'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                target:
                  type: string
                  description: A path on this instance (/mcp, /mcp/protected, /_rustybin/mcp, /mock/<name>/_rustybin/mcp) or an http(s) URL
                headers:
                  type: object
                  additionalProperties:
                    type: string
                  description: Headers sent upstream (MCP routing headers, credentials)
                connection:
                  type: string
                  description: A saved connection whose credentials fill in
                transport:
                  type: string
                  enum:
                  - modern
                  - session
                  - legacy
                version:
                  type: string
                  enum:
                  - 2026-07-28
                  - 2025-11-25
                  - 2025-06-18
                  - 2025-03-26
                checks:
                  type: array
                  items:
                    type: string
                    enum:
                    - connect
                    - version
                    - auth_refusal
                    - tools_list
                    - tools_call
                    - unknown_tool
                    - bad_arguments
                    - unknown_method
                    - resources
                    - prompts
                    - ping
                    - routing_headers
                    - server_stream
                    - session
                include_destructive:
                  type: boolean
                max_tools:
                  type: integer
                  minimum: 1
                  maximum: 50
                ai_arguments:
                  type: boolean
                  description: Tool arguments from the AI assist provider (else the schema generator)
                save:
                  type: boolean
                format:
                  type: string
                  enum:
                  - json
                  - junit
      responses:
        '200':
          description: The report (JSON or JUnit XML)
        '400':
          description: Invalid target, transport or check ids
        '403':
          description: 'permission_required: the caller lacks mcp.inspect'
      x-rustybin-permission: mcp.inspect
  /_rustybin/mcp-checks/{id}:
    get:
      tags:
      - Control Plane
      summary: A saved MCP check run
      operationId: getMcpCheckRun
      parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
      - name: format
        in: query
        schema:
          type: string
          enum:
          - json
          - junit
      responses:
        '200':
          description: The report
        '404':
          description: Unknown run
        '403':
          description: 'permission_required: the caller lacks mcp.inspect'
      x-rustybin-permission: mcp.inspect
    delete:
      tags:
      - Control Plane
      summary: Delete a saved run (admin)
      operationId: deleteMcpCheckRun
      parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
      responses:
        '200':
          description: Deleted
        '404':
          description: Unknown run
        '403':
          description: 'permission_required: the caller lacks mcp.connections'
      x-rustybin-permission: mcp.connections
  /_rustybin/mcp-connections:
    get:
      tags:
      - Control Plane
      summary: Saved MCP connections
      operationId: listMcpConnections
      responses:
        '200':
          description: '{connections, capacity, persisted}'
        '403':
          description: 'permission_required: the caller lacks mcp.inspect'
      x-rustybin-permission: mcp.inspect
  /_rustybin/mcp-connections/{id}:
    get:
      tags:
      - Control Plane
      summary: A saved MCP connection
      operationId: getMcpConnection
      parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
      responses:
        '200':
          description: The connection; secrets as {set, hint}
        '404':
          description: Unknown connection
        '403':
          description: 'permission_required: the caller lacks mcp.inspect'
      x-rustybin-permission: mcp.inspect
    put:
      tags:
      - Control Plane
      summary: Save an MCP connection (admin)
      operationId: putMcpConnection
      parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - name
              - target
              properties:
                name:
                  type: string
                target:
                  type: string
                transport:
                  type: string
                  enum:
                  - modern
                  - session
                  - legacy
                version:
                  type: string
                mode:
                  type: string
                  enum:
                  - browser
                  - relay
                auth:
                  type: object
                  properties:
                    kind:
                      type: string
                      enum:
                      - none
                      - bearer
                      - api_key
                      - basic
                      - headers
                      - oauth
                    name:
                      type: string
                    location:
                      type: string
                      enum:
                      - header
                      - query
                    username:
                      type: string
                    headers:
                      type: array
                      items:
                        type: string
                    oauth:
                      type: object
                secrets:
                  type: object
                  description: token, api_key, password, refresh_token, client_secret or header:<name>; null clears, absent keeps
      responses:
        '200':
          description: The saved connection
        '400':
          description: Invalid connection
        '403':
          description: 'permission_required: the caller lacks mcp.connections'
      x-rustybin-permission: mcp.connections
    delete:
      tags:
      - Control Plane
      summary: Delete a saved MCP connection (admin)
      operationId: deleteMcpConnection
      parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
      responses:
        '200':
          description: Deleted
        '404':
          description: Unknown connection
        '403':
          description: 'permission_required: the caller lacks mcp.connections'
      x-rustybin-permission: mcp.connections
  /mcp-registry:
    get:
      tags:
      - MCP
      summary: The mock MCP registry
      operationId: registryIndex
      responses:
        '200':
          description: '{name, api, aliases, servers, schema, scenario}'
  /mcp-registry/v0.1/servers:
    get:
      tags:
      - MCP
      summary: List MCP servers
      operationId: registryListServers
      description: One record per server version, ordered by name then version (as text). version=latest for the latest of each. Follow metadata.nextCursor with the same filters.
      parameters:
      - name: cursor
        in: query
        schema:
          type: string
      - name: limit
        in: query
        schema:
          type: integer
          minimum: 1
          maximum: 100
          default: 30
      - name: updated_since
        in: query
        schema:
          type: string
          format: date-time
      - name: search
        in: query
        description: Substring of the name, at least 3 characters
        schema:
          type: string
      - name: version
        in: query
        schema:
          type: string
      - name: include_deleted
        in: query
        schema:
          type: boolean
      responses:
        '200':
          description: A ServerListResponse
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RegistryServerList'
        '422':
          description: A problem (application/problem+json)
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/RegistryProblem'
        '503':
          description: A problem (application/problem+json)
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/RegistryProblem'
  /mcp-registry/v0.1/servers/{serverName}/versions:
    get:
      tags:
      - MCP
      summary: Every version of a server
      operationId: registryServerVersions
      parameters:
      - name: serverName
        in: path
        required: true
        description: The server name, URL-encoded (io.rustybin%2Fmcp)
        schema:
          type: string
      - name: include_deleted
        in: query
        schema:
          type: boolean
      responses:
        '200':
          description: A ServerListResponse
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RegistryServerList'
        '404':
          description: A problem (application/problem+json)
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/RegistryProblem'
  /mcp-registry/v0.1/servers/{serverName}/versions/{version}:
    get:
      tags:
      - MCP
      summary: One version of a server
      operationId: registryServerVersion
      parameters:
      - name: serverName
        in: path
        required: true
        description: The server name, URL-encoded (io.rustybin%2Fmcp)
        schema:
          type: string
      - name: version
        in: path
        required: true
        description: An exact version, or latest
        schema:
          type: string
      - name: include_deleted
        in: query
        schema:
          type: boolean
      responses:
        '200':
          description: A ServerResponse
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RegistryServerResponse'
        '404':
          description: A problem (application/problem+json)
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/RegistryProblem'
  /mcp-registry/v0.1/servers/{serverName}/versions/{version}/status:
    patch:
      tags:
      - MCP
      summary: Change the status of a published version
      operationId: registryVersionStatus
      parameters:
      - name: Authorization
        in: header
        required: true
        description: Bearer <registry token from auth/*>
        schema:
          type: string
      - name: serverName
        in: path
        required: true
        description: The server name, URL-encoded (io.rustybin%2Fmcp)
        schema:
          type: string
      - name: version
        in: path
        required: true
        description: An exact version, or latest
        schema:
          type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - status
              properties:
                status:
                  type: string
                  enum:
                  - active
                  - deprecated
                  - deleted
                statusMessage:
                  type: string
                  maxLength: 500
      responses:
        '200':
          description: A ServerResponse
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RegistryServerResponse'
        '401':
          description: A problem (application/problem+json)
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/RegistryProblem'
        '403':
          description: A problem (application/problem+json)
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/RegistryProblem'
        '404':
          description: A problem (application/problem+json)
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/RegistryProblem'
  /mcp-registry/v0.1/servers/{serverName}/status:
    patch:
      tags:
      - MCP
      summary: Change the status of every published version of a server
      operationId: registryServerStatus
      parameters:
      - name: Authorization
        in: header
        required: true
        description: Bearer <registry token from auth/*>
        schema:
          type: string
      - name: serverName
        in: path
        required: true
        description: The server name, URL-encoded (io.rustybin%2Fmcp)
        schema:
          type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - status
              properties:
                status:
                  type: string
                  enum:
                  - active
                  - deprecated
                  - deleted
                statusMessage:
                  type: string
                  maxLength: 500
      responses:
        '200':
          description: '{updatedCount, servers}'
        '401':
          description: A problem (application/problem+json)
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/RegistryProblem'
        '403':
          description: A problem (application/problem+json)
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/RegistryProblem'
        '404':
          description: A problem (application/problem+json)
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/RegistryProblem'
  /mcp-registry/v0.1/publish:
    post:
      tags:
      - MCP
      summary: Publish a server.json
      operationId: registryPublish
      description: Validated against the vendored server.json schema and the semantic checks. The io.rustybin namespace is reserved; a duplicate version is refused; at most 256 file and published entries. In public mode the admin token is needed as X-Rustybin-Admin-Token.
      parameters:
      - name: Authorization
        in: header
        required: true
        description: Bearer <registry token from auth/*>
        schema:
          type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/RegistryServerJson'
      responses:
        '200':
          description: A ServerResponse
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RegistryServerResponse'
        '400':
          description: A problem (application/problem+json)
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/RegistryProblem'
        '401':
          description: A problem (application/problem+json)
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/RegistryProblem'
        '403':
          description: A problem (application/problem+json)
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/RegistryProblem'
        '409':
          description: A problem (application/problem+json)
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/RegistryProblem'
        '422':
          description: A problem (application/problem+json)
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/RegistryProblem'
  /mcp-registry/v0.1/validate:
    post:
      tags:
      - MCP
      summary: Validate a server.json
      operationId: registryValidate
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/RegistryServerJson'
      responses:
        '200':
          description: '{valid, issues: [{type, path, message, severity, reference}]}'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RegistryValidation'
        '400':
          description: A problem (application/problem+json)
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/RegistryProblem'
  /mcp-registry/v0.1/auth/{method}:
    post:
      tags:
      - MCP
      summary: Exchange a credential for a registry token (stub)
      operationId: registryAuth
      description: github-at {github_token}, github-oidc and oidc {oidc_token}, dns and http {domain, timestamp, signed_timestamp}, none {}. Every credential is accepted (nothing is verified), except in the auth_fail scenario, where every exchange answers 401. Tokens last 5 minutes.
      parameters:
      - name: method
        in: path
        required: true
        schema:
          type: string
          enum:
          - github-at
          - github-oidc
          - oidc
          - dns
          - http
          - none
      requestBody:
        content:
          application/json:
            schema:
              type: object
      responses:
        '200':
          description: '{registry_token, expires_at}'
        '401':
          description: A problem (application/problem+json)
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/RegistryProblem'
        '404':
          description: A problem (application/problem+json)
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/RegistryProblem'
        '422':
          description: A problem (application/problem+json)
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/RegistryProblem'
  /mcp-registry/v0.1/health:
    get:
      tags:
      - MCP
      summary: Registry health
      operationId: registryHealth
      responses:
        '200':
          description: '{status}'
  /mcp-registry/v0.1/ping:
    get:
      tags:
      - MCP
      summary: Registry ping
      operationId: registryPing
      responses:
        '200':
          description: '{pong}'
  /mcp-registry/v0.1/version:
    get:
      tags:
      - MCP
      summary: Registry version
      operationId: registryVersion
      responses:
        '200':
          description: '{version, git_commit, build_time}'
  /mcp-registry/v0/{path}:
    get:
      tags:
      - MCP
      summary: The /v0.1 API under /v0
      operationId: registryV0Get
      parameters:
      - name: path
        in: path
        required: true
        schema:
          type: string
      responses:
        '200':
          description: As /v0.1
    post:
      tags:
      - MCP
      summary: The /v0.1 API under /v0
      operationId: registryV0Post
      parameters:
      - name: path
        in: path
        required: true
        schema:
          type: string
      responses:
        '200':
          description: As /v0.1
    put:
      tags:
      - MCP
      summary: The /v0.1 API under /v0
      operationId: registryV0Put
      parameters:
      - name: path
        in: path
        required: true
        schema:
          type: string
      responses:
        '200':
          description: As /v0.1
    patch:
      tags:
      - MCP
      summary: The /v0.1 API under /v0
      operationId: registryV0Patch
      parameters:
      - name: path
        in: path
        required: true
        schema:
          type: string
      responses:
        '200':
          description: As /v0.1
    delete:
      tags:
      - MCP
      summary: The /v0.1 API under /v0
      operationId: registryV0Delete
      parameters:
      - name: path
        in: path
        required: true
        schema:
          type: string
      responses:
        '200':
          description: As /v0.1
  /_rustybin/registry:
    get:
      tags:
      - Control Plane
      summary: The mock registry's state
      operationId: registryState
      responses:
        '200':
          description: '{path, api, base_url, scenario, scenarios, entries, counts, capacity, saved, sync_config}'
        '403':
          description: 'permission_required: the caller lacks instance.read'
      x-rustybin-permission: instance.read
  /_rustybin/registry/servers/{serverName}:
    delete:
      tags:
      - Control Plane
      summary: Remove a published entry (admin)
      operationId: registryDeleteServer
      parameters:
      - name: serverName
        in: path
        required: true
        description: The server name, URL-encoded (io.rustybin%2Fmcp)
        schema:
          type: string
      - name: version
        in: query
        schema:
          type: string
      responses:
        '200':
          description: '{deleted, versions}'
        '404':
          description: No published entry
        '403':
          description: 'permission_required: the caller lacks registry.write'
      x-rustybin-permission: registry.write
  /_rustybin/registry/validate:
    post:
      tags:
      - Control Plane
      summary: Validate a server.json
      operationId: registryValidateControl
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/RegistryServerJson'
      responses:
        '200':
          description: '{valid, issues: [{type, path, message, severity, reference}]}'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RegistryValidation'
        '400':
          description: Not JSON
        '403':
          description: 'permission_required: the caller lacks instance.read'
      x-rustybin-permission: instance.read
  /_rustybin/registry/browse:
    get:
      tags:
      - Control Plane
      summary: Browse an MCP registry
      operationId: registryBrowse
      parameters:
      - name: url
        in: query
        description: A registry base URL, or /mcp-registry for this instance
        schema:
          type: string
      - name: search
        in: query
        schema:
          type: string
      - name: cursor
        in: query
        schema:
          type: string
      - name: limit
        in: query
        schema:
          type: integer
          minimum: 1
          maximum: 100
      - name: version
        in: query
        schema:
          type: string
      responses:
        '200':
          description: '{registry, request, status, cached, fetched_at, count, next_cursor, servers: [{name, title, description, version, status, remotes: [{type, url, headers, auth}], packages, valid, issues, server}]}'
        '400':
          description: Invalid url
        '403':
          description: The egress policy refused the host
        '502':
          description: The registry failed or did not answer
      x-rustybin-permission: mcp.inspect
  /_rustybin/portal:
    get:
      tags:
      - Control Plane
      summary: Dev portal items and settings
      operationId: getPortal
      responses:
        '200':
          description: Settings, items, counts
          content:
            application/json:
              schema:
                type: object
        '403':
          description: 'permission_required: the caller lacks instance.read'
      x-rustybin-permission: instance.read
  /_rustybin/portal/items/{id}:
    put:
      tags:
      - Control Plane
      summary: Publish an item to the dev portal
      operationId: publishPortalItem
      parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
        description: mock:<name>, mcp:<server>, llm, a2a:<agent>, tools or registry
      requestBody:
        required: false
        content:
          application/json:
            schema:
              type: object
              properties:
                title:
                  type: string
                  maxLength: 120
                summary:
                  type: string
                  maxLength: 2000
                  description: Markdown
                tags:
                  type: array
                  items:
                    type: string
                    maxLength: 40
                  maxItems: 10
                contact:
                  type: string
                  maxLength: 200
                order:
                  type: integer
                  minimum: -1000
                  maximum: 1000
                credentials:
                  type: boolean
                  description: Show a mock's demo credentials (default true)
                access:
                  type: string
                  enum:
                  - open
                  - closed
                  description: 'open (default): anyone uses it; closed: everyone reads it, only portal readers (portal.read) use it'
                secure:
                  type: boolean
                  description: 'With access closed on a mock that cannot be closed as it is: turn its security on with random credentials in place of the defaults (api_key mode unless the spec''s or basic mode closes it) and deploy it first (needs mocks.write)'
      responses:
        '200':
          description: Updated
        '201':
          description: Published
        '400':
          description: Invalid id or metadata
        '403':
          description: Not allowed
        '404':
          description: Unknown mock
        '409':
          description: Defined in the config file, the portal is full, a mock that cannot be closed (security_off, default_credentials, obtainable_credentials, open_operations), or a draft not deployed (secure)
      x-rustybin-permission: portal.publish
    delete:
      tags:
      - Control Plane
      summary: Unpublish an item
      operationId: unpublishPortalItem
      parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
        description: mock:<name>, mcp:<server>, llm, a2a:<agent>, tools or registry
      responses:
        '200':
          description: Unpublished
        '404':
          description: Not published
        '409':
          description: Defined in the config file
        '403':
          description: 'permission_required: the caller lacks portal.publish'
      x-rustybin-permission: portal.publish
  /_rustybin/portal/insights:
    get:
      tags:
      - Control Plane
      summary: Dev portal insights
      operationId: getPortalInsights
      responses:
        '200':
          description: Counters per item
          content:
            application/json:
              schema:
                type: object
        '403':
          description: 'permission_required: the caller lacks instance.read'
      x-rustybin-permission: instance.read
  /portal:
    get:
      tags:
      - Docs
      summary: Dev portal
      operationId: getPortalRedirect
      responses:
        '307':
          description: Redirect to /portal/
        '404':
          description: The portal is off
  /portal/:
    get:
      tags:
      - Docs
      summary: Dev portal entry page
      operationId: getPortalIndex
      responses:
        '200':
          description: HTML
        '404':
          description: The portal is off
  /portal/{path}:
    get:
      tags:
      - Docs
      summary: Dev portal asset (SPA fallback to index.html)
      operationId: getPortalAsset
      parameters:
      - name: path
        in: path
        required: true
        schema:
          type: string
      responses:
        '200':
          description: Asset
        '304':
          description: Not modified
        '404':
          description: Unknown asset, or the portal is off
  /portal/api/catalog:
    get:
      tags:
      - Docs
      summary: Dev portal catalogue
      operationId: getPortalCatalog
      responses:
        '200':
          description: Published items
          content:
            application/json:
              schema:
                type: object
                properties:
                  portal:
                    type: object
                  kinds:
                    type: array
                    items:
                      type: object
                  items:
                    type: array
                    items:
                      type: object
                  whats_new:
                    type: array
                    items:
                      type: string
        '304':
          description: Not modified
        '401':
          description: portal.auth asks for a credential
        '404':
          description: The portal is off
  /portal/api/events:
    post:
      tags:
      - Docs
      summary: Count a portal view or Try it call
      operationId: postPortalEvent
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - item
              - event
              properties:
                item:
                  type: string
                event:
                  type: string
                  enum:
                  - view
                  - try
      responses:
        '204':
          description: Counted
        '400':
          description: Invalid event
        '401':
          description: A Try it of a closed item, not signed in
        '404':
          description: No such published item
  /portal/api/items/{id}/collections/{format}:
    get:
      tags:
      - Docs
      summary: A published mock as a collection
      operationId: getPortalCollection
      parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
        description: mock:<name>, mcp:<server>, llm, a2a:<agent>, tools or registry
      - name: format
        in: path
        required: true
        schema:
          type: string
          enum:
          - postman
          - hurl
          - curl
          - http
          - k6
          - bruno
          - insomnia
      responses:
        '200':
          description: The collection (attachment)
        '401':
          description: A closed item, not signed in (collections carry the credentials)
        '403':
          description: Signed in without portal.read
        '404':
          description: Not published, or an unknown format
  /portal/reference/{id}:
    get:
      tags:
      - Docs
      summary: A published mock's API reference
      operationId: getPortalReference
      parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
        description: mock:<name>, mcp:<server>, llm, a2a:<agent>, tools or registry
      responses:
        '200':
          description: HTML
        '401':
          description: portal.auth=password, not signed in
        '404':
          description: Not published
  /portal/api/login:
    post:
      tags:
      - Docs
      summary: Dev portal sign-in
      operationId: portalLogin
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - username
              - password
              properties:
                username:
                  type: string
                password:
                  type: string
                  format: password
      responses:
        '200':
          description: 'Signed in: Set-Cookie rustybin_portal'
          content:
            application/json:
              schema:
                type: object
                properties:
                  signed_in:
                    type: boolean
                  user:
                    type: string
                  display_name:
                    type: string
                  reader:
                    type: boolean
                  expires_in:
                    type: integer
        '400':
          description: Invalid body
        '401':
          description: Invalid user name or password
        '404':
          description: The portal or its sign-in is off
        '429':
          description: Too many failed attempts (shared with the console sign-in)
  /portal/api/logout:
    post:
      tags:
      - Docs
      summary: Dev portal sign-out
      operationId: portalLogout
      responses:
        '200':
          description: Signed out, the cookie cleared
        '404':
          description: The portal is off
  /portal/api/session:
    get:
      tags:
      - Docs
      summary: Dev portal session
      operationId: portalSession
      responses:
        '200':
          description: Who is signed in
          content:
            application/json:
              schema:
                type: object
                properties:
                  signed_in:
                    type: boolean
                  via:
                    type: string
                    enum:
                    - cookie
                    - bearer
                  user:
                    type: string
                  display_name:
                    type: string
                  reader:
                    type: boolean
                  signin:
                    type: boolean
        '404':
          description: The portal is off
  /.well-known/oauth-protected-resource/_rustybin/mcp:
    get:
      tags:
      - Control Plane
      summary: Protected resource metadata of /_rustybin/mcp (RFC 9728)
      operationId: getManageMcpResourceMetadata
      responses:
        '200':
          description: Metadata
        '404':
          description: OAuth sign-in is off on this instance
  /.well-known/oauth-authorization-server/_rustybin/oauth:
    get:
      tags:
      - Control Plane
      summary: Authorization server metadata (RFC 8414)
      operationId: getControlOAuthMetadata
      responses:
        '200':
          description: Metadata
        '404':
          description: OAuth sign-in is off on this instance
  /.well-known/openid-configuration/_rustybin/oauth:
    get:
      tags:
      - Control Plane
      summary: Authorization server metadata (OpenID Connect discovery path)
      operationId: getControlOAuthOidcMetadata
      responses:
        '200':
          description: Metadata
        '404':
          description: OAuth sign-in is off on this instance
  /_rustybin/oauth/register:
    post:
      tags:
      - Control Plane
      summary: Register a public client (RFC 7591)
      operationId: registerControlOAuthClient
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - redirect_uris
              properties:
                redirect_uris:
                  type: array
                  items:
                    type: string
                  maxItems: 3
                client_name:
                  type: string
                grant_types:
                  type: array
                  items:
                    type: string
                token_endpoint_auth_method:
                  type: string
                  enum:
                  - none
      responses:
        '201':
          description: Registered
        '400':
          description: Invalid metadata
        '404':
          description: OAuth sign-in is off
      x-rustybin-permission: open
  /_rustybin/oauth/authorize:
    get:
      tags:
      - Control Plane
      summary: 'Authorization endpoint: redirects to the console''s consent page'
      operationId: authorizeControlOAuth
      parameters:
      - name: response_type
        in: query
        required: true
        schema:
          type: string
      - name: client_id
        in: query
        required: true
        schema:
          type: string
      - name: redirect_uri
        in: query
        required: false
        schema:
          type: string
      - name: code_challenge
        in: query
        required: true
        schema:
          type: string
      - name: code_challenge_method
        in: query
        required: true
        schema:
          type: string
      - name: scope
        in: query
        required: false
        schema:
          type: string
      - name: state
        in: query
        required: false
        schema:
          type: string
      - name: resource
        in: query
        required: false
        schema:
          type: string
      responses:
        '302':
          description: To the consent page, or back to the client with an error
        '400':
          description: Unknown client or redirect URI (HTML page)
        '404':
          description: OAuth sign-in is off
      x-rustybin-permission: open
  /_rustybin/oauth/requests/{id}:
    get:
      tags:
      - Control Plane
      summary: A pending sign-in request (for the consent page)
      operationId: getControlOAuthRequest
      parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
      responses:
        '200':
          description: 'The request: the client, the scopes asked for and grantable, the approver, and what the agent will hold (grants per scope, permissions; a user''s agent holds the user''s permissions within the scope)'
        '401':
          description: Control-plane credentials required
        '404':
          description: Unknown or expired
      x-rustybin-permission: signed in
    post:
      tags:
      - Control Plane
      summary: Approve or deny a pending sign-in request
      operationId: answerControlOAuthRequest
      parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - decision
              properties:
                decision:
                  type: string
                  enum:
                  - approve
                  - deny
                scope:
                  type: string
                  description: 'Space separated: admin, console, inspector, harness (default: what the agent asked for, capped by the approver''s grant)'
      responses:
        '200':
          description: Where to send the browser (redirect_to)
        '403':
          description: The approver's credential cannot grant the scopes
        '404':
          description: Unknown or expired
      x-rustybin-permission: signed in
  /_rustybin/oauth/token:
    post:
      tags:
      - Control Plane
      summary: Token endpoint (authorization_code with PKCE S256, refresh_token)
      operationId: controlOAuthToken
      requestBody:
        required: true
        content:
          application/x-www-form-urlencoded:
            schema:
              type: object
              required:
              - grant_type
              - client_id
              properties:
                grant_type:
                  type: string
                  enum:
                  - authorization_code
                  - refresh_token
                client_id:
                  type: string
                code:
                  type: string
                code_verifier:
                  type: string
                redirect_uri:
                  type: string
                refresh_token:
                  type: string
                scope:
                  type: string
                resource:
                  type: string
      responses:
        '200':
          description: Tokens
        '400':
          description: OAuth error
        '401':
          description: Unknown client
        '404':
          description: OAuth sign-in is off
      x-rustybin-permission: open
  /_rustybin/login:
    post:
      tags:
      - Control Plane
      summary: Console sign-in
      description: 'Looks the user up in the user store and checks the password: the first admin''s is the sign-in settings'' (RUSTYBIN_CONSOLE_USER, RUSTYBIN_CONSOLE_PASSWORD or _HASH, the stored console.password_hash; rustybin / rustybin by default). Answers a 12 hour control token whose permissions are the user''s roles, resolved at every request (a disabled user is answered as a wrong password). Never behind control-plane auth. After 5 failed attempts from one client address within a minute the address waits (429, Retry-After, doubling up to 10 minutes).'
      operationId: consoleLogin
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - username
              - password
              properties:
                username:
                  type: string
                password:
                  type: string
      responses:
        '200':
          description: Signed in
          content:
            application/json:
              schema:
                type: object
                properties:
                  token:
                    type: string
                    description: 'A control token (HS256 JWT): send it as Authorization: Bearer'
                  token_type:
                    type: string
                    enum:
                    - Bearer
                  expires_in:
                    type: integer
                  expires_at:
                    type: string
                    format: date-time
                  scopes:
                    type: array
                    items:
                      type: string
                  user:
                    type: string
                  user_id:
                    type: string
                    description: The user's id in the user store (usr_ plus 16 hex)
                  default_password:
                    type: boolean
                    description: The instance still signs in with the default password
        '401':
          description: Wrong user name or password (reason invalid_credentials)
        '404':
          description: Not in password mode (or shared mode)
        '429':
          description: Too many failed attempts from this address (Retry-After)
      x-rustybin-permission: open
  /_rustybin/logout:
    post:
      tags:
      - Control Plane
      summary: Console sign-out
      description: 'Revokes the presented sign-in token for the rest of its life (a bounded denylist of token ids). Other credentials answer revoked: false.'
      operationId: consoleLogout
      responses:
        '200':
          description: Done
          content:
            application/json:
              schema:
                type: object
                properties:
                  revoked:
                    type: boolean
        '404':
          description: Not in password mode (or shared mode)
      x-rustybin-permission: signed in
  /_rustybin/session:
    get:
      tags:
      - Control Plane
      summary: This session
      description: 'Describes the credential the request presents: obtained (password, admin_token, control_jwt, api_key), the user (user, user_id, display_name) of a sign-in token, the roles and permissions resolved at this request, scopes, issue and expiry times, plus whether the instance still uses the default password.'
      operationId: getConsoleSession
      responses:
        '200':
          description: The session
          content:
            application/json:
              schema:
                type: object
                properties:
                  control_auth:
                    type: string
                  obtained:
                    type: string
                    enum:
                    - password
                    - sso
                    - admin_token
                    - control_jwt
                    - api_key
                    description: 'How the credential was obtained: password and sso are sign-in tokens (the user''s own password, or single sign-on)'
                  user:
                    type:
                    - string
                    - 'null'
                    description: The sign-in name (sign-in tokens)
                  user_id:
                    type:
                    - string
                    - 'null'
                    description: 'The user''s id (sign-in tokens): usr_ plus 16 hex'
                  display_name:
                    type:
                    - string
                    - 'null'
                  roles:
                    type: array
                    items:
                      type: string
                    description: The roles the permissions come from (admin for the admin token; a control JWT's scopes as roles plus rb_roles)
                  permissions:
                    type: array
                    items:
                      type: string
                    description: The caller's permissions, resolved at this request (dotted names, mocks.write)
                  scopes:
                    type: array
                    items:
                      type: string
                    description: The 0.3 scopes these permissions amount to
                  sub:
                    type: string
                    description: The sub claim of a control JWT
                  id:
                    type: string
                    description: The session id of a sign-in token
                  issued_at:
                    type: string
                    format: date-time
                  expires_at:
                    type: string
                    format: date-time
                  default_password:
                    type: boolean
                  sign_in_user:
                    type:
                    - string
                    - 'null'
                    description: The first admin's sign-in name (password mode)
        '404':
          description: The control plane is open
      x-rustybin-permission: signed in
  /_rustybin/sessions:
    get:
      tags:
      - Control Plane
      summary: Sign-in sessions
      description: 'The sign-in tokens issued since the last restart or sign-out-everywhere (at most 16 per user and 4096 in all, newest first; a sign-in past a user''s 16 ends their oldest session): id, user, user_id, issue, expiry and last use times, client address. Admin.'
      operationId: listConsoleSessions
      responses:
        '200':
          description: Sessions
          content:
            application/json:
              schema:
                type: object
                properties:
                  generation:
                    type: integer
                  count:
                    type: integer
                  current:
                    type:
                    - string
                    - 'null'
                    description: The caller's own session id
                  sessions:
                    type: array
                    items:
                      type: object
        '404':
          description: Not in password mode (or shared mode)
        '403':
          description: 'permission_required: the caller lacks users.read'
      x-rustybin-permission: users.read
    delete:
      tags:
      - Control Plane
      summary: Sign out everywhere
      description: 'Rotates the token generation: every sign-in token issued before now is refused, the caller''s included, and every OAuth and agent token. Needs every permission (the admin role or the admin token).'
      operationId: endAllConsoleSessions
      responses:
        '200':
          description: Done
          content:
            application/json:
              schema:
                type: object
                properties:
                  ended:
                    type: integer
                  generation:
                    type: integer
        '404':
          description: Not in password mode (or shared mode)
        '403':
          description: 'permission_required: the caller lacks users.manage'
      x-rustybin-permission: users.manage
  /_rustybin/sessions/{id}:
    delete:
      tags:
      - Control Plane
      summary: End a sign-in session
      description: Needs users.manage plus every permission the session's user holds (in the instance and in their projects), as for any change to that user.
      operationId: endConsoleSession
      parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
      responses:
        '200':
          description: 'Done (ended: false when the session is not listed)'
          content:
            application/json:
              schema:
                type: object
                properties:
                  ended:
                    type: boolean
        '404':
          description: Not in password mode (or shared mode)
        '403':
          description: 'permission_required: the caller lacks users.manage'
      x-rustybin-permission: users.manage
  /_rustybin/password:
    put:
      tags:
      - Control Plane
      summary: Change the console user name and password
      description: Needs admin and the current password. The new password (8 to 256 characters, not the default) is stored as an argon2id hash in the secret console.password_hash; a new username goes to console.user. Every other sign-in session ends and the caller gets a fresh token when it signed in with one.
      operationId: changeConsolePassword
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - current_password
              - new_password
              properties:
                current_password:
                  type: string
                new_password:
                  type: string
                  minLength: 8
                  maxLength: 256
                username:
                  type: string
                  description: A new user name (letters, digits and . _ @ -)
      responses:
        '200':
          description: Changed
          content:
            application/json:
              schema:
                type: object
                properties:
                  changed:
                    type: boolean
                  user:
                    type: string
                  signed_out:
                    type: integer
                  token:
                    type: string
                  expires_at:
                    type: string
                    format: date-time
        '400':
          description: The new password or user name breaks a rule
        '403':
          description: The current password is wrong
        '404':
          description: Not in password mode (or shared mode)
        '409':
          description: RUSTYBIN_SETTINGS_LOCK holds the sign-in settings
      x-rustybin-permission: signed in
  /_rustybin/me/password:
    put:
      tags:
      - Control Plane
      summary: Change my password
      description: 'The caller''s own password, with the current one: 8 to 256 characters, not the default. The first admin''s is stored in the sign-in settings (console.password_hash), anyone else''s in the user store. The caller''s other sessions end and a fresh token is answered. 400 not_a_user for a credential that is not a user''s.'
      operationId: changeMyPassword
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - current_password
              - new_password
              properties:
                current_password:
                  type: string
                new_password:
                  type: string
                  minLength: 8
                  maxLength: 256
      responses:
        '200':
          description: Changed
          content:
            application/json:
              schema:
                type: object
                properties:
                  changed:
                    type: boolean
                  user:
                    type: string
                  signed_out:
                    type: integer
                  token:
                    type: string
                  expires_at:
                    type: string
                    format: date-time
        '400':
          description: The new password breaks a rule, or not a user
        '403':
          description: The current password is wrong
        '404':
          description: Not in password mode (or shared mode)
        '429':
          description: Too many wrong current passwords
      x-rustybin-permission: signed in
  /_rustybin/join:
    post:
      tags:
      - Control Plane
      summary: Accept an invitation
      description: 'The token of an invitation link (/ui/#/join/<token>) and the new password (8 to 256 characters, not the default). One use, 72 hours. Answers a sign-in token for the user, so the person is signed in. Never behind control-plane auth; rate limited per client address (5 failures a minute) and for every address together (20 a minute, the invitations'' own lane: failures here never hold back a sign-in).'
      operationId: acceptInvitation
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - token
              - password
              properties:
                token:
                  type: string
                  description: rbi_ plus 43 base64url characters
                password:
                  type: string
                  minLength: 8
                  maxLength: 256
      responses:
        '200':
          description: Joined and signed in
          content:
            application/json:
              schema:
                type: object
                properties:
                  token:
                    type: string
                    description: 'A control token (HS256 JWT): send it as Authorization: Bearer'
                  token_type:
                    type: string
                    enum:
                    - Bearer
                  expires_in:
                    type: integer
                  expires_at:
                    type: string
                    format: date-time
                  scopes:
                    type: array
                    items:
                      type: string
                  user:
                    type: string
                  user_id:
                    type: string
                    description: The user's id in the user store (usr_ plus 16 hex)
                  default_password:
                    type: boolean
                    description: The instance still signs in with the default password
        '400':
          description: The password breaks a rule (the invitation is not used)
        '401':
          description: Not a valid invitation (reason invalid_invitation)
        '404':
          description: Not in password mode (or shared mode)
        '429':
          description: Too many attempts from this address (Retry-After)
      x-rustybin-permission: open
  /_rustybin/users:
    get:
      tags:
      - Control Plane
      summary: Users
      operationId: listUsers
      description: Every user with roles, status, the last sign-in, the open invitation (never its link) and the sessions listed. Password sign-in only.
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties:
                  users:
                    type: array
                    items:
                      type: object
                      properties:
                        id:
                          type: string
                          description: usr_ plus 16 hex
                        name:
                          type: string
                          description: The sign-in name
                        display_name:
                          type: string
                        email:
                          type:
                          - string
                          - 'null'
                        roles:
                          type: array
                          items:
                            type: string
                        disabled:
                          type: boolean
                        status:
                          type: string
                          enum:
                          - active
                          - invited
                          - disabled
                        source:
                          type: string
                          enum:
                          - password
                          - sso
                          - document
                        created_at:
                          type: string
                          format: date-time
                        last_sign_in:
                          type:
                          - string
                          - 'null'
                          format: date-time
                        invitation:
                          type:
                          - object
                          - 'null'
                          description: The open invitation (never its link)
                          properties:
                            created_at:
                              type: string
                              format: date-time
                            expires_at:
                              type: string
                              format: date-time
                            by:
                              type: string
                            reset:
                              type: boolean
                        sessions:
                          type: integer
                          description: Sign-in sessions listed for the user
                        first_admin:
                          type: boolean
                          description: 'User one: the console user of the sign-in settings'
                        projects:
                          type: array
                          description: 'The user''s roles in projects (sso: given by a single sign-on group)'
                          items:
                            type: object
                            properties:
                              project:
                                type: string
                              roles:
                                type: array
                                items:
                                  type: string
                              sso:
                                type: boolean
                  count:
                    type: integer
                  capacity:
                    type: integer
                  open_invitations:
                    type: integer
        '404':
          description: No such user, or not in password mode
        '403':
          description: 'permission_required: the caller lacks users.read'
      x-rustybin-permission: users.read
    post:
      tags:
      - Control Plane
      summary: Invite a user
      operationId: inviteUser
      description: 'Creates the user, invited (disabled until they accept), and answers the one-time link (72 hours, shown once, kept only as a SHA-256). The person opens it and sets their own password: nobody else ever sees or types it.'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - name
              properties:
                name:
                  type: string
                  description: 'The sign-in name: letters, digits and . _ @ -, at most 64'
                display_name:
                  type: string
                email:
                  type: string
                roles:
                  type: array
                  items:
                    type: string
                  description: 'Instance roles: at least one, unless projects gives roles in a project'
                projects:
                  type: array
                  description: Roles in projects (step 11)
                  items:
                    type: object
                    required:
                    - project
                    - roles
                    properties:
                      project:
                        type: string
                      roles:
                        type: array
                        items:
                          type: string
                        minItems: 1
      responses:
        '201':
          description: Invited
          content:
            application/json:
              schema:
                type: object
                properties:
                  user:
                    type: object
                    properties:
                      id:
                        type: string
                        description: usr_ plus 16 hex
                      name:
                        type: string
                        description: The sign-in name
                      display_name:
                        type: string
                      email:
                        type:
                        - string
                        - 'null'
                      roles:
                        type: array
                        items:
                          type: string
                      disabled:
                        type: boolean
                      status:
                        type: string
                        enum:
                        - active
                        - invited
                        - disabled
                      source:
                        type: string
                        enum:
                        - password
                        - sso
                        - document
                      created_at:
                        type: string
                        format: date-time
                      last_sign_in:
                        type:
                        - string
                        - 'null'
                        format: date-time
                      invitation:
                        type:
                        - object
                        - 'null'
                        description: The open invitation (never its link)
                        properties:
                          created_at:
                            type: string
                            format: date-time
                          expires_at:
                            type: string
                            format: date-time
                          by:
                            type: string
                          reset:
                            type: boolean
                      sessions:
                        type: integer
                        description: Sign-in sessions listed for the user
                      first_admin:
                        type: boolean
                        description: 'User one: the console user of the sign-in settings'
                      projects:
                        type: array
                        description: 'The user''s roles in projects (sso: given by a single sign-on group)'
                        items:
                          type: object
                          properties:
                            project:
                              type: string
                            roles:
                              type: array
                              items:
                                type: string
                            sso:
                              type: boolean
                  invitation:
                    type: object
                    properties:
                      url:
                        type: string
                        description: '<instance>/ui/#/join/<token>: send it to the person; shown once'
                      token:
                        type: string
                        description: rbi_ plus 43 base64url characters (32 random bytes)
                      expires_at:
                        type: string
                        format: date-time
                      reset:
                        type: boolean
        '400':
          description: Invalid
        '404':
          description: No such user, or not in password mode
        '409':
          description: A guard rail (reason last_admin, first_admin, invited, role_in_use, full)
        '403':
          description: 'permission_required: the caller lacks users.manage'
      x-rustybin-permission: users.manage
  /_rustybin/users/{id}:
    get:
      tags:
      - Control Plane
      summary: A user
      operationId: getUser
      parameters:
      - name: id
        in: path
        required: true
        description: A user id or sign-in name
        schema:
          type: string
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties:
                  id:
                    type: string
                    description: usr_ plus 16 hex
                  name:
                    type: string
                    description: The sign-in name
                  display_name:
                    type: string
                  email:
                    type:
                    - string
                    - 'null'
                  roles:
                    type: array
                    items:
                      type: string
                  disabled:
                    type: boolean
                  status:
                    type: string
                    enum:
                    - active
                    - invited
                    - disabled
                  source:
                    type: string
                    enum:
                    - password
                    - sso
                    - document
                  created_at:
                    type: string
                    format: date-time
                  last_sign_in:
                    type:
                    - string
                    - 'null'
                    format: date-time
                  invitation:
                    type:
                    - object
                    - 'null'
                    description: The open invitation (never its link)
                    properties:
                      created_at:
                        type: string
                        format: date-time
                      expires_at:
                        type: string
                        format: date-time
                      by:
                        type: string
                      reset:
                        type: boolean
                  sessions:
                    type: integer
                    description: Sign-in sessions listed for the user
                  first_admin:
                    type: boolean
                    description: 'User one: the console user of the sign-in settings'
                  projects:
                    type: array
                    description: 'The user''s roles in projects (sso: given by a single sign-on group)'
                    items:
                      type: object
                      properties:
                        project:
                          type: string
                        roles:
                          type: array
                          items:
                            type: string
                        sso:
                          type: boolean
        '404':
          description: No such user, or not in password mode
        '403':
          description: 'permission_required: the caller lacks users.read'
      x-rustybin-permission: users.read
    patch:
      tags:
      - Control Plane
      summary: Change a user
      operationId: updateUser
      parameters:
      - name: id
        in: path
        required: true
        description: A user id or sign-in name
        schema:
          type: string
      description: Roles, display name, email (null clears it), disabled. Disabling ends the user's sessions. Nobody changes their own roles; the last enabled admin stays.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                roles:
                  type: array
                  items:
                    type: string
                  minItems: 1
                display_name:
                  type: string
                email:
                  type:
                  - string
                  - 'null'
                disabled:
                  type: boolean
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties:
                  id:
                    type: string
                    description: usr_ plus 16 hex
                  name:
                    type: string
                    description: The sign-in name
                  display_name:
                    type: string
                  email:
                    type:
                    - string
                    - 'null'
                  roles:
                    type: array
                    items:
                      type: string
                  disabled:
                    type: boolean
                  status:
                    type: string
                    enum:
                    - active
                    - invited
                    - disabled
                  source:
                    type: string
                    enum:
                    - password
                    - sso
                    - document
                  created_at:
                    type: string
                    format: date-time
                  last_sign_in:
                    type:
                    - string
                    - 'null'
                    format: date-time
                  invitation:
                    type:
                    - object
                    - 'null'
                    description: The open invitation (never its link)
                    properties:
                      created_at:
                        type: string
                        format: date-time
                      expires_at:
                        type: string
                        format: date-time
                      by:
                        type: string
                      reset:
                        type: boolean
                  sessions:
                    type: integer
                    description: Sign-in sessions listed for the user
                  first_admin:
                    type: boolean
                    description: 'User one: the console user of the sign-in settings'
                  projects:
                    type: array
                    description: 'The user''s roles in projects (sso: given by a single sign-on group)'
                    items:
                      type: object
                      properties:
                        project:
                          type: string
                        roles:
                          type: array
                          items:
                            type: string
                        sso:
                          type: boolean
        '400':
          description: Invalid
        '404':
          description: No such user, or not in password mode
        '409':
          description: A guard rail (reason last_admin, first_admin, invited, role_in_use, full)
        '403':
          description: 'permission_required: the caller lacks users.manage'
      x-rustybin-permission: users.manage
    delete:
      tags:
      - Control Plane
      summary: Delete a user
      operationId: deleteUser
      parameters:
      - name: id
        in: path
        required: true
        description: A user id or sign-in name
        schema:
          type: string
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties:
                  deleted:
                    type: string
                  sessions_ended:
                    type: integer
        '404':
          description: No such user, or not in password mode
        '409':
          description: A guard rail (reason last_admin, first_admin, invited, role_in_use, full)
        '403':
          description: 'permission_required: the caller lacks users.manage'
      x-rustybin-permission: users.manage
  /_rustybin/users/{id}/invite:
    post:
      tags:
      - Control Plane
      summary: A new invitation link (a password reset)
      operationId: reinviteUser
      parameters:
      - name: id
        in: path
        required: true
        description: A user id or sign-in name
        schema:
          type: string
      responses:
        '201':
          description: A new link
          content:
            application/json:
              schema:
                type: object
                properties:
                  user:
                    type: object
                    properties:
                      id:
                        type: string
                        description: usr_ plus 16 hex
                      name:
                        type: string
                        description: The sign-in name
                      display_name:
                        type: string
                      email:
                        type:
                        - string
                        - 'null'
                      roles:
                        type: array
                        items:
                          type: string
                      disabled:
                        type: boolean
                      status:
                        type: string
                        enum:
                        - active
                        - invited
                        - disabled
                      source:
                        type: string
                        enum:
                        - password
                        - sso
                        - document
                      created_at:
                        type: string
                        format: date-time
                      last_sign_in:
                        type:
                        - string
                        - 'null'
                        format: date-time
                      invitation:
                        type:
                        - object
                        - 'null'
                        description: The open invitation (never its link)
                        properties:
                          created_at:
                            type: string
                            format: date-time
                          expires_at:
                            type: string
                            format: date-time
                          by:
                            type: string
                          reset:
                            type: boolean
                      sessions:
                        type: integer
                        description: Sign-in sessions listed for the user
                      first_admin:
                        type: boolean
                        description: 'User one: the console user of the sign-in settings'
                      projects:
                        type: array
                        description: 'The user''s roles in projects (sso: given by a single sign-on group)'
                        items:
                          type: object
                          properties:
                            project:
                              type: string
                            roles:
                              type: array
                              items:
                                type: string
                            sso:
                              type: boolean
                  invitation:
                    type: object
                    properties:
                      url:
                        type: string
                        description: '<instance>/ui/#/join/<token>: send it to the person; shown once'
                      token:
                        type: string
                        description: rbi_ plus 43 base64url characters (32 random bytes)
                      expires_at:
                        type: string
                        format: date-time
                      reset:
                        type: boolean
        '404':
          description: No such user, or not in password mode
        '409':
          description: A guard rail (reason last_admin, first_admin, invited, role_in_use, full)
        '403':
          description: 'permission_required: the caller lacks users.manage'
      x-rustybin-permission: users.manage
  /_rustybin/users/{id}/sessions:
    get:
      tags:
      - Control Plane
      summary: A user's sessions
      operationId: listUserSessions
      parameters:
      - name: id
        in: path
        required: true
        description: A user id or sign-in name
        schema:
          type: string
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties:
                  count:
                    type: integer
                  sessions:
                    type: array
                    items:
                      type: object
        '404':
          description: No such user, or not in password mode
        '403':
          description: 'permission_required: the caller lacks users.read'
      x-rustybin-permission: users.read
    delete:
      tags:
      - Control Plane
      summary: Sign a user out everywhere
      operationId: endUserSessions
      parameters:
      - name: id
        in: path
        required: true
        description: A user id or sign-in name
        schema:
          type: string
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties:
                  ended:
                    type: integer
        '404':
          description: No such user, or not in password mode
        '403':
          description: 'permission_required: the caller lacks users.manage'
      x-rustybin-permission: users.manage
  /_rustybin/roles:
    get:
      tags:
      - Control Plane
      summary: Roles
      operationId: listRoles
      description: The built-in roles, then the custom ones, each with its permissions and how many users hold it, and the permission table (name, group, summary).
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties:
                  roles:
                    type: array
                    items:
                      type: object
                      properties:
                        name:
                          type: string
                        description:
                          type: string
                        built_in:
                          type: boolean
                        privileged:
                          type: boolean
                          description: Grants users.manage or settings.security
                        permissions:
                          type: array
                          items:
                            type: string
                        users:
                          type: integer
                          description: Users holding it
                  permissions:
                    type: array
                    items:
                      type: object
        '403':
          description: 'permission_required: the caller lacks users.read'
      x-rustybin-permission: users.read
    post:
      tags:
      - Control Plane
      summary: Create a custom role
      operationId: createRole
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - permissions
              properties:
                name:
                  type: string
                  description: 1 to 40 lower case letters, digits, _ and -, starting with a letter; not a built-in name
                description:
                  type: string
                  maxLength: 200
                permissions:
                  type: array
                  items:
                    type: string
                  minItems: 1
                  description: Permission names (GET /_rustybin/roles lists them)
      responses:
        '201':
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties:
                  name:
                    type: string
                  description:
                    type: string
                  built_in:
                    type: boolean
                  privileged:
                    type: boolean
                    description: Grants users.manage or settings.security
                  permissions:
                    type: array
                    items:
                      type: string
                  users:
                    type: integer
                    description: Users holding it
        '400':
          description: Invalid, or a built-in name
        '409':
          description: A role of that name exists, or the cap
        '403':
          description: 'permission_required: the caller lacks users.manage'
      x-rustybin-permission: users.manage
  /_rustybin/roles/{name}:
    get:
      tags:
      - Control Plane
      summary: A role
      operationId: getRole
      parameters:
      - name: name
        in: path
        required: true
        schema:
          type: string
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties:
                  name:
                    type: string
                  description:
                    type: string
                  built_in:
                    type: boolean
                  privileged:
                    type: boolean
                    description: Grants users.manage or settings.security
                  permissions:
                    type: array
                    items:
                      type: string
                  users:
                    type: integer
                    description: Users holding it
        '404':
          description: No such role
        '403':
          description: 'permission_required: the caller lacks users.read'
      x-rustybin-permission: users.read
    put:
      tags:
      - Control Plane
      summary: Create or change a custom role
      operationId: putRole
      parameters:
      - name: name
        in: path
        required: true
        schema:
          type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - permissions
              properties:
                name:
                  type: string
                  description: 1 to 40 lower case letters, digits, _ and -, starting with a letter; not a built-in name
                description:
                  type: string
                  maxLength: 200
                permissions:
                  type: array
                  items:
                    type: string
                  minItems: 1
                  description: Permission names (GET /_rustybin/roles lists them)
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties:
                  name:
                    type: string
                  description:
                    type: string
                  built_in:
                    type: boolean
                  privileged:
                    type: boolean
                    description: Grants users.manage or settings.security
                  permissions:
                    type: array
                    items:
                      type: string
                  users:
                    type: integer
                    description: Users holding it
        '400':
          description: Invalid, or a built-in role
        '403':
          description: 'permission_required: the caller lacks users.manage'
      x-rustybin-permission: users.manage
    delete:
      tags:
      - Control Plane
      summary: Delete a custom role
      operationId: deleteRole
      parameters:
      - name: name
        in: path
        required: true
        schema:
          type: string
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties:
                  deleted:
                    type: string
        '404':
          description: No such custom role
        '409':
          description: Somebody holds it (role_in_use)
        '403':
          description: 'permission_required: the caller lacks users.manage'
      x-rustybin-permission: users.manage
  /_rustybin/me:
    get:
      tags:
      - Control Plane
      summary: My account
      operationId: getMe
      description: The caller's own user, roles, permissions and sessions count. 400 not_a_user for a credential that is not a user's.
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties:
                  id:
                    type: string
                    description: usr_ plus 16 hex
                  name:
                    type: string
                    description: The sign-in name
                  display_name:
                    type: string
                  email:
                    type:
                    - string
                    - 'null'
                  roles:
                    type: array
                    items:
                      type: string
                  disabled:
                    type: boolean
                  status:
                    type: string
                    enum:
                    - active
                    - invited
                    - disabled
                  source:
                    type: string
                    enum:
                    - password
                    - sso
                    - document
                  created_at:
                    type: string
                    format: date-time
                  last_sign_in:
                    type:
                    - string
                    - 'null'
                    format: date-time
                  invitation:
                    type:
                    - object
                    - 'null'
                    description: The open invitation (never its link)
                    properties:
                      created_at:
                        type: string
                        format: date-time
                      expires_at:
                        type: string
                        format: date-time
                      by:
                        type: string
                      reset:
                        type: boolean
                  sessions:
                    type: integer
                    description: Sign-in sessions listed for the user
                  first_admin:
                    type: boolean
                    description: 'User one: the console user of the sign-in settings'
                  projects:
                    type: array
                    description: 'The user''s roles in projects (sso: given by a single sign-on group)'
                    items:
                      type: object
                      properties:
                        project:
                          type: string
                        roles:
                          type: array
                          items:
                            type: string
                        sso:
                          type: boolean
        '400':
          description: Not a user (reason not_a_user)
        '404':
          description: Not in password mode
      x-rustybin-permission: signed in
    patch:
      tags:
      - Control Plane
      summary: Change my display name or email
      operationId: updateMe
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                display_name:
                  type: string
                email:
                  type:
                  - string
                  - 'null'
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties:
                  id:
                    type: string
                    description: usr_ plus 16 hex
                  name:
                    type: string
                    description: The sign-in name
                  display_name:
                    type: string
                  email:
                    type:
                    - string
                    - 'null'
                  roles:
                    type: array
                    items:
                      type: string
                  disabled:
                    type: boolean
                  status:
                    type: string
                    enum:
                    - active
                    - invited
                    - disabled
                  source:
                    type: string
                    enum:
                    - password
                    - sso
                    - document
                  created_at:
                    type: string
                    format: date-time
                  last_sign_in:
                    type:
                    - string
                    - 'null'
                    format: date-time
                  invitation:
                    type:
                    - object
                    - 'null'
                    description: The open invitation (never its link)
                    properties:
                      created_at:
                        type: string
                        format: date-time
                      expires_at:
                        type: string
                        format: date-time
                      by:
                        type: string
                      reset:
                        type: boolean
                  sessions:
                    type: integer
                    description: Sign-in sessions listed for the user
                  first_admin:
                    type: boolean
                    description: 'User one: the console user of the sign-in settings'
                  projects:
                    type: array
                    description: 'The user''s roles in projects (sso: given by a single sign-on group)'
                    items:
                      type: object
                      properties:
                        project:
                          type: string
                        roles:
                          type: array
                          items:
                            type: string
                        sso:
                          type: boolean
        '400':
          description: Invalid, or not a user
        '404':
          description: Not in password mode
      x-rustybin-permission: signed in
  /_rustybin/me/sessions:
    get:
      tags:
      - Control Plane
      summary: My sessions
      operationId: listMySessions
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties:
                  count:
                    type: integer
                  sessions:
                    type: array
                    items:
                      type: object
        '400':
          description: Not a user
        '404':
          description: Not in password mode
      x-rustybin-permission: signed in
    delete:
      tags:
      - Control Plane
      summary: Sign myself out everywhere
      operationId: endMySessions
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties:
                  ended:
                    type: integer
        '400':
          description: Not a user
        '404':
          description: Not in password mode
      x-rustybin-permission: signed in
  /_rustybin/me/sessions/{id}:
    delete:
      tags:
      - Control Plane
      summary: End one of my sessions
      operationId: endMySession
      parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties:
                  ended:
                    type: boolean
        '400':
          description: Not a user
        '404':
          description: Not in password mode
      x-rustybin-permission: signed in
  /_rustybin/projects:
    get:
      tags:
      - Control Plane
      summary: Projects
      operationId: listProjects
      responses:
        '200':
          description: The projects the caller may see
          content:
            application/json:
              schema:
                type: object
                properties:
                  count:
                    type: integer
                  capacity:
                    type: integer
                  projects:
                    type: array
                    items:
                      type: object
                      properties:
                        name:
                          type: string
                        description:
                          type: string
                        created_at:
                          type: string
                          format: date-time
                        member_count:
                          type: integer
                        members:
                          type: array
                          description: With users.read
                          items:
                            type: object
                            properties:
                              user:
                                type:
                                - string
                                - 'null'
                              user_id:
                                type: string
                              roles:
                                type: array
                                items:
                                  type: string
                        your_roles:
                          type: array
                          items:
                            type: string
                        your_permissions:
                          type: array
                          items:
                            type: string
                        resources:
                          type: object
                          properties:
                            mocks:
                              type: array
                              items:
                                type: string
                            tests:
                              type: array
                              items:
                                type: string
                            checkpoints:
                              type: array
                              items:
                                type: string
      x-rustybin-permission: signed in
  /_rustybin/projects/{name}:
    get:
      tags:
      - Control Plane
      summary: A project
      operationId: getProject
      parameters:
      - name: name
        in: path
        required: true
        schema:
          type: string
      responses:
        '200':
          description: The project
          content:
            application/json:
              schema:
                type: object
                properties:
                  name:
                    type: string
                  description:
                    type: string
                  created_at:
                    type: string
                    format: date-time
                  member_count:
                    type: integer
                  members:
                    type: array
                    description: With users.read
                    items:
                      type: object
                      properties:
                        user:
                          type:
                          - string
                          - 'null'
                        user_id:
                          type: string
                        roles:
                          type: array
                          items:
                            type: string
                  your_roles:
                    type: array
                    items:
                      type: string
                  your_permissions:
                    type: array
                    items:
                      type: string
                  resources:
                    type: object
                    properties:
                      mocks:
                        type: array
                        items:
                          type: string
                      tests:
                        type: array
                        items:
                          type: string
                      checkpoints:
                        type: array
                        items:
                          type: string
        '404':
          description: No such project, or one the caller may not see
      x-rustybin-permission: signed in
    put:
      tags:
      - Control Plane
      summary: Create or change a project
      operationId: saveProject
      parameters:
      - name: name
        in: path
        required: true
        schema:
          type: string
      requestBody:
        required: false
        content:
          application/json:
            schema:
              type: object
              additionalProperties: false
              properties:
                description:
                  type: string
                  maxLength: 200
                members:
                  type: array
                  description: The whole member list
                  items:
                    type: object
                    required:
                    - user
                    - roles
                    additionalProperties: false
                    properties:
                      user:
                        type: string
                        description: An id or a sign-in name
                      roles:
                        type: array
                        items:
                          type: string
      responses:
        '200':
          description: Changed
          content:
            application/json:
              schema:
                type: object
                properties:
                  name:
                    type: string
                  description:
                    type: string
                  created_at:
                    type: string
                    format: date-time
                  member_count:
                    type: integer
                  members:
                    type: array
                    description: With users.read
                    items:
                      type: object
                      properties:
                        user:
                          type:
                          - string
                          - 'null'
                        user_id:
                          type: string
                        roles:
                          type: array
                          items:
                            type: string
                  your_roles:
                    type: array
                    items:
                      type: string
                  your_permissions:
                    type: array
                    items:
                      type: string
                  resources:
                    type: object
                    properties:
                      mocks:
                        type: array
                        items:
                          type: string
                      tests:
                        type: array
                        items:
                          type: string
                      checkpoints:
                        type: array
                        items:
                          type: string
        '201':
          description: Created
          content:
            application/json:
              schema:
                type: object
                properties:
                  name:
                    type: string
                  description:
                    type: string
                  created_at:
                    type: string
                    format: date-time
                  member_count:
                    type: integer
                  members:
                    type: array
                    description: With users.read
                    items:
                      type: object
                      properties:
                        user:
                          type:
                          - string
                          - 'null'
                        user_id:
                          type: string
                        roles:
                          type: array
                          items:
                            type: string
                  your_roles:
                    type: array
                    items:
                      type: string
                  your_permissions:
                    type: array
                    items:
                      type: string
                  resources:
                    type: object
                    properties:
                      mocks:
                        type: array
                        items:
                          type: string
                      tests:
                        type: array
                        items:
                          type: string
                      checkpoints:
                        type: array
                        items:
                          type: string
        '400':
          description: An invalid name, description or member
        '403':
          description: users.manage, own_roles, or a role beyond the caller's permissions in the project
      x-rustybin-permission: users.manage
    delete:
      tags:
      - Control Plane
      summary: Delete a project
      operationId: deleteProject
      parameters:
      - name: name
        in: path
        required: true
        schema:
          type: string
      - name: move_to
        in: query
        required: false
        description: 'Where its mocks, saved tests and checkpoints go: another project, or instance'
        schema:
          type: string
      responses:
        '200':
          description: Deleted (and what moved)
          content:
            application/json:
              schema:
                type: object
        '404':
          description: No such project
        '409':
          description: 'not_empty: the project holds resources and no move_to was given'
        '403':
          description: 'permission_required: the caller lacks users.manage'
      x-rustybin-permission: users.manage
  /_rustybin/sso/status:
    get:
      tags:
      - Control Plane
      summary: Single sign-on status
      description: 'What the sign-in screen needs: whether single sign-on is on and ready, the button''s name and whether the password sign-in works. Never a secret; nothing is fetched. Open.'
      operationId: getSsoStatus
      security: []
      responses:
        '200':
          description: The status
          content:
            application/json:
              schema:
                type: object
                properties:
                  enabled:
                    type: boolean
                    description: sso.enabled
                  ready:
                    type: boolean
                    description: Enabled, with the issuer and the client id set
                  name:
                    type:
                    - string
                    - 'null'
                    description: 'The button''s name: Sign in with <name>'
                  password_sign_in:
                    type: boolean
                    description: The password sign-in works (off only while single sign-on is ready and sso.password_sign_in is off)
                  reason:
                    type:
                    - string
                    - 'null'
                    description: Why it is not ready
                  start:
                    type: string
        '404':
          description: Not in the password sign-in (RUSTYBIN_CONTROL_AUTH=password, outside shared mode)
      x-rustybin-permission: open
  /_rustybin/sso/start:
    get:
      tags:
      - Control Plane
      summary: Start a single sign-on
      description: 'Sends the browser to the identity provider''s authorization endpoint (authorization code flow, PKCE S256, state, nonce). return_to: console, or an http:// address on a loopback host (the CLI). challenge: the S256 challenge of a verifier the client keeps. Open; at most 10 sign-ins under way per client address. A refusal goes back to return_to (the console''s #/sso-error/<reason>, or the loopback address with error and error_description).'
      operationId: startSso
      security: []
      parameters:
      - name: return_to
        in: query
        required: false
        schema:
          type: string
          default: console
      - name: challenge
        in: query
        required: true
        schema:
          type: string
          minLength: 43
          maxLength: 43
      responses:
        '302':
          description: To the provider (or back to return_to with the refusal)
        '400':
          description: return_to or challenge is not valid
        '404':
          description: Not in the password sign-in (RUSTYBIN_CONTROL_AUTH=password, outside shared mode)
      x-rustybin-permission: open
  /_rustybin/sso/callback:
    get:
      tags:
      - Control Plane
      summary: Single sign-on callback
      description: 'The provider''s redirect (register it as the client''s redirect URI). The code is exchanged at the token endpoint, the ID token verified (issuer, audience, expiry, nonce; RS256 or ES256), the groups mapped to roles, the user linked or created, and the browser sent back to return_to with a one-time code (the console''s #/sso/<code>, or ?code= on the loopback address). The provider''s tokens are never kept. Open; throttled per client address like the sign-in. Refusal reasons: expired, denied, provider_error, invalid_id_token, domain_not_allowed, email_unverified, not_linked (no user this sign-in may use: a password account or another identity has the name, a user created ahead without the verified email, or none with sso.provision off; one reason for all, the detail is in the log), disabled, no_role, last_admin, rate_limited, busy.'
      operationId: ssoCallback
      security: []
      parameters:
      - name: code
        in: query
        required: false
        schema:
          type: string
      - name: state
        in: query
        required: true
        schema:
          type: string
      - name: error
        in: query
        required: false
        schema:
          type: string
      responses:
        '302':
          description: Back to return_to, with the code or the refusal
        '400':
          description: 'No sign-in under way for this state: an HTML page'
        '404':
          description: Not in the password sign-in (RUSTYBIN_CONTROL_AUTH=password, outside shared mode)
        '429':
          description: 'Too many failures from this address: an HTML page'
      x-rustybin-permission: open
  /_rustybin/sso/token:
    post:
      tags:
      - Control Plane
      summary: 'Single sign-on: the code for a sign-in token'
      description: The one-time code the callback sent back and the PKCE verifier whose challenge started the sign-in. One use, two minutes. Answers the sign-in token (as POST /_rustybin/login). Open; throttled per client address.
      operationId: ssoToken
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - code
              - verifier
              properties:
                code:
                  type: string
                  description: rbs_ plus 43 base64url characters
                verifier:
                  type: string
                  minLength: 43
                  maxLength: 128
      responses:
        '200':
          description: Signed in
          content:
            application/json:
              schema:
                type: object
                properties:
                  token:
                    type: string
                  token_type:
                    type: string
                  expires_in:
                    type: integer
                  expires_at:
                    type: string
                    format: date-time
                  user:
                    type: string
                  user_id:
                    type: string
                  obtained:
                    type: string
                    enum:
                    - sso
        '401':
          description: Not a valid code for this verifier (reason invalid_code)
        '404':
          description: Not in the password sign-in (RUSTYBIN_CONTROL_AUTH=password, outside shared mode)
        '429':
          description: Too many failures from this address (Retry-After)
      x-rustybin-permission: open
  /_rustybin/sso/test:
    post:
      tags:
      - Control Plane
      summary: Test single sign-on
      description: Fetches the provider's discovery document and keys now (purpose sso of the outbound policy) and answers them, the redirect URI to register at the provider, the role map and the roles it names that do not exist. Never the client secret.
      operationId: testSso
      responses:
        '200':
          description: 'The test (ok: false with error when the provider could not be read)'
          content:
            application/json:
              schema:
                type: object
                properties:
                  ok:
                    type: boolean
                  error:
                    type: string
                  redirect_uri:
                    type: string
                  discovery:
                    type: object
                  keys:
                    type: array
                    items:
                      type: object
                  unknown_roles:
                    type: array
                    items:
                      type: string
        '404':
          description: Not in the password sign-in (RUSTYBIN_CONTROL_AUTH=password, outside shared mode)
        '403':
          description: 'permission_required: the caller lacks settings.write'
      x-rustybin-permission: settings.write
  /_rustybin/assist:
    get:
      tags:
      - Control Plane
      summary: AI assist status
      description: Whether AI assist is on (a provider and, except for the mock, a key), the effective models, the providers with their curated models and key state ({set, hint}), the tasks, the static suggestions per console view, the usage totals and the privacy note.
      operationId: getAssist
      responses:
        '200':
          description: Status
          content:
            application/json:
              schema:
                type: object
        '403':
          description: 'permission_required: the caller lacks assist.use'
      x-rustybin-permission: assist.use
  /_rustybin/assist/test:
    post:
      tags:
      - Control Plane
      summary: Test the AI provider
      description: 'One short call ("Reply with the single word OK") to the configured provider: latency, model and the answer, or the provider''s error. Admin.'
      operationId: testAssist
      responses:
        '200':
          description: The provider answered
          content:
            application/json:
              schema:
                type: object
                properties:
                  ok:
                    type: boolean
                  provider:
                    type: string
                  model:
                    type: string
                  latency_ms:
                    type: integer
                  answer:
                    type: string
        '502':
          description: The provider refused or failed
        '503':
          description: No provider or key is set
        '403':
          description: 'permission_required: the caller lacks settings.write'
      x-rustybin-permission: settings.write
  /_rustybin/assist/run:
    post:
      tags:
      - Control Plane
      summary: Run an AI assist task
      description: 'Runs a task with validated inputs. Streamed (stream true, or Accept: text/event-stream): events `sent` (what went to the provider, never the key), `delta` ({text}), `json` (the validated structured answer), `action` (an action a text answer proposed: open_view, copy, run_request), `usage` (tokens and estimated cost), `error`, `done`. Otherwise one JSON document {task, text, json, actions, usage, sent}. Tasks that change the instance need admin; the others the console scope.'
      operationId: runAssist
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - task
              - inputs
              properties:
                task:
                  type: string
                  enum:
                  - free_answer
                  - explain
                  - suggest
                inputs:
                  type: object
                model:
                  type: string
                  description: A model id instead of the task's default
                stream:
                  type: boolean
      responses:
        '200':
          description: The answer (SSE or JSON)
        '400':
          description: Invalid inputs
        '404':
          description: Unknown task
        '422':
          description: The inputs contain a stored secret or the admin token
        '429':
          description: The caller's calls per minute (Retry-After)
        '502':
          description: The provider failed (JSON mode)
        '503':
          description: No provider or key is set
        '403':
          description: 'permission_required: the caller lacks assist.use'
      x-rustybin-permission: assist.use
  /_rustybin/assist/models:
    get:
      tags:
      - Control Plane
      summary: AI assist models
      description: 'The configured provider''s models: the list last loaded from the provider (POST) merged with the curated table. Each model: id, name, tier (fast or capable), context window, output limit, list price per million tokens when known, note, whether it reasons before answering, whether it is curated and whether the provider listed it, the jobs it is chosen for and whether it is the model or the fast model. Plus the jobs: every task with its model and whether a setting chose it.'
      operationId: getAssistModels
      responses:
        '200':
          description: The models and the jobs
          content:
            application/json:
              schema:
                type: object
        '403':
          description: 'permission_required: the caller lacks assist.use'
      x-rustybin-permission: assist.use
    post:
      tags:
      - Control Plane
      summary: Load the provider's models
      description: 'Asks the configured provider for its model list with the stored key (OpenAI and compatible endpoints: GET /models; Anthropic: GET /v1/models; Gemini: GET /v1beta/models, generateContent models only; the mock: its own), through the outbound policy, keeps at most 500 entries, and answers as GET does. Admin.'
      operationId: loadAssistModels
      responses:
        '200':
          description: The models and the jobs
        '502':
          description: The provider refused or failed
        '503':
          description: No provider or key is set
        '403':
          description: 'permission_required: the caller lacks settings.write'
      x-rustybin-permission: settings.write
  /_rustybin/assist/usage:
    get:
      tags:
      - Control Plane
      summary: AI assist usage
      description: Calls, errors, tokens and the estimated cost since start, in total, per provider and model, and per task. Admin.
      operationId: getAssistUsage
      responses:
        '200':
          description: Usage
          content:
            application/json:
              schema:
                type: object
        '403':
          description: 'permission_required: the caller lacks settings.read'
      x-rustybin-permission: settings.read
  /_rustybin/config:
    get:
      tags:
      - Control Plane
      summary: Effective configuration
      description: The running configuration without secrets (the admin token is reported only as `admin_token_configured`).
      operationId: getRustybinConfig
      responses:
        '200':
          description: Configuration
          content:
            application/json:
              schema:
                type: object
        '403':
          description: 'permission_required: the caller lacks instance.read'
      x-rustybin-permission: instance.read
  /_rustybin/version:
    get:
      tags:
      - Control Plane
      summary: Version information
      operationId: getRustybinVersion
      responses:
        '200':
          description: Version
          content:
            application/json:
              schema:
                type: object
                properties:
                  name:
                    type: string
                  version:
                    type: string
                  git_sha:
                    type:
                    - string
                    - 'null'
                    description: Commit the binary was built from, when known
                  rust_version:
                    type: string
                  profile:
                    type: string
                  control_auth:
                    type: string
                    enum:
                    - password
                    - open
                    - token
                    - jwt
                  hosted_mode:
                    type: boolean
                  grpc_on_http:
                    type: boolean
        '403':
          description: 'permission_required: the caller lacks instance.read'
      x-rustybin-permission: instance.read
  /_rustybin/ready:
    get:
      tags:
      - Control Plane
      summary: Readiness probe
      description: 200 while the process serves HTTP, independent of the `/health` demo toggle. Exempt from control-plane auth, API keys, plan limits and inspector capture. With API keys (RUSTYBIN_SHARED_MODE, RUSTYBIN_REQUIRE_KEY) 503 until the key registry was loaded once.
      operationId: getRustybinReady
      security: []
      responses:
        '200':
          description: Ready
          content:
            application/json:
              schema:
                type: object
                properties:
                  status:
                    type: string
                    enum:
                    - ready
        '503':
          description: 'Not ready: the key registry was never loaded'
          content:
            application/json:
              schema:
                type: object
                properties:
                  status:
                    type: string
                    enum:
                    - not_ready
                  reason:
                    type: string
                    enum:
                    - rustybin_keys_unavailable
      x-rustybin-permission: open
  /_rustybin/settings:
    get:
      tags:
      - Control Plane
      summary: Instance settings
      description: Every registered setting with its value, where it comes from (`stored`, `env`, `default`) and whether RUSTYBIN_SETTINGS_LOCK holds it. Secrets are `{set, hint, source, unreadable}`. Admin.
      operationId: getRustybinSettings
      responses:
        '200':
          description: Settings
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RustybinSettings'
        '401':
          description: Admin token required
        '403':
          description: Disabled in public mode without an admin token
      x-rustybin-permission: settings.read
    patch:
      tags:
      - Control Plane
      summary: Change settings
      description: Values are validated against each setting's schema; a `null` value forgets the stored one. A secret value is sealed at rest; `null` clears it. Admin. In password mode a change of console.user or console.password_hash also needs current_password (or the admin token itself), and signs every session out.
      operationId: patchRustybinSettings
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                values:
                  type: object
                  additionalProperties: {}
                  description: Non-secret settings by key
                secrets:
                  type: object
                  additionalProperties:
                    type:
                    - string
                    - 'null'
                  description: Secret settings by key
                current_password:
                  type: string
                  description: 'The console password: needed in password mode to change console.user or console.password_hash'
      responses:
        '200':
          description: The new settings, with `changed` listing the keys that changed
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RustybinSettings'
        '400':
          description: Unknown keys or invalid values (`problems`)
        '401':
          description: Admin token required
        '403':
          description: 'The sign-in changes only with the current password (`reason`: current_password_required, invalid_credentials)'
        '409':
          description: Locked by RUSTYBIN_SETTINGS_LOCK (`locked`)
        '429':
          description: 'Too many failed password checks (`reason`: rate_limited)'
      x-rustybin-permission: settings.write
  /_rustybin/settings/{key}:
    delete:
      tags:
      - Control Plane
      summary: Forget a stored setting
      description: In password mode console.user and console.password_hash are reset here only with the admin token itself (the operator's recovery); anyone else sends PATCH with current_password.
      operationId: deleteRustybinSetting
      parameters:
      - name: key
        in: path
        required: true
        schema:
          type: string
        example: console.title
      responses:
        '200':
          description: The settings after the reset
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RustybinSettings'
        '400':
          description: Invalid (`problems`), the sign-in without the admin token included
        '404':
          description: No such setting
        '409':
          description: Locked by RUSTYBIN_SETTINGS_LOCK
        '403':
          description: 'permission_required: the caller lacks settings.write'
      x-rustybin-permission: settings.write
  /_rustybin/diagnostics:
    get:
      tags:
      - Control Plane
      summary: Instance diagnostics
      description: Every check with its id, status (ok, warn, fail), message and fix (never a credential), and the worst status. Admin.
      operationId: getRustybinDiagnostics
      responses:
        '200':
          description: The checks
          content:
            application/json:
              schema:
                type: object
        '401':
          description: Admin token required
        '403':
          description: 'permission_required: the caller lacks settings.read'
      x-rustybin-permission: settings.read
  /_rustybin/scenarios:
    get:
      tags:
      - Control Plane
      summary: Gateway scenarios and the caller's runs
      operationId: listGatewayScenarios
      responses:
        '200':
          description: The catalogue and the runs
          content:
            application/json:
              schema:
                type: object
        '403':
          description: 'permission_required: the caller lacks instance.read'
      x-rustybin-permission: instance.read
  /_rustybin/scenarios/{id}/runs:
    post:
      tags:
      - Control Plane
      summary: Start a scenario run (admin)
      description: Creates the run's upstream (a mock that is never saved and ends with the run, or a /flaky route) and answers the gateway setup. At most 32 runs; a run lasts an hour.
      operationId: startGatewayScenario
      parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
          enum:
          - retries
          - rate_limiting
          - credential_injection
          - timeouts
          - caching
          - circuit_breaker
          - request_size_limit
          - cors
          - header_transformation
      responses:
        '201':
          description: The run
          content:
            application/json:
              schema:
                type: object
        '404':
          description: No such scenario
        '429':
          description: Too many runs
        '403':
          description: 'permission_required: the caller lacks scenarios.run'
      x-rustybin-permission: scenarios.run
  /_rustybin/scenarios/{id}/runs/{run}:
    get:
      tags:
      - Control Plane
      summary: A scenario run
      operationId: getGatewayScenarioRun
      parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
          enum:
          - retries
          - rate_limiting
          - credential_injection
          - timeouts
          - caching
          - circuit_breaker
          - request_size_limit
          - cors
          - header_transformation
      - name: run
        in: path
        required: true
        schema:
          type: string
      responses:
        '200':
          description: The run with its setup and last verdict
          content:
            application/json:
              schema:
                type: object
        '404':
          description: No such run in this session
        '403':
          description: 'permission_required: the caller lacks instance.read'
      x-rustybin-permission: instance.read
    delete:
      tags:
      - Control Plane
      summary: End a scenario run (admin)
      description: Removes the run's mock and resets its flaky counters; nothing else.
      operationId: endGatewayScenarioRun
      parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
          enum:
          - retries
          - rate_limiting
          - credential_injection
          - timeouts
          - caching
          - circuit_breaker
          - request_size_limit
          - cors
          - header_transformation
      - name: run
        in: path
        required: true
        schema:
          type: string
      responses:
        '200':
          description: What was removed
          content:
            application/json:
              schema:
                type: object
        '404':
          description: No such run in this session
        '409':
          description: A check is running
        '403':
          description: 'permission_required: the caller lacks scenarios.run'
      x-rustybin-permission: scenarios.run
  /_rustybin/scenarios/{id}/runs/{run}/check:
    post:
      tags:
      - Control Plane
      summary: Check a scenario through the gateway (admin)
      description: Sends the run's requests to gateway_url plus the scenario's path (through the egress policy, purpose scenario), then compares the answers with the upstream requests they caused.
      operationId: checkGatewayScenario
      parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
          enum:
          - retries
          - rate_limiting
          - credential_injection
          - timeouts
          - caching
          - circuit_breaker
          - request_size_limit
          - cors
          - header_transformation
      - name: run
        in: path
        required: true
        schema:
          type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - gateway_url
              additionalProperties: false
              properties:
                gateway_url:
                  type: string
                  description: The URL the gateway serves the route at
                limit:
                  type: integer
                  minimum: 1
                  maximum: 20
                  default: 5
                  description: 'rate_limiting only: The limit set on the gateway: the check sends twice as many requests (1 to 20, default 5)'
                half_open_ms:
                  type: integer
                  minimum: 100
                  maximum: 60000
                  default: 10000
                  description: 'circuit_breaker only: How long the gateway keeps the circuit open before it lets a trial request through, in milliseconds: the check waits that long (100 to 60000, default 10000)'
                size_limit:
                  type: integer
                  minimum: 16
                  maximum: 1048576
                  default: 1048576
                  description: 'request_size_limit only: The request body limit set on the gateway, in bytes: the check sends a body half as large and one twice as large (16 to 1048576, default 1048576)'
                allowed_origin:
                  type: string
                  format: origin
                  maxLength: 200
                  default: https://app.example.com
                  description: 'cors only: An origin the gateway''s CORS policy allows: the check sends a preflight and a GET from it (an origin scheme://host[:port], at most 200 characters, default https://app.example.com)'
                refused_origin:
                  type: string
                  format: origin
                  maxLength: 200
                  default: https://elsewhere.example.net
                  description: 'cors only: An origin the gateway''s CORS policy does not allow: the check sends a preflight from it (an origin scheme://host[:port], at most 200 characters, default https://elsewhere.example.net)'
                upstream_cors:
                  type: boolean
                  default: false
                  description: 'cors only: Whether the gateway lets preflights through to the upstream, which answers CORS for allowed_origin (the run''s upstream does so then), instead of answering them itself (true or false, default false)'
                add_request_header:
                  type: string
                  format: header-name
                  maxLength: 64
                  default: X-Gateway-Added
                  description: 'header_transformation only: A request header the gateway adds on the way to the upstream (a header name, at most 64 characters, default X-Gateway-Added)'
                add_request_value:
                  type: string
                  maxLength: 200
                  description: 'header_transformation only: The value the gateway gives add_request_header; any value passes when not given (optional, at most 200 visible characters)'
                remove_request_header:
                  type: string
                  format: header-name
                  maxLength: 64
                  default: X-Client-Internal
                  description: 'header_transformation only: A request header the gateway removes before the upstream: the check sends it (a header name, at most 64 characters, default X-Client-Internal)'
                add_response_header:
                  type: string
                  format: header-name
                  maxLength: 64
                  default: X-Gateway-Response
                  description: 'header_transformation only: A response header the gateway adds on the way back to the client (a header name, at most 64 characters, default X-Gateway-Response)'
      responses:
        '200':
          description: The verdict
          content:
            application/json:
              schema:
                type: object
        '400':
          description: Invalid body
        '403':
          description: The egress policy refused the gateway URL
        '404':
          description: No such run in this session
        '409':
          description: A check is running
      x-rustybin-permission: scenarios.run
  /_rustybin/tests:
    get:
      tags:
      - Control Plane
      summary: Saved tests
      operationId: listSavedTests
      parameters:
      - name: project
        in: query
        required: false
        description: 'One project''s tests (instance: the instance''s)'
        schema:
          type: string
      responses:
        '200':
          description: The tests with the variables each uses
          content:
            application/json:
              schema:
                type: object
        '403':
          description: 'permission_required: the caller lacks tests.read'
      x-rustybin-permission: tests.read
    post:
      tags:
      - Control Plane
      summary: Save a test (admin)
      description: 'From a captured request (capture: its inspector id; secret-looking headers and query values become {{variables}}) or a written request, with assertions (default for a capture: its status). project: the project it belongs to (tests.write there). At most 200 tests, 64 KiB each.'
      operationId: saveTest
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
      responses:
        '201':
          description: The saved test
          content:
            application/json:
              schema:
                type: object
        '400':
          description: Invalid test
        '404':
          description: No such capture you may read
        '409':
          description: A test of that name exists
        '403':
          description: 'permission_required: the caller lacks tests.write'
      x-rustybin-permission: tests.write
  /_rustybin/tests/{name}:
    get:
      tags:
      - Control Plane
      summary: A saved test
      operationId: getSavedTest
      parameters:
      - name: name
        in: path
        required: true
        schema:
          type: string
      responses:
        '200':
          description: The test
          content:
            application/json:
              schema:
                type: object
        '404':
          description: No such test
        '403':
          description: 'permission_required: the caller lacks tests.read'
      x-rustybin-permission: tests.read
    put:
      tags:
      - Control Plane
      summary: Save or replace a test (admin)
      operationId: putSavedTest
      parameters:
      - name: name
        in: path
        required: true
        schema:
          type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
      responses:
        '200':
          description: The saved test
          content:
            application/json:
              schema:
                type: object
        '400':
          description: Invalid test
        '403':
          description: 'permission_required: the caller lacks tests.write'
      x-rustybin-permission: tests.write
    delete:
      tags:
      - Control Plane
      summary: Delete a saved test (admin)
      operationId: deleteSavedTest
      parameters:
      - name: name
        in: path
        required: true
        schema:
          type: string
      responses:
        '200':
          description: Deleted
          content:
            application/json:
              schema:
                type: object
        '404':
          description: No such test
        '403':
          description: 'permission_required: the caller lacks tests.write'
      x-rustybin-permission: tests.write
  /_rustybin/tests/run:
    post:
      tags:
      - Control Plane
      summary: Run saved tests (admin)
      description: 'Every test, or names; variables fill {{name}} references (else RUSTYBIN_TEST_* environment variables). Tests without a gateway run in process with the caller''s session; gateway tests go through the egress policy (purpose replay). At most 200 requests and 60 seconds a run; one run at a time (409 while another goes). Every run is kept in the history: the answer has its run_id (the X-Rustybin-Test-Run header with JUnit) and the change since the previous run. X-Rustybin-Client (console or cli) labels the run''s trigger, else api.'
      operationId: runSavedTests
      parameters:
      - name: format
        in: query
        required: false
        schema:
          type: string
          enum:
          - json
          - junit
      requestBody:
        required: false
        content:
          application/json:
            schema:
              type: object
              additionalProperties: false
              properties:
                names:
                  type: array
                  items:
                    type: string
                variables:
                  type: object
                  additionalProperties:
                    type: string
      responses:
        '200':
          description: The report (JSON, or JUnit XML)
          content:
            application/json:
              schema:
                type: object
            application/xml:
              schema:
                type: string
        '403':
          description: The egress policy refused a gateway
        '404':
          description: No such test
        '409':
          description: Another run is going
      x-rustybin-permission: tests.run
  /_rustybin/tests/runs:
    get:
      tags:
      - Control Plane
      summary: The history of test runs (admin)
      description: 'Every run of the saved tests (console, CLI, API, MCP), newest first: id, ran_at, trigger, principal (never a credential), the counts, the failing tests and the change since the previous run. At most 200 runs and 30 days (50 in memory without a state directory). Variables given to a run are never kept.'
      operationId: listTestRuns
      parameters:
      - name: limit
        in: query
        required: false
        schema:
          type: integer
          minimum: 1
          maximum: 100
          default: 20
      - name: cursor
        in: query
        required: false
        schema:
          type: string
        description: The next_cursor of the page before
      responses:
        '200':
          description: 'A page: runs, next_cursor, total'
          content:
            application/json:
              schema:
                type: object
        '403':
          description: 'permission_required: the caller lacks tests.read'
      x-rustybin-permission: tests.read
    delete:
      tags:
      - Control Plane
      summary: Forget every test run (admin)
      operationId: clearTestRuns
      responses:
        '200':
          description: 'cleared: the number of runs forgotten'
          content:
            application/json:
              schema:
                type: object
        '403':
          description: 'permission_required: the caller lacks tests.write'
      x-rustybin-permission: tests.write
  /_rustybin/tests/runs/{id}:
    get:
      tags:
      - Control Plane
      summary: A kept test run (admin)
      operationId: getTestRun
      parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
      - name: format
        in: query
        required: false
        schema:
          type: string
          enum:
          - json
          - junit
      responses:
        '200':
          description: 'The run: the report, run_id, trigger, principal, selection, change (JSON, or JUnit XML)'
          content:
            application/json:
              schema:
                type: object
            application/xml:
              schema:
                type: string
        '404':
          description: No such run
        '403':
          description: 'permission_required: the caller lacks tests.read'
      x-rustybin-permission: tests.read
  /_rustybin/tests/export:
    get:
      tags:
      - Control Plane
      summary: Saved tests as Hurl
      operationId: exportSavedTests
      responses:
        '200':
          description: A Hurl file ({{base_url}} for in-process tests)
          content:
            text/plain:
              schema:
                type: string
        '403':
          description: 'permission_required: the caller lacks tests.read'
      x-rustybin-permission: tests.read
  /_rustybin/tests/schedules:
    get:
      tags:
      - Control Plane
      summary: Test schedules (admin)
      description: 'Every schedule with its status: running, next_due, last_result (passed, failed or skipped), last_at, last_detail, last_run_id, last_passed, runs, skipped, last_notice (event, result: pending, retrying, delivered, failed, refused or dropped) and the recent outcomes.'
      operationId: listTestSchedules
      responses:
        '200':
          description: schedules, max_schedules, pending_notices
          content:
            application/json:
              schema:
                type: object
        '403':
          description: 'permission_required: the caller lacks tests.read'
      x-rustybin-permission: tests.read
    post:
      tags:
      - Control Plane
      summary: Create a test schedule (admin)
      description: name, the tests (names, or prefix, or neither for every saved test), every (5m to 24h, aligned to multiples since the Unix epoch) or at (HH:MM, UTC), enabled, webhook (posted under the egress purpose notify, which has no default) and notify_recovery. At most 20 schedules. Variables come from RUSTYBIN_TEST_* only.
      operationId: createTestSchedule
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              additionalProperties: false
              required:
              - name
              properties:
                name:
                  type: string
                  maxLength: 64
                  pattern: ^[A-Za-z0-9._-]+$
                description:
                  type: string
                  maxLength: 200
                names:
                  type: array
                  items:
                    type: string
                    maxLength: 64
                    pattern: ^[A-Za-z0-9._-]+$
                  maxItems: 200
                prefix:
                  type: string
                  maxLength: 64
                  pattern: ^[A-Za-z0-9._-]+$
                every:
                  type: string
                  pattern: ^[0-9]+[smh]$
                  description: 5m to 24h
                at:
                  type: string
                  pattern: ^[0-2][0-9]:[0-5][0-9]$
                  description: HH:MM, UTC
                enabled:
                  type: boolean
                  default: true
                webhook:
                  type: string
                  format: uri
                  description: Posted a JSON summary when a run fails (egress purpose notify)
                notify_recovery:
                  type: boolean
                  default: false
      responses:
        '201':
          description: The schedule with its status
          content:
            application/json:
              schema:
                type: object
        '400':
          description: Invalid schedule
        '409':
          description: A schedule of that name exists
        '507':
          description: 20 schedules already
        '403':
          description: 'permission_required: the caller lacks tests.write'
      x-rustybin-permission: tests.write
  /_rustybin/tests/schedules/{name}:
    get:
      tags:
      - Control Plane
      summary: A test schedule (admin)
      operationId: getTestSchedule
      parameters:
      - name: name
        in: path
        required: true
        schema:
          type: string
      responses:
        '200':
          description: The schedule with its status
          content:
            application/json:
              schema:
                type: object
        '404':
          description: No such schedule
        '403':
          description: 'permission_required: the caller lacks tests.read'
      x-rustybin-permission: tests.read
    put:
      tags:
      - Control Plane
      summary: Save or replace a test schedule (admin)
      operationId: putTestSchedule
      description: The whole schedule (the name comes from the path); a replaced schedule keeps its status.
      parameters:
      - name: name
        in: path
        required: true
        schema:
          type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
      responses:
        '200':
          description: The schedule with its status
          content:
            application/json:
              schema:
                type: object
        '400':
          description: Invalid schedule
        '507':
          description: 20 schedules already
        '403':
          description: 'permission_required: the caller lacks tests.write'
      x-rustybin-permission: tests.write
    delete:
      tags:
      - Control Plane
      summary: Delete a test schedule (admin)
      operationId: deleteTestSchedule
      parameters:
      - name: name
        in: path
        required: true
        schema:
          type: string
      responses:
        '200':
          description: Deleted
          content:
            application/json:
              schema:
                type: object
        '404':
          description: No such schedule
        '403':
          description: 'permission_required: the caller lacks tests.write'
      x-rustybin-permission: tests.write
  /_rustybin/tests/schedules/{name}/run:
    post:
      tags:
      - Control Plane
      summary: Run a test schedule now (admin)
      description: Runs the schedule's tests as the scheduler would (trigger schedule:<name>, kept in the history, the notice queued when it fails); its next time stays. 409 while another run of the saved tests goes or the harness sends load.
      operationId: runTestSchedule
      parameters:
      - name: name
        in: path
        required: true
        schema:
          type: string
      responses:
        '200':
          description: result (passed, failed or skipped), detail, run (the kept run, as POST /_rustybin/tests/run answers it), schedule (with its status)
          content:
            application/json:
              schema:
                type: object
        '404':
          description: No such schedule
        '409':
          description: Another run goes, or the harness sends load
        '403':
          description: 'permission_required: the caller lacks tests.run'
      x-rustybin-permission: tests.run
  /_rustybin/checkpoints:
    get:
      tags:
      - Control Plane
      summary: Checkpoints
      operationId: listCheckpoints
      description: This instance's checkpoints; from=git lists the configuration repository's instead (the files under <git.path>/checkpoints/ at the working copy's HEAD, after a pull; admin), each read within the 16 MiB cap and checked, or listed with the reason it cannot be restored.
      parameters:
      - name: from
        in: query
        required: false
        schema:
          type: string
          enum:
          - instance
          - git
          default: instance
      - name: project
        in: query
        required: false
        description: 'One project''s checkpoints (instance: the instance''s)'
        schema:
          type: string
      responses:
        '200':
          description: The checkpoints and their scope
          content:
            application/json:
              schema:
                type: object
        '409':
          description: 'from=git: source control is not configured or connected'
        '403':
          description: 'permission_required: the caller lacks mocks.read'
      x-rustybin-permission: mocks.read
    post:
      tags:
      - Control Plane
      summary: Save a checkpoint (admin)
      operationId: saveCheckpoint
      description: Saves the instance's API mocks (deployed revision, a draft that differs, on or off, stateful data) and the health toggle. Never visitors' or keys' mocks, file mocks, settings or secrets. At most 20.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - name
              additionalProperties: false
              properties:
                name:
                  type: string
                replace:
                  type: boolean
                project:
                  type: string
                  description: 'Keep this project''s mocks only (a restore changes only those, never the health toggle); absent: the instance''s API mocks, or the one project the caller works in'
      responses:
        '201':
          description: Saved
          content:
            application/json:
              schema:
                type: object
        '409':
          description: A checkpoint of that name exists
        '403':
          description: 'permission_required: the caller lacks checkpoints.write'
      x-rustybin-permission: checkpoints.write
  /_rustybin/checkpoints/{name}:
    get:
      tags:
      - Control Plane
      summary: A checkpoint
      operationId: getCheckpoint
      parameters:
      - name: name
        in: path
        required: true
        schema:
          type: string
      responses:
        '200':
          description: What it holds
          content:
            application/json:
              schema:
                type: object
        '404':
          description: No such checkpoint
        '403':
          description: 'permission_required: the caller lacks mocks.read'
      x-rustybin-permission: mocks.read
    delete:
      tags:
      - Control Plane
      summary: Delete a checkpoint (admin)
      operationId: deleteCheckpoint
      parameters:
      - name: name
        in: path
        required: true
        schema:
          type: string
      responses:
        '200':
          description: Deleted
          content:
            application/json:
              schema:
                type: object
        '404':
          description: No such checkpoint
        '403':
          description: 'permission_required: the caller lacks checkpoints.write'
      x-rustybin-permission: checkpoints.write
  /_rustybin/checkpoints/{name}/restore:
    post:
      tags:
      - Control Plane
      summary: Restore a checkpoint (admin)
      operationId: restoreCheckpoint
      description: 'Brings the instance''s API mocks back as saved (created, replaced, data restored, unchanged; mocks the checkpoint does not hold deleted), sets the health toggle and resets the flaky counters. dry_run answers the same without changing anything. from=git restores the configuration repository''s checkpoint of that name (at the working copy''s HEAD): untrusted input, read within the size cap, checked and compiled like a local one.'
      parameters:
      - name: name
        in: path
        required: true
        schema:
          type: string
      - name: dry_run
        in: query
        required: false
        schema:
          type: boolean
      - name: from
        in: query
        required: false
        schema:
          type: string
          enum:
          - instance
          - git
          default: instance
      responses:
        '200':
          description: What changed
          content:
            application/json:
              schema:
                type: object
        '404':
          description: No such checkpoint
        '413':
          description: 'from=git: the file is larger than a checkpoint may be'
        '422':
          description: 'from=git: the file is not a valid checkpoint'
        '403':
          description: 'permission_required: the caller lacks checkpoints.restore'
      x-rustybin-permission: checkpoints.restore
  /_rustybin/egress/check:
    get:
      tags:
      - Control Plane
      summary: Dry check of a URL against the outbound policy
      description: What the policy would say to `url` for `purpose`, without calling it. The addresses a name resolves to are checked again when a call is made.
      operationId: checkRustybinEgress
      parameters:
      - name: url
        in: query
        required: true
        schema:
          type: string
      - name: purpose
        in: query
        required: false
        schema:
          type: string
          default: mcp_relay
      responses:
        '200':
          description: The answer
          content:
            application/json:
              schema:
                type: object
                properties:
                  url:
                    type: string
                  origin:
                    type: string
                  purpose:
                    type: string
                  allowed:
                    type: boolean
                  reasons:
                    type: array
                    items:
                      type: string
                  rule:
                    type: object
                  note:
                    type: string
        '400':
          description: No url, or an unknown purpose
        '401':
          description: Admin token required
        '403':
          description: 'permission_required: the caller lacks settings.read'
      x-rustybin-permission: settings.read
  /_rustybin/egress:
    get:
      tags:
      - Control Plane
      summary: Outbound connection policy
      description: The allowlist as the instance sees it, where it comes from, each feature's own rule, and the last 100 decisions (newest first). Admin.
      operationId: getRustybinEgress
      responses:
        '200':
          description: Policy
          content:
            application/json:
              schema:
                type: object
                properties:
                  allow:
                    type: array
                    items:
                      type: string
                  allow_source:
                    type: string
                    enum:
                    - stored
                    - env
                    - default
                  allow_loopback:
                    type: boolean
                  allow_loopback_source:
                    type: string
                    enum:
                    - stored
                    - env
                    - default
                  public_mode:
                    type: boolean
                  warnings:
                    type: array
                    items:
                      type: string
                  always_refused:
                    type: array
                    items:
                      type: string
                  purposes:
                    type: array
                    items:
                      type: object
                      properties:
                        purpose:
                          type: string
                        title:
                          type: string
                        configured_by:
                          type: string
                        allow:
                          type: array
                          items:
                            type: string
                        any:
                          type: boolean
                        loopback:
                          type: boolean
                  decisions:
                    type: array
                    items:
                      type: object
                      properties:
                        at:
                          type: string
                          format: date-time
                        purpose:
                          type: string
                        origin:
                          type: string
                        addrs:
                          type: array
                          items:
                            type: string
                        allowed:
                          type: boolean
                        reason:
                          type: string
        '401':
          description: Admin token required
        '403':
          description: 'permission_required: the caller lacks settings.read'
      x-rustybin-permission: settings.read
  /_rustybin/harness:
    get:
      tags:
      - Harness
      summary: Harness node status
      description: 'Roles, caps, engine, allowlist, the active run. `enabled: false` when harness mode is off.'
      operationId: getHarnessStatus
      responses:
        '200':
          description: Status
          content:
            application/json:
              schema:
                type: object
        '403':
          description: 'permission_required: the caller lacks harness.read'
      x-rustybin-permission: harness.read
  /_rustybin/harness/schema:
    get:
      tags:
      - Harness
      summary: Scenario JSON Schema
      description: JSON Schema (draft 2020-12) of a scenario, with this node's caps as bounds (`x-caps`).
      operationId: getHarnessSchema
      responses:
        '200':
          description: Schema
          content:
            application/schema+json:
              schema:
                type: object
        '403':
          description: 'permission_required: the caller lacks harness.read'
      x-rustybin-permission: harness.read
  /_rustybin/harness/scenarios:
    get:
      tags:
      - Harness
      summary: Built-in scenarios
      operationId: listHarnessScenarios
      responses:
        '200':
          description: Scenarios with their summaries
          content:
            application/json:
              schema:
                type: object
        '403':
          description: 'permission_required: the caller lacks harness.read'
      x-rustybin-permission: harness.read
  /_rustybin/harness/scenarios/{name}:
    get:
      tags:
      - Harness
      summary: One built-in scenario
      operationId: getHarnessScenario
      parameters:
      - name: name
        in: path
        required: true
        schema:
          type: string
      - name: format
        in: query
        required: false
        schema:
          type: string
          enum:
          - json
          - yaml
      responses:
        '200':
          description: The scenario
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/HarnessScenario'
            application/yaml:
              schema:
                type: string
        '404':
          description: Unknown scenario
        '403':
          description: 'permission_required: the caller lacks harness.read'
      x-rustybin-permission: harness.read
    put:
      tags:
      - Harness
      summary: Save a scenario
      description: Validated against this node's caps and kept under the name (at most 100; RUSTYBIN_HARNESS_STATE_DIR writes them to disk). Built-in names are taken.
      operationId: saveHarnessScenario
      parameters:
      - name: name
        in: path
        required: true
        schema:
          type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/HarnessScenario'
          application/yaml:
            schema:
              type: string
      responses:
        '201':
          description: Saved
          content:
            application/json:
              schema:
                type: object
        '200':
          description: Replaced
          content:
            application/json:
              schema:
                type: object
        '400':
          description: Invalid scenario or name
        '409':
          description: A built-in name
        '404':
          description: Harness mode is off (RUSTYBIN_HARNESS=on), or no such run
        '403':
          description: 'permission_required: the caller lacks harness.run'
      x-rustybin-permission: harness.run
    delete:
      tags:
      - Harness
      summary: Delete a saved scenario
      operationId: deleteHarnessScenario
      parameters:
      - name: name
        in: path
        required: true
        schema:
          type: string
      responses:
        '204':
          description: Deleted
        '409':
          description: A built-in scenario
        '404':
          description: Harness mode is off (RUSTYBIN_HARNESS=on), or no such run
        '403':
          description: 'permission_required: the caller lacks harness.run'
      x-rustybin-permission: harness.run
  /_rustybin/harness/validate:
    post:
      tags:
      - Harness
      summary: Validate a scenario
      description: Parses and validates a scenario against this node's caps and returns every problem, the normalised scenario, its hash and summary. Sends nothing.
      operationId: validateHarnessScenario
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/HarnessScenario'
          application/yaml:
            schema:
              type: string
      responses:
        '200':
          description: Validation result (`valid`, `problems`, `scenario`, `summary`)
          content:
            application/json:
              schema:
                type: object
        '400':
          description: Unparseable scenario
        '403':
          description: 'permission_required: the caller lacks harness.read'
      x-rustybin-permission: harness.read
  /_rustybin/harness/runs:
    get:
      tags:
      - Harness
      summary: Runs on this node
      operationId: listHarnessRuns
      responses:
        '200':
          description: Runs
          content:
            application/json:
              schema:
                type: object
        '404':
          description: Harness mode is off (RUSTYBIN_HARNESS=on), or no such run
        '403':
          description: 'permission_required: the caller lacks harness.read'
      x-rustybin-permission: harness.read
    post:
      tags:
      - Harness
      summary: Start a run
      description: Validates the scenario, pins and (optionally) verifies its targets and starts the run. One run at a time per node (409). Targets outside RUSTYBIN_HARNESS_TARGETS are refused (403).
      operationId: createHarnessRun
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/HarnessScenario'
          application/yaml:
            schema:
              type: string
      responses:
        '202':
          description: The run (live view), with a Location header
          content:
            application/json:
              schema:
                type: object
        '400':
          description: Invalid scenario (`problems`)
        '403':
          description: No client role, a target not allowed, or verification failed
        '409':
          description: A run is active
        '404':
          description: Harness mode is off (RUSTYBIN_HARNESS=on), or no such run
      x-rustybin-permission: harness.run
  /_rustybin/harness/runs/{run}:
    get:
      tags:
      - Harness
      summary: Live stats of a run
      operationId: getHarnessRun
      parameters:
      - name: run
        in: path
        required: true
        schema:
          type: string
      responses:
        '200':
          description: Live view
          content:
            application/json:
              schema:
                type: object
        '404':
          description: Harness mode is off (RUSTYBIN_HARNESS=on), or no such run
        '403':
          description: 'permission_required: the caller lacks harness.read'
      x-rustybin-permission: harness.read
    patch:
      tags:
      - Harness
      summary: Change a running run
      description: Checked like a new scenario (the caps included), applied on the next scheduler pass, recorded in the run's log and report.
      operationId: changeHarnessRun
      parameters:
      - name: run
        in: path
        required: true
        schema:
          type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              description: 'A live change: rate_multiplier, pause, resume, hold, skip_stage, set, add_layer, remove_layer, and layer to aim one (the schema is $defs.change of GET /_rustybin/harness/schema).'
      responses:
        '200':
          description: The run's controls after the change
          content:
            application/json:
              schema:
                type: object
        '400':
          description: Refused (`problems`)
        '409':
          description: Finished, or a cluster run (change it on the leader)
        '404':
          description: Harness mode is off (RUSTYBIN_HARNESS=on), or no such run
        '403':
          description: 'permission_required: the caller lacks harness.run'
      x-rustybin-permission: harness.run
  /_rustybin/harness/runs/{run}/stop:
    post:
      tags:
      - Harness
      summary: Stop a run
      operationId: stopHarnessRun
      parameters:
      - name: run
        in: path
        required: true
        schema:
          type: string
      responses:
        '202':
          description: Stopping (drains, then reports)
        '200':
          description: Already finished
        '404':
          description: Harness mode is off (RUSTYBIN_HARNESS=on), or no such run
        '403':
          description: 'permission_required: the caller lacks harness.run'
      x-rustybin-permission: harness.run
  /_rustybin/harness/runs/{run}/report:
    get:
      tags:
      - Harness
      summary: Final report
      operationId: getHarnessReport
      parameters:
      - name: run
        in: path
        required: true
        schema:
          type: string
      - name: format
        in: query
        required: false
        schema:
          type: string
          enum:
          - json
          - text
          - junit
      responses:
        '200':
          description: The report
          content:
            application/json:
              schema:
                type: object
            text/plain:
              schema:
                type: string
            application/xml:
              schema:
                type: string
        '409':
          description: The run has not finished
        '404':
          description: Harness mode is off (RUSTYBIN_HARNESS=on), or no such run
        '403':
          description: 'permission_required: the caller lacks harness.read'
      x-rustybin-permission: harness.read
  /_rustybin/harness/stop:
    post:
      tags:
      - Harness
      summary: Kill switch
      description: Stops every run on this node now.
      operationId: harnessKillSwitch
      responses:
        '200':
          description: Stopped runs
          content:
            application/json:
              schema:
                type: object
        '404':
          description: Harness mode is off (RUSTYBIN_HARNESS=on), or no such run
        '403':
          description: 'permission_required: the caller lacks harness.run'
      x-rustybin-permission: harness.run
  /harness:
    get:
      tags:
      - Harness
      summary: Shaped upstream
      description: 'Answers with the run''s server profile (scenario `servers`: log-normal latency, status mix, padded JSON body, chunked streaming, first-byte delay) when the request carries a verified harness marker, else 200 with a 256 byte body.'
      operationId: getShapedRootGET
      responses:
        '200':
          description: The shaped answer (any configured status)
          content:
            application/json:
              schema:
                type: object
    post:
      tags:
      - Harness
      summary: Shaped upstream
      description: 'Answers with the run''s server profile (scenario `servers`: log-normal latency, status mix, padded JSON body, chunked streaming, first-byte delay) when the request carries a verified harness marker, else 200 with a 256 byte body.'
      operationId: getShapedRootPOST
      responses:
        '200':
          description: The shaped answer (any configured status)
          content:
            application/json:
              schema:
                type: object
    put:
      tags:
      - Harness
      summary: Shaped upstream
      description: 'Answers with the run''s server profile (scenario `servers`: log-normal latency, status mix, padded JSON body, chunked streaming, first-byte delay) when the request carries a verified harness marker, else 200 with a 256 byte body.'
      operationId: getShapedRootPUT
      responses:
        '200':
          description: The shaped answer (any configured status)
          content:
            application/json:
              schema:
                type: object
    patch:
      tags:
      - Harness
      summary: Shaped upstream
      description: 'Answers with the run''s server profile (scenario `servers`: log-normal latency, status mix, padded JSON body, chunked streaming, first-byte delay) when the request carries a verified harness marker, else 200 with a 256 byte body.'
      operationId: getShapedRootPATCH
      responses:
        '200':
          description: The shaped answer (any configured status)
          content:
            application/json:
              schema:
                type: object
    delete:
      tags:
      - Harness
      summary: Shaped upstream
      description: 'Answers with the run''s server profile (scenario `servers`: log-normal latency, status mix, padded JSON body, chunked streaming, first-byte delay) when the request carries a verified harness marker, else 200 with a 256 byte body.'
      operationId: getShapedRootDELETE
      responses:
        '200':
          description: The shaped answer (any configured status)
          content:
            application/json:
              schema:
                type: object
  /harness/{path}:
    get:
      tags:
      - Harness
      summary: Shaped upstream
      description: 'Answers with the run''s server profile (scenario `servers`: log-normal latency, status mix, padded JSON body, chunked streaming, first-byte delay) when the request carries a verified harness marker, else 200 with a 256 byte body.'
      operationId: getShapedPathGET
      responses:
        '200':
          description: The shaped answer (any configured status)
          content:
            application/json:
              schema:
                type: object
      parameters:
      - name: path
        in: path
        required: true
        schema:
          type: string
    post:
      tags:
      - Harness
      summary: Shaped upstream
      description: 'Answers with the run''s server profile (scenario `servers`: log-normal latency, status mix, padded JSON body, chunked streaming, first-byte delay) when the request carries a verified harness marker, else 200 with a 256 byte body.'
      operationId: getShapedPathPOST
      responses:
        '200':
          description: The shaped answer (any configured status)
          content:
            application/json:
              schema:
                type: object
      parameters:
      - name: path
        in: path
        required: true
        schema:
          type: string
    put:
      tags:
      - Harness
      summary: Shaped upstream
      description: 'Answers with the run''s server profile (scenario `servers`: log-normal latency, status mix, padded JSON body, chunked streaming, first-byte delay) when the request carries a verified harness marker, else 200 with a 256 byte body.'
      operationId: getShapedPathPUT
      responses:
        '200':
          description: The shaped answer (any configured status)
          content:
            application/json:
              schema:
                type: object
      parameters:
      - name: path
        in: path
        required: true
        schema:
          type: string
    patch:
      tags:
      - Harness
      summary: Shaped upstream
      description: 'Answers with the run''s server profile (scenario `servers`: log-normal latency, status mix, padded JSON body, chunked streaming, first-byte delay) when the request carries a verified harness marker, else 200 with a 256 byte body.'
      operationId: getShapedPathPATCH
      responses:
        '200':
          description: The shaped answer (any configured status)
          content:
            application/json:
              schema:
                type: object
      parameters:
      - name: path
        in: path
        required: true
        schema:
          type: string
    delete:
      tags:
      - Harness
      summary: Shaped upstream
      description: 'Answers with the run''s server profile (scenario `servers`: log-normal latency, status mix, padded JSON body, chunked streaming, first-byte delay) when the request carries a verified harness marker, else 200 with a 256 byte body.'
      operationId: getShapedPathDELETE
      responses:
        '200':
          description: The shaped answer (any configured status)
          content:
            application/json:
              schema:
                type: object
      parameters:
      - name: path
        in: path
        required: true
        schema:
          type: string
  /.well-known/rustybin-harness:
    get:
      tags:
      - Harness
      summary: Harness verification token
      operationId: getHarnessVerificationToken
      security: []
      responses:
        '200':
          description: The token
          content:
            text/plain:
              schema:
                type: string
        '404':
          description: Harness mode is off (RUSTYBIN_HARNESS=on), or no such run
  /_rustybin/harness/join:
    post:
      tags:
      - Harness
      summary: Join a cluster
      description: Tells this node to join the cluster at `url` with `join_token` (orchestrated). The URL must not resolve to a forbidden address.
      operationId: joinHarnessCluster
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - url
              - join_token
              properties:
                url:
                  type: string
                join_token:
                  type: string
                advertise:
                  type: string
      responses:
        '200':
          description: Joined
          content:
            application/json:
              schema:
                type: object
        '400':
          description: Invalid body or URL
        '403':
          description: Forbidden join address
        '409':
          description: Already a member, or the leader refused
        '404':
          description: Harness mode is off (RUSTYBIN_HARNESS=on), or no such run
      x-rustybin-permission: harness.cluster
  /_rustybin/harness/leave:
    post:
      tags:
      - Harness
      summary: Leave the cluster
      operationId: leaveHarnessCluster
      responses:
        '200':
          description: Left
          content:
            application/json:
              schema:
                type: object
        '404':
          description: Harness mode is off (RUSTYBIN_HARNESS=on), or no such run
        '403':
          description: 'permission_required: the caller lacks harness.cluster'
      x-rustybin-permission: harness.cluster
  /_rustybin/harness/clusters:
    get:
      tags:
      - Harness
      summary: Clusters this node leads
      operationId: listHarnessClusters
      responses:
        '200':
          description: Clusters
          content:
            application/json:
              schema:
                type: object
        '404':
          description: Harness mode is off (RUSTYBIN_HARNESS=on), or no such run
        '403':
          description: 'permission_required: the caller lacks harness.read'
      x-rustybin-permission: harness.read
    post:
      tags:
      - Harness
      summary: Create a cluster
      description: Needs the coordinator role. Returns the cluster and its join token (once).
      operationId: createHarnessCluster
      requestBody:
        required: false
        content:
          application/json:
            schema:
              type: object
              properties:
                cluster_id:
                  type: string
                min_nodes:
                  type: object
                  properties:
                    client:
                      type: integer
                    server:
                      type: integer
                lease:
                  type: string
                  description: 500ms to 60s, default 5s
                election_timeout:
                  type: string
                  description: 'How long without the leader before an election starts: 200ms, below the lease (default RUSTYBIN_HARNESS_ELECTION_TIMEOUT, 3s)'
                join_token:
                  type: string
                  description: At least 16 characters; random when absent
      responses:
        '201':
          description: The cluster and its join token
          content:
            application/json:
              schema:
                type: object
        '400':
          description: Invalid body
        '403':
          description: No coordinator role
        '409':
          description: The id exists, or too many clusters
        '404':
          description: Harness mode is off (RUSTYBIN_HARNESS=on), or no such run
      x-rustybin-permission: harness.cluster
  /_rustybin/harness/clusters/{id}:
    get:
      tags:
      - Harness
      summary: A cluster
      operationId: getHarnessCluster
      parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
      responses:
        '200':
          description: Members, health, clock offsets, the current run
          content:
            application/json:
              schema:
                type: object
        '307':
          description: This node is a member; the leader answers
        '404':
          description: Harness mode is off (RUSTYBIN_HARNESS=on), or no such run
        '403':
          description: 'permission_required: the caller lacks harness.read'
      x-rustybin-permission: harness.read
    delete:
      tags:
      - Harness
      summary: Delete a cluster
      operationId: deleteHarnessCluster
      parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
      responses:
        '204':
          description: Deleted
        '409':
          description: A run is active
        '404':
          description: Harness mode is off (RUSTYBIN_HARNESS=on), or no such run
        '403':
          description: 'permission_required: the caller lacks harness.cluster'
      x-rustybin-permission: harness.cluster
  /_rustybin/harness/clusters/{id}/upstreams:
    get:
      tags:
      - Harness
      summary: Server nodes' URLs
      operationId: getHarnessUpstreams
      parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
      responses:
        '200':
          description: Advertised URLs
          content:
            application/json:
              schema:
                type: array
                items:
                  type: string
        '307':
          description: This node is a member; the leader answers
        '404':
          description: Harness mode is off (RUSTYBIN_HARNESS=on), or no such run
        '403':
          description: 'permission_required: the caller lacks harness.read'
      x-rustybin-permission: harness.read
  /_rustybin/harness/clusters/{id}/leader/step-aside:
    post:
      tags:
      - Harness
      summary: Hand the cluster to the first in line
      description: 'Before maintenance: the leader stops renewing leases and the first in the succession takes over by the ordinary election, with the run still on. 409 with fewer than three eligible nodes, or on a member (it names the leader).'
      operationId: stepAsideHarnessLeader
      parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
      responses:
        '202':
          description: Who is expected to take over
          content:
            application/json:
              schema:
                type: object
        '404':
          description: Harness mode is off (RUSTYBIN_HARNESS=on), or no such run
        '409':
          description: Nobody can take over, or this node does not lead
        '403':
          description: 'permission_required: the caller lacks harness.cluster'
      x-rustybin-permission: harness.cluster
  /_rustybin/harness/clusters/{id}/leader:
    get:
      tags:
      - Harness
      summary: The cluster's leader
      description: The leader (node, control URL, term, since), the eligible nodes, whether failover is available (three or more), the succession with work scores and the history of leader changes. Every member answers; never redirected.
      operationId: getHarnessLeader
      parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
      responses:
        '200':
          description: The leader as this node sees it
          content:
            application/json:
              schema:
                type: object
        '404':
          description: Harness mode is off (RUSTYBIN_HARNESS=on), or no such run
        '403':
          description: 'permission_required: the caller lacks harness.read'
      x-rustybin-permission: harness.read
  /_rustybin/harness/clusters/{id}/election/vote:
    post:
      tags:
      - Harness
      summary: Node vote
      description: 'A candidate asks an eligible node for its vote: `{term, candidate, snapshot}` with the candidate''s node credential as bearer. Granted when the node lost the leader too, has not voted in the term and the candidate''s snapshot is at least as recent.'
      operationId: harnessNodeVote
      security: []
      parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
      responses:
        '200':
          description: '`{granted, term, leader?, reason}`'
          content:
            application/json:
              schema:
                type: object
        '401':
          description: Invalid node credential
        '403':
          description: This node is not eligible
        '404':
          description: Harness mode is off (RUSTYBIN_HARNESS=on), or no such run
      x-rustybin-permission: open
  /_rustybin/harness/clusters/{id}/election/elected:
    post:
      tags:
      - Harness
      summary: New leader announcement
      description: A new leader announces its term (its node credential as bearer). A node that led an older term steps down; a member follows the new leader.
      operationId: harnessNodeElected
      security: []
      parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
      responses:
        '200':
          description: '`{accepted}`'
          content:
            application/json:
              schema:
                type: object
        '401':
          description: Invalid node credential
        '404':
          description: Harness mode is off (RUSTYBIN_HARNESS=on), or no such run
      x-rustybin-permission: open
  /_rustybin/harness/clusters/{id}/nodes:
    post:
      tags:
      - Harness
      summary: Node join
      description: Called by nodes with the join token as bearer. Returns a node credential and a lease.
      operationId: harnessNodeJoin
      security: []
      parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
      responses:
        '200':
          description: Credential, lease, leader, clock stamps
          content:
            application/json:
              schema:
                type: object
        '401':
          description: Invalid join token
        '404':
          description: Harness mode is off (RUSTYBIN_HARNESS=on), or no such run
      x-rustybin-permission: open
  /_rustybin/harness/clusters/{id}/nodes/{node}/heartbeat:
    post:
      tags:
      - Harness
      summary: Node heartbeat
      description: 'Called by nodes with their node credential as bearer: a long poll (up to 10 s, at most a third of the election timeout) carrying cumulative stats, the term and a work report; returns assignments, shares, stops, the lease and the snapshot when it changed. A node that does not lead answers 409 naming the leader it knows; a leader without a majority of the eligible nodes answers 503 (no lease renewal).'
      operationId: harnessNodeHeartbeat
      security: []
      parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
      - name: node
        in: path
        required: true
        schema:
          type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
      responses:
        '200':
          description: Assignments and the lease
          content:
            application/json:
              schema:
                type: object
        '401':
          description: Invalid node credential
        '404':
          description: Harness mode is off (RUSTYBIN_HARNESS=on), or no such run
      x-rustybin-permission: open
  /_rustybin/harness/clusters/{id}/runs:
    get:
      tags:
      - Harness
      summary: Cluster runs
      operationId: listHarnessClusterRuns
      parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
      responses:
        '200':
          description: Runs
          content:
            application/json:
              schema:
                type: object
        '307':
          description: This node is a member; the leader answers
        '404':
          description: Harness mode is off (RUSTYBIN_HARNESS=on), or no such run
        '403':
          description: 'permission_required: the caller lacks harness.read'
      x-rustybin-permission: harness.read
    post:
      tags:
      - Harness
      summary: Submit a scenario
      description: Validated against the cluster (each client's share against the caps), not started.
      operationId: createHarnessClusterRun
      parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/HarnessScenario'
          application/yaml:
            schema:
              type: string
      responses:
        '201':
          description: The run and its quorum
          content:
            application/json:
              schema:
                type: object
        '400':
          description: Invalid scenario (`problems`)
        '307':
          description: This node is a member; the leader answers
        '404':
          description: Harness mode is off (RUSTYBIN_HARNESS=on), or no such run
        '403':
          description: 'permission_required: the caller lacks harness.run'
      x-rustybin-permission: harness.run
  /_rustybin/harness/clusters/{id}/runs/{run}:
    get:
      tags:
      - Harness
      summary: Live merged stats of a cluster run
      operationId: getHarnessClusterRun
      parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
      - name: run
        in: path
        required: true
        schema:
          type: string
      responses:
        '200':
          description: Live view
          content:
            application/json:
              schema:
                type: object
        '307':
          description: This node is a member; the leader answers
        '404':
          description: Harness mode is off (RUSTYBIN_HARNESS=on), or no such run
        '403':
          description: 'permission_required: the caller lacks harness.read'
      x-rustybin-permission: harness.read
    patch:
      tags:
      - Harness
      summary: Change a running cluster run
      description: Checked against every client node's capacity for its share, carried in the cluster snapshot, followed by the nodes on their next heartbeat.
      operationId: changeHarnessClusterRun
      parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
      - name: run
        in: path
        required: true
        schema:
          type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              description: 'A live change: rate_multiplier, pause, resume, hold, skip_stage, set, add_layer, remove_layer, and layer to aim one (the schema is $defs.change of GET /_rustybin/harness/schema).'
      responses:
        '200':
          description: The run's controls after the change
          content:
            application/json:
              schema:
                type: object
        '400':
          description: Refused (`problems`)
        '409':
          description: Finished
        '307':
          description: This node is a member; the leader answers
        '404':
          description: Harness mode is off (RUSTYBIN_HARNESS=on), or no such run
        '403':
          description: 'permission_required: the caller lacks harness.run'
      x-rustybin-permission: harness.run
  /_rustybin/harness/clusters/{id}/runs/{run}/start:
    post:
      tags:
      - Harness
      summary: Start a cluster run
      operationId: startHarnessClusterRun
      parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
      - name: run
        in: path
        required: true
        schema:
          type: string
      responses:
        '202':
          description: Starting (synchronised)
          content:
            application/json:
              schema:
                type: object
        '409':
          description: No quorum, a run is active, or the clients cannot carry the rate
        '404':
          description: Harness mode is off (RUSTYBIN_HARNESS=on), or no such run
        '403':
          description: 'permission_required: the caller lacks harness.run'
      x-rustybin-permission: harness.run
  /_rustybin/harness/clusters/{id}/runs/{run}/stop:
    post:
      tags:
      - Harness
      summary: Stop a cluster run
      operationId: stopHarnessClusterRun
      parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
      - name: run
        in: path
        required: true
        schema:
          type: string
      responses:
        '202':
          description: Stopping
          content:
            application/json:
              schema:
                type: object
        '200':
          description: Already stopped or finished
          content:
            application/json:
              schema:
                type: object
        '404':
          description: Harness mode is off (RUSTYBIN_HARNESS=on), or no such run
        '403':
          description: 'permission_required: the caller lacks harness.run'
      x-rustybin-permission: harness.run
  /_rustybin/harness/clusters/{id}/runs/{run}/report:
    get:
      tags:
      - Harness
      summary: Merged report of a cluster run
      operationId: getHarnessClusterReport
      parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
      - name: run
        in: path
        required: true
        schema:
          type: string
      - name: format
        in: query
        required: false
        schema:
          type: string
          enum:
          - json
          - text
          - junit
      responses:
        '200':
          description: The report
          content:
            application/json:
              schema:
                type: object
            text/plain:
              schema:
                type: string
            application/xml:
              schema:
                type: string
        '409':
          description: The run has not finished
        '404':
          description: Harness mode is off (RUSTYBIN_HARNESS=on), or no such run
        '403':
          description: 'permission_required: the caller lacks harness.read'
      x-rustybin-permission: harness.read
  /_rustybin/harness/import:
    post:
      tags:
      - Harness
      summary: A recording as a scenario's source
      description: A HAR file, or the answer of GET /_rustybin/requests, as weighted request templates. Secret-looking headers and query parameters are removed and named; entries with a secret-looking field in the body are left out. Sends nothing.
      operationId: importHarnessRecording
      responses:
        '200':
          description: '`source.requests`, `entries`, `skipped`, `removed`, `hosts`'
          content:
            application/json:
              schema:
                type: object
        '400':
          description: Not a recording, or nothing to replay
        '403':
          description: 'permission_required: the caller lacks harness.read'
      x-rustybin-permission: harness.read
  /_rustybin/harness/preview:
    post:
      tags:
      - Harness
      summary: Preview a scenario
      description: The expected rate per second per layer and in total (users for closed layers), from the patterns' exact integrals, with the validation's problems. Sends nothing; works with harness mode off.
      operationId: previewHarnessScenario
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/HarnessScenario'
          application/yaml:
            schema:
              type: string
      responses:
        '200':
          description: '`layers[].series`, `total`, `step_secs`, `valid`, `problems`'
          content:
            application/json:
              schema:
                type: object
        '400':
          description: Unparseable scenario
        '403':
          description: 'permission_required: the caller lacks harness.read'
      x-rustybin-permission: harness.read
  /_rustybin/harness/runs/{run}/events:
    get:
      tags:
      - Harness
      summary: Run feed
      description: 'Server-Sent Events: `snapshot` every second, `change` and `finding`, then `finished` with the report''s summary.'
      operationId: harnessRunEvents
      parameters:
      - name: run
        in: path
        required: true
        schema:
          type: string
      responses:
        '200':
          description: Server-Sent Events
          content:
            text/event-stream:
              schema:
                type: string
        '404':
          description: Harness mode is off (RUSTYBIN_HARNESS=on), or no such run
        '403':
          description: 'permission_required: the caller lacks harness.read'
      x-rustybin-permission: harness.read
  /_rustybin/harness/clusters/{id}/runs/{run}/events:
    get:
      tags:
      - Harness
      summary: Cluster run feed
      description: 'Server-Sent Events: `snapshot` every second, `change`, `finding`, `scale_request`, `leader_changed` (this node no longer leads; reconnect to the URL in the event), then `finished`.'
      operationId: harnessClusterRunEvents
      parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
      - name: run
        in: path
        required: true
        schema:
          type: string
      responses:
        '200':
          description: Server-Sent Events
          content:
            text/event-stream:
              schema:
                type: string
        '307':
          description: This node is a member; the leader answers
        '404':
          description: Harness mode is off (RUSTYBIN_HARNESS=on), or no such run
        '403':
          description: 'permission_required: the caller lacks harness.read'
      x-rustybin-permission: harness.read
  /_rustybin/metrics:
    get:
      tags:
      - Control Plane
      summary: Prometheus metrics
      description: 'Prometheus text exposition (version 0.0.4): rustybin_requests_total{route,method,status_class}, rustybin_request_duration_seconds{route} (histogram, buckets 1 ms to 60 s), rustybin_streams_open, rustybin_egress_bytes_total, rustybin_protocol_requests_total{protocol}, rustybin_llm_tokens_total{provider,model_family,direction}, rustybin_llm_requests_total{provider,model_family,streaming}, rustybin_llm_faults_total{provider,kind}, rustybin_limit_rejections_total{dimension}, rustybin_faults_injected_total{kind}, rustybin_build_info{version,git_sha}; in harness mode rustybin_harness_requests_total{run,class,status}, rustybin_harness_latency_seconds{run,kind}, rustybin_harness_scheduler_lag_seconds{run}, rustybin_harness_inflight{run} and rustybin_harness_server_arrivals_total{run} (the run label is bounded to the active run plus the last 4); with API keys also rustybin_keys_active, rustybin_keys_last_sync_timestamp_seconds and (with a quota store) rustybin_quota_store_up. The route label is the matched route template.'
      operationId: getRustybinMetrics
      responses:
        '200':
          description: Metrics
          content:
            text/plain:
              schema:
                type: string
        '401':
          description: Control-plane authentication required (RUSTYBIN_CONTROL_AUTH token or jwt)
        '403':
          description: 'permission_required: the caller lacks instance.read'
      x-rustybin-permission: instance.read
  /_rustybin/usage:
    get:
      tags:
      - Control Plane
      summary: Plan limits and current usage
      description: 'The active plan (RUSTYBIN_PLAN), its limits and the usage of the current period. Session scope reports only the caller''s own counters. Shared mode (RUSTYBIN_SHARED_MODE): called with an API key (X-Rustybin-Key or ?rb_key=, or a control JWT with scope inspector and claim rb_key) it reports that key''s limits and today''s usage (scope `key`, plan = the key''s label). Exempt from every limit.'
      operationId: getRustybinUsage
      responses:
        '200':
          description: Usage
          content:
            application/json:
              schema:
                type: object
                properties:
                  plan:
                    type: string
                    description: none, free, pro, team, enterprise or custom; in shared mode the key's label
                  active:
                    type: boolean
                  scope:
                    type: string
                    enum:
                    - instance
                    - session
                    - key
                  session:
                    type: string
                    description: 'Session scope only: the caller''s session key'
                  key_id:
                    type: string
                    description: 'Shared mode: the API key id'
                  inspector:
                    type: object
                    description: 'Shared mode: the key''s inspector retention'
                    properties:
                      entries:
                        type:
                        - integer
                        - 'null'
                      ttl_secs:
                        type:
                        - integer
                        - 'null'
                  quota_store:
                    type: string
                    enum:
                    - local
                    - postgres
                    description: 'Shared mode: where daily totals are shared'
                  limits:
                    type: object
                  period:
                    type: object
                    properties:
                      kind:
                        type: string
                        enum:
                        - day
                        - month
                      start:
                        type: string
                        format: date-time
                      end:
                        type: string
                        format: date-time
                      resets_in_secs:
                        type: integer
                  requests:
                    type: object
                    properties:
                      used:
                        type: integer
                      limit:
                        type:
                        - integer
                        - 'null'
                      remaining:
                        type:
                        - integer
                        - 'null'
                  egress:
                    type: object
                    properties:
                      used_bytes:
                        type: integer
                      limit_bytes:
                        type:
                        - integer
                        - 'null'
                      remaining_bytes:
                        type:
                        - integer
                        - 'null'
                  rate:
                    type: object
                  in_flight:
                    type: object
                  open_streams:
                    type: object
                  persisted:
                    type: boolean
                  instance_id:
                    type: string
                  build:
                    type: object
                    properties:
                      version:
                        type: string
                      git_sha:
                        type:
                        - string
                        - 'null'
                      profile:
                        type: string
                      rustc:
                        type: string
                  control_auth:
                    type: string
                    enum:
                    - password
                    - open
                    - token
                    - jwt
        '403':
          description: 'permission_required: the caller lacks instance.read'
      x-rustybin-permission: instance.read
  /_rustybin/keys/usage:
    get:
      tags:
      - Control Plane
      summary: Per-key daily usage
      description: Per-key totals of one UTC day for a platform collector (shared mode or RUSTYBIN_REQUIRE_KEY). From the quota store when RUSTYBIN_QUOTA_STORE is set, else from this replica's in-memory counters (today and the two previous days). Sorted by key id; continue with ?after=<next_after>. Admin token required.
      operationId: getRustybinKeysUsage
      parameters:
      - name: day
        in: query
        required: false
        schema:
          type: string
          format: date
        description: UTC day, default today
      - name: after
        in: query
        required: false
        schema:
          type: string
        description: Return key ids greater than this one
      - name: limit
        in: query
        required: false
        schema:
          type: integer
          minimum: 1
          maximum: 1000
          default: 1000
      responses:
        '200':
          description: One page of per-key totals
          content:
            application/json:
              schema:
                type: object
                properties:
                  day:
                    type: string
                    format: date
                  keys:
                    type: array
                    items:
                      type: object
                      properties:
                        key_id:
                          type: string
                        requests:
                          type: integer
                        egress_bytes:
                          type: integer
                  next_after:
                    type:
                    - string
                    - 'null'
        '400':
          description: Invalid day, after or limit
        '401':
          description: Admin token required
        '403':
          description: Public or shared mode without an admin token
        '503':
          description: The quota store is unavailable (rustybin_quota_store_unavailable)
      x-rustybin-permission: settings.read
  /ui:
    get:
      tags:
      - Docs
      summary: Web console
      operationId: getConsoleRedirect
      responses:
        '307':
          description: Redirect to /ui/
  /ui/:
    get:
      tags:
      - Docs
      summary: Web console entry page
      operationId: getConsoleIndex
      responses:
        '200':
          description: HTML
          content:
            text/html:
              schema:
                type: string
  /ui/{path}:
    get:
      tags:
      - Docs
      summary: Console asset (SPA fallback to index.html)
      operationId: getConsoleAsset
      parameters:
      - name: path
        in: path
        required: true
        schema:
          type: string
      responses:
        '200':
          description: Asset
        '304':
          description: Not modified (If-None-Match)
        '404':
          description: Unknown asset
  /_rustybin/catalog:
    get:
      tags:
      - Control Plane
      summary: Route catalogue
      operationId: getRustybinCatalog
      responses:
        '200':
          description: Catalogue
          content:
            application/json:
              schema:
                type: object
                properties:
                  count:
                    type: integer
                  categories:
                    type: array
                    items:
                      type: object
                  endpoints:
                    type: array
                    items:
                      type: object
                      properties:
                        path:
                          type: string
                        methods:
                          type: array
                          items:
                            type: string
                        category:
                          type: string
                        summary:
                          type: string
                        description:
                          type: string
                        protocol:
                          type: string
                          enum:
                          - http
                          - sse
                          - websocket
                        examples:
                          type: array
                          items:
                            type: object
        '403':
          description: 'permission_required: the caller lacks instance.read'
      x-rustybin-permission: instance.read
  /_rustybin/status:
    get:
      tags:
      - Control Plane
      summary: Instance status
      operationId: getRustybinStatus
      responses:
        '200':
          description: Status
          content:
            application/json:
              schema:
                type: object
        '403':
          description: 'permission_required: the caller lacks instance.read'
      x-rustybin-permission: instance.read
  /bin:
    get:
      tags:
      - Request Bin
      summary: List the caller's bins
      operationId: listBins
      responses:
        '200':
          description: Bins of the caller's session
    post:
      tags:
      - Request Bin
      summary: Create a request bin
      operationId: createBin
      requestBody:
        required: false
        content:
          application/json:
            schema:
              type: object
              properties:
                status:
                  type: integer
                  minimum: 200
                  maximum: 599
                headers:
                  type: object
                  additionalProperties:
                    type: string
                body:
                  description: String returned verbatim, any other JSON value is serialised
                delay_ms:
                  type: integer
                  minimum: 0
      responses:
        '201':
          description: Bin created (id, URLs, expiry, limits)
        '400':
          description: Invalid response definition
        '429':
          description: Too many bins for this session
        '503':
          description: Store full (public mode)
  /bin/{id}:
    parameters:
    - name: id
      in: path
      required: true
      schema:
        type: string
      description: Bin id returned by POST /bin
    post:
      tags:
      - Request Bin
      summary: Capture a request
      operationId: captureBin
      requestBody:
        required: false
        content:
          '*/*':
            schema: {}
      responses:
        '200':
          description: The bin's configured response (status, headers and body are configurable)
        '404':
          description: Unknown or expired bin
    delete:
      tags:
      - Request Bin
      summary: Delete the bin
      operationId: deleteBin
      responses:
        '200':
          description: Deleted
        '404':
          description: Unknown or expired bin
    get:
      tags:
      - Request Bin
      summary: Capture a request
      operationId: captureBin_get
      responses:
        '200':
          description: The bin's configured response (status, headers and body are configurable)
        '404':
          description: Unknown or expired bin
    put:
      tags:
      - Request Bin
      summary: Capture a request
      operationId: captureBin_put
      requestBody:
        required: false
        content:
          '*/*':
            schema: {}
      responses:
        '200':
          description: The bin's configured response (status, headers and body are configurable)
        '404':
          description: Unknown or expired bin
    patch:
      tags:
      - Request Bin
      summary: Capture a request
      operationId: captureBin_patch
      requestBody:
        required: false
        content:
          '*/*':
            schema: {}
      responses:
        '200':
          description: The bin's configured response (status, headers and body are configurable)
        '404':
          description: Unknown or expired bin
  /bin/{id}/{path}:
    parameters:
    - name: id
      in: path
      required: true
      schema:
        type: string
      description: Bin id returned by POST /bin
    - name: path
      in: path
      required: true
      schema:
        type: string
      description: Any sub path
    post:
      tags:
      - Request Bin
      summary: Capture a request
      operationId: captureBinPath
      requestBody:
        required: false
        content:
          '*/*':
            schema: {}
      responses:
        '200':
          description: The bin's configured response (status, headers and body are configurable)
        '404':
          description: Unknown or expired bin
    get:
      tags:
      - Request Bin
      summary: Capture a request
      operationId: captureBinPath_get
      responses:
        '200':
          description: The bin's configured response (status, headers and body are configurable)
        '404':
          description: Unknown or expired bin
    put:
      tags:
      - Request Bin
      summary: Capture a request
      operationId: captureBinPath_put
      requestBody:
        required: false
        content:
          '*/*':
            schema: {}
      responses:
        '200':
          description: The bin's configured response (status, headers and body are configurable)
        '404':
          description: Unknown or expired bin
    patch:
      tags:
      - Request Bin
      summary: Capture a request
      operationId: captureBinPath_patch
      requestBody:
        required: false
        content:
          '*/*':
            schema: {}
      responses:
        '200':
          description: The bin's configured response (status, headers and body are configurable)
        '404':
          description: Unknown or expired bin
    delete:
      tags:
      - Request Bin
      summary: Capture a request
      operationId: captureBinPath_delete
      responses:
        '200':
          description: The bin's configured response (status, headers and body are configurable)
        '404':
          description: Unknown or expired bin
  /bin/{id}/requests:
    get:
      tags:
      - Request Bin
      summary: List captured requests
      operationId: listBinRequests
      parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
        description: Bin id returned by POST /bin
      - name: limit
        in: query
        schema:
          type: integer
          default: 50
      responses:
        '200':
          description: Bin summary and requests, newest first
        '404':
          description: Unknown or expired bin
  /bin/{id}/requests/stream:
    get:
      tags:
      - Request Bin
      summary: Live feed of captured requests (SSE)
      operationId: streamBinRequests
      parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
        description: Bin id returned by POST /bin
      - name: Last-Event-ID
        in: header
        schema:
          type: integer
      responses:
        '200':
          description: text/event-stream of `request` events
          content:
            text/event-stream: {}
        '404':
          description: Unknown or expired bin
  /bin/{id}/requests/{n}:
    get:
      tags:
      - Request Bin
      summary: One captured request
      operationId: getBinRequest
      parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
        description: Bin id returned by POST /bin
      - name: n
        in: path
        required: true
        schema:
          type: integer
      responses:
        '200':
          description: Captured request
        '404':
          description: Unknown bin or request
  /mock/{name}:
    post:
      tags:
      - Mocks
      summary: Call a mock
      operationId: callMock
      parameters:
      - name: name
        in: path
        required: true
        schema:
          type: string
        description: Mock name
      requestBody:
        required: false
        content:
          '*/*':
            schema: {}
      responses:
        '200':
          description: The mocked response (status, headers and body follow the mock's spec)
        '400':
          description: Request does not match the spec (validation enforce)
        '401':
          description: Mocked security rejected the request
        '404':
          description: Unknown mock or no matching operation
        '405':
          description: Method not documented for this path
        '503':
          description: Mock switched off or not deployed
    get:
      tags:
      - Mocks
      summary: Call a mock
      operationId: callMock_get
      parameters:
      - name: name
        in: path
        required: true
        schema:
          type: string
        description: Mock name
      responses:
        '200':
          description: The mocked response (status, headers and body follow the mock's spec)
        '400':
          description: Request does not match the spec (validation enforce)
        '401':
          description: Mocked security rejected the request
        '404':
          description: Unknown mock or no matching operation
        '405':
          description: Method not documented for this path
        '503':
          description: Mock switched off or not deployed
    put:
      tags:
      - Mocks
      summary: Call a mock
      operationId: callMock_put
      parameters:
      - name: name
        in: path
        required: true
        schema:
          type: string
        description: Mock name
      requestBody:
        required: false
        content:
          '*/*':
            schema: {}
      responses:
        '200':
          description: The mocked response (status, headers and body follow the mock's spec)
        '400':
          description: Request does not match the spec (validation enforce)
        '401':
          description: Mocked security rejected the request
        '404':
          description: Unknown mock or no matching operation
        '405':
          description: Method not documented for this path
        '503':
          description: Mock switched off or not deployed
    patch:
      tags:
      - Mocks
      summary: Call a mock
      operationId: callMock_patch
      parameters:
      - name: name
        in: path
        required: true
        schema:
          type: string
        description: Mock name
      requestBody:
        required: false
        content:
          '*/*':
            schema: {}
      responses:
        '200':
          description: The mocked response (status, headers and body follow the mock's spec)
        '400':
          description: Request does not match the spec (validation enforce)
        '401':
          description: Mocked security rejected the request
        '404':
          description: Unknown mock or no matching operation
        '405':
          description: Method not documented for this path
        '503':
          description: Mock switched off or not deployed
    delete:
      tags:
      - Mocks
      summary: Call a mock
      operationId: callMock_delete
      parameters:
      - name: name
        in: path
        required: true
        schema:
          type: string
        description: Mock name
      responses:
        '200':
          description: The mocked response (status, headers and body follow the mock's spec)
        '400':
          description: Request does not match the spec (validation enforce)
        '401':
          description: Mocked security rejected the request
        '404':
          description: Unknown mock or no matching operation
        '405':
          description: Method not documented for this path
        '503':
          description: Mock switched off or not deployed
  /mock/{name}/{path}:
    post:
      tags:
      - Mocks
      summary: Call a mock
      operationId: callMockPath
      parameters:
      - name: name
        in: path
        required: true
        schema:
          type: string
        description: Mock name
      - name: path
        in: path
        required: true
        schema:
          type: string
        description: Path below the mock root
      requestBody:
        required: false
        content:
          '*/*':
            schema: {}
      responses:
        '200':
          description: The mocked response (status, headers and body follow the mock's spec)
        '400':
          description: Request does not match the spec (validation enforce)
        '401':
          description: Mocked security rejected the request
        '404':
          description: Unknown mock or no matching operation
        '405':
          description: Method not documented for this path
        '503':
          description: Mock switched off or not deployed
    get:
      tags:
      - Mocks
      summary: Call a mock
      operationId: callMockPath_get
      parameters:
      - name: name
        in: path
        required: true
        schema:
          type: string
        description: Mock name
      - name: path
        in: path
        required: true
        schema:
          type: string
        description: Path below the mock root
      responses:
        '200':
          description: The mocked response (status, headers and body follow the mock's spec)
        '400':
          description: Request does not match the spec (validation enforce)
        '401':
          description: Mocked security rejected the request
        '404':
          description: Unknown mock or no matching operation
        '405':
          description: Method not documented for this path
        '503':
          description: Mock switched off or not deployed
    put:
      tags:
      - Mocks
      summary: Call a mock
      operationId: callMockPath_put
      parameters:
      - name: name
        in: path
        required: true
        schema:
          type: string
        description: Mock name
      - name: path
        in: path
        required: true
        schema:
          type: string
        description: Path below the mock root
      requestBody:
        required: false
        content:
          '*/*':
            schema: {}
      responses:
        '200':
          description: The mocked response (status, headers and body follow the mock's spec)
        '400':
          description: Request does not match the spec (validation enforce)
        '401':
          description: Mocked security rejected the request
        '404':
          description: Unknown mock or no matching operation
        '405':
          description: Method not documented for this path
        '503':
          description: Mock switched off or not deployed
    patch:
      tags:
      - Mocks
      summary: Call a mock
      operationId: callMockPath_patch
      parameters:
      - name: name
        in: path
        required: true
        schema:
          type: string
        description: Mock name
      - name: path
        in: path
        required: true
        schema:
          type: string
        description: Path below the mock root
      requestBody:
        required: false
        content:
          '*/*':
            schema: {}
      responses:
        '200':
          description: The mocked response (status, headers and body follow the mock's spec)
        '400':
          description: Request does not match the spec (validation enforce)
        '401':
          description: Mocked security rejected the request
        '404':
          description: Unknown mock or no matching operation
        '405':
          description: Method not documented for this path
        '503':
          description: Mock switched off or not deployed
    delete:
      tags:
      - Mocks
      summary: Call a mock
      operationId: callMockPath_delete
      parameters:
      - name: name
        in: path
        required: true
        schema:
          type: string
        description: Mock name
      - name: path
        in: path
        required: true
        schema:
          type: string
        description: Path below the mock root
      responses:
        '200':
          description: The mocked response (status, headers and body follow the mock's spec)
        '400':
          description: Request does not match the spec (validation enforce)
        '401':
          description: Mocked security rejected the request
        '404':
          description: Unknown mock or no matching operation
        '405':
          description: Method not documented for this path
        '503':
          description: Mock switched off or not deployed
  /mock/{name}/_rustybin/openapi.json:
    get:
      tags:
      - Mocks
      summary: Deployed spec (JSON)
      operationId: getMockSpec
      parameters:
      - name: name
        in: path
        required: true
        schema:
          type: string
        description: Mock name
      responses:
        '200':
          description: OK
        '404':
          description: Unknown mock
  /mock/{name}/_rustybin/openapi.yaml:
    get:
      tags:
      - Mocks
      summary: Deployed spec (YAML)
      operationId: getMockSpecYaml
      parameters:
      - name: name
        in: path
        required: true
        schema:
          type: string
        description: Mock name
      responses:
        '200':
          description: OK
        '404':
          description: Unknown mock
  /_rustybin/mocks:
    get:
      tags:
      - Mocks
      summary: List mocks
      operationId: listMocks
      parameters:
      - name: owner
        in: query
        required: false
        description: 'all: every visitor''s mocks too (admin)'
        schema:
          type: string
      - name: project
        in: query
        required: false
        description: 'One project''s mocks (instance: the instance''s without one)'
        schema:
          type: string
      responses:
        '200':
          description: OK
        '404':
          description: Unknown mock
        '403':
          description: 'permission_required: the caller lacks mocks.read'
      x-rustybin-permission: mocks.read
    post:
      tags:
      - Mocks
      summary: Create a mock from an OpenAPI spec
      operationId: createMock
      parameters: []
      responses:
        '201':
          description: Created (details)
        '400':
          description: Invalid spec or settings
        '409':
          description: Name taken
        '422':
          description: Deploy checks failed (report)
        '403':
          description: 'permission_required: the caller lacks mocks.write'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - spec
              properties:
                spec:
                  description: OpenAPI 3.0/3.1 document (text or object)
                name:
                  type: string
                  pattern: ^[a-z0-9]([a-z0-9-]{0,38}[a-z0-9])?$
                enabled:
                  type: boolean
                deploy:
                  type: boolean
                  default: true
                force:
                  type: boolean
                strip_server_prefix:
                  type: boolean
                responses:
                  type: object
                security:
                  type: object
                operations:
                  type: object
                mcp:
                  type: object
                  description: '{enabled, exclude}: the mock''s MCP server'
                project:
                  type: string
                  description: 'The project it belongs to (mocks.write there); absent: the instance''s, or the one project the caller works in'
      x-rustybin-permission: mocks.write
  /_rustybin/mocks/import:
    post:
      tags:
      - Mocks
      summary: Preview a spec
      operationId: previewMockSpec
      parameters: []
      responses:
        '200':
          description: OK
        '404':
          description: Unknown mock
        '403':
          description: 'permission_required: the caller lacks mocks.read'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                spec: {}
      x-rustybin-permission: mocks.read
  /_rustybin/mocks/apply:
    post:
      tags:
      - Mocks
      summary: Apply a rustybin.yaml document
      operationId: applyMocks
      parameters:
      - name: prune
        in: query
        schema:
          type: boolean
      responses:
        '200':
          description: OK
        '401':
          description: Control-plane credentials required
        '403':
          description: Admin token or admin scope required
        '404':
          description: Unknown mock
      x-rustybin-permission: config.apply
  /_rustybin/mocks/reload:
    post:
      tags:
      - Mocks
      summary: Reload file-defined mocks
      operationId: reloadMocks
      parameters: []
      responses:
        '200':
          description: OK
        '401':
          description: Control-plane credentials required
        '403':
          description: Admin token or admin scope required
        '404':
          description: Unknown mock
      x-rustybin-permission: config.apply
  /_rustybin/mocks/export:
    get:
      tags:
      - Mocks
      summary: Export mocks as YAML
      operationId: exportMocks
      parameters: []
      responses:
        '200':
          description: OK
        '404':
          description: Unknown mock
        '403':
          description: 'permission_required: the caller lacks mocks.read'
      x-rustybin-permission: mocks.read
  /_rustybin/mocks/import/fetch:
    post:
      tags:
      - Mocks
      summary: Fetch a spec from a URL on the server
      operationId: fetchMockSpec
      parameters: []
      responses:
        '200':
          description: OK
        '404':
          description: Unknown mock
        '403':
          description: 'permission_required: the caller lacks mocks.write'
      x-rustybin-permission: mocks.write
  /_rustybin/mocks/config.schema.json:
    get:
      tags:
      - Mocks
      summary: JSON Schema of rustybin.yaml
      operationId: getMocksConfigSchema
      parameters: []
      responses:
        '200':
          description: OK
        '404':
          description: Unknown mock
        '403':
          description: 'permission_required: the caller lacks mocks.read'
      x-rustybin-permission: mocks.read
  /_rustybin/mocks/{name}:
    get:
      tags:
      - Mocks
      summary: Mock details
      operationId: getMock
      parameters:
      - name: name
        in: path
        required: true
        schema:
          type: string
        description: Mock name
      responses:
        '200':
          description: OK
        '404':
          description: Unknown mock
        '403':
          description: 'permission_required: the caller lacks mocks.read'
      x-rustybin-permission: mocks.read
    patch:
      tags:
      - Mocks
      summary: Edit the draft
      operationId: updateMock
      parameters:
      - name: name
        in: path
        required: true
        schema:
          type: string
        description: Mock name
      responses:
        '200':
          description: OK
        '401':
          description: Control-plane credentials required
        '403':
          description: Admin token or admin scope required
        '404':
          description: Unknown mock
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                project:
                  type:
                  - string
                  - 'null'
                  description: 'Move the mock to this project (null or instance: the instance''s); mocks.write in both'
      x-rustybin-permission: mocks.write
    delete:
      tags:
      - Mocks
      summary: Delete a mock
      operationId: deleteMock
      parameters:
      - name: name
        in: path
        required: true
        schema:
          type: string
        description: Mock name
      responses:
        '200':
          description: OK
        '401':
          description: Control-plane credentials required
        '403':
          description: Admin token or admin scope required
        '404':
          description: Unknown mock
      x-rustybin-permission: mocks.delete
  /_rustybin/mocks/{name}/enable:
    post:
      tags:
      - Mocks
      summary: Switch on
      operationId: enableMock
      parameters:
      - name: name
        in: path
        required: true
        schema:
          type: string
        description: Mock name
      responses:
        '200':
          description: OK
        '401':
          description: Control-plane credentials required
        '403':
          description: Admin token or admin scope required
        '404':
          description: Unknown mock
      x-rustybin-permission: mocks.write
  /_rustybin/mocks/{name}/disable:
    post:
      tags:
      - Mocks
      summary: Switch off
      operationId: disableMock
      parameters:
      - name: name
        in: path
        required: true
        schema:
          type: string
        description: Mock name
      responses:
        '200':
          description: OK
        '401':
          description: Control-plane credentials required
        '403':
          description: Admin token or admin scope required
        '404':
          description: Unknown mock
      x-rustybin-permission: mocks.write
  /_rustybin/mocks/{name}/deploy:
    post:
      tags:
      - Mocks
      summary: Deploy the draft
      operationId: deployMock
      parameters:
      - name: name
        in: path
        required: true
        schema:
          type: string
        description: Mock name
      - name: force
        in: query
        schema:
          type: boolean
      - name: If-Match
        in: header
        required: false
        schema:
          type: string
        description: 'The draft revision to deploy (the mock''s ETag): 409 rustybin_stale_revision when the draft has moved on'
      responses:
        '200':
          description: OK
        '401':
          description: Control-plane credentials required
        '403':
          description: Admin token or admin scope required
        '404':
          description: Unknown mock
        '409':
          description: 'rustybin_stale_revision: the draft is no longer the revision named'
        '422':
          description: The deploy checks found errors (deploy with force)
      requestBody:
        required: false
        content:
          application/json:
            schema:
              type: object
              additionalProperties: false
              properties:
                expected_revision:
                  type: integer
                  description: The draft revision to deploy (else If-Match; both must agree)
      x-rustybin-permission: mocks.write
  /_rustybin/mocks/{name}/rollback:
    post:
      tags:
      - Mocks
      summary: Roll back
      operationId: rollbackMock
      parameters:
      - name: name
        in: path
        required: true
        schema:
          type: string
        description: Mock name
      responses:
        '200':
          description: OK
        '401':
          description: Control-plane credentials required
        '403':
          description: Admin token or admin scope required
        '404':
          description: Unknown mock
      x-rustybin-permission: mocks.write
  /_rustybin/mocks/{name}/report:
    get:
      tags:
      - Mocks
      summary: Deploy-check report
      operationId: getMockReport
      parameters:
      - name: name
        in: path
        required: true
        schema:
          type: string
        description: Mock name
      responses:
        '200':
          description: OK
        '404':
          description: Unknown mock
        '403':
          description: 'permission_required: the caller lacks mocks.read'
      x-rustybin-permission: mocks.read
  /_rustybin/mocks/{name}/coverage:
    get:
      tags:
      - Mocks
      summary: 'Example coverage: which operations have an example for their success responses (draft and live)'
      operationId: getMockCoverage
      parameters:
      - name: name
        in: path
        required: true
        schema:
          type: string
        description: Mock name
      responses:
        '200':
          description: OK
        '404':
          description: Unknown mock
        '403':
          description: 'permission_required: the caller lacks mocks.read'
      x-rustybin-permission: mocks.read
  /_rustybin/mocks/ai/spec:
    post:
      tags:
      - Mocks
      summary: 'AI: an OpenAPI document from a description'
      description: 'Runs the assist task spec_from_description on the configured provider: the document on the chosen version (3.1.0, 3.0.3, 2.0) with the style options, parsed and checked, plus its preview (as POST /_rustybin/mocks/import gives). Nothing is created. Streamed as SSE (events progress, usage, result, error, done) with Accept: text/event-stream or stream: true, else one JSON document. Operator only, unless RUSTYBIN_ASSIST_VISITORS.'
      operationId: aiSpecFromDescription
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - description
              properties:
                description:
                  type: string
                openapi_version:
                  type: string
                  enum:
                  - 3.1.0
                  - 3.0.3
                  - '2.0'
                style:
                  type: object
                  properties:
                    auth:
                      type: string
                      enum:
                      - none
                      - api_key
                      - bearer
                      - oauth2
                    pagination:
                      type: string
                      enum:
                      - none
                      - page
                      - cursor
                    errors:
                      type: string
                      enum:
                      - problem
                      - simple
                model:
                  type: string
                stream:
                  type: boolean
      responses:
        '200':
          description: '{spec, text, preview, usage, sent}'
        '403':
          description: Visitors need RUSTYBIN_ASSIST_VISITORS
        '429':
          description: The caller's AI calls per minute
        '502':
          description: The provider failed or the document never passed the checks
        '503':
          description: No AI provider or key
      x-rustybin-permission: assist.jobs
  /_rustybin/mocks/{name}/ai/examples:
    post:
      tags:
      - Mocks
      summary: 'AI: examples for the operations that lack them'
      description: Runs examples_for_operations in batches of 10 (source ai), or the deterministic generator (source generated, no key). Every example is validated against its response schema. With apply the examples are stored in the draft (operations.<id>.examples, source ai or generated); a person's examples stay.
      operationId: aiExamplesForMock
      parameters:
      - name: name
        in: path
        required: true
        schema:
          type: string
        description: Mock name
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                operations:
                  type: array
                  items:
                    type: string
                  description: 'operationIds (default: every operation without examples)'
                per_response:
                  type: integer
                  minimum: 1
                  maximum: 3
                persona:
                  type: string
                source:
                  type: string
                  enum:
                  - ai
                  - generated
                apply:
                  type: boolean
                model:
                  type: string
                stream:
                  type: boolean
      responses:
        '200':
          description: '{examples, source, applied, stored, coverage, usage}'
        '404':
          description: Unknown mock
        '409':
          description: Every operation has examples
        '503':
          description: No AI provider or key
        '403':
          description: 'permission_required: the caller lacks assist.jobs'
      x-rustybin-permission: assist.jobs
  /_rustybin/mocks/{name}/ai/edit:
    post:
      tags:
      - Mocks
      summary: 'AI: edit the spec with an instruction'
      description: 'Runs edit_spec: the whole document after the instruction, parsed and checked, with a structural diff (operations added, removed, changed; at most 40 percent removed without an explicit ask). With apply the new document becomes the draft.'
      operationId: aiEditMock
      parameters:
      - name: name
        in: path
        required: true
        schema:
          type: string
        description: Mock name
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - instruction
              properties:
                instruction:
                  type: string
                apply:
                  type: boolean
                model:
                  type: string
                stream:
                  type: boolean
      responses:
        '200':
          description: '{spec, text, before_text, diff, preview, applied, detail?, usage, sent}'
        '404':
          description: Unknown mock
        '502':
          description: The provider failed or the edit never passed the checks
        '503':
          description: No AI provider or key
        '403':
          description: 'permission_required: the caller lacks assist.jobs'
      x-rustybin-permission: assist.jobs
  /_rustybin/mocks/{name}/export:
    get:
      tags:
      - Mocks
      summary: Export one mock
      operationId: exportMock
      parameters:
      - name: name
        in: path
        required: true
        schema:
          type: string
        description: Mock name
      responses:
        '200':
          description: OK
        '404':
          description: Unknown mock
        '403':
          description: 'permission_required: the caller lacks mocks.read'
      x-rustybin-permission: mocks.read
  /_rustybin/mocks/{name}/spec:
    get:
      tags:
      - Mocks
      summary: The spec as uploaded
      operationId: getMockSpecSource
      parameters:
      - name: name
        in: path
        required: true
        schema:
          type: string
        description: Mock name
      responses:
        '200':
          description: OK
        '404':
          description: Unknown mock
        '403':
          description: 'permission_required: the caller lacks mocks.read'
      x-rustybin-permission: mocks.read
  /_rustybin/mocks/{name}/revisions/{revision}:
    get:
      tags:
      - Mocks
      summary: A kept revision of a mock
      description: 'A revision the mock still keeps: the draft, the live revision, the last deployed ones (5, 2 in public mode) and as many drafts replaced before they were deployed. Its spec as uploaded, its settings (secrets redacted unless the caller may change the mock) and changes_to_draft, what the current draft changes since (the sections a 409 rustybin_stale_revision names). Read like the mock: another visitor''s mock is 404.'
      operationId: getMockRevision
      parameters:
      - name: name
        in: path
        required: true
        schema:
          type: string
        description: Mock name
      - name: revision
        in: path
        required: true
        schema:
          type: integer
          minimum: 0
      responses:
        '200':
          description: '{name, revision, created_at, draft, live, current_draft_revision, changes_to_draft, spec: {format, openapi, title, version, text}, operations, settings, secrets_hidden}'
        '400':
          description: Not a revision number
        '404':
          description: No such mock, or the revision is no longer kept
        '403':
          description: 'permission_required: the caller lacks mocks.read'
      x-rustybin-permission: mocks.read
  /_rustybin/mocks/{name}/duplicate:
    post:
      tags:
      - Mocks
      summary: Copy a mock
      operationId: duplicateMock
      parameters:
      - name: name
        in: path
        required: true
        schema:
          type: string
        description: Mock name
      responses:
        '200':
          description: OK
        '401':
          description: Control-plane credentials required
        '403':
          description: Admin token or admin scope required
        '404':
          description: Unknown mock
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                name:
                  type: string
      x-rustybin-permission: mocks.write
  /_rustybin/mocks/{name}/state:
    get:
      tags:
      - Mocks
      summary: Stored items (stateful mode)
      operationId: getMockState
      parameters:
      - name: name
        in: path
        required: true
        schema:
          type: string
        description: Mock name
      responses:
        '200':
          description: OK
        '404':
          description: Unknown mock
        '403':
          description: 'permission_required: the caller lacks mocks.read'
      x-rustybin-permission: mocks.read
    delete:
      tags:
      - Mocks
      summary: Reset stored items
      operationId: clearMockState
      parameters:
      - name: name
        in: path
        required: true
        schema:
          type: string
        description: Mock name
      responses:
        '200':
          description: OK
        '401':
          description: Control-plane credentials required
        '403':
          description: Admin token or admin scope required
        '404':
          description: Unknown mock
      x-rustybin-permission: mocks.data
  /_rustybin/mocks/{name}/collections/{format}:
    get:
      tags:
      - Mocks
      summary: The mock as a collection
      operationId: getMockCollection
      parameters:
      - name: name
        in: path
        required: true
        schema:
          type: string
        description: Mock name
      - name: format
        in: path
        required: true
        schema:
          type: string
          enum:
          - postman
          - hurl
          - curl
          - http
          - k6
          - bruno
          - insomnia
      - name: base_url
        in: query
        schema:
          type: string
      responses:
        '200':
          description: OK
        '404':
          description: Unknown mock
        '403':
          description: 'permission_required: the caller lacks mocks.read'
      x-rustybin-permission: mocks.read
  /_rustybin/mocks/{name}/operations/{op}/rules/test:
    post:
      tags:
      - Mocks
      summary: Which rule would answer a captured call
      operationId: testMockRules
      parameters:
      - name: name
        in: path
        required: true
        schema:
          type: string
        description: Mock name
      - name: op
        in: path
        required: true
        schema:
          type: string
        description: operationId
      responses:
        '200':
          description: OK
        '404':
          description: Unknown mock
        '403':
          description: 'permission_required: the caller lacks mocks.read'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                rules:
                  type: array
                request:
                  type: integer
      x-rustybin-permission: mocks.read
  /_rustybin/mocks/{name}/operations/{op}:
    get:
      tags:
      - Mocks
      summary: Operation details
      operationId: getMockOperation
      parameters:
      - name: name
        in: path
        required: true
        schema:
          type: string
        description: Mock name
      - name: op
        in: path
        required: true
        schema:
          type: string
        description: operationId
      responses:
        '200':
          description: OK
        '404':
          description: Unknown mock
        '403':
          description: 'permission_required: the caller lacks mocks.read'
      x-rustybin-permission: mocks.read
    put:
      tags:
      - Mocks
      summary: Set operation settings
      operationId: setMockOperation
      parameters:
      - name: name
        in: path
        required: true
        schema:
          type: string
        description: Mock name
      - name: op
        in: path
        required: true
        schema:
          type: string
        description: operationId
      responses:
        '200':
          description: OK
        '401':
          description: Control-plane credentials required
        '403':
          description: Admin token or admin scope required
        '404':
          description: Unknown mock
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
      x-rustybin-permission: mocks.write
  /_rustybin/mocks/{name}/operations/{op}/sample:
    get:
      tags:
      - Mocks
      summary: Generate a valid request and response
      operationId: sampleMockOperation
      parameters:
      - name: name
        in: path
        required: true
        schema:
          type: string
        description: Mock name
      - name: op
        in: path
        required: true
        schema:
          type: string
        description: operationId
      - name: status
        in: query
        schema:
          type: integer
      - name: mode
        in: query
        schema:
          type: string
          enum:
          - examples
          - generated
          - static
          - stateful
      - name: example
        in: query
        schema:
          type: string
      - name: seed
        in: query
        schema:
          type: integer
      responses:
        '200':
          description: OK
        '404':
          description: Unknown mock
        '403':
          description: 'permission_required: the caller lacks mocks.read'
      x-rustybin-permission: mocks.read
  /_rustybin/mocks/{name}/operations/{op}/validate:
    post:
      tags:
      - Mocks
      summary: Validate a body
      operationId: validateMockBody
      parameters:
      - name: name
        in: path
        required: true
        schema:
          type: string
        description: Mock name
      - name: op
        in: path
        required: true
        schema:
          type: string
        description: operationId
      responses:
        '200':
          description: OK
        '404':
          description: Unknown mock
        '403':
          description: 'permission_required: the caller lacks mocks.read'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - body
              properties:
                kind:
                  type: string
                  enum:
                  - response
                  - request
                status:
                  type: integer
                body: {}
      x-rustybin-permission: mocks.read
  /_rustybin/mocks/{name}/requests:
    get:
      tags:
      - Mocks
      summary: Captured requests
      operationId: listMockRequests
      parameters:
      - name: name
        in: path
        required: true
        schema:
          type: string
        description: Mock name
      - name: limit
        in: query
        schema:
          type: integer
      responses:
        '200':
          description: OK
        '404':
          description: Unknown mock
        '403':
          description: 'permission_required: the caller lacks traffic.read'
      x-rustybin-permission: traffic.read
    delete:
      tags:
      - Mocks
      summary: Clear captured requests
      operationId: clearMockRequests
      parameters:
      - name: name
        in: path
        required: true
        schema:
          type: string
        description: Mock name
      responses:
        '200':
          description: OK
        '401':
          description: Control-plane credentials required
        '403':
          description: Admin token or admin scope required
        '404':
          description: Unknown mock
      x-rustybin-permission: mocks.data
  /_rustybin/mocks/{name}/requests/stream:
    get:
      tags:
      - Mocks
      summary: Live feed of captured requests (SSE)
      operationId: streamMockRequests
      parameters:
      - name: name
        in: path
        required: true
        schema:
          type: string
        description: Mock name
      responses:
        '200':
          description: text/event-stream of `request` events
          content:
            text/event-stream: {}
        '404':
          description: Unknown mock
        '403':
          description: 'permission_required: the caller lacks traffic.read'
      x-rustybin-permission: traffic.read
  /_rustybin/mocks/{name}/requests/{n}:
    get:
      tags:
      - Mocks
      summary: One captured request
      operationId: getMockRequest
      parameters:
      - name: name
        in: path
        required: true
        schema:
          type: string
        description: Mock name
      - name: n
        in: path
        required: true
        schema:
          type: integer
      responses:
        '200':
          description: OK
        '404':
          description: Unknown mock
        '403':
          description: 'permission_required: the caller lacks traffic.read'
      x-rustybin-permission: traffic.read
  /_rustybin/mocks/visit:
    get:
      tags:
      - Mocks
      summary: The caller's self-service visit
      operationId: getMockVisit
      parameters:
      - name: X-Rustybin-Mock-Token
        in: header
        required: false
        schema:
          type: string
        description: Manage token of the visit (self-service)
      responses:
        '200':
          description: The visit
        '201':
          description: 'Visit started: manage_token (shown once) and the visit'
        '403':
          description: 'rustybin_email_required: give an email address'
        '404':
          description: Self-service off, or no running visit for the token
        '409':
          description: rustybin_extend_too_early
        '429':
          description: rustybin_daily_limit or rustybin_visit_limit (Retry-After)
        '503':
          description: rustybin_mocks_busy or rustybin_self_service_paused (Retry-After)
      x-rustybin-permission: mocks.read
    post:
      tags:
      - Mocks
      summary: Start a self-service visit
      operationId: startMockVisit
      parameters:
      - name: X-Rustybin-Mock-Token
        in: header
        required: false
        schema:
          type: string
        description: Manage token of the visit (self-service)
      responses:
        '200':
          description: The visit
        '201':
          description: 'Visit started: manage_token (shown once) and the visit'
        '403':
          description: 'rustybin_email_required: give an email address'
        '404':
          description: Self-service off, or no running visit for the token
        '409':
          description: rustybin_extend_too_early
        '429':
          description: rustybin_daily_limit or rustybin_visit_limit (Retry-After)
        '503':
          description: rustybin_mocks_busy or rustybin_self_service_paused (Retry-After)
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                email:
                  type: string
                  format: email
                marketing_consent:
                  type: boolean
      x-rustybin-permission: signed in
    delete:
      tags:
      - Mocks
      summary: End the visit and delete its mocks
      operationId: endMockVisit
      parameters:
      - name: X-Rustybin-Mock-Token
        in: header
        required: false
        schema:
          type: string
        description: Manage token of the visit (self-service)
      responses:
        '200':
          description: The visit
        '201':
          description: 'Visit started: manage_token (shown once) and the visit'
        '403':
          description: 'rustybin_email_required: give an email address'
        '404':
          description: Self-service off, or no running visit for the token
        '409':
          description: rustybin_extend_too_early
        '429':
          description: rustybin_daily_limit or rustybin_visit_limit (Retry-After)
        '503':
          description: rustybin_mocks_busy or rustybin_self_service_paused (Retry-After)
      x-rustybin-permission: signed in
  /_rustybin/mocks/visit/extend:
    post:
      tags:
      - Mocks
      summary: One more period (optionally with the email step)
      operationId: extendMockVisit
      parameters:
      - name: X-Rustybin-Mock-Token
        in: header
        required: false
        schema:
          type: string
        description: Manage token of the visit (self-service)
      responses:
        '200':
          description: The visit
        '201':
          description: 'Visit started: manage_token (shown once) and the visit'
        '403':
          description: 'rustybin_email_required: give an email address'
        '404':
          description: Self-service off, or no running visit for the token
        '409':
          description: rustybin_extend_too_early
        '429':
          description: rustybin_daily_limit or rustybin_visit_limit (Retry-After)
        '503':
          description: rustybin_mocks_busy or rustybin_self_service_paused (Retry-After)
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                email:
                  type: string
                  format: email
                marketing_consent:
                  type: boolean
      x-rustybin-permission: signed in
  /_rustybin/mocks/visit/token:
    post:
      tags:
      - Mocks
      summary: Replace the manage token
      operationId: rotateMockVisitToken
      parameters:
      - name: X-Rustybin-Mock-Token
        in: header
        required: false
        schema:
          type: string
        description: Manage token of the visit (self-service)
      responses:
        '200':
          description: The visit
        '201':
          description: 'Visit started: manage_token (shown once) and the visit'
        '403':
          description: 'rustybin_email_required: give an email address'
        '404':
          description: Self-service off, or no running visit for the token
        '409':
          description: rustybin_extend_too_early
        '429':
          description: rustybin_daily_limit or rustybin_visit_limit (Retry-After)
        '503':
          description: rustybin_mocks_busy or rustybin_self_service_paused (Retry-After)
      x-rustybin-permission: signed in
  /_rustybin/mocks/visits:
    get:
      tags:
      - Mocks
      summary: Running visits (operators)
      operationId: listMockVisits
      parameters: []
      responses:
        '200':
          description: OK
        '401':
          description: Control-plane credentials required
        '403':
          description: Admin token or admin scope required
        '404':
          description: Unknown mock
      x-rustybin-permission: visits.operate
    delete:
      tags:
      - Mocks
      summary: End every visit (operators)
      operationId: endAllMockVisits
      parameters: []
      responses:
        '200':
          description: OK
        '401':
          description: Control-plane credentials required
        '403':
          description: Admin token or admin scope required
        '404':
          description: Unknown mock
      x-rustybin-permission: visits.operate
  /_rustybin/mocks/visits/pause:
    post:
      tags:
      - Mocks
      summary: Stop new visits (operators)
      operationId: pauseMockVisits
      parameters: []
      responses:
        '200':
          description: OK
        '401':
          description: Control-plane credentials required
        '403':
          description: Admin token or admin scope required
        '404':
          description: Unknown mock
      x-rustybin-permission: visits.operate
  /_rustybin/mocks/visits/resume:
    post:
      tags:
      - Mocks
      summary: Allow new visits (operators)
      operationId: resumeMockVisits
      parameters: []
      responses:
        '200':
          description: OK
        '401':
          description: Control-plane credentials required
        '403':
          description: Admin token or admin scope required
        '404':
          description: Unknown mock
      x-rustybin-permission: visits.operate
  /_rustybin/mocks/visits/{id}:
    delete:
      tags:
      - Mocks
      summary: End one visit (operators)
      operationId: endMockVisit
      parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
        description: Visit id from GET /_rustybin/mocks/visits
      responses:
        '200':
          description: OK
        '401':
          description: Control-plane credentials required
        '403':
          description: Admin token or admin scope required
        '404':
          description: Unknown mock
      x-rustybin-permission: visits.operate
  /sse:
    get:
      tags:
      - Streaming
      summary: Numbered Server-Sent Events stream
      operationId: sseStream
      parameters:
      - name: count
        in: query
        required: false
        schema:
          type: integer
        description: Number of events (default 10)
      - name: interval_ms
        in: query
        required: false
        schema:
          type: integer
        description: Delay between events (default 1000)
      - name: event
        in: query
        required: false
        schema:
          type: string
        description: Event name
      - name: retry
        in: query
        required: false
        schema:
          type: integer
        description: Reconnection delay sent with the first event
      - name: mode
        in: query
        required: false
        schema:
          type: string
        description: json (default) or text
      - name: heartbeat_ms
        in: query
        required: false
        schema:
          type: integer
        description: Comment heartbeat interval (default 15000, 0 disables)
      - name: last_event_id
        in: query
        required: false
        schema:
          type: string
        description: Resume after this id (Last-Event-ID header wins)
      - name: Last-Event-ID
        in: header
        required: false
        schema:
          type: string
      responses:
        '200':
          description: text/event-stream
          content:
            text/event-stream: {}
        '400':
          description: Invalid parameters
  /sse/chat:
    get:
      tags:
      - Streaming
      summary: Streamed chat-style text
      operationId: sseChat
      parameters:
      - name: prompt
        in: query
        required: false
        schema:
          type: string
        description: Prompt echoed in the reply
      - name: max_tokens
        in: query
        required: false
        schema:
          type: integer
        description: Maximum number of word tokens
      - name: delay_ms
        in: query
        required: false
        schema:
          type: integer
        description: Delay between tokens (default 50)
      responses:
        '200':
          description: text/event-stream
          content:
            text/event-stream: {}
        '400':
          description: Invalid parameters
    post:
      tags:
      - Streaming
      summary: Streamed chat-style text
      operationId: sseChatPost
      requestBody:
        required: false
        content:
          application/json:
            schema:
              type: object
              properties:
                prompt:
                  type: string
                max_tokens:
                  type: integer
                delay_ms:
                  type: integer
      responses:
        '200':
          description: text/event-stream
          content:
            text/event-stream: {}
        '400':
          description: Invalid parameters
  /webhooks:
    get:
      tags:
      - Webhooks
      summary: Webhook schemes and demo secrets
      operationId: webhooksInfo
      responses:
        '200':
          description: Schemes and demo secrets
  /webhooks/verify:
    post:
      tags:
      - Webhooks
      summary: Verify a Standard Webhooks signature
      operationId: verifyWebhook
      parameters:
      - name: X-Webhook-Secret
        in: header
        required: false
        schema:
          type: string
        description: Signing secret (or ?secret=)
      - name: secret
        in: query
        required: false
        schema:
          type: string
      - name: tolerance
        in: query
        required: false
        schema:
          type: integer
          default: 300
      - name: ignore_timestamp
        in: query
        required: false
        schema:
          type: boolean
      - name: webhook-id
        in: header
        schema:
          type: string
      - name: webhook-timestamp
        in: header
        schema:
          type: string
      - name: webhook-signature
        in: header
        schema:
          type: string
      requestBody:
        required: true
        content:
          '*/*':
            schema:
              type: string
      responses:
        '200':
          description: Valid signature (report)
        '400':
          description: 'Malformed input: missing_headers, missing_secret, invalid_secret, invalid_timestamp'
        '401':
          description: 'Invalid: signature_mismatch, timestamp_too_old, timestamp_too_new, no_supported_signature'
  /webhooks/verify/{scheme}:
    post:
      tags:
      - Webhooks
      summary: Verify a webhook signature (standard, github, stripe)
      operationId: verifyWebhookScheme
      parameters:
      - name: scheme
        in: path
        required: true
        schema:
          type: string
          enum:
          - standard
          - github
          - stripe
      - name: X-Webhook-Secret
        in: header
        required: false
        schema:
          type: string
        description: Signing secret (or ?secret=)
      - name: secret
        in: query
        required: false
        schema:
          type: string
      - name: tolerance
        in: query
        required: false
        schema:
          type: integer
          default: 300
      - name: ignore_timestamp
        in: query
        required: false
        schema:
          type: boolean
      requestBody:
        required: true
        content:
          '*/*':
            schema:
              type: string
      responses:
        '200':
          description: Valid signature (report)
        '400':
          description: 'Malformed input: missing_headers, missing_secret, invalid_secret, invalid_timestamp'
        '401':
          description: 'Invalid: signature_mismatch, timestamp_too_old, timestamp_too_new, no_supported_signature'
  /webhooks/sign:
    get:
      tags:
      - Webhooks
      summary: Produce a signed example webhook
      operationId: signWebhook
      parameters:
      - name: scheme
        in: query
        schema:
          type: string
          enum:
          - standard
          - github
          - stripe
      - name: secret
        in: query
        required: false
        schema:
          type: string
      - name: payload
        in: query
        schema:
          type: string
      - name: id
        in: query
        schema:
          type: string
      - name: timestamp
        in: query
        schema:
          type: integer
      responses:
        '200':
          description: Headers, body, signed content and curl command
        '400':
          description: Invalid secret or scheme
    post:
      tags:
      - Webhooks
      summary: Sign the request body
      operationId: signWebhookBody
      parameters:
      - name: scheme
        in: query
        schema:
          type: string
      - name: secret
        in: query
        required: false
        schema:
          type: string
      - name: id
        in: query
        schema:
          type: string
      - name: timestamp
        in: query
        schema:
          type: integer
      requestBody:
        required: true
        content:
          '*/*':
            schema:
              type: string
      responses:
        '200':
          description: Headers, body, signed content and curl command
        '400':
          description: Invalid secret or scheme
  /webhooks/receive/{secret_id}:
    post:
      tags:
      - Webhooks
      summary: Receive a webhook signed with a demo secret
      operationId: receiveWebhook
      parameters:
      - name: secret_id
        in: path
        required: true
        schema:
          type: string
          enum:
          - demo
          - github-docs
      requestBody:
        required: true
        content:
          '*/*':
            schema:
              type: string
      responses:
        '204':
          description: Valid
        '401':
          description: Invalid or unsigned
        '404':
          description: Unknown secret id
  /guardrails/azure/contentsafety/{operation}:
    post:
      tags:
      - Guardrails
      summary: Azure AI Content Safety style analysis
      description: 'operation: text:analyze or text:shieldPrompt'
      operationId: guardrailsAzureContentSafety
      parameters:
      - name: operation
        in: path
        required: true
        schema:
          type: string
          enum:
          - text:analyze
          - text:shieldPrompt
      - name: api-version
        in: query
        required: false
        schema:
          type: string
          example: 2024-09-01
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                text:
                  type: string
                categories:
                  type: array
                  items:
                    type: string
                    enum:
                    - Hate
                    - SelfHarm
                    - Sexual
                    - Violence
                blocklistNames:
                  type: array
                  items:
                    type: string
                haltOnBlocklistHit:
                  type: boolean
                outputType:
                  type: string
                  enum:
                  - FourSeverityLevels
                  - EightSeverityLevels
                userPrompt:
                  type: string
                documents:
                  type: array
                  items:
                    type: string
      responses:
        '200':
          description: categoriesAnalysis + blocklistsMatch, or userPromptAnalysis + documentsAnalysis
        '400':
          description: InvalidRequestBody
        '404':
          description: Unknown operation
  /guardrails/bedrock/guardrail/{id}/version/{version}/apply:
    post:
      tags:
      - Guardrails
      summary: AWS Bedrock ApplyGuardrail style assessment
      operationId: guardrailsBedrockApply
      parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
      - name: version
        in: path
        required: true
        schema:
          type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - source
              - content
              properties:
                source:
                  type: string
                  enum:
                  - INPUT
                  - OUTPUT
                outputScope:
                  type: string
                  enum:
                  - INTERVENTIONS
                  - FULL
                content:
                  type: array
                  items:
                    type: object
                    properties:
                      text:
                        type: object
                        properties:
                          text:
                            type: string
                          qualifiers:
                            type: array
                            items:
                              type: string
      responses:
        '200':
          description: action (GUARDRAIL_INTERVENED or NONE), outputs, assessments, usage
        '400':
          description: ValidationException
  /guardrails/check:
    post:
      tags:
      - Guardrails
      summary: Generic guardrail check
      operationId: guardrailsCheck
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                text:
                  type: string
          text/plain:
            schema:
              type: string
      responses:
        '200':
          description: flagged, action, categories, prompt_injection, blocklist, pii, redacted_text
        '400':
          description: Invalid body
  /guardrails/pii/redact:
    post:
      tags:
      - Guardrails
      summary: Redact PII
      operationId: guardrailsPiiRedact
      parameters:
      - name: mode
        in: query
        schema:
          type: string
          enum:
          - label
          - mask
      - name: types
        in: query
        schema:
          type: string
        description: Comma separated PII types
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                text:
                  type: string
                mode:
                  type: string
                  enum:
                  - label
                  - mask
                types:
                  type: array
                  items:
                    type: string
                    enum:
                    - EMAIL
                    - PHONE
                    - SSN
                    - CREDIT_CARD
                    - IP_ADDRESS
          text/plain:
            schema:
              type: string
      responses:
        '200':
          description: Redacted text (JSON report or text/plain)
        '400':
          description: Invalid body, mode or type
  /gzip:
    get:
      tags:
      - Response Shaping
      summary: 'JSON echo compressed with Content-Encoding: gzip'
      operationId: gzip
      responses:
        '200':
          description: 'Compressed JSON with gzipped: true'
          headers:
            Content-Encoding:
              schema:
                type: string
                enum:
                - gzip
          content:
            application/json: {}
  /deflate:
    get:
      tags:
      - Response Shaping
      summary: 'JSON echo compressed with Content-Encoding: deflate'
      operationId: deflate
      responses:
        '200':
          description: 'Compressed JSON with deflated: true'
          headers:
            Content-Encoding:
              schema:
                type: string
                enum:
                - deflate
          content:
            application/json: {}
  /brotli:
    get:
      tags:
      - Response Shaping
      summary: 'JSON echo compressed with Content-Encoding: br'
      operationId: brotli
      responses:
        '200':
          description: 'Compressed JSON with brotli: true'
          headers:
            Content-Encoding:
              schema:
                type: string
                enum:
                - br
          content:
            application/json: {}
  /zstd:
    get:
      tags:
      - Response Shaping
      summary: 'JSON echo compressed with Content-Encoding: zstd'
      operationId: zstd
      responses:
        '200':
          description: 'Compressed JSON with zstd: true'
          headers:
            Content-Encoding:
              schema:
                type: string
                enum:
                - zstd
          content:
            application/json: {}
  /encoding/utf8:
    get:
      tags:
      - Response Shaping
      summary: UTF-8 sample page
      operationId: encodingUtf8
      responses:
        '200':
          description: HTML
          content:
            text/html: {}
  /range/{n}:
    get:
      tags:
      - Response Shaping
      summary: n bytes with Range support
      operationId: range
      parameters:
      - name: n
        in: path
        required: true
        schema:
          type: integer
          minimum: 1
          maximum: 102400
      - name: Range
        in: header
        required: false
        schema:
          type: string
          example: bytes=0-99
      - name: If-Range
        in: header
        required: false
        schema:
          type: string
      responses:
        '200':
          description: Full body
          content:
            application/octet-stream: {}
        '206':
          description: Partial content (Content-Range)
        '400':
          description: n out of range
        '416':
          description: 'Range not satisfiable (Content-Range: bytes */n)'
  /bytes/{n}:
    get:
      tags:
      - Response Shaping
      summary: Random bytes
      operationId: bytes
      parameters:
      - name: n
        in: path
        required: true
        schema:
          type: integer
          minimum: 0
        description: Number of bytes (capped)
      - name: seed
        in: query
        required: false
        schema:
          type: integer
      responses:
        '200':
          description: Bytes
          content:
            application/octet-stream: {}
        '400':
          description: Invalid n
  /stream/{n}:
    get:
      tags:
      - Response Shaping
      summary: Stream n JSON lines
      operationId: streamLines
      parameters:
      - name: n
        in: path
        required: true
        schema:
          type: integer
          minimum: 0
        description: Number of lines (capped)
      responses:
        '200':
          description: Newline delimited JSON
          content:
            application/json: {}
  /stream-bytes/{n}:
    get:
      tags:
      - Response Shaping
      summary: Stream random bytes
      operationId: streamBytes
      parameters:
      - name: n
        in: path
        required: true
        schema:
          type: integer
          minimum: 0
        description: Number of bytes (capped)
      - name: seed
        in: query
        required: false
        schema:
          type: integer
      - name: chunk_size
        in: query
        required: false
        schema:
          type: integer
          default: 10240
      responses:
        '200':
          description: Bytes
          content:
            application/octet-stream: {}
  /drip:
    get:
      tags:
      - Response Shaping
      summary: Drip bytes over time
      operationId: drip
      parameters:
      - name: duration
        in: query
        schema:
          type: number
          default: 2
      - name: numbytes
        in: query
        schema:
          type: integer
          default: 10
      - name: code
        in: query
        schema:
          type: integer
          default: 200
      - name: delay
        in: query
        schema:
          type: number
          default: 0
      responses:
        '200':
          description: Bytes
          content:
            application/octet-stream: {}
        '400':
          description: Parameter out of range
  /links/{n}:
    get:
      tags:
      - Response Shaping
      summary: Redirect to the first links page
      operationId: linksRoot
      parameters:
      - name: n
        in: path
        required: true
        schema:
          type: integer
          minimum: 0
        description: Number of links
      responses:
        '302':
          description: Redirect to /links/{n}/0
  /links/{n}/{offset}:
    get:
      tags:
      - Response Shaping
      summary: HTML page of links
      operationId: links
      parameters:
      - name: n
        in: path
        required: true
        schema:
          type: integer
          minimum: 0
        description: Number of links (max 200)
      - name: offset
        in: path
        required: true
        schema:
          type: integer
      responses:
        '200':
          description: HTML
          content:
            text/html: {}
  /base64/{value}:
    get:
      tags:
      - Response Shaping
      summary: Decode base64
      operationId: base64Decode
      parameters:
      - name: value
        in: path
        required: true
        schema:
          type: string
      responses:
        '200':
          description: Decoded text or bytes
        '400':
          description: Invalid base64
  /redirect-to:
    get:
      tags:
      - Redirects & Cookies
      summary: Redirect to a relative or same-host URL
      operationId: redirectTo
      parameters:
      - name: url
        in: query
        required: true
        schema:
          type: string
      - name: status_code
        in: query
        required: false
        schema:
          type: integer
          default: 302
      responses:
        '302':
          description: Redirect
        '400':
          description: Target refused (external or malformed)
    post:
      tags:
      - Redirects & Cookies
      summary: Redirect to a relative or same-host URL
      operationId: redirectTo_post
      parameters:
      - name: url
        in: query
        required: true
        schema:
          type: string
      - name: status_code
        in: query
        required: false
        schema:
          type: integer
          default: 302
      responses:
        '302':
          description: Redirect
        '400':
          description: Target refused (external or malformed)
    put:
      tags:
      - Redirects & Cookies
      summary: Redirect to a relative or same-host URL
      operationId: redirectTo_put
      parameters:
      - name: url
        in: query
        required: true
        schema:
          type: string
      - name: status_code
        in: query
        required: false
        schema:
          type: integer
          default: 302
      responses:
        '302':
          description: Redirect
        '400':
          description: Target refused (external or malformed)
    patch:
      tags:
      - Redirects & Cookies
      summary: Redirect to a relative or same-host URL
      operationId: redirectTo_patch
      parameters:
      - name: url
        in: query
        required: true
        schema:
          type: string
      - name: status_code
        in: query
        required: false
        schema:
          type: integer
          default: 302
      responses:
        '302':
          description: Redirect
        '400':
          description: Target refused (external or malformed)
    delete:
      tags:
      - Redirects & Cookies
      summary: Redirect to a relative or same-host URL
      operationId: redirectTo_delete
      parameters:
      - name: url
        in: query
        required: true
        schema:
          type: string
      - name: status_code
        in: query
        required: false
        schema:
          type: integer
          default: 302
      responses:
        '302':
          description: Redirect
        '400':
          description: Target refused (external or malformed)
  /absolute-redirect/{n}:
    get:
      tags:
      - Redirects & Cookies
      summary: Absolute redirect chain
      operationId: absoluteRedirect
      parameters:
      - name: n
        in: path
        required: true
        schema:
          type: integer
          minimum: 0
        description: Remaining redirects (1-10)
      responses:
        '302':
          description: Redirect
        '400':
          description: Invalid n or Host
  /jsonrpc:
    post:
      tags:
      - Orchestration
      summary: JSON-RPC 2.0 endpoint
      operationId: jsonRpc
      requestBody:
        required: true
        content:
          application/json:
            schema:
              oneOf:
              - type: object
                required:
                - jsonrpc
                - method
                properties:
                  jsonrpc:
                    type: string
                    enum:
                    - '2.0'
                  method:
                    type: string
                    enum:
                    - echo
                    - add
                    - subtract
                    - sleep
                    - error
                  params:
                    oneOf:
                    - type: array
                      items: {}
                    - type: object
                  id:
                    oneOf:
                    - type: string
                    - type: number
                    nullable: true
              - type: array
                items:
                  type: object
                  required:
                  - jsonrpc
                  - method
                  properties:
                    jsonrpc:
                      type: string
                      enum:
                      - '2.0'
                    method:
                      type: string
                      enum:
                      - echo
                      - add
                      - subtract
                      - sleep
                      - error
                    params:
                      oneOf:
                      - type: array
                        items: {}
                      - type: object
                    id:
                      oneOf:
                      - type: string
                      - type: number
                      nullable: true
      responses:
        '200':
          description: Response object or array of response objects
        '204':
          description: Only notifications were sent
  /auth/basic-auth:
    get:
      tags:
      - Auth
      summary: HTTP Basic authentication (default credentials)
      description: Validates HTTP Basic auth with the default credentials basic:password.
      operationId: getBasicAuth
      security:
      - basicAuth: []
      responses:
        '200':
          description: Authenticated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AuthResponse'
            application/xml:
              schema:
                $ref: '#/components/schemas/AuthResponse'
        '401':
          description: Unauthorized
          headers:
            WWW-Authenticate:
              schema:
                type: string
                example: Basic realm="rustybin", charset="UTF-8"
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AuthFailure'
            application/xml:
              schema:
                $ref: '#/components/schemas/AuthFailure'
    post:
      tags:
      - Auth
      summary: HTTP Basic authentication (default credentials)
      description: Validates HTTP Basic auth with the default credentials basic:password.
      operationId: getBasicAuth_post
      security:
      - basicAuth: []
      responses:
        '200':
          description: Authenticated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AuthResponse'
            application/xml:
              schema:
                $ref: '#/components/schemas/AuthResponse'
        '401':
          description: Unauthorized
          headers:
            WWW-Authenticate:
              schema:
                type: string
                example: Basic realm="rustybin", charset="UTF-8"
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AuthFailure'
            application/xml:
              schema:
                $ref: '#/components/schemas/AuthFailure'
    put:
      tags:
      - Auth
      summary: HTTP Basic authentication (default credentials)
      description: Validates HTTP Basic auth with the default credentials basic:password.
      operationId: getBasicAuth_put
      security:
      - basicAuth: []
      responses:
        '200':
          description: Authenticated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AuthResponse'
            application/xml:
              schema:
                $ref: '#/components/schemas/AuthResponse'
        '401':
          description: Unauthorized
          headers:
            WWW-Authenticate:
              schema:
                type: string
                example: Basic realm="rustybin", charset="UTF-8"
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AuthFailure'
            application/xml:
              schema:
                $ref: '#/components/schemas/AuthFailure'
    patch:
      tags:
      - Auth
      summary: HTTP Basic authentication (default credentials)
      description: Validates HTTP Basic auth with the default credentials basic:password.
      operationId: getBasicAuth_patch
      security:
      - basicAuth: []
      responses:
        '200':
          description: Authenticated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AuthResponse'
            application/xml:
              schema:
                $ref: '#/components/schemas/AuthResponse'
        '401':
          description: Unauthorized
          headers:
            WWW-Authenticate:
              schema:
                type: string
                example: Basic realm="rustybin", charset="UTF-8"
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AuthFailure'
            application/xml:
              schema:
                $ref: '#/components/schemas/AuthFailure'
    delete:
      tags:
      - Auth
      summary: HTTP Basic authentication (default credentials)
      description: Validates HTTP Basic auth with the default credentials basic:password.
      operationId: getBasicAuth_delete
      security:
      - basicAuth: []
      responses:
        '200':
          description: Authenticated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AuthResponse'
            application/xml:
              schema:
                $ref: '#/components/schemas/AuthResponse'
        '401':
          description: Unauthorized
          headers:
            WWW-Authenticate:
              schema:
                type: string
                example: Basic realm="rustybin", charset="UTF-8"
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AuthFailure'
            application/xml:
              schema:
                $ref: '#/components/schemas/AuthFailure'
  /auth/basic-auth/{username}/{password}:
    get:
      tags:
      - Auth
      summary: HTTP Basic authentication (custom credentials)
      description: Validates HTTP Basic auth against the username and password given in the path.
      operationId: getBasicAuthCustom
      security:
      - basicAuth: []
      parameters:
      - name: username
        in: path
        required: true
        schema:
          type: string
        description: Expected username
      - name: password
        in: path
        required: true
        schema:
          type: string
        description: Expected password
      responses:
        '200':
          description: Authenticated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AuthResponse'
            application/xml:
              schema:
                $ref: '#/components/schemas/AuthResponse'
        '401':
          description: Unauthorized
          headers:
            WWW-Authenticate:
              schema:
                type: string
                example: Basic realm="rustybin", charset="UTF-8"
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AuthFailure'
            application/xml:
              schema:
                $ref: '#/components/schemas/AuthFailure'
    post:
      tags:
      - Auth
      summary: HTTP Basic authentication (custom credentials)
      description: Validates HTTP Basic auth against the username and password given in the path.
      operationId: getBasicAuthCustom_post
      security:
      - basicAuth: []
      parameters:
      - name: username
        in: path
        required: true
        schema:
          type: string
        description: Expected username
      - name: password
        in: path
        required: true
        schema:
          type: string
        description: Expected password
      responses:
        '200':
          description: Authenticated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AuthResponse'
            application/xml:
              schema:
                $ref: '#/components/schemas/AuthResponse'
        '401':
          description: Unauthorized
          headers:
            WWW-Authenticate:
              schema:
                type: string
                example: Basic realm="rustybin", charset="UTF-8"
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AuthFailure'
            application/xml:
              schema:
                $ref: '#/components/schemas/AuthFailure'
    put:
      tags:
      - Auth
      summary: HTTP Basic authentication (custom credentials)
      description: Validates HTTP Basic auth against the username and password given in the path.
      operationId: getBasicAuthCustom_put
      security:
      - basicAuth: []
      parameters:
      - name: username
        in: path
        required: true
        schema:
          type: string
        description: Expected username
      - name: password
        in: path
        required: true
        schema:
          type: string
        description: Expected password
      responses:
        '200':
          description: Authenticated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AuthResponse'
            application/xml:
              schema:
                $ref: '#/components/schemas/AuthResponse'
        '401':
          description: Unauthorized
          headers:
            WWW-Authenticate:
              schema:
                type: string
                example: Basic realm="rustybin", charset="UTF-8"
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AuthFailure'
            application/xml:
              schema:
                $ref: '#/components/schemas/AuthFailure'
    patch:
      tags:
      - Auth
      summary: HTTP Basic authentication (custom credentials)
      description: Validates HTTP Basic auth against the username and password given in the path.
      operationId: getBasicAuthCustom_patch
      security:
      - basicAuth: []
      parameters:
      - name: username
        in: path
        required: true
        schema:
          type: string
        description: Expected username
      - name: password
        in: path
        required: true
        schema:
          type: string
        description: Expected password
      responses:
        '200':
          description: Authenticated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AuthResponse'
            application/xml:
              schema:
                $ref: '#/components/schemas/AuthResponse'
        '401':
          description: Unauthorized
          headers:
            WWW-Authenticate:
              schema:
                type: string
                example: Basic realm="rustybin", charset="UTF-8"
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AuthFailure'
            application/xml:
              schema:
                $ref: '#/components/schemas/AuthFailure'
    delete:
      tags:
      - Auth
      summary: HTTP Basic authentication (custom credentials)
      description: Validates HTTP Basic auth against the username and password given in the path.
      operationId: getBasicAuthCustom_delete
      security:
      - basicAuth: []
      parameters:
      - name: username
        in: path
        required: true
        schema:
          type: string
        description: Expected username
      - name: password
        in: path
        required: true
        schema:
          type: string
        description: Expected password
      responses:
        '200':
          description: Authenticated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AuthResponse'
            application/xml:
              schema:
                $ref: '#/components/schemas/AuthResponse'
        '401':
          description: Unauthorized
          headers:
            WWW-Authenticate:
              schema:
                type: string
                example: Basic realm="rustybin", charset="UTF-8"
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AuthFailure'
            application/xml:
              schema:
                $ref: '#/components/schemas/AuthFailure'
  /auth/api-key:
    get:
      tags:
      - Auth
      summary: API key authentication (default header)
      description: Validates the API key in the `apikey` header against the default value `my-key`.
      operationId: getApiKey
      security:
      - apiKeyAuth: []
      responses:
        '200':
          description: Authenticated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AuthResponse'
            application/xml:
              schema:
                $ref: '#/components/schemas/AuthResponse'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AuthFailure'
            application/xml:
              schema:
                $ref: '#/components/schemas/AuthFailure'
    post:
      tags:
      - Auth
      summary: API key authentication (default header)
      description: Validates the API key in the `apikey` header against the default value `my-key`.
      operationId: getApiKey_post
      security:
      - apiKeyAuth: []
      responses:
        '200':
          description: Authenticated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AuthResponse'
            application/xml:
              schema:
                $ref: '#/components/schemas/AuthResponse'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AuthFailure'
            application/xml:
              schema:
                $ref: '#/components/schemas/AuthFailure'
    put:
      tags:
      - Auth
      summary: API key authentication (default header)
      description: Validates the API key in the `apikey` header against the default value `my-key`.
      operationId: getApiKey_put
      security:
      - apiKeyAuth: []
      responses:
        '200':
          description: Authenticated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AuthResponse'
            application/xml:
              schema:
                $ref: '#/components/schemas/AuthResponse'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AuthFailure'
            application/xml:
              schema:
                $ref: '#/components/schemas/AuthFailure'
    patch:
      tags:
      - Auth
      summary: API key authentication (default header)
      description: Validates the API key in the `apikey` header against the default value `my-key`.
      operationId: getApiKey_patch
      security:
      - apiKeyAuth: []
      responses:
        '200':
          description: Authenticated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AuthResponse'
            application/xml:
              schema:
                $ref: '#/components/schemas/AuthResponse'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AuthFailure'
            application/xml:
              schema:
                $ref: '#/components/schemas/AuthFailure'
    delete:
      tags:
      - Auth
      summary: API key authentication (default header)
      description: Validates the API key in the `apikey` header against the default value `my-key`.
      operationId: getApiKey_delete
      security:
      - apiKeyAuth: []
      responses:
        '200':
          description: Authenticated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AuthResponse'
            application/xml:
              schema:
                $ref: '#/components/schemas/AuthResponse'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AuthFailure'
            application/xml:
              schema:
                $ref: '#/components/schemas/AuthFailure'
  /auth/api-key/{header_name}/{key_value}:
    get:
      tags:
      - Auth
      summary: API key authentication (custom header and value)
      description: Validates the API key in a custom header against a custom value, both from the path.
      operationId: getApiKeyCustom
      parameters:
      - name: header_name
        in: path
        required: true
        schema:
          type: string
        description: Header name to check
      - name: key_value
        in: path
        required: true
        schema:
          type: string
        description: Expected key value
      responses:
        '200':
          description: Authenticated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AuthResponse'
            application/xml:
              schema:
                $ref: '#/components/schemas/AuthResponse'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AuthFailure'
            application/xml:
              schema:
                $ref: '#/components/schemas/AuthFailure'
    post:
      tags:
      - Auth
      summary: API key authentication (custom header and value)
      description: Validates the API key in a custom header against a custom value, both from the path.
      operationId: getApiKeyCustom_post
      parameters:
      - name: header_name
        in: path
        required: true
        schema:
          type: string
        description: Header name to check
      - name: key_value
        in: path
        required: true
        schema:
          type: string
        description: Expected key value
      responses:
        '200':
          description: Authenticated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AuthResponse'
            application/xml:
              schema:
                $ref: '#/components/schemas/AuthResponse'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AuthFailure'
            application/xml:
              schema:
                $ref: '#/components/schemas/AuthFailure'
    put:
      tags:
      - Auth
      summary: API key authentication (custom header and value)
      description: Validates the API key in a custom header against a custom value, both from the path.
      operationId: getApiKeyCustom_put
      parameters:
      - name: header_name
        in: path
        required: true
        schema:
          type: string
        description: Header name to check
      - name: key_value
        in: path
        required: true
        schema:
          type: string
        description: Expected key value
      responses:
        '200':
          description: Authenticated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AuthResponse'
            application/xml:
              schema:
                $ref: '#/components/schemas/AuthResponse'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AuthFailure'
            application/xml:
              schema:
                $ref: '#/components/schemas/AuthFailure'
    patch:
      tags:
      - Auth
      summary: API key authentication (custom header and value)
      description: Validates the API key in a custom header against a custom value, both from the path.
      operationId: getApiKeyCustom_patch
      parameters:
      - name: header_name
        in: path
        required: true
        schema:
          type: string
        description: Header name to check
      - name: key_value
        in: path
        required: true
        schema:
          type: string
        description: Expected key value
      responses:
        '200':
          description: Authenticated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AuthResponse'
            application/xml:
              schema:
                $ref: '#/components/schemas/AuthResponse'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AuthFailure'
            application/xml:
              schema:
                $ref: '#/components/schemas/AuthFailure'
    delete:
      tags:
      - Auth
      summary: API key authentication (custom header and value)
      description: Validates the API key in a custom header against a custom value, both from the path.
      operationId: getApiKeyCustom_delete
      parameters:
      - name: header_name
        in: path
        required: true
        schema:
          type: string
        description: Header name to check
      - name: key_value
        in: path
        required: true
        schema:
          type: string
        description: Expected key value
      responses:
        '200':
          description: Authenticated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AuthResponse'
            application/xml:
              schema:
                $ref: '#/components/schemas/AuthResponse'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AuthFailure'
            application/xml:
              schema:
                $ref: '#/components/schemas/AuthFailure'
  /auth/hmac:
    get:
      tags:
      - Auth
      summary: HMAC authentication (default credentials alice / secret)
      description: 'Validates `Authorization` or `Proxy-Authorization: hmac username="...", algorithm="hmac-sha256", headers="date request-line", signature="..."` (or draft-cavage `Signature keyId=...`). The signature is base64(HMAC(secret, signing string)); the signing string has one `name: value` line per listed header (default `date`; repeated headers joined with ", "), plus the pseudo headers `request-line` and `(request-target)`. Date or X-Date is required and checked against the clock skew. When `digest` is signed, `Digest: SHA-256=<base64>` must match the body. Supports hmac-sha1/sha256/sha384/sha512. Default credentials: alice / secret.'
      operationId: authHmac
      parameters:
      - name: clock_skew
        in: query
        required: false
        schema:
          type: integer
          minimum: 0
          maximum: 604800
          default: 300
        description: Allowed difference in seconds between Date / X-Date and now (0 disables the check)
      responses:
        '200':
          description: Signature valid
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AuthResponse'
            application/xml:
              schema:
                $ref: '#/components/schemas/AuthResponse'
        '400':
          description: Invalid clock_skew
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AuthFailure'
            application/xml:
              schema:
                $ref: '#/components/schemas/AuthFailure'
        '401':
          description: Missing or invalid signature, date outside the clock skew, or digest mismatch
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AuthFailure'
            application/xml:
              schema:
                $ref: '#/components/schemas/AuthFailure'
    post:
      tags:
      - Auth
      summary: HMAC authentication (default credentials alice / secret)
      description: 'Validates `Authorization` or `Proxy-Authorization: hmac username="...", algorithm="hmac-sha256", headers="date request-line", signature="..."` (or draft-cavage `Signature keyId=...`). The signature is base64(HMAC(secret, signing string)); the signing string has one `name: value` line per listed header (default `date`; repeated headers joined with ", "), plus the pseudo headers `request-line` and `(request-target)`. Date or X-Date is required and checked against the clock skew. When `digest` is signed, `Digest: SHA-256=<base64>` must match the body. Supports hmac-sha1/sha256/sha384/sha512. Default credentials: alice / secret.'
      operationId: authHmac_post
      parameters:
      - name: clock_skew
        in: query
        required: false
        schema:
          type: integer
          minimum: 0
          maximum: 604800
          default: 300
        description: Allowed difference in seconds between Date / X-Date and now (0 disables the check)
      responses:
        '200':
          description: Signature valid
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AuthResponse'
            application/xml:
              schema:
                $ref: '#/components/schemas/AuthResponse'
        '400':
          description: Invalid clock_skew
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AuthFailure'
            application/xml:
              schema:
                $ref: '#/components/schemas/AuthFailure'
        '401':
          description: Missing or invalid signature, date outside the clock skew, or digest mismatch
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AuthFailure'
            application/xml:
              schema:
                $ref: '#/components/schemas/AuthFailure'
    put:
      tags:
      - Auth
      summary: HMAC authentication (default credentials alice / secret)
      description: 'Validates `Authorization` or `Proxy-Authorization: hmac username="...", algorithm="hmac-sha256", headers="date request-line", signature="..."` (or draft-cavage `Signature keyId=...`). The signature is base64(HMAC(secret, signing string)); the signing string has one `name: value` line per listed header (default `date`; repeated headers joined with ", "), plus the pseudo headers `request-line` and `(request-target)`. Date or X-Date is required and checked against the clock skew. When `digest` is signed, `Digest: SHA-256=<base64>` must match the body. Supports hmac-sha1/sha256/sha384/sha512. Default credentials: alice / secret.'
      operationId: authHmac_put
      parameters:
      - name: clock_skew
        in: query
        required: false
        schema:
          type: integer
          minimum: 0
          maximum: 604800
          default: 300
        description: Allowed difference in seconds between Date / X-Date and now (0 disables the check)
      responses:
        '200':
          description: Signature valid
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AuthResponse'
            application/xml:
              schema:
                $ref: '#/components/schemas/AuthResponse'
        '400':
          description: Invalid clock_skew
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AuthFailure'
            application/xml:
              schema:
                $ref: '#/components/schemas/AuthFailure'
        '401':
          description: Missing or invalid signature, date outside the clock skew, or digest mismatch
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AuthFailure'
            application/xml:
              schema:
                $ref: '#/components/schemas/AuthFailure'
    patch:
      tags:
      - Auth
      summary: HMAC authentication (default credentials alice / secret)
      description: 'Validates `Authorization` or `Proxy-Authorization: hmac username="...", algorithm="hmac-sha256", headers="date request-line", signature="..."` (or draft-cavage `Signature keyId=...`). The signature is base64(HMAC(secret, signing string)); the signing string has one `name: value` line per listed header (default `date`; repeated headers joined with ", "), plus the pseudo headers `request-line` and `(request-target)`. Date or X-Date is required and checked against the clock skew. When `digest` is signed, `Digest: SHA-256=<base64>` must match the body. Supports hmac-sha1/sha256/sha384/sha512. Default credentials: alice / secret.'
      operationId: authHmac_patch
      parameters:
      - name: clock_skew
        in: query
        required: false
        schema:
          type: integer
          minimum: 0
          maximum: 604800
          default: 300
        description: Allowed difference in seconds between Date / X-Date and now (0 disables the check)
      responses:
        '200':
          description: Signature valid
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AuthResponse'
            application/xml:
              schema:
                $ref: '#/components/schemas/AuthResponse'
        '400':
          description: Invalid clock_skew
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AuthFailure'
            application/xml:
              schema:
                $ref: '#/components/schemas/AuthFailure'
        '401':
          description: Missing or invalid signature, date outside the clock skew, or digest mismatch
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AuthFailure'
            application/xml:
              schema:
                $ref: '#/components/schemas/AuthFailure'
    delete:
      tags:
      - Auth
      summary: HMAC authentication (default credentials alice / secret)
      description: 'Validates `Authorization` or `Proxy-Authorization: hmac username="...", algorithm="hmac-sha256", headers="date request-line", signature="..."` (or draft-cavage `Signature keyId=...`). The signature is base64(HMAC(secret, signing string)); the signing string has one `name: value` line per listed header (default `date`; repeated headers joined with ", "), plus the pseudo headers `request-line` and `(request-target)`. Date or X-Date is required and checked against the clock skew. When `digest` is signed, `Digest: SHA-256=<base64>` must match the body. Supports hmac-sha1/sha256/sha384/sha512. Default credentials: alice / secret.'
      operationId: authHmac_delete
      parameters:
      - name: clock_skew
        in: query
        required: false
        schema:
          type: integer
          minimum: 0
          maximum: 604800
          default: 300
        description: Allowed difference in seconds between Date / X-Date and now (0 disables the check)
      responses:
        '200':
          description: Signature valid
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AuthResponse'
            application/xml:
              schema:
                $ref: '#/components/schemas/AuthResponse'
        '400':
          description: Invalid clock_skew
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AuthFailure'
            application/xml:
              schema:
                $ref: '#/components/schemas/AuthFailure'
        '401':
          description: Missing or invalid signature, date outside the clock skew, or digest mismatch
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AuthFailure'
            application/xml:
              schema:
                $ref: '#/components/schemas/AuthFailure'
  /auth/hmac/{username}/{secret}:
    get:
      tags:
      - Auth
      summary: HMAC authentication (custom credentials)
      description: Same as /auth/hmac but validates against the username and secret supplied in the path.
      operationId: authHmacCustom
      parameters:
      - name: username
        in: path
        required: true
        schema:
          type: string
      - name: secret
        in: path
        required: true
        schema:
          type: string
      - name: clock_skew
        in: query
        required: false
        schema:
          type: integer
          minimum: 0
          maximum: 604800
          default: 300
        description: Allowed difference in seconds between Date / X-Date and now (0 disables the check)
      responses:
        '200':
          description: Signature valid
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AuthResponse'
            application/xml:
              schema:
                $ref: '#/components/schemas/AuthResponse'
        '401':
          description: Missing or invalid signature
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AuthFailure'
            application/xml:
              schema:
                $ref: '#/components/schemas/AuthFailure'
    post:
      tags:
      - Auth
      summary: HMAC authentication (custom credentials)
      description: Same as /auth/hmac but validates against the username and secret supplied in the path.
      operationId: authHmacCustom_post
      parameters:
      - name: username
        in: path
        required: true
        schema:
          type: string
      - name: secret
        in: path
        required: true
        schema:
          type: string
      - name: clock_skew
        in: query
        required: false
        schema:
          type: integer
          minimum: 0
          maximum: 604800
          default: 300
        description: Allowed difference in seconds between Date / X-Date and now (0 disables the check)
      responses:
        '200':
          description: Signature valid
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AuthResponse'
            application/xml:
              schema:
                $ref: '#/components/schemas/AuthResponse'
        '401':
          description: Missing or invalid signature
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AuthFailure'
            application/xml:
              schema:
                $ref: '#/components/schemas/AuthFailure'
    put:
      tags:
      - Auth
      summary: HMAC authentication (custom credentials)
      description: Same as /auth/hmac but validates against the username and secret supplied in the path.
      operationId: authHmacCustom_put
      parameters:
      - name: username
        in: path
        required: true
        schema:
          type: string
      - name: secret
        in: path
        required: true
        schema:
          type: string
      - name: clock_skew
        in: query
        required: false
        schema:
          type: integer
          minimum: 0
          maximum: 604800
          default: 300
        description: Allowed difference in seconds between Date / X-Date and now (0 disables the check)
      responses:
        '200':
          description: Signature valid
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AuthResponse'
            application/xml:
              schema:
                $ref: '#/components/schemas/AuthResponse'
        '401':
          description: Missing or invalid signature
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AuthFailure'
            application/xml:
              schema:
                $ref: '#/components/schemas/AuthFailure'
    patch:
      tags:
      - Auth
      summary: HMAC authentication (custom credentials)
      description: Same as /auth/hmac but validates against the username and secret supplied in the path.
      operationId: authHmacCustom_patch
      parameters:
      - name: username
        in: path
        required: true
        schema:
          type: string
      - name: secret
        in: path
        required: true
        schema:
          type: string
      - name: clock_skew
        in: query
        required: false
        schema:
          type: integer
          minimum: 0
          maximum: 604800
          default: 300
        description: Allowed difference in seconds between Date / X-Date and now (0 disables the check)
      responses:
        '200':
          description: Signature valid
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AuthResponse'
            application/xml:
              schema:
                $ref: '#/components/schemas/AuthResponse'
        '401':
          description: Missing or invalid signature
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AuthFailure'
            application/xml:
              schema:
                $ref: '#/components/schemas/AuthFailure'
    delete:
      tags:
      - Auth
      summary: HMAC authentication (custom credentials)
      description: Same as /auth/hmac but validates against the username and secret supplied in the path.
      operationId: authHmacCustom_delete
      parameters:
      - name: username
        in: path
        required: true
        schema:
          type: string
      - name: secret
        in: path
        required: true
        schema:
          type: string
      - name: clock_skew
        in: query
        required: false
        schema:
          type: integer
          minimum: 0
          maximum: 604800
          default: 300
        description: Allowed difference in seconds between Date / X-Date and now (0 disables the check)
      responses:
        '200':
          description: Signature valid
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AuthResponse'
            application/xml:
              schema:
                $ref: '#/components/schemas/AuthResponse'
        '401':
          description: Missing or invalid signature
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AuthFailure'
            application/xml:
              schema:
                $ref: '#/components/schemas/AuthFailure'
  /auth/jwt:
    get:
      tags:
      - Auth
      summary: JWT validation
      description: 'Verifies the Bearer JWT: HS256 with the demo secret or RS256 with the IdP key (selected by the header alg; none and other algorithms are rejected), exp (required), nbf, and the optional iss / aud query constraints.'
      operationId: getJwt
      security:
      - bearerAuth: []
      parameters:
      - name: iss
        in: query
        required: false
        schema:
          type: string
        description: Required issuer
      - name: aud
        in: query
        required: false
        schema:
          type: string
        description: Required audience
      responses:
        '200':
          description: Token valid
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AuthResponse'
            application/xml:
              schema:
                $ref: '#/components/schemas/AuthResponse'
        '401':
          description: 'Missing or invalid token (WWW-Authenticate: Bearer error="invalid_token")'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AuthFailure'
            application/xml:
              schema:
                $ref: '#/components/schemas/AuthFailure'
    post:
      tags:
      - Auth
      summary: JWT validation
      description: 'Verifies the Bearer JWT: HS256 with the demo secret or RS256 with the IdP key (selected by the header alg; none and other algorithms are rejected), exp (required), nbf, and the optional iss / aud query constraints.'
      operationId: getJwt_post
      security:
      - bearerAuth: []
      parameters:
      - name: iss
        in: query
        required: false
        schema:
          type: string
        description: Required issuer
      - name: aud
        in: query
        required: false
        schema:
          type: string
        description: Required audience
      responses:
        '200':
          description: Token valid
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AuthResponse'
            application/xml:
              schema:
                $ref: '#/components/schemas/AuthResponse'
        '401':
          description: 'Missing or invalid token (WWW-Authenticate: Bearer error="invalid_token")'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AuthFailure'
            application/xml:
              schema:
                $ref: '#/components/schemas/AuthFailure'
    put:
      tags:
      - Auth
      summary: JWT validation
      description: 'Verifies the Bearer JWT: HS256 with the demo secret or RS256 with the IdP key (selected by the header alg; none and other algorithms are rejected), exp (required), nbf, and the optional iss / aud query constraints.'
      operationId: getJwt_put
      security:
      - bearerAuth: []
      parameters:
      - name: iss
        in: query
        required: false
        schema:
          type: string
        description: Required issuer
      - name: aud
        in: query
        required: false
        schema:
          type: string
        description: Required audience
      responses:
        '200':
          description: Token valid
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AuthResponse'
            application/xml:
              schema:
                $ref: '#/components/schemas/AuthResponse'
        '401':
          description: 'Missing or invalid token (WWW-Authenticate: Bearer error="invalid_token")'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AuthFailure'
            application/xml:
              schema:
                $ref: '#/components/schemas/AuthFailure'
    patch:
      tags:
      - Auth
      summary: JWT validation
      description: 'Verifies the Bearer JWT: HS256 with the demo secret or RS256 with the IdP key (selected by the header alg; none and other algorithms are rejected), exp (required), nbf, and the optional iss / aud query constraints.'
      operationId: getJwt_patch
      security:
      - bearerAuth: []
      parameters:
      - name: iss
        in: query
        required: false
        schema:
          type: string
        description: Required issuer
      - name: aud
        in: query
        required: false
        schema:
          type: string
        description: Required audience
      responses:
        '200':
          description: Token valid
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AuthResponse'
            application/xml:
              schema:
                $ref: '#/components/schemas/AuthResponse'
        '401':
          description: 'Missing or invalid token (WWW-Authenticate: Bearer error="invalid_token")'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AuthFailure'
            application/xml:
              schema:
                $ref: '#/components/schemas/AuthFailure'
    delete:
      tags:
      - Auth
      summary: JWT validation
      description: 'Verifies the Bearer JWT: HS256 with the demo secret or RS256 with the IdP key (selected by the header alg; none and other algorithms are rejected), exp (required), nbf, and the optional iss / aud query constraints.'
      operationId: getJwt_delete
      security:
      - bearerAuth: []
      parameters:
      - name: iss
        in: query
        required: false
        schema:
          type: string
        description: Required issuer
      - name: aud
        in: query
        required: false
        schema:
          type: string
        description: Required audience
      responses:
        '200':
          description: Token valid
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AuthResponse'
            application/xml:
              schema:
                $ref: '#/components/schemas/AuthResponse'
        '401':
          description: 'Missing or invalid token (WWW-Authenticate: Bearer error="invalid_token")'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AuthFailure'
            application/xml:
              schema:
                $ref: '#/components/schemas/AuthFailure'
  /auth/jwt/decode:
    get:
      tags:
      - Auth
      summary: JWT decode (no validation)
      description: Returns the header and claims of the Bearer JWT WITHOUT verifying signature or claims.
      operationId: getJwtDecode
      security:
      - bearerAuth: []
      responses:
        '200':
          description: Decoded token
          content:
            application/json:
              schema:
                type: object
                properties:
                  verified:
                    type: boolean
                    example: false
                  jwt_header:
                    type: object
                  claims:
                    type: object
                  expired:
                    type: boolean
                  note:
                    type: string
            application/xml:
              schema:
                type: object
                properties:
                  verified:
                    type: boolean
                    example: false
                  jwt_header:
                    type: object
                  claims:
                    type: object
                  expired:
                    type: boolean
                  note:
                    type: string
        '400':
          description: Malformed token
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AuthFailure'
            application/xml:
              schema:
                $ref: '#/components/schemas/AuthFailure'
        '401':
          description: Missing Bearer token
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AuthFailure'
            application/xml:
              schema:
                $ref: '#/components/schemas/AuthFailure'
    post:
      tags:
      - Auth
      summary: JWT decode (no validation)
      description: Returns the header and claims of the Bearer JWT WITHOUT verifying signature or claims.
      operationId: getJwtDecode_post
      security:
      - bearerAuth: []
      responses:
        '200':
          description: Decoded token
          content:
            application/json:
              schema:
                type: object
                properties:
                  verified:
                    type: boolean
                    example: false
                  jwt_header:
                    type: object
                  claims:
                    type: object
                  expired:
                    type: boolean
                  note:
                    type: string
            application/xml:
              schema:
                type: object
                properties:
                  verified:
                    type: boolean
                    example: false
                  jwt_header:
                    type: object
                  claims:
                    type: object
                  expired:
                    type: boolean
                  note:
                    type: string
        '400':
          description: Malformed token
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AuthFailure'
            application/xml:
              schema:
                $ref: '#/components/schemas/AuthFailure'
        '401':
          description: Missing Bearer token
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AuthFailure'
            application/xml:
              schema:
                $ref: '#/components/schemas/AuthFailure'
    put:
      tags:
      - Auth
      summary: JWT decode (no validation)
      description: Returns the header and claims of the Bearer JWT WITHOUT verifying signature or claims.
      operationId: getJwtDecode_put
      security:
      - bearerAuth: []
      responses:
        '200':
          description: Decoded token
          content:
            application/json:
              schema:
                type: object
                properties:
                  verified:
                    type: boolean
                    example: false
                  jwt_header:
                    type: object
                  claims:
                    type: object
                  expired:
                    type: boolean
                  note:
                    type: string
            application/xml:
              schema:
                type: object
                properties:
                  verified:
                    type: boolean
                    example: false
                  jwt_header:
                    type: object
                  claims:
                    type: object
                  expired:
                    type: boolean
                  note:
                    type: string
        '400':
          description: Malformed token
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AuthFailure'
            application/xml:
              schema:
                $ref: '#/components/schemas/AuthFailure'
        '401':
          description: Missing Bearer token
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AuthFailure'
            application/xml:
              schema:
                $ref: '#/components/schemas/AuthFailure'
    patch:
      tags:
      - Auth
      summary: JWT decode (no validation)
      description: Returns the header and claims of the Bearer JWT WITHOUT verifying signature or claims.
      operationId: getJwtDecode_patch
      security:
      - bearerAuth: []
      responses:
        '200':
          description: Decoded token
          content:
            application/json:
              schema:
                type: object
                properties:
                  verified:
                    type: boolean
                    example: false
                  jwt_header:
                    type: object
                  claims:
                    type: object
                  expired:
                    type: boolean
                  note:
                    type: string
            application/xml:
              schema:
                type: object
                properties:
                  verified:
                    type: boolean
                    example: false
                  jwt_header:
                    type: object
                  claims:
                    type: object
                  expired:
                    type: boolean
                  note:
                    type: string
        '400':
          description: Malformed token
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AuthFailure'
            application/xml:
              schema:
                $ref: '#/components/schemas/AuthFailure'
        '401':
          description: Missing Bearer token
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AuthFailure'
            application/xml:
              schema:
                $ref: '#/components/schemas/AuthFailure'
    delete:
      tags:
      - Auth
      summary: JWT decode (no validation)
      description: Returns the header and claims of the Bearer JWT WITHOUT verifying signature or claims.
      operationId: getJwtDecode_delete
      security:
      - bearerAuth: []
      responses:
        '200':
          description: Decoded token
          content:
            application/json:
              schema:
                type: object
                properties:
                  verified:
                    type: boolean
                    example: false
                  jwt_header:
                    type: object
                  claims:
                    type: object
                  expired:
                    type: boolean
                  note:
                    type: string
            application/xml:
              schema:
                type: object
                properties:
                  verified:
                    type: boolean
                    example: false
                  jwt_header:
                    type: object
                  claims:
                    type: object
                  expired:
                    type: boolean
                  note:
                    type: string
        '400':
          description: Malformed token
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AuthFailure'
            application/xml:
              schema:
                $ref: '#/components/schemas/AuthFailure'
        '401':
          description: Missing Bearer token
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AuthFailure'
            application/xml:
              schema:
                $ref: '#/components/schemas/AuthFailure'
  /auth/jwt/exchange:
    post:
      tags:
      - Auth
      summary: JWT token exchange
      description: Validates the Bearer JWT like /auth/jwt, then returns a new HS256 token inheriting its claims (new iss, iat, jti, exp).
      operationId: postJwtExchange
      security:
      - bearerAuth: []
      parameters:
      - name: iss
        in: query
        required: false
        schema:
          type: string
        description: Required issuer
      - name: aud
        in: query
        required: false
        schema:
          type: string
        description: Required audience
      responses:
        '200':
          description: Exchanged token
          content:
            application/json:
              schema:
                type: object
                properties:
                  authenticated:
                    type: boolean
                  auth_type:
                    type: string
                  claims:
                    type: object
                  exchanged_token:
                    type: string
            application/xml:
              schema:
                type: object
                properties:
                  authenticated:
                    type: boolean
                  auth_type:
                    type: string
                  claims:
                    type: object
                  exchanged_token:
                    type: string
        '401':
          description: Missing or invalid token
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AuthFailure'
            application/xml:
              schema:
                $ref: '#/components/schemas/AuthFailure'
    get:
      tags:
      - Auth
      summary: JWT token exchange
      description: Validates the Bearer JWT like /auth/jwt, then returns a new HS256 token inheriting its claims (new iss, iat, jti, exp).
      operationId: postJwtExchange_get
      security:
      - bearerAuth: []
      parameters:
      - name: iss
        in: query
        required: false
        schema:
          type: string
        description: Required issuer
      - name: aud
        in: query
        required: false
        schema:
          type: string
        description: Required audience
      responses:
        '200':
          description: Exchanged token
          content:
            application/json:
              schema:
                type: object
                properties:
                  authenticated:
                    type: boolean
                  auth_type:
                    type: string
                  claims:
                    type: object
                  exchanged_token:
                    type: string
            application/xml:
              schema:
                type: object
                properties:
                  authenticated:
                    type: boolean
                  auth_type:
                    type: string
                  claims:
                    type: object
                  exchanged_token:
                    type: string
        '401':
          description: Missing or invalid token
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AuthFailure'
            application/xml:
              schema:
                $ref: '#/components/schemas/AuthFailure'
    put:
      tags:
      - Auth
      summary: JWT token exchange
      description: Validates the Bearer JWT like /auth/jwt, then returns a new HS256 token inheriting its claims (new iss, iat, jti, exp).
      operationId: postJwtExchange_put
      security:
      - bearerAuth: []
      parameters:
      - name: iss
        in: query
        required: false
        schema:
          type: string
        description: Required issuer
      - name: aud
        in: query
        required: false
        schema:
          type: string
        description: Required audience
      responses:
        '200':
          description: Exchanged token
          content:
            application/json:
              schema:
                type: object
                properties:
                  authenticated:
                    type: boolean
                  auth_type:
                    type: string
                  claims:
                    type: object
                  exchanged_token:
                    type: string
            application/xml:
              schema:
                type: object
                properties:
                  authenticated:
                    type: boolean
                  auth_type:
                    type: string
                  claims:
                    type: object
                  exchanged_token:
                    type: string
        '401':
          description: Missing or invalid token
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AuthFailure'
            application/xml:
              schema:
                $ref: '#/components/schemas/AuthFailure'
    patch:
      tags:
      - Auth
      summary: JWT token exchange
      description: Validates the Bearer JWT like /auth/jwt, then returns a new HS256 token inheriting its claims (new iss, iat, jti, exp).
      operationId: postJwtExchange_patch
      security:
      - bearerAuth: []
      parameters:
      - name: iss
        in: query
        required: false
        schema:
          type: string
        description: Required issuer
      - name: aud
        in: query
        required: false
        schema:
          type: string
        description: Required audience
      responses:
        '200':
          description: Exchanged token
          content:
            application/json:
              schema:
                type: object
                properties:
                  authenticated:
                    type: boolean
                  auth_type:
                    type: string
                  claims:
                    type: object
                  exchanged_token:
                    type: string
            application/xml:
              schema:
                type: object
                properties:
                  authenticated:
                    type: boolean
                  auth_type:
                    type: string
                  claims:
                    type: object
                  exchanged_token:
                    type: string
        '401':
          description: Missing or invalid token
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AuthFailure'
            application/xml:
              schema:
                $ref: '#/components/schemas/AuthFailure'
    delete:
      tags:
      - Auth
      summary: JWT token exchange
      description: Validates the Bearer JWT like /auth/jwt, then returns a new HS256 token inheriting its claims (new iss, iat, jti, exp).
      operationId: postJwtExchange_delete
      security:
      - bearerAuth: []
      parameters:
      - name: iss
        in: query
        required: false
        schema:
          type: string
        description: Required issuer
      - name: aud
        in: query
        required: false
        schema:
          type: string
        description: Required audience
      responses:
        '200':
          description: Exchanged token
          content:
            application/json:
              schema:
                type: object
                properties:
                  authenticated:
                    type: boolean
                  auth_type:
                    type: string
                  claims:
                    type: object
                  exchanged_token:
                    type: string
            application/xml:
              schema:
                type: object
                properties:
                  authenticated:
                    type: boolean
                  auth_type:
                    type: string
                  claims:
                    type: object
                  exchanged_token:
                    type: string
        '401':
          description: Missing or invalid token
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AuthFailure'
            application/xml:
              schema:
                $ref: '#/components/schemas/AuthFailure'
  /.well-known/openid-configuration:
    get:
      tags:
      - Auth
      summary: OIDC discovery document
      operationId: getOidcDiscovery
      responses:
        '200':
          description: Metadata
          content:
            application/json:
              schema:
                type: object
  /.well-known/oauth-authorization-server:
    get:
      tags:
      - Auth
      summary: OAuth 2.0 authorization server metadata (RFC 8414)
      operationId: getOAuthServerMetadata
      responses:
        '200':
          description: Metadata
          content:
            application/json:
              schema:
                type: object
  /oauth/jwks:
    get:
      tags:
      - Auth
      summary: JWKS endpoint
      operationId: getJwks
      description: JSON Web Key Set with the RS256 public key.
      responses:
        '200':
          description: JWKS
  /oauth/token:
    post:
      tags:
      - Auth
      summary: OAuth 2.0 token endpoint
      description: 'Grants: authorization_code (+PKCE), refresh_token (rotating), client_credentials, password, RFC 8693 token exchange. Client auth: client_secret_basic, client_secret_post, or client_id only for public clients. Responses carry Cache-Control: no-store.'
      operationId: postOAuthToken
      requestBody:
        required: true
        content:
          application/x-www-form-urlencoded:
            schema:
              type: object
              required:
              - grant_type
              properties:
                grant_type:
                  type: string
                  enum:
                  - authorization_code
                  - client_credentials
                  - password
                  - refresh_token
                  - urn:ietf:params:oauth:grant-type:token-exchange
                scope:
                  type: string
                resource:
                  type: string
                  description: RFC 8707 resource indicator (repeatable); becomes the access token aud
                username:
                  type: string
                  description: password grant (demo/demo, alice/alice, bob/bob)
                password:
                  type: string
                  description: password grant
                code:
                  type: string
                  description: authorization_code grant
                redirect_uri:
                  type: string
                  description: authorization_code grant (must match the authorization request)
                code_verifier:
                  type: string
                  description: PKCE verifier
                refresh_token:
                  type: string
                  description: refresh_token grant (rotated on use)
                subject_token:
                  type: string
                  description: 'RFC 8693: token signed by this server'
                subject_token_type:
                  type: string
                actor_token:
                  type: string
                actor_token_type:
                  type: string
                audience:
                  type: string
                  description: RFC 8693 target audience (repeatable)
                requested_token_type:
                  type: string
                client_id:
                  type: string
                  example: rustybin
                client_secret:
                  type: string
                  example: secret
      responses:
        '200':
          description: Token response
          content:
            application/json:
              schema:
                type: object
                properties:
                  access_token:
                    type: string
                  token_type:
                    type: string
                    example: Bearer
                  expires_in:
                    type: integer
                    example: 3600
                  scope:
                    type: string
                  id_token:
                    type: string
                  refresh_token:
                    type: string
                  issued_token_type:
                    type: string
        '400':
          description: OAuth error (invalid_request, invalid_grant, unsupported_grant_type, invalid_scope, invalid_target, ...)
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
                    example: invalid_request
                  error_description:
                    type: string
        '401':
          description: invalid_client
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
                    example: invalid_request
                  error_description:
                    type: string
  /oauth/authorize:
    get:
      tags:
      - Auth
      summary: Authorization endpoint (login form)
      operationId: getOAuthAuthorize
      parameters:
      - name: response_type
        in: query
        required: true
        schema:
          type: string
          enum:
          - code
      - name: client_id
        in: query
        required: true
        schema:
          type: string
      - name: redirect_uri
        in: query
        schema:
          type: string
      - name: scope
        in: query
        schema:
          type: string
      - name: state
        in: query
        schema:
          type: string
      - name: nonce
        in: query
        schema:
          type: string
      - name: code_challenge
        in: query
        schema:
          type: string
      - name: code_challenge_method
        in: query
        schema:
          type: string
          enum:
          - S256
          - plain
      - name: resource
        in: query
        schema:
          type: string
      - name: login_hint
        in: query
        schema:
          type: string
      responses:
        '200':
          description: HTML login form
        '303':
          description: Error redirect to the client
        '400':
          description: Unknown client or invalid redirect_uri (HTML)
    post:
      tags:
      - Auth
      summary: Authorization endpoint (submit login)
      operationId: postOAuthAuthorize
      requestBody:
        required: true
        content:
          application/x-www-form-urlencoded:
            schema:
              type: object
              required:
              - client_id
              - response_type
              - username
              - password
              properties:
                username:
                  type: string
                password:
                  type: string
                client_id:
                  type: string
                redirect_uri:
                  type: string
                response_type:
                  type: string
                scope:
                  type: string
                state:
                  type: string
                nonce:
                  type: string
                code_challenge:
                  type: string
                code_challenge_method:
                  type: string
                resource:
                  type: string
                action:
                  type: string
                  enum:
                  - approve
                  - deny
      responses:
        '303':
          description: Redirect with code and state (or an error)
        '401':
          description: Invalid credentials (login form shown again)
  /oauth/userinfo:
    get:
      tags:
      - Auth
      summary: OIDC UserInfo
      operationId: getOAuthUserinfo
      security:
      - bearerAuth: []
      responses:
        '200':
          description: User claims
          content:
            application/json:
              schema:
                type: object
                properties:
                  sub:
                    type: string
                  name:
                    type: string
                  preferred_username:
                    type: string
                  email:
                    type: string
                  email_verified:
                    type: boolean
                  groups:
                    type: array
                    items:
                      type: string
        '401':
          description: Missing or invalid token
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
                    example: invalid_request
                  error_description:
                    type: string
        '403':
          description: insufficient_scope
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
                    example: invalid_request
                  error_description:
                    type: string
    post:
      tags:
      - Auth
      summary: OIDC UserInfo (POST)
      operationId: postOAuthUserinfo
      security:
      - bearerAuth: []
      responses:
        '200':
          description: User claims
        '401':
          description: Missing or invalid token
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
                    example: invalid_request
                  error_description:
                    type: string
  /oauth/introspect:
    post:
      tags:
      - Auth
      summary: Token introspection (RFC 7662)
      operationId: postOAuthIntrospect
      requestBody:
        required: true
        content:
          application/x-www-form-urlencoded:
            schema:
              type: object
              required:
              - token
              properties:
                client_id:
                  type: string
                  example: rustybin
                client_secret:
                  type: string
                  example: secret
                token:
                  type: string
                token_type_hint:
                  type: string
      responses:
        '200':
          description: Introspection result (active=false for anything invalid)
          content:
            application/json:
              schema:
                type: object
                properties:
                  active:
                    type: boolean
                  scope:
                    type: string
                  client_id:
                    type: string
                  username:
                    type: string
                  token_type:
                    type: string
                  exp:
                    type: integer
                  iat:
                    type: integer
                  sub:
                    type: string
                  aud: {}
                  iss:
                    type: string
                  jti:
                    type: string
        '401':
          description: invalid_client
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
                    example: invalid_request
                  error_description:
                    type: string
  /oauth/revoke:
    post:
      tags:
      - Auth
      summary: Token revocation (RFC 7009)
      operationId: postOAuthRevoke
      requestBody:
        required: true
        content:
          application/x-www-form-urlencoded:
            schema:
              type: object
              required:
              - token
              properties:
                client_id:
                  type: string
                  example: rustybin
                client_secret:
                  type: string
                  example: secret
                token:
                  type: string
                token_type_hint:
                  type: string
      responses:
        '200':
          description: Revoked (or the token was already invalid)
        '401':
          description: invalid_client
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
                    example: invalid_request
                  error_description:
                    type: string
  /oauth/register:
    post:
      tags:
      - Auth
      summary: Dynamic client registration (RFC 7591)
      operationId: postOAuthRegister
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                redirect_uris:
                  type: array
                  items:
                    type: string
                client_name:
                  type: string
                grant_types:
                  type: array
                  items:
                    type: string
                response_types:
                  type: array
                  items:
                    type: string
                token_endpoint_auth_method:
                  type: string
                  enum:
                  - client_secret_basic
                  - client_secret_post
                  - none
                scope:
                  type: string
      responses:
        '201':
          description: Registered client (client_id, client_secret, client_secret_expires_at, metadata)
        '400':
          description: invalid_redirect_uri or invalid_client_metadata
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
                    example: invalid_request
                  error_description:
                    type: string
  /auth/mtls:
    get:
      tags:
      - Auth
      summary: Mutual TLS authentication
      description: Uses the TLS client certificate on the HTTPS listener (verified against the demo CA during the handshake). Otherwise, when RUSTYBIN_MTLS_IN_HEADER is set, reads the certificate forwarded in that header (URL-encoded PEM, PEM or base64 DER) and verifies it was issued by the demo CA and is within its validity period.
      operationId: getMtls
      responses:
        '200':
          description: Authenticated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AuthResponse'
            application/xml:
              schema:
                $ref: '#/components/schemas/AuthResponse'
        '401':
          description: Missing, invalid or untrusted client certificate
    post:
      tags:
      - Auth
      summary: Mutual TLS authentication
      description: Uses the TLS client certificate on the HTTPS listener (verified against the demo CA during the handshake). Otherwise, when RUSTYBIN_MTLS_IN_HEADER is set, reads the certificate forwarded in that header (URL-encoded PEM, PEM or base64 DER) and verifies it was issued by the demo CA and is within its validity period.
      operationId: getMtls_post
      responses:
        '200':
          description: Authenticated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AuthResponse'
            application/xml:
              schema:
                $ref: '#/components/schemas/AuthResponse'
        '401':
          description: Missing, invalid or untrusted client certificate
    put:
      tags:
      - Auth
      summary: Mutual TLS authentication
      description: Uses the TLS client certificate on the HTTPS listener (verified against the demo CA during the handshake). Otherwise, when RUSTYBIN_MTLS_IN_HEADER is set, reads the certificate forwarded in that header (URL-encoded PEM, PEM or base64 DER) and verifies it was issued by the demo CA and is within its validity period.
      operationId: getMtls_put
      responses:
        '200':
          description: Authenticated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AuthResponse'
            application/xml:
              schema:
                $ref: '#/components/schemas/AuthResponse'
        '401':
          description: Missing, invalid or untrusted client certificate
    patch:
      tags:
      - Auth
      summary: Mutual TLS authentication
      description: Uses the TLS client certificate on the HTTPS listener (verified against the demo CA during the handshake). Otherwise, when RUSTYBIN_MTLS_IN_HEADER is set, reads the certificate forwarded in that header (URL-encoded PEM, PEM or base64 DER) and verifies it was issued by the demo CA and is within its validity period.
      operationId: getMtls_patch
      responses:
        '200':
          description: Authenticated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AuthResponse'
            application/xml:
              schema:
                $ref: '#/components/schemas/AuthResponse'
        '401':
          description: Missing, invalid or untrusted client certificate
    delete:
      tags:
      - Auth
      summary: Mutual TLS authentication
      description: Uses the TLS client certificate on the HTTPS listener (verified against the demo CA during the handshake). Otherwise, when RUSTYBIN_MTLS_IN_HEADER is set, reads the certificate forwarded in that header (URL-encoded PEM, PEM or base64 DER) and verifies it was issued by the demo CA and is within its validity period.
      operationId: getMtls_delete
      responses:
        '200':
          description: Authenticated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AuthResponse'
            application/xml:
              schema:
                $ref: '#/components/schemas/AuthResponse'
        '401':
          description: Missing, invalid or untrusted client certificate
  /auth/mtls/get-client-cert:
    get:
      tags:
      - Auth
      summary: Get demo client certificate
      description: Returns a demo client certificate (CN demo-client, issued by the demo CA) and its private key.
      operationId: getMtlsClientCert
      responses:
        '200':
          description: Client cert and key PEM
          content:
            application/json:
              schema:
                type: object
                properties:
                  cert_pem:
                    type: string
                  key_pem:
                    type: string
                  usage:
                    type: string
            application/xml:
              schema:
                type: object
                properties:
                  cert_pem:
                    type: string
                  key_pem:
                    type: string
                  usage:
                    type: string
  /auth/mtls/get-ca-cert:
    get:
      tags:
      - Auth
      summary: Get demo CA certificate
      description: Returns the demo CA certificate for configuring trust in your API gateway. The CA is persisted next to the TLS certificate when that directory is writable.
      operationId: getMtlsCaCert
      responses:
        '200':
          description: CA certificate PEM
          content:
            application/json:
              schema:
                type: object
                properties:
                  ca_cert_pem:
                    type: string
                  usage:
                    type: string
            application/xml:
              schema:
                type: object
                properties:
                  ca_cert_pem:
                    type: string
                  usage:
                    type: string
  /mcp:
    post:
      tags:
      - MCP
      summary: 'MCP: send a JSON-RPC message'
      operationId: mcpPost
      parameters:
      - name: MCP-Protocol-Version
        in: header
        required: false
        schema:
          type: string
          example: 2026-07-28
      - name: Mcp-Method
        in: header
        required: false
        schema:
          type: string
        description: Required on 2026-07-28 requests; must equal the body method
      - name: Mcp-Name
        in: header
        required: false
        schema:
          type: string
        description: tools/call, prompts/get, resources/read (2026-07-28)
      - name: Mcp-Session-Id
        in: header
        required: false
        schema:
          type: string
        description: Session from initialize (2025-xx)
      - name: page_size
        in: query
        required: false
        schema:
          type: integer
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/McpJsonRpcMessage'
      responses:
        '200':
          description: JSON-RPC response (application/json) or SSE stream (text/event-stream)
          content:
            application/json: {}
            text/event-stream: {}
        '202':
          description: Notification or response accepted
        '400':
          description: Invalid message, header mismatch (-32020), unsupported version (-32022), missing session
        '401':
          description: Authentication required
        '403':
          description: Origin not allowed or insufficient scope
        '404':
          description: Unknown method (-32601, 2026-07-28) or unknown session
        '406':
          description: Accept does not allow JSON or SSE
    get:
      tags:
      - MCP
      summary: 'MCP: open the server-to-client SSE stream (2025-xx sessions)'
      operationId: mcpStream
      parameters:
      - name: MCP-Protocol-Version
        in: header
        required: false
        schema:
          type: string
          example: 2026-07-28
      - name: Mcp-Method
        in: header
        required: false
        schema:
          type: string
        description: Required on 2026-07-28 requests; must equal the body method
      - name: Mcp-Name
        in: header
        required: false
        schema:
          type: string
        description: tools/call, prompts/get, resources/read (2026-07-28)
      - name: Mcp-Session-Id
        in: header
        required: false
        schema:
          type: string
        description: Session from initialize (2025-xx)
      - name: page_size
        in: query
        required: false
        schema:
          type: integer
      responses:
        '200':
          description: JSON-RPC response (application/json) or SSE stream (text/event-stream)
          content:
            application/json: {}
            text/event-stream: {}
        '202':
          description: Notification or response accepted
        '400':
          description: Invalid message, header mismatch (-32020), unsupported version (-32022), missing session
        '401':
          description: Authentication required
        '403':
          description: Origin not allowed or insufficient scope
        '404':
          description: Unknown method (-32601, 2026-07-28) or unknown session
        '406':
          description: Accept does not allow JSON or SSE
    delete:
      tags:
      - MCP
      summary: 'MCP: terminate the session (2025-xx)'
      operationId: mcpDelete
      parameters:
      - name: MCP-Protocol-Version
        in: header
        required: false
        schema:
          type: string
          example: 2026-07-28
      - name: Mcp-Method
        in: header
        required: false
        schema:
          type: string
        description: Required on 2026-07-28 requests; must equal the body method
      - name: Mcp-Name
        in: header
        required: false
        schema:
          type: string
        description: tools/call, prompts/get, resources/read (2026-07-28)
      - name: Mcp-Session-Id
        in: header
        required: false
        schema:
          type: string
        description: Session from initialize (2025-xx)
      - name: page_size
        in: query
        required: false
        schema:
          type: integer
      responses:
        '200':
          description: JSON-RPC response (application/json) or SSE stream (text/event-stream)
          content:
            application/json: {}
            text/event-stream: {}
        '202':
          description: Notification or response accepted
        '400':
          description: Invalid message, header mismatch (-32020), unsupported version (-32022), missing session
        '401':
          description: Authentication required
        '403':
          description: Origin not allowed or insufficient scope
        '404':
          description: Unknown method (-32601, 2026-07-28) or unknown session
        '406':
          description: Accept does not allow JSON or SSE
  /mcp/protected:
    post:
      tags:
      - MCP
      summary: 'Protected MCP: send a JSON-RPC message'
      operationId: mcpProtectedPost
      parameters:
      - name: MCP-Protocol-Version
        in: header
        required: false
        schema:
          type: string
          example: 2026-07-28
      - name: Mcp-Method
        in: header
        required: false
        schema:
          type: string
        description: Required on 2026-07-28 requests; must equal the body method
      - name: Mcp-Name
        in: header
        required: false
        schema:
          type: string
        description: tools/call, prompts/get, resources/read (2026-07-28)
      - name: Mcp-Session-Id
        in: header
        required: false
        schema:
          type: string
        description: Session from initialize (2025-xx)
      - name: page_size
        in: query
        required: false
        schema:
          type: integer
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/McpJsonRpcMessage'
      responses:
        '200':
          description: JSON-RPC response (application/json) or SSE stream (text/event-stream)
          content:
            application/json: {}
            text/event-stream: {}
        '202':
          description: Notification or response accepted
        '400':
          description: Invalid message, header mismatch (-32020), unsupported version (-32022), missing session
        '401':
          description: Authentication required
        '403':
          description: Origin not allowed or insufficient scope
        '404':
          description: Unknown method (-32601, 2026-07-28) or unknown session
        '406':
          description: Accept does not allow JSON or SSE
      security:
      - bearerAuth: []
    get:
      tags:
      - MCP
      summary: 'Protected MCP: open the server-to-client SSE stream (2025-xx sessions)'
      operationId: mcpProtectedStream
      parameters:
      - name: MCP-Protocol-Version
        in: header
        required: false
        schema:
          type: string
          example: 2026-07-28
      - name: Mcp-Method
        in: header
        required: false
        schema:
          type: string
        description: Required on 2026-07-28 requests; must equal the body method
      - name: Mcp-Name
        in: header
        required: false
        schema:
          type: string
        description: tools/call, prompts/get, resources/read (2026-07-28)
      - name: Mcp-Session-Id
        in: header
        required: false
        schema:
          type: string
        description: Session from initialize (2025-xx)
      - name: page_size
        in: query
        required: false
        schema:
          type: integer
      security:
      - bearerAuth: []
      responses:
        '200':
          description: JSON-RPC response (application/json) or SSE stream (text/event-stream)
          content:
            application/json: {}
            text/event-stream: {}
        '202':
          description: Notification or response accepted
        '400':
          description: Invalid message, header mismatch (-32020), unsupported version (-32022), missing session
        '401':
          description: Authentication required
        '403':
          description: Origin not allowed or insufficient scope
        '404':
          description: Unknown method (-32601, 2026-07-28) or unknown session
        '406':
          description: Accept does not allow JSON or SSE
    delete:
      tags:
      - MCP
      summary: 'Protected MCP: terminate the session (2025-xx)'
      operationId: mcpProtectedDelete
      parameters:
      - name: MCP-Protocol-Version
        in: header
        required: false
        schema:
          type: string
          example: 2026-07-28
      - name: Mcp-Method
        in: header
        required: false
        schema:
          type: string
        description: Required on 2026-07-28 requests; must equal the body method
      - name: Mcp-Name
        in: header
        required: false
        schema:
          type: string
        description: tools/call, prompts/get, resources/read (2026-07-28)
      - name: Mcp-Session-Id
        in: header
        required: false
        schema:
          type: string
        description: Session from initialize (2025-xx)
      - name: page_size
        in: query
        required: false
        schema:
          type: integer
      security:
      - bearerAuth: []
      responses:
        '200':
          description: JSON-RPC response (application/json) or SSE stream (text/event-stream)
          content:
            application/json: {}
            text/event-stream: {}
        '202':
          description: Notification or response accepted
        '400':
          description: Invalid message, header mismatch (-32020), unsupported version (-32022), missing session
        '401':
          description: Authentication required
        '403':
          description: Origin not allowed or insufficient scope
        '404':
          description: Unknown method (-32601, 2026-07-28) or unknown session
        '406':
          description: Accept does not allow JSON or SSE
  /mcp/apikey:
    post:
      tags:
      - MCP
      summary: 'API key MCP: send a JSON-RPC message'
      operationId: mcpApiKeyPost
      parameters:
      - name: MCP-Protocol-Version
        in: header
        required: false
        schema:
          type: string
          example: 2026-07-28
      - name: Mcp-Method
        in: header
        required: false
        schema:
          type: string
        description: Required on 2026-07-28 requests; must equal the body method
      - name: Mcp-Name
        in: header
        required: false
        schema:
          type: string
        description: tools/call, prompts/get, resources/read (2026-07-28)
      - name: Mcp-Session-Id
        in: header
        required: false
        schema:
          type: string
        description: Session from initialize (2025-xx)
      - name: page_size
        in: query
        required: false
        schema:
          type: integer
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/McpJsonRpcMessage'
      responses:
        '200':
          description: JSON-RPC response (application/json) or SSE stream (text/event-stream)
          content:
            application/json: {}
            text/event-stream: {}
        '202':
          description: Notification or response accepted
        '400':
          description: Invalid message, header mismatch (-32020), unsupported version (-32022), missing session
        '401':
          description: Authentication required
        '403':
          description: Origin not allowed or insufficient scope
        '404':
          description: Unknown method (-32601, 2026-07-28) or unknown session
        '406':
          description: Accept does not allow JSON or SSE
      security:
      - mcpApiKey: []
    get:
      tags:
      - MCP
      summary: 'API key MCP: open the server-to-client SSE stream (2025-xx sessions)'
      operationId: mcpApiKeyStream
      parameters:
      - name: MCP-Protocol-Version
        in: header
        required: false
        schema:
          type: string
          example: 2026-07-28
      - name: Mcp-Method
        in: header
        required: false
        schema:
          type: string
        description: Required on 2026-07-28 requests; must equal the body method
      - name: Mcp-Name
        in: header
        required: false
        schema:
          type: string
        description: tools/call, prompts/get, resources/read (2026-07-28)
      - name: Mcp-Session-Id
        in: header
        required: false
        schema:
          type: string
        description: Session from initialize (2025-xx)
      - name: page_size
        in: query
        required: false
        schema:
          type: integer
      security:
      - mcpApiKey: []
      responses:
        '200':
          description: JSON-RPC response (application/json) or SSE stream (text/event-stream)
          content:
            application/json: {}
            text/event-stream: {}
        '202':
          description: Notification or response accepted
        '400':
          description: Invalid message, header mismatch (-32020), unsupported version (-32022), missing session
        '401':
          description: Authentication required
        '403':
          description: Origin not allowed or insufficient scope
        '404':
          description: Unknown method (-32601, 2026-07-28) or unknown session
        '406':
          description: Accept does not allow JSON or SSE
    delete:
      tags:
      - MCP
      summary: 'API key MCP: terminate the session (2025-xx)'
      operationId: mcpApiKeyDelete
      parameters:
      - name: MCP-Protocol-Version
        in: header
        required: false
        schema:
          type: string
          example: 2026-07-28
      - name: Mcp-Method
        in: header
        required: false
        schema:
          type: string
        description: Required on 2026-07-28 requests; must equal the body method
      - name: Mcp-Name
        in: header
        required: false
        schema:
          type: string
        description: tools/call, prompts/get, resources/read (2026-07-28)
      - name: Mcp-Session-Id
        in: header
        required: false
        schema:
          type: string
        description: Session from initialize (2025-xx)
      - name: page_size
        in: query
        required: false
        schema:
          type: integer
      security:
      - mcpApiKey: []
      responses:
        '200':
          description: JSON-RPC response (application/json) or SSE stream (text/event-stream)
          content:
            application/json: {}
            text/event-stream: {}
        '202':
          description: Notification or response accepted
        '400':
          description: Invalid message, header mismatch (-32020), unsupported version (-32022), missing session
        '401':
          description: Authentication required
        '403':
          description: Origin not allowed or insufficient scope
        '404':
          description: Unknown method (-32601, 2026-07-28) or unknown session
        '406':
          description: Accept does not allow JSON or SSE
  /mcp/servers/{name}:
    post:
      tags:
      - MCP
      summary: 'Named MCP server: send a JSON-RPC message'
      operationId: mcpNamedServerPost
      parameters:
      - name: name
        in: path
        required: true
        schema:
          type: string
          enum:
          - weather
          - crm
          - devtools
      - name: MCP-Protocol-Version
        in: header
        required: false
        schema:
          type: string
          example: 2026-07-28
      - name: Mcp-Method
        in: header
        required: false
        schema:
          type: string
        description: Required on 2026-07-28 requests; must equal the body method
      - name: Mcp-Name
        in: header
        required: false
        schema:
          type: string
        description: tools/call, prompts/get, resources/read (2026-07-28)
      - name: Mcp-Session-Id
        in: header
        required: false
        schema:
          type: string
        description: Session from initialize (2025-xx)
      - name: page_size
        in: query
        required: false
        schema:
          type: integer
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/McpJsonRpcMessage'
      responses:
        '200':
          description: JSON-RPC response (application/json) or SSE stream (text/event-stream)
          content:
            application/json: {}
            text/event-stream: {}
        '202':
          description: Notification or response accepted
        '400':
          description: Invalid message, header mismatch (-32020), unsupported version (-32022), missing session
        '401':
          description: Authentication required
        '403':
          description: Origin not allowed or insufficient scope
        '404':
          description: Unknown method (-32601, 2026-07-28) or unknown session
        '406':
          description: Accept does not allow JSON or SSE
    get:
      tags:
      - MCP
      summary: 'Named MCP server: open the server-to-client SSE stream (2025-xx sessions)'
      operationId: mcpNamedServerStream
      parameters:
      - name: name
        in: path
        required: true
        schema:
          type: string
          enum:
          - weather
          - crm
          - devtools
      - name: MCP-Protocol-Version
        in: header
        required: false
        schema:
          type: string
          example: 2026-07-28
      - name: Mcp-Method
        in: header
        required: false
        schema:
          type: string
        description: Required on 2026-07-28 requests; must equal the body method
      - name: Mcp-Name
        in: header
        required: false
        schema:
          type: string
        description: tools/call, prompts/get, resources/read (2026-07-28)
      - name: Mcp-Session-Id
        in: header
        required: false
        schema:
          type: string
        description: Session from initialize (2025-xx)
      - name: page_size
        in: query
        required: false
        schema:
          type: integer
      responses:
        '200':
          description: JSON-RPC response (application/json) or SSE stream (text/event-stream)
          content:
            application/json: {}
            text/event-stream: {}
        '202':
          description: Notification or response accepted
        '400':
          description: Invalid message, header mismatch (-32020), unsupported version (-32022), missing session
        '401':
          description: Authentication required
        '403':
          description: Origin not allowed or insufficient scope
        '404':
          description: Unknown method (-32601, 2026-07-28) or unknown session
        '406':
          description: Accept does not allow JSON or SSE
    delete:
      tags:
      - MCP
      summary: 'Named MCP server: terminate the session (2025-xx)'
      operationId: mcpNamedServerDelete
      parameters:
      - name: name
        in: path
        required: true
        schema:
          type: string
          enum:
          - weather
          - crm
          - devtools
      - name: MCP-Protocol-Version
        in: header
        required: false
        schema:
          type: string
          example: 2026-07-28
      - name: Mcp-Method
        in: header
        required: false
        schema:
          type: string
        description: Required on 2026-07-28 requests; must equal the body method
      - name: Mcp-Name
        in: header
        required: false
        schema:
          type: string
        description: tools/call, prompts/get, resources/read (2026-07-28)
      - name: Mcp-Session-Id
        in: header
        required: false
        schema:
          type: string
        description: Session from initialize (2025-xx)
      - name: page_size
        in: query
        required: false
        schema:
          type: integer
      responses:
        '200':
          description: JSON-RPC response (application/json) or SSE stream (text/event-stream)
          content:
            application/json: {}
            text/event-stream: {}
        '202':
          description: Notification or response accepted
        '400':
          description: Invalid message, header mismatch (-32020), unsupported version (-32022), missing session
        '401':
          description: Authentication required
        '403':
          description: Origin not allowed or insufficient scope
        '404':
          description: Unknown method (-32601, 2026-07-28) or unknown session
        '406':
          description: Accept does not allow JSON or SSE
  /mock/{name}/_rustybin/mcp:
    post:
      tags:
      - Mocks
      summary: 'OpenAPI mock MCP server: send a JSON-RPC message'
      operationId: mockMcpPost
      parameters:
      - name: name
        in: path
        required: true
        schema:
          type: string
        description: The mock's name
      - name: MCP-Protocol-Version
        in: header
        required: false
        schema:
          type: string
          example: 2026-07-28
      - name: Mcp-Method
        in: header
        required: false
        schema:
          type: string
        description: Required on 2026-07-28 requests; must equal the body method
      - name: Mcp-Name
        in: header
        required: false
        schema:
          type: string
        description: tools/call, prompts/get, resources/read (2026-07-28)
      - name: Mcp-Session-Id
        in: header
        required: false
        schema:
          type: string
        description: Session from initialize (2025-xx)
      - name: page_size
        in: query
        required: false
        schema:
          type: integer
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/McpJsonRpcMessage'
      responses:
        '200':
          description: JSON-RPC response (application/json) or SSE stream (text/event-stream)
          content:
            application/json: {}
            text/event-stream: {}
        '202':
          description: Notification or response accepted
        '400':
          description: Invalid message, header mismatch (-32020), unsupported version (-32022), missing session
        '401':
          description: Authentication required
        '403':
          description: Origin not allowed or insufficient scope
        '404':
          description: Unknown method (-32601, 2026-07-28) or unknown session
        '406':
          description: Accept does not allow JSON or SSE
    get:
      tags:
      - Mocks
      summary: 'OpenAPI mock MCP server: open the server-to-client SSE stream (2025-xx sessions)'
      operationId: mockMcpStream
      parameters:
      - name: name
        in: path
        required: true
        schema:
          type: string
        description: The mock's name
      - name: MCP-Protocol-Version
        in: header
        required: false
        schema:
          type: string
          example: 2026-07-28
      - name: Mcp-Method
        in: header
        required: false
        schema:
          type: string
        description: Required on 2026-07-28 requests; must equal the body method
      - name: Mcp-Name
        in: header
        required: false
        schema:
          type: string
        description: tools/call, prompts/get, resources/read (2026-07-28)
      - name: Mcp-Session-Id
        in: header
        required: false
        schema:
          type: string
        description: Session from initialize (2025-xx)
      - name: page_size
        in: query
        required: false
        schema:
          type: integer
      responses:
        '200':
          description: JSON-RPC response (application/json) or SSE stream (text/event-stream)
          content:
            application/json: {}
            text/event-stream: {}
        '202':
          description: Notification or response accepted
        '400':
          description: Invalid message, header mismatch (-32020), unsupported version (-32022), missing session
        '401':
          description: Authentication required
        '403':
          description: Origin not allowed or insufficient scope
        '404':
          description: Unknown method (-32601, 2026-07-28) or unknown session
        '406':
          description: Accept does not allow JSON or SSE
    delete:
      tags:
      - Mocks
      summary: 'OpenAPI mock MCP server: terminate the session (2025-xx)'
      operationId: mockMcpDelete
      parameters:
      - name: name
        in: path
        required: true
        schema:
          type: string
        description: The mock's name
      - name: MCP-Protocol-Version
        in: header
        required: false
        schema:
          type: string
          example: 2026-07-28
      - name: Mcp-Method
        in: header
        required: false
        schema:
          type: string
        description: Required on 2026-07-28 requests; must equal the body method
      - name: Mcp-Name
        in: header
        required: false
        schema:
          type: string
        description: tools/call, prompts/get, resources/read (2026-07-28)
      - name: Mcp-Session-Id
        in: header
        required: false
        schema:
          type: string
        description: Session from initialize (2025-xx)
      - name: page_size
        in: query
        required: false
        schema:
          type: integer
      responses:
        '200':
          description: JSON-RPC response (application/json) or SSE stream (text/event-stream)
          content:
            application/json: {}
            text/event-stream: {}
        '202':
          description: Notification or response accepted
        '400':
          description: Invalid message, header mismatch (-32020), unsupported version (-32022), missing session
        '401':
          description: Authentication required
        '403':
          description: Origin not allowed or insufficient scope
        '404':
          description: Unknown method (-32601, 2026-07-28) or unknown session
        '406':
          description: Accept does not allow JSON or SSE
  /mcp/sse:
    get:
      tags:
      - MCP
      summary: Legacy HTTP+SSE transport (2024-11-05) event stream
      operationId: mcpLegacySse
      responses:
        '200':
          description: SSE stream; first event `endpoint`
          content:
            text/event-stream: {}
  /mcp/messages:
    post:
      tags:
      - MCP
      summary: Legacy HTTP+SSE transport (2024-11-05) message endpoint
      operationId: mcpLegacyMessages
      parameters:
      - name: sessionId
        in: query
        required: true
        schema:
          type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/McpJsonRpcMessage'
      responses:
        '202':
          description: Accepted; the reply arrives on the SSE stream
        '400':
          description: Invalid JSON-RPC
        '404':
          description: Unknown session
  /.well-known/oauth-protected-resource:
    get:
      tags:
      - MCP
      summary: OAuth 2.0 Protected Resource Metadata (RFC 9728) for /mcp/protected
      responses:
        '200':
          description: Metadata document
          content:
            application/json:
              schema:
                type: object
                properties:
                  resource:
                    type: string
                  authorization_servers:
                    type: array
                    items:
                      type: string
                  scopes_supported:
                    type: array
                    items:
                      type: string
                  bearer_methods_supported:
                    type: array
                    items:
                      type: string
      operationId: mcpProtectedResourceMetadata
  /.well-known/oauth-protected-resource/mcp/protected:
    get:
      tags:
      - MCP
      summary: OAuth 2.0 Protected Resource Metadata (RFC 9728) for /mcp/protected
      responses:
        '200':
          description: Metadata document
          content:
            application/json:
              schema:
                type: object
                properties:
                  resource:
                    type: string
                  authorization_servers:
                    type: array
                    items:
                      type: string
                  scopes_supported:
                    type: array
                    items:
                      type: string
                  bearer_methods_supported:
                    type: array
                    items:
                      type: string
      operationId: mcpProtectedResourceMetadataPath
  /.well-known/oauth-protected-resource/mock/{name}/_rustybin/mcp:
    get:
      tags:
      - MCP
      summary: Protected resource metadata of a mock with mcp.oauth
      operationId: getMockMcpResourceMetadata
      parameters:
      - name: name
        in: path
        required: true
        schema:
          type: string
      responses:
        '200':
          description: RFC 9728 metadata
        '404':
          description: No such mock, or it does not ask for a token
  /ai/openai/v1/chat/completions:
    post:
      tags:
      - AI Gateway
      summary: Chat completions (OpenAI-compatible)
      operationId: openaiChatCompletions
      parameters:
      - name: X-Rustybin-Mode
        in: header
        schema:
          type: string
          enum:
          - canned
          - echo
          - scripted
          - random
        description: Reply mode (also selectable by a model name segment, e.g. rustybin-echo)
      - name: X-Rustybin-Latency-Ms
        in: header
        schema:
          type: integer
        description: Delay before response headers
      - name: X-Rustybin-TTFT-Ms
        in: header
        schema:
          type: integer
        description: Time to first token
      - name: X-Rustybin-Tokens-Per-Second
        in: header
        schema:
          type: integer
        description: Streaming pace (default 100, 0 = unpaced)
      - name: X-Rustybin-Fail
        in: header
        schema:
          type: string
        description: 'kind[:percent]: 429, 500, 503, 529, 504, context_length, content_filter, prompt_filter, 401, ... in the provider''s native error shape'
      - name: X-Rustybin-Require-Auth
        in: header
        schema:
          type: boolean
        description: Enforce the provider's native credential
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ChatCompletionResponse'
            text/event-stream:
              schema:
                type: string
                description: 'chat.completion.chunk events, then data: [DONE]'
        '400':
          description: Invalid request (OpenAI error shape)
        '401':
          description: Missing or invalid credential (when required)
        '429':
          description: Simulated rate limit
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ChatCompletionRequest'
  /ai/v1/chat/completions:
    post:
      tags:
      - AI Gateway
      summary: Chat completions (OpenAI-compatible)
      operationId: postChatCompletions
      parameters:
      - name: X-Rustybin-Mode
        in: header
        schema:
          type: string
          enum:
          - canned
          - echo
          - scripted
          - random
        description: Reply mode (also selectable by a model name segment, e.g. rustybin-echo)
      - name: X-Rustybin-Latency-Ms
        in: header
        schema:
          type: integer
        description: Delay before response headers
      - name: X-Rustybin-TTFT-Ms
        in: header
        schema:
          type: integer
        description: Time to first token
      - name: X-Rustybin-Tokens-Per-Second
        in: header
        schema:
          type: integer
        description: Streaming pace (default 100, 0 = unpaced)
      - name: X-Rustybin-Fail
        in: header
        schema:
          type: string
        description: 'kind[:percent]: 429, 500, 503, 529, 504, context_length, content_filter, prompt_filter, 401, ... in the provider''s native error shape'
      - name: X-Rustybin-Require-Auth
        in: header
        schema:
          type: boolean
        description: Enforce the provider's native credential
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ChatCompletionResponse'
            text/event-stream:
              schema:
                type: string
                description: 'chat.completion.chunk events, then data: [DONE]'
        '400':
          description: Invalid request (OpenAI error shape)
        '401':
          description: Missing or invalid credential (when required)
        '429':
          description: Simulated rate limit
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ChatCompletionRequest'
  /ai/openai/v1/completions:
    post:
      tags:
      - AI Gateway
      summary: Legacy text completions
      operationId: openaiCompletions
      parameters:
      - name: X-Rustybin-Mode
        in: header
        schema:
          type: string
          enum:
          - canned
          - echo
          - scripted
          - random
        description: Reply mode (also selectable by a model name segment, e.g. rustybin-echo)
      - name: X-Rustybin-Latency-Ms
        in: header
        schema:
          type: integer
        description: Delay before response headers
      - name: X-Rustybin-TTFT-Ms
        in: header
        schema:
          type: integer
        description: Time to first token
      - name: X-Rustybin-Tokens-Per-Second
        in: header
        schema:
          type: integer
        description: Streaming pace (default 100, 0 = unpaced)
      - name: X-Rustybin-Fail
        in: header
        schema:
          type: string
        description: 'kind[:percent]: 429, 500, 503, 529, 504, context_length, content_filter, prompt_filter, 401, ... in the provider''s native error shape'
      - name: X-Rustybin-Require-Auth
        in: header
        schema:
          type: boolean
        description: Enforce the provider's native credential
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: object
        '400':
          description: Invalid request (OpenAI error shape)
        '401':
          description: Missing or invalid credential (when required)
        '429':
          description: Simulated rate limit
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                model:
                  type: string
                prompt:
                  oneOf:
                  - type: string
                  - type: array
                    items: {}
                max_tokens:
                  type: integer
                stream:
                  type: boolean
                echo:
                  type: boolean
                suffix:
                  type: string
                n:
                  type: integer
  /ai/v1/completions:
    post:
      tags:
      - AI Gateway
      summary: Legacy text completions
      operationId: postCompletions
      parameters:
      - name: X-Rustybin-Mode
        in: header
        schema:
          type: string
          enum:
          - canned
          - echo
          - scripted
          - random
        description: Reply mode (also selectable by a model name segment, e.g. rustybin-echo)
      - name: X-Rustybin-Latency-Ms
        in: header
        schema:
          type: integer
        description: Delay before response headers
      - name: X-Rustybin-TTFT-Ms
        in: header
        schema:
          type: integer
        description: Time to first token
      - name: X-Rustybin-Tokens-Per-Second
        in: header
        schema:
          type: integer
        description: Streaming pace (default 100, 0 = unpaced)
      - name: X-Rustybin-Fail
        in: header
        schema:
          type: string
        description: 'kind[:percent]: 429, 500, 503, 529, 504, context_length, content_filter, prompt_filter, 401, ... in the provider''s native error shape'
      - name: X-Rustybin-Require-Auth
        in: header
        schema:
          type: boolean
        description: Enforce the provider's native credential
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: object
        '400':
          description: Invalid request (OpenAI error shape)
        '401':
          description: Missing or invalid credential (when required)
        '429':
          description: Simulated rate limit
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                model:
                  type: string
                prompt:
                  oneOf:
                  - type: string
                  - type: array
                    items: {}
                max_tokens:
                  type: integer
                stream:
                  type: boolean
                echo:
                  type: boolean
                suffix:
                  type: string
                n:
                  type: integer
  /ai/openai/v1/embeddings:
    post:
      tags:
      - AI Gateway
      summary: Embeddings
      operationId: openaiEmbeddings
      parameters:
      - name: X-Rustybin-Mode
        in: header
        schema:
          type: string
          enum:
          - canned
          - echo
          - scripted
          - random
        description: Reply mode (also selectable by a model name segment, e.g. rustybin-echo)
      - name: X-Rustybin-Latency-Ms
        in: header
        schema:
          type: integer
        description: Delay before response headers
      - name: X-Rustybin-TTFT-Ms
        in: header
        schema:
          type: integer
        description: Time to first token
      - name: X-Rustybin-Tokens-Per-Second
        in: header
        schema:
          type: integer
        description: Streaming pace (default 100, 0 = unpaced)
      - name: X-Rustybin-Fail
        in: header
        schema:
          type: string
        description: 'kind[:percent]: 429, 500, 503, 529, 504, context_length, content_filter, prompt_filter, 401, ... in the provider''s native error shape'
      - name: X-Rustybin-Require-Auth
        in: header
        schema:
          type: boolean
        description: Enforce the provider's native credential
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties:
                  object:
                    type: string
                  data:
                    type: array
                    items:
                      type: object
                      properties:
                        index:
                          type: integer
                        embedding:
                          oneOf:
                          - type: array
                            items:
                              type: number
                          - type: string
                  usage:
                    type: object
        '400':
          description: Invalid request (OpenAI error shape)
        '401':
          description: Missing or invalid credential (when required)
        '429':
          description: Simulated rate limit
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - input
              properties:
                model:
                  type: string
                  example: text-embedding-3-small
                input:
                  oneOf:
                  - type: string
                  - type: array
                    items: {}
                dimensions:
                  type: integer
                encoding_format:
                  type: string
                  enum:
                  - float
                  - base64
  /ai/v1/embeddings:
    post:
      tags:
      - AI Gateway
      summary: Embeddings
      operationId: postEmbeddings
      parameters:
      - name: X-Rustybin-Mode
        in: header
        schema:
          type: string
          enum:
          - canned
          - echo
          - scripted
          - random
        description: Reply mode (also selectable by a model name segment, e.g. rustybin-echo)
      - name: X-Rustybin-Latency-Ms
        in: header
        schema:
          type: integer
        description: Delay before response headers
      - name: X-Rustybin-TTFT-Ms
        in: header
        schema:
          type: integer
        description: Time to first token
      - name: X-Rustybin-Tokens-Per-Second
        in: header
        schema:
          type: integer
        description: Streaming pace (default 100, 0 = unpaced)
      - name: X-Rustybin-Fail
        in: header
        schema:
          type: string
        description: 'kind[:percent]: 429, 500, 503, 529, 504, context_length, content_filter, prompt_filter, 401, ... in the provider''s native error shape'
      - name: X-Rustybin-Require-Auth
        in: header
        schema:
          type: boolean
        description: Enforce the provider's native credential
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties:
                  object:
                    type: string
                  data:
                    type: array
                    items:
                      type: object
                      properties:
                        index:
                          type: integer
                        embedding:
                          oneOf:
                          - type: array
                            items:
                              type: number
                          - type: string
                  usage:
                    type: object
        '400':
          description: Invalid request (OpenAI error shape)
        '401':
          description: Missing or invalid credential (when required)
        '429':
          description: Simulated rate limit
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - input
              properties:
                model:
                  type: string
                  example: text-embedding-3-small
                input:
                  oneOf:
                  - type: string
                  - type: array
                    items: {}
                dimensions:
                  type: integer
                encoding_format:
                  type: string
                  enum:
                  - float
                  - base64
  /ai/openai/v1/models:
    get:
      tags:
      - AI Gateway
      summary: List models
      operationId: openaiListModels
      responses:
        '200':
          description: Model list
  /ai/v1/models:
    get:
      tags:
      - AI Gateway
      summary: List models
      operationId: getModels
      responses:
        '200':
          description: Model list
  /ai/openai/v1/models/{model}:
    get:
      tags:
      - AI Gateway
      summary: Retrieve a model
      operationId: openaiGetModel
      parameters:
      - name: model
        in: path
        required: true
        schema:
          type: string
      responses:
        '200':
          description: Model
        '404':
          description: Unknown model
  /ai/v1/models/{model}:
    get:
      tags:
      - AI Gateway
      summary: Retrieve a model
      operationId: getModel
      parameters:
      - name: model
        in: path
        required: true
        schema:
          type: string
      responses:
        '200':
          description: Model
        '404':
          description: Unknown model
  /ai/openai/v1/moderations:
    post:
      tags:
      - AI Gateway
      summary: Moderations
      operationId: openaiModerations
      parameters:
      - name: X-Rustybin-Mode
        in: header
        schema:
          type: string
          enum:
          - canned
          - echo
          - scripted
          - random
        description: Reply mode (also selectable by a model name segment, e.g. rustybin-echo)
      - name: X-Rustybin-Latency-Ms
        in: header
        schema:
          type: integer
        description: Delay before response headers
      - name: X-Rustybin-TTFT-Ms
        in: header
        schema:
          type: integer
        description: Time to first token
      - name: X-Rustybin-Tokens-Per-Second
        in: header
        schema:
          type: integer
        description: Streaming pace (default 100, 0 = unpaced)
      - name: X-Rustybin-Fail
        in: header
        schema:
          type: string
        description: 'kind[:percent]: 429, 500, 503, 529, 504, context_length, content_filter, prompt_filter, 401, ... in the provider''s native error shape'
      - name: X-Rustybin-Require-Auth
        in: header
        schema:
          type: boolean
        description: Enforce the provider's native credential
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties:
                  id:
                    type: string
                  model:
                    type: string
                  results:
                    type: array
                    items:
                      type: object
        '400':
          description: Invalid request (OpenAI error shape)
        '401':
          description: Missing or invalid credential (when required)
        '429':
          description: Simulated rate limit
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - input
              properties:
                input:
                  oneOf:
                  - type: string
                  - type: array
                    items: {}
                model:
                  type: string
  /ai/v1/moderations:
    post:
      tags:
      - AI Gateway
      summary: Moderations
      operationId: postModerations
      parameters:
      - name: X-Rustybin-Mode
        in: header
        schema:
          type: string
          enum:
          - canned
          - echo
          - scripted
          - random
        description: Reply mode (also selectable by a model name segment, e.g. rustybin-echo)
      - name: X-Rustybin-Latency-Ms
        in: header
        schema:
          type: integer
        description: Delay before response headers
      - name: X-Rustybin-TTFT-Ms
        in: header
        schema:
          type: integer
        description: Time to first token
      - name: X-Rustybin-Tokens-Per-Second
        in: header
        schema:
          type: integer
        description: Streaming pace (default 100, 0 = unpaced)
      - name: X-Rustybin-Fail
        in: header
        schema:
          type: string
        description: 'kind[:percent]: 429, 500, 503, 529, 504, context_length, content_filter, prompt_filter, 401, ... in the provider''s native error shape'
      - name: X-Rustybin-Require-Auth
        in: header
        schema:
          type: boolean
        description: Enforce the provider's native credential
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties:
                  id:
                    type: string
                  model:
                    type: string
                  results:
                    type: array
                    items:
                      type: object
        '400':
          description: Invalid request (OpenAI error shape)
        '401':
          description: Missing or invalid credential (when required)
        '429':
          description: Simulated rate limit
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - input
              properties:
                input:
                  oneOf:
                  - type: string
                  - type: array
                    items: {}
                model:
                  type: string
  /ai/openai/v1/images/generations:
    post:
      tags:
      - AI Gateway
      summary: Image generation
      operationId: openaiImages
      parameters:
      - name: X-Rustybin-Mode
        in: header
        schema:
          type: string
          enum:
          - canned
          - echo
          - scripted
          - random
        description: Reply mode (also selectable by a model name segment, e.g. rustybin-echo)
      - name: X-Rustybin-Latency-Ms
        in: header
        schema:
          type: integer
        description: Delay before response headers
      - name: X-Rustybin-TTFT-Ms
        in: header
        schema:
          type: integer
        description: Time to first token
      - name: X-Rustybin-Tokens-Per-Second
        in: header
        schema:
          type: integer
        description: Streaming pace (default 100, 0 = unpaced)
      - name: X-Rustybin-Fail
        in: header
        schema:
          type: string
        description: 'kind[:percent]: 429, 500, 503, 529, 504, context_length, content_filter, prompt_filter, 401, ... in the provider''s native error shape'
      - name: X-Rustybin-Require-Auth
        in: header
        schema:
          type: boolean
        description: Enforce the provider's native credential
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties:
                  created:
                    type: integer
                  data:
                    type: array
                    items:
                      type: object
                      properties:
                        url:
                          type: string
                        b64_json:
                          type: string
        '400':
          description: Invalid request (OpenAI error shape)
        '401':
          description: Missing or invalid credential (when required)
        '429':
          description: Simulated rate limit
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - prompt
              properties:
                prompt:
                  type: string
                model:
                  type: string
                n:
                  type: integer
                size:
                  type: string
                response_format:
                  type: string
                  enum:
                  - url
                  - b64_json
  /ai/v1/images/generations:
    post:
      tags:
      - AI Gateway
      summary: Image generation
      operationId: postImages
      parameters:
      - name: X-Rustybin-Mode
        in: header
        schema:
          type: string
          enum:
          - canned
          - echo
          - scripted
          - random
        description: Reply mode (also selectable by a model name segment, e.g. rustybin-echo)
      - name: X-Rustybin-Latency-Ms
        in: header
        schema:
          type: integer
        description: Delay before response headers
      - name: X-Rustybin-TTFT-Ms
        in: header
        schema:
          type: integer
        description: Time to first token
      - name: X-Rustybin-Tokens-Per-Second
        in: header
        schema:
          type: integer
        description: Streaming pace (default 100, 0 = unpaced)
      - name: X-Rustybin-Fail
        in: header
        schema:
          type: string
        description: 'kind[:percent]: 429, 500, 503, 529, 504, context_length, content_filter, prompt_filter, 401, ... in the provider''s native error shape'
      - name: X-Rustybin-Require-Auth
        in: header
        schema:
          type: boolean
        description: Enforce the provider's native credential
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties:
                  created:
                    type: integer
                  data:
                    type: array
                    items:
                      type: object
                      properties:
                        url:
                          type: string
                        b64_json:
                          type: string
        '400':
          description: Invalid request (OpenAI error shape)
        '401':
          description: Missing or invalid credential (when required)
        '429':
          description: Simulated rate limit
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - prompt
              properties:
                prompt:
                  type: string
                model:
                  type: string
                n:
                  type: integer
                size:
                  type: string
                response_format:
                  type: string
                  enum:
                  - url
                  - b64_json
  /ai/openai/v1/audio/transcriptions:
    post:
      tags:
      - AI Gateway
      summary: Audio transcription
      operationId: openaiTranscriptions
      parameters:
      - name: X-Rustybin-Mode
        in: header
        schema:
          type: string
          enum:
          - canned
          - echo
          - scripted
          - random
        description: Reply mode (also selectable by a model name segment, e.g. rustybin-echo)
      - name: X-Rustybin-Latency-Ms
        in: header
        schema:
          type: integer
        description: Delay before response headers
      - name: X-Rustybin-TTFT-Ms
        in: header
        schema:
          type: integer
        description: Time to first token
      - name: X-Rustybin-Tokens-Per-Second
        in: header
        schema:
          type: integer
        description: Streaming pace (default 100, 0 = unpaced)
      - name: X-Rustybin-Fail
        in: header
        schema:
          type: string
        description: 'kind[:percent]: 429, 500, 503, 529, 504, context_length, content_filter, prompt_filter, 401, ... in the provider''s native error shape'
      - name: X-Rustybin-Require-Auth
        in: header
        schema:
          type: boolean
        description: Enforce the provider's native credential
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties:
                  text:
                    type: string
        '400':
          description: Invalid request (OpenAI error shape)
        '401':
          description: Missing or invalid credential (when required)
        '429':
          description: Simulated rate limit
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              type: object
              required:
              - file
              - model
              properties:
                file:
                  type: string
                  format: binary
                model:
                  type: string
                response_format:
                  type: string
                  enum:
                  - json
                  - text
                  - srt
                  - verbose_json
                  - vtt
                language:
                  type: string
  /ai/v1/audio/transcriptions:
    post:
      tags:
      - AI Gateway
      summary: Audio transcription
      operationId: postTranscriptions
      parameters:
      - name: X-Rustybin-Mode
        in: header
        schema:
          type: string
          enum:
          - canned
          - echo
          - scripted
          - random
        description: Reply mode (also selectable by a model name segment, e.g. rustybin-echo)
      - name: X-Rustybin-Latency-Ms
        in: header
        schema:
          type: integer
        description: Delay before response headers
      - name: X-Rustybin-TTFT-Ms
        in: header
        schema:
          type: integer
        description: Time to first token
      - name: X-Rustybin-Tokens-Per-Second
        in: header
        schema:
          type: integer
        description: Streaming pace (default 100, 0 = unpaced)
      - name: X-Rustybin-Fail
        in: header
        schema:
          type: string
        description: 'kind[:percent]: 429, 500, 503, 529, 504, context_length, content_filter, prompt_filter, 401, ... in the provider''s native error shape'
      - name: X-Rustybin-Require-Auth
        in: header
        schema:
          type: boolean
        description: Enforce the provider's native credential
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties:
                  text:
                    type: string
        '400':
          description: Invalid request (OpenAI error shape)
        '401':
          description: Missing or invalid credential (when required)
        '429':
          description: Simulated rate limit
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              type: object
              required:
              - file
              - model
              properties:
                file:
                  type: string
                  format: binary
                model:
                  type: string
                response_format:
                  type: string
                  enum:
                  - json
                  - text
                  - srt
                  - verbose_json
                  - vtt
                language:
                  type: string
  /ai/openai/v1/responses:
    post:
      tags:
      - AI Gateway
      summary: Responses API (OpenAI-compatible)
      operationId: openaiResponses
      parameters:
      - name: X-Rustybin-Mode
        in: header
        schema:
          type: string
          enum:
          - canned
          - echo
          - scripted
          - random
        description: Reply mode (also selectable by a model name segment, e.g. rustybin-echo)
      - name: X-Rustybin-Latency-Ms
        in: header
        schema:
          type: integer
        description: Delay before response headers
      - name: X-Rustybin-TTFT-Ms
        in: header
        schema:
          type: integer
        description: Time to first token
      - name: X-Rustybin-Tokens-Per-Second
        in: header
        schema:
          type: integer
        description: Streaming pace (default 100, 0 = unpaced)
      - name: X-Rustybin-Fail
        in: header
        schema:
          type: string
        description: 'kind[:percent]: 429, 500, 503, 529, 504, context_length, content_filter, prompt_filter, 401, ... in the provider''s native error shape'
      - name: X-Rustybin-Require-Auth
        in: header
        schema:
          type: boolean
        description: Enforce the provider's native credential
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - input
              properties:
                model:
                  type: string
                input:
                  oneOf:
                  - type: string
                  - type: array
                    items:
                      type: object
                instructions:
                  type: string
                tools:
                  type: array
                  items:
                    type: object
                tool_choice: {}
                text:
                  type: object
                  properties:
                    format:
                      type: object
                max_output_tokens:
                  type: integer
                stream:
                  type: boolean
      responses:
        '200':
          description: Response object, or text/event-stream with response.* events
          content:
            application/json:
              schema:
                type: object
                properties:
                  id:
                    type: string
                  object:
                    type: string
                  status:
                    type: string
                  output:
                    type: array
                    items:
                      type: object
                  usage:
                    type: object
            text/event-stream:
              schema:
                type: string
        '400':
          description: Invalid request
  /ai/v1/responses:
    post:
      tags:
      - AI Gateway
      summary: Responses API (OpenAI-compatible)
      operationId: postResponses
      parameters:
      - name: X-Rustybin-Mode
        in: header
        schema:
          type: string
          enum:
          - canned
          - echo
          - scripted
          - random
        description: Reply mode (also selectable by a model name segment, e.g. rustybin-echo)
      - name: X-Rustybin-Latency-Ms
        in: header
        schema:
          type: integer
        description: Delay before response headers
      - name: X-Rustybin-TTFT-Ms
        in: header
        schema:
          type: integer
        description: Time to first token
      - name: X-Rustybin-Tokens-Per-Second
        in: header
        schema:
          type: integer
        description: Streaming pace (default 100, 0 = unpaced)
      - name: X-Rustybin-Fail
        in: header
        schema:
          type: string
        description: 'kind[:percent]: 429, 500, 503, 529, 504, context_length, content_filter, prompt_filter, 401, ... in the provider''s native error shape'
      - name: X-Rustybin-Require-Auth
        in: header
        schema:
          type: boolean
        description: Enforce the provider's native credential
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - input
              properties:
                model:
                  type: string
                input:
                  oneOf:
                  - type: string
                  - type: array
                    items:
                      type: object
                instructions:
                  type: string
                tools:
                  type: array
                  items:
                    type: object
                tool_choice: {}
                text:
                  type: object
                  properties:
                    format:
                      type: object
                max_output_tokens:
                  type: integer
                stream:
                  type: boolean
      responses:
        '200':
          description: Response object, or text/event-stream with response.* events
          content:
            application/json:
              schema:
                type: object
                properties:
                  id:
                    type: string
                  object:
                    type: string
                  status:
                    type: string
                  output:
                    type: array
                    items:
                      type: object
                  usage:
                    type: object
            text/event-stream:
              schema:
                type: string
        '400':
          description: Invalid request
  /ai/azure/openai/deployments/{deployment}/chat/completions:
    post:
      tags:
      - AI Gateway
      summary: Azure OpenAI chat completions
      operationId: azureChatCompletions
      parameters:
      - name: deployment
        in: path
        required: true
        schema:
          type: string
      - name: api-version
        in: query
        required: true
        schema:
          type: string
          example: 2024-10-21
      - name: api-key
        in: header
        schema:
          type: string
      - name: X-Rustybin-Mode
        in: header
        schema:
          type: string
          enum:
          - canned
          - echo
          - scripted
          - random
        description: Reply mode (also selectable by a model name segment, e.g. rustybin-echo)
      - name: X-Rustybin-Latency-Ms
        in: header
        schema:
          type: integer
        description: Delay before response headers
      - name: X-Rustybin-TTFT-Ms
        in: header
        schema:
          type: integer
        description: Time to first token
      - name: X-Rustybin-Tokens-Per-Second
        in: header
        schema:
          type: integer
        description: Streaming pace (default 100, 0 = unpaced)
      - name: X-Rustybin-Fail
        in: header
        schema:
          type: string
        description: 'kind[:percent]: 429, 500, 503, 529, 504, context_length, content_filter, prompt_filter, 401, ... in the provider''s native error shape'
      - name: X-Rustybin-Require-Auth
        in: header
        schema:
          type: boolean
        description: Enforce the provider's native credential
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ChatCompletionRequest'
      responses:
        '200':
          description: OK (OpenAI shape with Azure content filter annotations)
        '400':
          description: Missing api-version or invalid request
        '401':
          description: Missing or invalid api-key (when required)
  /ai/azure/openai/deployments/{deployment}/completions:
    post:
      tags:
      - AI Gateway
      summary: Azure OpenAI completions
      operationId: azureCompletions
      parameters:
      - name: deployment
        in: path
        required: true
        schema:
          type: string
      - name: api-version
        in: query
        required: true
        schema:
          type: string
          example: 2024-10-21
      - name: api-key
        in: header
        schema:
          type: string
      - name: X-Rustybin-Mode
        in: header
        schema:
          type: string
          enum:
          - canned
          - echo
          - scripted
          - random
        description: Reply mode (also selectable by a model name segment, e.g. rustybin-echo)
      - name: X-Rustybin-Latency-Ms
        in: header
        schema:
          type: integer
        description: Delay before response headers
      - name: X-Rustybin-TTFT-Ms
        in: header
        schema:
          type: integer
        description: Time to first token
      - name: X-Rustybin-Tokens-Per-Second
        in: header
        schema:
          type: integer
        description: Streaming pace (default 100, 0 = unpaced)
      - name: X-Rustybin-Fail
        in: header
        schema:
          type: string
        description: 'kind[:percent]: 429, 500, 503, 529, 504, context_length, content_filter, prompt_filter, 401, ... in the provider''s native error shape'
      - name: X-Rustybin-Require-Auth
        in: header
        schema:
          type: boolean
        description: Enforce the provider's native credential
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                prompt: {}
      responses:
        '200':
          description: OK (OpenAI shape with Azure content filter annotations)
        '400':
          description: Missing api-version or invalid request
        '401':
          description: Missing or invalid api-key (when required)
  /ai/azure/openai/deployments/{deployment}/embeddings:
    post:
      tags:
      - AI Gateway
      summary: Azure OpenAI embeddings
      operationId: azureEmbeddings
      parameters:
      - name: deployment
        in: path
        required: true
        schema:
          type: string
      - name: api-version
        in: query
        required: true
        schema:
          type: string
          example: 2024-10-21
      - name: api-key
        in: header
        schema:
          type: string
      - name: X-Rustybin-Mode
        in: header
        schema:
          type: string
          enum:
          - canned
          - echo
          - scripted
          - random
        description: Reply mode (also selectable by a model name segment, e.g. rustybin-echo)
      - name: X-Rustybin-Latency-Ms
        in: header
        schema:
          type: integer
        description: Delay before response headers
      - name: X-Rustybin-TTFT-Ms
        in: header
        schema:
          type: integer
        description: Time to first token
      - name: X-Rustybin-Tokens-Per-Second
        in: header
        schema:
          type: integer
        description: Streaming pace (default 100, 0 = unpaced)
      - name: X-Rustybin-Fail
        in: header
        schema:
          type: string
        description: 'kind[:percent]: 429, 500, 503, 529, 504, context_length, content_filter, prompt_filter, 401, ... in the provider''s native error shape'
      - name: X-Rustybin-Require-Auth
        in: header
        schema:
          type: boolean
        description: Enforce the provider's native credential
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                input: {}
      responses:
        '200':
          description: OK (OpenAI shape with Azure content filter annotations)
        '400':
          description: Missing api-version or invalid request
        '401':
          description: Missing or invalid api-key (when required)
  /ai/anthropic/v1/messages:
    post:
      tags:
      - AI Gateway
      summary: Anthropic Messages API (mock)
      operationId: anthropicMessages
      parameters:
      - name: x-api-key
        in: header
        schema:
          type: string
      - name: anthropic-version
        in: header
        schema:
          type: string
          example: 2023-06-01
      - name: X-Rustybin-Mode
        in: header
        schema:
          type: string
          enum:
          - canned
          - echo
          - scripted
          - random
        description: Reply mode (also selectable by a model name segment, e.g. rustybin-echo)
      - name: X-Rustybin-Latency-Ms
        in: header
        schema:
          type: integer
        description: Delay before response headers
      - name: X-Rustybin-TTFT-Ms
        in: header
        schema:
          type: integer
        description: Time to first token
      - name: X-Rustybin-Tokens-Per-Second
        in: header
        schema:
          type: integer
        description: Streaming pace (default 100, 0 = unpaced)
      - name: X-Rustybin-Fail
        in: header
        schema:
          type: string
        description: 'kind[:percent]: 429, 500, 503, 529, 504, context_length, content_filter, prompt_filter, 401, ... in the provider''s native error shape'
      - name: X-Rustybin-Require-Auth
        in: header
        schema:
          type: boolean
        description: Enforce the provider's native credential
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/AnthropicMessagesRequest'
      responses:
        '200':
          description: Message, or text/event-stream when stream=true
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AnthropicMessage'
            text/event-stream:
              schema:
                type: string
        '400':
          description: invalid_request_error (e.g. missing max_tokens)
        '401':
          description: authentication_error (when required)
        '529':
          description: overloaded_error (simulated)
  /ai/anthropic/v1/messages/count_tokens:
    post:
      tags:
      - AI Gateway
      summary: Count input tokens
      operationId: anthropicCountTokens
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/AnthropicMessagesRequest'
      responses:
        '200':
          description: Token count
          content:
            application/json:
              schema:
                type: object
                properties:
                  input_tokens:
                    type: integer
  /ai/anthropic/v1/models:
    get:
      tags:
      - AI Gateway
      summary: List models
      operationId: anthropicListModels
      responses:
        '200':
          description: Models
  /ai/anthropic/v1/models/{model}:
    get:
      tags:
      - AI Gateway
      summary: Retrieve a model
      operationId: anthropicGetModel
      parameters:
      - name: model
        in: path
        required: true
        schema:
          type: string
      responses:
        '200':
          description: Model
        '404':
          description: not_found_error
  /ai/gemini/v1beta/models:
    get:
      tags:
      - AI Gateway
      summary: 'Gemini: list models'
      operationId: geminiListModels
      responses:
        '200':
          description: Models
  /ai/gemini/v1beta/models/{rest}:
    get:
      tags:
      - AI Gateway
      summary: 'Gemini: get a model'
      operationId: geminiGetModel
      parameters:
      - name: rest
        in: path
        required: true
        schema:
          type: string
      responses:
        '200':
          description: Model
    post:
      tags:
      - AI Gateway
      summary: 'Gemini: generateContent / streamGenerateContent / countTokens / embedContent / batchEmbedContents'
      operationId: geminiAction
      parameters:
      - name: rest
        in: path
        required: true
        schema:
          type: string
        description: '{model}:{action}, e.g. gemini-2.5-flash:generateContent'
      - name: alt
        in: query
        schema:
          type: string
          enum:
          - sse
      - name: key
        in: query
        schema:
          type: string
      - name: x-goog-api-key
        in: header
        schema:
          type: string
      - name: X-Rustybin-Mode
        in: header
        schema:
          type: string
          enum:
          - canned
          - echo
          - scripted
          - random
        description: Reply mode (also selectable by a model name segment, e.g. rustybin-echo)
      - name: X-Rustybin-Latency-Ms
        in: header
        schema:
          type: integer
        description: Delay before response headers
      - name: X-Rustybin-TTFT-Ms
        in: header
        schema:
          type: integer
        description: Time to first token
      - name: X-Rustybin-Tokens-Per-Second
        in: header
        schema:
          type: integer
        description: Streaming pace (default 100, 0 = unpaced)
      - name: X-Rustybin-Fail
        in: header
        schema:
          type: string
        description: 'kind[:percent]: 429, 500, 503, 529, 504, context_length, content_filter, prompt_filter, 401, ... in the provider''s native error shape'
      - name: X-Rustybin-Require-Auth
        in: header
        schema:
          type: boolean
        description: Enforce the provider's native credential
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                contents:
                  type: array
                  items:
                    type: object
                    properties:
                      role:
                        type: string
                        enum:
                        - user
                        - model
                      parts:
                        type: array
                        items:
                          type: object
                systemInstruction:
                  type: object
                tools:
                  type: array
                  items:
                    type: object
                toolConfig:
                  type: object
                generationConfig:
                  type: object
                  properties:
                    maxOutputTokens:
                      type: integer
                    responseMimeType:
                      type: string
                    responseSchema:
                      type: object
                    candidateCount:
                      type: integer
                    stopSequences:
                      type: array
                      items:
                        type: string
                content:
                  type: object
                  description: embedContent
                requests:
                  type: array
                  items:
                    type: object
                  description: batchEmbedContents
      responses:
        '200':
          description: GenerateContentResponse (candidates, usageMetadata), SSE chunks, token count or embedding
        '400':
          description: INVALID_ARGUMENT
        '403':
          description: PERMISSION_DENIED (missing key, when required)
        '429':
          description: RESOURCE_EXHAUSTED (simulated)
  /ai/bedrock/model/{model_id}/converse:
    post:
      tags:
      - AI Gateway
      summary: Bedrock Converse
      operationId: bedrockConverse
      parameters:
      - name: model_id
        in: path
        required: true
        schema:
          type: string
        example: anthropic.claude-3-5-sonnet-20240620-v1:0
      - name: Authorization
        in: header
        schema:
          type: string
        description: AWS4-HMAC-SHA256 Credential=AKID/yyyymmdd/region/bedrock/aws4_request, SignedHeaders=..., Signature=...
      - name: X-Amz-Date
        in: header
        schema:
          type: string
      - name: X-Rustybin-Mode
        in: header
        schema:
          type: string
          enum:
          - canned
          - echo
          - scripted
          - random
        description: Reply mode (also selectable by a model name segment, e.g. rustybin-echo)
      - name: X-Rustybin-Latency-Ms
        in: header
        schema:
          type: integer
        description: Delay before response headers
      - name: X-Rustybin-TTFT-Ms
        in: header
        schema:
          type: integer
        description: Time to first token
      - name: X-Rustybin-Tokens-Per-Second
        in: header
        schema:
          type: integer
        description: Streaming pace (default 100, 0 = unpaced)
      - name: X-Rustybin-Fail
        in: header
        schema:
          type: string
        description: 'kind[:percent]: 429, 500, 503, 529, 504, context_length, content_filter, prompt_filter, 401, ... in the provider''s native error shape'
      - name: X-Rustybin-Require-Auth
        in: header
        schema:
          type: boolean
        description: Enforce the provider's native credential
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                messages:
                  type: array
                  items:
                    type: object
                system:
                  type: array
                  items:
                    type: object
                inferenceConfig:
                  type: object
                toolConfig:
                  type: object
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: object
        '400':
          description: ValidationException
        '403':
          description: MissingAuthenticationTokenException / IncompleteSignatureException (when required)
        '429':
          description: ThrottlingException (simulated)
  /ai/bedrock/model/{model_id}/converse-stream:
    post:
      tags:
      - AI Gateway
      summary: Bedrock ConverseStream
      operationId: bedrockConverseStream
      parameters:
      - name: model_id
        in: path
        required: true
        schema:
          type: string
        example: anthropic.claude-3-5-sonnet-20240620-v1:0
      - name: Authorization
        in: header
        schema:
          type: string
        description: AWS4-HMAC-SHA256 Credential=AKID/yyyymmdd/region/bedrock/aws4_request, SignedHeaders=..., Signature=...
      - name: X-Amz-Date
        in: header
        schema:
          type: string
      - name: X-Rustybin-Mode
        in: header
        schema:
          type: string
          enum:
          - canned
          - echo
          - scripted
          - random
        description: Reply mode (also selectable by a model name segment, e.g. rustybin-echo)
      - name: X-Rustybin-Latency-Ms
        in: header
        schema:
          type: integer
        description: Delay before response headers
      - name: X-Rustybin-TTFT-Ms
        in: header
        schema:
          type: integer
        description: Time to first token
      - name: X-Rustybin-Tokens-Per-Second
        in: header
        schema:
          type: integer
        description: Streaming pace (default 100, 0 = unpaced)
      - name: X-Rustybin-Fail
        in: header
        schema:
          type: string
        description: 'kind[:percent]: 429, 500, 503, 529, 504, context_length, content_filter, prompt_filter, 401, ... in the provider''s native error shape'
      - name: X-Rustybin-Require-Auth
        in: header
        schema:
          type: boolean
        description: Enforce the provider's native credential
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                messages:
                  type: array
                  items:
                    type: object
                system:
                  type: array
                  items:
                    type: object
                inferenceConfig:
                  type: object
                toolConfig:
                  type: object
      responses:
        '200':
          description: OK
          content:
            application/vnd.amazon.eventstream:
              schema:
                type: string
                format: binary
        '400':
          description: ValidationException
        '403':
          description: MissingAuthenticationTokenException / IncompleteSignatureException (when required)
        '429':
          description: ThrottlingException (simulated)
  /ai/bedrock/model/{model_id}/invoke:
    post:
      tags:
      - AI Gateway
      summary: Bedrock InvokeModel
      operationId: bedrockInvoke
      parameters:
      - name: model_id
        in: path
        required: true
        schema:
          type: string
        example: anthropic.claude-3-5-sonnet-20240620-v1:0
      - name: Authorization
        in: header
        schema:
          type: string
        description: AWS4-HMAC-SHA256 Credential=AKID/yyyymmdd/region/bedrock/aws4_request, SignedHeaders=..., Signature=...
      - name: X-Amz-Date
        in: header
        schema:
          type: string
      - name: X-Rustybin-Mode
        in: header
        schema:
          type: string
          enum:
          - canned
          - echo
          - scripted
          - random
        description: Reply mode (also selectable by a model name segment, e.g. rustybin-echo)
      - name: X-Rustybin-Latency-Ms
        in: header
        schema:
          type: integer
        description: Delay before response headers
      - name: X-Rustybin-TTFT-Ms
        in: header
        schema:
          type: integer
        description: Time to first token
      - name: X-Rustybin-Tokens-Per-Second
        in: header
        schema:
          type: integer
        description: Streaming pace (default 100, 0 = unpaced)
      - name: X-Rustybin-Fail
        in: header
        schema:
          type: string
        description: 'kind[:percent]: 429, 500, 503, 529, 504, context_length, content_filter, prompt_filter, 401, ... in the provider''s native error shape'
      - name: X-Rustybin-Require-Auth
        in: header
        schema:
          type: boolean
        description: Enforce the provider's native credential
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                messages:
                  type: array
                  items:
                    type: object
                system:
                  type: array
                  items:
                    type: object
                inferenceConfig:
                  type: object
                toolConfig:
                  type: object
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: object
        '400':
          description: ValidationException
        '403':
          description: MissingAuthenticationTokenException / IncompleteSignatureException (when required)
        '429':
          description: ThrottlingException (simulated)
  /ai/bedrock/model/{model_id}/invoke-with-response-stream:
    post:
      tags:
      - AI Gateway
      summary: Bedrock InvokeModelWithResponseStream
      operationId: bedrockInvokeStream
      parameters:
      - name: model_id
        in: path
        required: true
        schema:
          type: string
        example: anthropic.claude-3-5-sonnet-20240620-v1:0
      - name: Authorization
        in: header
        schema:
          type: string
        description: AWS4-HMAC-SHA256 Credential=AKID/yyyymmdd/region/bedrock/aws4_request, SignedHeaders=..., Signature=...
      - name: X-Amz-Date
        in: header
        schema:
          type: string
      - name: X-Rustybin-Mode
        in: header
        schema:
          type: string
          enum:
          - canned
          - echo
          - scripted
          - random
        description: Reply mode (also selectable by a model name segment, e.g. rustybin-echo)
      - name: X-Rustybin-Latency-Ms
        in: header
        schema:
          type: integer
        description: Delay before response headers
      - name: X-Rustybin-TTFT-Ms
        in: header
        schema:
          type: integer
        description: Time to first token
      - name: X-Rustybin-Tokens-Per-Second
        in: header
        schema:
          type: integer
        description: Streaming pace (default 100, 0 = unpaced)
      - name: X-Rustybin-Fail
        in: header
        schema:
          type: string
        description: 'kind[:percent]: 429, 500, 503, 529, 504, context_length, content_filter, prompt_filter, 401, ... in the provider''s native error shape'
      - name: X-Rustybin-Require-Auth
        in: header
        schema:
          type: boolean
        description: Enforce the provider's native credential
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                messages:
                  type: array
                  items:
                    type: object
                system:
                  type: array
                  items:
                    type: object
                inferenceConfig:
                  type: object
                toolConfig:
                  type: object
      responses:
        '200':
          description: OK
          content:
            application/vnd.amazon.eventstream:
              schema:
                type: string
                format: binary
        '400':
          description: ValidationException
        '403':
          description: MissingAuthenticationTokenException / IncompleteSignatureException (when required)
        '429':
          description: ThrottlingException (simulated)
  /ai/ollama/api/chat:
    post:
      tags:
      - AI Gateway
      summary: Ollama chat
      operationId: ollamaChat
      parameters:
      - name: X-Rustybin-Mode
        in: header
        schema:
          type: string
          enum:
          - canned
          - echo
          - scripted
          - random
        description: Reply mode (also selectable by a model name segment, e.g. rustybin-echo)
      - name: X-Rustybin-Latency-Ms
        in: header
        schema:
          type: integer
        description: Delay before response headers
      - name: X-Rustybin-TTFT-Ms
        in: header
        schema:
          type: integer
        description: Time to first token
      - name: X-Rustybin-Tokens-Per-Second
        in: header
        schema:
          type: integer
        description: Streaming pace (default 100, 0 = unpaced)
      - name: X-Rustybin-Fail
        in: header
        schema:
          type: string
        description: 'kind[:percent]: 429, 500, 503, 529, 504, context_length, content_filter, prompt_filter, 401, ... in the provider''s native error shape'
      - name: X-Rustybin-Require-Auth
        in: header
        schema:
          type: boolean
        description: Enforce the provider's native credential
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                model:
                  type: string
                messages:
                  type: array
                  items:
                    type: object
                prompt:
                  type: string
                input: {}
                stream:
                  type: boolean
                  default: true
                format: {}
                options:
                  type: object
                tools:
                  type: array
                  items:
                    type: object
      responses:
        '200':
          description: OK
          content:
            application/x-ndjson:
              schema:
                type: object
        '400':
          description: '{"error": ...}'
  /ai/ollama/api/generate:
    post:
      tags:
      - AI Gateway
      summary: Ollama generate
      operationId: ollamaGenerate
      parameters:
      - name: X-Rustybin-Mode
        in: header
        schema:
          type: string
          enum:
          - canned
          - echo
          - scripted
          - random
        description: Reply mode (also selectable by a model name segment, e.g. rustybin-echo)
      - name: X-Rustybin-Latency-Ms
        in: header
        schema:
          type: integer
        description: Delay before response headers
      - name: X-Rustybin-TTFT-Ms
        in: header
        schema:
          type: integer
        description: Time to first token
      - name: X-Rustybin-Tokens-Per-Second
        in: header
        schema:
          type: integer
        description: Streaming pace (default 100, 0 = unpaced)
      - name: X-Rustybin-Fail
        in: header
        schema:
          type: string
        description: 'kind[:percent]: 429, 500, 503, 529, 504, context_length, content_filter, prompt_filter, 401, ... in the provider''s native error shape'
      - name: X-Rustybin-Require-Auth
        in: header
        schema:
          type: boolean
        description: Enforce the provider's native credential
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                model:
                  type: string
                messages:
                  type: array
                  items:
                    type: object
                prompt:
                  type: string
                input: {}
                stream:
                  type: boolean
                  default: true
                format: {}
                options:
                  type: object
                tools:
                  type: array
                  items:
                    type: object
      responses:
        '200':
          description: OK
          content:
            application/x-ndjson:
              schema:
                type: object
        '400':
          description: '{"error": ...}'
  /ai/ollama/api/embed:
    post:
      tags:
      - AI Gateway
      summary: Ollama embed
      operationId: ollamaEmbed
      parameters:
      - name: X-Rustybin-Mode
        in: header
        schema:
          type: string
          enum:
          - canned
          - echo
          - scripted
          - random
        description: Reply mode (also selectable by a model name segment, e.g. rustybin-echo)
      - name: X-Rustybin-Latency-Ms
        in: header
        schema:
          type: integer
        description: Delay before response headers
      - name: X-Rustybin-TTFT-Ms
        in: header
        schema:
          type: integer
        description: Time to first token
      - name: X-Rustybin-Tokens-Per-Second
        in: header
        schema:
          type: integer
        description: Streaming pace (default 100, 0 = unpaced)
      - name: X-Rustybin-Fail
        in: header
        schema:
          type: string
        description: 'kind[:percent]: 429, 500, 503, 529, 504, context_length, content_filter, prompt_filter, 401, ... in the provider''s native error shape'
      - name: X-Rustybin-Require-Auth
        in: header
        schema:
          type: boolean
        description: Enforce the provider's native credential
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                model:
                  type: string
                messages:
                  type: array
                  items:
                    type: object
                prompt:
                  type: string
                input: {}
                stream:
                  type: boolean
                  default: true
                format: {}
                options:
                  type: object
                tools:
                  type: array
                  items:
                    type: object
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: object
        '400':
          description: '{"error": ...}'
  /ai/ollama/api/embeddings:
    post:
      tags:
      - AI Gateway
      summary: Ollama legacy embeddings
      operationId: ollamaEmbeddings
      parameters:
      - name: X-Rustybin-Mode
        in: header
        schema:
          type: string
          enum:
          - canned
          - echo
          - scripted
          - random
        description: Reply mode (also selectable by a model name segment, e.g. rustybin-echo)
      - name: X-Rustybin-Latency-Ms
        in: header
        schema:
          type: integer
        description: Delay before response headers
      - name: X-Rustybin-TTFT-Ms
        in: header
        schema:
          type: integer
        description: Time to first token
      - name: X-Rustybin-Tokens-Per-Second
        in: header
        schema:
          type: integer
        description: Streaming pace (default 100, 0 = unpaced)
      - name: X-Rustybin-Fail
        in: header
        schema:
          type: string
        description: 'kind[:percent]: 429, 500, 503, 529, 504, context_length, content_filter, prompt_filter, 401, ... in the provider''s native error shape'
      - name: X-Rustybin-Require-Auth
        in: header
        schema:
          type: boolean
        description: Enforce the provider's native credential
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                model:
                  type: string
                messages:
                  type: array
                  items:
                    type: object
                prompt:
                  type: string
                input: {}
                stream:
                  type: boolean
                  default: true
                format: {}
                options:
                  type: object
                tools:
                  type: array
                  items:
                    type: object
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: object
        '400':
          description: '{"error": ...}'
  /ai/ollama/api/tags:
    get:
      tags:
      - AI Gateway
      summary: Ollama tags
      operationId: ollamaTags
      responses:
        '200':
          description: Models
  /ai/cohere/v2/rerank:
    post:
      tags:
      - AI Gateway
      summary: Rerank documents
      operationId: cohereRerank
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - query
              - documents
              properties:
                model:
                  type: string
                query:
                  type: string
                documents:
                  type: array
                  items: {}
                top_n:
                  type: integer
                return_documents:
                  type: boolean
      responses:
        '200':
          description: Results sorted by relevance
          content:
            application/json:
              schema:
                type: object
                properties:
                  results:
                    type: array
                    items:
                      type: object
                      properties:
                        index:
                          type: integer
                        relevance_score:
                          type: number
        '400':
          description: Invalid request
  /ai/v1/rerank:
    post:
      tags:
      - AI Gateway
      summary: Rerank documents
      operationId: rerank
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - query
              - documents
              properties:
                model:
                  type: string
                query:
                  type: string
                documents:
                  type: array
                  items: {}
                top_n:
                  type: integer
                return_documents:
                  type: boolean
      responses:
        '200':
          description: Results sorted by relevance
          content:
            application/json:
              schema:
                type: object
                properties:
                  results:
                    type: array
                    items:
                      type: object
                      properties:
                        index:
                          type: integer
                        relevance_score:
                          type: number
        '400':
          description: Invalid request
  /ai/cohere/v2/embed:
    post:
      tags:
      - AI Gateway
      summary: Cohere embed
      operationId: cohereEmbed
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                model:
                  type: string
                texts:
                  type: array
                  items:
                    type: string
                input_type:
                  type: string
                embedding_types:
                  type: array
                  items:
                    type: string
                output_dimension:
                  type: integer
      responses:
        '200':
          description: Embeddings by type
        '400':
          description: Invalid request
  /ai/requests:
    get:
      tags:
      - AI Gateway
      summary: Recent mock LLM exchanges
      operationId: aiListRequests
      parameters:
      - name: limit
        in: query
        schema:
          type: integer
          maximum: 200
      - name: session
        in: query
        schema:
          type: string
      responses:
        '200':
          description: Summaries, newest first
  /ai/requests/{id}:
    get:
      tags:
      - AI Gateway
      summary: What the upstream received for one AI request
      operationId: aiGetRequest
      parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
        description: X-Rustybin-Request-Id of the AI response
      - name: session
        in: query
        schema:
          type: string
      responses:
        '200':
          description: Recorded exchange
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AiRequestRecord'
        '404':
          description: Unknown or expired id (or another session's request in public mode)
  /graphql/ws:
    get:
      tags:
      - GraphQL
      summary: GraphQL subscriptions over WebSocket
      description: 'WebSocket upgrade with Sec-WebSocket-Protocol graphql-transport-ws (graphql-ws library) or graphql-ws (legacy subscriptions-transport-ws). Subscriptions: ticker(count, intervalMs), orderUpdates(orderId, intervalMs).'
      operationId: graphqlSubscriptions
      parameters:
      - name: Sec-WebSocket-Protocol
        in: header
        required: true
        schema:
          type: string
          enum:
          - graphql-transport-ws
          - graphql-ws
      responses:
        '101':
          description: Switching Protocols
        '400':
          description: Missing or unsupported subprotocol
  /.well-known/agent-card.json:
    get:
      tags:
      - A2A Agents
      summary: A2A Agent Card (default agent)
      operationId: a2aAgentCard
      description: v1.0 card with supportedInterfaces plus the v0.3 url/preferredTransport fields; lists all demo agents in capabilities.extensions
      parameters:
      - name: A2A-Version
        in: header
        required: false
        schema:
          type: string
          enum:
          - '1.0'
          - '0.3'
        description: Protocol version; empty means 0.3
      responses:
        '200':
          description: A2A response
          content:
            application/json:
              schema:
                type: object
  /.well-known/agent.json:
    get:
      tags:
      - A2A Agents
      summary: Legacy A2A v0.3 Agent Card
      operationId: a2aLegacyAgentCard
      description: v0.3 card of the default agent
      parameters:
      - name: A2A-Version
        in: header
        required: false
        schema:
          type: string
          enum:
          - '1.0'
          - '0.3'
        description: Protocol version; empty means 0.3
      responses:
        '200':
          description: A2A response
          content:
            application/json:
              schema:
                type: object
  /a2a:
    get:
      tags:
      - A2A Agents
      summary: A2A agent directory
      operationId: a2aDirectory
      description: Lists the demo agents and their endpoints
      parameters:
      - name: A2A-Version
        in: header
        required: false
        schema:
          type: string
          enum:
          - '1.0'
          - '0.3'
        description: Protocol version; empty means 0.3
      responses:
        '200':
          description: A2A response
          content:
            application/json:
              schema:
                type: object
    post:
      tags:
      - A2A Agents
      summary: A2A JSON-RPC (default echo agent)
      operationId: a2aJsonRpcDefault
      description: 'JSON-RPC 2.0. v1.0 methods (A2A-Version: 1.0): SendMessage, SendStreamingMessage (SSE), GetTask, ListTasks, CancelTask, SubscribeToTask (SSE), Create/Get/List/DeleteTaskPushNotificationConfig, GetExtendedAgentCard. v0.3 methods (no header): message/send, message/stream, tasks/get, tasks/cancel, tasks/resubscribe, tasks/pushNotificationConfig/set|get|list|delete, agent/getAuthenticatedExtendedCard.'
      parameters:
      - name: A2A-Version
        in: header
        required: false
        schema:
          type: string
          enum:
          - '1.0'
          - '0.3'
        description: Protocol version; empty means 0.3
      responses:
        '200':
          description: A2A response
          content:
            application/json:
              schema:
                type: object
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
  /a2a/{agent}:
    get:
      tags:
      - A2A Agents
      summary: A2A Agent Card of an agent
      operationId: a2aAgentCardByAgent
      description: Same as /a2a/{agent}/.well-known/agent-card.json
      parameters:
      - name: A2A-Version
        in: header
        required: false
        schema:
          type: string
          enum:
          - '1.0'
          - '0.3'
        description: Protocol version; empty means 0.3
      - name: agent
        in: path
        required: true
        schema:
          type: string
          enum:
          - echo
          - weather
          - travel-planner
          - approval
          - flaky
          - secure
          - reject
      responses:
        '200':
          description: A2A response
          content:
            application/json:
              schema:
                type: object
    post:
      tags:
      - A2A Agents
      summary: A2A JSON-RPC endpoint
      operationId: a2aJsonRpc
      description: 'JSON-RPC 2.0. v1.0 methods (A2A-Version: 1.0): SendMessage, SendStreamingMessage (SSE), GetTask, ListTasks, CancelTask, SubscribeToTask (SSE), Create/Get/List/DeleteTaskPushNotificationConfig, GetExtendedAgentCard. v0.3 methods (no header): message/send, message/stream, tasks/get, tasks/cancel, tasks/resubscribe, tasks/pushNotificationConfig/set|get|list|delete, agent/getAuthenticatedExtendedCard.'
      parameters:
      - name: A2A-Version
        in: header
        required: false
        schema:
          type: string
          enum:
          - '1.0'
          - '0.3'
        description: Protocol version; empty means 0.3
      - name: agent
        in: path
        required: true
        schema:
          type: string
          enum:
          - echo
          - weather
          - travel-planner
          - approval
          - flaky
          - secure
          - reject
      responses:
        '200':
          description: A2A response
          content:
            application/json:
              schema:
                type: object
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
  /a2a/{agent}/.well-known/agent-card.json:
    get:
      tags:
      - A2A Agents
      summary: A2A Agent Card of an agent
      operationId: a2aAgentCardWellKnown
      description: v1.0 card plus v0.3 fields
      parameters:
      - name: A2A-Version
        in: header
        required: false
        schema:
          type: string
          enum:
          - '1.0'
          - '0.3'
        description: Protocol version; empty means 0.3
      - name: agent
        in: path
        required: true
        schema:
          type: string
          enum:
          - echo
          - weather
          - travel-planner
          - approval
          - flaky
          - secure
          - reject
      responses:
        '200':
          description: A2A response
          content:
            application/json:
              schema:
                type: object
  /a2a/{agent}/.well-known/agent.json:
    get:
      tags:
      - A2A Agents
      summary: Legacy v0.3 Agent Card of an agent
      operationId: a2aLegacyAgentCardByAgent
      description: v0.3 card
      parameters:
      - name: A2A-Version
        in: header
        required: false
        schema:
          type: string
          enum:
          - '1.0'
          - '0.3'
        description: Protocol version; empty means 0.3
      - name: agent
        in: path
        required: true
        schema:
          type: string
          enum:
          - echo
          - weather
          - travel-planner
          - approval
          - flaky
          - secure
          - reject
      responses:
        '200':
          description: A2A response
          content:
            application/json:
              schema:
                type: object
  /a2a/{agent}/v1/{rest}:
    get:
      tags:
      - A2A Agents
      summary: A2A REST binding (get)
      operationId: a2aRestget
      description: 'HTTP+JSON binding. v1.0: POST message:send, POST message:stream (SSE), GET tasks, GET tasks/{id}, POST tasks/{id}:cancel, GET|POST tasks/{id}:subscribe (SSE), POST|GET tasks/{id}/pushNotificationConfigs, GET|DELETE tasks/{id}/pushNotificationConfigs/{configId}, GET extendedAgentCard. v0.3 (no header): same paths with v0.3 ProtoJSON plus GET card.'
      parameters:
      - name: A2A-Version
        in: header
        required: false
        schema:
          type: string
          enum:
          - '1.0'
          - '0.3'
        description: Protocol version; empty means 0.3
      - name: agent
        in: path
        required: true
        schema:
          type: string
          enum:
          - echo
          - weather
          - travel-planner
          - approval
          - flaky
          - secure
          - reject
      - name: rest
        in: path
        required: true
        schema:
          type: string
        description: Binding path, e.g. message:send or tasks/{id}:cancel
      responses:
        '200':
          description: A2A response
          content:
            application/json:
              schema:
                type: object
    post:
      tags:
      - A2A Agents
      summary: A2A REST binding (post)
      operationId: a2aRestpost
      description: 'HTTP+JSON binding. v1.0: POST message:send, POST message:stream (SSE), GET tasks, GET tasks/{id}, POST tasks/{id}:cancel, GET|POST tasks/{id}:subscribe (SSE), POST|GET tasks/{id}/pushNotificationConfigs, GET|DELETE tasks/{id}/pushNotificationConfigs/{configId}, GET extendedAgentCard. v0.3 (no header): same paths with v0.3 ProtoJSON plus GET card.'
      parameters:
      - name: A2A-Version
        in: header
        required: false
        schema:
          type: string
          enum:
          - '1.0'
          - '0.3'
        description: Protocol version; empty means 0.3
      - name: agent
        in: path
        required: true
        schema:
          type: string
          enum:
          - echo
          - weather
          - travel-planner
          - approval
          - flaky
          - secure
          - reject
      - name: rest
        in: path
        required: true
        schema:
          type: string
        description: Binding path, e.g. message:send or tasks/{id}:cancel
      responses:
        '200':
          description: A2A response
          content:
            application/json:
              schema:
                type: object
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
    put:
      tags:
      - A2A Agents
      summary: A2A REST binding (put)
      operationId: a2aRestput
      description: 'HTTP+JSON binding. v1.0: POST message:send, POST message:stream (SSE), GET tasks, GET tasks/{id}, POST tasks/{id}:cancel, GET|POST tasks/{id}:subscribe (SSE), POST|GET tasks/{id}/pushNotificationConfigs, GET|DELETE tasks/{id}/pushNotificationConfigs/{configId}, GET extendedAgentCard. v0.3 (no header): same paths with v0.3 ProtoJSON plus GET card.'
      parameters:
      - name: A2A-Version
        in: header
        required: false
        schema:
          type: string
          enum:
          - '1.0'
          - '0.3'
        description: Protocol version; empty means 0.3
      - name: agent
        in: path
        required: true
        schema:
          type: string
          enum:
          - echo
          - weather
          - travel-planner
          - approval
          - flaky
          - secure
          - reject
      - name: rest
        in: path
        required: true
        schema:
          type: string
        description: Binding path, e.g. message:send or tasks/{id}:cancel
      responses:
        '200':
          description: A2A response
          content:
            application/json:
              schema:
                type: object
    patch:
      tags:
      - A2A Agents
      summary: A2A REST binding (patch)
      operationId: a2aRestpatch
      description: 'HTTP+JSON binding. v1.0: POST message:send, POST message:stream (SSE), GET tasks, GET tasks/{id}, POST tasks/{id}:cancel, GET|POST tasks/{id}:subscribe (SSE), POST|GET tasks/{id}/pushNotificationConfigs, GET|DELETE tasks/{id}/pushNotificationConfigs/{configId}, GET extendedAgentCard. v0.3 (no header): same paths with v0.3 ProtoJSON plus GET card.'
      parameters:
      - name: A2A-Version
        in: header
        required: false
        schema:
          type: string
          enum:
          - '1.0'
          - '0.3'
        description: Protocol version; empty means 0.3
      - name: agent
        in: path
        required: true
        schema:
          type: string
          enum:
          - echo
          - weather
          - travel-planner
          - approval
          - flaky
          - secure
          - reject
      - name: rest
        in: path
        required: true
        schema:
          type: string
        description: Binding path, e.g. message:send or tasks/{id}:cancel
      responses:
        '200':
          description: A2A response
          content:
            application/json:
              schema:
                type: object
    delete:
      tags:
      - A2A Agents
      summary: A2A REST binding (delete)
      operationId: a2aRestdelete
      description: 'HTTP+JSON binding. v1.0: POST message:send, POST message:stream (SSE), GET tasks, GET tasks/{id}, POST tasks/{id}:cancel, GET|POST tasks/{id}:subscribe (SSE), POST|GET tasks/{id}/pushNotificationConfigs, GET|DELETE tasks/{id}/pushNotificationConfigs/{configId}, GET extendedAgentCard. v0.3 (no header): same paths with v0.3 ProtoJSON plus GET card.'
      parameters:
      - name: A2A-Version
        in: header
        required: false
        schema:
          type: string
          enum:
          - '1.0'
          - '0.3'
        description: Protocol version; empty means 0.3
      - name: agent
        in: path
        required: true
        schema:
          type: string
          enum:
          - echo
          - weather
          - travel-planner
          - approval
          - flaky
          - secure
          - reject
      - name: rest
        in: path
        required: true
        schema:
          type: string
        description: Binding path, e.g. message:send or tasks/{id}:cancel
      responses:
        '200':
          description: A2A response
          content:
            application/json:
              schema:
                type: object
  /a2a/{agent}/v1/message:send:
    post:
      tags:
      - A2A Agents
      summary: REST SendMessage
      operationId: a2aRestSendMessage
      description: 'HTTP+JSON binding. v1.0: POST message:send, POST message:stream (SSE), GET tasks, GET tasks/{id}, POST tasks/{id}:cancel, GET|POST tasks/{id}:subscribe (SSE), POST|GET tasks/{id}/pushNotificationConfigs, GET|DELETE tasks/{id}/pushNotificationConfigs/{configId}, GET extendedAgentCard. v0.3 (no header): same paths with v0.3 ProtoJSON plus GET card.'
      parameters:
      - name: A2A-Version
        in: header
        required: false
        schema:
          type: string
          enum:
          - '1.0'
          - '0.3'
        description: Protocol version; empty means 0.3
      - name: agent
        in: path
        required: true
        schema:
          type: string
          enum:
          - echo
          - weather
          - travel-planner
          - approval
          - flaky
          - secure
          - reject
      responses:
        '200':
          description: A2A response
          content:
            application/json:
              schema:
                type: object
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
  /a2a/{agent}/v1/message:stream:
    post:
      tags:
      - A2A Agents
      summary: REST SendStreamingMessage (SSE)
      operationId: a2aRestSendStreamingMessage
      description: 'HTTP+JSON binding. v1.0: POST message:send, POST message:stream (SSE), GET tasks, GET tasks/{id}, POST tasks/{id}:cancel, GET|POST tasks/{id}:subscribe (SSE), POST|GET tasks/{id}/pushNotificationConfigs, GET|DELETE tasks/{id}/pushNotificationConfigs/{configId}, GET extendedAgentCard. v0.3 (no header): same paths with v0.3 ProtoJSON plus GET card.'
      parameters:
      - name: A2A-Version
        in: header
        required: false
        schema:
          type: string
          enum:
          - '1.0'
          - '0.3'
        description: Protocol version; empty means 0.3
      - name: agent
        in: path
        required: true
        schema:
          type: string
          enum:
          - echo
          - weather
          - travel-planner
          - approval
          - flaky
          - secure
          - reject
      responses:
        '200':
          description: A2A response
          content:
            application/json:
              schema:
                type: object
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
  /a2a/{agent}/v1/tasks:
    get:
      tags:
      - A2A Agents
      summary: REST ListTasks (contextId, status, pageSize, pageToken, historyLength, statusTimestampAfter, includeArtifacts)
      operationId: a2aRestListTasks
      description: 'HTTP+JSON binding. v1.0: POST message:send, POST message:stream (SSE), GET tasks, GET tasks/{id}, POST tasks/{id}:cancel, GET|POST tasks/{id}:subscribe (SSE), POST|GET tasks/{id}/pushNotificationConfigs, GET|DELETE tasks/{id}/pushNotificationConfigs/{configId}, GET extendedAgentCard. v0.3 (no header): same paths with v0.3 ProtoJSON plus GET card.'
      parameters:
      - name: A2A-Version
        in: header
        required: false
        schema:
          type: string
          enum:
          - '1.0'
          - '0.3'
        description: Protocol version; empty means 0.3
      - name: agent
        in: path
        required: true
        schema:
          type: string
          enum:
          - echo
          - weather
          - travel-planner
          - approval
          - flaky
          - secure
          - reject
      responses:
        '200':
          description: A2A response
          content:
            application/json:
              schema:
                type: object
  /a2a/{agent}/v1/extendedAgentCard:
    get:
      tags:
      - A2A Agents
      summary: REST GetExtendedAgentCard (bearer token)
      operationId: a2aRestExtendedAgentCard
      description: 'HTTP+JSON binding. v1.0: POST message:send, POST message:stream (SSE), GET tasks, GET tasks/{id}, POST tasks/{id}:cancel, GET|POST tasks/{id}:subscribe (SSE), POST|GET tasks/{id}/pushNotificationConfigs, GET|DELETE tasks/{id}/pushNotificationConfigs/{configId}, GET extendedAgentCard. v0.3 (no header): same paths with v0.3 ProtoJSON plus GET card.'
      parameters:
      - name: A2A-Version
        in: header
        required: false
        schema:
          type: string
          enum:
          - '1.0'
          - '0.3'
        description: Protocol version; empty means 0.3
      - name: agent
        in: path
        required: true
        schema:
          type: string
          enum:
          - echo
          - weather
          - travel-planner
          - approval
          - flaky
          - secure
          - reject
      responses:
        '200':
          description: A2A response
          content:
            application/json:
              schema:
                type: object
  /a2a/webhook-sink/{id}:
    get:
      tags:
      - A2A Agents
      summary: List push notifications received by a sink
      operationId: a2aSinkList
      description: 'Bounded: 50 notifications per sink, idle sinks expire after an hour'
      parameters:
      - name: A2A-Version
        in: header
        required: false
        schema:
          type: string
          enum:
          - '1.0'
          - '0.3'
        description: Protocol version; empty means 0.3
      responses:
        '200':
          description: A2A response
          content:
            application/json:
              schema:
                type: object
    post:
      tags:
      - A2A Agents
      summary: Record a push notification
      operationId: a2aSinkRecord
      description: Any JSON body; selected headers are stored
      parameters:
      - name: A2A-Version
        in: header
        required: false
        schema:
          type: string
          enum:
          - '1.0'
          - '0.3'
        description: Protocol version; empty means 0.3
      responses:
        '200':
          description: A2A response
          content:
            application/json:
              schema:
                type: object
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
    delete:
      tags:
      - A2A Agents
      summary: Clear a sink
      operationId: a2aSinkClear
      description: Removes all stored notifications
      parameters:
      - name: A2A-Version
        in: header
        required: false
        schema:
          type: string
          enum:
          - '1.0'
          - '0.3'
        description: Protocol version; empty means 0.3
      responses:
        '200':
          description: A2A response
          content:
            application/json:
              schema:
                type: object
components:
  schemas:
    ErrorResponse:
      type: object
      properties:
        error:
          type: string
          example: bad_request
        details:
          type: string
          example: Detailed error description
          nullable: true
      required:
      - error
    AuthResponse:
      type: object
      properties:
        authenticated:
          type: boolean
        auth_type:
          type: string
          enum:
          - basic-auth
          - api-key
          - hmac
          - jwt
          - mtls
        username:
          type: string
          nullable: true
        header:
          type: string
          nullable: true
        claims:
          type: object
          nullable: true
        jwt_header:
          type: object
          nullable: true
        client_dn:
          type: string
          nullable: true
        client_ca:
          type: string
          nullable: true
      required:
      - authenticated
      - auth_type
    AuthFailure:
      type: object
      properties:
        authenticated:
          type: boolean
          example: false
        error:
          type: string
          example: unauthorized
      required:
      - authenticated
      - error
    EchoResponse:
      type: object
      properties:
        method:
          type: string
          example: GET
        url:
          type: string
          example: https://api.example.com/echo?a=1
          description: As the client used it (trusted X-Forwarded-Proto/Host/Port and Forwarded applied)
        path:
          type: string
          example: /echo
        path_info:
          type: array
          items:
            type: string
        query_string:
          type: string
        query_params:
          type: object
          additionalProperties:
            type: string
        headers:
          type: object
          additionalProperties:
            type: array
            items:
              type: string
        host:
          type: string
        port:
          type: integer
        scheme:
          type: string
        remote_ip:
          type: string
        body:
          $ref: '#/components/schemas/EchoBody'
        timestamp_unix_ms:
          type: integer
          format: int64
    EchoBody:
      type: object
      properties:
        present:
          type: boolean
        included:
          type: boolean
        body:
          type: string
          nullable: true
        bytes:
          type: integer
        truncated:
          type: boolean
        utf8:
          type: boolean
        reason:
          type: string
          nullable: true
    IdentityResponse:
      type: object
      properties:
        instance_id:
          type: string
          format: uuid
        hostname:
          type: string
        version:
          type: string
        uptime_seconds:
          type: integer
        request_count:
          type: integer
        port:
          type: object
          properties:
            http:
              type: integer
            https:
              type: integer
            grpc:
              type: integer
            listener:
              type: object
              properties:
                scheme:
                  type: string
                port:
                  type: integer
        config:
          type: object
          properties:
            host:
              type: string
            public_mode:
              type: boolean
            trust_forward:
              type: boolean
            body_limit:
              type: integer
            request_timeout_secs:
              type: integer
            max_delay_ms:
              type: integer
            inspector_capacity:
              type: integer
            cors_allow_origins:
              type: array
              items:
                type: string
            admin_token_configured:
              type: boolean
        environment:
          type: object
          properties:
            rust_version:
              type: string
              example: rustc 1.86.0 (05f9846f8 2025-03-31)
            msrv:
              type: string
            profile:
              type: string
            os:
              type: string
            arch:
              type: string
        request:
          type: object
          properties:
            method:
              type: string
            peer_ip:
              type: string
            remote_ip:
              type: string
            forwarded_for:
              type: string
              nullable: true
            forwarded:
              type: string
              nullable: true
            host:
              type: string
            scheme:
              type: string
            url_base:
              type: string
            via:
              type: string
              nullable: true
        timestamp:
          type: string
          format: date-time
    FlakySuccess:
      type: object
      properties:
        status:
          type: string
          example: ok
        fail_rate:
          type: integer
        message:
          type: string
          example: Request succeeded
        request_number:
          type: integer
    FlakyFailure:
      type: object
      properties:
        error:
          type: string
          example: service_unavailable
        fail_rate:
          type: integer
        message:
          type: string
          example: Simulated failure (50% fail rate)
    CapturedRequest:
      type: object
      properties:
        id:
          type: string
        request_id:
          type: string
          nullable: true
        timestamp:
          type: string
          format: date-time
        method:
          type: string
        uri:
          type: string
        path:
          type: string
        query:
          type: string
          nullable: true
        version:
          type: string
        headers:
          type: array
          items:
            type: object
            properties:
              name:
                type: string
              value:
                type: string
        client_ip:
          type: string
          nullable: true
        session:
          type: string
          nullable: true
        key_id:
          type: string
          description: API key id (shared mode, RUSTYBIN_REQUIRE_KEY); absent otherwise
        body:
          type: string
          nullable: true
        body_base64:
          type: string
          nullable: true
        body_size:
          type: integer
        body_truncated:
          type: boolean
        status:
          type: integer
        latency_ms:
          type: number
    HarnessScenario:
      type: object
      description: 'A harness scenario: targets, layers with sources and patterns, scale, timeout and thresholds. The full JSON Schema with descriptions and this node''s bounds is served at GET /_rustybin/harness/schema.'
      required:
      - name
      properties:
        name:
          type: string
        description:
          type: string
        target:
          type: string
          description: Shorthand for one target URL
        targets:
          type: array
          items:
            type: object
        scale:
          type: object
        layers:
          type: array
          items:
            type: object
        source:
          type: object
          description: 'Shorthand: the source of a single layer'
        pattern:
          type: object
          description: 'Shorthand: the pattern of a single layer'
        model:
          type: string
          enum:
          - open
          - closed
        think_time:
          type: string
        timeout:
          type: string
        thresholds:
          type: object
    McpJsonRpcMessage:
      type: object
      description: A JSON-RPC 2.0 request, notification or response
      required:
      - jsonrpc
      properties:
        jsonrpc:
          type: string
          enum:
          - '2.0'
        id:
          oneOf:
          - type: string
          - type: integer
        method:
          type: string
        params:
          type: object
        result:
          type: object
        error:
          type: object
    RegistryProblem:
      type: object
      properties:
        title:
          type: string
        status:
          type: integer
        detail:
          type: string
        errors:
          type: array
          items:
            type: object
            properties:
              message:
                type: string
              location:
                type: string
              value: {}
    RegistryServerJson:
      type: object
      description: A server.json document (https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json)
      required:
      - name
      - description
      - version
      properties:
        $schema:
          type: string
          format: uri
        name:
          type: string
          pattern: ^[a-zA-Z0-9.-]+/[a-zA-Z0-9._-]+$
        title:
          type: string
        description:
          type: string
          maxLength: 100
        version:
          type: string
        websiteUrl:
          type: string
          format: uri
        repository:
          type: object
        remotes:
          type: array
          items:
            type: object
        packages:
          type: array
          items:
            type: object
        _meta:
          type: object
    RegistryServerResponse:
      type: object
      required:
      - server
      - _meta
      properties:
        server:
          $ref: '#/components/schemas/RegistryServerJson'
        _meta:
          type: object
          properties:
            io.modelcontextprotocol.registry/official:
              type: object
              properties:
                status:
                  type: string
                  enum:
                  - active
                  - deprecated
                  - deleted
                statusMessage:
                  type: string
                statusChangedAt:
                  type: string
                  format: date-time
                publishedAt:
                  type: string
                  format: date-time
                updatedAt:
                  type: string
                  format: date-time
                isLatest:
                  type: boolean
    RegistryServerList:
      type: object
      required:
      - servers
      - metadata
      properties:
        servers:
          type: array
          items:
            $ref: '#/components/schemas/RegistryServerResponse'
        metadata:
          type: object
          required:
          - count
          properties:
            count:
              type: integer
            nextCursor:
              type: string
    RegistryValidation:
      type: object
      required:
      - valid
      - issues
      properties:
        valid:
          type: boolean
        issues:
          type: array
          items:
            type: object
            properties:
              type:
                type: string
              path:
                type: string
              message:
                type: string
              severity:
                type: string
                enum:
                - error
                - warning
              reference:
                type: string
    ChatCompletionRequest:
      type: object
      required:
      - messages
      properties:
        model:
          type: string
          example: rustybin-gpt
        messages:
          type: array
          items:
            type: object
            required:
            - role
            properties:
              role:
                type: string
                enum:
                - system
                - developer
                - user
                - assistant
                - tool
                - function
              content:
                nullable: true
                oneOf:
                - type: string
                - type: array
                  items:
                    type: object
                    properties:
                      type:
                        type: string
                        enum:
                        - text
                        - image_url
                        - input_audio
                        - file
                        - refusal
                      text:
                        type: string
                      image_url:
                        type: object
                        properties:
                          url:
                            type: string
              tool_calls:
                type: array
                items:
                  type: object
              tool_call_id:
                type: string
        stream:
          type: boolean
          default: false
        stream_options:
          type: object
          properties:
            include_usage:
              type: boolean
        n:
          type: integer
          minimum: 1
          maximum: 8
        max_tokens:
          type: integer
        max_completion_tokens:
          type: integer
        stop:
          oneOf:
          - type: string
          - type: array
            items:
              type: string
        temperature:
          type: number
          minimum: 0
          maximum: 2
        tools:
          type: array
          items:
            type: object
            properties:
              type:
                type: string
                enum:
                - function
              function:
                type: object
                properties:
                  name:
                    type: string
                  description:
                    type: string
                  parameters:
                    type: object
        tool_choice:
          oneOf:
          - type: string
            enum:
            - none
            - auto
            - required
          - type: object
        response_format:
          type: object
          properties:
            type:
              type: string
              enum:
              - text
              - json_object
              - json_schema
            json_schema:
              type: object
    ChatCompletionResponse:
      type: object
      properties:
        id:
          type: string
          example: chatcmpl-abc123
        object:
          type: string
          example: chat.completion
        created:
          type: integer
        model:
          type: string
        choices:
          type: array
          items:
            type: object
            properties:
              index:
                type: integer
              message:
                type: object
                properties:
                  role:
                    type: string
                  content:
                    type: string
                    nullable: true
                  tool_calls:
                    type: array
                    items:
                      type: object
              logprobs:
                nullable: true
              finish_reason:
                type: string
                enum:
                - stop
                - length
                - tool_calls
                - content_filter
        usage:
          type: object
          properties:
            prompt_tokens:
              type: integer
            completion_tokens:
              type: integer
            total_tokens:
              type: integer
    AnthropicMessagesRequest:
      type: object
      required:
      - model
      - max_tokens
      - messages
      properties:
        model:
          type: string
          example: claude-sonnet-4-5
        max_tokens:
          type: integer
          example: 256
        system:
          oneOf:
          - type: string
          - type: array
            items:
              type: object
              properties:
                type:
                  type: string
                text:
                  type: string
                cache_control:
                  type: object
        messages:
          type: array
          items:
            type: object
            required:
            - role
            - content
            properties:
              role:
                type: string
                enum:
                - user
                - assistant
              content:
                oneOf:
                - type: string
                - type: array
                  items:
                    type: object
                    properties:
                      type:
                        type: string
                        enum:
                        - text
                        - image
                        - document
                        - tool_use
                        - tool_result
        tools:
          type: array
          items:
            type: object
            properties:
              name:
                type: string
              description:
                type: string
              input_schema:
                type: object
        tool_choice:
          type: object
          properties:
            type:
              type: string
              enum:
              - auto
              - any
              - tool
              - none
            name:
              type: string
        stop_sequences:
          type: array
          items:
            type: string
        stream:
          type: boolean
          default: false
    AnthropicMessage:
      type: object
      properties:
        id:
          type: string
        type:
          type: string
          example: message
        role:
          type: string
        model:
          type: string
        content:
          type: array
          items:
            type: object
        stop_reason:
          type: string
          enum:
          - end_turn
          - max_tokens
          - stop_sequence
          - tool_use
          - refusal
        stop_sequence:
          type: string
          nullable: true
        usage:
          type: object
          properties:
            input_tokens:
              type: integer
            output_tokens:
              type: integer
            cache_creation_input_tokens:
              type: integer
            cache_read_input_tokens:
              type: integer
    AiRequestRecord:
      type: object
      properties:
        request_id:
          type: string
        provider:
          type: string
        endpoint:
          type: string
        model:
          type: string
        mode:
          type: string
        stream:
          type: boolean
        credential:
          type: string
          nullable: true
        headers:
          type: object
          additionalProperties:
            type: string
        body: {}
        normalized_prompt:
          type: string
        usage:
          type: object
          properties:
            prompt_tokens:
              type: integer
            completion_tokens:
              type: integer
            total_tokens:
              type: integer
        finish_reason:
          type: string
        reply_preview:
          type: string
        captured:
          type: object
          nullable: true
  securitySchemes:
    basicAuth:
      type: http
      scheme: basic
      description: 'HTTP Basic authentication. Default credentials: basic:password'
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT
      description: JWT Bearer token authentication
    apiKeyAuth:
      type: apiKey
      in: header
      name: apikey
      description: 'API key in header. Default key: my-key'
    mcpApiKey:
      type: apiKey
      in: header
      name: X-API-Key
  RustybinSettings:
    type: object
    properties:
      persisted:
        type: boolean
        description: Changes are written to RUSTYBIN_STATE_DIR
      locked:
        type: boolean
        description: RUSTYBIN_SETTINGS_LOCK is on
      secrets_key:
        type: string
        enum:
        - env
        - file
        - process
      updated_at:
        type:
        - string
        - 'null'
        format: date-time
      unreadable_secrets:
        type: array
        items:
          type: string
      groups:
        type: array
        items:
          type: object
          properties:
            id:
              type: string
            title:
              type: string
            description:
              type: string
      settings:
        type: array
        items:
          type: object
          properties:
            key:
              type: string
            group:
              type: string
            title:
              type: string
            description:
              type: string
            schema:
              type: object
              description: JSON Schema of the value
            default: {}
            secret:
              type: boolean
            env:
              type:
              - string
              - 'null'
            lockable:
              type: boolean
            locked:
              type: boolean
            stored:
              type: boolean
            value:
              description: The effective value, or `{set, hint, source, unreadable}` for a secret
            source:
              type:
              - string
              - 'null'
              enum:
              - stored
              - env
              - default
              - null
      changed:
        type: array
        items:
          type: string
        description: 'After a write: the keys that changed'
