AI Providers¶
Comaney's AI features (AI Express Creation, the dashboard card assistant, Catalog Partnership mapping and Unclassified Expenses) need an AI service. You pick it with AI_PROVIDER:
AI_PROVIDER |
Service | Data leaves your server | Reads receipt photos |
|---|---|---|---|
claude |
Claude by Anthropic | yes | yes |
deepseek |
DeepSeek | yes | depends on the model |
local |
A model on any OpenAI-compatible server, your own or someone else's | not if the model runs on your own server | if you say so |
none or unset |
AI switched off |
Each service has its own settings, all starting with AI_PROVIDER_<NAME>_.
How users get access (details in each section):
- Shared access (all services): the access you configure for the instance. Every user without their own key uses it, limited per user by
AI_TRIAL_USAGE_LIMIT. See Shared AI Access. Forlocalit can be switched off. - Own keys (all services): users can enter their own key in their account settings; it wins over shared access and is not limited by Comaney. Without shared access, only these users can use AI.
When you switch to another service, users' saved settings of the previous one are deleted automatically (see purge_inactive_ai_config).
Claude (claude)¶
Claude by Anthropic, through Anthropic's API.
Options¶
| Variable | Required | Default | Meaning |
|---|---|---|---|
AI_PROVIDER_CLAUDE_API_KEY |
no | (empty) | Shared API key from console.anthropic.com. See Authentication. |
AI_PROVIDER_CLAUDE_MODEL |
no | claude-sonnet-5-5 |
Model. Comaney knows the prices of claude-sonnet-5-5; any other model also needs both prices below, otherwise the site shows a configuration error. |
AI_PROVIDER_CLAUDE_MAX_TOKENS |
no | 8192 |
Upper limit for the length of one answer (whole number). |
AI_PROVIDER_CLAUDE_EFFORT |
no | medium |
How much the model thinks: low, medium, high, xhigh or max. Higher is slower and costs more. |
AI_PROVIDER_CLAUDE_PRICE_INPUT |
no | (built in) | Input price in USD per million tokens. Set together with _PRICE_OUTPUT or not at all. |
AI_PROVIDER_CLAUDE_PRICE_OUTPUT |
no | (built in) | Output price in USD per million tokens. |
AI_TRIAL_API_KEY is the deprecated old name of AI_PROVIDER_CLAUDE_API_KEY; it still works with AI_PROVIDER=claude and logs a warning.
Authentication¶
- Shared key:
AI_PROVIDER_CLAUDE_API_KEY. Users without a key of their own use it, limited byAI_TRIAL_USAGE_LIMIT. Empty: no shared access. - Own key per user: the account settings show the field Anthropic API key under "AI". A user's own key always wins over the shared key and is not limited by Comaney.
- The key is sent the way Anthropic's API expects it (
x-api-keyheader, via Anthropic's SDK).
Images¶
Receipt photos work with every supported Claude model.
Cost¶
Computed from the token usage Anthropic reports, at the model's prices (or your price override). If the safety system declines a request, Anthropic may retry it on a fallback model; each attempt is billed at its own model's price.
Example¶
environment:
AI_PROVIDER: claude
AI_PROVIDER_CLAUDE_API_KEY: "sk-ant-..." # omit: only users with their own key
AI_TRIAL_USAGE_LIMIT: 10
DeepSeek (deepseek)¶
DeepSeek's hosted API at https://api.deepseek.com (OpenAI-compatible).
Options¶
| Variable | Required | Default | Meaning |
|---|---|---|---|
AI_PROVIDER_DEEPSEEK_API_KEY |
no | (empty) | Shared API key from platform.deepseek.com. See Authentication. |
AI_PROVIDER_DEEPSEEK_MODEL |
no | deepseek-flash |
Model. Comaney knows the prices of deepseek-flash and deepseek-v4-pro; any other model also needs both prices below. |
AI_PROVIDER_DEEPSEEK_MAX_TOKENS |
no | 8192 |
Upper limit for the length of one answer (whole number). |
AI_PROVIDER_DEEPSEEK_PRICE_INPUT |
no | (built in) | Input price in USD per million tokens. Set together with _PRICE_OUTPUT or not at all. |
AI_PROVIDER_DEEPSEEK_PRICE_OUTPUT |
no | (built in) | Output price in USD per million tokens. |
AI_PROVIDER_DEEPSEEK_SUPPORTS_VISION |
no | (by model) | true or false: whether the model reads images. Empty: Comaney decides by the model name. |
Authentication¶
- Shared key:
AI_PROVIDER_DEEPSEEK_API_KEY. Users without a key of their own use it, limited byAI_TRIAL_USAGE_LIMIT. Empty: no shared access. - Own key per user: the account settings show the field DeepSeek API key under "AI". A user's own key always wins over the shared key and is not limited by Comaney.
- The key is sent as
Authorization: Bearer <key>.
Images¶
Receipt photos work with deepseek-flash only; with any other model the photo upload is hidden. AI_PROVIDER_DEEPSEEK_SUPPORTS_VISION overrides this, e.g. for a newer vision model.
Cost¶
Computed from the token usage DeepSeek reports, with cached input billed at DeepSeek's lower cache price. DeepSeek halves its prices outside peak hours; Comaney always counts the peak price, so users' budgets are never undercharged. A price override bills cached input at the input price.
Example¶
environment:
AI_PROVIDER: deepseek
AI_PROVIDER_DEEPSEEK_API_KEY: "sk-..." # omit: only users with their own key
AI_TRIAL_USAGE_LIMIT: 10
Self-hosted model (local)¶
Any server that offers the OpenAI-compatible Chat Completions API (POST <base URL>/chat/completions): a model on your own hardware, or an OpenAI-compatible endpoint someone runs for you (for example your organisation's AI gateway).
Options¶
| Variable | Required | Default | Meaning |
|---|---|---|---|
AI_PROVIDER_LOCAL_BASE_URL |
yes | Base URL including the version part, e.g. http://host.docker.internal:11434/v1 or https://ai.example.org/v1. Must start with http:// or https://. |
|
AI_PROVIDER_LOCAL_MODEL |
yes | Model name exactly as the server knows it (e.g. qwen3.8:27b, or an alias such as medium on a gateway). |
|
AI_PROVIDER_LOCAL_API_KEY |
no | (empty) | Key for servers that require one. See Authentication. |
AI_PROVIDER_LOCAL_SHARED_ACCESS |
no | true |
false: only users with their own API key can use AI. See Authentication. |
AI_PROVIDER_LOCAL_SUPPORTS_VISION |
no | false |
true turns on the receipt photo upload. Set it only if the model and server read images. |
AI_PROVIDER_LOCAL_CENTS_PER_MTOK |
no | 0 |
Cost in US cents per million tokens, input and output alike, counted against users' budgets. |
AI_PROVIDER_LOCAL_MAX_TOKENS |
no | 8192 |
Upper limit for the length of one answer (whole number). |
AI_PROVIDER_LOCAL_TIMEOUT |
no | 600 |
Seconds to wait for an answer. Models on modest hardware are slow, and the first request after a pause also loads the model. |
Authentication¶
- Without authentication: leave
AI_PROVIDER_LOCAL_API_KEYempty. Requests carry noAuthorizationheader at all. Only for servers reachable from the Comaney container alone, never from the internet. - With authentication: set
AI_PROVIDER_LOCAL_API_KEY; it is sent asAuthorization: Bearer <key>. Use the key the server was started with (e.g.llama-server --api-key,vllm serve --api-key), the token you created in the server (LM Studio), or the key the endpoint's operator gave you. For a server without its own key check, put a reverse proxy with authentication in front of it (see Common servers). - Shared access: with
AI_PROVIDER_LOCAL_SHARED_ACCESS=true(default), every user without a key of their own uses the server withAI_PROVIDER_LOCAL_API_KEY(or without a key if it is empty), limited per user byAI_TRIAL_USAGE_LIMIT(0for no limit). Comaney cannot tell whether a server needs a key, so this switch decides it. - Own key per user: the account settings show the field API key under "AI". A user's own key always wins over
AI_PROVIDER_LOCAL_API_KEYand is not limited by Comaney. Useful for gateways that hand out a key per person, each with its own quota. - Only own keys: set
AI_PROVIDER_LOCAL_SHARED_ACCESS=falsefor a gateway without a key for the whole instance. Otherwise users without their own key reach the server unauthenticated, the rejection counts as a broken shared key, shared access is switched off and you get an email. - A wrong or missing key shows users "Self-hosted model rejected the API key".
Images¶
Whether receipt photos work depends on the model and the server (some need an extra vision component, e.g. --mmproj for llama-server). Images are sent as base64 data URIs in image_url parts, so a gateway must accept those. Set AI_PROVIDER_LOCAL_SUPPORTS_VISION=true only after checking; otherwise the upload stays hidden.
Cost¶
AI_PROVIDER_LOCAL_CENTS_PER_MTOK times the tokens the server reports (prompt_tokens + completion_tokens, which usually includes the model's thinking). A server that reports no token usage costs 0 (logged once). With the default 0, usage is still recorded but costs nothing.
Common servers¶
The table lists typical defaults; check the documentation of the version you run.
| Server | Typical base URL | Requiring an API key |
|---|---|---|
llama.cpp (llama-server) |
http://<host>:9931/v1 |
--api-key <key> (or LLAMA_API_KEY) |
| Ollama | http://<host>:11434/v1 |
No built-in key check; put a reverse proxy with authentication in front |
| LM Studio | http://<host>:1234/v1 |
Server Settings: Require Authentication, then create a token under Manage Tokens |
vLLM (vllm serve) |
http://<host>:8000/v1 |
--api-key <key> |
| SGLang | http://<host>:30000/v1 |
--api-key <key> |
MLX-LM (mlx_lm.server) |
http://<host>:8080/v1 |
No built-in key check; reverse proxy |
| LocalAI | http://<host>:8080/v1 |
LOCALAI_API_KEY (or --api-keys) |
| Docker Model Runner | http://<host>:12434/engines/v1 |
Ignores keys; reverse proxy |
Where the server runs¶
- Same host, server outside Docker: use
http://host.docker.internal:<port>/v1. On Linux, addextra_hosts: ["host.docker.internal:host-gateway"]to thewebservice and let the server listen on an address the container can reach (not only127.0.0.1). - Mac: Docker on macOS cannot give containers access to the GPU. Run the model server natively on the Mac (e.g.
llama-serveror Ollama installed with Homebrew) or with Docker Model Runner, and point Comaney at it as above.
Examples¶
Without authentication, a server on the Docker host:
environment:
AI_PROVIDER: local
AI_PROVIDER_LOCAL_BASE_URL: "http://host.docker.internal:11434/v1"
AI_PROVIDER_LOCAL_MODEL: "qwen3.8:27b"
AI_PROVIDER_LOCAL_SUPPORTS_VISION: "true"
AI_TRIAL_USAGE_LIMIT: 0 # no per-user limit
extra_hosts:
- "host.docker.internal:host-gateway"
With authentication, a server started with its own key:
environment:
AI_PROVIDER: local
AI_PROVIDER_LOCAL_BASE_URL: "http://host.docker.internal:9931/v1"
AI_PROVIDER_LOCAL_MODEL: "qwen3.8"
AI_PROVIDER_LOCAL_API_KEY: "a-long-random-string" # the server's --api-key
extra_hosts:
- "host.docker.internal:host-gateway"
With authentication, an OpenAI-compatible endpoint run by someone else:
environment:
AI_PROVIDER: local
AI_PROVIDER_LOCAL_BASE_URL: "https://ai.example.org/v1"
AI_PROVIDER_LOCAL_MODEL: "medium" # the operator's model name or alias
AI_PROVIDER_LOCAL_API_KEY: "sk-..." # from the operator
AI_PROVIDER_LOCAL_CENTS_PER_MTOK: 50 # what the operator charges, if anything
A gateway that hands out a key per person, no key for the whole instance: every user enters their own key in their account settings.
environment:
AI_PROVIDER: local
AI_PROVIDER_LOCAL_BASE_URL: "https://ai.example.org/v1"
AI_PROVIDER_LOCAL_MODEL: "medium"
AI_PROVIDER_LOCAL_SHARED_ACCESS: "false" # only users with their own key
Adding a community provider¶
A provider is one Python file. Mount it into the container and select it by its file name:
services:
web:
volumes:
- ./openrouter.py:/app/ai_providers/openrouter.py:ro
environment:
AI_PROVIDER: openrouter
AI_PROVIDER_OPENROUTER_API_KEY: "..."
The provider's own documentation lists its AI_PROVIDER_<NAME>_* settings. A provider file runs as part of Comaney with access to everything Comaney can reach, including all environment variables and the database connection; mount only files you trust. A file that cannot be loaded, or that rejects its settings, takes the site down with a configuration error page. Writing one: Writing an AI provider.