Model access decides which AI models exist for whom. Nothing is usable by default: a model reaches a person only when its provider connection is enabled, the model is in the catalog and enabled, it is assigned at a scope that applies to the person, and it is allowed for the request's data classification.
1. Connect a provider
Open Models > Providers and keys and add a connection, or use the API:
curl -sS -X POST "$KEYSTONE_URL/api/v1/models/providers" \
-H "Authorization: Bearer $ADMIN_TOKEN" -H 'Content-Type: application/json' \
-d '{"name": "Anthropic production", "kind": "anthropic", "base_url": "https://api.anthropic.com", "region": "us"}'
curl -sS -X PUT "$KEYSTONE_URL/api/v1/models/providers/$PROVIDER_ID/key" \
-H "Authorization: Bearer $KEY_ADMIN_TOKEN" -H 'Content-Type: application/json' \
-d '{"key": "<provider API key>"}'
Connection kinds are anthropic, openai, google, azure_openai, bedrock, vertex, openai_compatible and ollama. Azure takes config.azure_api_version; Vertex takes config.vertex_project and config.vertex_location.
| Kind | Key format |
|---|---|
anthropic, openai, google, azure_openai | The provider's API key |
bedrock | A JSON document {"access_key_id", "secret_access_key", "session_token"} (session token optional). Requests are signed with AWS Signature Version 4. |
vertex | A Google service account JSON. Govern mints short-lived access tokens from it. |
openai_compatible, ollama | Optional |
Keys are write-only
Keys are stored in Govern Vault and never returned. Responses show only set, the last four characters (hint), a version and the rotation time. Setting, rotating and deleting a key needs models:keys, separate from configuring the connection (models:admin), so the person who handles keys need not manage policy.
Changing a connection's base URL, region or settings drops its stored key (audited models.provider.key.removed, reason endpoint_changed), so nobody can redirect a key to a host they control. Key changes are refused while the vault is sealed.
Outbound connections
Every upstream request goes through Govern's outbound guard: loopback, private, link-local, carrier-grade NAT and cloud metadata addresses are refused at connect time, and no redirect is followed. The operator of a self-hosted installation can open private networks for model servers with NI_AUTH_MODELS_PRIVATE_NETWORKS (comma-separated CIDR prefixes or addresses; refused on the hosted service). With it, openai_compatible and ollama connections are confined to those networks and their models count as self-hosted. Without it they reach public addresses only and do not count as self-hosted.
Provider health
POST /api/v1/models/providers/{id}/health/check (models:admin) probes a connection now; GET /api/v1/models/providers/{id}/health (models:view) shows the last 20 probes and the circuit state of its models. The console page is Models > Provider health. A scheduled probe runs only when the operator sets NI_AUTH_MODELS_PROVIDER_HEALTH_SCHEDULE (for example @every 15m).
2. Add models to the catalog
Open Models > Model catalog, or POST /api/v1/models/catalog (models:write). Govern does not discover models from providers: you enter each model and its prices, which keeps the catalog air-gap safe and prices under your control.
| Field | Meaning |
|---|---|
provider_id | The connection |
model_key | The name clients use; unique among model keys and route keys |
upstream_model | The provider's model id |
display_name | Shown in the apps |
uses | Any of chat, agent, completion, embedding, rerank, image, audio |
region, residency | Where the model runs |
max_classification | The strictest classification the model may serve |
zero_retention | The provider keeps no data for this model |
input_usd_per_mtok, output_usd_per_mtok | Prices per million tokens; budgets charge with them |
usd_per_image, audio_usd_per_mchar | Required above 0 for image and audio models |
cache_read_usd_per_mtok, cache_write_usd_per_mtok | Prompt cache prices (optional) |
context_window, max_output_tokens | Limits |
enabled | Default on |
PUT /api/v1/models/catalog/{id}/fallbacks sets a model's fallback chain (up to 20). Each fallback is decided again from scratch, so a fallback never reaches a model the caller may not use.
3. Assign models
Assignments allow or deny a model at a scope. Open the model in the catalog, or use PUT /api/v1/models/assignments (models:write) with scope_kind (org, team or project), scope_id, model_id and effect (allow or deny).
- A model is usable when it is allowed at a scope that applies to the caller and denied at none. A deny at any applicable scope wins.
- Applicable scopes are the organisation, every team the caller belongs to, and the request's project when the caller is a member of it.
- Leaving out the project never loosens a project's rules. A request without a project gets the denies of every project the caller belongs to, and is classified at the strictest scope the caller works in.
- To give one project a model the organisation does not want generally, allow it on that project only.
Defaults
PUT /api/v1/models/defaults sets a default model per use (chat, agent, completion, embedding) at a scope. The most specific scope wins: project, then team, then organisation. A default the caller may not use is skipped.
4. Set the routing policy
Models > Routing edits the organisation's policy (GET/PUT /api/v1/models/policy; models:admin to change):
| Field | Meaning |
|---|---|
classification_rules | Per classification, require (any, zero_retention, self_hosted, self_hosted_or_zero_retention) and optional regions |
allowed_regions | When set, a model whose region is not listed is not eligible |
require_project | regulated (default), always or never |
user_keys_enabled | Let people bring their own provider key |
user_key_max_classification | The strictest classification a personal key may serve (default internal) |
offline_grace_seconds | How long the apps' signed model manifest stays valid (default 24 hours, at most 7 days) |
Classification
Each request is classified from its project: the strictest of project, team, department and organisation. A client may raise the classification with X-Keystone-Classification, never lower it. A project with compliance frameworks set is regulated. Then:
- The model's
max_classificationmust be at least the request's classification. - The organisation's rule for that classification must hold.
| Classification | Default rule |
|---|---|
| public, internal, confidential | any |
| restricted | self_hosted_or_zero_retention |
require_project: regulated refuses a request without a project when the caller's strictest classification is confidential or restricted, or when the caller belongs to a regulated project. always refuses every request without one. An API key belongs to no team or project, so it is decided at the organisation root's classification.
Personal keys
With user_keys_enabled, people may store their own Anthropic, OpenAI or Google key with PUT /api/v1/models/me/keys/{provider}. The gateway uses it for catalog models on connections of that kind, only up to user_key_max_classification, only for models the organisation allowed, and only through the organisation's configured endpoint. Spend on a personal key is not counted against spend budgets (tokens are).
Permissions
| Permission | Grants |
|---|---|
models:read | Using models: the signed manifest, the gateway and one's own keys. Held by Member. |
models:view | Reading the configuration: connections (never keys), catalog, assignments, defaults, policy, budgets with live usage. Not held by Member, because base URLs can name internal hosts and budgets show what people spend. |
models:write | Catalog entries, fallbacks, assignments, defaults, budgets, routes |
models:admin | Connections (not keys), the routing policy, gateway settings, guardrails, rate limits, request logs |
models:keys | Setting, rotating and deleting a connection's key |
Model connections are also shareable resources: a Viewer of one connection can read its configuration, a User can call models through it. See Roles and permissions.
Limits
50 provider connections, 500 catalog models, 20 fallbacks per model, 2,000 assignments, 2,000 defaults and 500 budgets per organisation.
Not supported
Bedrock models other than Anthropic models (the Converse API) and Gemini's native API are not built. Google and Vertex Gemini models are reached through their OpenAI-compatible endpoints.
Audit
Every change is audited in its own transaction. Key changes write two records, the change and a key record with the new version and hint, never the key.