Browse API documentation
Apply for API access

POST /v1/speech

Generate Persian speech with explicit quality tiers.

Speech is a discriminated request: Basic uses a small stable control set, while Premium exposes expressive delivery options. Both return an expiring audio download rather than embedding audio in JSON.

POSThttps://api.vowelmarks.com/v1/speech

Updated September 17, 2026 · API version 1

Tier availability: asynchronous Basic jobs, Basic MP3 event streams, and completed-audio HEAD/range requests are available in production. Asynchronous Premium jobs and Premium PCM event streams remain a development/staging preview and are disabled in production. Existing synchronous Basic and Premium integrations continue to use POST /v1/speech.

Basic speech

Basic accepts kian, leyla, ava, mana, or amir. Kian and Leyla are the featured voices; Ava, Mana, and Amir remain available as self-hosted alternatives. pace defaults to 1 and accepts 0.75–1.25. Premium-only fields are rejected rather than ignored.

{
  "text": "امروز هوا خوب است.",
  "tier": "basic",
  "voice": "kian",
  "pace": 1
}

If Kian or Leyla is temporarily unavailable, the response may contain requested_voice plus a bounded fallback object while voice identifies Ava as the audio actually returned. That substituted result is not reused for a later new request.

Premium speech

Premium accepts a voice from the published voice registry plus optional pace, style, expressiveness, regional_mode, and director_notes. Defaults are pace 1, style neutral, expressiveness natural, regional mode standard, and no director notes.

regional_mode accepts only standard and can be omitted.

{
  "text": "امروز هوا خوب است.",
  "tier": "premium",
  "voice": "Kore",
  "style": "warm",
  "expressiveness": "natural",
  "regional_mode": "standard",
  "director_notes": "Gentle, clear, and unhurried."
}

Premium provider capacity can temporarily return retryable 503 service_unavailable. Honor the response's Retry-After value, reuse the original idempotency key and exact body, and retry sequentially. Stop after three attempts and queue the request for later rather than running an unbounded retry loop.

Download and replay

A successful response includes audio_url, audio_expires_at, mime_type, duration_seconds, the chosen tier and voice, and resolved delivery settings. The MIME type is audio/wav or audio/mpeg; use it when storing or serving the downloaded file. The signed URL expires after one hour. Replaying an idempotent request can issue a fresh URL to the same cached audio without a second usage charge.

The POST is synchronous: it waits for complete generation, storage, and usage settlement, then returns JSON rather than audio bytes or base64. Make a separate GET request to audio_url to download the completed file. That GET has a streamed HTTP response body, but it is not live synthesis streaming and does not provide earlier playback while speech is generated.

Basic can return WAV or MP3: short clips stay lossless, while longer clips may be compressed to reduce download size. Premium returns WAV. Always check mime_type rather than assuming the file format from the voice.

For example, after saving the successful JSON response as speech.json, download it with curl --fail --show-error --output speech.wav "$(jq -r '.audio_url' speech.json)". Use speech.mp3 instead when mime_type is audio/mpeg. The signed URL authorizes this download; do not send your API key to it. Save the file on your server if you need it after the URL expires.

The following is an audio-fields excerpt; the complete response also contains request_id, engine_version, original, and usage.

{
  "audio_url": "https://api.vowelmarks.com/v1/developer/audio?…",
  "audio_expires_at": "2026-08-10T20:00:00.000Z",
  "mime_type": "audio/wav",
  "duration_seconds": 3.42,
  "tier": "premium",
  "voice": "Kore",
  "delivery": {
    "pace": 1,
    "style": "warm",
    "expressiveness": "natural",
    "regional_mode": "standard",
    "director_notes": "Gentle, clear, and unhurried."
  }
}

Asynchronous Basic speech jobs

All job routes require a server-side Bearer key with the speech scope. In production, POST /v1/speech/jobs accepts the same Basic JSON object as POST /v1/speech, but returns 202 Accepted after the VowelMarks Workflow durably queues the job instead of holding the creation request open for synthesis. Idempotency-Key is required. The same key and exact body return the existing job and never start or charge a second generation.

The request schema also documents Premium for development and staging. In production, a valid Premium job request returns non-retryable 503 premium_speech_jobs_unavailable before allowance reservation, storage, or Workflow start. Use synchronous POST /v1/speech for Premium, or submit a Basic job.

curl --request POST https://api.vowelmarks.com/v1/speech/jobs   -H "Authorization: Bearer $VOWELMARKS_API_KEY"   -H "Content-Type: application/json"   -H "Idempotency-Key: narration-section-42"   --data '{"text":"امروز هوا خوب است.","tier":"basic","voice":"kian","pace":1}'

The creation response contains the job ID, request ID, current status, tier, preview format, timestamps, and absolute polling, event-stream, and cancellation URLs. Status is one of queued, running, succeeded, failed, or cancelled. Production Basic jobs use stream_format: "mp3". Premium's published preview format is pcm_s16le, but the current staging experiment runs Premium provider sections in buffered mode: it emits status updates without audio events, then publishes one completed WAV file.

{
  "id": "00000000-0000-4000-8000-000000000042",
  "request_id": "95b…",
  "status": "queued",
  "tier": "basic",
  "stream_format": "mp3",
  "created_at": "2026-09-15T23:00:00.000Z",
  "started_at": null,
  "finished_at": null,
  "status_url": "https://api.vowelmarks.com/v1/speech/jobs/00000000-0000-4000-8000-000000000042",
  "stream_url": "https://api.vowelmarks.com/v1/speech/jobs/00000000-0000-4000-8000-000000000042/events",
  "cancel_url": "https://api.vowelmarks.com/v1/speech/jobs/00000000-0000-4000-8000-000000000042/cancel"
}

Poll GET /v1/speech/jobs/:id when streaming is unnecessary or after a stream interruption. It returns the same job fields. A succeeded job also includes result, usage, audio_url, and audio_expires_at. A failed job includes the normal structured error object. Reading a succeeded job again within its 24-hour job lifetime renews the signed URL without regenerating or charging again.

Follow ordered Basic synthesis events

GET /v1/speech/jobs/:id/events is an authenticated Server-Sent Events response. It sends ordered status, audio, reset, complete, and error events plus heartbeat comments while the job is active. Status data is {"job_id":"…","status":"running"}. A production Basic audio event is {"job_id":"…","format":"mp3","audio_base64":"…"}. The Premium schema reserves pcm_s16le for signed 16-bit little-endian PCM at 24 kHz mono when preview streaming is enabled. In the current staging buffered experiment, Premium emits no audio events before completion. Treat status-only SSE as normal and poll the job until it reaches a terminal state and exposes the finished WAV.

Basic live preview is currently available only when fresh Kian or Leyla synthesis emits audio. Legacy Ava, Mana, or Amir voices, cache hits, and a Kian or Leyla request that completes through a fallback can reach complete without any audio events. Clients must tolerate zero preview chunks and always await complete or poll the final job.

curl --no-buffer   -H "Authorization: Bearer $VOWELMARKS_API_KEY"   -H "Accept: text/event-stream"   -H "Last-Event-ID: 17"   "https://api.vowelmarks.com/v1/speech/jobs/$JOB_ID/events"

A reset event means previously streamed preview audio must be discarded before accepting later chunks. Preview bytes are not the durable completed file and do not settle usage. Use the terminal complete event or a succeeded polling response for the final artifact and accounting result.

A complete event contains the succeeded job object and event: "complete". An error event contains the job ID, failed status, and structured error. Every replayable event has a monotonic positive integer SSE ID. Reconnect with Last-Event-ID, or pass the same value as ?after=<id> when your HTTP client cannot set that header; the query value wins if both are present. A stream disconnect does not cancel the job. End-of-file is not success: if the connection closes before complete or error, reconnect or poll the job until it reaches a terminal status.

Browser playback depends on the preview format. Basic MP3 chunks can be appended only when the browser supports MP3 through Media Source Extensions. Premium pcm_s16le preview data, when enabled in development or staging, is raw PCM rather than MP3 or WAV. The current staging Premium buffered experiment has no live preview audio. Keep polling the accepted job for its completed WAV. Playback or event-stream failure does not cancel generation.

Developer keys remain server-side secrets. Browser EventSource cannot safely carry the Bearer key; open and parse this stream from your backend, then forward only the events your client needs.

Recover, cancel, and retain jobs

POST /v1/speech/jobs/:id/cancel requests best-effort cancellation, returns the current job object, and is safe to repeat. If completion wins the race, the job succeeds and the normal generated duration is charged. If cancellation or failure wins before completion, the reservation is released. Closing the client event stream alone never requests cancellation, and the VowelMarks Workflow continues independently.

For the staging-only Premium experiment, VowelMarks Workflow owns the asynchronous job while provider streaming is disabled. It requests and validates each planned section as a complete buffered response, privately checkpoints completed section audio, and assembles one finished file for the client job. The client should submit once, keep the same job ID, and poll rather than expect live audio. Long-form Premium completion and restart recovery remain under validation.

A job can run for at most two hours. Job input and replayable event metadata remain available for 24 hours. Successfully completed cache audio follows the normal 30-day sliding cache policy, while each signed download URL lasts one hour. Save the completed file or poll the still-live job for a fresh signed URL before job metadata expires.

Completed audio downloads support HEAD, Content-Length, Content-Type, ETag, Accept-Ranges: bytes, If-None-Match, and one byte range with If-Range. A valid range returns 206 Partial Content; an unsatisfiable range returns 416. The signed URL is the complete authorization; do not add the API key.

Both job creation and synchronous speech retain the same 6,000-code-point and 32 KiB JSON limits. The durable Premium path estimates section duration, aims near 150 seconds, and treats an estimated 180 seconds as its section-planning ceiling. Those figures guide planning only; they neither guarantee nor hard-limit the audio duration the provider returns. Asynchronous execution lets one client creation request continue as a Workflow and eventually expose one finished file, but it does not remove provider per-interaction duration, output-size, or quality limits. Section planning and completed-section checkpoints reduce expected interaction size and restart work; they do not guarantee provider completion.

Common creation failures include 400 idempotency_key_required, 402 basic_speech_quota_exhausted, 409 idempotency_conflict, 429 rate_limit_exceeded, and retryable 503 speech_jobs_unavailable or speech_job_start_uncertain. Production Premium job creation returns non-retryable 503 premium_speech_jobs_unavailable with the action “Use the synchronous speech endpoint or submit a Basic speech job.” Authentication and scope failures use 401 or 403. Job reads are organization- and environment-scoped, so an inaccessible job is returned as 404. For an uncertain Basic start, retry the exact body with the original idempotency key.

Long text and mobile integrations

Each request accepts at most 6,000 Unicode code points and the complete serialized JSON body must fit within 32 KiB (32,768 bytes). For longer passages, your server must split the source at sentence or paragraph boundaries, retain the original order, and send a separate request for each section. Count with Array.from(text).length in JavaScript. Spaces, ZWNJ, and combining marks count too; do not split a combining sequence or remove characters when forming sections.

Give each section its own idempotency key, and reuse that exact key and payload only for a retry of that section. Start with sequential requests; limit any later concurrency to your approved capacity and shared rate limit. Completed sections are charged even if a later section fails. Retry only the failed section when its error is retryable.

Download each audio file before its signed URL expires. A failed complete-file GET can be retried with the same signed URL while it remains valid because generation and usage settlement already completed. Play sections in order, or combine compatible audio using an audio tool; concatenating WAV or MP3 file bytes is not a valid general-purpose merge. Voices and pacing may vary slightly between separate generations.

Some speech origins sleep while idle, so the first request can spend several extra seconds waking an origin. Long synthesis can take several minutes. Keep a complete-response deadline of up to 15 minutes for a maximum-size synchronous request, or use shorter sections. If the POST times out, reuse its original idempotency key and exact payload; a new key can start and charge a second generation.

For a mobile or browser app, call VowelMarks from your own backend and deliver the result to the app. Never ship your API key in the client. Treat signed audio URLs as temporary access credentials and keep them out of logs and analytics. Word timings, captions, pronunciation overrides, batch processing, and asynchronous Premium jobs are not part of the current production API contract.

Allowances

  • Basic and Premium seconds are separate from text units.
  • Basic allows one request to finish when at least one second remained at generation start; later requests stop until the allowance resets.
  • Premium reserves a conservative upper bound before generation and is hard-capped.
  • If valid Premium audio exceeds its reservation, VowelMarks discards it, charges nothing, and returns a retryable service error.
  • Speech overage billing is not part of API v1.