Hello! Welcome to our city.
2.2s · 27 charactersBUILD 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
Same preset for every card
cards.json5 phrases × 4 languagesqueued → succeededSequential jobs + checkpoints20 × .mp3 + manifest.jsonSaved audio, not placeholders
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.
你好!欢迎来到我们的城市。
2.9s · 13 charactersこんにちは。私たちの町へようこそ。
3.6s · 17 characters¡Hola! Bienvenido a nuestra ciudad.
2.7s · 35 charactersEvaluate 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.
npm install
node generate.mjs --planThe 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.
node generate.mjs --run
# output/ contains MP3 files, manifest.json
# and private recovery state.jsonWhat each request does
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.
| Situation | What the script does |
|---|---|
| MP3 already saved and checksum matches | Uses the local file, with no API request. |
| Submission returned an uncertain network result | Retries the unchanged body with its saved idempotency key. |
| Job is still running or its download failed | Follows the saved job ID and downloads that result. |
| Job failed, was cancelled or expired | Stops for inspection; does not create a replacement paid job. |
| Text or voice changed | Refuses 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 / language | Audio file | USD |
|---|---|---|
| English Hello! Welcome to our city. | welcome-en.mp3 | 0.000216 |
| 中文 你好!欢迎来到我们的城市。 | welcome-zh.mp3 | 0.000104 |
| 日本語 こんにちは。私たちの町へようこそ。 | welcome-ja.mp3 | 0.000136 |
| Español ¡Hola! Bienvenido a nuestra ciudad. | welcome-es.mp3 | 0.000280 |
| English Excuse me, where is the train station? | station-en.mp3 | 0.000304 |
| 中文 请问,火车站在哪里? | station-zh.mp3 | 0.000080 |
| 日本語 すみません、駅はどこですか。 | station-ja.mp3 | 0.000112 |
| Español Disculpe, ¿dónde está la estación de tren? | station-es.mp3 | 0.000336 |
| English Could I have a glass of water, please? | water-en.mp3 | 0.000304 |
| 中文 请给我一杯水,谢谢。 | water-zh.mp3 | 0.000080 |
| 日本語 お水を一杯いただけますか。 | water-ja.mp3 | 0.000104 |
| Español ¿Me da un vaso de agua, por favor? | water-es.mp3 | 0.000272 |
| English Thank you for your help. | thanks-en.mp3 | 0.000192 |
| 中文 谢谢你的帮助。 | thanks-zh.mp3 | 0.000056 |
| 日本語 手伝ってくれてありがとうございます。 | thanks-ja.mp3 | 0.000144 |
| Español Gracias por su ayuda. | thanks-es.mp3 | 0.000168 |
| English See you tomorrow. Have a good evening! | tomorrow-en.mp3 | 0.000304 |
| 中文 明天见,祝你今晚愉快! | tomorrow-zh.mp3 | 0.000088 |
| 日本語 また明日。よい夜をお過ごしください。 | tomorrow-ja.mp3 | 0.000144 |
| Español Hasta mañana. ¡Que tenga una buena noche! | tomorrow-es.mp3 | 0.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.