VoiceMaker

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

LanguageCodeLive
EnglishenYes
YorubayoYes
HausahaYes
Nigerian PidginpcmYes
IgboigComing soon

Pass auto (the default) to detect the language automatically; the detected code is returned as detected_language.

Submit a transcription

POST/dev/v1/transcribe

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

ParameterTypeStatusDescription
audiostring (URL) or fileRequiredA publicly accessible audio URL (JSON) or an uploaded file (multipart).
languagestringOptionalLanguage code, or auto to detect. Default auto.
webhookUrlstringOptionalHTTPS URL notified when the job finishes (JSON requests only).
cURL
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
curl -X POST https://api.myvoicemaker.ai/dev/v1/transcribe \
  -H "Authorization: Bearer vmk_live_a1b2c3d4e5f6_8h9i0jKLmnOPqrSt" \
  -F "audio=@./interview.mp3" \
  -F "language=ha"
Python
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

JSON
{
  "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

GET/dev/v1/transcribe/{job_id}

Poll this endpoint until status is completed or failed. Requires the TRANSCRIPTIONS_READ scope.

cURL
curl -X GET https://api.myvoicemaker.ai/dev/v1/transcribe/trans_1a2b3c4d5e6f7g8h9i0j \
  -H "Authorization: Bearer vmk_live_a1b2c3d4e5f6_8h9i0jKLmnOPqrSt"

Response — 200 Completed

JSON
{
  "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

GET/dev/v1/transcriptions

Returns your transcription jobs, newest first, with cursor pagination.

ParameterTypeStatusDescription
limitintegerOptionalItems per page, 1–100. Default 20.
cursorstringOptionalPagination cursor from next_cursor.
statusstringOptionalFilter by QUEUED, PROCESSING, COMPLETED, or FAILED.
JSON
{
  "requestId": "1c2d3e4f-5a6b-7c8d-9e0f-1a2b3c4d5e6f",
  "items": [
    { "job_id": "trans_1a2b3c...", "status": "completed", "text": "Sannu da zuwa." }
  ],
  "next_cursor": "eyJjcmVhdGVkQXQiOiIyMDI2LTAzLTAzVDEwOjMwOjAwWiJ9"
}

Real-time streaming

GET/dev/v1/transcribe/stream

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.

Code
wss://api.myvoicemaker.ai/dev/v1/transcribe/stream?language=yo&sample_rate=16000

Connection parameters

ParameterTypeStatusDescription
languagestringRequireden, yo, ha, ig or pcm. Auto-detection is batch-only.
sample_rateintegerOptionalSample rate of your PCM audio, 8000–48000. Default 16000.
encodingstringOptionallinear16 (raw PCM 16-bit little-endian mono) — the only supported value today.
interim_resultsbooleanOptionalEmit revisable interim transcripts. Default true.

Authentication

Pick whichever channel your client supports (checked in this order):

  1. Authorization: Bearer vmk_... header — servers and SDKs.
  2. 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 echoes vmk.v1 back.
  3. ?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:

MessageEffect
{"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:

JSON
{"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

CodeMeaning
1000Normal end (always preceded by session.end).
4002Purchased credits exhausted mid-stream.
4008Idle: no audio or KeepAlive for 30 seconds.
4009Plan's maximum stream duration reached.
1011Server or internal error.
1013Server at capacity — retry shortly.
1012Server 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

TypeScript
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();
Python
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)