> ## Documentation Index
> Fetch the complete documentation index at: https://docs.knoxcall.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Update Agent

> Partially update an agent — routing, model policy, budgets, guardrails.

Partial update. Omitted fields are left as they are. This is where most of an
agent's configuration is set: everything beyond the handful of fields create
accepts is applied here.

Two changes have effects worth knowing before you make them:

* **`status: "paused"`** refuses the agent's traffic with `503 agent_not_active`
  on `/v1/ai`, and JSON-RPC `-32603` on `/v1/mcp`.
* **`guardrail_webhook_url`** is resolved and refused at write time through the
  SSRF chokepoint, and may not carry credentials in the URL
  (`https://user:pass@host/`) — a credential written into a URL is stored in
  plaintext and leaks into every log line that echoes it.

Setting `firewall_policy_id` to a policy with `action: "block"` is what makes
the firewall refuse rather than warn — see [Prompt firewall](/ai-gateway/firewall).

Requires the `update` capability on `ai_gateway`. See the [control-plane overview](/api-reference/ai-gateway/control-plane-overview#authentication) for authentication, the `{data, meta}` envelope, pagination and error types.


## OpenAPI

````yaml PATCH /ai-gateway/agents/{id}
openapi: 3.1.0
info:
  title: KnoxCall Client Management API
  version: '1.0'
  description: >
    The KnoxCall Client Management API provides programmatic access to manage
    your API gateway

    resources including routes, secrets, clients, environments, webhooks, and
    API keys.


    ## Authentication


    All requests must include an OAuth 2.1 access token **or** a legacy API key.
    The

    recommended path for new integrations is the first-party SDKs, which mint,
    cache, and

    refresh OAuth tokens for you (start with `knoxcall login` on a developer
    machine, or

    `KNOXCALL_CLIENT_ID` / `KNOXCALL_CLIENT_SECRET` in CI). You can authenticate
    using any of:


    - **Authorization header (OAuth 2.1 bearer token — recommended)**:
    `Authorization: Bearer <access_token>`

    - **Authorization header (legacy API key)**: `Authorization: Bearer
    tk_xxx_yyy`

    - **x-api-key header (legacy API key)**: `x-api-key: tk_xxx_yyy`


    OAuth 2.1 access tokens (`kc_` prefix) are minted at the root-host token
    endpoint

    `https://api.knoxcall.com/oauth/token` (not under `/v1`) using the
    `client_credentials`

    or `authorization_code` grant. See the `OAuth2` security scheme and the
    `/oauth/*`

    endpoints. Some resources that are managed exclusively through OAuth reject
    API-key auth

    with a `403 use_oauth` error.


    Long-lived API keys remain fully supported. They come in three types:

    - **Standard keys** (`tk_` prefix) are used on the production API
    (`api.knoxcall.com`).

    - **Test keys** (`tk_` prefix, type=test) are used on the sandbox API
    (`sandbox.knoxcall.com`).

    - **Enterprise Access Keys** (`AKE` prefix) follow the format
    `AKE[16chars]:[40chars]`.


    Using a test key on the production API (or vice versa) will return a `403
    wrong_key_type` error.


    ## Request IDs


    Every response — success or error — includes an `X-Request-Id` response
    header (a UUID).

    Its value is identical to the `request_id` field in the response body
    (`meta.request_id`

    on success, `error.request_id` on error). Quote it when contacting support.


    ## API Versioning


    The API is versioned on two axes:


    - **Major version — the `/v1` path prefix.** Breaking changes ship under a
    new major
      (`/v2`). `/v1` never receives a breaking change.
    - **Minor version — the `KnoxCall-Version` header (dated, `YYYY-MM-DD`).**
    Send
      `KnoxCall-Version: 2026-08-05` to pin the behaviour of a specific dated release. Omit
      the header to get the newest minor version. Every response echoes the resolved version
      back in a `KnoxCall-Version` response header.

    Within a major version, **additive changes are not breaking** and may appear
    without a new

    dated version: new endpoints, new optional request fields, new response
    fields, new

    `error.type` values, and new enum members. Clients must tolerate unknown
    response fields

    and unknown `error.type` values (see the `ErrorResponse` schema).


    An **unknown or malformed** `KnoxCall-Version` value returns `400` with

    `error.type: invalid_api_version`.


    ## Rate Limiting


    API keys can have optional rate limits configured. When a limit is
    configured, the

    following headers are emitted on **every** response (not only `429`s):

    - `X-RateLimit-Limit` -- maximum requests allowed per window

    - `X-RateLimit-Remaining` -- remaining requests in the current window

    - `X-RateLimit-Reset` -- epoch seconds at which the window resets


    When the limit is exceeded the API returns `429` (`rate_limit_exceeded` /
    `rate_limited`)

    and additionally sets `Retry-After` (seconds until the window resets).


    ## Idempotency


    All mutating requests (`POST`/`PUT`/`PATCH`/`DELETE`) accept an idempotency
    key so a

    retried request is executed at most once. Supply it as `X-Idempotency-Key`

    (the standard `Idempotency-Key` spelling is also accepted), max 255
    characters.


    - Replaying a completed request with the same key returns the original
    stored response,
      annotated with an `X-Idempotent-Replay: true` response header.
    - A key that is still being processed returns `409 request_in_progress`.

    - Re-using a key with a **different** request body returns `422
    idempotency_key_reuse`.

    - A malformed key returns `400 invalid_idempotency_key`.


    ## Pagination


    List endpoints support pagination with the following query parameters:

    - `page` -- page number (default: 1)

    - `per_page` -- items per page (default: 20, max: 100)

    - `sort` -- field to sort by (varies per endpoint)

    - `order` -- sort direction: `asc` or `desc`


    Paginated responses include a `meta` object with `total`, `page`,
    `per_page`, and `total_pages`.


    ### Offset ceiling


    The server derives its SQL offset as `(page - 1) * per_page` and will not

    exceed 100,000 rows, so `page` is clamped to `floor(100000 / per_page) + 1`

    (1,001 at the maximum `per_page` of 100). Two consequences:


    - `meta.page` echoes the **effective** page, which may be lower than the one
      you asked for. It always matches the rows returned.
    - `meta.total_pages` is capped at that same page, so it is
      `min(ceil(total / per_page), floor(100000 / per_page) + 1)`. A pager that
      walks until `page >= total_pages` therefore terminates.

    `meta.total` still reports the true row count. If `total` exceeds

    `total_pages * per_page` you are being truncated by the ceiling -- narrow

    the result set with a filter (date range, status, resource id) instead of

    paging deeper.


    ## Error Handling


    All errors follow one canonical shape everywhere on `/v1`:

    `{"error": {"type", "message", "request_id"}}`. The `type` field is a
    machine-readable

    category (see the `ErrorResponse` schema for the full enum). Request-body
    validation

    failures are always `400 validation_error`; the one `422` case is
    `idempotency_key_reuse`.
  contact:
    name: KnoxCall Support
    url: https://knoxcall.com
    email: support@knoxcall.com
  license:
    name: Proprietary
    url: https://knoxcall.com/terms
servers:
  - url: https://api.knoxcall.com/v1
    description: Production
  - url: https://sandbox.knoxcall.com/v1
    description: Sandbox (test keys only)
security:
  - OAuth2: []
  - BearerAuth: []
  - ApiKeyAuth: []
tags:
  - name: Routes
    description: Manage proxy routes that forward API requests to upstream targets.
  - name: Secrets
    description: Manage encrypted secrets injected into route headers and bodies.
  - name: Clients
    description: Manage authorized clients (users or servers) with IP-based access control.
  - name: Environments
    description: >-
      Manage deployment environments (e.g. production, staging) for route
      configuration overrides.
  - name: API Keys
    description: Create and revoke API keys for programmatic access.
  - name: Webhooks
    description: Manage webhook subscriptions for real-time event notifications.
  - name: Account
    description: Retrieve account information and usage metrics.
  - name: Audit Logs
    description: Query the immutable audit trail of all API actions.
  - name: Signup
    description: >-
      Headless account creation — the only unauthenticated endpoint, designed so
      scripts and AI agents can onboard without a browser.
  - name: OAuth Clients
    description: Manage OAuth 2.1 clients used to mint access tokens for the API.
  - name: Agents
    description: Manage BYO server agents (self-hosted data-plane / tunnel agents).
  - name: Crypto Keys
    description: >-
      Transit encryption keys — encrypt, decrypt, rewrap, sign, verify, and
      issue JWTs without key material ever leaving KnoxCall.
  - name: Encryption
    description: >-
      Portable structure-preserving encryption — encrypt any JSON into storable
      `kc:` ciphertext strings and decrypt them later.
  - name: Client Tokens
    description: >-
      Single-use client-side capability tokens (`kct_` prefix) that let a
      browser exchange one bound ciphertext or vault token for its plaintext.
  - name: PKI
    description: >-
      Private certificate authority — roots, issuing roles, leaf certificates,
      revocation, and CRLs.
  - name: Dynamic DB Credentials
    description: >-
      Just-in-time database credentials — register connections and roles, then
      mint short-lived leased users on demand.
  - name: Vaults
    description: >-
      Data tokenization vaults — swap sensitive values for format-preserving
      tokens and detokenize them under audit.
  - name: Ephemeral Proxy
    description: >-
      One-shot proxy that resolves vault-token template expressions on the wire
      — no pre-configured route required.
  - name: Wrap Credentials
    description: >-
      Escrow a third-party provider key into KnoxCall custody so it leaves your
      codebase, host-pinned; the ephemeral proxy injects it under
      X-Knox-Upstream-Auth-Secret.
  - name: Opportunities
    description: >-
      Suggestions to turn detected outbound API usage (agent-observed or
      gateway-traffic) into a durable route; accept promotes to a route + secret
      binding.
  - name: Workflows
    description: >-
      Create, manage, and run workflows. Executions are asynchronous; poll
      executions or subscribe a webhook to workflow.execution.completed|failed.
  - name: AI Gateway
    description: >-
      Manage AI egress gateways, their agents, and capability (phantom) tokens,
      plus cost/token spend rollups and FinOps exports.
paths:
  /ai-gateway/agents/{id}:
    parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
          format: uuid
        description: The AI agent UUID.
    patch:
      tags:
        - AI Gateway
      summary: Update an agent
      description: >-
        Updates one or more fields on an agent. Only provided fields are
        changed.
      operationId: updateAgent
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UpdateAiGatewayAgentRequest'
            example:
              default_model: claude-opus-4-8
              streaming_enabled: true
      responses:
        '200':
          description: The updated agent.
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: '#/components/schemas/AiGatewayAgent'
                  meta:
                    $ref: '#/components/schemas/RequestMeta'
        '400':
          $ref: '#/components/responses/ValidationError'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '402':
          $ref: '#/components/responses/PlanLimit'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '500':
          $ref: '#/components/responses/InternalError'
components:
  schemas:
    UpdateAiGatewayAgentRequest:
      type: object
      description: >-
        Only the provided fields are changed. Every writable column on an agent
        appears here — this schema is what the typed SDKs are generated and
        reviewed against, so a field missing from it becomes a field missing
        from them (AIGW-161).
      properties:
        name:
          type: string
        slug:
          type: string
          description: >-
            RENAMES the agent, which MOVES its data-plane URL: `agent_url` is
            computed from the slug rather than stored, so every caller pointed
            at the old URL gets a 404 from the moment this returns. The new URL
            is on this response — read it rather than reconstructing it.
        description:
          type: string
          nullable: true
        primary_route_id:
          type: string
          format: uuid
          nullable: true
        fallback_route_ids:
          type: array
          items:
            type: string
            format: uuid
        default_model:
          type: string
          nullable: true
        model_allowlist:
          type: array
          items:
            type: string
        model_denylist:
          type: array
          items:
            type: string
        model_rewrite:
          type: object
          additionalProperties:
            type: string
          maxProperties: 100
          description: >-
            Map of requested model → substituted model. Values must be STRINGS
            (the server validates with a string map) and at most 100 entries.
        budget_daily_usd:
          type: number
          nullable: true
        budget_monthly_usd:
          type: number
          nullable: true
        budget_per_call_max_tokens:
          type: integer
          nullable: true
        budget_overage_action:
          type: string
          enum:
            - block
            - warn
            - fallback
          description: >-
            What happens once a budget is exhausted. `fallback` needs
            `fallback_agent_id`; without one it behaves as `block`.
        fallback_agent_id:
          type: string
          format: uuid
          nullable: true
        streaming_enabled:
          type: boolean
        firewall_policy_id:
          type: string
          format: uuid
          nullable: true
        pii_redact_policy_id:
          type: string
          format: uuid
          nullable: true
        pii_detokenize_response:
          type: boolean
          deprecated: true
          description: >-
            AIGW-100 legacy alias for `pii_response_mode` (`true` ->
            `detokenize`, `false` -> `redact`). Still accepted; sending both
            with contradictory values is a 400.
        pii_request_mode:
          type: string
          enum:
            - 'off'
            - tokenize
          description: >-
            What happens to the PROMPT before it leaves KnoxCall. Default
            `tokenize`. Set `off` only for an agent whose payload must reach the
            provider byte-for-byte (structured tool-use JSON, for example) — it
            means prompts are forwarded unscanned.
        pii_response_mode:
          type: string
          enum:
            - redact
            - detokenize
          description: What happens to the provider's answer. Default `detokenize`.
        pii_streaming_holdback_chars:
          type: integer
          description: >-
            How many characters of a STREAMED answer are held back while a
            detector decides. Larger catches an entity split across more chunks;
            smaller is lower latency.
        pii_streaming_mode:
          type: string
          enum:
            - holdback
            - buffer
            - monitor
          description: >-
            How a streamed answer is rewritten. `holdback` releases text behind
            a sliding window; `buffer` withholds the whole answer until it is
            complete; `monitor` REPORTS detections without rewriting, so the raw
            value reaches the client — an observability mode, not a redaction
            one.
        tags:
          type: object
          additionalProperties: true
          description: >-
            FinOps attribution labels (cost_center/team/project/…) echoed onto
            this agent's usage rows.
        cache_mode:
          type: string
          enum:
            - 'off'
            - exact
            - semantic
          description: >-
            `semantic` additionally needs `cache_embedding_model`; without one
            the cache reports itself degraded and serves nothing.
        cache_ttl_seconds:
          type: integer
        cache_similarity_threshold:
          type: number
          minimum: 0
          maximum: 1
          description: >-
            Cosine floor for a semantic cache hit, 0–1. Lower means more hits,
            and more wrong ones. NOT nullable: the column is `NOT NULL` and the
            validator is non-nullable, so an explicit `null` is a 400.
        cache_embedding_model:
          type: string
          nullable: true
        tool_allowlist:
          type: array
          items:
            type: string
        output_schema:
          type: object
          additionalProperties: true
          nullable: true
          description: JSON Schema the answer is validated against.
        output_validation_action:
          type: string
          enum:
            - block
            - retry
            - warn
        data_residency_region:
          type: string
          nullable: true
        cmek_key_id:
          type: string
          format: uuid
          nullable: true
        routing_policy:
          $ref: '#/components/schemas/AiGatewayRoutingPolicy'
        guardrail_webhook_url:
          type: string
          format: uri
          nullable: true
          description: >
            HTTPS endpoint your own scanner listens on (AIGW-45). Every prompt —
            and, in `response`/`both` mode, every response — is POSTed to it for
            a verdict before it moves.


            What it receives is the REDACTED, post-tokenization body: the same
            bytes the provider would get, never a resolved provider credential
            and never your KnoxCall token. Shipping raw PHI to a third party to
            ask whether it contains PHI would be the leak the gateway exists to
            prevent.


            The destination is resolved and refused at write time AND on every
            call — a private, loopback, link-local or cloud-metadata address is
            a 400, because DNS is not a promise.
        guardrail_webhook_secret_id:
          type: string
          format: uuid
          nullable: true
          description: >
            Secret holding the HMAC-SHA256 key deliveries are signed with. The
            signature covers `<timestamp>.<body>` and travels in
            `x-knox-guardrail-signature` with the timestamp in
            `x-knox-guardrail-timestamp`, so a captured delivery is not
            replayable. Omit to send unsigned — the receiver then cannot prove
            the call came from KnoxCall.
        guardrail_webhook_mode:
          type: string
          enum:
            - 'off'
            - request
            - response
            - both
          description: >
            Which directions are inspected. `response` and `both` evaluate the
            complete buffered response before it is sent, so an agent in those
            modes REFUSES a streaming request (400
            `guardrail_streaming_unsupported`) rather than serving one past a
            control that could not run on it.
        guardrail_webhook_timeout_ms:
          type: integer
          minimum: 100
          maximum: 10000
          description: >
            How long to wait for a verdict. Clamped, because a request waiting
            on your scanner is holding one of the gateway's API workers.
        guardrail_webhook_failure_action:
          type: string
          enum:
            - fail_open
            - fail_closed
          description: >
            What happens when the hook does not answer. Applies identically to
            EVERY non-answer — timeout, refused connection, refused destination,
            non-2xx, unparseable body, an `action` verb we do not recognise.
            Defaults to `fail_open`, which is the posture an agent already has
            with no hook at all.


            A hook responds `200` with
            `{"action":"allow"|"block"|"flag","reason":"…"}`. `block` refuses
            the request with 400 `guardrail_block` and your `reason` in the
            message; `flag` serves it and records the outcome.
    AiGatewayAgent:
      type: object
      description: An agent under a gateway — the addressable unit an AI SDK points at.
      properties:
        id:
          type: string
          format: uuid
        tenant_id:
          type: string
          format: uuid
        gateway_id:
          type: string
          format: uuid
        name:
          type: string
        slug:
          type: string
        description:
          type: string
          nullable: true
        primary_route_id:
          type: string
          format: uuid
          nullable: true
        fallback_route_ids:
          type: array
          items:
            type: string
            format: uuid
        model_allowlist:
          type: array
          items:
            type: string
        model_denylist:
          type: array
          items:
            type: string
        default_model:
          type: string
          nullable: true
        model_rewrite:
          type: object
          additionalProperties:
            type: string
          maxProperties: 100
          description: >-
            Map of requested model → substituted model. Values must be STRINGS
            (the server validates with a string map) and at most 100 entries.
        budget_daily_usd:
          type: number
          nullable: true
        budget_monthly_usd:
          type: number
          nullable: true
        budget_per_call_max_tokens:
          type: integer
          nullable: true
        budget_overage_action:
          type: string
        fallback_agent_id:
          type: string
          format: uuid
          nullable: true
        pii_redact_policy_id:
          type: string
          format: uuid
          nullable: true
        pii_detokenize_response:
          type: boolean
          readOnly: true
          description: >-
            AIGW-100 legacy alias. Read-only mirror of `pii_response_mode ==
            "detokenize"`; the column is GENERATED in the database so the two
            can never disagree. Still ACCEPTED on create and update, where it is
            folded onto `pii_response_mode`.
        pii_request_mode:
          type: string
          enum:
            - 'off'
            - tokenize
          description: >-
            What happens to the PROMPT before it leaves KnoxCall. `tokenize`
            (default) replaces detected entities with conversation-scoped,
            format-preserving tokens before the provider sees them; `off`
            forwards the prompt exactly as sent. Independent of
            `pii_response_mode` — before AIGW-100 request tokenization was gated
            on the response toggle, so an agent with a PII policy and
            `pii_detokenize_response: false` sent prompts verbatim.
        pii_response_mode:
          type: string
          enum:
            - redact
            - detokenize
          description: >-
            What happens to the provider's answer. `redact` runs the detector
            stack (fresh PII in the completion, canary tokens) and leaves `KC_*`
            tokens as tokens; `detokenize` (default) also swaps them back to
            this conversation's originals. There is no `off` — the detector
            stack runs on every 2xx.
        pii_streaming_holdback_chars:
          type: integer
        pii_streaming_mode:
          type: string
        cache_mode:
          type: string
        cache_ttl_seconds:
          type: integer
        cache_similarity_threshold:
          type: number
          nullable: true
        cache_embedding_model:
          type: string
          nullable: true
        streaming_enabled:
          type: boolean
        firewall_policy_id:
          type: string
          format: uuid
          nullable: true
        tool_allowlist:
          type: array
          items:
            type: string
        output_schema:
          type: object
          nullable: true
          additionalProperties: true
        output_validation_action:
          type: string
        data_residency_region:
          type: string
          nullable: true
          enum:
            - us
            - eu
            - uk
            - ca
            - au
            - jp
            - in
          description: >-
            AIGW-53: pin this agent's upstream egress to a data-residency
            region. When set, a request whose upstream endpoint KnoxCall cannot
            classify as serving that region is refused on the data plane with
            HTTP 403 `residency_violation` — including a fallback route, which
            is where an unpinned failover would otherwise leave the region.
            `null` (the default) applies no residency constraint. See
            https://docs.knoxcall.com/ai-gateway/data-residency for the
            endpoints KnoxCall classifies and for what this control does and
            does not guarantee.
        cmek_key_id:
          type: string
          format: uuid
          nullable: true
          description: >-
            AIGW-53: the `tenant_master_keys` id this agent's AI data (its PII
            token map) is encrypted under. Must be one of THIS tenant's keys and
            must be customer-managed (`wrap_method = customer_kms`, i.e. BYOK
            configured) — a KnoxCall-wrapped key is refused, because a CMEK the
            platform can unwrap is not a CMEK. If the key is missing, retired,
            revoked, or your KMS refuses to unwrap it, the data-plane request is
            refused with HTTP 503 `cmek_unavailable` rather than written under a
            different key.
        status:
          type: string
          enum:
            - active
            - paused
            - archived
        paused_reason:
          type: string
          nullable: true
        tags:
          type: object
          additionalProperties: true
          description: >-
            FinOps attribution labels (cost_center/team/project/…) echoed onto
            this agent's usage rows.
        provider:
          type: string
          nullable: true
          description: >-
            The upstream shape this agent fronts — one of the values
            `CreateAiGatewayAgentRequest.provider` enumerates (the catalog has
            grown to fourteen; `ai_gateway_agents_provider_check` is the
            authority). Set once, at create time, and deliberately NOT
            patchable: changing it without re-pointing `primary_route_id` at a
            matching route would make the stored value a lie. `null` on an agent
            created before the field existed whose route shape the backfill did
            not recognise — render that as "custom", never as a guess.
        routing_policy:
          allOf:
            - $ref: '#/components/schemas/AiGatewayRoutingPolicy'
          description: >
            Returned on every agent read. `{}` — the default for every agent —
            means one attempt per route and fail-over to `fallback_route_ids` on
            5xx only.
        guardrail_webhook_url:
          type: string
          format: uri
          nullable: true
          description: >
            The external scanner this agent POSTs to for a verdict, or null when
            it has none. See `CreateAiGatewayAgentRequest.guardrail_webhook_url`
            for what the hook receives and how the destination is refused.
        guardrail_webhook_secret_id:
          type: string
          format: uuid
          nullable: true
          description: >
            Secret holding the HMAC-SHA256 key deliveries are signed with, or
            null when deliveries are sent unsigned.
        guardrail_webhook_mode:
          type: string
          enum:
            - 'off'
            - request
            - response
            - both
          description: >
            Which directions are inspected. `off` when the agent has no hook —
            the server returns the string, never a boolean.
        guardrail_webhook_timeout_ms:
          type: integer
          minimum: 100
          maximum: 10000
          description: How long the gateway waits for a verdict. Defaults to 2000.
        guardrail_webhook_failure_action:
          type: string
          enum:
            - fail_open
            - fail_closed
          description: >
            What happens when the hook does not answer. Defaults to `fail_open`,
            the posture an agent already has with no hook at all.
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
        created_by:
          type: string
          nullable: true
        agent_url:
          type: string
          description: >
            The data-plane base URL for this agent —
            `https://{tenant}.knoxcall.com/v1/ai/{slug}`.

            Point an AI SDK's `base_url` here and give it a capability token as
            the API key.


            Present on EVERY agent projection: create, get, update and the list
            rows (AIGW-160

            era it was create and get only, so a list row was shaped differently
            from the row

            create returned). It is server-computed rather than stored, so it
            MOVES when `slug`

            changes — which is why `PATCH` returns it too.


            An EMPTY STRING when the tenant slug cannot be resolved. Treat empty
            as "not

            available", never as a URL.
      required:
        - id
        - gateway_id
        - name
        - slug
        - status
        - agent_url
        - created_at
        - updated_at
    RequestMeta:
      type: object
      description: Metadata included with every API response.
      properties:
        request_id:
          type: string
          format: uuid
          description: >-
            Unique identifier for this API request, useful for support and
            debugging.
          example: 550e8400-e29b-41d4-a716-446655440000
      required:
        - request_id
    AiGatewayRoutingPolicy:
      type: object
      description: >
        How this agent retries and spreads load (AIGW-42). An empty object — the
        default for every agent — means one attempt per route and fail-over to
        `fallback_route_ids` on 5xx only, which is the behaviour the gateway has
        always had.


        Every field is clamped server-side. A request parked waiting to retry
        occupies an API worker, so `max_attempts` caps at 5, any single delay at
        30s, a honoured `Retry-After` at 60s, and the total sleep for one
        request at 60s regardless of how the fields combine.
      additionalProperties: false
      properties:
        retry_on:
          type: array
          description: >
            Which outcomes are retried on the SAME route before failing over.
            `429` also promotes a rate-limit response to a fail-over trigger;
            without it a 429 is returned to you immediately, unchanged.
          items:
            type: string
            enum:
              - '429'
              - 5xx
              - timeout
        max_attempts:
          type: integer
          minimum: 1
          maximum: 5
          description: >
            Attempts per candidate route, including the first. Forced to 1 when
            `retry_on` is empty, so a stored policy never claims a retry that
            cannot happen.
        backoff_ms:
          type: integer
          minimum: 0
          maximum: 30000
          description: Delay before the second attempt.
        backoff_multiplier:
          type: number
          minimum: 1
          maximum: 10
        max_backoff_ms:
          type: integer
          minimum: 0
          maximum: 30000
        respect_retry_after:
          type: boolean
          description: >
            Default true. An upstream `Retry-After` is honoured when it asks for
            LONGER than the computed backoff — it can extend a wait, never
            shorten one.
        max_retry_after_ms:
          type: integer
          minimum: 0
          maximum: 60000
          description: Ceiling on a honoured `Retry-After`.
        jitter:
          type: boolean
          description: >
            Default true. Spreads each delay across 50–100% of its computed
            value so a fleet does not retry in lockstep.
        route_weights:
          type: object
          description: >
            `{route_id: weight}`. When present, the route that serves a request
            is chosen in proportion to its weight across the primary and every
            fallback, and the remainder stay in weighted order for fail-over. A
            route not named here keeps weight 1; a route weighted 0 is drained
            and receives no traffic at all, including fail-over traffic. The
            token's own capability scope is enforced on every candidate, so a
            weight can never reach a route the token was not granted.
          additionalProperties:
            type: integer
            minimum: 0
            maximum: 1000
    ErrorResponse:
      type: object
      description: Standard error response envelope.
      properties:
        error:
          type: object
          properties:
            type:
              type: string
              description: >-
                Machine-readable error type. The set is OPEN — endpoints define
                specific types beyond the common ones below, so clients should
                branch on the types they know and fall back to the HTTP status
                class for anything unrecognized. Common values:
                authentication_required, invalid_api_key, wrong_key_type,
                subscription_inactive, rate_limit_exceeded, validation_error,
                forbidden, not_found, conflict, in_use, plan_limit,
                plan_feature, internal_error, invalid_request,
                invalid_definition, not_cancellable, invalid_idempotency_key,
                idempotency_key_reuse, request_in_progress,
                system_client_protected, use_oauth.
              example: validation_error
            message:
              type: string
              description: Human-readable error message.
              example: name and target_base_url are required.
            request_id:
              type: string
              format: uuid
              description: Unique identifier for this request.
              example: 550e8400-e29b-41d4-a716-446655440000
          required:
            - type
            - message
            - request_id
      required:
        - error
  responses:
    ValidationError:
      description: >-
        The request failed validation (always `400 validation_error`). A
        malformed idempotency key returns `400 invalid_idempotency_key`.
      headers:
        X-Request-Id:
          $ref: '#/components/headers/X-Request-Id'
        KnoxCall-Version:
          $ref: '#/components/headers/KnoxCall-Version'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          examples:
            validation_error:
              summary: Request body failed validation
              value:
                error:
                  type: validation_error
                  message: name and target_base_url are required.
                  request_id: 550e8400-e29b-41d4-a716-446655440000
            invalid_idempotency_key:
              summary: Malformed idempotency key
              value:
                error:
                  type: invalid_idempotency_key
                  message: >-
                    X-Idempotency-Key must be a non-empty string of at most 255
                    characters.
                  request_id: 550e8400-e29b-41d4-a716-446655440000
    Unauthorized:
      description: Authentication is required or the provided API key is invalid.
      headers:
        X-Request-Id:
          $ref: '#/components/headers/X-Request-Id'
        KnoxCall-Version:
          $ref: '#/components/headers/KnoxCall-Version'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          examples:
            missing_key:
              summary: No API key provided
              value:
                error:
                  type: authentication_required
                  message: >-
                    API key is required. Use Authorization: Bearer <api_key> or
                    x-api-key header.
                  request_id: 550e8400-e29b-41d4-a716-446655440000
            invalid_key:
              summary: Invalid API key
              value:
                error:
                  type: invalid_api_key
                  message: Invalid API key.
                  request_id: 550e8400-e29b-41d4-a716-446655440000
    PlanLimit:
      description: >
        Payment Required. The action exceeds your plan's limits. Two
        `error.type` values

        share this status: `plan_limit` when a COUNTED resource quota was
        reached (routes,

        secrets, AI agents, MCP servers), and `plan_feature` when the CAPABILITY
        itself is

        not on your tier (custom PII redaction policies, compliance packs,
        custom

        prompt-firewall policies). Both mean "upgrade to proceed"; branch on
        `type` only if

        you want to distinguish "you have used your allowance" from "buy a
        bigger plan".


        Neither ever revokes something you already have: a plan downgrade leaves
        existing

        agents, policies and installed packs in place and keeps working.
      headers:
        X-Request-Id:
          $ref: '#/components/headers/X-Request-Id'
        KnoxCall-Version:
          $ref: '#/components/headers/KnoxCall-Version'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          example:
            error:
              type: plan_limit
              message: Route limit reached (10). Upgrade your plan to add more.
              request_id: 550e8400-e29b-41d4-a716-446655440000
    Forbidden:
      description: >
        The key is authenticated but not authorized for this action (missing
        scope), a plan

        limit was reached, or the resource must be managed via OAuth
        (`use_oauth`) / is a

        protected system client (`system_client_protected`).
      headers:
        X-Request-Id:
          $ref: '#/components/headers/X-Request-Id'
        KnoxCall-Version:
          $ref: '#/components/headers/KnoxCall-Version'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          examples:
            forbidden:
              summary: Missing scope
              value:
                error:
                  type: forbidden
                  message: This key is not authorized to list AI gateways.
                  request_id: 550e8400-e29b-41d4-a716-446655440000
            use_oauth:
              summary: Resource is OAuth-only
              value:
                error:
                  type: use_oauth
                  message: >-
                    This resource must be managed with an OAuth 2.1 access
                    token.
                  request_id: 550e8400-e29b-41d4-a716-446655440000
            system_client_protected:
              summary: Protected system client
              value:
                error:
                  type: system_client_protected
                  message: >-
                    This client is managed by the platform and cannot be
                    modified.
                  request_id: 550e8400-e29b-41d4-a716-446655440000
    NotFound:
      description: The requested resource was not found.
      headers:
        X-Request-Id:
          $ref: '#/components/headers/X-Request-Id'
        KnoxCall-Version:
          $ref: '#/components/headers/KnoxCall-Version'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          example:
            error:
              type: not_found
              message: Route not found.
              request_id: 550e8400-e29b-41d4-a716-446655440000
    InternalError:
      description: An unexpected server error occurred.
      headers:
        X-Request-Id:
          $ref: '#/components/headers/X-Request-Id'
        KnoxCall-Version:
          $ref: '#/components/headers/KnoxCall-Version'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          example:
            error:
              type: internal_error
              message: An unexpected error occurred.
              request_id: 550e8400-e29b-41d4-a716-446655440000
  headers:
    X-Request-Id:
      description: >
        Unique identifier for this request (UUID). Present on **every** response
        and equal

        to the `request_id` field in the response body. Quote it when contacting
        support.
      schema:
        type: string
        format: uuid
      example: 550e8400-e29b-41d4-a716-446655440000
    KnoxCall-Version:
      description: >
        The dated API version (`YYYY-MM-DD`) that served this response. Equals
        the value

        of the `KnoxCall-Version` request header when supplied, otherwise the
        newest version.
      schema:
        type: string
        pattern: ^\d{4}-\d{2}-\d{2}$
      example: '2026-08-05T00:00:00.000Z'
  securitySchemes:
    OAuth2:
      type: oauth2
      description: >
        OAuth 2.1 authentication — recommended for new integrations. Access
        tokens

        (`kc_` prefix) are minted at the root-host token endpoint

        `https://api.knoxcall.com/oauth/token` and passed as `Authorization:
        Bearer <access_token>`.

        Public clients must use PKCE with the `authorization_code` grant;
        confidential

        clients may use `client_credentials`. The first-party SDKs and the

        `knoxcall login` CLI handle token minting, caching, refresh, and DPoP
        for you.
      flows:
        clientCredentials:
          tokenUrl: https://api.knoxcall.com/oauth/token
          refreshUrl: https://api.knoxcall.com/oauth/token
          scopes:
            routes:read: Read routes.
            routes:write: Create, update, and delete routes.
            secrets:read: >-
              Read secret metadata. Does NOT include retrieving a live OAuth2
              access token.
            secrets:oauth_token: >-
              Retrieve a live upstream OAuth2 access token (GET
              /v1/secrets/{id}/oauth2/token). Distinct from secrets:read because
              the call refreshes the credential at the provider.
            secrets:write: Create, update, and delete secrets.
            vaults:detokenize: >-
              Turn a vault token back into its original value. Required by GET
              /v1/vaults/{vault}/tokens/{token}, by a token template reference
              in a /v1/proxy body, and by creating a `detokenize` route action.
              Distinct from vaults:read, which lists vaults and token metadata
              and never returns a value.
            transit:decrypt: >-
              Open a portable `kc:` ciphertext with a tenant transit key.
              Required by creating a `decrypt` route action, which stores a
              standing instruction to open every matching field on every later
              request through that route. It is the same capability name the
              policy engine uses for POST /v1/decrypt. No routes scope satisfies
              it — managing a route is not permission to open the data flowing
              through it.
            proxy:invoke: >-
              Call the one-shot ephemeral proxy (any method on /v1/proxy).
              Required for EVERY verb — the proxy is one capability behind one
              handler, and a bodyless GET spends an escrowed credential exactly
              as a POST does. Neither proxy:read nor proxy:write satisfies it.
            clients:read: Read clients.
            clients:write: Manage clients.
            webhooks:write: Manage webhooks.
            workflows:write: Manage and execute workflows.
            '*:*': Full access (all scopes).
        authorizationCode:
          authorizationUrl: https://api.knoxcall.com/oauth/authorize
          tokenUrl: https://api.knoxcall.com/oauth/token
          refreshUrl: https://api.knoxcall.com/oauth/token
          scopes:
            routes:read: Read routes.
            routes:write: Create, update, and delete routes.
            secrets:read: >-
              Read secret metadata. Does NOT include retrieving a live OAuth2
              access token.
            secrets:oauth_token: >-
              Retrieve a live upstream OAuth2 access token (GET
              /v1/secrets/{id}/oauth2/token). Distinct from secrets:read because
              the call refreshes the credential at the provider.
            secrets:write: Create, update, and delete secrets.
            vaults:detokenize: >-
              Turn a vault token back into its original value. Required by GET
              /v1/vaults/{vault}/tokens/{token}, by a token template reference
              in a /v1/proxy body, and by creating a `detokenize` route action.
              Distinct from vaults:read, which lists vaults and token metadata
              and never returns a value.
            transit:decrypt: >-
              Open a portable `kc:` ciphertext with a tenant transit key.
              Required by creating a `decrypt` route action, which stores a
              standing instruction to open every matching field on every later
              request through that route. It is the same capability name the
              policy engine uses for POST /v1/decrypt. No routes scope satisfies
              it — managing a route is not permission to open the data flowing
              through it.
            proxy:invoke: >-
              Call the one-shot ephemeral proxy (any method on /v1/proxy).
              Required for EVERY verb — the proxy is one capability behind one
              handler, and a bodyless GET spends an escrowed credential exactly
              as a POST does. Neither proxy:read nor proxy:write satisfies it.
            clients:read: Read clients.
            clients:write: Manage clients.
            webhooks:write: Manage webhooks.
            workflows:write: Manage and execute workflows.
            '*:*': Full access (all scopes).
    BearerAuth:
      type: http
      scheme: bearer
      description: >
        Legacy long-lived API key authentication via the Authorization header.

        Pass your API key as: `Authorization: Bearer tk_xxx_yyy`.


        Standard keys (`tk_` prefix) are accepted on `api.knoxcall.com`.

        Test keys (type=test) are accepted on `sandbox.knoxcall.com`.

        Enterprise Access Keys (`AKE` prefix) are accepted on both.


        Fully supported, but prefer the `OAuth2` scheme (or the SDKs, which
        handle

        OAuth for you) for new integrations.
    ApiKeyAuth:
      type: apiKey
      in: header
      name: x-api-key
      description: >
        Legacy API key authentication via the `x-api-key` header.

        This is an alternative to Bearer token authentication for clients that
        cannot

        set the `Authorization` header. Prefer the `OAuth2` scheme for new
        integrations.

````