Skip to main content

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.

Two different products

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

EndpointWhat it does
GET /v1/voicesThe voice catalogue, plus your own cloned voices when authenticated
POST /v1/tts · GET /v1/tts/{jobId}Asynchronous synthesis
POST /v1/tts/streamLow-latency streaming synthesis, presets or your cloned voices
POST /v1/audio/speechOpenAI-compatible synthesis (sync or streaming)
POST /v1/vapi/speechVapi custom-voice webhook — raw PCM for voice agents
POST /v1/voices · DELETE /v1/voices/{id}Create and delete reusable cloned voices
POST /v1/cloneOne-shot cloning without saving a voice
POST /v1/dialogueMulti-speaker dialogue in one call
POST /v1/dubRe-voice an existing audio file
POST /v1/srtDub 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:

EngineMultiplier
v31.5×
v4

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.

StatusMeaning
400Malformed request — an unknown voice, a bad format, text too long
401Missing, malformed or revoked API key
403Out of tokens, grant expired, or your plan does not include this engine
422Content refused by moderation (only when aiRefine is on)
429Rate limit or token quota — check Retry-After
503No worker available for the requested engine or format; retry shortly