Otpremanje vraća posao, a ne transkript. Stavite Idempotency-Key uz svaki zahtjev za kreiranje da ponovni pokušaj nikada ne kreira niti naplati drugu transkripciju; zatim pratite posao dok ne bude gotov i čitajte rečenice, riječi ili izvoz s istog id-a.
Transkripcija datoteke najjednostavnija je stvar koju VocalStack API radi, i ima oblik svakog asinhronog posla: otpremi, čekaj, čitaj. Otpremanje odmah odgovara transkripcijom i njenim poslom, ali još bez riječi. Dok vaš program provjerava posao, server transkribuje datoteku mnogo puta brže nego što se zvuk reproducira. Kada stanje bude done, transkript čitate kao rečenice sa govornicima i vremenima, ili ga izvozite kao datoteku u jednom od četiri formata, bilo na jeziku govora ili prevedenu.
Sve u ovom obliku važi za bilo koji batch API: kreiranje koje vraća id, status koji provjeravate i rezultat koji kasnije dohvatite. Samo su nazivi polja specifični za VocalStack, a ovaj vodič prolazi kroz njih s TypeScript i Python SDK-ovima i HTTP pozivima gdje pomažu.
Otvorite Developers → API keys u kontrolnoj ploči i kreirajte ključ s dozvolama Read i Write. Ključ se prikazuje samo jednom; kopirajte ga u varijablu okruženja:
export VOCALSTACK_API_KEY="<your API key>"
Ključ je namijenjen serverima i skriptama. Sve što se isporučuje u preglednik isporučuje ključ s njim, zato ga držite van koda stranice i van URL-ova, koji završavaju u logovima. Za streaming iz preglednika, vaš server za njega kreira realtime sesiju; to je drugi vodič.
Svaki poziv se autentifikuje ključem kao bearer tokenom:
Authorization: Bearer vs_key_...
S ključem u okruženju, sljedeća stvar je klijent koji ga šalje.
Postoje dva SDK-a. Svaki ima jednog klijenta, VocalStack, a metode su grupisane po resursu: vs.transcriptions, vs.jobs, vs.recordings, vs.realtime i još nekoliko. TypeScript SDK radi na Node 20 ili novijem i u preglednicima; Python SDK je izgrađen na asyncio i traži Python 3.10 ili noviji.
SDK-ovi nisu na npm-u niti na PyPI-ju. API servira builde svakom računu. Komande za instalaciju, popunjene za vaše okruženje, nalaze se u odjeljku Install vodiča za brzi početak u referenci API-ja, a šalju vaš ključ samo API hostu. Ako ništa ne želite instalirati, HTTP pozivi ispod trebaju samo curl i jq.
Koji god izaberete, prvi poziv je otpremanje.
POST /v1/transcriptions prima datoteku kao multipart/form-data: dio file, plus neobavezni dio options s JSON-om. Odgovara sa 201 i transkripcijom. Njegov job.recordingId je id snimka napravljenog iz datoteke, a svaki poziv koji slijedi adresira se tim id-om.
Dvije postavke su važne za prvo pokretanje, obje unutar options.settings:
spokenLanguage: kod poput en. Ako ga izostavite, jezik se detektuje. Navođenje jezika poboljšava tačnost na bučnim snimcima i jakim akcentima.speakerLabels: auto, on ili off. Uz on, svaka rečenica nosi broj govornika; uz auto, oznake se pojavljuju tek kada se čuje drugi govornik.Većina audio i video datoteka je prihvaćena: mp3, wav, flac, ogg, m4a, mp4 i druge. Video zadržava samo svoj zvuk. Format gotovo ne utiče na tačnost, jer prepoznavanje govora radi na 16 kHz mono signalu i sve se pri dolasku prevodi na taj. Stereo WAV od 48 kHz s jednom osobom koja govori većinu svojih bajtova troši na opisivanje tišine u visokoj vjernosti. Transkribuje se potpuno isto kao MP3 od 64 kbps mono ili Opus datoteka istog govora, ali je oko dvadeset puta veći, pa otpremanje traje dvadeset puta duže. Ako pretvarate prije otpremanja, mono i umjerena bitska brzina su dvije postavke koje vrijedi promijeniti.
Pošaljite Idempotency-Key uz svako otpremanje i razumijte zašto. Istek vremena na mreži ne govori vam ništa o tome šta je server uradio: možda nikada nije vidio zahtjev, a možda je sačuvao datoteku i stavio posao u red prije nego što je odgovor izgubljen. Slijepi ponovni pokušaj zato riskira drugu transkripciju i drugu naplatu. Ključ to rješava. Kada se zahtjev ponovi s istim ključem i tijelom u roku od 24 sata, server vraća prvi odgovor umjesto da bilo šta kreira, što pretvara „najmanje jednom” u „efektivno jednom”. Pravilo je dakle jedan ključ po logičkom otpremanju, generisan prije prvog pokušaja i ponovo korišten pri svakom ponovnom pokušaju. Svež ključ po pokušaju učinio bi svaki ponovni pokušaj novim otpremanjem, a zaglavlje ne bi štitilo ništa.
import { VocalStack } from '@vocalstack/sdk';
const vs = new VocalStack({ baseUrl: '{{api}}', apiKey: process.env.VOCALSTACK_API_KEY });
const uploadKey = crypto.randomUUID(); // one key for this upload, however many attempts it takes
const tx = await vs.transcriptions.create(
{ file: './talk.mp3', name: 'Talk', settings: { spokenLanguage: 'en', speakerLabels: 'on' } },
{ idempotencyKey: uploadKey },
);
const recordingId = tx.job!.recordingId;
import asyncio
import os
import uuid
from vocalstack import VocalStack
async def main() -> None:
upload_key = str(uuid.uuid4()) # one key for this upload, however many attempts it takes
async with VocalStack("{{api}}", api_key=os.environ["VOCALSTACK_API_KEY"]) as vs:
tx = await vs.transcriptions.create(
file="./talk.mp3",
name="Talk",
settings={"spokenLanguage": "en", "speakerLabels": "on"},
idempotency_key=upload_key,
)
assert tx.job is not None
recording_id = tx.job.recording_id
Isti poziv s curl-om:
API={{api}}
AUTH="Authorization: Bearer $VOCALSTACK_API_KEY"
UPLOAD_KEY=$(uuidgen)
REC=$(curl -sSf "$API/v1/transcriptions" -H "$AUTH" -H "Idempotency-Key: $UPLOAD_KEY" \
-F file=@talk.mp3 \
-F 'options={"name":"Talk","settings":{"spokenLanguage":"en","speakerLabels":"on"}}' \
| jq -r .job.recordingId)
201 stiže prije nego što je i jedna riječ transkribovana; ono što imate je id posla, pa je sljedeći korak čekanje na njega.
Posao prelazi iz queued → transcribing → done, ili završava u failed s porukom error i, kada je uzrok označen mašinski, errorCode. Četvrto stanje, cropping, znači da je posao zaustavljen dok neko ne izabere dio snimka koji će se transkribovati, kao što to radi korak obrezivanja u kontrolnoj ploči. Obična otpremljena datoteka nikada ne ulazi u njega, iako se jobs.wait vraća i kada posao dođe do njega. Čitajte GET /v1/recordings/{id}/job dok stanje ne bude done ili failed.
Nema webhook-ova, i to je namjerna odluka. Webhook traži javni HTTPS endpoint na vašoj strani, provjeru potpisa da niko drugi ne bi mogao da ga pozove, i politiku ponovnih pokušaja za slučaj da je vaš endpoint pao, što je puno infrastrukture za posao koji traje minut. Provjeravanje ne traži ništa od toga. Vaša skripta će svake dvije sekunde pitati „jesi li gotov?”, kao dijete na zadnjem sjedištu, ali za razliku od djeteta dobiće procenat.
Web stranica koja je već otvorena može umjesto toga pratiti događaje transkripcije preko socketa (WS /v1/transcriptions/{id}/events, okvir job.progress dok posao napreduje). Za skriptu je provjeravanje pravi alat.
SDK-ovi rade provjeravanje: vs.jobs.wait čita posao svake 2 sekunde i svako čitanje prosljeđuje u onProgress (on_progress u Pythonu). progress posla nosi pct (0 do 100) i processedS, sekunde medija obrađene do sada. Programi ispod ispisuju red samo kada se stanje ili procenat promijeni.
let last = '';
const job = await vs.jobs.wait(recordingId, {
onProgress: (j) => {
const line = `${j.state} ${j.progress.pct}%`;
if (line !== last) console.log((last = line));
},
});
if (job.state !== 'done') throw new Error(`transcription ${job.state}: ${job.error ?? ''}`);
last = ""
def on_progress(j) -> None:
nonlocal last
line = f"{j.state} {j.progress.pct}%"
if line != last:
last = line
print(line)
job = await vs.jobs.wait(recording_id, on_progress=on_progress)
if job.state != "done":
raise RuntimeError(f"transcription {job.state}: {job.error}")
Napomena: wait ne baca grešku na failed; vraća posao i prepušta provjeru vama. Dvije sekunde su dovoljne za jedno predavanje. Za snimak od tri sata ili seriju od stotine, proširite interval (intervalMs, interval_s) ili ga povećavajte kako posao odmiče: prva čitanja kažu najviše, a kasnija uglavnom potvrđuju da je server i dalje zauzet. Bez SDK-a, istu petlju pišete ručno:
while :; do
JOB=$(curl -sSf "$API/v1/recordings/$REC/job" -H "$AUTH")
STATE=$(jq -r .state <<<"$JOB")
echo "$STATE $(jq -r .progress.pct <<<"$JOB")%"
case $STATE in
done) break ;;
failed) jq -r .error <<<"$JOB" >&2; exit 1 ;;
esac
sleep 2
done
Kada stanje glasi done, transkript je spreman.
GET /v1/recordings/{id}/transcript odgovara cijelim transkriptom u jednom dokumentu:
sentences: ono što se prikazuje ljudima. Svaka ima text, speaker, te start i end u sekundama.segments: linije mašine, svaka s vremenima riječi u words (svaka riječ ima text, start i end).speakers: jedan unos po broju govornika, sa njegovim name.Govornici su numerisani od 0. Vrijednost speaker -1 znači da su oznake govornika bile isključene za snimak. Dodajte ?lang=es da pročitate transkript preveden na španski; rečenice se vraćaju na tom jeziku.
const { sentences } = await vs.recordings.transcript(recordingId);
for (const s of sentences) console.log(s.speaker >= 0 ? `[speaker ${s.speaker}] ${s.text}` : s.text);
snapshot = await vs.recordings.transcript(recording_id)
for s in snapshot.sentences:
print(f"[speaker {s.speaker}] {s.text}" if s.speaker >= 0 else s.text)
curl -sSf "$API/v1/recordings/$REC/transcript" -H "$AUTH" \
| jq -r '.sentences[] | if .speaker >= 0 then "[speaker \(.speaker)] \(.text)" else .text end'
Dva nivoa vremena služe dvama poslovima. Rečenice su jedinica čitanja, pa ih prikazuje titl ili stranica transkripta. Riječi su jedinica pokazivanja, pa istaknuto označavanje u stilu karaokea, pretraga ili rezanje isječka prolaze kroz segments[].words. Formati izvoza grade se od rečenica.
GET /v1/recordings/{id}/export vraća pravu datoteku. format je jedan od srt, json, xlsx ili pdf, a datoteka uključuje sve ispravke. Dodajte lang= za prevedenu datoteku: izvoz je tada na tom jeziku, jedan red po rečenici, a svaka rečenica koja još nije prevedena prvo se prevodi.
SRT vrijedi prepoznati na prvi pogled, jer je to format titlova koji skoro svaki plejer prihvata i on je običan tekst. Datoteka je niz titlova odvojenih praznim redovima, a svaki titl je brojač, vremenski raspon i tekst. Vremenske oznake koriste zarez prije milisekundi, francuska navika koju format nikada nije odbacio:
1
00:00:01,000 --> 00:00:04,200
Welcome, everyone. Let's get started.
Jedna rečenica po titlu je pravi nivo: svaki titl može se pročitati za vrijeme koliko je na ekranu, i nijedan se ne prekida usred misli. Kada je snimak imao oznake govornika, tekst svakog titla počinje imenom govornika i dvotačkom.
U TypeScript SDK-u, vs.recordings.export vraća Response; bajtove čitate s arrayBuffer(). U Pythonu vraća bytes.
import { writeFile } from 'node:fs/promises';
const srt = await vs.recordings.export(recordingId, 'srt');
await writeFile('talk.srt', Buffer.from(await srt.arrayBuffer()));
const pdf = await vs.recordings.export(recordingId, 'pdf', { lang: 'es' });
await writeFile('talk.es.pdf', Buffer.from(await pdf.arrayBuffer()));
srt = await vs.recordings.export(recording_id, "srt")
with open("talk.srt", "wb") as f:
f.write(srt)
pdf = await vs.recordings.export(recording_id, "pdf", lang="es")
with open("talk.es.pdf", "wb") as f:
f.write(pdf)
asyncio.run(main())
curl -sSf "$API/v1/recordings/$REC/export?format=srt" -H "$AUTH" -o talk.srt
curl -sSf "$API/v1/recordings/$REC/export?format=pdf&lang=es" -H "$AUTH" -o talk.es.pdf
Transkripcija je sačuvana, pa se pojavljuje pod Transcriptions u kontrolnoj ploči, može se dijeliti linkom i čitati ponovo bilo kada kasnije. To je osnovna petlja: kreiraj, čekaj, čitaj, izvezi. Ostatak ovog vodiča pokriva varijacije: medij koji je već na mreži, greške na koje ćete naići i šta datoteka košta.
Ako je medij već na mreži, API mu daje link i sam preuzima datoteku: javnu http(s) adresu (mp3, video, epizodu podkasta) ili YouTube video. Poziv je isti POST /v1/transcriptions, s JSON tijelom { "url": "..." } umjesto multipart otpremanja, a name i settings kao ranije. U SDK-ovima, vs.transcriptions.createFromUrl(url) (TypeScript) i vs.transcriptions.create(url=...) (Python) šalju isti zahtjev.
Prije kreiranja bilo čega, GET /v1/transcriptions/probe?url= vam kaže šta je link. Njegov kind je jedan od tri:
file: snimak s krajem; transkribujte ga ovdje.live: YouTube prijenos uživo, radio stream bez kraja ili HLS playlista bez kraja. Live link nikada ne završava, pa ne može biti transkribovan kao datoteka; koristite ga kao izvor transkripcije uživo.unknown: server nije mogao da utvrdi.Kada se link uopšte ne otvara kao medij (web stranica, na primjer), ok je false, a problem objašnjava zašto u jednoj rečenici. Provjera je neobavezna i ograničena na 60 poziva u minuti.
Posao s linkom počinje u fetching prije nego što pređe u transcribing i done. Dok preuzima, progress.phase kaže šta server radi: fetching dok bajtovi stižu (s fetchedBytes i fetchTotalBytes kada druga strana kaže koliko ih ima), ili extracting dok se zvuk vadi iz videa. progress.phasePct kaže koliko je posao odmakao u toj fazi. Dug video traje, pa provjeravajte svakih nekoliko sekundi; vs.jobs.wait za to prima intervalMs (interval_s u Pythonu).
const probe = await vs.transcriptions.probe(url);
if (!probe.ok) throw new Error(`that link did not open: ${probe.problem}`);
if (probe.kind === 'live') throw new Error('a live link: record it with a live transcription instead');
const linkKey = crypto.randomUUID();
const tx = await vs.transcriptions.createFromUrl(url, { name: 'From a link' }, { idempotencyKey: linkKey });
const job = await vs.jobs.wait(tx.job!.recordingId, {
intervalMs: 3000,
onProgress: (j) => {
const p = j.progress;
if (j.state === 'fetching') console.log(`${p.phase ?? 'fetching'} ${p.phasePct ?? 0}%`);
else console.log(`${j.state} ${p.pct}%`);
},
});
Transkribujte link vodič u referenci ima kompletan program na oba jezika i u HTTP-u. Preuzimanje s linkova donosi više načina da zakaže, pa evo grešaka na koje ćete zaista naići.
Svaki neuspjeli poziv vraća jedan JSON omotač. code je string po kojem se grana, a status HTTP status; message je za ljude, a requestId za podršku. Pod data, retryable kaže da li slanje istog zahtjeva ponovo može pomoći, a details sadrži što god kod treba. SDK-ovi bacaju jedan tip greške, VocalStackError, s istim poljima.
Tri koda vrijedi obraditi od prvog dana.
402 insufficient_credit: kredit računa je potrošen, i ništa nije naplaćeno. details.balance je stanje u dolarima, a details.topUpUrl je mjesto gdje se dodaje kredit. Kada je trošak bio poznat unaprijed, kao za datoteku poznate dužine, details.required kaže koliko bi iznosio.429 rate_limited: sačekajte Retry-After sekundi. Kada je details.metric creates_per_hour, dostigli ste stopu kreiranja paketa (novih transkripcija po satu), a ne ograničenje za kratki nalet; limit i resetsAt kažu koja je granica i kada se oslobađa.503 unavailable: servis je nakratko nedostupan. Pošaljite zahtjev ponovo nakon Retry-After.SDK-ovi ponavljaju 429 i 503 nakon Retry-After, po defaultu dva puta (maxRetries / max_retries na klijentu). 503 se, međutim, ponavlja samo za poziv koji je bezbjedno poslati dvaput: GET, ili kreiranje s Idempotency-Key. Kreiranje bez njega ostaje vama, jer SDK ne može znati da li je prvi pokušaj prošao. To je praktičan razlog da stavite ključ na svako otpremanje.
Svaki odgovor također nosi RateLimit-Limit, RateLimit-Remaining i RateLimit-Reset, pa zauzeta obrada može usporiti prije 429.
{
"code": "insufficient_credit",
"status": 402,
"message": "this account has no credit left; add credit to continue",
"data": {
"retryable": false,
"details": { "balance": -0.42, "required": 0.075, "currency": "usd", "plan": "payg", "topUpUrl": "https://…" }
},
"requestId": "01J…"
}
Cijela tabela kodova nalazi se u Greške, ponovni pokušaji i ograničenja. Ta 402 greška je jedina vezana za novac, pa evo koliko košta datoteka.
Datoteke koštaju $0.20 po satu audia, naplaćeno po sekundi ($0.0033 po minuti) uz minimum od 5 minuta: datoteka od 30 minuta košta $0.10. To je deset centi, manje od kafe koju ćete popiti dok se traka napretka puni. Cijena je ista na svakom paketu i kroz API kao u kontrolnoj ploči. Novi račun počinje s jednokratnim kreditom za registraciju od $5, a kartica nije potrebna.
Tri ograničenja paketa tiču se ovog vodiča.
GET /v1/realtime/capabilities prijavljuje kao limits.maxUploadBytes. Važi manja od te dvije, a datoteka veća od nje odbija se s 413 prije nego što se išta transkribuje. Za dug snimak, to je još jedan razlog da otpremite kompresovanu mono datoteku umjesto WAV-a.GET /v1/billing prijavljuje vaš paket, stanje i ograničenja u bilo koje vrijeme; cijela tabela je na stranici cijena.
Od ovdje, sljedeći koraci su strimovanje zvuka u realnom vremenu kada ne možete čekati da se datoteka završi, ili povezivanje AI agenta s istim računom preko MCP servera, da transkribuje i pretražuje umjesto vas, bez ijednog koda iz ovog vodiča.