Adding a Vendor¶
Tokensor ships with OpenAI, Anthropic, and OpenCode provider integrations. The platform is designed so adding another key-based vendor takes a single registry entry in the backend and one in the frontend — no per-vendor routing logic, endpoints, or UI code.
This guide is for the Tokensor team (or contributors) extending the product.
How provider integrations work¶
A "provider" integration has two parts:
- A stored key — the user saves a provider API key on their account in onboarding or Settings. The key is stored (encrypted for
opencode) and surfaced back asconnected: true/false. - A proxy/ingest path — calls made through the transparent proxies or the ingest API get attributed to that account. This part is vendor-agnostic; the same
llm_callspipeline handles every model.
Because the ingest pipeline is shared, adding a vendor is only about admitting its key — which is why both registries are small lists.
1. Backend registry¶
Add an entry to SUPPORTED_KEY_PROVIDERS in backend/api/settings.py, mapping the provider id to the account column that stores its key:
SUPPORTED_KEY_PROVIDERS: dict[str, dict[str, str]] = {
"openai": {"attr": "openai_api_key"},
"anthropic": {"attr": "anthropic_api_key"},
"opencode": {"attr": "opencode_api_key"},
# "grok": {"attr": "grok_api_key"}, # your vendor
}
That immediately enables:
PUT /v1/settings/integrations/{provider}— save the keyDELETE /v1/settings/integrations/{provider}— clear the keyGET /v1/settings/integrations— the authoritative supported list for API consumers
There is no per-provider handler: the routes are consolidated and validate against the registry, so an unknown provider returns 404. (GET on a provider's key endpoint is 405 — it is not a read route; key status comes from GET /v1/settings/account.)
If the new vendor's key should be encrypted at rest, define the account column as
EncryptedSecretthe wayopencode_api_keyis inbackend/models/account.py(otherwise it is stored in plaintext likeopenai/anthropic).
2. Frontend registry¶
Add a matching entry to KEY_PROVIDERS in frontend/src/providers.ts:
export const KEY_PROVIDERS: ProviderConfig[] = [
{
id: 'openai',
label: 'OpenAI',
apiKeyLabel: 'OpenAI API Key',
placeholder: 'sk-proj-...',
snippet: ingestSnippet('gpt-4'),
},
// ... your vendor
]
Each entry carries the metadata the UI needs:
id— must match the backend registry idlabel— shown on the onboarding tab and the Settings sectionapiKeyLabel/placeholder— the key input fieldsnippet— the integration example shown in onboarding Step 2
That's it. The onboarding tabs, the Settings provider sections, and the save flow all render by mapping over KEY_PROVIDERS — nothing else changes.
What you get automatically¶
- Onboarding shows a new tab for the vendor, including its key field and integration snippet.
- Settings renders a new "Provider API Keys" section with save + status.
- The vendor's
llm_callsappear in the dashboard, analytics, and connection status with no further work.
Test both registries stay in sync¶
- Backend:
tests/api/test_settings_provider_keys.pyasserts the consolidated routes and the supported list. - Frontend:
Onboarding.test.tsxandSettings.test.tsxassert the tabs, labels, placeholders, and save calls are driven by the registry.
If you add a vendor to one registry and not the other, the backend 404s the missing provider at save time, so drift fails loudly rather than silently.