AI Provider & Model Management

Atamaia's routing layer is multi-provider and multi-model. It is not a thin proxy. Agents, chat sessions, and councils ask for a model for a role and get a working connection, or a refusal. The caller does not need to know whether the model is Claude via OpenRouter, a local llama.cpp server, or a floating alias.

A registered model is not automatically usable. The flags below are separate on purpose.

Canonical host: https://api.atamaia.ai. All endpoints require authentication. Responses are wrapped in ApiEnvelope<T>.

The reason this layer exists is Model freedom. Lock-in is Provider portability.

The four fields

Field Owner Question
enabled Human Should this model be routable at all?
available Sync Is the provider serving it right now?
approvedForAgent Human May autonomous runs use it?
approvedForChat Human May interactive chat use it?

Dispatch requires enabled and available. Agent role resolution also requires approvedForAgent. The locked default must be all three, and its provider must be enabled.

available defaults to true, including for providers that never push a roster. Absence of a roster is not an assertion of absence.

Roster sync never writes enabled. A model that returns to the roster becomes routable again on its own. A human disable survives the next sync.

Resolve a model with GET /api/ai/resolve/{modelId}. A disabled model returns HTTP 400, errorCode: INVALID_OPERATION. A broken role route is refused. It is not quietly replaced with another provider.

How a call is routed

A fully qualified model id is {provider.Prefix}:{modelId}. The OpenRouter prefix is openrouter.

Resolve the model, reject it if it is not routable, pick a healthy provider, attach credentials, call, failover on retriable errors, and record the result for the circuit breaker.

Providers

Type What it is
OpenRouter OpenRouter meta-router
LocalLlamaCpp Direct llama.cpp server
Anthropic Anthropic API
OpenAI OpenAI API, also the compat type for other OpenAI-shaped endpoints
Custom Any OpenAI-compatible endpoint
AnthropicAgentSdk Claude Agent SDK subprocess
GET    /api/ai/providers
POST   /api/ai/providers
PATCH  /api/ai/providers/{id}
DELETE /api/ai/providers/{id}
GET    /api/ai/providers/{id}/catalog
POST   /api/ai/providers/{id}/models/sync
POST   /api/ai/providers/{id}/sync-key

Create body: name, type, prefix, baseUrl, apiKey, defaultModel, priority, timeoutSeconds, stripPrefixInRequests, configJson. The raw key is never returned.

Models

GET    /api/ai/models
POST   /api/ai/models
PATCH  /api/ai/models/{id}
DELETE /api/ai/models/{id}

Human sets enabled, approvedForAgent, and approvedForChat. Sync sets available. Sync must not send approval flags. isLockedDefault is at most one model per tenant. Sampler null means the upstream did not say, not zero.

Create does not take enabled, available, or isLockedDefault. Those are PATCH or sync.

Role routes

Routes map a role (builder, scribe, tester, planner, researcher, reviewer, designer, orchestrator, and extras such as chat, coding, council, editor) to a preferred model.

GET    /api/ai/routes
POST   /api/ai/routes
PATCH  /api/ai/routes/{id}
DELETE /api/ai/routes/{id}

PATCH body: providerId, modelId, clearModel, role, priority, notes.

A route may have no model, which means use the provider default. That is different from a route whose model was deleted. A broken route sets isBroken and brokenReason, and clears them when the model recovers. Resolution refuses a broken route. Repointing it is a PATCH.

Locked default

One model per tenant may be isLockedDefault. It is the last resort when a role's own model is unusable. It cannot be deleted or disabled while it holds that flag. Setting it on a new model clears it from the previous one. It must be enabled, available, and approved for agents, and its provider must be enabled.

Resolution order

GET /api/ai/resolve/{modelId}

  1. Exact match on modelId or prefix:modelId.
  2. Provider prefix, split on :.
  3. For a role: highest-priority non-broken route whose model is routable.
  4. Locked default, if usable.
  5. Otherwise refuse. There is no silent substitute.

Agent runs: explicit run modelId, then role route, then locked default.

Chat and streaming

POST /api/ai/chat takes modelId, message, optional systemPrompt, sampler overrides, and tools. POST /api/ai/broadcast sends the same message to several models.

Streams use Open Responses events (response.created, output_text.delta, response.completed). Clients do not need to know which provider answered.

Circuit breaker

Three consecutive failures open the circuit. The provider is skipped for five minutes, then the next request tests it. Retriable errors are HTTP 408, 429, 5xx, and transport timeouts. Failover candidates must be enabled, available, approved for agents, and on a healthy provider.

Roster push

Local inference hosts push their model list. Atamaia does not poll them.

POST /api/ai/providers/{providerId}/models/sync
Header: X-Provider-Sync-Key: <key>

Auth is that header, or a bearer token with AIModelManage. Issue a key with POST /api/ai/providers/{id}/sync-key.

additive defaults to false, which replaces the roster. dryRun plans the change without writing it. An empty model list with additive: false marks every model for that provider unavailable. That is not a no-op. Use dryRun: true first.

Send modelId, contextLength, localModelPath, isLocal, capabilities, and sampler settings. Do not send approval flags.

Model groups, credentials, pricing

Model groups are named rosters: GET/POST/PATCH/DELETE /api/ai/model-groups.

Tenant keys live at /api/ai/credentials. The global catalogue is GET /api/ai/catalog. POST /api/ai/sync-pricing pulls OpenRouter per-token prices. Keys are encrypted at rest.

cost = (promptTokens * inputCostPer1M / 1_000_000) + (completionTokens * outputCostPer1M / 1_000_000)

Local models typically have zero cost.

Limits

A provider that stops serving a model without a sync event is not detected until something touches that model. There is no periodic reconcile sweep.

Model updates, deletions, and roster syncs write audit_events, including refused attempts.

Last updated