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

# Update a model mapping

> When `require_pricing_for_mappings` is on, an enabled post-update
row must have pricing configured for its `upstream_model`. Mirrors
the create-path guard so disabled rows can't be silently re-enabled
(or swapped to an unpriced upstream) without re-validating.
`enabled: false` is always allowed.



## OpenAPI

````yaml /api-reference/openapi.yaml put /api/llm-gateway/admin/model-mappings/{id}
openapi: 3.1.0
info:
  title: Barndoor.ai Public API
  description: Unified public API specification for Barndoor.ai platform
  version: 2.18.0
servers:
  - url: https://{host}
    description: Barndoor public API
    variables:
      host:
        default: platform.barndoor.ai
        description: >-
          The host serving your Barndoor deployment. Defaults to production;
          override it for another environment or a dedicated deployment.
security:
  - ApiKey: []
  - OAuth2:
      - openid
      - profile
      - email
tags:
  - name: dlp
    x-group: Data Control Center
  - name: identity
    x-group: Identity
  - name: llm-gateway
    x-group: LLM Gateway
  - name: notification
    x-group: Notifications
  - name: policy
    x-group: Policies
  - name: registry
    x-group: MCP Gateway
  - name: system-management
    x-group: Log Exports
paths:
  /api/llm-gateway/admin/model-mappings/{id}:
    put:
      tags:
        - llm-gateway
      summary: Update a model mapping
      description: |-
        When `require_pricing_for_mappings` is on, an enabled post-update
        row must have pricing configured for its `upstream_model`. Mirrors
        the create-path guard so disabled rows can't be silently re-enabled
        (or swapped to an unpriced upstream) without re-validating.
        `enabled: false` is always allowed.
      operationId: update_model_mapping
      parameters:
        - name: id
          in: path
          description: Identifier, a UUID
          required: true
          schema:
            type: string
            format: uuid
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UpdateModelMappingRequest'
        required: true
      responses:
        '200':
          description: The requested record
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ModelMapping'
        '400':
          description: Malformed request
        '401':
          description: Missing or invalid credentials
        '403':
          description: Caller is not permitted to act on this resource in this organization
        '404':
          description: No such resource in this organization
        '409':
          description: >-
            The edit would duplicate an existing 1:1 anchor mapping for this
            provider
        '500':
          description: Unexpected server error
        '503':
          description: Governance config read timed out; safe to retry
components:
  schemas:
    UpdateModelMappingRequest:
      type: object
      properties:
        bare_alias:
          type:
            - boolean
            - 'null'
        change_note:
          type:
            - string
            - 'null'
          description: >-
            Optional free-text note explaining why this edit was made
            (BCP-3998).


            Deliberately *not* tri-state like the timeout overrides above:
            omitting

            it clears the previous note, because the note describes the latest

            change and an unrelated later edit must not inherit an explanation

            written for a different one.
        cooldown_429_default_secs:
          type:
            - integer
            - 'null'
          format: int32
          description: |-
            Cooldown on an upstream 429 that carries no usable `Retry-After`
            (a `Retry-After` sets the window instead). Range 1-3600, and at most
            `cooldown_max_secs`; default 30.

            Omitted (or `null`) keeps the stored value.
        cooldown_base_secs:
          type:
            - integer
            - 'null'
          format: int32
          description: >-
            First cooldown window once the threshold trips, doubled on each
            failed

            recovery probe up to `cooldown_max_secs`. Range 1-3600, and at most

            `cooldown_max_secs`; default 30.


            Omitted (or `null`) keeps the stored value.
        cooldown_failure_threshold:
          type:
            - integer
            - 'null'
          format: int32
          description: >-
            Gateway failures (5xx, timeouts, connection errors, managed-key
            401/403)

            within `cooldown_window_secs` that cool the route. `0` disables
            every

            cooldown of the shared route (rolling failures, 429 and 529);
            per-user

            credential cooldowns on passthrough routes still apply. Range 0-100;

            default 10.


            Omitted (or `null`) keeps the stored value.
        cooldown_max_secs:
          type:
            - integer
            - 'null'
          format: int32
          description: |-
            Cap on every cooldown window: doubling, 429 and 529. Range 1-86400;
            default 300.

            Omitted (or `null`) keeps the stored value.
        cooldown_overloaded_secs:
          type:
            - integer
            - 'null'
          format: int32
          description: >-
            Flat cooldown on an upstream 529 "overloaded" with no usable

            `Retry-After`; it does not spend the failure budget. `0` counts a
            529 as

            an ordinary gateway failure instead. Range 0-3600, and at most

            `cooldown_max_secs` unless 0; default 10.


            Omitted (or `null`) keeps the stored value.
        cooldown_window_secs:
          type:
            - integer
            - 'null'
          format: int32
          description: >-
            Width of the rolling failure window, seconds. Range 1-3600; default
            60.


            Omitted (or `null`) keeps the stored value.
        enabled:
          type:
            - boolean
            - 'null'
        model_alias:
          type:
            - string
            - 'null'
        priority:
          type:
            - integer
            - 'null'
          format: int32
        request_timeout_secs:
          type:
            - integer
            - 'null'
          format: int32
          description: >-
            `None` (omitted) leaves the column unchanged. `Some(None)` (JSON

            `null`) clears the override. `Some(Some(secs))` sets the
            non-streaming

            total-request timeout; the range is fixed platform policy

            (`TOTAL_REQUEST_TIMEOUT_MIN_SECS..=TOTAL_REQUEST_TIMEOUT_MAX_SECS`,
            see

            [`crate::proxy::timeouts`]).
        retry_on_429_count:
          type:
            - integer
            - 'null'
          format: int32
        retry_on_429_max_wait_secs:
          type:
            - integer
            - 'null'
          format: int32
        stream_idle_timeout_secs:
          type:
            - integer
            - 'null'
          format: int32
          description: |-
            `None` (omitted) leaves the column unchanged. `Some(None)` (JSON
            `null`) clears the override. `Some(Some(secs))` sets the streaming
            per-chunk timeout; the range is fixed platform policy
            (`STREAM_IDLE_TIMEOUT_MIN_SECS..=STREAM_IDLE_TIMEOUT_MAX_SECS`, see
            [`crate::proxy::timeouts`]).
        upstream_model:
          type:
            - string
            - 'null'
    ModelMapping:
      allOf:
        - $ref: '#/components/schemas/CooldownPolicy'
          description: |-
            The route's passive cooldown policy (BCP-2671 H5), serialized as six
            flat `cooldown_*` fields.
        - $ref: '#/components/schemas/ChangeAttribution'
          description: |-
            Who created and last changed this route, and why (BCP-3998).

            Read with `try_get`, so the reads that don't select the columns come
            back unattributed rather than failing.
        - type: object
          required:
            - id
            - provider_id
            - model_alias
            - upstream_model
            - enabled
            - priority
            - retry_on_429_count
            - retry_on_429_max_wait_secs
            - bare_alias
            - kind
            - source
          properties:
            auto_disabled_reason:
              oneOf:
                - $ref: '#/components/schemas/AutoDisabledReason'
                  description: >-
                    Set when the *gateway* turned this row off (BCP-3811's
                    `full` sync

                    retiring a model the catalog dropped). `None` means a human
                    decided

                    the `enabled` flag, and the reconciler will not touch it in
                    either

                    direction — see migration V63. Any admin write to `enabled`
                    clears this.


                    Clearing it is *not* a durable claim on the row, and it is
                    easy to read

                    it that way. It protects a human's **disable** — the
                    re-enable branch

                    requires the marker to be present, so a row a human turned
                    off is never

                    turned back on. It cannot protect a human's **re-enable**: a
                    cleared

                    marker is exactly what makes the row eligible for retirement
                    again, so

                    under `full` a model the catalog still does not list is
                    re-disabled on

                    the next tick. That is `full`'s contract ("enabled set
                    tracks the

                    catalog"), not a bug — see

                    `model_sync::tests::re_enabling_a_dropped_model_is_retired_again_on_the_next_tick`.
                - type: 'null'
            bare_alias:
              type: boolean
              description: >-
                When `true` the row participates in bare-name resolution

                (`POST /v1/chat/completions {"model": "<alias>"}`). Route
                targets are

                `true`; enablements (`kind = model`) are always `false` and are

                addressable solely via the explicit `<provider>/<upstream>`
                form.
            enabled:
              type: boolean
            id:
              type: string
              format: uuid
            kind:
              $ref: '#/components/schemas/MappingKind'
              description: >-
                Whether this row is the model's enablement or a route target —
                see

                [`MappingKind`]. Never inferred from `model_alias ==
                upstream_model`:

                a route may carry its upstream's name (migration V84).
            model_alias:
              type: string
            priority:
              type: integer
              format: int32
            provider_id:
              type: string
              format: uuid
            request_timeout_secs:
              type:
                - integer
                - 'null'
              format: int32
              description: >-
                Per-mapping (route-tier / model-tier) total-request timeout for

                non-streaming requests (streaming responses have no total
                timeout).

                Defaults to `TOTAL_REQUEST_TIMEOUT_DEFAULT_SECS` at insert. On a
                route

                target this is the *route* tier; on the enablement (`kind =
                model`)

                it is the *model* tier consulted by the resolver for every route

                target of that model. The range is fixed platform policy

                (`TOTAL_REQUEST_TIMEOUT_MIN_SECS..=TOTAL_REQUEST_TIMEOUT_MAX_SECS`),

                enforced by the column CHECK.
            retry_on_429_count:
              type: integer
              format: int32
              description: Same-route retries on upstream 429. 0 means immediate failover.
            retry_on_429_max_wait_secs:
              type: integer
              format: int32
              description: >-
                Cap on honoring upstream `Retry-After` header. 0 means use a
                small

                built-in default and ignore the header value.
            source:
              $ref: '#/components/schemas/ModelSource'
              description: >-
                Whether the model was picked from the provider catalog or typed
                in by

                an admin. Stamped once at create time and never recomputed — see

                [`ModelSource`] and migration V61.
            stream_idle_timeout_secs:
              type:
                - integer
                - 'null'
              format: int32
              description: >-
                Per-mapping stream-idle (per-chunk) timeout for streaming
                responses.

                Defaults to `STREAM_IDLE_TIMEOUT_DEFAULT_SECS` at insert;
                enforced via

                `tokio::time::timeout` around each upstream SSE/eventstream
                chunk

                read. The range is fixed platform policy

                (`STREAM_IDLE_TIMEOUT_MIN_SECS..=STREAM_IDLE_TIMEOUT_MAX_SECS`),

                enforced by the column CHECK.
            upstream_model:
              type: string
    CooldownPolicy:
      type: object
      description: |-
        A route's cooldown policy, one field per column.

        Route-health *state* is keyed by `(provider_id, upstream_model)`, so an
        enablement row and an alias row that target the same upstream share one
        failure window and ejection deadline; each request judges that shared
        state by the policy of the row it resolved.
      required:
        - cooldown_failure_threshold
        - cooldown_window_secs
        - cooldown_base_secs
        - cooldown_max_secs
        - cooldown_429_default_secs
        - cooldown_overloaded_secs
      properties:
        cooldown_429_default_secs:
          type: integer
          format: int32
          description: Cooldown on a 429 with no usable `Retry-After`, seconds.
        cooldown_base_secs:
          type: integer
          format: int32
          description: |-
            First cooldown window, doubled on each failed recovery probe up to
            `cooldown_max_secs`.
        cooldown_failure_threshold:
          type: integer
          format: int32
          description: |-
            Gateway failures within `cooldown_window_secs` that cool the route.
            `0` disables every cooldown of the shared route.
        cooldown_max_secs:
          type: integer
          format: int32
          description: Cap on every cooldown window.
        cooldown_overloaded_secs:
          type: integer
          format: int32
          description: |-
            Flat cooldown on a 529 "overloaded" with no usable `Retry-After`,
            seconds. `0` counts a 529 as an ordinary gateway failure instead.
        cooldown_window_secs:
          type: integer
          format: int32
          description: Width of the rolling failure window, seconds.
    ChangeAttribution:
      type: object
      description: >-
        The attribution carried on an admin-configurable row, as returned to the

        portal.


        Every field is `Option` and nothing is backfilled: a row that predates
        V75

        has no truthful actor to attribute it to, and inventing one would be
        worse

        than rendering a placeholder. Rows pick up attribution on their first

        write.
      properties:
        created_by_email:
          type:
            - string
            - 'null'
        created_by_name:
          type:
            - string
            - 'null'
        created_by_user_id:
          type:
            - string
            - 'null'
          format: uuid
        last_change_note:
          type:
            - string
            - 'null'
          description: >-
            Optional free text explaining *why* the last change was made

            ("Raised for the Q3 batch job", "Vendor deprecated the old
            endpoint").


            Admin-authored and rendered in the portal, so it is untrusted input:

            length-capped here, escaped on render.
        last_modified_at:
          type:
            - string
            - 'null'
          format: date-time
          description: >-
            When the change named by `last_modified_by_*` happened.


            Its own column rather than the row's `updated_at`, because
            `updated_at`

            is bumped by writes nobody is attributed for -- pairing the two
            would

            date a named admin's edit to a background job's clock. See the V75

            migration for the full reasoning.
        last_modified_by_email:
          type:
            - string
            - 'null'
        last_modified_by_name:
          type:
            - string
            - 'null'
        last_modified_by_user_id:
          type:
            - string
            - 'null'
          format: uuid
    AutoDisabledReason:
      type: string
      description: >-
        Why the *gateway* — not an admin — turned a `model_mappings` row off.

        `None` on the column means a human decided, and the reconciler leaves
        the

        row alone in both directions. Persisted as a nullable `VARCHAR(32)` (see

        migration V63).
      enum:
        - catalog_retired
    MappingKind:
      type: string
      description: >-
        What a `model_mappings` row is (BCP-4580, migration V84). Persisted as a

        `VARCHAR(8)` constrained to `model | route`.


        The table holds two different things, and they used to be told apart
        only

        by `model_alias = upstream_model`. That made a route named after its own

        upstream indistinguishable from the model's enablement, so the two
        shared a

        row and every route operation on it had to tiptoe around the enablement.

        The kind is now stated, never inferred from a name.
      enum:
        - model
        - route
    ModelSource:
      type: string
      description: >-
        Where a `model_mappings` row came from. Persisted as a `VARCHAR(16)`

        constrained to `catalog | custom` (see migration V61).


        This records *intent at create time* — whether an admin picked the model

        out of the provider catalog or typed a name into the "Custom Model" box
        —

        and deliberately never changes afterwards. It is not a claim about
        whether

        the catalog still lists the model today: that is live state, drifts as

        vendors retire models, and belongs to the Stale annotation (BCP-3812).

        Keeping the two apart is what lets a retired-from-catalog model avoid

        rendering as a deliberate custom choice. See migration V61 for the full

        rationale.
      enum:
        - catalog
        - custom
  securitySchemes:
    ApiKey:
      type: http
      scheme: bearer
      description: >-
        A Barndoor API key, sent as `Authorization: Bearer <key>`. Keys are
        prefixed `bdai_` and are opaque — do not attempt to decode one.
        Interchangeable with an OAuth 2.0 access token.
    OAuth2:
      type: oauth2
      x-issuer: https://auth.barndoor.ai/realms/barndoor
      description: >-
        An OAuth 2.0 access token from Barndoor's identity provider, sent as
        `Authorization: Bearer <token>`. Public clients must use PKCE.
        Interchangeable with an API key.
      flows:
        authorizationCode:
          authorizationUrl: >-
            https://auth.barndoor.ai/realms/barndoor/protocol/openid-connect/auth
          tokenUrl: >-
            https://auth.barndoor.ai/realms/barndoor/protocol/openid-connect/token
          scopes:
            openid: Issue an OIDC ID token identifying the caller.
            profile: Read the caller's basic profile claims.
            email: Read the caller's email address.
            offline_access: Issue a refresh token. Optional — no operation requires it.
        clientCredentials:
          tokenUrl: >-
            https://auth.barndoor.ai/realms/barndoor/protocol/openid-connect/token
          scopes:
            openid: Issue an OIDC ID token identifying the caller.
            profile: Read the caller's basic profile claims.
            email: Read the caller's email address.
            offline_access: Issue a refresh token. Optional — no operation requires it.

````

This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.