BUILD WITH VOICE API

20 learning cards. Four languages. Ready-to-use audio.

Turn a small content list into downloadable MP3s. This Node.js project handles one queued job at a time, saves your progress and produces a manifest your app can read.

Node.js 22+ · activated API workspace · server-side key · normal generation charges

Rowan

Same preset for every card

  1. cards.json5 phrases × 4 languages
  2. queued → succeededSequential jobs + checkpoints
  3. 20 × .mp3 + manifest.jsonSaved audio, not placeholders
469billed characters
$0.003752actual generation charge

Internal example run, 14 September 2026. No trial characters were used. This is not a customer case, performance SLA or language-quality benchmark.

ACTUAL API OUTPUT

Start by listening.

Here is one phrase in each language, generated with the same prepared voice. The source file includes separate text for every language; the API does not translate.

English

Hello! Welcome to our city.

2.2s · 27 characters
中文

你好!欢迎来到我们的城市。

2.9s · 13 characters
日本語

こんにちは。私たちの町へようこそ。

3.6s · 17 characters
Español

¡Hola! Bienvenido a nuestra ciudad.

2.7s · 35 characters

Evaluate pronunciation and similarity with your target users. These samples have technical generation and file checks, not a claim of native-speaker review in every language.

Run the complete project.

1. Inspect before spending

Download and unzip the starter. The default plan command reads the 20 rows locally and does not call the API.

Terminal
npm install
node generate.mjs --plan

The SDK package installs from our official site; it is not listed on npm. Inspect cards.json and the estimated character cost before running.

2. Generate with your workspace

Set CASTREADER_API_KEY in your server environment or secret manager, then run the script. Never embed your key in a web page or a public repository.

Terminal
node generate.mjs --run

# output/ contains MP3 files, manifest.json
# and private recovery state.json

Need an API workspace? Request access

What each request does

Node.js · request excerpt; complete recovery logic is in the download
const job = await client.createJob({
  model: 'clone-v1',
  voice_id: readyVoiceId,
  text: card.text,
  language: card.language,
  output_format: 'mp3',
}, { idempotencyKey: savedKey });

// Save job.id, then poll until succeeded.
const { audio, chargedUSD } = await client.getJobAudio(job.id);
await writeFile(card.id + '.mp3', audio);

This is an application loop, not a native batch endpoint. Requests run sequentially to respect the current one-operation workspace concurrency. Each row may wait for capacity.

Interrupted? Continue the same work.

SituationWhat the script does
MP3 already saved and checksum matchesUses the local file, with no API request.
Submission returned an uncertain network resultRetries the unchanged body with its saved idempotency key.
Job is still running or its download failedFollows the saved job ID and downloads that result.
Job failed, was cancelled or expiredStops for inspection; does not create a replacement paid job.
Text or voice changedRefuses to overwrite the old run. A new folder is a deliberate new generation.

Keep output/state.json private. Re-run with the same folder and unchanged inputs. A hard kill can leave a lock file; confirm the earlier process is stopped before removing that specific .running lock. Retained server output expires within 24 hours, so download promptly.

Every text stays paired with its audio.

This run generated 20 files using 469 characters at $8 per million. The API returned a total charge of $0.003752. Your available trial credit may change what you pay.

See every input, file and charge
Input / languageAudio fileUSD
English

Hello! Welcome to our city.

welcome-en.mp30.000216
中文

你好!欢迎来到我们的城市。

welcome-zh.mp30.000104
日本語

こんにちは。私たちの町へようこそ。

welcome-ja.mp30.000136
Español

¡Hola! Bienvenido a nuestra ciudad.

welcome-es.mp30.000280
English

Excuse me, where is the train station?

station-en.mp30.000304
中文

请问,火车站在哪里?

station-zh.mp30.000080
日本語

すみません、駅はどこですか。

station-ja.mp30.000112
Español

Disculpe, ¿dónde está la estación de tren?

station-es.mp30.000336
English

Could I have a glass of water, please?

water-en.mp30.000304
中文

请给我一杯水,谢谢。

water-zh.mp30.000080
日本語

お水を一杯いただけますか。

water-ja.mp30.000104
Español

¿Me da un vaso de agua, por favor?

water-es.mp30.000272
English

Thank you for your help.

thanks-en.mp30.000192
中文

谢谢你的帮助。

thanks-zh.mp30.000056
日本語

手伝ってくれてありがとうございます。

thanks-ja.mp30.000144
Español

Gracias por su ayuda.

thanks-es.mp30.000168
English

See you tomorrow. Have a good evening!

tomorrow-en.mp30.000304
中文

明天见,祝你今晚愉快!

tomorrow-zh.mp30.000088
日本語

また明日。よい夜をお過ごしください。

tomorrow-ja.mp30.000144
Español

Hasta mañana. ¡Que tenga una buena noche!

tomorrow-es.mp30.000328

Replace the cards. Keep the workflow.

Edit the input JSON, keep unique filename-safe IDs and provide the language of each text. Use VOICE_ID for a ready voice you are authorized to use. Start with a small set and review its audio before scaling.

The starter accepts up to 200 rows, each up to 500 normalized characters; current service limits and available languages still apply. No automatic translation, real-time conversation or audio mixing is included.