Register Models and Authorize Teams
Register global models, test connections, set capability flags, and authorize teams with quotas and priority
Clouisle models use a two-level structure: an administrator first registers global models on the Models page of the admin panel (provider, endpoint, credentials, capabilities, pricing), then authorizes models to teams with quotas. The platform UI (the Models page and the model selectors in Agents and workflows) only shows models authorized to the current team.
Prerequisite: model egress is gated by the Model Endpoint Allowlist site setting (model_endpoint_allowlist). Custom endpoints (self-hosted gateways, internal Ollama nodes) must be allowlisted first, otherwise saving, testing, and runtime calls are rejected with model_endpoint_not_allowlisted. See Providers, model types, and model discovery.
Registering a global model
In the admin panel > Models, select Add Model, fill in the form, then Test Connection before saving. Writable fields and constraints:
| Field | Type | Required | Constraints and default | Notes |
|---|---|---|---|---|
name | string | Yes | 1-100 characters | Display name shown in team model lists and selectors |
provider | enum | Yes | One of 23 provider identifiers | Determines the adapter and default endpoint |
model_id | string | Yes | 1-100 characters | Real upstream model ID or deployment name; a typo surfaces as "model not found" at call time |
model_type | enum | Yes | One of 9 types | Determines the call protocol; must match the provider |
provider_display_name | string | No | ≤ 100 characters | Overrides the user-facing provider name for gateway/proxy setups |
base_url | string | No | ≤ 512 characters | Empty falls back to the provider/type default endpoint |
api_key | string | No | ≤ 1024 characters | Required for every provider except ollama; stored encrypted |
context_length | integer | No | >= 1 | Used for display and capacity checks only; it does not truncate |
max_output_tokens | integer | No | >= 1 | Default output cap, overridable by default_params.max_tokens or a call argument |
input_price / output_price | decimal | No | >= 0, max 6 decimal places | Price per 1M tokens, recorded for reference |
default_params | object | No | — | Default inference parameters, e.g. {"temperature": 0.7, "top_p": 0.9} |
capabilities | object | No | — | Capability flags, see below |
config | object | No | — | Extra configuration such as Azure api_version/deployment or the video test poll_timeout_s |
is_enabled | boolean | No | Default true | Global on/off switch |
is_default | boolean | No | Default false | Whether this is the default model for its model_type |
sort_order | integer | No | Default 0 | Ascending sort weight |
provider, model_id, and model_type are immutable after creation: the update endpoint accepts every field in the table except these three. To change a model ID, provider, or type, create a new model, re-authorize teams, and adjust any knowledge bases or Agents referencing the old one.
Example: registering an OpenAI chat model.
{
"name": "GPT-4 Turbo",
"provider": "openai",
"model_id": "gpt-4-turbo-preview",
"model_type": "chat",
"api_key": "sk-...",
"context_length": 128000,
"max_output_tokens": 4096,
"input_price": 10.0,
"output_price": 30.0,
"default_params": {"temperature": 0.7, "top_p": 0.9},
"capabilities": {"streaming": true, "function_call": true}
}For Azure OpenAI, the deployment name and API version go through config, and model_id holds the deployment name:
{
"name": "GPT-4 (Azure)",
"provider": "azure_openai",
"model_id": "gpt-4-deployment",
"model_type": "chat",
"base_url": "https://your-resource.openai.azure.com",
"config": {"api_version": "2024-02-15-preview", "deployment": "gpt-4-deployment"}
}Discovering provider models
While the create form is unsaved, you can select Discover Models: after filling in base_url and the API Key, the backend calls the provider's model-list endpoint (such as /v1/models or Ollama's /api/tags) and fills id, name, context length, max output, and capability flags into the form, so you never mistype model_id. 16 providers support discovery; azure_openai, runway, pika, luma, kling, stability, and midjourney do not. Full rules (paths, auth, 15-second timeout, 2 MiB cap, TypeSafe gateway path rebasing) are in Providers, model types, and model discovery.
Credentials and endpoints
api_keyis stored encrypted by the backend. Every read endpoint returns onlyhas_api_key: true/falseand never echoes the plaintext; enter a new value in the edit form to rotate it.- API Key format is validated:
openaimust start withsk-,anthropicwithsk-ant-, otherwise the request fails withinvalid_api_key_format.ollamais the only provider that needs no API Key. - The effective request address resolves as the model's
base_url→ the type-specific default → the provider default; see Provider identifiers and default endpoints. The chosen endpoint must be an allowlisted Origin.
Capability flags
capabilities holds four flags that drive UI and runtime behavior:
| Flag | Effect | Cost of getting it wrong |
|---|---|---|
function_call | Lets the model call tools | Off: an Agent cannot use tools. On while the model does not support it: protocol errors during tool calls |
vision | Allows image input | Off: images cannot be used in chat |
streaming | Supports streaming responses | Off: chat returns in one shot and time-to-first-token grows noticeably |
json_mode | Supports structured JSON output | Off: JSON-dependent nodes and parameter extraction degrade to text parsing |
Before enabling tools for an Agent, confirm the selected chat model truly supports function_call; use Discover Models to read the provider's declaration, then correct it by hand if needed.
Testing connections
Two test endpoints differ in permission and purpose:
| Endpoint | Permission | Purpose |
|---|---|---|
POST /api/v1/admin/models/{model_id}/test | admin:model:update | Tests a saved model's actual configuration |
POST /api/v1/admin/models/test | admin:model:create | Tests the configuration in the form before saving; credentials are not persisted |
The test drives the real adapter for that model_type with one minimal request:
| Model type | Test action |
|---|---|
chat | Sends one chat message whose fixed content is Hi and requires non-empty text back |
embedding | Embeds the string test and requires a non-empty vector |
rerank | Reranks a fixed query against two documents and requires results |
decision | Sends a TypeSafe decision request with one noul question and requires answers |
text_to_image | Generates one A simple connection test image (openai_responses adds quality: low) and requires image content |
text_to_video | Creates a video task and polls until completion using config.poll_timeout_s (default 120 seconds) and config.poll_interval_ms (default 3000) |
tts | Synthesizes Hello using default_params.speaker or voice |
audio_generation | Generates A short, gentle bell sound |
stt | Validates only the API Key format; no real transcription is sent |
The response is always success, message, and latency_ms. Two things to note:
- If the upstream returns a rate limit error, every type except
text_to_videotreats it as "configuration is valid but currently throttled" and still returnssuccess: truewith a rate-limit notice — a passing connection test does not mean the quota is sufficient. - The video test really creates a task; a polling timeout (default
120seconds) returnsmodel_test_connection_timeout. Raisepoll_timeout_sinconfigwhen the provider needs longer.
Not implemented / Roadmap. There is no multi-request performance test (request count, concurrency, p50/p95/p99, cost aggregation). Testing is a single connection/response check.
Enabling, disabling, and default models
is_enabledis the model's only status bit: turning it off removes the model from selectors while keeping its configuration and team authorizations, and turning it back on restores them.- There is no
testingordeprecatedstate, no deprecation notice, EOL date, or replacement-model suggestion. is_defaultis unique permodel_type: callingPOST /api/v1/admin/models/{model_id}/set-defaultclears the flag on other models of the same type. Knowledge base embedding models and the Agent fallback when no model is specified both rely on it.
Authorizing models to teams
Granting, changing quotas, enabling/disabling a grant, and revoking a grant are superuser-only (the backend handlers depend on get_current_active_superuser). A team administrator who can see the Authorized Models tab still gets a 403 on submit.
- Open the admin panel > Teams and open the target team.
- Switch to the Authorized Models tab and select Add Model.
- Select one or more global models (the dropdown lists only not-yet-authorized, globally enabled models) and submit them together.
- Fill in the daily/monthly Token and request limits plus the priority for each grant; leave a field empty for unlimited.
- After saving, team members can see the model on their Models page (see below).
An authorization is unique on the team + model pair: authorizing the same model twice returns "already authorized". Batch authorization POST /teams/{team_id}/models/batch only creates new grants and silently skips existing ones; batch revoke DELETE /teams/{team_id}/models/batch returns the number actually deleted.
The Models page a team member sees lists every grant for the team (including disabled ones with is_enabled: false, filterable by status), while model selectors in Agents and workflows only offer grants that are both authorized and enabled (GET /teams/{team_id}/available-models returns only grants that are enabled and whose global model is enabled).
Quotas and usage
All four limits may be null (unlimited), and 0 is a valid value (immediately exhausted):
| Field | Type | Default | Meaning |
|---|---|---|---|
daily_token_limit | integer / null | null | Daily token cap |
monthly_token_limit | integer / null | null | Monthly token cap |
daily_request_limit | integer / null | null | Daily request cap |
monthly_request_limit | integer / null | null | Monthly request cap |
Enforcement is not symmetric, so configure with care:
- Token limits: rejected only when
used + estimated for this call > limit. Reaching exactly the limit does not fail immediately, but any further tokens do. - Request limits: rejected when
used >= limit, so request numberlimitstill succeeds and requestlimit + 1is blocked. - Usage resets lazily in the site timezone (
TIMEZONE, defaultAsia/Shanghai): after 00:00 each day and after 00:00 on the 1st of each month, the next quota check zeroes the counters and writes them back. - Over quota, the chat endpoint returns business error
6103with HTTP429and the message "model quota exceeded".
GET /teams/{team_id}/models/quota returns each grant's limits, usage, and percentages. Note that is_quota_exceeded accounts only for token limits (used >= limit); compare daily_request_limit with daily_requests_used yourself for request quotas.
Priority
priority defaults to 0 and only affects ordering among models of the same type, never availability. The team model list sorts by -priority, created_at and the available model list by -priority — a larger value moves a model to the front of selectors. Change any limit or the priority in the Edit dialog.
Updating, revoking, and deleting
| Action | Effect |
|---|---|
Update model (PUT /admin/models/{id}) | Only name, credentials, endpoint, capabilities, pricing, default parameters, status, and sort order; provider/model_id/model_type cannot change |
| Disable model | Globally hidden, team grants kept, restorable at any time |
| Revoke team grant | Deletes the team + model grant and its usage records; the team immediately loses the model |
| Delete global model | Removes the global registry entry and cascades to every team's authorization via the foreign key |
Deleting a global model cascades to all team authorizations and is irreversible. To retire a model for one team only, revoke that grant or disable it instead of deleting the global model.
Troubleshooting
Connection test fails
- Check the allowlist: confirm the custom endpoint's
scheme://host:portis in Site Settings > Security > Model Endpoint Allowlist. - Check the credential format:
openaineeds thesk-prefix andanthropicthesk-ant-prefix; verify the key is not expired and can access the target model. - Check provider/type matching: combinations such as
anthropic+embeddingorstability+text_to_videofail at call time, not on save. - Check the network: a 15-second timeout and no redirects apply; confirm internal endpoints are reachable and not blocked by a firewall.
- Check upstream status: provider throttling or outages show as test failures, but throttling normally still returns
success: true.
Quota exhausted (6103 / HTTP 429)
- Call
GET /teams/{team_id}/models/quotato see which limit was hit. - Raise the limit, switch to a same-type model that has headroom, or wait for the lazy daily/monthly reset.
- If you suspect a false positive, check for one oversized request; token limits compare
used + estimate.
Slow responses or timeouts
- Review the model's
context_lengthandmax_output_tokens; an oversized output cap noticeably lengthens responses. - Lower
max_tokens, enable streaming (capabilities.streaming), or switch to a faster model. - For video models, check
config.poll_timeout_s; the default120seconds may be shorter than the provider's render time.
API access
Admin model endpoints live under /api/v1/admin/models and require admin:model:read, admin:model:create, admin:model:update, or admin:model:delete per operation. See the Models API for the endpoint list.
# List models (admin)
models = api.get("/api/v1/admin/models")
# Create a model
model = api.post("/api/v1/admin/models", json={
"name": "GPT-4 Turbo",
"provider": "openai",
"model_id": "gpt-4-turbo-preview",
"model_type": "chat",
"api_key": "sk-...",
"input_price": 10.0, # per 1M tokens
"output_price": 30.0
})
# Test the form configuration before saving (credentials not persisted)
api.post("/api/v1/admin/models/test", json={
"provider": "openai",
"model_id": "gpt-4-turbo-preview",
"model_type": "chat",
"api_key": "sk-..."
})
# Test a saved model
api.post(f"/api/v1/admin/models/{model_id}/test")
# Discover available provider models
api.post("/api/v1/admin/models/discover", json={
"provider": "openai",
"base_url": "https://api.openai.com/v1",
"api_key": "sk-..."
})
# Make it the default model for its type
api.post(f"/api/v1/admin/models/{model_id}/set-default")
# Authorize a team (superuser only) with quotas and priority
api.post(f"/api/v1/teams/{team_id}/models", json={
"model_id": str(model_id),
"daily_token_limit": 1_000_000,
"monthly_token_limit": 20_000_000,
"daily_request_limit": 2000,
"monthly_request_limit": 40000,
"priority": 10
})
# Batch authorize / batch revoke
api.post(f"/api/v1/teams/{team_id}/models/batch", json={
"model_ids": [str(a), str(b)],
"monthly_token_limit": 5_000_000
})
api.delete(f"/api/v1/teams/{team_id}/models/batch", json={"model_ids": [str(a)]})
# Inspect quota usage
api.get(f"/api/v1/teams/{team_id}/models/quota")Related pages
- Providers, model types, and model discovery — 23 provider identifiers, default endpoints, and discovery rules
- Model — two-level structure and quota concepts
- Teams and permissions — team member roles
How is this guide?