Model policy
Developer·5 minutes to read
What it does
The organisation's model policy — which providers and which models its bots may run on — read and set through the API. It is the same policy Organisation → Policies sets on screen: a model turned off here is off on the screen, and the other way round, and a bot whose model is off is refused at its next run start exactly as when the model is turned off on screen.
The policy is in one of two states:
unrestricted— every model on every provider, including models and providers the catalogue gains later. This is where every organisation starts.custom— an allowlist of models, an exclusion of providers, or both. The firstPUT /model-policywithstate: "custom"seeds the current catalogue: every provider and every model starts on, so turning the policy on changes nothing until you turn something off.newModelDefaultandnewProviderDefaultdecide whether a model, or a provider, that appears in the catalogue later starts on or off.
Writing state: "unrestricted" clears the allowlist, the exclusion and the two defaults. A model's switch is the model's, not the route's: a model two providers serve is listed under both and has one switch, and it runs only through a provider that is on. Per-model parameter limits are not part of the policy.
Authentication
The three reads need a key of scope read or wider. The three writes need admin, and the person the key is bound to must still be an administrator. Each write is passed to the repository as that person, through the writers the Policies page uses, so each one writes the same audit row as the screen: model.allowlist_changed for a model switch or for turning the policy on or off, model.providers_excluded_changed for a provider switch, org.byok_changed for BYOK, and model.policy_defaults_changed when a default changes. See Authentication and keys.
Request
GET /model-policy
<!-- generated:begin GET /model-policy --> <!-- generated:end GET /model-policy -->PUT /model-policy
<!-- generated:begin PUT /model-policy --> <!-- generated:end PUT /model-policy -->Every field is required: state (unrestricted or custom), newModelDefault and newProviderDefault (on or off), and byok (true or false). BYOK is written only when it changes; a personal organisation has no BYOK switch, so changing it there is conflict.
GET /model-policy/providers
<!-- generated:begin GET /model-policy/providers --> <!-- generated:end GET /model-policy/providers -->Every provider the catalogue has a live route from, with its display name, whether it is on, and its models with each one's switch. The list is the whole catalogue in one page.
PUT /model-policy/providers/{provider}
<!-- generated:begin PUT /model-policy/providers/{provider} --> <!-- generated:end PUT /model-policy/providers/{provider} -->{provider} is the provider's id as the list shows it (anthropic, openai, openrouter …). The body is { "enabled": true } or { "enabled": false }. A provider switch is a team-organisation setting; on a personal organisation it is conflict.
GET /model-policy/providers/{provider}/models
<!-- generated:begin GET /model-policy/providers/{provider}/models --> <!-- generated:end GET /model-policy/providers/{provider}/models -->PUT /model-policy/providers/{provider}/models/{model}
<!-- generated:begin PUT /model-policy/providers/{provider}/models/{model} --> <!-- generated:end PUT /model-policy/providers/{provider}/models/{model} -->{model} is the model's id as the provider's list shows it. The body is { "enabled": true } or { "enabled": false }. The last model that is on cannot be turned off: a policy with no model on would refuse every run, so turn the policy back to unrestricted, or turn another model on first.
curl -sS -X PUT "$BOTSEON_API_URL/api/v1/model-policy" \
-H "authorization: Bearer $BOTSEON_API_KEY" \
-H "content-type: application/json" \
-d '{"state":"custom","newModelDefault":"off","newProviderDefault":"on","byok":true}'
const res = await fetch(
`${process.env.BOTSEON_API_URL}/api/v1/model-policy/providers/openai/models/gpt-5.6-luna`,
{
method: 'PUT',
headers: {
authorization: `Bearer ${process.env.BOTSEON_API_KEY}`,
'content-type': 'application/json',
},
body: JSON.stringify({ enabled: false }),
},
);
if (!res.ok) throw new Error(`model policy: ${res.status}`);
Response
GET and PUT /model-policy:
{
"state": "custom",
"newModelDefault": "off",
"newProviderDefault": "on",
"byok": true,
"excludedProviders": ["openrouter"]
}
GET /model-policy/providers answers { "data": [provider, …] }, and PUT /model-policy/providers/{provider} the one provider:
{
"id": "anthropic",
"displayName": "Anthropic",
"enabled": true,
"models": [{ "id": "claude-sonnet-5", "displayName": "Claude Sonnet 5", "enabled": true }]
}
GET /model-policy/providers/{provider}/models answers { "data": [model, …] }, and PUT …/models/{model} the one model.
Errors
| Status | Code | When | What to do |
|---|---|---|---|
| 401 | unauthenticated | The credential is missing or refused, a write is made with a read or write key, or the key's person is no longer an administrator. | Use an admin key for the writes. |
| 400 | invalid_request | A body field is missing or not one of the accepted values. | Read details. |
| 404 | not_found | The provider has no live route in the catalogue, or the model is not one the provider serves. | List the providers first. |
| 409 | conflict | A provider or model switch while the policy is unrestricted; turning off the last model that is on; a BYOK or provider switch on a personal organisation; more than 100 models on, or more than 50 providers off. | PUT /model-policy with state: "custom" first; turn another model on; use a team organisation. |
| 429 | rate_limited | A bucket is full. | Wait for Retry-After. |
Rate limits
Every call on this page is counted against the same one-minute buckets as the rest of the API: 300 requests per key and 1 000 per organisation. A full bucket answers rate_limited with Retry-After: 60. See Envelopes, errors, idempotency and paging.
Last verified against build c19fc1f.