AI writing assistant
The campaign composer can draft and edit messages with a model your organization connects: OpenAI, Anthropic, Gemini, DeepSeek, OpenRouter, or one you run yourself. The assistant never sends anything. You review each result and choose whether to use it.
Connect a provider#
- 1
Open Settings → AI providers
Owners and admins (theorg:managepermission) choose Connect provider. - 2
Pick a provider and paste a key
The built-in providers already know their endpoint. Get a key opens the provider's key page. The key is stored encrypted; Oqim shows only its last characters afterwards. - 3
Pick a model
Load models asks the provider for its chat models, with search. You can also type a model ID. - 4
Connect and test
Oqim saves the provider, sends a tiny request and shows Connected or the provider's error. The first provider becomes the default; Make default changes it.
In the campaign builder, the AI button in the message editor opens the assistant: write a draft from a brief, improve, shorten, make it friendlier or more formal, fix spelling and grammar, or translate to English, Русский or Oʻzbekcha. The result appears in a Telegram-style preview; Replace message or Insert at cursor puts it in the editor. People who can edit campaigns (campaigns:manage) can use it and switch between connected providers.
Supported providers#
- OpenAI requests use
max_completion_tokens, which its reasoning models require; the others usemax_tokens. When a server refuses one of these parameters, Oqim adjusts the request and tries again, at most three times. temperatureis left out for models that reject it: OpenAI's o-series and GPT-5 models, and Claude, whose Opus 4.7 and later models refuse sampling parameters.- OpenRouter requests carry its optional attribution headers:
HTTP-Referer(yourPUBLIC_APP_URL) and the titleOqim. - Where a provider lists every kind of model (OpenAI, Gemini, OpenRouter), the model picker shows only chat models.
Custom endpoints and self-hosted models#
Custom connects any server that speaks OpenAI Chat Completions (POST {base}/chat/completions, GET {base}/models) or Anthropic Messages (POST {base}/messages). Enter the base URL: the part before /chat/completions. A key is optional.
A model on the same server
Inside Docker, localhost is the API container itself, so Oqim refuses it. Both compose files map host.docker.internal to the Docker host for the API service:
A model on the same machine is then reachable at http://host.docker.internal:11434/v1 (Ollama). That is a private address, so a platform administrator must first turn on Allow private network endpoints (see below). The model server must listen on an address Docker can reach: the Docker bridge (usually 172.17.0.1) keeps it off the internet, while 0.0.0.0 needs a firewall rule.
What is sent to providers#
How the assistant writes#
- It keeps
{{variables}}and{{opt_out_url}}exactly as written. Oqim warns when a result drops one or adds one your recipients may not have. - It never invents facts, prices, dates or links: missing details become placeholders such as [date] or [price].
- It writes only the message, in the composer's format (Formatted, HTML or Plain) and under 4,096 characters. Oqim warns when a result is longer.
- It declines phishing, impersonation, deception, harassment and messages to people who didn't opt in. The API answers
422 ai_refusedwith the reason. - It doesn't produce per-recipient variations or spintax. Varying copies to get past spam detection is filter evasion, which Oqim doesn't support.
Private endpoints#
Organizations choose custom URLs, and the API runs next to Postgres, Redis and the worker. Oqim therefore checks every address when it connects, after the name is resolved, so a name can't later point somewhere internal.
- Only http and https URLs, without a username or password in them.
- Always refused: loopback (
127.0.0.0/8,::1), link-local including cloud metadata (169.254.0.0/16,fe80::/10), unspecified, multicast and other reserved addresses, and this stack's service names: postgres, redis, worker, api, web, edge, scheduler, oqim-* and localhost. - Refused unless allowed by a platform administrator: private ranges
10.0.0.0/8,172.16.0.0/12,192.168.0.0/16,100.64.0.0/10andfc00::/7, includinghost.docker.internal. Ports 5432, 6379 and 8090 on private addresses stay refused. - Redirects to another host aren't followed, and HTTP proxy settings aren't used.
- The built-in providers always go to their fixed public endpoints.
Keys and security#
- API keys are encrypted with
APP_KEYlike proxy passwords, bound to their organization and provider, and never returned. The console shows only the last characters, like•••• 1a2b. - A saved key is only sent to the endpoint it was saved for: changing the provider or base URL needs the key again.
- Keys, briefs, messages and results aren't logged. Provider errors are shortened to a readable message; response bodies are never shown or stored.
- Connecting, changing (including key rotation) and removing providers is in the audit log as
AI_PROVIDER_ADDED,AI_PROVIDER_UPDATEDandAI_PROVIDER_REMOVED. Individual compose requests aren't audited. - Requests time out after 45 seconds (20 for model lists); responses over 2 MB (8 MB for model lists) are refused.
Rate limits and usage#
Windows are UTC hours. Over a limit, the API answers 429 ai_rate_limited with Retry-After in seconds. Your provider's own limits apply as well. Compose usage is metered per UTC day as ai_requests, ai_input_tokens and ai_output_tokens in GET /organization/usage.
Errors#
Endpoints#
All under /api/v1; see the API reference for bodies and responses.