Cloud API overview
The VieNeu Cloud API turns Vietnamese text into speech over HTTPS. You send text and a voice id, you get audio back — no model to download, no GPU to rent.
This section documents the hosted API at api.vieneu.io. The SDK section
documents the separate on-device package, which runs a model on your own machine.
They share voices and a name, and nothing else: different install, different
billing, different code. If you are integrating VieNeu into a product, you almost
certainly want this section.
The full reference
Every endpoint, every field, every error is in the API reference, rendered from the OpenAPI specification the server itself generates. No login required.
The same document is served as a plain file at
/openapi.json — point openapi-generator,
oapi-codegen, Kiota or your language's equivalent at it and you have a typed
client:
curl -O https://docs.vieneu.io/openapi.json
It is generated from the API's own route definitions, and CI fails the build if
the committed file no longer matches them — so it cannot quietly fall behind what
the server actually accepts.
It covers /api/v1 only — the application's own dashboard, billing and
administration endpoints are not part of the public contract.
Base URL
https://api.vieneu.io/api/v1
Authentication
Every request carries your API key, either way round:
-H "Authorization: Bearer vn_sk_..." # or
-H "X-API-Key: vn_sk_..."
Keys beginning vn_sk_ are live; vn_test_ keys work the same way but cap each
request at 100 words. Create either in the dashboard.
The two ways to synthesize
Synchronous — you get the audio bytes in the response:
curl https://api.vieneu.io/api/v1/audio/speech \
-H "Authorization: Bearer $VIENEU_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "input": "Xin chào, đây là VieNeu.", "voice": "Ngọc Lan" }' \
--output speech.mp3
This is the OpenAI-compatible endpoint — the fastest way in if you already have an OpenAI client, and the one to use for short text.
Asynchronous — for long text, submit a job and poll it:
curl -X POST https://api.vieneu.io/api/v1/tts \
-H "Authorization: Bearer $VIENEU_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "text": "…", "voiceId": "Ngọc Lan" }'
# → { "jobId": "…", "status": "queued" }
curl https://api.vieneu.io/api/v1/tts/<jobId> \
-H "Authorization: Bearer $VIENEU_API_KEY"
# → { "status": "completed", "audioUrl": "https://…" }
And when you need audio to start playing before the whole text is generated, use streaming.
What else is here
| Endpoint | What it does |
|---|---|
GET /v1/voices | The voice catalogue, plus your own cloned voices when authenticated |
POST /v1/tts · GET /v1/tts/{jobId} | Asynchronous synthesis |
POST /v1/tts/stream | Low-latency streaming synthesis, presets or your cloned voices |
POST /v1/audio/speech | OpenAI-compatible synthesis (sync or streaming) |
POST /v1/vapi/speech | Vapi custom-voice webhook — raw PCM for voice agents |
POST /v1/voices · DELETE /v1/voices/{id} | Create and delete reusable cloned voices |
POST /v1/clone | One-shot cloning without saving a voice |
POST /v1/dialogue | Multi-speaker dialogue in one call |
POST /v1/dub | Re-voice an existing audio file |
POST /v1/srt | Dub a subtitle file into a timecode-aligned track |
Engines
Requests carry an optional engine: v4 (the default) or v3. Each is billed
at its own multiplier, applied on top of the per-character rate:
| Engine | Multiplier |
|---|---|
v3 | 1.5× |
v4 | 3× |
These are set per deployment and can change, so the table above is a snapshot,
not a contract. GET /v1/engines returns the live values — no API key
needed. If you are budgeting a large workload, read them from there rather than
from this page:
curl -s https://api.vieneu.io/api/v1/engines
{ "engines": [
{ "key": "v3", "isDefault": false, "billingMultiplier": 1.5,
"features": ["generate", "dialogue", "clone", "dub", "srt", "vc"] },
{ "key": "v4", "isDefault": true, "billingMultiplier": 3,
"features": ["generate", "clone", "stream", "dialogue", "dub", "srt"] }
] }
features is also how you check, in code, which engines can stream — stream
appears on v4 only, and that is why every stream is billed at the v4 rate. A voice belongs to exactly one engine —
GET /v1/voices?engine=v4 lists that engine's catalogue. Passing a voice from
the other engine is rejected rather than silently substituted.
Streaming is v4 only. Not because v3 refuses to stream, but because it
does not really stream: it delivers its chunks in a burst at the end, so the
low-latency promise the endpoint makes would be false on that engine. A stream
request naming v3 is rejected outright rather than served slowly under a
label it cannot honour. Everything else — POST /v1/tts, dialogue, dub, SRT,
cloning — still runs on both.
That is an engine rule, not a feature rule: a cloned voice streams too, provided
it is enrolled on v4 (new clones always are). See
Cloned voices.
Billing
Synthesis is billed per submitted character, so the cost of a call is predictable from the request itself, before you make it. Minimum 50 characters per request. A request that fails to produce audio is refunded automatically.
aiRefine defaults to off on the API. With it off your text is synthesized
exactly as sent. Turn it on ("aiRefine": true) for the AI pass the web app uses
— formulas, acronyms and mixed-in English read correctly, and the content is
checked — billed with a surcharge and one extra round-trip of latency.
Deterministic text preparation runs either way, so Vietnamese is pronounced correctly regardless.
Rate limits and request ids
Limits are counted per API key, not per IP, so spreading calls across machines does not buy you more and sharing an office network does not cost you any. Every response tells you where you stand:
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 58
X-RateLimit-Reset: 43
X-Request-Id: 0f7c…
A 429 additionally carries Retry-After, in seconds.
Quote X-Request-Id in a support request — it is the id our logs are keyed by.
Errors
Native endpoints return the usual shape:
{ "statusCode": 403, "message": "Insufficient tokens", "traceId": "0f7c…" }
/v1/audio/speech returns OpenAI's shape instead, so OpenAI clients can parse it.
| Status | Meaning |
|---|---|
| 400 | Malformed request — an unknown voice, a bad format, text too long |
| 401 | Missing, malformed or revoked API key |
| 403 | Out of tokens, grant expired, or your plan does not include this engine |
| 422 | Content refused by moderation (only when aiRefine is on) |
| 429 | Rate limit or token quota — check Retry-After |
| 503 | No worker available for the requested engine or format; retry shortly |