Transcribe audio and video files to text using Daktilo, an Arabic-optimized speech recognition model fine-tuned for Arabic dialects.
POST https://api.sawalni.com/v1/speech/transcribe
Streaming transcription is available over WebSocket:
WS wss://api.sawalni.com/v1/speech/transcribe/stream
Batch mode is recommended for final offline transcription. Streaming mode is designed for live microphone UX and returns partial hypotheses that may revise until the final done event.
Send a multipart/form-data request with the audio file and optional parameters.
| Parameter | Type | Required | Description |
|---|---|---|---|
audio | file | Yes | Audio or video file (wav, mp3, flac, mp4, mov, mkv, etc.) |
language | string | No | ISO 639-1 language code. Default: ar |
model | string | No | Model identifier. Default: daktilo. Compatibility aliases: daktilo-v1, daktilo-v2. |
Size limit: Audio files up to 100 MB.
curl -X POST https://api.sawalni.com/v1/speech/transcribe \ -H "Authorization: Bearer YOUR_API_KEY" \ -F "[email protected]" \ -F "language=ar" \ -F "model=daktilo"
import requests response = requests.post( "https://api.sawalni.com/v1/speech/transcribe", headers={"Authorization": "Bearer YOUR_API_KEY"}, files={"audio": ("recording.wav", open("recording.wav", "rb"), "audio/wav")}, data={"language": "ar", "model": "daktilo"}, ) result = response.json() print(result["text"]) print(f"Duration: {result['duration']}s")
const formData = new FormData(); formData.append('audio', audioFile); formData.append('language', 'ar'); formData.append('model', 'daktilo'); const response = await fetch('https://api.sawalni.com/v1/speech/transcribe', { method: 'POST', headers: { 'Authorization': 'Bearer YOUR_API_KEY' }, body: formData, }); const result = await response.json(); console.log(result.text); console.log('Duration:', result.duration, 's');
Use wss://api.sawalni.com/v1/speech/transcribe/stream for live microphone transcription. The browser authentication path uses WebSocket subprotocols because browsers cannot set an Authorization header on WebSocket connections. Batch mode remains best when you need the most stable offline final transcript.
The first client text frame must be JSON:
{ "type": "start", "model": "daktilo", "language": "ar", "sample_rate": 16000, "channels": 1, "audio_format": "pcm_s16le" }
After the start frame, send binary frames containing little-endian PCM16 mono audio at 16 kHz. Send {"type":"flush"} to request a partial decode and {"type":"stop"} to finish the stream.
| Event | Meaning |
|---|---|
ready | Session accepted and routed to a Daktilo deployment |
partial | Unstable live hypothesis, may revise |
sync | Corrected transcript snapshot emitted during longer streams |
final | Final committed text delta |
usage | Metering data such as audio seconds and processing milliseconds |
done | Final transcript and usage summary |
error | Machine-readable error and detail |
const protocol = 'sawalni.transcribe.v1'; const keyProtocol = 'sawalni-key.' + btoa('YOUR_API_KEY') .replace(/\+/g, '-') .replace(/\//g, '_') .replace(/=+$/, ''); const ws = new WebSocket( 'wss://api.sawalni.com/v1/speech/transcribe/stream', [protocol, keyProtocol] ); ws.onopen = () => { ws.send(JSON.stringify({ type: 'start', model: 'daktilo', language: 'ar', sample_rate: 16000, channels: 1, audio_format: 'pcm_s16le', })); }; ws.onmessage = (event) => { const message = JSON.parse(event.data); if (message.type === 'partial') console.log('partial:', message.text); if (message.type === 'sync') console.log('sync:', message.text); if (message.type === 'final') console.log('final:', message.text_delta); if (message.type === 'done') console.log('done:', message.text); };
import asyncio import json import wave import websockets async def main(): async with websockets.connect( "wss://api.sawalni.com/v1/speech/transcribe/stream", extra_headers={"Authorization": "Bearer YOUR_API_KEY"}, subprotocols=["sawalni.transcribe.v1"], ) as ws: await ws.send(json.dumps({ "type": "start", "model": "daktilo", "language": "ar", "sample_rate": 16000, "channels": 1, "audio_format": "pcm_s16le", })) with wave.open("recording.wav", "rb") as wav: assert wav.getframerate() == 16000 and wav.getnchannels() == 1 while chunk := wav.readframes(3200): await ws.send(chunk) await ws.send(json.dumps({"type": "stop"})) async for message in ws: print(message) asyncio.run(main())
| Code | Meaning |
|---|---|
1008 | Authentication, payment, or entitlement failure |
1003 | Invalid frame or unsupported audio format |
1013 | No healthy deployment capacity; retry shortly |
1011 | Upstream or inference failure |
Streaming transcription is metered by processed audio seconds reported by the backend in usage and done events. Streaming audio blobs are not persisted by the balancer.
{ "text": "مرحبا بكم في هذا التسجيل الصوتي", "language": "ar", "model": "daktilo", "duration": 5.76, "usage": { "audio_bytes": 184320, "audio_seconds": 5.76, "processing_ms": 1230.5 } }
| Field | Type | Description |
|---|---|---|
text | string | Transcribed text |
language | string | Language code used for transcription |
model | string | Model identifier used |
duration | number | Audio duration in seconds |
usage | object | Metering data |
usage.audio_bytes | integer | Size of uploaded audio in bytes |
usage.audio_seconds | number | Duration of audio in seconds |
usage.processing_ms | number | Server-side processing time in milliseconds |
wav, mp3, flac, ogg, m4a, aac, wma, mp4, mov, mkv, avi, webm, m4v
Audio is automatically converted to 16kHz mono WAV via FFmpeg before processing.
Daktilo supports 90+ languages with specialized optimization for Arabic dialects. Common language codes:
| Code | Language |
|---|---|
ar | Arabic (all dialects) |
en | English |
fr | French |
de | German |
es | Spanish |
it | Italian |
pt | Portuguese |
ja | Japanese |
ko | Korean |
zh | Chinese |
ru | Russian |
tr | Turkish |
hi | Hindi |
| Property | Value |
|---|---|
| Model ID | daktilo |
| Aliases | daktilo-v1, daktilo-v2 |
| Specialization | Arabic dialects |
| HTTP Code | Meaning |
|---|---|
| 200 | Success — transcription returned |
| 400 | Empty audio file |
| 413 | Audio file exceeds 100 MB limit |
| 422 | Audio conversion failed (unsupported format or corrupted file) |
| 503 | Model not loaded or no healthy deployments |
| 504 | Transcription timed out |
Speech transcription is metered by audio duration (seconds of audio processed).
| Metric | Rate |
|---|---|
| Audio seconds | $6.00 per 1M seconds (~$0.006/min) |
This is competitive with major cloud ASR providers while offering specialized Arabic dialect support.
language parameter for best accuracy. Auto-detection works but explicit language codes produce better results.