API documentation
One API for text to speech, speech to text, speech recognition and voice cloning. All endpoints live under a single base URL and share one bearer token.
https://api.nexacalling.com/api/v1Every response carries an X-Request-ID header, and every error repeats it in the body. It identifies one row in your request log — quote it when contacting support.
Authentication
Create a key on the API keys page and send it as a bearer token.
Authorization: Bearer vp_live_xxxxxxxxxxxxThe secret is shown once, at creation. We store only a prefix and a hash, so it cannot be recovered — if you lose it, revoke the key and create another. Keys are scoped to one organization and can only ever see that organization’s voices, usage and logs.
Quick start
List your voices, then synthesize speech with one. Set VOICE_API_KEY in your environment first.
# 1. List the voices available to you
curl https://api.nexacalling.com/api/v1/voices -H "Authorization: Bearer $VOICE_API_KEY"
# 2. Synthesize speech with one of them
curl -X POST https://api.nexacalling.com/api/v1/text-to-speech \
-H "Authorization: Bearer $VOICE_API_KEY" \
-H "Content-Type: application/json" \
-d '{"text":"My first request.","voice_id":"voice_xxxxx"}' \
--output speech.mp3Text to speech
/api/v1/text-to-speechBearer authParameters · JSON body
| Name | Type | Required | Description |
|---|---|---|---|
| text | string | Required | The text to speak. 1 to 5000 characters. Billed at one unit per character. |
| voice_id | string | Required | A voice ID from GET /voices. Must belong to your organization or the shared catalogue. |
Example request
curl -X POST https://api.nexacalling.com/api/v1/text-to-speech \
-H "Authorization: Bearer $VOICE_API_KEY" \
-H "Content-Type: application/json" \
-d '{"text":"Hello from the Voice API Platform.","voice_id":"voice_xxxxx"}' \
--output speech.mp3Example response
HTTP/1.1 200 OK
Content-Type: audio/mpeg
X-Request-ID: req_k3f9x2mQ7Lp0
<binary audio data>Example error
{
"error": {
"code": "INVALID_REQUEST",
"message": "text must not be empty.",
"request_id": "req_k3f9x2mQ7Lp0"
}
}Errors
| Status | Code | When |
|---|---|---|
| 400 | INVALID_REQUEST | Missing text, empty text, or over 5000 characters. |
| 404 | NOT_FOUND | No such voice in your organization. |
| 401 | INVALID_API_KEY | The key is missing, malformed, unknown or revoked. |
| 403 | ACCOUNT_SUSPENDED | The account or organization is suspended. |
| 429 | RATE_LIMITED | More than 100 requests in a minute on this key. |
| 502 | PROVIDER_ERROR | Synthesis failed upstream. Safe to retry. |
Streaming text to speech
/api/v1/text-to-speech/streamBearer authformat to choose MP3 (default) or raw PCM; the sample rate is returned in the X-Sample-Rate header. Billed per character, exactly like /text-to-speech.Parameters · JSON body
| Name | Type | Required | Description |
|---|---|---|---|
| text | string | Required | The text to speak. 1 to 5000 characters. Billed at one unit per character. |
| voice_id | string | Required | A voice ID from GET /voices. |
| format | string | Optional | mp3_44100 (default), pcm_16000 or pcm_24000. PCM is raw 16-bit mono, ideal for agent frameworks. |
Example request
curl -N -X POST https://api.nexacalling.com/api/v1/text-to-speech/stream \
-H "Authorization: Bearer $VOICE_API_KEY" \
-H "Content-Type: application/json" \
-d '{"text":"Streaming, for real-time agents.","voice_id":"voice_xxxxx","format":"mp3_44100"}' \
--output stream.mp3Example response
HTTP/1.1 200 OK
Content-Type: audio/mpeg
X-Sample-Rate: 44100
X-Request-ID: req_k3f9x2mQ7Lp0
Transfer-Encoding: chunked
<binary audio data, streamed>Example error
{
"error": {
"code": "INVALID_REQUEST",
"message": "text must not be empty.",
"request_id": "req_k3f9x2mQ7Lp0"
}
}Errors
| Status | Code | When |
|---|---|---|
| 400 | INVALID_REQUEST | Missing text, an unknown format, or over 5000 characters. |
| 404 | NOT_FOUND | No such voice in your organization. |
| 401 | INVALID_API_KEY | The key is missing, malformed, unknown or revoked. |
| 403 | ACCOUNT_SUSPENDED | The account or organization is suspended. |
| 429 | RATE_LIMITED | More than 100 requests in a minute on this key. |
| 502 | PROVIDER_ERROR | Synthesis failed upstream. Safe to retry. |
Speech to text
/api/v1/speech-to-textBearer authParameters · multipart/form-data
| Name | Type | Required | Description |
|---|---|---|---|
| file | file | Required | mp3, wav, m4a, webm or ogg, up to 25 MB. Validated by content, not just extension. |
| language | string | Optional | ISO language hint such as 'en'. Omit to detect automatically. |
Example request
curl -X POST https://api.nexacalling.com/api/v1/speech-to-text \
-H "Authorization: Bearer $VOICE_API_KEY" \
-F "file=@recording.mp3" \
-F "language=en"Example response
{
"text": "Hello, and welcome to the Voice API Platform.",
"language": "en",
"request_id": "req_k3f9x2mQ7Lp0"
}Example error
{
"error": {
"code": "UNSUPPORTED_MEDIA_TYPE",
"message": "That file does not appear to be audio. Accepted formats: mp3, wav, m4a, webm, ogg.",
"request_id": "req_k3f9x2mQ7Lp0"
}
}Errors
| Status | Code | When |
|---|---|---|
| 400 | INVALID_REQUEST | No file was supplied, or the language code is invalid. |
| 413 | PAYLOAD_TOO_LARGE | The file is larger than 25 MB. |
| 415 | UNSUPPORTED_MEDIA_TYPE | The file is not mp3, wav, m4a, webm or ogg. |
| 502 | PROVIDER_ERROR | Transcription failed upstream. Safe to retry. |
| 401 | INVALID_API_KEY | The key is missing, malformed, unknown or revoked. |
| 403 | ACCOUNT_SUSPENDED | The account or organization is suspended. |
| 429 | RATE_LIMITED | More than 100 requests in a minute on this key. |
Speech recognition
/api/v1/asrBearer authParameters · multipart/form-data
| Name | Type | Required | Description |
|---|---|---|---|
| file | file | Required | mp3, wav, m4a, webm or ogg, up to 25 MB. Validated by content, not just extension. |
| language | string | Optional | ISO language hint such as 'en'. Omit to detect automatically. |
Example request
curl -X POST https://api.nexacalling.com/api/v1/asr \
-H "Authorization: Bearer $VOICE_API_KEY" \
-F "file=@recording.mp3" \
-F "language=en"Example response
{
"text": "Hello, and welcome to the Voice API Platform.",
"language": "en",
"request_id": "req_k3f9x2mQ7Lp0"
}Example error
{
"error": {
"code": "PAYLOAD_TOO_LARGE",
"message": "Audio must be 25 MB or smaller.",
"request_id": "req_k3f9x2mQ7Lp0"
}
}Errors
| Status | Code | When |
|---|---|---|
| 400 | INVALID_REQUEST | No file was supplied, or the language code is invalid. |
| 413 | PAYLOAD_TOO_LARGE | The file is larger than 25 MB. |
| 415 | UNSUPPORTED_MEDIA_TYPE | The file is not mp3, wav, m4a, webm or ogg. |
| 502 | PROVIDER_ERROR | Transcription failed upstream. Safe to retry. |
| 401 | INVALID_API_KEY | The key is missing, malformed, unknown or revoked. |
| 403 | ACCOUNT_SUSPENDED | The account or organization is suspended. |
| 429 | RATE_LIMITED | More than 100 requests in a minute on this key. |
List voices
/api/v1/voicesBearer authExample request
curl https://api.nexacalling.com/api/v1/voices \
-H "Authorization: Bearer $VOICE_API_KEY"Example response
{
"voices": [
{
"id": "voice_c1x2n8fk4p",
"name": "Narrator",
"language": "en",
"type": "PROVIDER",
"preview_available": true,
"created_at": "2026-08-30T12:00:00.000Z"
}
]
}Example error
{
"error": {
"code": "INVALID_API_KEY",
"message": "Invalid or revoked API key.",
"request_id": "req_k3f9x2mQ7Lp0"
}
}Errors
| Status | Code | When |
|---|---|---|
| 401 | INVALID_API_KEY | The key is missing, malformed, unknown or revoked. |
| 403 | ACCOUNT_SUSPENDED | The account or organization is suspended. |
| 429 | RATE_LIMITED | More than 100 requests in a minute on this key. |
Play a sample
/api/v1/voices/{id}/previewBearer authaudio/mpeg bytes, so you can audition it before using it. This is a recording, not synthesis: nothing is generated and nothing is billed. Only voices listed with preview_available: true have one.Example request
curl https://api.nexacalling.com/api/v1/voices/voice_xxxxx/preview \
-H "Authorization: Bearer $VOICE_API_KEY" \
--output preview.mp3Example response
HTTP/1.1 200 OK
Content-Type: audio/mpeg
X-Request-ID: req_k3f9x2mQ7Lp0
<binary audio data>Example error
{
"error": {
"code": "NOT_FOUND",
"message": "No preview is available for this voice.",
"request_id": "req_k3f9x2mQ7Lp0"
}
}Errors
| Status | Code | When |
|---|---|---|
| 404 | NOT_FOUND | No such voice in your organization, or the voice has no sample. |
| 401 | INVALID_API_KEY | The key is missing, malformed, unknown or revoked. |
| 403 | ACCOUNT_SUSPENDED | The account or organization is suspended. |
| 429 | RATE_LIMITED | More than 100 requests in a minute on this key. |
| 502 | PROVIDER_ERROR | The sample could not be fetched. Retry, or contact support with the request ID. |
Clone a voice
/api/v1/voices/cloneBearer authtrue: by sending it you confirm you have permission and authorization to use the recording for voice cloning. The confirmation is stored with a timestamp against the voice.Parameters · multipart/form-data
| Name | Type | Required | Description |
|---|---|---|---|
| name | string | Required | A name for the voice. Up to 64 characters. |
| consent | string | Required | Must be 'true'. Confirms you are authorized to clone this voice. |
| file | file | Required | A clear sample of a single speaker. mp3, wav, m4a, webm or ogg, up to 25 MB. |
| description | string | Optional | Optional note, up to 500 characters. |
Example request
curl -X POST https://api.nexacalling.com/api/v1/voices/clone \
-H "Authorization: Bearer $VOICE_API_KEY" \
-F "name=Narrator" \
-F "consent=true" \
-F "file=@sample.mp3"Example response
{
"voice": {
"id": "voice_9k2m4x7qab",
"name": "Narrator",
"language": null,
"type": "CLONED",
"created_at": "2026-08-30T12:00:00.000Z"
}
}Example error
{
"error": {
"code": "INVALID_REQUEST",
"message": "consent: consent must be true. You must confirm you are authorized to clone this voice.",
"request_id": "req_k3f9x2mQ7Lp0"
}
}Errors
| Status | Code | When |
|---|---|---|
| 400 | INVALID_REQUEST | Missing name, missing consent, or the clone limit is reached. |
| 403 | SERVICE_DISABLED | Voice cloning is disabled for this account. |
| 413 | PAYLOAD_TOO_LARGE | The file is larger than 25 MB. |
| 415 | UNSUPPORTED_MEDIA_TYPE | The file is not mp3, wav, m4a, webm or ogg. |
| 502 | PROVIDER_ERROR | Transcription failed upstream. Safe to retry. |
| 401 | INVALID_API_KEY | The key is missing, malformed, unknown or revoked. |
| 403 | ACCOUNT_SUSPENDED | The account or organization is suspended. |
| 429 | RATE_LIMITED | More than 100 requests in a minute on this key. |
Delete a voice
/api/v1/voices/{id}Bearer authExample request
curl -X DELETE https://api.nexacalling.com/api/v1/voices/voice_xxxxx \
-H "Authorization: Bearer $VOICE_API_KEY"Example response
{ "deleted": true, "id": "voice_9k2m4x7qab" }Example error
{
"error": {
"code": "NOT_FOUND",
"message": "Voice not found.",
"request_id": "req_k3f9x2mQ7Lp0"
}
}Errors
| Status | Code | When |
|---|---|---|
| 404 | NOT_FOUND | No such cloned voice in your organization. |
| 401 | INVALID_API_KEY | The key is missing, malformed, unknown or revoked. |
| 403 | ACCOUNT_SUSPENDED | The account or organization is suspended. |
| 429 | RATE_LIMITED | More than 100 requests in a minute on this key. |
Build a voice agent
The streaming endpoint is built for real-time agents. Our nexavoice-livekit plugin wires it into LiveKit Agents so your agent speaks with one of your voices — you write the agent, we are the voice engine inside it. Install the plugin alongside LiveKit Agents:
pip install nexavoice-livekit "livekit-agents[silero]"Point it at your key, then drop the TTS into an AgentSession. The plugin calls our API — your agent code never names an upstream provider.
export NEXAVOICE_API_KEY=vp_live_xxxxxxxxxxxxfrom livekit.agents import Agent, AgentSession
from livekit.plugins import openai, silero
from nexavoice_livekit import TTS # reads NEXAVOICE_API_KEY from the environment
session = AgentSession(
stt=openai.STT(),
llm=openai.LLM(model="gpt-4o-mini"),
tts=TTS(voice_id="voice_xxxxx"), # one of your voices from GET /voices
vad=silero.VAD.load(),
)
# session.start(agent=Agent(instructions="You are a helpful assistant."), room=ctx.room)The plugin streams audio over plain HTTP, so there is nothing new to host. A complete, runnable agent and the plugin source are in plugins/nexavoice-livekit. For the full design — including how the voice engine stays swappable behind this contract — see docs/voice-agents.md.
Usage
Every request is recorded. Billable work also records units. Failed requests are logged but never billed.
| Endpoint | Unit |
|---|---|
| /text-to-speech | One per character of text submitted |
| /text-to-speech/stream | One per character of text submitted |
| /speech-to-text | One per second of audio, rounded up |
| /asr | One per second of audio, rounded up |
| /voices/clone | One per voice created |
Where audio duration cannot be determined, one unit is recorded rather than zero. Your usage and request history are on the usage and requests pages.
Errors
Every error uses one envelope, whatever the status code.
{
"error": {
"code": "INVALID_REQUEST",
"message": "text must not be empty.",
"request_id": "req_k3f9x2mQ7Lp0"
}
}| Status | Code | Meaning |
|---|---|---|
| 400 | INVALID_REQUEST | The request failed validation. |
| 401 | INVALID_API_KEY | The key is missing, malformed, unknown or revoked. |
| 403 | ACCOUNT_SUSPENDED | The account or organization is suspended. |
| 403 | SERVICE_DISABLED | An administrator has disabled this service. |
| 404 | NOT_FOUND | The resource does not exist, or is not yours. |
| 413 | PAYLOAD_TOO_LARGE | The upload exceeds the size cap. |
| 415 | UNSUPPORTED_MEDIA_TYPE | The file is not an accepted audio format. |
| 429 | RATE_LIMITED | You exceeded the per-key rate limit. |
| 500 | INTERNAL_ERROR | Something failed on our side. |
| 502 | PROVIDER_ERROR | The voice service failed. Safe to retry. |
A resource owned by another organization returns 404, not 403 — a 403 would confirm the ID exists.