Models
For organization administrators who add model providers. You need the rights
named for each task. Only a platform admin can change settings under
/v1/platform. Start with the administrator manual.
A Bot needs a language model to do work. OpenBot does not include a model. You connect one. OpenBot keeps two kinds of records for this:
- A model describes one model, such as
gpt-5: its type, its name, its options and its limits. A model does not belong to a provider. - A model provider is a model service and its access credentials. A provider links the models that it offers. A link can change some values of the model for this provider only.
You can also connect ChatGPT as a model provider. See Connect ChatGPT. Its account belongs to the provider.
You add both records through the API. OpenBot stores them in the database, so every instance uses the same ones.
The platform or an organization owns each model and each provider:
| Owner | Who adds it | Who can use it |
|---|---|---|
| Organization | An organization, with its own key | Only that organization. This is “bring your own key”. |
| Platform | A platform admin, with the platform’s key | The organizations in its availability. See Platform. |
An organization can use a model of a provider only when it may use both the provider and the model.
Each model has one of three types:
- A
languagemodel writes text and calls tools. It runs Turns, judges Rules and learns Bot memory. - A
decisionmodel only picks an answer from a fixed list. It does not write text. It can judge Rules. Jev from TypeSafe is an example. See Decision model. - A
livemodel talks with a person in real time, with sound. Only Live mode uses it.
When you run OpenBot from code, you can also give built-in models. See Built-in models.
Add a model
Section titled “Add a model”POST /v1/organization/models adds a model to your organization. It needs
write on ModelProvider.
curl -H "Authorization: Bearer $OPENBOT_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "id": "qwen3-32b", "name": "Qwen3 32B", "reasoningEfforts": [], "serviceTiers": [], "contextWindow": 131072 }' \ https://api.example.com/v1/organization/models| Field | Rules |
|---|---|
id |
Required. 1 to 200 characters. It is unique for its owner, and you cannot change it. A provider sends it as the model name, unless the link sets upstream. |
type |
Optional. language, decision, or live. The default is language. You cannot change it later. |
name |
Required. A name for people. |
reasoningEfforts |
Required. The reasoningEffort values that the model accepts. Can be empty. |
serviceTiers |
Required. The serviceTier values that the model accepts. Can be empty. |
contextWindow |
Optional. The size of the context window, in tokens. |
maxOutputTokens |
Optional. The most tokens that the model can write in one answer. |
If your organization already has a model with this id, OpenBot returns 409
with ModelConflict.
| Route | What it does |
|---|---|
GET /v1/organization/models |
Lists the models of your organization. Shared models are not in this list. |
GET /v1/organization/models/:modelId |
Reads one model. |
PATCH /v1/organization/models/:modelId |
Changes a model. The body needs expectedVersion. Send null for a limit to remove it. |
DELETE /v1/organization/models/:modelId |
Removes a model. |
Reading needs read on ModelProvider. Changes need write on
ModelProvider. You cannot remove a model while one of your providers links it.
OpenBot returns 409 with ModelInUse. Remove the link first.
Add a provider
Section titled “Add a provider”POST /v1/organization/model_providers adds a provider to your organization. It
needs write on ModelProvider.
curl -H "Authorization: Bearer $OPENBOT_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "kind": "openai-compat", "name": "Local vLLM", "endpoint": "https://llm.example.com/v1", "apiKey": "sk-...", "models": [ { "model": "qwen3-32b", "modelOwner": "organization", "upstream": "Qwen/Qwen3-32B" } ] }' \ https://api.example.com/v1/organization/model_providers| Field | Rules |
|---|---|
kind |
Required. The API that the service speaks: chatgpt, openai, anthropic, openai-compat or typesafe. See the table below. |
name |
Required. 1 to 120 characters. |
endpoint |
Required for openai-compat and typesafe. Optional for the others. An http or https base URL, often ending in /v1. No user name, password, query, or # part. |
apiKey |
Required except for chatgpt. ChatGPT uses browser sign-in. |
models |
Required. 0 to 200 links. The order is the order of the model list. |
kind |
API | Model types it can link |
|---|---|---|
openai |
OpenAI Responses API, and the Realtime API for live. |
language and live |
anthropic |
Anthropic Messages API. | language |
chatgpt |
OpenAI Responses API (OAuth) | language |
openai-compat |
OpenAI Chat Completions API, and services that copy it. | language and decision |
typesafe |
The decision API of TypeSafe. | decision |
An openai-compat provider can link both types, so a gateway (a service that
passes calls on to other services), such as OpenRouter or LiteLLM, can offer
both. OpenBot connects only to public addresses. For a model server in your own
network or on your machine, the operator must list its host in
OPENBOT_OUTBOUND_ALLOW. Until then, calls to it fail. See
Outbound connections. A typesafe
provider has no default address yet, because TypeSafe has not published one
official address. Give the address that your account uses. If a provider links a
model of a type that it cannot serve, OpenBot returns 400 with
ModelProviderInvalid.
Each link has these fields:
| Field | Rules |
|---|---|
model |
Required. The id of the model. A provider links each model ID only once. |
modelOwner |
Required. organization for a model of your organization, or platform for a shared model. |
upstream |
Optional. The model name that the service gets, when it is not the model id. |
reasoningEfforts |
Optional. Replaces the reasoningEfforts of the model for this provider. |
serviceTiers |
Optional. Replaces the serviceTiers of the model for this provider. |
contextWindow |
Optional. Replaces the contextWindow of the model for this provider. |
maxOutputTokens |
Optional. Replaces the maxOutputTokens of the model for this provider. |
A provider of your organization can link your own models, and the shared models
that the platform makes available to you. For any other model, OpenBot returns
404 with ModelNotFound.
OpenBot answers with the provider. Its id starts with prov_. A provider ID
is unique in the whole platform. A model selection names it as provider. The
API key never comes back. The answer shows apiKey: { set, hint } instead. See
Platform.
Change or remove a provider
Section titled “Change or remove a provider”| Route | What it does |
|---|---|
GET /v1/organization/model_providers |
Lists the providers. |
GET /v1/organization/model_providers/:providerId |
Reads one provider. |
PATCH /v1/organization/model_providers/:providerId |
Changes a provider. |
DELETE /v1/organization/model_providers/:providerId |
Removes a provider. |
Reading needs read on ModelProvider. Changes need write on
ModelProvider.
The body of PATCH needs expectedVersion, the version that you read. If
someone changed the provider after you read it, OpenBot returns 409 with
ModelProviderConflict. Leave out apiKey to keep the current key. Send
models to replace all links of the provider.
curl -X PATCH -H "Authorization: Bearer $OPENBOT_TOKEN" \ -H "Content-Type: application/json" \ -d '{"expectedVersion":0,"apiKey":"sk-new..."}' \ https://api.example.com/v1/organization/model_providers/prov_4c1e...The next model call after a change uses the new key, endpoint or links. OpenBot builds each model once, and builds it again when its provider changes.
You cannot remove a provider while rulesModel or botMemoryModel in the
organization settings names it. OpenBot returns 409
with ModelProviderInUse. Change the setting first.
Connect ChatGPT
Section titled “Connect ChatGPT”A chatgpt model provider uses browser sign-in in place of an API key. The
organization or platform owns the provider. All work that may use it uses the
same connected account. ChatGPT uses the same model links, permissions, prices,
budgets, and platform credits as other providers.
Create it with POST /v1/organization/model_providers:
{ "kind": "chatgpt", "name": "ChatGPT", "models": [] }Do not send apiKey or endpoint. Keep the returned prov_* ID. For a
platform provider, use /v1/platform/model_providers and set availability.
You need the same provider write rights as for an API key provider.
Use the provider API to start sign-in, submit the browser callback, and
disconnect the account. The paths below start with
/v1/organization/model_providers/:providerId. For the platform, replace
organization with platform.
| Method and path | Use |
|---|---|
POST /oauth/authorizations |
Start sign-in with redirectUri and expectedVersion. Read id, url, and expiresAt. |
POST /oauth/authorizations/:authorizationId/complete |
Send the full callbackUrl from the local listener. |
DELETE /oauth |
Clear tokens and try to end the remote session. Keep the provider and model links. |
GET /available_models |
Read the account’s upstream models in items. |
The local listener uses http://127.0.0.1:<port>/auth/callback. Start it before
opening the authorization URL. A sign-in attempt lasts ten minutes and can be
completed once, by the user who started it. A provider change ends old attempts.
API keys and same-origin session cookies work. Cookie writes need the exact
public Origin. The server stores tokens encrypted and renews them as needed.
Read the provider’s oauth field for connected, loginRequired, and account
info. No token comes back. The apiKey field has set: false and hint: null.
If sign-out returns revoked: false, local tokens are gone but remote sign-out
was not confirmed. You can also disconnect the app in ChatGPT Settings.
The available model list does not change saved models. Add a model and link it
through the normal provider API. Use the returned model id as the upstream
name when the local model ID differs. Select the normal provider ID:
{ "_tag": "Config.Selection", "provider": "prov_example", "model": "my-model" }ChatGPT works in Chats, Rules, Bot memory, and scheduled work. It supports language models and OpenBot function tools, not Live audio. It uses Sign in with ChatGPT and the public Responses API. Plan limits still apply. A failed login or a spent plan limit does not switch the work to another provider.
See Client for typed calls.
Choose a model
Section titled “Choose a model”GET /v1/organization/model_catalog lists the models that you may use now: the
models of your organization where your policy allows use on Model. Each item
is one model of one provider, with the values of the link already applied. A
model that two providers link shows two times.
curl -H "Authorization: Bearer $OPENBOT_TOKEN" \ https://api.example.com/v1/organization/model_catalog{ "providers": [ { "id": "prov_4c1e...", "owner": "organization", "kind": "openai-compat", "name": "Local vLLM" } ], "models": [ { "provider": "prov_4c1e...", "id": "qwen3-32b", "owner": "organization", "type": "language", "name": "Qwen3 32B", "reasoningEfforts": [], "serviceTiers": [], "contextWindow": 131072, "price": { "model": "qwen3-32b", "timeZone": "UTC", "tiers": [ { "upToInputTokens": null, "inputUncached": 400000, "inputCacheRead": 100000, "inputCacheWrite": 0, "output": 1200000, "outputReasoning": null } ], "periods": [], "effectiveFrom": "2026-10-20T08:00:00.000Z" } } ]}price is the current price of the model at this provider. It is missing when
nobody set a price. See Prices.
A model selection names both the provider and the model ID:
{ "_tag": "Config.Selection", "provider": "prov_4c1e...", "model": "qwen3-32b" }OpenBot never picks a model for you. There is no default model. Each use names its own model:
- A new Chat uses the
modelof its Bot. When the Bot has no model, the first message to it fails with400,ChatModelInvalidand the reasonmissing. A new Bot has no model unless you give it one, so set itsmodelwhen you make it, or later withPATCH /v1/bots/:id. - The judge of Rules uses
rulesModelin the organization settings. When it isnull, the judge gives no answer, so a person or a Bot decides. - Bot memory uses
botMemoryModelin the same settings. When it isnull, Bots learn nothing.
rulesModel can be a language model or a decision model. botMemoryModel
must be a language model, because the memory Bot writes text. Otherwise
OpenBot returns 400 with SettingsInvalid. The catalog gives the type of
each model, and a Chat can choose only language models.
Older versions of OpenBot had defaultModel and helperModel. When you
upgrade, OpenBot gives the old defaultModel to each Bot that has no model.
rulesModel and botMemoryModel get the old helper model: helperModel, or
else defaultModel. When both were null, the new values stay empty, and you
must set them.
For example, to give a Bot a model, send its current revision as expected:
curl -X PATCH -H "Authorization: Bearer $OPENBOT_TOKEN" -H 'Content-Type: application/json' \ -d '{"expected":0,"model":{"_tag":"Config.Selection","provider":"prov_4c1e...","model":"qwen3-32b"}}' \ https://api.example.com/v1/bots/$BOT_IDA Chat can save its own choice. Send the current Chat revision as expected:
curl -X PATCH -H "Authorization: Bearer $OPENBOT_TOKEN" -H 'Content-Type: application/json' \ -d '{"expected":3,"model":{"_tag":"Config.Selection","provider":"prov_4c1e...","model":"qwen3-32b"}}' \ https://api.example.com/v1/chats/$CHAT_IDSend "model": null to use the model of the Bot again.
GET /v1/chats/:id/models shows the same models for one Chat. See the
Chats API reference for all fields.
To limit which providers a member may choose, give the member a deny rule for
use on Model with the provider field. See
Permissions.
Decision model
Section titled “Decision model”Your organization has no setting for one decision model. Each use of a model names its own model. Some uses can take a decision model or a language model, so one fixed decision model for the whole organization does not fit them.
For example, to use Jev through TypeSafe, add the model, then a provider that links it:
curl -H "Authorization: Bearer $OPENBOT_TOKEN" -H "Content-Type: application/json" \ -d '{ "id": "jev-latest", "type": "decision", "name": "Jev", "reasoningEfforts": [], "serviceTiers": [] }' \ https://api.example.com/v1/organization/models
curl -H "Authorization: Bearer $OPENBOT_TOKEN" -H "Content-Type: application/json" \ -d '{ "kind": "typesafe", "name": "TypeSafe", "endpoint": "https://typesafe.example.com/v1", "apiKey": "...", "models": [{ "model": "jev-latest", "modelOwner": "organization" }] }' \ https://api.example.com/v1/organization/model_providersGET /v1/organization/model_catalog then lists Jev with "type": "decision".
OpenBot calls a typesafe provider with the TypeSafe client of Effect
(@effect/ai-typesafe). It sends each request to <endpoint>/systemone with
the API key as a bearer token, and sends upstream as the model name. OpenBot
builds the client once for each provider and model, and builds it again when the
provider changes. OpenBot cannot ask a decision model through an openai-compat
provider yet. You can link one there, but use a typesafe provider to ask it.
The judge of Rules can use a decision model. Set rulesModel to it. See
The judge model.
Run without a model
Section titled “Run without a model”An organization without any provider can still read the Chats that it has, and delete their history. It cannot talk to a Bot, because each Turn needs a model. See Choose a model.
A message to a Bot fails with status 503 and the error
ChatExecutionUnavailable when no model can run its Turn:
{ "_tag": "ChatExecutionUnavailable", "chat": "..." }A notice from OpenBot, such as the result of a delegation, still goes into the inbox of its Chat. See Notices.
Built-in models
Section titled “Built-in models”For this development task, see Model backends.
Development models
Section titled “Development models”For this development task, see Model backends.