Devniks Voice API
Yiddish-first speech-to-text and text-to-speech over plain HTTPS and JSON. Free, with daily limits per key.
Quick start
- Create an account and accept the Terms.
- Create a key on the API keys page.
- Send your first file:
curl https://voice.devniks.com/v1/asr \ -H "Authorization: Bearer $DEVNIKS_API_KEY" \ -F file=@shiur.mp3 \ -F language=yi \ -F wait=true
import os, requests
r = requests.post(
"https://voice.devniks.com/v1/asr",
headers={"Authorization": f"Bearer {os.environ['DEVNIKS_API_KEY']}"},
files={"file": open("shiur.mp3", "rb")},
data={"language": "yi", "wait": "true"},
)
r.raise_for_status()
for seg in r.json()["segments"]:
print(seg["start"], seg["text"])const form = new FormData();
form.append("file", fileInput.files[0]);
form.append("language", "yi");
form.append("wait", "true");
const res = await fetch("https://voice.devniks.com/v1/asr", {
method: "POST",
headers: { Authorization: `Bearer ${process.env.DEVNIKS_API_KEY}` },
body: form,
});
const job = await res.json();
console.log(job.segments.map(s => s.text).join("\n"));Authentication
Send your key in the Authorization header on every request. Keys start with dv_live_. Keep them on your server, never in a public page or app. If a key leaks, revoke it on the API keys page and create a new one.
Authorization: Bearer dv_live_xxxxxxxxxxxxxxxxxxxx
Transcribe audio
Upload audio as multipart/form-data. Short files (under 60 s) can wait for the result with wait=true; longer files return 202 with a job id: poll GET /v1/asr/{id} or use a webhook.
| Field | Type | Description |
|---|---|---|
file | file, required | MP3, WAV, M4A, OGG, FLAC, MP4. Max 500 MB / 2 hours. |
language | string | yi, he, en or auto (default). |
timestamps | string | segment (default) or word. |
wait | boolean | Hold the connection until done (files under 60 s). Default false. |
webhook_url | string | Optional. We POST asr.completed here. |
Response
{
"id": "asr_01HF8Q3K2Z",
"status": "done",
"language": "yi",
"duration": 42.3,
"model": "devniks-asr-2.3",
"text": "שלום עליכם, דאס איז א מעלדונג פון די שול. ...",
"segments": [
{ "id": 0, "start": 0.0, "end": 4.2, "confidence": 0.97,
"text": "שלום עליכם, דאס איז א מעלדונג פון די שול." },
{ "id": 1, "start": 4.2, "end": 8.9, "confidence": 0.81,
"text": "מנחה וועט היינט זיין פינף מינוט פאר שקיעה." }
],
"created_at": "2026-10-27T13:12:04Z"
}
Get a transcript
Returns the same object. status is queued, processing, done or failed. Add ?format=srt, vtt or txt to get subtitles or plain text instead of JSON.
Submit a correction
Send corrected text for one or more segments (or the full text). We keep the original model output next to your correction. Corrections earn the same free credits as on the web: +1 minute per corrected audio minute, after a spot check.
curl -X PATCH https://voice.devniks.com/v1/asr/asr_01HF8Q3K2Z \
-H "Authorization: Bearer $DEVNIKS_API_KEY" \
-H "Content-Type: application/json" \
-d '{"segments": [{"id": 2, "text": "נאך מעריב איז דא א שיעור אין גמרא בבא מציעא."}]}' requests.patch(
"https://voice.devniks.com/v1/asr/asr_01HF8Q3K2Z",
headers={"Authorization": f"Bearer {key}"},
json={"segments": [{"id": 2, "text": "נאך מעריב איז דא א שיעור אין גמרא בבא מציעא."}]},
)await fetch("https://voice.devniks.com/v1/asr/asr_01HF8Q3K2Z", {
method: "PATCH",
headers: { Authorization: `Bearer ${key}`, "Content-Type": "application/json" },
body: JSON.stringify({ segments: [{ id: 2, text: "נאך מעריב איז דא א שיעור אין גמרא בבא מציעא." }] }),
});Response
{
"id": "asr_01HF8Q3K2Z",
"corrected_segments": 1,
"credit": { "minutes": 0.1, "status": "pending_review" }
}
Generate speech
| Field | Type | Description |
|---|---|---|
text | string, required | Up to 5,000 characters. Yiddish, Hebrew or English. |
voice | string, required | A voice id from GET /v1/voices. |
format | string | mp3 (default) or wav (24 kHz). |
speed | number | 0.6 to 1.6. Default 1.0. |
pitch | integer | -6 to 6 semitones. Default 0. |
Returns the audio file. The X-Devniks-Request-Id header is the id to rate it with POST /v1/tts/{id}/rating ({"good": false, "words": [3], "issue_type": "pronunciation"}). If generation takes longer than 60 s you get 202 with a JSON job instead: poll GET /v1/tts/{id} and download from GET /v1/tts/{id}/audio.
curl https://voice.devniks.com/v1/tts \
-H "Authorization: Bearer $DEVNIKS_API_KEY" \
-H "Content-Type: application/json" \
-d '{"text": "ברוכים הבאים צו דעווניקס.", "voice": "shmuel", "format": "mp3"}' \
--output welcome.mp3r = requests.post(
"https://voice.devniks.com/v1/tts",
headers={"Authorization": f"Bearer {key}"},
json={"text": "ברוכים הבאים צו דעווניקס.", "voice": "shmuel",
"format": "mp3", "speed": 1.0},
)
open("welcome.mp3", "wb").write(r.content)
print(r.headers["X-Devniks-Request-Id"])const res = await fetch("https://voice.devniks.com/v1/tts", {
method: "POST",
headers: { Authorization: `Bearer ${key}`, "Content-Type": "application/json" },
body: JSON.stringify({ text: "ברוכים הבאים צו דעווניקס.", voice: "shmuel", format: "mp3" }),
});
const audio = new Audio(URL.createObjectURL(await res.blob()));
audio.play();List voices
{
"voices": [
{ "id": "shmuel", "name": "Shmuel", "style": "warm", "languages": ["yi", "he", "en"] },
{ "id": "dovid", "name": "Dovid", "style": "narrator", "languages": ["yi", "he", "en"] },
{ "id": "kol", "name": "Kol", "style": "neutral", "languages": ["yi", "he", "en"] },
{ "id": "vc_7Hk2", "name": "My voice", "style": "cloned", "private": true }
]
}
Cloned voices are created in the web app (with the speaker-consent step) and are visible only to your account.
Rate limits (free tier)
| Limit | Free tier, per key |
|---|---|
| Requests | 60 per minute |
| ASR audio | 120 minutes per day |
| TTS text | 50,000 characters per day |
| Concurrent ASR jobs | 2 |
| Keys per account | 5 |
Every response carries X-RateLimit-Remaining and X-RateLimit-Reset. GET /v1/usage returns what is left today for the key. Need more for a community project? Write to us; corrections you send back also raise your limits.
Errors
| HTTP | code | Meaning |
|---|---|---|
| 400 | invalid_request | A field is missing or wrong. |
| 401 | unauthorized | Missing, wrong or revoked key. |
| 403 | consent_required | The account hasn't accepted the current Terms. |
| 404 | not_found | No such transcript or voice on your account. |
| 413 | file_too_large | Over 500 MB or 2 hours. |
| 415 | unsupported_media | We couldn't read the audio. |
| 429 | rate_limited / quota_exceeded | Too many requests, or the daily limit is used up. |
| 500 / 503 | server_error / model_busy | Our side. Safe to retry with backoff. |
{
"error": {
"code": "quota_exceeded",
"message": "Daily free ASR limit reached (120 min). Resets 00:00 UTC.",
"request_id": "req_9kd2Lq"
}
}
Webhooks (optional)
Pass webhook_url (HTTPS) with an ASR job and we POST asr.completed or asr.failed to it when the job finishes. Check the signature with your webhook secret (shown on the API keys page) and reply 2xx within 10 s; we retry up to 5 times. The same event can arrive twice, so dedupe on delivery_id.
POST https://your-app.example.com/hooks/devniks
X-Devniks-Event: asr.completed
X-Devniks-Delivery: evt_01jb8q3k2z...
X-Devniks-Signature: t=1761570724,v1=5f2b9c... (HMAC-SHA256 of "t.body" with your webhook secret)
{ "event": "asr.completed", "delivery_id": "evt_01jb8q3k2z...", "id": "asr_01HF8Q3K2Z", "status": "done" }
Verify in Python: hmac.compare_digest(v1, hmac.new(secret.encode(), f"{t}.{raw_body}".encode(), "sha256").hexdigest()), and reject timestamps older than 5 minutes.
Your data
DELETE /v1/asr/{id}, or everything from Settings.