AI Providers
The AI Providers module lets you register and manage LLM provider configurations for a project. Each provider record stores the model slug, optional base URL, optional configuration, and an optional link to a Secret that supplies the API key.
Overview
An AI provider is a named configuration that tells the system how to reach a specific LLM endpoint. A project can have multiple providers — for example, one for GPT-4o and another for Claude 3.5.
When a provider is linked to a secret the secret's encrypted value is retrieved and passed as the API key when calling the LLM. The key is never exposed through the API. See it end to end in Connect Third-Party LLMs - Step 4 (Create provider records).
See the Permissions Reference for the IAM action strings for this module.
Related Tutorials
- Chat with an LLM - Step 3 (Create a local AI provider)
- Connect Third-Party LLMs - Step 4 (Create provider records)
- Multi-Agent Sonnet with Nested Agent Calls - Step 3 (Create an AI provider)
Data Model
| Field | Type | Description |
|---|---|---|
id | string | Public identifier (e.g. aip_…) |
project_id | string | ID of the owning project |
secret_id | string | null | Public ID of the linked secret, or null |
name | string | Human-readable label |
provider | AiProviderSlug | Provider slug (see below) |
default_model | string | Default model name sent to the provider API |
base_url | string | null | Override base URL (optional, useful for self-hosted LLMs) |
config | object | null | Arbitrary provider-specific configuration object |
created_at | string | ISO 8601 creation timestamp |
updated_at | string | ISO 8601 last-updated timestamp |
Provider Slugs
Valid values for the provider field:
| Slug | Description |
|---|---|
openai | OpenAI |
anthropic | Anthropic |
google | Google Gemini |
xai | xAI (Grok) |
groq | Groq |
ollama | Ollama (local) |
azure | Azure OpenAI |
bedrock | Amazon Bedrock |
gateway | Generic API gateway |
custom | Custom / self-hosted model |
A local ollama provider needs no linked secret — it uses the server's OLLAMA_BASE_URL instead. See it end to end in Chat with an LLM - Step 3 (Create a local AI provider).
Key Concepts
Bedrock authentication
The bedrock provider supports two authentication modes, determined by the shape of the linked secret's JSON value:
IAM credentials — pass accessKeyId, secretAccessKey, and optionally sessionToken. The client signs requests with AWS SigV4.
{
"accessKeyId": "<aws-access-key-id>",
"secretAccessKey": "<aws-secret-access-key>",
"sessionToken": "<optional-session-token>"
}
Bedrock API key — pass apiKey only (format ABSK…). The client uses Bearer token authentication via AWS_BEARER_TOKEN_BEDROCK. This is the new authentication mechanism introduced for Amazon Bedrock in 2025.
{ "apiKey": "ABSK..." }
Important: Store the secret value as a JSON object (shown above) — this is the canonical form and the only one that supports IAM credentials. As a convenience, a bare
ABSK…string (with no JSON wrapper) is also accepted: the server tries to parse the value as JSON first, and if that fails but the value starts withABSKit is treated as{ "apiKey": "<value>" }. IAM credentials (accessKeyId/secretAccessKey) must always use the JSON object form.
If neither field is present the default AWS credential chain (environment variables, instance profile, etc.) is used. The region field in the provider's config object defaults to us-east-1.
You can also pass the API key directly in the provider's config object as api_key (without linking a secret). This is useful for quick testing but the secret-linked approach is recommended for production.
{ "api_key": "ABSK..." }
Price overrides
A project can price its own provider instances without a global admin. A per-provider price override is a price-book row bound to a specific AI provider — an enterprise-negotiated rate or a gateway with markup — that wins over the global default when usage cost is computed for that provider. Manage them with:
GET /api/v1/ai-providers/{ai_provider_id}/prices— list this provider's overridesPUT /api/v1/ai-providers/{ai_provider_id}/prices— upsert them, keyed on(model, effective_from)
Both are authorized by the caller's access to the provider's own project (ai-providers:GetAiProviderPrices / ai-providers:ManageAiProviderPrices), so one project never sees another's negotiated rates — unlike the global price book, which lists defaults only. The provider slug is taken from the AI provider itself (an override matches only when its slug equals the provider's), so you supply just the model, rates, and effective_from. effective_from must be in the future; past prices are immutable, so ship corrections as new future-dated rows. See Usage - Pricing for how the effective price is chosen and frozen onto each meter.
Deleting a provider
DELETE /api/v1/ai-providers/{ai_provider_id} classifies everything that references the provider into two kinds:
| Dependent | Kind | Behavior |
|---|---|---|
| Chats, agents, discussions | Live reference | Always block with 409. force does not override them — delete or repoint each resource first. |
| Price overrides | Soft dependent | Block with 409 unless force=true, which deletes the overrides (meaningless without the provider). |
| Usage/generation records, discussion participants | Soft dependent | Block with 409 unless force=true, which unlinks them (nulls the provider FK), preserving the row and its as-billed receipt. |
A delete with no dependents (or force=true and only soft dependents) returns 204. On a 409 the response carries error.code = "AI_PROVIDER_HAS_DEPENDENTS" and an error.meta describing what blocked it:
{
"error": {
"code": "AI_PROVIDER_HAS_DEPENDENTS",
"message": "AI provider 'aip_01' is in use by 2 chat(s), 1 agent(s) ...",
"meta": {
"chatCount": 2, "chatIds": ["chat_01", "chat_02"],
"agentCount": 1, "agentIds": ["agent_01"],
"discussionCount": 0, "discussionIds": [],
"priceOverrideCount": 0, "usageEventCount": 0, "discussionParticipantCount": 0,
"forcible": false
}
}
}
forcible is true only when the block comes solely from soft dependents — i.e. a force=true retry would succeed. The *Ids arrays sample up to 20 offending IDs so you can act on them directly; the *Count fields always report the true totals.
Examples
Create an AI provider
- CLI
- SDK
- curl
soat create-ai-provider \
--project-id proj_ABC \
--name "OpenAI GPT-4o" \
--provider openai \
--default-model gpt-4o \
--secret-id sec_01
// SDK
import { SoatClient } from '@soat/sdk';
const soat = new SoatClient({
baseUrl: 'https://api.example.com',
token: 'sk_...',
});
const { data, error } = await soat.aiProviders.createAiProvider({
body: {
project_id: 'proj_ABC',
name: 'OpenAI GPT-4o',
provider: 'openai',
default_model: 'gpt-4o',
secret_id: 'sec_01',
},
});
if (error) throw new Error(JSON.stringify(error));
curl -X POST https://api.example.com/api/v1/ai-providers \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{
"project_id": "proj_ABC",
"name": "OpenAI GPT-4o",
"provider": "openai",
"default_model": "gpt-4o",
"secret_id": "sec_01"
}'
List providers in a project
- CLI
- SDK
- curl
soat list-ai-providers --project-id proj_ABC
// SDK
const { data, error } = await soat.aiProviders.listAiProviders({
query: { project_id: 'proj_ABC' },
});
if (error) throw new Error(JSON.stringify(error));
curl https://api.example.com/api/v1/ai-providers?project_id=proj_ABC \
-H "Authorization: Bearer <token>"
Set a per-provider price override
- CLI
- SDK
- curl
soat update-ai-provider-prices \
--ai-provider-id aip_ABC \
--prices '[{"model":"gpt-4o","input_price_per_m":5,"output_price_per_m":15,"effective_from":"2099-01-01T00:00:00.000Z"}]'
const { data, error } = await soat.aiProviders.updateAiProviderPrices({
path: { ai_provider_id: 'aip_ABC' },
body: {
prices: [
{
model: 'gpt-4o',
input_price_per_m: 5,
output_price_per_m: 15,
effective_from: '2099-01-01T00:00:00.000Z',
},
],
},
});
if (error) throw new Error(JSON.stringify(error));
curl -X PUT https://api.example.com/api/v1/ai-providers/aip_ABC/prices \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{
"prices": [
{
"model": "gpt-4o",
"input_price_per_m": 5,
"output_price_per_m": 15,
"effective_from": "2099-01-01T00:00:00.000Z"
}
]
}'