Models
Endpoints for managing AI models and for running one-off calls against a model.
A model is either canonical (it points at a Provider plus a provider-side
model identifier) or an alias (it references another model via
referencesId and inherits that model's provider and capabilities).
Listing and reading models is open to any authenticated user. Creating,
updating, and deleting models require permission.ai.manage (superuser). None
of these routes require the AI module to be enabled on the tenant.
GET /api/models
List the tenant's models.
Authentication: Required (no permission gate)
Query Parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
chat | boolean | false | Keep only chat-capable models. Alias models inherit the referenced model's chat flag. |
includeInternal | boolean | false | Include internal/system models. Excluded by default. |
Response:
A HAL-wrapped collection: a top-level { start, count, total } envelope with
the models under _embedded["inf:model"]. It is not a hand-shaped document and
there is no inf:model key at the document root. Canonical models eager-load
their provider; alias models eager-load referencedModel.
{
"_links": {
"self": {
"href": "https://informer.example.com/api/models"
}
},
"start": 0,
"count": 5,
"total": 5,
"_embedded": {
"inf:model": [
{
"_links": {
"self": {
"href": "https://informer.example.com/api/models/00000000-0000-4000-8000-000000000001"
}
},
"naturalId": "go_advanced",
"permissions": {},
"promptPpm": null,
"completionPpm": null,
"cacheReadPpm": null,
"cacheWriteShortPpm": null,
"cacheWriteLongPpm": null,
"tenant": "acme",
"id": "00000000-0000-4000-8000-000000000001",
"providerId": null,
"referencesId": null,
"name": "go_advanced",
"slug": "go_advanced",
"model": null,
"options": null,
"chat": false,
"moderated": false,
"contextWindow": 200000,
"maxOutputTokens": null,
"instructions": null,
"description": null,
"rates": null,
"managed": true,
"internal": false,
"deprecatedAt": null,
"web": false,
"attachments": false,
"generateImage": false,
"createEmbeddings": false,
"score": null,
"pricingTier": null,
"badges": null,
"tier": null,
"customized": false,
"createdAt": "2026-01-15T16:20:00.000Z",
"updatedAt": "2026-01-15T16:20:00.000Z",
"referencedModel": null,
"provider": null
},
{
"_links": {
"self": {
"href": "https://informer.example.com/api/models/00000000-0000-4000-8000-000000000002"
}
},
"naturalId": "go_everyday",
"permissions": {},
"promptPpm": null,
"completionPpm": null,
"cacheReadPpm": null,
"cacheWriteShortPpm": null,
"cacheWriteLongPpm": null,
"tenant": "acme",
"id": "00000000-0000-4000-8000-000000000002",
"providerId": null,
"referencesId": null,
"name": "go_everyday",
"slug": "go_everyday",
"model": null,
"options": null,
"chat": false,
"moderated": false,
"contextWindow": 200000,
"maxOutputTokens": null,
"instructions": null,
"description": null,
"rates": null,
"managed": true,
"internal": false,
"deprecatedAt": null,
"web": false,
"attachments": false,
"generateImage": false,
"createEmbeddings": false,
"score": null,
"pricingTier": null,
"badges": null,
"tier": null,
"customized": false,
"createdAt": "2026-01-15T16:20:00.000Z",
"updatedAt": "2026-01-15T16:20:00.000Z",
"referencedModel": null,
"provider": null
},
{
"_links": {
"self": {
"href": "https://informer.example.com/api/models/00000000-0000-4000-8000-000000000003"
}
},
"naturalId": "informer-basic",
"permissions": {},
"promptPpm": 0.4,
"completionPpm": 1.6,
"cacheReadPpm": null,
"cacheWriteShortPpm": null,
"cacheWriteLongPpm": null,
"tenant": "acme",
"id": "00000000-0000-4000-8000-000000000003",
"providerId": "00000000-0000-4000-8000-000000000004",
"referencesId": null,
"name": "Informer Basic",
"slug": "informer-basic",
"model": "gpt-4.1",
"options": null,
"chat": true,
"moderated": false,
"contextWindow": 200000,
"maxOutputTokens": null,
"instructions": null,
"description": null,
"rates": null,
"managed": true,
"internal": false,
"deprecatedAt": null,
"web": false,
"attachments": false,
"generateImage": false,
"createEmbeddings": false,
"score": null,
"pricingTier": null,
"badges": null,
"tier": null,
"customized": false,
"createdAt": "2026-01-15T16:20:00.000Z",
"updatedAt": "2026-01-15T16:20:00.000Z",
"referencedModel": null,
"provider": {
"permissions": {},
"tenant": "acme",
"id": "00000000-0000-4000-8000-000000000004",
"name": "OpenAI",
"slug": "openai",
"type": "openai",
"key": null,
"managed": true,
"internal": false,
"settings": null,
"data": null,
"createdAt": "2026-01-15T16:20:00.000Z",
"updatedAt": "2026-01-15T16:20:00.000Z"
}
},
"… 2 more items (5 total) — omitted from docs"
]
}
}
Model properties:
| Field | Type | Description |
|---|---|---|
id | string (UUID) | Model id |
naturalId | string | Stable natural identifier |
name | string | Display name |
slug | string | URL-safe identifier |
description | string | null | Model description |
providerId | string (UUID) | null | Owning Provider (canonical models) |
referencesId | string (UUID) | null | Base model id (alias models) |
model | string | null | Provider's model identifier |
chat | boolean | Supports chat completions |
web | boolean | Can access web content |
generateImage | boolean | Can generate images |
attachments | boolean | Supports file attachments |
createEmbeddings | boolean | Can create embeddings |
effortOverride | boolean | null | Admin override for the reasoning-effort dial: true or false pins it, null (the default) lets the provider driver decide from the model id |
supportsEffort | boolean | Read-only. Whether a chat on this model may set an effort level: the override when set, otherwise the driver's answer for the canonical model. A tier alias may pin its own; a plain alias mirrors its base model's |
moderated | boolean | Content moderation enabled |
tier | string | null | Budget tier: everyday, advanced, strategic |
pricingTier | string | null | Cost category: Low, Medium, High, Very High |
contextWindow | integer | null | Maximum context tokens |
maxOutputTokens | integer | null | Maximum output tokens |
promptPpm | number | null | Cost per million prompt tokens |
completionPpm | number | null | Cost per million completion tokens |
cacheReadPpm | number | null | Cost per million cached read tokens |
cacheWriteShortPpm | number | null | Cost per million short-term cache writes |
cacheWriteLongPpm | number | null | Cost per million long-term cache writes |
rates | object | null | Named provider fee rates that do not fit the per-million columns (for example webSearchPer1k, imagePerUnit, longContext), delivered by model sync from the License Manager. A fee may be { value, charged }, where charged: false meters it at full rate but never debits it |
score | integer | null | Quality/capability score |
badges | array | null | Display badges |
instructions | string | null | System instructions |
options | object | null | Provider-specific options |
managed | boolean | System-managed (License Manager controlled) |
internal | boolean | Internal/system-only model |
customized | boolean | Whether a managed model has been customized |
deprecatedAt | date | null | Deprecation date (null if active) |
provider | object | null | Eager-loaded Provider (canonical models) |
referencedModel | object | null | Eager-loaded base model (alias models) |
Alias models:
Alias models reference another model via referencesId. They:
- Inherit capabilities from the referenced model.
- Can override
name,slug,instructions, andoptions. - Take their
providerIdandmodelfrom the referenced model via a database trigger, so those are never sent when creating an alias.
POST /api/models
Create a model (canonical or alias).
Authentication: permission.ai.manage
Pre-blocks: permission.ai.manage
The payload is an XOR. If referencesId is present it is treated as an alias
payload and only { referencesId, name, slug, instructions, options } are kept;
every other field is stripped. Otherwise it is a canonical payload requiring
{ providerId, model, name }. Both referencesId and providerId must be
UUIDs.
Request Body (canonical):
| Field | Type | Required | Description |
|---|---|---|---|
providerId | string (UUID) | Yes | Owning Provider |
model | string | Yes | Provider's model identifier |
name | string | Yes | Display name |
slug | string | No | URL-safe id (defaults to model) |
description | string | No | Model description |
chat | boolean | No | Supports chat |
web | boolean | No | Web access |
generateImage | boolean | No | Image generation |
attachments | boolean | No | File attachment support |
createEmbeddings | boolean | No | Embedding generation |
effortOverride | boolean | null | No | Override for the reasoning-effort dial; omit or send null to let the provider driver decide |
moderated | boolean | No | Content moderation |
tier | string | No | everyday, advanced, strategic |
pricingTier | string | No | Low, Medium, High, Very High |
contextWindow | integer | No | Max context tokens |
maxOutputTokens | integer | No | Max output tokens |
promptPpm | number | No | Prompt cost per million tokens |
completionPpm | number | No | Completion cost per million tokens |
cacheReadPpm | number | No | Cache read cost per million |
cacheWriteShortPpm | number | No | Short cache write cost per million |
cacheWriteLongPpm | number | No | Long cache write cost per million |
score | integer | No | Quality score |
instructions | string | No | System instructions |
options | object | No | Provider-specific options |
deprecatedAt | date | No | Deprecation date |
Example (canonical):
{
"providerId": "00000000-0000-4000-8000-000000000001",
"model": "claude-3-5-sonnet-20241022",
"name": "Claude 3.5 Sonnet",
"slug": "claude-3-5-sonnet",
"chat": true,
"tier": "advanced",
"contextWindow": 200000,
"promptPpm": 3.0,
"completionPpm": 15.0
}
Response:
Responds 200 with the created model. The handler returns the new row
directly, so there is no 201 status and no Location header.
{
"providerId": "00000000-0000-4000-8000-000000000001",
"model": "claude-3-5-sonnet-20241022",
"name": "Claude 3.5 Sonnet",
"slug": "claude-3-5-sonnet",
"chat": true,
"tier": "advanced",
"contextWindow": 200000,
"promptPpm": 3,
"completionPpm": 15
}
{
"_links": {
"self": {
"href": "https://informer.example.com/api/models"
}
},
"naturalId": "claude-35-sonnet",
"permissions": {},
"promptPpm": 3,
"completionPpm": 15,
"cacheReadPpm": null,
"cacheWriteShortPpm": null,
"cacheWriteLongPpm": null,
"tenant": "acme",
"id": "00000000-0000-4000-8000-000000000002",
"web": false,
"attachments": false,
"generateImage": false,
"createEmbeddings": false,
"customized": false,
"name": "Claude 3.5 Sonnet",
"slug": "claude-35-sonnet",
"chat": true,
"tier": "advanced",
"contextWindow": 200000,
"providerId": "00000000-0000-4000-8000-000000000001",
"model": "claude-3-5-sonnet-20241022",
"referencesId": null,
"updatedAt": "2026-01-15T16:20:00.000Z",
"createdAt": "2026-01-15T16:20:00.000Z",
"options": null,
"instructions": null,
"moderated": false,
"internal": false,
"description": null,
"deprecatedAt": null,
"managed": false,
"score": null,
"pricingTier": null,
"badges": null,
"maxOutputTokens": null,
"rates": null
}
Request Body (alias):
| Field | Type | Required | Description |
|---|---|---|---|
referencesId | string (UUID) | Yes | Base model id to reference |
name | string | Yes | Display name for the alias |
slug | string | Yes | URL-safe identifier |
instructions | string | No | Override instructions |
options | object | No | Override options |
Example (alias):
{
"referencesId": "00000000-0000-4000-8000-000000000002",
"name": "Strategic Assistant",
"slug": "strategic-assistant",
"instructions": "Focus on strategic analysis"
}
Response:
Responds 200 with the created alias. Its providerId and model are filled
in from the referenced model by a database trigger.
{
"referencesId": "00000000-0000-4000-8000-000000000001",
"name": "Strategic Assistant",
"slug": "strategic-assistant",
"instructions": "Focus on strategic analysis"
}
{
"_links": {
"self": {
"href": "https://informer.example.com/api/models"
}
},
"naturalId": "strategic-assistant",
"permissions": {},
"promptPpm": 15,
"completionPpm": 75,
"cacheReadPpm": null,
"cacheWriteShortPpm": null,
"cacheWriteLongPpm": null,
"tenant": "acme",
"id": "00000000-0000-4000-8000-000000000002",
"web": false,
"attachments": false,
"generateImage": false,
"createEmbeddings": false,
"customized": false,
"name": "Strategic Assistant",
"slug": "strategic-assistant",
"instructions": null,
"referencesId": "00000000-0000-4000-8000-000000000001",
"model": "claude-opus-4-20250514",
"updatedAt": "2026-01-15T16:20:00.000Z",
"createdAt": "2026-01-15T16:20:00.000Z",
"providerId": "00000000-0000-4000-8000-000000000003",
"options": null,
"moderated": false,
"chat": true,
"internal": false,
"contextWindow": 200000,
"description": null,
"deprecatedAt": null,
"managed": false,
"score": 95,
"pricingTier": "Very High",
"badges": null,
"maxOutputTokens": 16384,
"tier": "strategic",
"rates": null
}
GET /api/models/{id}
Retrieve a single model.
Authentication: Required (no permission gate)
Pre-blocks: model.lookup
The id may be a UUID or a slug. Canonical models return their eager-loaded
provider; alias models return referencedModel.
Response:
{
"_links": {
"self": {
"href": "https://informer.example.com/api/models/00000000-0000-4000-8000-000000000001"
}
},
"naturalId": "claude-opus-4",
"permissions": {},
"promptPpm": 15,
"completionPpm": 75,
"cacheReadPpm": null,
"cacheWriteShortPpm": null,
"cacheWriteLongPpm": null,
"tenant": "acme",
"id": "00000000-0000-4000-8000-000000000001",
"providerId": "00000000-0000-4000-8000-000000000002",
"referencesId": null,
"name": "Claude Opus 4",
"slug": "claude-opus-4",
"model": "claude-opus-4-20250514",
"options": null,
"chat": true,
"moderated": false,
"contextWindow": 200000,
"maxOutputTokens": 16384,
"instructions": null,
"description": null,
"rates": null,
"managed": false,
"internal": false,
"deprecatedAt": null,
"web": false,
"attachments": false,
"generateImage": false,
"createEmbeddings": false,
"score": 95,
"pricingTier": "Very High",
"badges": null,
"tier": "strategic",
"customized": false,
"createdAt": "2026-01-15T16:20:00.000Z",
"updatedAt": "2026-01-15T16:20:00.000Z",
"provider": {
"permissions": {},
"tenant": "acme",
"id": "00000000-0000-4000-8000-000000000002",
"name": "Anthropic",
"slug": "anthropic",
"type": "anthropic",
"key": null,
"managed": true,
"internal": false,
"settings": null,
"data": null,
"createdAt": "2026-01-15T16:20:00.000Z",
"updatedAt": "2026-01-15T16:20:00.000Z"
}
}
PUT /api/models/{id}
Update a model.
Authentication: permission.ai.manage
Pre-blocks: permission.ai.manage, then model.lookup
Unknown fields are stripped. providerId, model, and referencesId can be
sent but are ignored for non-managed edits. Returns the updated model.
Request Body:
The same optional fields as the canonical create payload, minus the identity fields above.
Example:
{ "description": "Updated description", "score": 90 }
Response:
Responds 200 with the updated model.
{
"description": "Updated description",
"score": 90
}
{
"_links": {
"self": {
"href": "https://informer.example.com/api/models/00000000-0000-4000-8000-000000000001"
}
},
"naturalId": "claude-opus-4",
"permissions": {},
"promptPpm": 15,
"completionPpm": 75,
"cacheReadPpm": null,
"cacheWriteShortPpm": null,
"cacheWriteLongPpm": null,
"tenant": "acme",
"id": "00000000-0000-4000-8000-000000000001",
"providerId": "00000000-0000-4000-8000-000000000002",
"referencesId": null,
"name": "Claude Opus 4",
"slug": "claude-opus-4",
"model": "claude-opus-4-20250514",
"options": null,
"chat": true,
"moderated": false,
"contextWindow": 200000,
"maxOutputTokens": 16384,
"instructions": null,
"description": "Updated description",
"rates": null,
"managed": false,
"internal": false,
"deprecatedAt": null,
"web": false,
"attachments": false,
"generateImage": false,
"createEmbeddings": false,
"score": 90,
"pricingTier": "Very High",
"badges": null,
"tier": "strategic",
"customized": false,
"createdAt": "2026-01-15T16:20:00.000Z",
"updatedAt": "2026-01-15T16:20:00.000Z",
"provider": {
"permissions": {},
"tenant": "acme",
"id": "00000000-0000-4000-8000-000000000002",
"name": "Anthropic",
"slug": "anthropic",
"type": "anthropic",
"key": null,
"managed": true,
"internal": false,
"settings": null,
"data": null,
"createdAt": "2026-01-15T16:20:00.000Z",
"updatedAt": "2026-01-15T16:20:00.000Z"
}
}
Managed models (those controlled by License Manager) reject edits with 403,
except for the tier-alias customization carve-outs and the effortOverride
field, which an admin owns on every managed row. Send effortOverride on its
own (true, false, or null for auto) and the write is allowed on any
managed model, alias or canonical; the License Manager sync never touches it.
Send it alongside any other field and the whole request is a 403.
On a model that is not managed, a plain alias (one with referencesId and
no tier) rejects effortOverride with a 400: the sync_model_from_reference
trigger mirrors the base model's capabilities onto it on every write, so an
override written there would not survive. Set it on the base model instead.
Managed rows never reach this check — the managed rule above settles them first.
DELETE /api/models/{id}
Delete a model.
Authentication: permission.ai.manage
Pre-blocks: permission.ai.manage, then model.lookup
Response:
Responds 200 with an empty body (Informer convention), not 204 No Content.
Deleting a managed model answers 403. Deleting a canonical model may orphan
alias models that reference it.
POST /api/models/{id}/_chat
Run a chat against a model without persisting anything. Nothing is written to the database, so each request starts from the messages you send it. To save the conversation instead, see Choosing an endpoint.
Authentication: Required (AI access)
Pre-blocks: Model resolution, then credit enforcement (tiered when the model has a tier, legacy otherwise)
An unknown model id answers 404. A model row that exists but has not yet been
populated by the License Manager sync answers 503 with
data.code = "AI_MODEL_PENDING", which is retryable.
Metering: Consumes credits subject to the caller's usage profile (skipped under BYOK).
Request Body:
The payload is stripUnknown, so unrecognized fields are dropped rather than
rejected.
| Field | Type | Default | Description |
|---|---|---|---|
messages | array | - | Required. Messages in parts format |
primaryAssistantId | string | null | - | The Assistant that answers, with its full configuration |
assistantIds | array | [] | Assistants the model may dispatch to |
toolkitIds | array | [] | Additional Toolkits to enable |
tools | object | {} | Tool configuration |
skills | object | {} | Inline skill configuration, keyed by skill type |
functions | array | [] | Built-in function names to enable |
system | string | null | - | System prompt |
dynamicSystem | string | null | - | Dynamic system prompt fragment |
webSearch | boolean | false | Enable the web search function |
helpCenter | boolean | false | Enable the Help Center search function |
magicReport | boolean | false | Select the Magic Report admin instructions |
maxSteps | number | 20 | Maximum tool-calling steps |
outputSize | string | medium | small, medium, or large |
Datasets, knowledge files, secrets, and Toolkits are not payload fields. They
reach the request through the Assistant's own configuration, so attaching an
Assistant is what brings them in. Toolkits configured on the Assistant are merged
with any toolkitIds you pass.
primaryAssistantId and assistantIds both resolve through read_access, so an
id the caller cannot see is treated as absent. When assistantIds holds exactly
one Assistant and no primaryAssistantId is given, that Assistant is promoted to
the primary.
Admin instructions are selected by context: Magic Report, magic visual, Assistant, Help Center, or Copilot, in that order of precedence.
Example Request:
{
"messages": [
{ "role": "user", "parts": [{ "type": "text", "text": "Which programs have fall deadlines?" }] }
],
"primaryAssistantId": "admin:admissions-helper"
}
Response:
A live model stream served as Server-Sent Events (text/event-stream); not
shown. The connection closes on the abort signal.
POST /api/models/{id}/_completion
Generate a completion from a model, with no Assistant and no persistence.
Authentication: Required (AI access)
Pre-blocks: model.lookup(params.id)
Request Body:
The payload is stripUnknown.
| Field | Type | Default | Description |
|---|---|---|---|
prompt | string | - | Required. Appended to messages as a user message |
messages | array | [] | Prior message context, in parts format |
Sampling controls such as temperature and stop sequences are not accepted here;
they belong to the model's own options. See
POST /api/models.
Example Request:
{
"prompt": "Summarize the fall admissions timeline in one sentence."
}
Response:
A live model stream served as Server-Sent Events (text/event-stream), despite
the endpoint's name; not shown.
POST /api/models/{id}/_object
Generate a structured object from a model using a JSON Schema. This is the only AI endpoint that answers with a plain JSON body rather than a stream.
Authentication: Required (AI access)
Pre-blocks: model.lookup(params.id)
Request Body:
The payload is stripUnknown.
| Field | Type | Default | Description |
|---|---|---|---|
schema | object | - | Required. JSON Schema describing the output |
messages | array | - | Required. Prompt messages, in parts format |
outputSize | string | - | small, medium, or large |
There is no field for an Assistant, a system prompt, or Toolkits, so an
Assistant's skills never run on this route. When you need those, use
_chat and read the stream.
Example Request:
{
"schema": {
"type": "object",
"properties": {
"sentiment": { "type": "string", "enum": ["positive", "negative", "neutral"] },
"confidence": { "type": "number", "minimum": 0, "maximum": 1 },
"keywords": { "type": "array", "items": { "type": "string" } }
},
"required": ["sentiment", "confidence"]
},
"messages": [
{ "role": "user", "parts": [{ "type": "text", "text": "Analyze the sentiment: 'This product is amazing!'" }] }
]
}
Response:
The generated object itself, as the entire response body, with no envelope and no HAL links. Because a live model call produces it, no captured example is shown here.
Use advanced or strategic tier models for complex schema generation. Everyday models may struggle with intricate structures.