Public API v1

OpenAI-compatible speech synthesis

Changed on

Drop-in for OpenAI POST /v1/audio/speech: point an OpenAI SDK at https://api.vieneu.io/api/v1 with your VieNeu key and it works unchanged. Returns the audio bytes directly — mp3 by default, like OpenAI; wav, opus and pcm are also available via response_format. Set stream_format to audio or sse to receive audio as it is generated instead of waiting for the whole file — that requires response_format: pcm or ulaw, the only two encodings whose frames concatenate. Maps input→text, voice→VieNeu voice id (from GET /v1/audio/voices) and model→engine. Billed by the submitted character count (minimum 50, × the AI surcharge only if you opt in with aiRefine: true) and refunded if synthesis fails. Auth via Authorization: Bearer <api key>.

post/api/v1/audio/speech

Request

  • Base URL
    https://api.vieneu.io
  • Auth
    • API key in header X-API-Key
    • or HTTP bearer

Request body

modelstring

Selects the engine. OpenAI model names (tts-1, tts-1-hd, gpt-4o-mini-tts) map to the default engine so an unmodified OpenAI client works; vieneu-v4 selects the engine explicitly (vieneu-v3 is refused with 400 since V3 was retired on 2026-09-24). Any other value is accepted and ignored, as this endpoint has always done. A vieneu-… name for an engine that does not exist IS rejected — that is a choice of engine, and rendering on a different one would bill at a rate you did not pick. engine wins if both are sent.

inputstring required

The text to synthesize (OpenAI: input). Required.

voicestring

VieNeu voice id from GET /v1/voices (e.g. "Ngọc Lan"), or its display name (name), or one of your own cloned voices (clone_…, created in the web Studio at https://vieneu.io/#/clone). A clone renders on the engine it was enrolled on. Omit to use the default voice. (OpenAI voice names like "alloy" are not mapped — pass a VieNeu voice.)

response_format'wav' | 'mp3' | 'opus' | 'pcm' | 'ulaw'

Output format. Defaults to mp3, as OpenAI does — a client that omits the field expects mp3, and returning WAV to it produced a file its player refused. wav, opus, pcm and ulaw are also supported; pcm is raw headerless signed 16-bit little-endian at 24 kHz — the rate OpenAI documents, and what this route defaults to unless sample_rate says otherwise — and ulaw is raw 8 kHz G.711 for telephony. OpenAI's aac and flac are not supported and return 400.

sample_rate8000 | 16000 | 22050 | 24000 | 44100 | 48000

VieNeu extension: output sample rate in Hz. Omit for 48000 — except with pcm, which defaults to 24000 (the rate OpenAI documents, since an OpenAI client has no field to ask for another and headerless PCM at the wrong rate plays at the wrong speed), and ulaw, which is always 8000.

speednumber

Playback rate, applied by a pitch-preserving time-stretch. OpenAI accepts 0.25–4.0 and so does this endpoint, but VieNeu's engine only holds quality across 0.5–2.0, so values outside that are CLAMPED, not rejected — a 400 for a value OpenAI itself considers valid would break the drop-in promise. The native /v1 endpoints validate 0.5–2.0 strictly instead.

instructionsstring

Accepted and ignored. It exists because gpt-4o-mini-tts — one of the model names this endpoint accepts — is the model whose whole point is this parameter, and the global validation pipe rejects unknown fields. Sending it must not 400; VieNeu conveys style through emotion and inline cues.

stream_format'audio' | 'sse'

Stream the response instead of buffering the whole file. audio sends the encoded bytes as they are produced (chunked transfer). sse sends OpenAI-shaped Server-Sent Events: speech.audio.delta carrying base64 audio, then a final speech.audio.done. That final event is the only positive proof the stream finished — without it, treat the audio as truncated. ONLY valid with response_format: pcm or ulaw: streamed frames are encoded independently, so mp3/opus/wav cannot be concatenated into one playable file and are rejected with 400. Note response_format defaults to mp3, so streaming requires setting it explicitly.

emotionstring

VieNeu extension: speaking style — 'natural' or 'storytelling'.

aiRefineboolean

VieNeu extension — AI text refinement. Defaults to FALSE on the public API: the text is synthesized as submitted, with no AI moderation and no AI normalization, billed at the plain per-character rate. Set true to run the same AI step the web app uses — formulas, acronyms and mixed-in English are read correctly and the content is checked — billed with the AI surcharge and one extra model round-trip of latency. Deterministic text preparation is applied either way, and so is your pronunciation dictionary (see applyPronunciation).

applyPronunciationboolean

VieNeu extension — Read the text through your account's pronunciation dictionary — the rules saved at https://vieneu.io/#/pronunciation (Từ điển phát âm). Defaults to TRUE, and changes nothing until you have saved rules there. The rules run after aiRefine and before synthesis; you are billed for the text you sent, and that is the text kept with the job. Set false to read this request as written. If your rules make the text longer than the request limit, or more than twice as long plus 1,000 characters, the call is refused with 400 PRONUNCIATION_EXPANDED_TOO_LONG, uncharged.

engine'v4'

VieNeu extension: TTS engine — "v4" (48 kHz), billed at its own multiplier. "v3" was retired on 2026-09-24 and is refused. Omit for the configured default; GET /v1/engines reports which one that is (isDefault).

Example request

{
  "model": "tts-1",
  "input": "Xin chào, đây là VieNeu.",
  "voice": "Ngọc Lan",
  "response_format": "mp3",
  "sample_rate": 24000,
  "speed": 1,
  "instructions": "Speak in a calm tone.",
  "stream_format": "audio",
  "emotion": "natural",
  "applyPronunciation": true,
  "engine": "v4"
}

Response

The audio bytes. Content-Type follows response_format (mp3 by default). With stream_format: "sse" the body is instead a text/event-stream of base64 audio deltas, which may include : keep-alive comment lines (skip any line starting with : if you parse the events yourself); with "audio" it is the same bytes, chunked, which may include a few samples of digital silence while generation pauses after the first audio. For the headerless pcm and ulaw formats the sample rate is in X-Sample-Rate (pcm defaults to 24 kHz here, not the engine native rate).

Changes