DEVELOPER DOCUMENTATION
Your first speech request.
Create a key, clone a voice and save your first MP3. One account and wallet, with regional voice processing and a usage receipt for every generation.
clone-v1 · en · MP3 / WAV · 120 characters per request · 1 concurrent generation. Early access is by invitation. Check service status before sending work.Quickstart
Create an API key
Continue with Google using your invited CastReader account and activate your developer workspace. Open API keys, give your key a name and create it. Expiration defaults to Never; choose 7, 30 or 90 days if you prefer. Copy the secret when it is shown once and keep it on your server.
Add your voice
In My voices, choose Record myself, Invite a friend or Upload audio. Use a clear recording up to 4 MiB that you own or have permission to clone. A saved recording starts as Processing. Wait for Readybefore generating speech, then listen to the preview and copy the voice ID. You can also create a voice through the API.
Generate your first MP3
Set your API key below. The example uses
speech:generate; listing voices and reading usage needvoices:readandusage:readon the same key.export CASTREADER_API_KEY="YOUR_API_KEY" # Resolve your network region first. Requires jq; sends no text or audio. export CASTREADER_API_BASE=$(curl --fail --silent --show-error \ 'https://voice.castreader.com/v1/route' \ -H "Authorization: Bearer $CASTREADER_API_KEY" | jq -er '.base_url')Replace
voice_YOUR_IDbelow with a Ready voice in the returned region. Your key and wallet are shared. See regional routing for resource lookup and retry rules.curl "$CASTREADER_API_BASE/audio/speech" \ -H "Authorization: Bearer $CASTREADER_API_KEY" \ -H 'Content-Type: application/json' \ -H 'Idempotency-Key: example-speech-0001' \ --data '{"model":"clone-v1","voice_id":"voice_YOUR_ID", "text":"Hello from my application.","language":"en", "output_format":"mp3","speed":1}' \ --fail-with-body --dump-header speech.headers --output speech.mp3Open
speech.mp3to listen. Keepexample-speech-0001for retries of this exact request; choose a new idempotency key only for a new generation. Changeoutput_formattowavand the output filename tospeech.wavfor WAV audio, using a new idempotency key for that separate generation. Response headers and the receipt are saved tospeech.headers. If cURL exits with an error, inspect the response before playing the file.Check your usage
Open Usage to see the request, billable characters, trial use and exact charge. Or read it from your backend:
curl "$CASTREADER_API_BASE/usage" \ -H "Authorization: Bearer $CASTREADER_API_KEY" \ --fail-with-bodyAdd credits when you need them
Your 3,000 trial characters are used first while valid and available. Open API billing for your balance, prepaid top-ups and payment history. Credits are separate from CastReader Pro. View pricing →
Authentication and API keys
Sign in with the Google identity already linked to your CastReader account. Console manages keys using your login session; your backend authenticates with an API key. Email codes are a backup sign-in method for eligible accounts. A CastReader subscription does not include API credits.
Send a project key in the Authorization header on public API requests; GET /v1/health is the only endpoint that does not require a key. Console uses your signed-in account to manage keys; API requests use Bearer authentication.
Authorization: Bearer YOUR_API_KEYCreate keys in Console → API keys. The full secret is displayed once. Expiration defaults to Never; 7-, 30- and 90-day options are available. Under advanced settings, limit scopes, allowed voices or a monthly budget to match your integration. A voice-restricted key cannot register a new voice.
| Scope | Access |
|---|---|
speech:generate | Generate speech, estimate cost, read request receipts and download retained audio. |
voices:read | List, inspect and preview authorized voices. |
voices:write | Register, rename and delete voices. |
usage:read | Read usage and the balance or budget information permitted for the key creator. |
Keep keys in your server’s secret manager. Rotate a key when replacing a credential; copy its replacement secret once and update your backend. An optional overlap lets the old key remain valid until the earlier of the overlap deadline and its existing expiry. Revoke a compromised key immediately. Revoked or expired keys return 401.
Every request checks project access, scopes and voice restrictions. The key creator must retain an active Owner, Admin or Developer role and an available verified account. Keep the permanent API key out of browser JavaScript, mobile binaries, URLs and public repositories.
Voice registration and permission
Each workspace allows two active voices and 5 new registrations per UTC day. GET /v1/models reports active_voice_limit and daily_voice_registrations for the deployment. Processing and deleting voices count toward the active limit; failed, revoked and deleted voices do not. Deleting a voice does not restore that day’s registration allowance. Exceeding either limit returns 429 with voice_registration_limit.
List voices with GET /v1/voices?limit=20. The response is {data: [...], has_more: true, next_cursor: "..."}. The default page size is 20; limit accepts 1–100. If has_more is true, pass the returned next_cursor unchanged as the next request’s cursor query parameter. Encode it with your URL builder; treat it as opaque. The last page has next_cursor: null. Results are ordered newest first and each page checks your current project and voice permissions.
Use a key with voices:write and unrestricted voice access to register a new voice. The multipart fields below use self only when you are the speaker.
curl "$CASTREADER_API_BASE/voices" \
-H "Authorization: Bearer $CASTREADER_API_KEY" \
-H 'Idempotency-Key: example-voice-0001' \
-F 'name=My voice' -F 'language=en' \
-F 'reference_audio=@reference.wav;type=audio/wav' \
-F 'consent_type=self' -F 'subject_name=Your name' \
-F 'consent_confirmed=true' -F 'terms_version=voice-consent-v1' \
--fail-with-bodyFor another speaker, use consent_type=authorized and include proof_reference, an 8–1,000 character reference to their explicit permission. Always provide subject_name, consent_confirmed=true and terms_version=voice-consent-v1. Optional reference_text is limited to 600 Unicode code points.
Supported upload MIME types are audio/wav, audio/x-wav, audio/mpeg, audio/mp4, audio/webm, audio/ogg and audio/flac. The name is 1–80 characters and the subject name is 1–120. The reference language must be enabled.
The Voice response contains id, object: voice, name, status, language, voice_version, created_at, updated_at and nullable error. States are processing, ready, failed, revoked, deleting and deleted. The preview endpoint can return 404 when no preview is available.
Renaming changes only the display name. Deleting revokes permission before asynchronous artifact cleanup. A voice ID from another project grants no access. Read the data and consent policy.
Use a recording you own or have explicit speaker permission to clone. A publicly available demo recording is not a grant to publish or commercially offer the cloned voice. Internal test samples remain private and do not replace the permission required for your use.
Generate speech
Send the following JSON fields to POST /v1/audio/speechwith a ready voice ID and an Idempotency-Key. Complete requests return MP3 or WAV audio. For the first cURL request, follow the quickstart. Keep the input within the model limits before submitting.
Speech request body
| Field | Default / requirement | Meaning |
|---|---|---|
return_timestamps | false | Optional v3 English word or word-group alignment for MP3/WAV and queued jobs. Read the authenticated timestamps_url after generation. |
model | Required | Exactly clone-v1. The receipt identifies the concrete model_revision. |
voice_id | Required | A ready, authorized voice in the key’s project. |
text | Required | Normalize line endings to LF, normalize Unicode NFC, trim, then count Unicode code points. Empty text and invalid Unicode are rejected. |
language | Default en | Must be enabled in GET /v1/models. |
output_format | Default mp3 | mp3 or wav. The response is a complete audio file. |
speed | Default 1 | MP3: 0.5–2. WAV: 1. |
stream | Default false | Omit or use false for complete audio. Streaming is not part of the current public release. |
This configuration permits 120 normalized code points per short request and these languages: en. Check GET /v1/models on the deployment you call. A JSON request has a 64 KiB body limit.
For audio word timings, add return_timestamps: true. Read the v3 speech timestamp guide for examples, word-group behavior, queued jobs and billing.
Read the metering receipt
Complete audio responses include X-Request-ID, X-Model-Revision, X-Voice-Version, X-Billable-Characters, X-Trial-Characters, X-Charged-USD, X-Idempotency-Replayed and X-Result-Expires-At.
GET /v1/requests/{request_id} returns id, status, voice_id, pinned model/voice/price versions, creation and completion dates, result_url, timestamps_url, error and usage. Usage contains billable_characters, trial_characters, charged_usd, reserved_usd and metering_version. USD decimals are strings; the active reservation becomes a charge only on success.
Result URLs are relative authenticated API paths. Output audio is retained privately for at most 24 hours, according to result_expires_at. The original key or another currently authorized key in the project can retrieve it. Voice revocation blocks retained downloads immediately; expired results return 410.
Usage and API billing
API usage costs $12 per million normalized Unicode code points, calculated at $0.000012 per character. The current enrollment configuration provides 3,000 trial characters; expiry and daily allowance also apply. Review pricing and inspect your actual wallet before generating.
POST /v1/usage/estimate accepts the speech body and returns billable_characters, trial_characters, estimated_charge_usd, maximum_charge_usd, balance_sufficient, metering and price versions. It creates no reservation. Trial availability and existing reservations can change between the estimate and generation; an estimate does not authorize an otherwise restricted request.
The balance endpoint returns wallet fields for an Owner-created key: workspaceId, paidBalanceMicrousd, paidReservedMicrousd, availablePaidMicrousd, paidDebtMicrousd, trialRemainingCharacters, trialReservedCharacters and trialExpiresAt. Monetary values are integer strings in micro-USD.
A member-created key receives project_id, nullable monthly_budget_usd, charged_usd, reserved_usd and workspace_wallet_visible: false. Workspace funds remain restricted to Owner and Billing users in Console.
Owner and Billing users can open API billing to start a Stripe Checkout top-up within the amount range shown for that deployment. On return, Console checks the workspace’s payment record for up to a minute and refreshes the wallet after confirmation. A delayed confirmation remains pending; use Check payment status or Refresh instead of paying again. Payment history includes recorded top-ups and refunds. The billing portal is available when configured and this workspace has a payment customer.
Idempotency, errors and retries
Speech and voice registration require an 8–128 character Idempotency-Key containing ASCII letters, numbers, dots, underscores, colons or hyphens. Give each logical operation its own key and retain it alongside the request ID. Retry the same input with the same key; changed input returns 409.
A completed speech retry returns the original retained result without another charge. An in-flight conflict includes the original request ID in error details; the top-level error request ID identifies the error response itself. Query the original operation after a network failure. Retry retryable errors with bounded exponential backoff and jitter, honoring Retry-After.
Never automatically mint a new key after an ambiguous timeout. A deliberate new attempt after a confirmed terminal failure requires a new key. Expired audio returns 410 and cannot be recovered by retrying the old request.
{
"error": {
"code": "concurrency_limit_exceeded",
"message": "Generation capacity is currently occupied.",
"request_id": "req_ERROR_RESPONSE_ID",
"retryable": true
}
}| Status | What to do |
|---|---|
| 401 · API key | Check the Bearer key and whether it expired or was revoked. |
| 402 · Balance | Check trial availability and paid balance in API billing. |
| 403 / 404 · Access | Check project, scopes and voice ID. Use a ready voice from My voices. |
| 409 · Conflict | For an in-progress request, read its original receipt. Changed input needs a new logical request and key. |
| 410 · Expired audio | The retained result has expired. The old key never generates it again. |
| 413 / 415 / 422 · Input | Fix body size, audio type, text length or language before submitting. |
| 429 · Limit | Wait, honor Retry-After and reduce concurrent work or polling. |
| 503 · Unavailable | Check service status. Use bounded backoff and preserve the same request key. |
Authentication and validation errors do not charge. Other 5xx responses can leave an operation awaiting reconciliation: inspect its receipt before starting again. A network timeout alone is not proof of zero usage.
Maintenance and feature availability
If speech processing is temporarily unavailable while admission remains enabled, you can still save recordings and create friend invitations in the console, subject to workspace limits. Saved voices stay in Processing until the service can prepare them. Existing previews remain accessible. This differs from an explicit admission pause, described below.
During an admission pause, new generation and voice registration return 503 with admission_paused and retryable: true. Completed speech retries can still replay the retained result with the original idempotency key. Read existing requests, check their receipts and retry new work with bounded backoff after service resumes.
A full admission pause also blocks new top-ups. Existing reads, authorized retained downloads, key revocation, voice deletion and payment webhook processing remain available while the overall service is enabled. Turning off the entire service returns 404 instead. A configured feature or worker is not a health check: consult the service status and actual API response.
The preview rate limit is 60 public API requests per minute per key, including polling. Generation also obeys workspace and service concurrency limits. Use moderate polling intervals and back off on 429.
Generation reserves funds and capacity first, then charges after verified audio is durably stored. If a timed-out worker may still be running, its reservation remains until reconciliation establishes the outcome. A client disconnect does not prove that nothing was charged.
HTTP reference
Use Authorization: Bearer <key>. JSON requests require Content-Type: application/json; unknown fields are rejected. Audio endpoints return binary or NDJSON as documented. The OpenAPI document contains the full schemas and response types.
| Endpoint | Response / behavior | Scope |
|---|---|---|
GET /v1/route | Select a region before sending text or recordings. Optional voice_id, request_id or job_id; no voice data in the scheduler. | Any valid key |
GET /v1/requests/{request_id}/timestamps | Retained v3 word or word-group alignment. Opt in during generation; authenticated; no extra charge. | speech:generate |
GET /v1/health | Service identity; enabled service only. | No key |
GET /v1/models | Model revision, enabled languages, MP3/WAV formats, short text limit, streaming flag and price. | Any valid key |
POST /v1/audio/speech | Complete MP3/WAV audio and metering headers. Idempotency-Key required. | speech:generate |
GET /v1/requests/{request_id} | Speech request status, pinned versions, usage, result expiry and error. | speech:generate |
GET /v1/requests/{request_id}/audio | Private retained audio for the original request. | speech:generate |
GET /v1/voices | Cursor page: {data:[...],has_more,next_cursor}. Default 20; limit 1–100. | voices:read |
POST /v1/voices | Multipart registration; 202 Voice with processing status. Idempotency-Key required. | voices:write |
GET /v1/voices/{voice_id} | One Voice object, including status and version. | voices:read |
PATCH /v1/voices/{voice_id} | Rename with {"name":"New name"}; returns the updated Voice. | voices:write |
DELETE /v1/voices/{voice_id} | Revoke access and queue deletion; 202 Voice. | voices:write |
GET /v1/voices/{voice_id}/preview | Available private MP3 preview for a ready voice. | voices:read |
POST /v1/usage/estimate | Speech input → normalized characters and cost estimate; no reservation or charge. | speech:generate |
GET /v1/usage | Current UTC month totals, daily usage and up to 20 recent requests. | usage:read |
GET /v1/billing/balance | Owner-created key: wallet. Member-created key: project budget summary. | usage:read |
POST /v1/audio/jobs | Speech input → 202 Job with ordered chunks. Idempotency-Key required. | speech:generate |
GET /v1/audio/jobs/{job_id} | Job progress and each chunk’s request ID and charge. | speech:generate |
DELETE /v1/audio/jobs/{job_id} | Request cancellation; 202 Job. Completed chunks retain their charges. | speech:generate |
POST /v1/audio/jobs/{job_id}/cancel | Same cancellation behavior as DELETE. | speech:generate |
/v1/balance is an alias for /v1/billing/balance. Advanced, separately enabled endpoints remain documented in the OpenAPI specification and advanced reference.
Node.js and Python
The repository contains private Node.js 22+ and Python 3.11+ clients in packages/voice-api-sdk. They are not published npm or PyPI packages. Use the supplied source files and their README. Both clients support complete MP3/WAV generation, optional v3 timestamps, request receipts and safe retries. cURL works immediately without installing a client.
Node.js example
// Preview source client, Node.js 22+. Run on your backend.
import { writeFile } from 'node:fs/promises';
import { VoiceAPI } from './index.mjs';
const client = new VoiceAPI({
apiKey: process.env.CASTREADER_API_KEY,
baseURL: process.env.CASTREADER_API_BASE,
});
const result = await client.speech({
voice_id: process.env.CASTREADER_VOICE_ID,
text: 'Hello from my application.',
language: 'en',
}, { idempotencyKey: 'sdk-example-speech-0001' });
await writeFile('speech.mp3', result.audio);
console.log(result.requestId, result.chargedUSD);Python example
# Preview source client, Python 3.11+. Run on your backend.
import os
from pathlib import Path
from voice_api import VoiceAPI
client = VoiceAPI(os.environ['CASTREADER_API_KEY'],
os.environ['CASTREADER_API_BASE'])
result = client.speech('Hello from my application.',
os.environ['CASTREADER_VOICE_ID'],
idempotency_key='sdk-example-speech-0001')
Path('speech.mp3').write_bytes(result['audio'])
print(result['request_id'], result['charged_usd'])SDK retries within one generating operation retain its idempotency key. A new method call without the previous key creates a new logical request. Store the key before starting the operation so your application can reconcile process crashes or ambiguous responses.
Advanced reference
Complete MP3 and WAV generation is the current product. Streaming, long jobs, realtime and automatic recharge are not open in this release. These implementation references are for separately enabled integrations; they are not required for your first request.
HTTP streaming · not open
Incremental audio
When streaming is enabled, call /audio/stream or /audio/speech with stream: true. The response type is application/x-ndjson. Each line is a JSON event; decode the audio field from base64 into signed 16-bit little-endian PCM at 24 kHz, mono.
curl "$CASTREADER_API_BASE/audio/stream" \
-H "Authorization: Bearer $CASTREADER_API_KEY" \
-H 'Content-Type: application/json' \
-H 'Idempotency-Key: example-stream-0001' \
--data '{"model":"clone-v1","voice_id":"voice_YOUR_ID",
"text":"Hello from my application.","language":"en",
"output_format":"pcm_s16le","speed":1}' \
--no-buffer --fail-with-bodystartsupplies the request ID, format, replay flag and request receipt.audiosuppliesseq,offset_bytesand base64audio. Validate order and offsets as you consume it.donesupplies finalaudio_bytes,audio_samples,sha256and the settled receipt. Verify the byte count and hash before accepting the complete artifact.
An error event or connection close without done is incomplete. Preserve the request_id from start and reconcile it before retrying. Bytes may already have been played when a request later fails. A successful stream’s retained download is audio/pcm; use the declared sample format when playing or packaging it.
Queued speech · available
Queued speech
Use this endpoint when immediate generation is busy. Submit once, save the job ID, then check its progress every 5 seconds. The task stays queued when you close the page. New jobs are offered for execution immediately; if capacity is unavailable, they wait for a later attempt. Progress checks can also resume an eligible task when capacity returns. Waiting does not reserve or charge your balance; successful audio is billed once. The current task deadline is 10 minutes. A task that cannot run before its deadline expires without a generation charge.
Send the speech JSON body and a stable Idempotency-Key to POST /v1/audio/jobs. This configuration permits up to 120 normalized source code points, split into chunks of up to 120. Set stream to false or omit it.
- Submit to
POST /v1/jobs(alias of/v1/audio/jobs). A successful submission returns HTTP 202 andid. - Read
GET /v1/jobs/{id}. Keep the original ID through queued, blocked and running states. - When status is
succeeded, downloadGET /v1/requests/{chunks[0].requestId}/audio. The current short-task limit produces one audio file. - Cancel with
DELETE /v1/jobs/{id}. Running work must finish cancellation reconciliation before its final charge is known.
The 202 response intentionally uses camelCase: id, status, voiceId, modelRevision, voiceVersion, priceVersion, unitMicroUsd, meteringVersion, sourceCharacters, billableCharacters, completedCharacters, chargedMicrousd, trialCharacters, chunkCount, completedChunks, timestamps, errorCode and chunks.
Each chunk includes its ordinal, status, source offsets, billable characters, requestId, chargedMicrousd, trial characters, result expiry and error code. Poll the job, then download each successful chunk through its request audio endpoint. Amounts in micro-USD are integer strings; divide by 1,000,000 for USD.
Jobs move through queued, running, blocked, cancel_requested and terminal succeeded, failed, cancelled or expired states. Cancellation prevents unstarted chunks; running work may need reconciliation. Completed chunks retain their charges. Billing sums the normalized chunk inputs, so boundary whitespace can make the billable total differ from the source count.
The current response provides ordered chunk results. It does not supply a merged audio artifact; use an audio container tool if your application needs one file.
Realtime sessions · not open
Realtime sessions
Your backend creates a session with POST /v1/realtime/sessions using {"voice_id":"voice_YOUR_ID","language":"en","allowed_origins":["https://your-app.example"]}. Only these three fields are accepted. Browser clients need an exact allowed origin; an empty list permits clients without an Origin header, while rejecting browser origins.
The 201 result returns id, one-use client_secret, expires_at, websocket_path, audio format and session limits. Pass that temporary credential to your client. Connect to the deployment’s WebSocket origin plus /v1/realtime, then send:
{
"type": "session.authenticate",
"session_id": "rts_YOUR_SESSION",
"token": "ONE_USE_CLIENT_SECRET"
}Wait for session.ready, send {"type":"text.append","text":"Hello."}, then {"type":"text.commit"}. Each committed segment produces the streaming events described above. Wait for its verified done before committing another segment. Supported control events are ping, generation.cancel and session.close; generation cancellation closes the session.
Current configuration: token lifetime 60 seconds, session duration 120 seconds, idle timeout 15 seconds, 1,200 characters per session and 120 per segment. At most two pending or active sessions are allowed per workspace. Frames are limited to 16 KiB and 240 per minute.
Session creation has no idempotency deduplication. Do not automatically retry it after an ambiguous response. Reconnection requires an explicitly created new session; the previous token is single-use. Never put either credential in the WebSocket URL or persistent browser storage. A disconnected session’s request IDs still need billing reconciliation.
Workspace roles and permissions
Team access
Use Console’s workspace selector before creating a key, generating audio or changing billing. In Team, an authorized manager creates an invitation bound to the recipient’s verified email and shares the one-time link. Invitations are not emailed automatically. The recipient signs in with the matching account and explicitly accepts.
| Role | Access |
|---|---|
| Owner | Workspace, usage, voices, keys, generation, billing and all member roles. Ownership transfer is not available in this preview. |
| Admin | Usage, voices, keys and generation; manage Developer and Viewer invitations/members. No wallet or payment access. |
| Developer | Usage, voice management, keys and generation. No wallet or payment access. |
| Billing | Read usage and voices; view wallet, top up and manage automatic recharge. No generation or key management. |
| Viewer | Read workspace usage, voices and team information. |
Keys retain their project, scopes, voice restrictions and budgets. The key creator must still have an active Owner, Admin or Developer role and an available verified account. Removing a member revokes their keys; unavailable accounts fail authentication. Console role permissions and API key scopes are separate checks.
Keys, invitations, billing and auto-recharge authorization are managed through authenticated Console sessions. Public Bearer endpoints in this reference do not expose those account administration actions.
Automatic recharge · not open
Explicit auto-recharge authorization
Automatic recharge starts disabled. When the capability is configured, an Owner or Billing user selects a balance threshold, top-up amount and monthly hard cap, accepts the displayed auto-recharge-v1 authorization and completes Stripe’s card setup. Defaults are a $2 threshold, $10 top-up and $50 monthly cap. Saving a card alone does not charge or add credits.
The current automatic recharge implementation permits Stripe test mode only. It cannot debit a live card. The Console shows when authorization is disabled, pending setup, enabled or paused.
At most one recharge can be pending per workspace. Pending attempts count toward the UTC monthly cap; attempts are at least one hour apart and three failed attempts in a month stop further attempts. A payment requiring action or having an unknown outcome pauses automatic charging for review. It does not initiate another charge as a blind retry.
Disabling authorization prevents new charges. A charge already submitted remains visible until its final outcome is verified and may settle afterward. Return to Billing to inspect that result before authorizing again.