> ## 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.

# List the model mappings for one provider



## OpenAPI

````yaml /api-reference/openapi.yaml get /api/llm-gateway/admin/providers/{provider_id}/model-mappings
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/providers/{provider_id}/model-mappings:
    get:
      tags:
        - llm-gateway
      summary: List the model mappings for one provider
      operationId: list_model_mappings
      parameters:
        - name: provider_id
          in: path
          description: Identifier, a UUID
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: The requested record
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/ModelMappingWithBudgetStatus'
        '401':
          description: Missing or invalid credentials
        '403':
          description: Caller is not permitted to act on this resource in this organization
components:
  schemas:
    ModelMappingWithBudgetStatus:
      allOf:
        - $ref: '#/components/schemas/ModelMapping'
        - type: object
          required:
            - effective_pricing
          properties:
            budget_status:
              oneOf:
                - $ref: '#/components/schemas/BudgetTargetStatus'
                  description: >-
                    Populated when a `LlmProvider` / `Model` / `ModelAlias`
                    budget with

                    `action_on_exhaust = block` is currently exhausted for the

                    mapping's `(provider_id, upstream_model, model_alias)`
                    triple.

                    Runtime would drop this route from the resolved plan; the
                    pill

                    surfaces the same state without filtering the row out of the

                    listing.
                - type: 'null'
            effective_pricing:
              $ref: '#/components/schemas/EffectivePricing'
              description: >-
                The rate a request against this mapping would be billed at,
                resolved

                through the same tier chain the cost calculator uses (BCP-3564).

                Always present; `source == "none"` means unpriced. See

                [`crate::effective_pricing`].
            route_health_status:
              oneOf:
                - $ref: '#/components/schemas/RouteHealthStatus'
                  description: >-
                    Populated when the mapping's `(provider_id, upstream_model)`
                    route is

                    in a passive-health cooldown. The page renders a
                    "temporarily

                    unavailable" pill; the row stays in the listing.
                - type: 'null'
            stale_status:
              oneOf:
                - $ref: '#/components/schemas/ModelStaleStatus'
                  description: >-
                    Populated when the model is enabled from a provider catalog
                    that no

                    longer lists it (BCP-3812). Advisory: nothing is disabled on
                    the

                    strength of it — retirement is BCP-3811's `full` mode, which
                    the org

                    opts into. See [`crate::catalog_status`].
                - type: 'null'
      description: |-
        Wraps a [`ModelMapping`] with the BCP-2813 `budget_status` annotation so
        the admin Model Mappings page can render a "disabled by budget" pill.
        The inner `ModelMapping` fields are flattened so the response is a
        drop-in superset of the legacy `Vec<ModelMapping>` shape — older clients
        that ignore `budget_status` keep working unchanged.
    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
    BudgetTargetStatus:
      type: object
      description: |-
        Per-mapping annotation: a budget that is currently blocking traffic for
        the (provider, upstream, alias) the mapping resolves to. Serialised as
        `budget_status` on each listing row.
      required:
        - kind
        - limit_kind
        - budget_id
        - budget_name
        - period
        - percentage_used
        - message
      properties:
        budget_id:
          type: string
          format: uuid
        budget_name:
          type: string
        kind:
          $ref: '#/components/schemas/BudgetTargetKind'
        limit_kind:
          $ref: '#/components/schemas/BudgetLimitKind'
        message:
          type: string
          description: Short human-readable summary suitable for a tooltip / pill label.
        percentage_used:
          type: number
          format: double
        period:
          type: string
    EffectivePricing:
      type: object
      description: >-
        The rate a request against a given mapping would be billed at, plus
        which

        tier produced it.


        `source` is always present. Every other field is `None` when

        `source == "none"` — meaning no rule and no Barndoor default covers this

        model, so requests succeed but record $0. The rates are deliberately

        nullable rather than zero-filled: a UI that renders `$0.00` for an

        unpriced model reads as "this model is free", which is exactly the wrong

        conclusion.


        Clients distinguish four states:


        * field absent entirely → backend predates this annotation

        * `source == "none"` → unpriced, bills at $0 because the rate is
        *unknown*

        * `source == "provider_reported"` → bills what the provider reports per
          request (BCP-4543, OpenRouter). No rates: the price varies per request.
        * `source == "not_metered"` → bills at $0 *by decision* (BCP-3876): the
          provider's `billing_mode` is `subscription`, so no rate is consulted.
          Rates are `None` here too, but for the opposite reason, and the UI must
          not lump it in with "unpriced" — that bucket is a misconfiguration it
          nags about and offers to bulk-disable.
        * anything else → priced, `input`/`output` populated
      required:
        - source
      properties:
        cache_read_cost_per_million_tokens:
          type:
            - number
            - 'null'
          format: double
          description: |-
            Prompt-cache read rate. `None` on a *priced* row means the rule sets
            no explicit cache rate, and the calculator bills cached input at the
            base input rate — render it as an inherited default, never as free.
        cache_write_cost_per_million_tokens:
          type:
            - number
            - 'null'
          format: double
          description: Prompt-cache write rate. Same `None` semantics as the read rate.
        input_cost_per_million_tokens:
          type:
            - number
            - 'null'
          format: double
        long_context:
          oneOf:
            - $ref: '#/components/schemas/LongContextTier'
              description: >-
                The long-context surcharge on the matched rule (BCP-3904), when
                it

                has one.


                Present here for the same reason the rates are: an admin
                ordering

                route priorities on cost has to be able to see that a target's
                price

                doubles above a prompt-size threshold. Rendering only the

                short-context rate for a Grok or GPT-6 route would understate
                its

                cost on exactly the long-context traffic that makes the choice

                matter.
            - type: 'null'
        output_cost_per_million_tokens:
          type:
            - number
            - 'null'
          format: double
        pricing_rule_id:
          type:
            - string
            - 'null'
          format: uuid
          description: |-
            The `model_pricing` version row that matched, so the UI can link a
            displayed rate back to the rule that produced it. `None` when
            `source == "none"`.
        source:
          type: string
          description: |-
            Which resolution tier matched: `org_instance`, `org_catalog`,
            `org_generic`, `barndoor_default`, `provider_reported`,
            `not_metered`, or `none`. Mirrors
            [`PricingSource::as_str`] so the value matches what audit events
            already record for the same decision.
    RouteHealthStatus:
      type: object
      description: |-
        Per-mapping annotation: the route is currently suspended by
        passive-health cooldown. Serialised as `route_health_status` on each
        listing row (omitted entirely when the route is healthy).
      required:
        - state
        - reason
        - scope
        - recovers_in_secs
        - message
      properties:
        message:
          type: string
          description: Human-readable summary suitable for a tooltip.
        reason:
          type: string
          description: |-
            Coarse cause: `"rate_limited"` | `"provider_errors"` |
            `"auth_failed"` | `"unavailable"` (the last when only a cross-pod
            deadline is known). `"auth_failed"` only ever pairs with
            `scope = "current_credential"` — see [`status_for_cooldown`].
        recovers_in_secs:
          type: integer
          format: int64
          description: Approx seconds until the model is usable again.
          minimum: 0
        scope:
          type: string
          description: >-
            Who the suspension affects: `"all_users"` for a shared route
            cooldown

            or `"current_credential"` for the authenticated caller's passthrough

            credential.
        state:
          type: string
          description: '`"suspended"` (in cooldown) or `"recovering"` (probing recovery).'
    ModelStaleStatus:
      type: object
      required:
        - reason
      properties:
        reason:
          $ref: '#/components/schemas/StaleReason'
    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
    BudgetTargetKind:
      type: string
      description: |-
        Which target dimension the exhausted budget is scoped to. Drives the
        pill label in the admin Model Mappings page and the user-portal Models
        page.
      enum:
        - provider
        - upstream_model
        - model_alias
    BudgetLimitKind:
      type: string
      description: Which limit on the budget actually exhausted.
      enum:
        - token
        - cost
    LongContextTier:
      type: object
      description: >-
        The alternate rate set a pricing row switches to once a request's prompt

        reaches `threshold_prompt_tokens` (BCP-3904).


        Crossing the threshold re-rates the **whole** request, not just the
        tokens

        past it — that is how xAI, OpenAI and Azure all publish it, and it is
        why

        this is a rate switch rather than a marginal bracket.


        Note the asymmetry that makes this easy to get wrong: the gate is
        measured

        on the *prompt*, but the surcharge applies to output tokens too. The
        gate

        quantity and the billed quantities are different numbers.
      required:
        - threshold_prompt_tokens
        - input_cost_per_million_tokens
        - output_cost_per_million_tokens
      properties:
        cache_read_cost_per_million_tokens:
          type:
            - number
            - 'null'
          format: double
          description: |-
            `None` ⇒ bill cache reads at this tier's input rate, mirroring the
            base-rate fallback rather than falling back to the *base* cache rate
            (which would price a long-context cache read below its own tier).
        cache_write_cost_per_million_tokens:
          type:
            - number
            - 'null'
          format: double
          description: '`None` ⇒ same fallback as `cache_read_cost_per_million_tokens`.'
        input_cost_per_million_tokens:
          type: number
          format: double
        output_cost_per_million_tokens:
          type: number
          format: double
        threshold_prompt_tokens:
          type: integer
          format: int64
          description: |-
            Total prompt tokens at or above which this tier replaces the base
            rates. Inclusive — see [`ModelPricing::rates_for_prompt`].
    StaleReason:
      type: string
      enum:
        - catalog_removed
        - upstream_unlisted
  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.