Model Access
By default every application exposes all models from its configured providers to end-users. Model Access lets you narrow this to a curated list — either at the application level or per billing plan.
Concepts
Access modes
| Mode | Behaviour |
|---|---|
| All Models | Every model from every configured provider is available. No allow-list is consulted. |
| Restricted | Only models explicitly toggled on are available. Everything else returns model_not_allowed. |
The mode is stored in applications.model_access_restricted (app-wide) and billing_plans.model_access_restricted (per plan).
Scope: application vs plan
You can configure access at two layers:
- Application-wide (
application_model_accesstable) — applies to all end-users unless a more specific plan rule overrides it. - Per billing plan (
plan_model_accesstable) — overrides the application-wide setting for users on that plan. If the plan is also in restricted mode, app-level entries serve as the inherited baseline and plan-level entries layer on top.
Inheritance logic
When a plan is in restricted mode and the parent application is also in restricted mode, the effective allow-list is constructed as:
- Start with all app-level
application_model_accessrows for the application. - Layer plan-level
plan_model_accessrows on top (plan entries take precedence).
When the application is in All Models mode, plan-level restrictions are ignored — the entire catalog is open.
Provider wildcards
Setting model_identifier = '*' for a provider enables all current and future models from that provider in a single entry.
What end-users see
GET /v1/models returns only models that pass the access check for the requesting user’s plan. If a user sends a request with a model that is blocked, the guard returns:
{
"error": {
"message": "The model 'openai/gpt-4o' is not available for this application. Contact your administrator to enable access.",
"type": "invalid_request_error",
"code": "model_not_allowed",
"param": "model"
}
}HTTP status: 403. Affected endpoints: all AI endpoints (/v1/chat/completions, /v1/responses, /v1/messages, /v1/embeddings, /v1/images/generations, /v1/audio/*, /v1/video/generations, /v1/realtime).
Changes to model access may take up to 60 seconds to propagate due to capability caching in the guard.
Builder API
All model access configuration is done via Hasura GraphQL. Use your organization token (see API Keys).
Check current application access mode
query GetAppAccessMode($appId: uuid!) {
applications_by_pk(id: $appId) {
id
model_access_restricted
}
}Enable restricted mode for an application
mutation SetAppRestricted($appId: uuid!, $restricted: Boolean!) {
update_applications_by_pk(
pk_columns: { id: $appId }
_set: { model_access_restricted: $restricted }
) {
id
model_access_restricted
}
}Read the application allow-list
query GetAppModelAccess($appId: uuid!) {
application_model_access(
where: { application_id: { _eq: $appId } }
order_by: [{ provider: asc }, { model_identifier: asc }]
) {
id
provider
model_identifier
enabled
created_at
updated_at
}
}Upsert application allow-list entries
Use the unique constraint application_model_access_app_provider_model_unique for idempotent upserts.
mutation UpsertAppModelAccess(
$objects: [application_model_access_insert_input!]!
) {
insert_application_model_access(
objects: $objects
on_conflict: {
constraint: application_model_access_app_provider_model_unique
update_columns: [enabled]
}
) {
affected_rows
}
}Example variables — allow GPT-4o and block GPT-4o-mini:
{
"objects": [
{ "application_id": "<appId>", "provider": "openai", "model_identifier": "gpt-4o", "enabled": true },
{ "application_id": "<appId>", "provider": "openai", "model_identifier": "gpt-4o-mini", "enabled": false }
]
}Enable an entire provider (wildcard):
{
"objects": [
{ "application_id": "<appId>", "provider": "anthropic", "model_identifier": "*", "enabled": true }
]
}Per-plan access
The same pattern applies to billing_plans.model_access_restricted and the plan_model_access table.
Enable restricted mode for a plan:
mutation SetPlanRestricted($planId: uuid!, $restricted: Boolean!) {
update_billing_plans_by_pk(
pk_columns: { id: $planId }
_set: { model_access_restricted: $restricted }
) {
id
model_access_restricted
}
}Upsert plan allow-list entries (constraint: plan_model_access_plan_provider_model_unique):
mutation UpsertPlanModelAccess(
$objects: [plan_model_access_insert_input!]!
) {
insert_plan_model_access(
objects: $objects
on_conflict: {
constraint: plan_model_access_plan_provider_model_unique
update_columns: [enabled]
}
) {
affected_rows
}
}Query available models
query GetAvailableModels {
model_capabilities(
order_by: [{ provider: asc }, { model_identifier: asc }]
) {
provider
model_identifier
display_name
modality
capabilities
pricing
}
}Use these identifiers to build your allow-list entries.
Dashboard
The Model Access page in the Builder UI (application → Model Access) provides a visual editor:
- Scope selector — toggle between application-wide and a specific billing plan.
- Access mode — “All Models” or “Restricted” toggle with a confirmation dialog when switching to restricted with zero models enabled.
- Preset filters — quick filter buttons (e.g. “Vision”, “Audio”) to select groups of models in bulk.
- Provider access section — enable/disable all current and future models from a provider with a single toggle (writes a wildcard entry).
- Virtual model table — sortable by provider, model name, modality, price per 1 M input tokens, and enabled state. Renders up to 2,500+ rows with a windowed scroll container (40 px fixed row height, ±10 overscan rows).
Changes are saved only when you click Save and take effect within 60 seconds.