Providers
Providers are the AI services that power each stage of the voice pipeline. VOCALS supports swappable providers for three types: STT (speech-to-text), LLM (language model), and TTS (text-to-speech).
Each provider configuration stores an encrypted API key, the selected model, and optional provider-specific settings. Agents reference provider IDs to define their pipeline.
Available Providers
| Type | Name | Description |
|---|---|---|
stt | deepgram | Deepgram Nova real-time STT |
stt | openai | OpenAI Whisper API STT |
stt | whisper | OpenAI Whisper (local) STT |
stt | elevenlabs | ElevenLabs STT |
stt | qwen | Alibaba Qwen STT |
stt | fish | Fish Audio STT |
llm | openai | OpenAI GPT models |
llm | claude | Anthropic Claude models |
llm | google | Google Gemini models |
llm | kimi | Moonshot Kimi models |
tts | deepgram | Deepgram TTS |
tts | openai | OpenAI TTS |
tts | elevenlabs | ElevenLabs TTS |
tts | qwen | Alibaba Qwen TTS |
tts | resemble | Resemble AI TTS |
tts | fish | Fish Audio TTS |
tts | google | Google Cloud TTS (Neural2/WaveNet, Chirp3-HD, Studio) |
tts | google_gemini | Google Gemini TTS (Gemini API) |
google and google_gemini are different providersThey target different Google APIs and are not interchangeable. google calls Cloud Text-to-Speech and takes a Cloud Text-to-Speech API key; google_gemini calls the Gemini API and takes a Gemini API key, which is billed separately. A key for one is rejected by the other. See Provider Integration for the full comparison.
Google Gemini TTS models
google_gemini accepts one of three model_id values:
model_id | Notes |
|---|---|
gemini-2.5-flash-preview-tts | Default. Low latency, cost-efficient - the model to use for telephony. |
gemini-2.5-pro-preview-tts | Higher quality, aimed at podcast/audiobook output. |
gemini-3.1-flash-tts-preview | Tuned for expressive narration. |
Reproduce these ids exactly. The 2.5 models end -preview-tts while the 3.1 model ends -tts-preview; the inconsistency is Google's, and a "corrected" id returns 404 at synthesis time. All three are Preview models and Google may change the ids when they reach stable.
The voice is set on the agent as a bare prebuilt name (for example Zephyr), not a fully-qualified locale-prefixed id, and google_gemini takes no language parameter - the spoken language follows the agent's text.
List Providers
GET /providers
Returns all provider configurations for the current tenant, ordered by type and name.
Response
[
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"type": "stt",
"name": "deepgram",
"model_id": "nova-2",
"extra_config": null,
"is_active": true,
"created_at": "2026-01-15T10:30:00Z",
"updated_at": "2026-02-20T14:00:00Z"
}
]
Response Fields
| Field | Type | Description |
|---|---|---|
id | uuid | Provider config ID |
type | string | stt, llm, or tts |
name | string | Provider name (e.g. deepgram, openai) |
model_id | string | Selected model identifier |
extra_config | object | null | Provider-specific configuration |
is_active | boolean | Whether the provider is active |
created_at | datetime | Creation timestamp |
updated_at | datetime | Last update timestamp |
The API key is never returned in responses.
Get Provider
GET /providers/{provider_id}
Returns a single provider configuration.
Response
Same schema as the list item above.
Create Provider
POST /providers
Request Body
{
"type": "stt",
"name": "deepgram",
"api_key": "dg_live_abc123...",
"model_id": "nova-2",
"extra_config": { "language": "en" },
"is_active": true
}
Request Fields
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
type | string | Yes | -- | Provider type: stt, llm, or tts |
name | string | Yes | -- | Provider name (must be a registered provider) |
api_key | string | Yes | -- | Provider API key (encrypted before storage) |
model_id | string | Yes | -- | Model identifier to use |
extra_config | object | No | null | Provider-specific settings |
is_active | boolean | No | true | Whether the provider is active |
Response
201 Created -- Returns the ProviderResponse object.
If the type is not stt, llm, or tts, the API returns 400. If the name is not a registered provider for the given type, the API returns 400 with the list of available providers.
Google Gemini default: thinking_budget
Creating an LLM provider with name: "google" and no thinking_budget in extra_config stores "extra_config": { "thinking_budget": 0 }, which asks for the least extended reasoning the selected model allows: off entirely on older Gemini models, and the lowest reasoning level on Google's current generation, which cannot switch it off. Reasoning adds seconds before the first word is spoken, and a caller hears that as silence. Send your own thinking_budget in extra_config to keep reasoning on (for example { "thinking_budget": 1024 }); the value you send is always stored as-is.
This applies to newly created providers only. Existing providers are never modified, and PUT /providers/{provider_id} never adds the field.
Update Provider
PUT /providers/{provider_id}
Update a provider's API key, model, configuration, or active status. Only include the fields you want to change.
Request Body
{
"api_key": "new_key_here",
"model_id": "nova-2-general",
"is_active": true
}
Request Fields
| Field | Type | Required | Description |
|---|---|---|---|
api_key | string | No | New API key (re-encrypted) |
model_id | string | No | New model identifier |
extra_config | object | No | Updated provider-specific settings |
is_active | boolean | No | Enable or disable the provider |
Response
200 OK -- Returns the updated ProviderResponse.
Delete Provider
DELETE /providers/{provider_id}
Permanently deletes a provider configuration.
The delete is refused while any agent still references the provider, so no agent is ever detached as a side effect. Repoint or delete those agents first, then retry.
Response
204 No Content -- the provider was deleted.
409 Conflict -- one or more agents still use this provider. The body names them:
{
"detail": {
"error_code": "provider_in_use",
"message": "This provider is still used by Demo Restaurant, Reception Bot. Point those agents elsewhere or delete them, then try again.",
"agents": [
{ "id": "550e8400-e29b-41d4-a716-446655440000", "name": "Demo Restaurant" },
{ "id": "6ba7b810-9dad-11d1-80b4-00c04fd430c8", "name": "Reception Bot" }
]
}
}
| Field | Type | Description |
|---|---|---|
error_code | string | provider_in_use |
message | string | Human-readable summary, naming up to five agents |
agents | array[object] | Every agent blocking the delete, as id + name |
403 Forbidden -- the provider is managed by VOCALS and cannot be deleted here.
List Available Models
GET /providers/{provider_id}/models
Fetches the list of available models for a saved provider using its stored API key. Queries the provider's API when possible, or returns a hardcoded list for providers that do not support model enumeration.
Response
{
"models": ["nova-2", "nova-2-general", "nova-2-meeting"],
"source": "api"
}
| Field | Type | Description |
|---|---|---|
models | array[string] | Available model identifiers |
source | string | api (fetched from provider), hardcoded (static list), or error (fetch failed) |
For google_gemini this returns the three Gemini TTS model ids with the default first and source: "hardcoded" - the list is static, so no call is made to Google.
List Google Voices
GET /providers/{provider_id}/google-voices
Returns the structured Google Cloud TTS voice catalogue for a saved Google TTS provider, used to populate the agent voice picker. The Neural2/WaveNet/Chirp3-HD voices are fetched live from the tenant's own Google Cloud project (scoped to the stored API key). The provider must be a tts provider named google owned by the current tenant.
Response
{
"voices": [
{
"id": "en-US-Chirp3-HD-Charon",
"label": "Charon (Female)",
"tier": "chirp",
"language": "en-US",
"gender": "Female"
}
],
"source": "live"
}
Response Fields
| Field | Type | Description |
|---|---|---|
voices | array[object] | Voice entries for the picker |
voices[].id | string | Fully-qualified voice identifier to store as the agent's voice_id |
voices[].label | string | Human-readable label for display |
voices[].tier | string | neural2, wavenet, or chirp |
voices[].language | string | BCP-47 locale (one of the seven supported: en-US, en-GB, es-ES, fr-FR, de-DE, pt-BR, it-IT) |
voices[].gender | string | Male, Female, Neutral, or empty when unspecified |
source | string | live (fetched from Google), cache (served from a recent fetch), or error (live fetch failed; voices is empty and the caller should fall back to its own static list) |
A source of error is not an HTTP failure - the endpoint still returns 200 with an empty voices array so the picker can degrade gracefully to its own static list. Studio voices are never returned (they are no longer selectable for new voices).
Returns 404 if the provider is not found for the tenant or is not a Google Cloud TTS provider. This endpoint is Cloud-TTS-only: a google_gemini provider also returns 404, because its 30 prebuilt voices are a fixed, model-defined list with no per-tenant catalogue to fetch.
Fetch Models (Ad-hoc)
POST /providers/models
Fetch available models without saving a provider configuration. Useful for testing an API key before creating a provider.
Request Body
{
"type": "stt",
"name": "deepgram",
"api_key": "dg_live_abc123..."
}
Response
Same ProviderModelsResponse schema as above.
Test Provider
POST /providers/{provider_id}/test
Validates that a saved provider's API key and configuration are working correctly. Calls the provider's validate() method.
Response
{
"success": true,
"message": "Provider deepgram (stt) is working correctly."
}
| Field | Type | Description |
|---|---|---|
success | boolean | Whether validation passed |
message | string | Human-readable result message |
Returns 400 if the API key is invalid, or a response with success: false if validation fails for other reasons.