Copilot LLM errors
The Building OS Copilot talks to whichever LLM a building has connected in Building Settings → AI Assistant. Every failure on that path — a bad key, a throttled account, an unreachable self-hosted endpoint — is mapped to one of the stable codes below before it reaches the UI or the API. Users never see a raw provider response or a 500; they see the code's title and detail, plus a Learn more link to the section on this page.
How errors reach you
- In Building OS the Copilot panel renders the error card. The Recent calls panel in Building Settings → AI Assistant keeps the last calls per building with their provider, model, latency and error code.
- Over the API a failed chat request answers with the code's HTTP status and this body:
{
"code": "RATE_LIMITED",
"title": "Rate limited",
"detail": "The provider is throttling your requests. Wait a moment and try again, or upgrade your plan.",
"helpUrl": "https://developers.tango.vision/reference/llm-errors#rate-limited"
}code is stable and safe to branch on. title and detail are copy and may change. When the provider returns a JSON error body, detail carries the provider's own message so you do not have to open its console.
- Test connection (in the add-LLM wizard, or
POST …/llm-configs/:id/test) answers{ ok: false, errorCode, message }with the same codes.
| Code | HTTP status |
|---|---|
INVALID_API_KEY | 401 |
MODEL_NOT_FOUND | 404 |
RATE_LIMITED | 429 |
PROVIDER_UNREACHABLE | 502 |
PROVIDER_TIMEOUT | 504 |
PROVIDER_INTERNAL_ERROR | 502 |
UNSUPPORTED_FEATURE | 422 |
BAD_REQUEST | 400 |
NOT_CONFIGURED | 424 |
ENCRYPTION_UNAVAILABLE | 503 |
UNKNOWN | 500 |
Invalid API key
INVALID_API_KEY — the provider answered 401 or 403.
- Re-paste the key in Building Settings → AI Assistant. Keys are stored encrypted and never shown again, so a typo is invisible until the first call.
- Check the key belongs to the account and region of the base URL you entered. A key from one provider's EU console does not work against its US endpoint.
- Self-hosted endpoints (custom endpoints) that require no key still receive a placeholder bearer token; if your gateway rejects unknown tokens, configure it to accept the placeholder or issue a real key.
- Providers that authenticate with a header other than
Authorization: Bearer(Azure OpenAI'sapi-key, for example) need that header set viaextraHeaderswhen the config is created over the API.
Model not available
MODEL_NOT_FOUND — the provider answered 404.
- The model name is misspelled, retired, or not enabled for this account or tier. Pick one from the provider's model list.
- A wrong base URL also produces a 404. The platform appends
/chat/completionsto the base URL, sohttps://host/v1is right andhttps://hostorhttps://host/v1/chat/completionsis not. Fix the base URL before changing the model.
Rate limited
RATE_LIMITED — the provider answered 429.
- Wait and retry; most limits are per minute.
- Raise the account's quota or tier, or move the building to a key with its own quota. Several buildings sharing one key share one limit.
Provider unreachable
PROVIDER_UNREACHABLE — the platform could not open a connection at all (DNS lookup failed, connection refused or reset).
- Check the base URL host for typos.
- The request leaves from the platform's API server, not from your browser. A self-hosted model that answers on your laptop or inside a private network must be reachable from the cluster that runs
tv-api.localhostin a base URL refers to the API server, not to your machine. - Check the provider's status page and your outbound network rules.
Provider too slow
PROVIDER_TIMEOUT — no complete answer within the platform's limit (60 s per chat call, 10 s for the connection test), or the provider answered 408 or 504.
- Try a smaller or faster model, or a region closer to the cluster.
- Self-hosted models loading into memory on the first request often time out once and succeed afterwards; run Test connection twice before digging in.
Provider error
PROVIDER_INTERNAL_ERROR — the provider answered 5xx (other than 504).
- Usually transient; retry in a moment.
- If it persists, check the provider's status page. Nothing on the platform side changes this outcome.
Feature not supported
UNSUPPORTED_FEATURE — the provider or model lacks something the Copilot needs.
- The Copilot drives the building through tool calls (OpenAI-style function calling). Choose a model that supports it; many small self-hosted models do not.
- The native Anthropic, Gemini and GigaChat providers do not implement streaming yet. The Copilot uses non-streaming calls, so this only affects integrations that request streaming explicitly.
Invalid request
BAD_REQUEST — the provider rejected the request with another 4xx.
detailcarries the provider's own message when it sent one.- Typical causes: a parameter the model does not accept (temperature, max tokens), a request larger than the model's context window, or a gateway that expects a different request dialect. The call is listed in the Recent calls panel.
AI Assistant not configured
NOT_CONFIGURED — no LLM is connected to this building, and the platform has no fallback configured.
- Open Building Settings → AI Assistant and add a provider. The building's default config is the one the Copilot uses; a saved but non-default config is not enough.
- Platform operators can set an environment-level fallback (
LLM_PROVIDER,LLM_API_KEY,LLM_BASE_URL,LLM_MODELontv-api) that applies to every building without its own config.
LLM bridge offline
ENCRYPTION_UNAVAILABLE — the platform could not decrypt the stored API key.
- The API server's
LLM_CONFIG_ENCRYPTION_KEYis missing, or it changed after this config was saved. Keys encrypted with the old value cannot be read. - A building admin can recover by re-entering the API key in Building Settings → AI Assistant, which re-encrypts it with the current platform key. Ask your platform administrator if it keeps happening.
Unexpected error
UNKNOWN — a failure none of the mappings above recognised.
- The detail is recorded with the call in the Recent calls panel and in the API server's logs.
- Report it with the building id and the time of the call; every
UNKNOWNthat is reproducible becomes a named code.
Adding a code
Codes live in LlmErrorCode in tv-api (src/copilot/providers/), and each provider maps its wire-level failures to one of them. A new code needs, in the same change: the union member, its entry in ERROR_CATALOG with a helpUrl pointing at this page, and a section on this page whose anchor is the code in lower-kebab-case (PROVIDER_TIMEOUT → #provider-timeout). The catalog's tests check the URL shape; this page is what the link has to land on.