Align audio to text using phonetic forced alignment. Upload an audio or video file with a transcript, and receive perfectly timed SRT subtitles. Powered by Sawalign — a phonetic aligner combining wav2vec2 (audio→IPA) with Sawtone (text→IPA) and CTC Viterbi forced alignment.
POST https://api.sawalni.com/v1/speech/align
This is an asynchronous endpoint — it returns a job ID immediately. Poll for status and download the result when done.
Send a multipart/form-data request with the audio file and text.
| Parameter | Type | Required | Description |
|---|---|---|---|
audio | file | Yes | Audio or video file (wav, mp3, flac, mp4, mov, mkv, etc.) |
text | string | Yes | Transcript text (plain text or SRT format — timestamps are stripped automatically) |
Size limits:
curl -X POST https://api.sawalni.com/v1/speech/align \ -H "Authorization: Bearer YOUR_API_KEY" \ -F "audio=@my_recording.wav" \ -F "text=Hello everyone, welcome to the presentation. Today we will cover three main topics."
{ "job_id": "a1b2c3d4e5f67890", "status": "pending", "message": "Alignment job queued. Poll /v1/speech/align/job/{job_id} for status.", "usage": { "audio_bytes": 1567890, "text_bytes": 82 } }
GET https://api.sawalni.com/v1/speech/align/job/{job_id}
The status response includes progress (0.0–1.0) and words_so_far (partial list of aligned words) to enable streaming UI.
{ "job_id": "a1b2c3d4e5f67890", "status": "processing", "step": "Aligning audio to text...", "progress": 0.42, "words_so_far": [ { "word": "Hello", "index": 0, "start": 0.32, "end": 0.68 }, { "word": "everyone", "index": 1, "start": 0.72, "end": 1.24 } ], "usage": { "audio_bytes": 1567890, "text_bytes": 82 } }
{ "job_id": "a1b2c3d4e5f67890", "status": "done", "step": "Alignment complete", "srt_url": "/v1/speech/align/job/a1b2c3d4e5f67890/srt", "usage": { "audio_bytes": 1567890, "text_bytes": 82 } }
GET https://api.sawalni.com/v1/speech/align/job/{job_id}/srt
Returns the SRT file as text/plain. Example output:
1 00:00:00,320 --> 00:00:02,840 Hello everyone, welcome to the presentation. 2 00:00:03,200 --> 00:00:05,760 Today we will cover three main topics.
import requests import time API_KEY = "YOUR_API_KEY" BASE = "https://api.sawalni.com/v1" headers = {"Authorization": f"Bearer {API_KEY}"} # Step 1: Submit alignment with open("recording.wav", "rb") as f: resp = requests.post( f"{BASE}/speech/align", headers=headers, files={"audio": ("recording.wav", f, "audio/wav")}, data={"text": open("transcript.txt").read()}, ) resp.raise_for_status() job_id = resp.json()["job_id"] print(f"Job submitted: {job_id}") # Step 2: Poll until done while True: status = requests.get( f"{BASE}/speech/align/job/{job_id}", headers=headers, ).json() print(f" Status: {status['status']} — {status.get('step', '')}") if status["status"] == "done": break if status["status"] == "error": raise Exception(status.get("error", "Unknown error")) time.sleep(2) # Step 3: Download SRT srt = requests.get( f"{BASE}/speech/align/job/{job_id}/srt", headers=headers, ).text with open("output.srt", "w") as f: f.write(srt) print(f"Saved output.srt ({len(srt)} bytes)")
const API_KEY = 'YOUR_API_KEY'; const BASE = 'https://api.sawalni.com/v1'; const headers = { 'Authorization': `Bearer ${API_KEY}` }; // Step 1: Submit const form = new FormData(); form.append('audio', audioFile); // File or Blob form.append('text', transcriptText); // string const submitRes = await fetch(`${BASE}/speech/align`, { method: 'POST', headers, body: form, }); const { job_id } = await submitRes.json(); // Step 2: Poll let status; do { await new Promise(r => setTimeout(r, 2000)); const res = await fetch(`${BASE}/speech/align/job/${job_id}`, { headers }); status = await res.json(); console.log(status.status, status.step); } while (status.status === 'pending' || status.status === 'processing'); if (status.status === 'error') throw new Error(status.error); // Step 3: Download SRT const srt = await (await fetch(`${BASE}/speech/align/job/${job_id}/srt`, { headers })).text(); console.log(srt);
The text parameter accepts multiple formats — the server auto-detects:
| Format | Example | Behavior |
|---|---|---|
| Plain text | Hello world. This is a test. | Split by sentences/lines |
| Line-per-sentence | Line 1\nLine 2\nLine 3 | Each line becomes a segment |
| SRT format | Full SRT with timestamps | Timestamps stripped, text extracted |
wav, mp3, flac, ogg, m4a, aac, wma, mp4, mov, mkv, avi, webm, m4v
Audio is automatically converted to 16kHz mono WAV and denoised before alignment.
| Status | Meaning |
|---|---|
pending | Queued, waiting for processing |
processing | Actively being aligned |
done | Complete — SRT available for download |
error | Failed — check error field for details |
Jobs and their files are retained for 24 hours after completion, then automatically cleaned up.
| HTTP Code | Meaning |
|---|---|
| 202 | Job accepted and queued |
| 400 | Invalid input (unsupported format, empty text, file too large) |
| 404 | Job not found |
| 409 | Job not yet complete (returned for SRT download on pending/processing jobs) |
| 413 | Audio file exceeds 128 MB limit |
| 422 | Job failed — check the error detail |
Speech alignment is metered by audio file size (bytes uploaded).
| Metric | Rate |
|---|---|
| Audio bytes | 0.1 credits per 1 MB |
Example: a 10 MB audio file costs 1.0 credits.