AI configuration
Per-user AI provider settings and the migration import endpoint.
AI settings are user-scoped (like /api/settings) — they follow
the signed-in user across clinics. Provider API keys are encrypted at rest and never
returned by the API.
| Method | Path | Auth | Returns |
|---|---|---|---|
GET | /api/ai/config | Signed in | Non-secret config + apiKeySet |
PUT | /api/ai/config | Signed in | The saved config |
POST | /api/ai/test | Signed in | { ok, message } |
POST | /api/ai/import | patient:write | { created, failed } |
POST | /api/ai/import/validate | patient:write | { valid, invalid, total } |
GET | /api/ai/policy | Member of clinic | The clinic's AI policy |
PUT | /api/ai/policy | Owner/admin | The saved policy |
The config object
{
"config": {
"mode": "local",
"provider": "anthropic",
"ollamaBaseUrl": "http://localhost:11434",
"ollamaModel": "llama3.1",
"defaultModel": "claude-sonnet-4-6",
"defaultEffort": "medium",
"veilLevel": "full",
"apiKeySet": { "openai": false, "anthropic": true, "gemini": false }
}
}mode— how a model is chosen:"api"(a cloud provider key),"local"(Ollama on your own infrastructure),"auto"(use a cloud key when one is set, otherwise fall back to local Ollama), or"off"(the assistant is disabled). New installs default to"auto".provider— the active cloud provider:"openai","anthropic", or"gemini".veilLevel— Veil de-identification strictness:"full","names", or"off".apiKeySet— which providers have a stored key. The keys themselves are never returned.
Saving config
PUT accepts any subset of the fields above, plus an optional apiKey (plaintext)
for the currently selected provider — it is encrypted before storage and used only to
call the provider. Send "apiKey": "" to clear it.
curl -b cookies.txt -X PUT http://localhost:4000/api/ai/config \
-H "Content-Type: application/json" \
-d '{ "mode": "api", "provider": "anthropic", "apiKey": "sk-ant-…" }'Testing connectivity
POST /api/ai/test does a lightweight probe before you rely on a setting: for local
mode it pings Ollama's /api/tags; for API mode it confirms a key is stored (it does
not spend a token).
curl -b cookies.txt -X POST http://localhost:4000/api/ai/test \
-H "Content-Type: application/json" \
-d '{ "mode": "local", "ollamaBaseUrl": "http://localhost:11434" }'Clinic-wide AI policy
Unlike the per-user config above, the AI policy is clinic-scoped (org-wide) and only
owners/admins can change it. It is the kill-switch behind the chat:
when AI is off for a caller, POST /api/chat returns 403, and the frontend hides the AI
page and sidebar entry.
{
"aiEnabled": true,
"disabledForEmployees": false
}aiEnabled— master switch.falsedisables the AI for everyone in the clinic.disabledForEmployees— whentrue(andaiEnabledistrue), the AI is hidden and blocked for staff; owners and admins keep access.
GET is readable by any member (the app needs it to gate the UI); PUT requires
owner/admin and returns the saved policy.
curl -b cookies.txt -X PUT http://localhost:4000/api/ai/policy \
-H "Content-Type: application/json" \
-d '{ "aiEnabled": true, "disabledForEmployees": true }'Importing records
POST /api/ai/import commits patient records the clinician approved in the chat
import preview (see the chat agent). The body is { "records": [...] }
of patient objects in temetro's patient shape. The server
re-validates every record and writes via the audited patient service — the AI never
writes to the database directly. Imported records are stamped source: "ai" (surfaced as
an "Added by AI" badge), gaps are filled with safe placeholders, and a missing
fileNumber is auto-assigned — so only rows with no usable identity (e.g. no name) or a
duplicate file number land in failed.
{
"created": ["10293", "10294"],
"failed": [{ "fileNumber": "10295", "error": "name: Patient name is required." }]
}POST /api/ai/import/validate is a dry run — it runs the same validation and returns
{ valid, invalid, total } (each invalid entry includes the original record) without
writing anything. The "Review & edit before import" UI calls it to refresh which rows are
ready as the clinician edits them.