The Credentials screen supplies the provider keys that the TeleQuick voice runtime uses when it calls an LLM, an ASR engine, or a TTS engine. Without a resolvable key for the providers an agent has selected in its config, the agent fails at hydrate with no credentials for <provider>. The screen writes to two scopes and never reads a secret back.

The two scopes and the resolution order

The runtime reads the agent override first and falls back to the tenant default when the agent has no override for that provider. An agent with no override is not broken — it simply inherits the org-level key. In the This agent tab, a provider with no override shows uses tenant default rather than not set. Use an override when one agent must talk to a different OpenAI organisation, a different Deepgram project, or a different ElevenLabs voice library than the rest of the org. Both scopes are org-scoped procedures. For the agent scope, the server additionally reads agent_config and checks that agentId belongs to orgId before any write or delete; a mismatch returns NOT_FOUND (Agent not found in this org). admin.resolveAgentProviderCredentials reports which scope actually answered, via its source field.

Which providers accept a per-agent override

Only six providers are on the runtime’s per-agent resolve path: openai, anthropic, gemini, deepgram, elevenlabs, ollama The This agent tab lists exactly these. Every other provider in the table below is tenant-scope only — set it once under Tenant defaults and all agents in the org use it.

Required fields per provider

Each provider writes a flat payload object of string fields. Every field listed for a provider is required: the screen blocks a save with <field> required rather than letting a half-filled payload (an Azure key with no region, a PlayHT key with no user id) be stored and then fail later on a live call. The server independently validates the payload with validateProviderPayload and returns BAD_REQUEST if it does not pass. The payload shape is shared: the contact-centre credentials screen and the voice AI credentials screen write the same object for a given provider. The server persists whatever validated object it is given, so a client that invented its own field names would store credentials the runtime cannot read.

Write-only storage and what the mask shows

Keys are write-only from this screen. The secret fields of the payload are sealed under the owning org (sealProviderPayload(payload, orgId)) before being written to Redis — including for a per-agent override, which is sealed under the org, not the agent, so the engine (which knows the agent’s org) can open the hydrated config derived from it. The meta queries return only presence and a mask, never the secret:
  • admin.getProviderCredentialsMeta / admin.getProviderCredentialsMetaAll — tenant scope
  • admin.getAgentProviderOverrideMeta / admin.getAgentProviderOverrideMetaAll — agent scope
The screen uses the …All variants: one batched query per tab returns a map keyed by provider key, so a page load issues a single request rather than one per provider row. Each entry carries configured and, when available, masked. The screen renders the mask string (first/last characters of the stored value) as the row’s badge; if masked is an object it renders the first string value; if it is absent but configured is true it renders configured. You can recognise which key is stored, but you cannot read it back. To change a key, save a new one — Replace overwrites the stored value.

What happens when you save

For a tenant default (admin.setProviderCredentials):
  1. validateProviderPayload(provider, payload) runs. On failure the call returns BAD_REQUEST with the validator’s message.
  2. The filtered payload is sealed under orgId.
  3. It is written to provider_creds:{orgId}:{provider}.
For a per-agent override (admin.setAgentProviderOverride):
  1. The agent is looked up in agent_config by id + org_id; NOT_FOUND if it does not belong to the org.
  2. The payload is validated and sealed under the owning org.
  3. It is written to agent_provider_creds:{agentId}:{provider}.
  4. Re-hydration. hydrateAndPublish republishes the agent’s runtime config to agent:{agentId}:config, so the new key is in effect for the next call that originates. This step is best-effort — it is wrapped in a try/catch because the agent_config row may not exist yet during early setup. A hydration miss is recovered by the next explicit admin.hydrateAgent or by the next originate.
  5. Audit. An entry is recorded with action: 'set-credential', resourceType: 'agent_provider_override', and resourceId: "{agentId}:{provider}". The after payload contains only the field names that were written (for example ['openai_api_key']) — never the key bytes — so the trail proves a credential changed without exposing it to anyone with audit-read.
Note the asymmetry: writing a tenant default does not itself re-hydrate any agent. If you change a tenant key and want it reflected immediately, call admin.hydrateAgent for the affected agent. That procedure returns ready, missing_providers and hydrated_at_ms, and records an audit entry with action: 'hydrate' so you can later correlate a save with the hydration that followed it.

Removing a key or an override

Both removals are confirmed in the UI before they run. admin.removeProviderCredentials deletes provider_creds:{orgId}:{provider}. Every agent that was relying on that tenant default now has no credential for that provider unless it carries its own override. admin.removeAgentProviderOverride verifies the agent belongs to the org, deletes agent_provider_creds:{agentId}:{provider}, re-hydrates the agent (best-effort, as above), and records an audit entry with action: 'delete-credential'. Removing an override does not remove the tenant default — the agent falls back to it. Deleting an agent entirely is handled by admin.unpublishAgent, which wipes the hydrated config and sweeps the agent’s provider override keys for every provider, so a future agent that reuses the same id does not inherit ghost credentials.

Diagnosing missing credentials at hydrate

admin.resolveAgentProviderCredentials is the read-only check behind the “is this agent ready” warning. It loads the agent’s llm_provider, asr_provider and tts_provider, resolves credentials for the agent (override first, tenant default second), and returns: Only the providers the agent actually selected are required. The procedure deliberately does not report every unpopulated provider — telling an Anthropic user to fill in Gemini is noise. The slot values map to required credential fields as follows: Note that the Gemini credential is stored under the provider key gemini, while the agent config slot that requires it is google. To fix a no credentials for X failure:
  1. Confirm which providers the agent selected in its config (LLM, ASR, TTS).
  2. Open Credentials → Tenant defaults and check that the matching provider row shows a mask rather than not set.
  3. If the agent should use different keys, set them under This agent — available only for the six overridable providers, and only once an agent is selected.
  4. Call admin.hydrateAgent (the screen’s Save path on the agent config, or the explicit hydrate action) and check ready and missing_providers in the response before starting a test call.

Procedure reference