Skip to content

Build with Speech Engine

Create an authorised personal voice and generate private narration with a scoped API key.

Before you begin#

Create a live key in API keys and explicitly grant speech.read, speech.voices.write and speech.generate. Existing keys do not inherit these private-voice permissions. Never put a key in browser code, URLs or logs. Revoke it in the same key manager; subsequent requests will fail authentication.

Your account needs AI voiceovers and enough credits. Voice profiles are private to the owner by default, including when a team credit pool funds the job. The owner can separately approve a preview and explicitly publish a voice from the signed-in Speech Lab. Developer keys and agents cannot grant that publication consent. Developer-bot end users and test-key inference are not enabled in the initial pilot.

Permission and recording#

Show the exact consent text returned by capabilities and obtain explicit permission from the voice owner. Send consent=true and the current consentVersion. Consent records the account and time; it does not prove legal identity. Agents must send the person to Speech Lab to enroll rather than granting consent for them.

Use WAV, MP3, M4A or WebM, a sample rate of 16 kHz or higher mono/stereo, 10 to 120 seconds and 25 MB maximum. Aim for 20 to 30 seconds of one person speaking naturally without music. The worker checks decoding, clipping, detected speech and an estimated background-noise ratio. It prepares a reusable reference and an actual synthesis preview; it does not retrain a foundation model.

End-to-end examples#

Set VIDMOAT_KEY as a server-side environment variable. These examples check HTTP errors, enroll an authorised recording, poll its real job, generate speech, and download it. Replace my-voice.wav with a recording you are permitted to use. Never copy the example consent flag without the person’s permission.

cURL
# Requires curl and jq. Never echo the key or signed audio URL.
BASE=https://api.vidmoat.com/v1/speech
curl --fail-with-body "$BASE/capabilities" -H "Authorization: Bearer $VIDMOAT_KEY"
curl --fail-with-body "$BASE/voices" -H "Authorization: Bearer $VIDMOAT_KEY"
CONSENT_VERSION=$(curl --fail-with-body -sS "$BASE/capabilities" -H "Authorization: Bearer $VIDMOAT_KEY" | jq -r '.consent.version')
ENROLL=$(curl --fail-with-body -sS "$BASE/voices" -H "Authorization: Bearer $VIDMOAT_KEY" -H "Idempotency-Key: $(uuidgen)" -F '[email protected]' -F 'name=My voice' -F 'language=en' -F 'consent=true' -F "consentVersion=$CONSENT_VERSION")
VOICE_ID=$(printf '%s' "$ENROLL" | jq -r .voiceId)
wait_job() {
  for attempt in $(seq 1 400); do
    JOB=$(curl --fail-with-body -sS "$BASE/generations/$1" -H "Authorization: Bearer $VIDMOAT_KEY") || return 1
    STATE=$(printf '%s' "$JOB" | jq -r .status)
    case "$STATE" in succeeded) return 0;; failed|cancelled) printf '%s' "$JOB" | jq .error >&2; return 1;; esac
    sleep 5
  done
  echo 'Polling timed out; keep the job ID and resume polling later.' >&2; return 1
}
wait_job "$(printf '%s' "$ENROLL" | jq -r .id)" || exit 1
GEN=$(curl --fail-with-body -sS "$BASE/generations" -H "Authorization: Bearer $VIDMOAT_KEY" -H 'Content-Type: application/json' -H "Idempotency-Key: $(uuidgen)" --data "$(jq -nc --arg voice "$VOICE_ID" '{voiceId:$voice,text:"Every story starts with a voice.",speed:1}')")
ID=$(printf '%s' "$GEN" | jq -r .id)
wait_job "$ID" || exit 1
URL=$(curl --fail-with-body -sS "$BASE/generations/$ID/audio" -H "Authorization: Bearer $VIDMOAT_KEY" | jq -r .url)
curl --fail-with-body "$URL" -o narration.wav

Jobs, credits and recovery#

POSTs return 202 with a durable job ID. Keep both that ID and your Idempotency-Key. Poll every five seconds. A transport timeout is not a model failure: query/retry the same request key; a different key buys a different job. A failed/cancelled job is terminal and refunds its reserved amount exactly once.

Generation uses configured credits per started 1,000 Unicode characters, measured after the complete script is validated. Enrollment has its own configured rate. GET capabilities shows both; there is no hardcoded public price. Six submissions/minute, 50/day, two active jobs per account; a shared bounded worker queue also applies. Redis failure refuses new spending. History and Usage show credits, failures and successful audio duration.

HTTP / codeRecovery
401 unauthorizedCheck or replace the key; a revoked key cannot be reused.
403 insufficient_scopeGrant the named scope on the existing key/app.
402 plan_required / insufficient_credits / quota_exceededCheck plan, credits and active job count; do not loop retries.
409 conflictCheck whether the key was reused for different inputs or the job already started.
413 payload_too_largeShorten the script/recording.
422 prompt_rejectedRevise the refused input; nothing was queued.
429 rate_limitedHonour Retry-After, or error.retry_after.
503 unavailableBack off; preserve the request key. Check capabilities before new spending.

Private files and editor use#

Download URLs expire after 900 seconds and must not be logged or published. Get a fresh URL through the authenticated API. Deletion immediately revokes previous links; file cleanup is retried. Deleting a voice also deletes its generated audio, so projects using it will need replacement audio.

To place speech in an edit, call POST /v1/speech/generations/{id}/import with projectId, requiring speech.read and projects.write. Then use the existing project command API to add an audio clip with the returned URL and measured result.duration. This shares only the generated audio with that project. Vidmoat renews its private source on playback/export; the voice reference remains private.

Shared voices and distribution#

GET /v1/speech/voices?source=community lists up to 20 published voices. Optional q searches the public title, creator and description; follow nextCursor with cursor. Published voices can be used by passing their id to the normal generation endpoint. The generated job and credit charge belong to the caller; recordings and reusable references are never exposed.

Owners publish or withdraw from Speech Lab, after approving their enrollment preview and giving separate publication permission. A public /voices/{id} page provides attribution and discovery. GET /v1/speech/voices/{id}/usage requires speech.read and ownership, and returns generations, creators and ownGenerations as aggregate counts of completed audio jobs. It does not expose other users or their scripts. Sharing is permission to use a voice, not a verified identity or royalty programme. Withdrawal stops new jobs and preview access; accepted jobs and existing audio may remain until the voice is deleted.

Review a narration draft#

POST /v1/speech/directions accepts voiceId, brief (up to 2,000 characters), optional currentScript, speed and supported expression, with speech.generate and an Idempotency-Key. It reserves capabilities.pricing.direction credits. Poll /v1/speech/generations/{id}; a succeeded direction job returns result.reply, result.script and result.speed, with applied=false and audioGenerated=false. Present that draft for review, then separately submit the chosen text to /generations. This never generates audio or publishes a voice automatically. Invalid/failed drafts refund the reserved credits.

Agent workflow#

MCP and web/native/channel agents use agent_workspace: speech_voices → speech_generate → speech_status → speech_prepare. speech_generate requires speechVoiceId, speechText and a stable requestId; speechSpeed/speechLanguage are optional, and speechExpression is optional when capabilities.settings.expression is present. speech_prepare returns an addClip command, not a claim that an edit already happened. Apply it through the existing host and verify the audio. Programmatic credentials still need explicit speech scopes; preparing an edit also needs projects.write.

Limits and quality#

The English pilot uses a separately validated Chatterbox English 0.1.6 direct-reference runtime when enabled, with compatible OpenVoice V2/Melo jobs retained. Check capabilities.pipeline and settings before sending controls. New expressive output is mono 24 kHz PCM WAV and uses the model’s Perth watermark. Legacy output is mono 22.05 kHz. Pace is 0.75 to 1.25; expressive pace adjustment preserves pitch. Optional expression ranges from 0 to 1: subtle .25, balanced .5 and animated .75. These are delivery intensity controls, not guaranteed emotions. An unsupported expression input is rejected. This is reference conditioning, not dedicated per-user training. Cloning can still differ from the speaker, so review previews before publishing. Other upstream languages and Ghanaian accents/Pidgin require end-to-end evaluation before being advertised.

The full endpoint contracts are under API reference → Speech Engine. For a job failure, retain its job ID and error code and contact support. Do not send private recordings, scripts or API keys in logs.