Skip to main content
PUT
Update a model-access policy

Authorizations

Authorization
string
header
required

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.

Path Parameters

id
string<uuid>
required

Identifier, a UUID

Body

application/json
change_note
string | null

Optional free-text note explaining why this edit was made. Omitting it clears any previous note — deliberately not the tri-state treatment the scope fields above get, because the note describes the latest change and carrying an old one forward would explain the wrong edit.

enabled
boolean | null
name
string | null
policy_type
string | null
scope_id
string<uuid> | null

Tri-state (see [deserialize_explicit_null]). A policy bound to one entity is unbound by sending null — that is how the admin UI moves an api_key-scoped policy back to "All API Keys". A plain Option<Uuid> made that indistinguishable from omitting the field, so the update silently kept the old binding (BCP-3955).

scope_ids
string<uuid>[] | null

Replaces the whole UUID binding when present; [] unbinds. Omitted leaves it unchanged. See [CreateModelAccessRequest::scope_ids].

scope_type
string | null
scope_value
string | null
scope_values
string[] | null

Replaces the whole role / group binding when present; [] unbinds.

targets
object[] | null

A single restriction target inside a [ModelAccessPolicy].

Targets answer the question "what is being allowed/denied?" — distinct from scope_type, which answers "who is the policy applied to?". A policy's targets list is OR-ed when matching: a request matches the policy if any one of its targets matches the (provider, model) pair.

Serialised to JSONB with a kind discriminator so it round-trips cleanly through the admin REST API.

ModelAlias matches against the literal model field in the request. Model matches against the resolved upstream model — i.e. the underlying model the routed mapping points at. The two are distinct because the admin UI lets admins pick along either dimension: a goto alias fanned out across providers is best expressed with ModelAlias, while "block any request that ends up at gpt-4o-mini regardless of the alias used" is best expressed with Model (so it fires even when goto resolves to gpt-4o-mini).

traffic_type
string | null

Response

The requested record

An admin-API response for an entity whose core type lives in ai_governance and so cannot carry portal-only columns.

TokenBudget and RateLimitPolicy are read by the enforcement path on every request; hanging five display-only fields off them would put portal concerns in the hot path's type and force every governance consumer to carry them. Wrapping instead keeps the enforcement reads (get_enabled_by_org and friends) returning the bare governance type, while the admin reads return this.

Both halves are #[serde(flatten)], so the wire shape stays the flat object the portal already consumes — same choice AdminModelPricing makes.

enabled
boolean
required
id
string<uuid>
required
name
string
required
org_id
string<uuid>
required
policy_type
string
required
scope_ids
string<uuid>[]
required

Every UUID-keyed entity (user / project / api_key / agent) the policy is bound to (BCP-5001). Empty means unbound: the policy is not pinned to a specific entity of its scope type.

scope_type
string
required
scope_values
string[]
required

Every role / group name the policy is bound to (BCP-5001). Empty means unbound.

targets
object[]
required

A single restriction target inside a [ModelAccessPolicy].

Targets answer the question "what is being allowed/denied?" — distinct from scope_type, which answers "who is the policy applied to?". A policy's targets list is OR-ed when matching: a request matches the policy if any one of its targets matches the (provider, model) pair.

Serialised to JSONB with a kind discriminator so it round-trips cleanly through the admin REST API.

ModelAlias matches against the literal model field in the request. Model matches against the resolved upstream model — i.e. the underlying model the routed mapping points at. The two are distinct because the admin UI lets admins pick along either dimension: a goto alias fanned out across providers is best expressed with ModelAlias, while "block any request that ends up at gpt-4o-mini regardless of the alias used" is best expressed with Model (so it fires even when goto resolves to gpt-4o-mini).

traffic_type
string
required
scope_id
string<uuid> | null

First element of [Self::scope_ids], kept for clients that predate multi-entity scopes (BCP-5001). Never read for matching when scope_ids is non-empty -- see [Self::bound_ids].

scope_value
string | null

First element of [Self::scope_values]; same compatibility role as scope_id.

created_by_email
string | null
created_by_name
string | null
created_by_user_id
string<uuid> | null
last_change_note
string | null

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
string<date-time> | null

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
string | null
last_modified_by_name
string | null
last_modified_by_user_id
string<uuid> | null