Skip to documentation
Browse documentation

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

  1. 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.

  2. 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.

  3. Generate your first MP3

    Set your API key below. The example uses speech:generate; listing voices and reading usage need voices:read and usage:read on the same key.

    Terminal
    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_ID below with a Ready voice in the returned region. Your key and wallet are shared. See regional routing for resource lookup and retry rules.

    cURL · MP3
    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.mp3

    Open speech.mp3 to listen. Keep example-speech-0001 for retries of this exact request; choose a new idempotency key only for a new generation. Change output_format to wav and the output filename to speech.wav for WAV audio, using a new idempotency key for that separate generation. Response headers and the receipt are saved to speech.headers. If cURL exits with an error, inspect the response before playing the file.

  4. Check your usage

    Open Usage to see the request, billable characters, trial use and exact charge. Or read it from your backend:

    cURL · Usage
    curl "$CASTREADER_API_BASE/usage" \
      -H "Authorization: Bearer $CASTREADER_API_KEY" \
      --fail-with-body
  5. Add 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.

Code
Authorization: Bearer YOUR_API_KEY

Create 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.

ScopeAccess
speech:generateGenerate speech, estimate cost, read request receipts and download retained audio.
voices:readList, inspect and preview authorized voices.
voices:writeRegister, rename and delete voices.
usage:readRead 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.

Code
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-body

For 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

FieldDefault / requirementMeaning
return_timestampsfalseOptional v3 English word or word-group alignment for MP3/WAV and queued jobs. Read the authenticated timestamps_url after generation.
modelRequiredExactly clone-v1. The receipt identifies the concrete model_revision.
voice_idRequiredA ready, authorized voice in the key’s project.
textRequiredNormalize line endings to LF, normalize Unicode NFC, trim, then count Unicode code points. Empty text and invalid Unicode are rejected.
languageDefault enMust be enabled in GET /v1/models.
output_formatDefault mp3mp3 or wav. The response is a complete audio file.
speedDefault 1MP3: 0.5–2. WAV: 1.
streamDefault falseOmit 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.

Code
{
  "error": {
    "code": "concurrency_limit_exceeded",
    "message": "Generation capacity is currently occupied.",
    "request_id": "req_ERROR_RESPONSE_ID",
    "retryable": true
  }
}
StatusWhat to do
401 · API keyCheck the Bearer key and whether it expired or was revoked.
402 · BalanceCheck trial availability and paid balance in API billing.
403 / 404 · AccessCheck project, scopes and voice ID. Use a ready voice from My voices.
409 · ConflictFor an in-progress request, read its original receipt. Changed input needs a new logical request and key.
410 · Expired audioThe retained result has expired. The old key never generates it again.
413 / 415 / 422 · InputFix body size, audio type, text length or language before submitting.
429 · LimitWait, honor Retry-After and reduce concurrent work or polling.
503 · UnavailableCheck 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.

EndpointResponse / behaviorScope
GET /v1/routeSelect 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}/timestampsRetained v3 word or word-group alignment. Opt in during generation; authenticated; no extra charge.speech:generate
GET /v1/healthService identity; enabled service only.No key
GET /v1/modelsModel revision, enabled languages, MP3/WAV formats, short text limit, streaming flag and price.Any valid key
POST /v1/audio/speechComplete 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}/audioPrivate retained audio for the original request.speech:generate
GET /v1/voicesCursor page: {data:[...],has_more,next_cursor}. Default 20; limit 1–100.voices:read
POST /v1/voicesMultipart 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}/previewAvailable private MP3 preview for a ready voice.voices:read
POST /v1/usage/estimateSpeech input → normalized characters and cost estimate; no reservation or charge.speech:generate
GET /v1/usageCurrent UTC month totals, daily usage and up to 20 recent requests.usage:read
GET /v1/billing/balanceOwner-created key: wallet. Member-created key: project budget summary.usage:read
POST /v1/audio/jobsSpeech 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}/cancelSame 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
Code
// 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
Code
# 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.

Code
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-body
  1. start supplies the request ID, format, replay flag and request receipt.
  2. audio supplies seq, offset_bytes and base64 audio. Validate order and offsets as you consume it.
  3. done supplies final audio_bytes, audio_samples, sha256 and 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.

  1. Submit to POST /v1/jobs (alias of /v1/audio/jobs). A successful submission returns HTTP 202 and id.
  2. Read GET /v1/jobs/{id}. Keep the original ID through queued, blocked and running states.
  3. When status is succeeded, download GET /v1/requests/{chunks[0].requestId}/audio. The current short-task limit produces one audio file.
  4. 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:

Code
{
  "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.

Realtime requires streaming to be enabled and the dedicated Node WebSocket adapter to be running. The REST session endpoint alone does not establish that the WebSocket transport or a GPU worker is available.
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.

RoleAccess
OwnerWorkspace, usage, voices, keys, generation, billing and all member roles. Ownership transfer is not available in this preview.
AdminUsage, voices, keys and generation; manage Developer and Viewer invitations/members. No wallet or payment access.
DeveloperUsage, voice management, keys and generation. No wallet or payment access.
BillingRead usage and voices; view wallet, top up and manage automatic recharge. No generation or key management.
ViewerRead 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.