Transcription (ASR)
Convert audio files to text. VoiceMaker's automatic speech recognition supports Yoruba, Hausa, Nigerian Pidgin, and Nigerian English, with automatic language detection. Transcription runs asynchronously: submit a job, then poll for the result or receive a webhook.
Supported audio formats
MP3, WAV, M4A, OGG, WEBM. Maximum file size depends on your plan (see
Rate Limits).
Languages & codes
| Language | Code | Live |
|---|---|---|
| English | en | Yes |
| Yoruba | yo | Yes |
| Hausa | ha | Yes |
| Nigerian Pidgin | pcm | Yes |
| Igbo | ig | Coming soon |
Pass auto (the default) to detect the language automatically; the detected code
is returned as detected_language.
Submit a transcription
Send either a JSON body with a public audio URL, or multipart/form-data with an
uploaded file. Requires the TRANSCRIPTIONS_WRITE scope.
Request parameters
| Parameter | Type | Status | Description |
|---|---|---|---|
audio | string (URL) or file | Required | A publicly accessible audio URL (JSON) or an uploaded file (multipart). |
language | string | Optional | Language code, or auto to detect. Default auto. |
webhookUrl | string | Optional | HTTPS URL notified when the job finishes (JSON requests only). |
curl -X POST https://api.myvoicemaker.ai/dev/v1/transcribe \
-H "Authorization: Bearer vmk_live_a1b2c3d4e5f6_8h9i0jKLmnOPqrSt" \
-H "Content-Type: application/json" \
-d '{
"audio": "https://storage.example.com/interview.wav",
"language": "ha",
"webhookUrl": "https://myapp.com/webhooks/voicemaker"
}'Upload a local file instead with multipart/form-data:
curl -X POST https://api.myvoicemaker.ai/dev/v1/transcribe \
-H "Authorization: Bearer vmk_live_a1b2c3d4e5f6_8h9i0jKLmnOPqrSt" \
-F "audio=@./interview.mp3" \
-F "language=ha"from myvoicemaker import VoiceMaker
client = VoiceMaker(api_key="vmk_live_a1b2c3d4e5f6_8h9i0jKLmnOPqrSt")
job = client.asr.transcribe(
"https://storage.example.com/interview.wav",
language="ha",
webhook_url="https://myapp.com/webhooks/voicemaker",
)
print(job.job_id)Response — 202 Accepted
{
"requestId": "8f1c2d3e-4a5b-6c7d-8e9f-0a1b2c3d4e5f",
"job_id": "trans_1a2b3c4d5e6f7g8h9i0j",
"status": "queued",
"language": "ha",
"detected_language": null,
"text": null,
"confidence": null,
"duration_seconds": null,
"credits_used": null,
"created_at": "2026-03-03T10:30:00Z",
"completed_at": null
}Get a transcription result
Poll this endpoint until status is completed or failed. Requires the
TRANSCRIPTIONS_READ scope.
curl -X GET https://api.myvoicemaker.ai/dev/v1/transcribe/trans_1a2b3c4d5e6f7g8h9i0j \
-H "Authorization: Bearer vmk_live_a1b2c3d4e5f6_8h9i0jKLmnOPqrSt"Response — 200 Completed
{
"requestId": "9a8b7c6d-5e4f-3a2b-1c0d-9e8f7a6b5c4d",
"job_id": "trans_1a2b3c4d5e6f7g8h9i0j",
"status": "completed",
"language": "ha",
"detected_language": "ha",
"text": "Sannu da zuwa VoiceMaker.",
"confidence": 0.96,
"duration_seconds": 4.2,
"credits_used": 21,
"created_at": "2026-03-03T10:30:00Z",
"completed_at": "2026-03-03T10:30:18Z"
}List transcriptions
Returns your transcription jobs, newest first, with cursor pagination.
| Parameter | Type | Status | Description |
|---|---|---|---|
limit | integer | Optional | Items per page, 1–100. Default 20. |
cursor | string | Optional | Pagination cursor from next_cursor. |
status | string | Optional | Filter by QUEUED, PROCESSING, COMPLETED, or FAILED. |
{
"requestId": "1c2d3e4f-5a6b-7c8d-9e0f-1a2b3c4d5e6f",
"items": [
{ "job_id": "trans_1a2b3c...", "status": "completed", "text": "Sannu da zuwa." }
],
"next_cursor": "eyJjcmVhdGVkQXQiOiIyMDI2LTAzLTAzVDEwOjMwOjAwWiJ9"
}Real-time streaming
Stream live audio over a WebSocket (wss://) and receive transcripts as you
speak: interim results within about a second, followed by stable finals.
Requires the TRANSCRIPTIONS_WRITE scope.
wss://api.myvoicemaker.ai/dev/v1/transcribe/stream?language=yo&sample_rate=16000Connection parameters
| Parameter | Type | Status | Description |
|---|---|---|---|
language | string | Required | en, yo, ha, ig or pcm. Auto-detection is batch-only. |
sample_rate | integer | Optional | Sample rate of your PCM audio, 8000–48000. Default 16000. |
encoding | string | Optional | linear16 (raw PCM 16-bit little-endian mono) — the only supported value today. |
interim_results | boolean | Optional | Emit revisable interim transcripts. Default true. |
Authentication
Pick whichever channel your client supports (checked in this order):
Authorization: Bearer vmk_...header — servers and SDKs.Sec-WebSocket-Protocol: vmk.v1, vmk.key.vmk_...— recommended for browsers, which cannot set headers on WebSockets; keeps the key out of URLs and access logs. The server echoesvmk.v1back.?api_key=vmk_...query parameter — last resort; the key will appear in server access logs.
Auth, plan and credit checks happen before the upgrade, so failures are
plain HTTP responses (401, 402, 403, 429 with stream_concurrency_limit,
or 503 when streaming is not enabled on the deployment).
Sending audio
Send binary frames of raw PCM16LE mono audio at your declared
sample_rate — 50–250 ms per frame works well. Text frames carry JSON control
messages:
| Message | Effect |
|---|---|
{"type": "KeepAlive"} | Resets the 30s idle timer without sending audio. |
{"type": "Finalize"} | Forces pending words into a final transcript; the stream stays open. Acked with {"type": "finalized"}. |
{"type": "CloseStream"} | Flushes remaining audio, emits the last finals and session.end, then closes with code 1000. |
Receiving transcripts
All server messages are JSON text frames:
{"type": "session.begin", "session_id": "stream_...", "request_id": "...",
"language": "yo", "sample_rate": 16000, "encoding": "linear16",
"interim_results": true, "max_duration_seconds": 1800, "credits_per_second": 5}
{"type": "transcript", "text": "bawo ni", "is_final": false,
"start": 1.02, "end": 2.48, "confidence": null}
{"type": "transcript", "text": "Bawo ni o.", "is_final": true,
"start": 1.02, "end": 2.9, "confidence": null}
{"type": "session.end", "session_id": "stream_...", "duration_seconds": 94,
"credits_used": 470, "reason": "client_close"}Interim transcripts (is_final: false) may be revised by later events; final
transcripts never change. session.end is always sent before the socket
closes; reason is one of client_close, idle_timeout, max_duration,
insufficient_credits, error, server_shutdown.
Errors surface as {"type": "error", "code": "...", "message": "..."} followed
by a close.
Close codes
| Code | Meaning |
|---|---|
1000 | Normal end (always preceded by session.end). |
4002 | Purchased credits exhausted mid-stream. |
4008 | Idle: no audio or KeepAlive for 30 seconds. |
4009 | Plan's maximum stream duration reached. |
1011 | Server or internal error. |
1013 | Server at capacity — retry shortly. |
1012 | Server restarting — reconnect. |
Billing & limits
Streaming bills purchased credits only, at the same per-second rate as
batch transcription, metered on streamed audio time. Credits are reserved in
60-second blocks while you stream and settled to the exact ceil(seconds) when
the session ends — unused reserve is refunded immediately. Each connection also
counts as one request against your per-minute rate limit.
Concurrent streams and the maximum stream duration depend on your plan (up to 5 concurrent sessions and 60 minutes per stream on Pro; Enterprise limits are customizable). A stream never outlives 60 minutes — reconnect to continue.
When the session ends, the assembled transcript is stored like any other job:
retrieve it later with GET /dev/v1/transcribe/{job_id} or in
GET /dev/v1/transcriptions listings.
SDK example
const stream = client.asr.stream({ language: 'yo', sampleRate: 16000 });
stream.on('transcript', (t) => console.log(t.is_final ? 'FINAL' : 'interim', t.text));
stream.sendAudio(pcmChunk); // PCM16LE mono, 50-250ms per chunk
const summary = await stream.close();with client.asr.stream(language="yo", sample_rate=16000) as conn:
threading.Thread(target=lambda: send_chunks(conn), daemon=True).start()
for event in conn:
if isinstance(event, StreamTranscript) and event.is_final:
print(event.text)