API sənədləri
Audio faylı və ya video linkini mətnə (ASR), mətni isə səsə (TTS) çevirmək üçün REST API.
Bütün sorğular X-API-Key header-i ilə gedir.
1 API açarı
Açar yalnız yaradılanda bir dəfə göstərilir — saxlayın. Balans bitənə qədər işləyir, istənilən vaxt ləğv edilə bilər.
import requests
BASE = "https://milligram.az/voice" # birbaşa server: http://SERVER_IP:4009
KEY = "asr_..." # yuxarıda yaratdığınız açar
H = {"X-API-Key": KEY}
me = requests.get(f"{BASE}/me", headers=H).json()
print(me["balance"], "saniyə balans")
BASE=https://milligram.az/voice
KEY=asr_...
curl -s "$BASE/me" -H "X-API-Key: $KEY"
# {"id":1,"username":"...","balance":33651}
2temp_id və record_id nədir?
| Nömrə | Haradan gəlir | Harada işlənir | Nə qədər yaşayır |
|---|---|---|---|
temp_id |
POST /upload-file cavabında |
yalnız POST /transcribe-uploaded sorğusunda |
işə salana (və ya /discard-upload) qədər |
record_id |
POST /transcribe-uploaded və ya
POST /transcribe-link cavabında |
bundan sonrakı HƏR ŞEYDƏ: vəziyyət, endirmə, axın, redaktə, silmə | daimi (siz silənə qədər) |
record_id-i bilmirsiniz? GET /files bütün qeydlərinizi
id ilə qaytarır — record_id məhz o id-dir.
Buradakı 12 sadəcə nümunədir, sizin nömrəniz başqa olacaq.
# Nömrələr cavabdan götürülür, əl ilə yazılmır:
up = requests.post(f"{BASE}/upload-file", headers=H,
files={"file": ("audio.mp3", open("audio.mp3", "rb"))}).json()
# {"temp_id": "cd0463e8-8e7b-...", "duration": 25, "enough_balance": true}
temp_id = up["temp_id"]
job = requests.post(f"{BASE}/transcribe-uploaded", headers=H,
data={"temp_id": temp_id}).json()
# {"record_id": 12, "status": "processing", "punct": true}
rid = job["record_id"] # <-- bundan sonra hər yerdə BU işlənir
requests.get(f"{BASE}/files/{rid}", headers=H) # vəziyyət
requests.get(f"{BASE}/files/{rid}/download", params={"fmt": "json"}) # nəticə
# Nömrəni itirsəniz - siyahıdan tapın:
for f in requests.get(f"{BASE}/files", headers=H).json():
print(f["id"], f["original_name"], f["status"])
# jq varsa nömrələr avtomatik götürülür:
TEMP_ID=$(curl -s -X POST "$BASE/upload-file" -H "X-API-Key: $KEY" \
-F "file=@audio.mp3" | jq -r .temp_id)
echo "$TEMP_ID" # cd0463e8-8e7b-...
ID=$(curl -s -X POST "$BASE/transcribe-uploaded" -H "X-API-Key: $KEY" \
-F "temp_id=$TEMP_ID" | jq -r .record_id)
echo "$ID" # 12 <-- bundan sonra hər yerdə $ID işlənir
# jq yoxdursa cavabı ekranda görüb dəyəri əl ilə yazın:
# {"record_id":12,"status":"processing",...} -> ID=12
# Nömrəni itirsəniz - siyahıdan tapın:
curl -s "$BASE/files" -H "X-API-Key: $KEY" | jq '.[] | {id, original_name, status}'
3 Transkripsiyanı başlat
İki yol var: kompüterdəki fayl və ya video linki.
Hər ikisi arxa planda işləyir və dərhal record_id qaytarır.
punct=true (default) durğu işarələri, böyük hərflər
və rəqəmlər bərpa olunur. punct=false xam mətn verir:
«iki min iyirmi altıncı ilin dövlət büdcəsi» —
true ilə isə «2026-cı ilin dövlət büdcəsi...»
# --- A) Fayl ---
with open("audio.mp3", "rb") as f:
up = requests.post(f"{BASE}/upload-file", headers=H,
files={"file": ("audio.mp3", f)}).json()
print(up["duration_fmt"], "| balans yetərli:", up["enough_balance"])
job = requests.post(f"{BASE}/transcribe-uploaded", headers=H,
data={"temp_id": up["temp_id"], "punct": "true"}).json()
rid = job["record_id"]
# --- B) Link ---
URL = "https://www.youtube.com/watch?v=XXXXXXXXXXX"
# əvvəlcə yoxla (heç nə tutulmur): müddət nə qədər, balans çatırmı
probe = requests.post(f"{BASE}/probe-link", headers=H, data={"url": URL}).json()
print(probe["duration_fmt"], probe["enough_balance"])
job = requests.post(f"{BASE}/transcribe-link", headers=H,
data={"url": URL, "punct": "false"}).json()
rid = job["record_id"]
# --- A) Fayl ---
curl -s -X POST "$BASE/upload-file" -H "X-API-Key: $KEY" -F "file=@audio.mp3"
# {"temp_id":"cd0463e8-...","duration":25,"duration_fmt":"0 dəq. 25 san.",
# "enough_balance":true,"balance":33651}
curl -s -X POST "$BASE/transcribe-uploaded" -H "X-API-Key: $KEY" \
-F "temp_id=$TEMP_ID" -F "punct=true"
# {"record_id":12,"status":"processing","punct":true,...}
# --- B) Link ---
URL="https://www.youtube.com/watch?v=XXXXXXXXXXX"
curl -s -X POST "$BASE/probe-link" -H "X-API-Key: $KEY" -F "url=$URL"
# {"duration":189,"duration_fmt":"3 dəq. 9 san.","enough_balance":true}
curl -s -X POST "$BASE/transcribe-link" -H "X-API-Key: $KEY" \
-F "url=$URL" -F "punct=false"
# {"record_id":13,"status":"downloading",...}
4 Hazır olmasını gözlə
İki üsul: polling (sadə — vəziyyəti təkrar soruşursunuz) və ya canlı axın (mətn seqment-seqment gəlir).
# --- A) Polling ---
import time
while True:
info = requests.get(f"{BASE}/files/{rid}", headers=H).json()
if not info["processing"]:
break
if info["progress"]:
print(info["progress"]["current"], "/", info["progress"]["total"])
time.sleep(3)
if info["status"] == "error":
raise SystemExit("transkripsiya alınmadı")
print(info["transcript"])
# --- B) Canlı axın (SSE) ---
import json
# bilet 60 saniyəlik və birdəfəlikdir - açar URL-ə düşmür
ticket = requests.post(f"{BASE}/stream/{rid}/ticket", headers=H).json()["ticket"]
with requests.get(f"{BASE}/stream/{rid}", params={"ticket": ticket},
stream=True, timeout=(30, 3600)) as r:
for line in r.iter_lines(decode_unicode=True):
if not line or not line.startswith("data:"):
continue # ": keep-alive" sətirləri buraxılır
ev = json.loads(line[5:])
if ev["type"] == "segment":
print(ev["text"]) # XAM seqment (canlı)
elif ev["type"] == "complete":
print(ev["transcript"]) # punktuasiyalı YEKUN mətn
break
elif ev["type"] == "error":
print("xəta:", ev["message"])
break
# --- A) Polling ---
curl -s "$BASE/files/$ID" -H "X-API-Key: $KEY"
# {"status":"transcribing","processing":true,"progress":{"current":3,"total":38}}
# ... processing:false olana qədər təkrarlayın
# --- B) Canlı axın (SSE) ---
TICKET=$(curl -s -X POST "$BASE/stream/$ID/ticket" -H "X-API-Key: $KEY" | jq -r .ticket)
curl -N "$BASE/stream/$ID?ticket=$TICKET"
segment hadisələri xam gəlir (canlı göstərmək üçün),
punktuasiya isə yalnız sonda tam mətnə tətbiq olunur və
complete ilə gəlir. Yəni interfeys sonda mətni
complete ilə əvəz etməlidir.
5 Nəticəni al
Eyni record_id üç formatda verilir.
| fmt | Nə qaytarır | Nə vaxt lazımdır |
|---|---|---|
txt (default) | düz mətn | sadəcə oxumaq üçün |
srt | altyazı faylı | videoya altyazı qoşmaq üçün |
json | mətn + cues + hər sözün vaxtı | redaktor, axtarış, sinxron oxutma |
dl = f"{BASE}/files/{rid}/download"
txt = requests.get(dl, headers=H, params={"fmt": "txt"}).text
srt = requests.get(dl, headers=H, params={"fmt": "srt"}).text
data = requests.get(dl, headers=H, params={"fmt": "json"}).json()
open("audio.srt", "w", encoding="utf-8").write(srt)
data["transcript"] # tam mətn
data["duration"] # saniyə
data["words"][0] # {"word": "salam", "start": 1.24, "end": 1.61}
data["cues"][0] # sətir: start, end, text, words
# $ID yuxarıda `record_id`-dən gəlir (məs. 12) curl -s "$BASE/files/$ID/download?fmt=txt" -H "X-API-Key: $KEY" curl -s "$BASE/files/$ID/download?fmt=srt" -H "X-API-Key: $KEY" -o audio.srt curl -s "$BASE/files/$ID/download?fmt=json" -H "X-API-Key: $KEY" -o audio.json
6 Redaktə və silmə
# Sözləri vaxtı ilə yenidən yaz - mətn və SRT yenidən qurulur
words = [{"word": "Salam", "start": 0.0, "end": 0.4},
{"word": "dünya.", "start": 0.4, "end": 0.9}]
requests.post(f"{BASE}/files/{rid}/words", headers=H, json={"words": words})
requests.get(f"{BASE}/files", headers=H).json() # siyahı
requests.delete(f"{BASE}/files/{rid}", headers=H) # sil
curl -s -X POST "$BASE/files/$ID/words" -H "X-API-Key: $KEY" \
-H "Content-Type: application/json" \
-d '{"words":[{"word":"Salam","start":0,"end":0.4}]}'
curl -s "$BASE/files" -H "X-API-Key: $KEY" # siyahı
curl -s -X DELETE "$BASE/files/$ID" -H "X-API-Key: $KEY" # sil
7 TTS — mətni səsə çevir
Referans səs (3–15 san, ilk 15 saniyə işlənir) + onda deyilən mətn + səsləndiriləcək
mətn göndərilir. Yüklənən referans səs bazasında mətni ilə qalır
(GET /tts/voices), sonrakı sorğularda faylı yenidən göndərmək
lazım deyil: voice_id=<id> kifayətdir (ref_text boşdursa
bazadakı mətn işlənir). Siyahıda öz səsləriniz və admin-in "ümumi" etdiyi
səslər görünür. İş növbəyə düşür, GET /tts/files/{id} ilə izlənir.
Balansdan yaranan səsin müddəti (yuxarı yuvarlaqlaşdırılmış saniyə) tutulur — ASR ilə
eyni balans. Xəta olarsa heç nə tutulmur. Eyni səslə təkrar üçün faylı yenidən
göndərmək lazım deyil: ref_from=<əvvəlki id>.
# (istəyə görə) referansın mətnini ASR ilə al
with open("ref.wav", "rb") as f:
ref_text = requests.post(f"{BASE}/tts/ref-transcribe", headers=H,
files={"file": f}).json()["text"]
with open("ref.wav", "rb") as f:
job = requests.post(f"{BASE}/tts/generate", headers=H, files={"ref_audio": f},
data={"ref_text": ref_text,
"gen_text": "Salam, bu sınaq mətnidir.",
"speed": 0, # 0 = avtomatik
"seed": 44}).json()
while True:
rec = requests.get(f"{BASE}/tts/files/{job['id']}", headers=H).json()
if rec["status"] in ("completed", "error"):
break
time.sleep(1.5)
audio = requests.get(BASE + rec["audio_url"]).content # WAV, 24 kHz (imzalı link, ~12 saat keçərli)
open("tts.wav", "wb").write(audio)
curl -s -X POST "$BASE/tts/ref-transcribe" -H "X-API-Key: $KEY" -F file=@ref.wav
curl -s -X POST "$BASE/tts/generate" -H "X-API-Key: $KEY" \
-F ref_audio=@ref.wav -F "ref_text=Referans səsdə deyilən mətn" \
-F "gen_text=Salam, bu sınaq mətnidir."
# -> {"id": 7, "status": "queued", "queue_position": 0, ...}
curl -s "$BASE/tts/files/7" -H "X-API-Key: $KEY" # status: completed -> audio_url
curl -s "$BASE/media/..._tts.wav?e=...&s=..." -o tts.wav # audio_url olduğu kimi; ~12 saat keçərli
Arayış
Cavab nümunəsi — GET /files/{id}
{
"id": 12, // <- record_id məhz budur
"original_name": "audio.mp3",
"audio_url": "/media/6f1c..._audio.mp3?e=1791360000&s=9b1e...", // imzalı link, ~12 saat
"transcript": "2026-cı ilin dövlət büdcəsi layihəsində...",
"duration": 189,
"status": "completed", // processing|transcribing|downloading|completed|error
"processing": false,
"progress": null, // işləyəndə: {"current": 3, "total": 38}
"cues": [
{"id": 0, "start": 0.72, "end": 4.56, "text": "2026-cı ilin dövlət...",
"words": [{"word": "2026-cı", "start": 0.72, "end": 1.52}]}
]
}
Xəta kodları
| Kod | Səbəb |
|---|---|
| 401 | Açar yoxdur, yanlışdır və ya ləğv olunub |
| 402 | Balans kifayət etmir (cavabda lazım olan müddət yazılır) |
| 400 | Dəstəklənməyən format, yanlış link, canlı yayım, boş/uzun TTS mətni |
| 404 | Belə record_id yoxdur (və ya sizin deyil) |
| 413 | Fayl çox böyükdür |
| 429 | Sorğu limiti aşılıb - bir qədər gözləyin |
Bütün endpointlər
| Metod | Yol | İzah |
|---|---|---|
| GET | /me | Balans və istifadəçi məlumatı |
| POST | /api-keys | API açarı yarat |
| GET | /api-keys | Öz açarlarının siyahısı |
| DELETE | /api-keys/{id} | Açarı ləğv et |
| POST | /upload-file | Fayl yüklə → temp_id |
| POST | /discard-upload | Yüklənmiş faylı sil (işə salmadan) |
| POST | /transcribe-uploaded | Faylı işə sal → record_id |
| POST | /probe-link | Linki yoxla (müddət, qiymət) |
| POST | /transcribe-link | Linki işə sal → record_id |
| GET | /files | Bütün qeydlər (id ilə) |
| GET | /files/{id} | Qeyd + söz vaxtları |
| POST | /files/{id}/words | Mətni redaktə et |
| GET | /files/{id}/download?fmt= | txt | srt | json |
| DELETE | /files/{id} | Qeydi sil |
| POST | /files/bulk-delete | Bir neçəsini sil (JSON: file_ids) |
| POST | /stream/{id}/ticket | Axın bileti (60 san) |
| GET | /stream/{id}?ticket= | Canlı axın (SSE) |
| POST | /tts/ref-transcribe | Referans səsin mətni (ASR, ilk 15 san) |
| GET | /tts/voices | Səs bazası: öz + ümumi səslər (audio_url, text) |
| POST | /tts/voices | Yeni səs (fayl; transcribe=true → mətn ASR ilə) |
| PATCH | /tts/voices/{id} | Ad / mətn dəyiş (öz səsi) |
| DELETE | /tts/voices/{id} | Səsi bazadan sil (öz səsi) |
| POST | /tts/generate | Səs yarat (voice_id və ya fayl) → id |
| GET | /tts/files | TTS generasiyaları |
| GET | /tts/files/{id} | Vəziyyət, audio_url, müddət |
| DELETE | /tts/files/{id} | Generasiyanı və faylını sil |
| POST | /tts/files/bulk-delete | Bir neçəsini sil (JSON: file_ids) |
| POST | /chat/send | Çat: mesaj göndər, cavab SSE ilə axır (JSON: text, conversation_id; yeni söhbət üçün agent: "ai" və ya "banx" - yalnız bank sualları; files: [id] - əlavə edilmiş fayllar, yalnız "ai") |
| POST | /chat/files | Çata fayl/şəkil yüklə (multipart: file; pdf, doc, docx, odt, rtf, txt, md, csv, png, jpg, webp, gif, bmp; 20 MB) -> id, sonra /chat/send-də files |
| DELETE | /chat/files/{id} | Göndərilməmiş faylı sil |
| GET | /chat/conversations | Söhbətlər (?agent=ai | banx) |
| GET | /chat/conversations/{id} | Söhbət + mesajlar |
| DELETE | /chat/conversations/{id} | Söhbəti sil |
| GET | /health | Servisin vəziyyəti (açarsız) |