Nahratie vráti úlohu, nie prepis. Pri každom vytvorení pošlite Idempotency-Key, aby opakovaný pokus nikdy nevytvoril ani nespoplatnil druhý prepis; potom úlohu dotazujte, kým nie je hotová, a z rovnakého id čítajte vety, slová alebo export.
Prepis súboru je najjednoduchšia vec, ktorú VocalStack API robí, a má tvar každej asynchrónnej úlohy: nahrať, počkať, prečítať. Nahratie odpovie hneď prepisom a jeho úlohou, ale zatiaľ bez slov. Kým váš program úlohu dotazuje, server prepisuje súbor mnohonásobne rýchlejšie, než sa audio prehráva. Keď má stav done, prečítate prepis ako vety s rečníkmi a časovaním, alebo ho exportujete ako súbor v jednom zo štyroch formátov, a to v hovorenom jazyku alebo preložený.
Všetko v tomto tvare sa dá preniesť na akékoľvek dávkové API: vytvorenie, ktoré vráti identifikátor, stav, ktorý dotazujete, a výsledok, ktorý si následne vyžiadate. Iba názvy polí sú špecifické pre VocalStack, a tento návod ich prejde s TypeScript a Python SDK a tam, kde pomôžu, aj s HTTP volaniami.
Otvorte Developers → API keys v nástenke a vytvorte kľúč s oprávneniami Read a Write. Kľúč sa zobrazí iba raz; skopírujte ho do premennej prostredia:
export VOCALSTACK_API_KEY="<your API key>"
Kľúč je určený pre servery a skripty. Všetko, čo sa nasadzuje do prehliadača, nesie kľúč so sebou, preto ho držte mimo kódu stránky a mimo URL, ktoré končia v logoch. Ak chcete streamovať z prehliadača, váš server pre ňu vytvorí realtime reláciu; to je iný návod.
Každé volanie sa autentizuje kľúčom ako bearer token:
Authorization: Bearer vs_key_...
S kľúčom v prostredí je ďalšou vecou klient, ktorý ho pošle.
Existujú dva SDK. Každé má jedného klienta, VocalStack, a jeho metódy sú zoskupené podľa zdroja: vs.transcriptions, vs.jobs, vs.recordings, vs.realtime a ešte niekoľko ďalších. TypeScript SDK beží na Node 20 alebo novšom a v prehliadačoch; Python SDK je postavené na asyncio a vyžaduje Python 3.10 alebo novší.
SDK nie sú na npm ani PyPI. API ich servíruje každému účtu. Inštalačné príkazy, doplnené pre vaše prostredie, sú v sekcii Install rýchleho štartu v referencii API, a posielajú váš kľúč iba na API hostiteľa. Ak nechcete nič inštalovať, HTTP volania nižšie potrebujú iba curl a jq.
Nech ste zvolili čokoľvek, prvým volaním je nahratie.
POST /v1/transcriptions prijíma súbor ako multipart/form-data: časť file a voliteľnú časť options s JSON. Odpovie 201 s prepisom. Jeho job.recordingId je identifikátor nahrávky vytvorenej zo súboru a každé ďalšie volanie sa adresuje týmto id.
Pri prvom spustení záležia dve nastavenia, obe v options.settings:
spokenLanguage: kód, napríklad en. Ak ho vynecháte, jazyk sa zistí automaticky. Keď ho uvediete, zlepší sa presnosť pri hlučných nahrávkach a silnom prízvuku.speakerLabels: auto, on alebo off. Pri on každá veta nesie číslo rečníka; pri auto sa označenia objavia až potom, čo sa ozve druhý rečník.Väčšina zvukových a video súborov je prijímaná: mp3, wav, flac, ogg, m4a, mp4 a ďalšie. Z videa sa zachová iba zvuk. Formát na presnosti takmer nezáleží, lebo rozpoznávanie reči pracuje s 16 kHz mono a všetko sa pri príchode prevzorkuje na túto hodnotu. 48 kHz stereo WAV jednej osoby, ktorá rozpráva, minie väčšinu bajtov na popis ticha vo vysokej kvalite. Prepíše sa presne tak dobre ako 64 kbps mono MP3 alebo Opus súbor rovnakej reči, no je asi dvadsaťkrát väčší, takže sa nahráva dvadsaťkrát dlhšie. Ak konvertujete pred nahratím, stoja za zmenu dve nastavenia: mono a mierny bitový tok.
Posielajte Idempotency-Key pri každom nahratí a pochopte prečo. Sieťový timeout vám nepovie nič o tom, čo server urobil: mohol požiadavku nikdy nedostať, alebo mohol súbor uložiť a úlohu zaradiť skôr, než sa odpoveď stratila. Slepé opakovanie preto riskuje druhý prepis a druhý poplatok. Kľúč to rieši. Keď sa požiadavka zopakuje s tým istým kľúčom a telom do 24 hodín, server vráti prvú odpoveď namiesto vytvorenia čohokoľvek, čo mení „aspoň raz“ na „efektívne raz“. Pravidlo je preto jeden kľúč na jedno logické nahratie, vygenerovaný pred prvým pokusom a znovupoužitý pri každom opakovaní. Nový kľúč pri každom pokuse by z každého opakovania spravil nové nahratie a hlavička by nechránila nič.
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
Rovnaké volanie cez curl:
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 príde skôr, než sa stihlo prepísať jediné slovo; to, čo držíte, je identifikátor úlohy, takže ďalší krok je čakať na ňu.
Úloha prechádza stavmi queued → transcribing → done, alebo skončí v stave failed so správou error a, ak je príčina strojovo čitateľná, s errorCode. Štvrtý stav, cropping, znamená, že úloha čaká, kým niekto vyberie úsek nahrávky na prepis, ako to robí krok orezania v nástenke. Bežné nahratie sa do neho nedostane, hoci jobs.wait vráti aj vtedy, keď úloha tam dospeje. Čítajte GET /v1/recordings/{id}/job, kým stav nie je done alebo failed.
Webhooky nie sú; je to zámerný kompromis. Webhook potrebuje verejný HTTPS koncový bod na vašej strane, kontrolu podpisu, aby ho nevolal niekto iný, a politiku opakovania pre prípad, že váš koncový bod nefunguje, čo je dosť zložitá infraštruktúra pre úlohu, ktorá trvá minútu. Dotazovanie nič z toho nepotrebuje. Váš skript sa každé dve sekundy opýta „hotovo?“ ako dieťa na zadnom sedadle, no na rozdiel od neho dostane späť percento.
Už otvorená webová stránka môže namiesto toho sledovať udalosti prepisu cez socket (WS /v1/transcriptions/{id}/events, rámec job.progress pri postupe úlohy). Pre skript je dotazovanie správny nástroj.
SDK dotazovanie robia za vás: vs.jobs.wait číta úlohu každé dve sekundy a každé čítanie odovzdá onProgress (on_progress v Pythone). progress úlohy nesie pct (0 až 100) a processedS, sekundy médií prepísané doteraz. Programy nižšie vypíšu riadok iba vtedy, keď sa zmení stav alebo percento.
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}")
Všimnite si, že wait nevyvolá výnimku pri failed; vráti úlohu a kontrolu necháva na vás. Dve sekundy stačia na rozhovor. Pri trojhodinovej nahrávke alebo dávke stoviek ich zväčšite (intervalMs, interval_s) alebo ich postupne predlžujte, ako úloha beží: prvé čítania povedia najviac, neskoršie väčšinou už len potvrdzujú, že server je stále zaneprázdnený. Bez SDK napíšete rovnakú slučku ručne:
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
Keď stav ukazuje done, prepis je pripravený.
GET /v1/recordings/{id}/transcript odpovie celým prepisom v jednom dokumente:
sentences: to, čo ukázať ľuďom. Každá má text, speaker a start a end v sekundách.segments: riadky enginu, každý s časovaním na úrovni slov v words (každé slovo má text, start a end).speakers: jeden záznam pre každé číslo rečníka s jeho name.Rečníci sa číslujú od 0. speaker s hodnotou -1 znamená, že označenia rečníkov boli pri nahrávke vypnuté. Pridajte ?lang=es, ak chcete prepis preložený do španielčiny; vety sa vrátia v tomto jazyku.
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'
Dve úrovne časovania slúžia dvom úlohám. Vety sú jednotkou čítania, preto ich zobrazuje titulok alebo stránka prepisu. Slová sú jednotkou ukazovania, preto zvýraznenie v štýle karaoke, výsledok vyhľadávania alebo strih klipu prechádzajú segments[].words. Formáty exportu sa skladajú z viet.
GET /v1/recordings/{id}/export vráti skutočný súbor. format je jeden z srt, json, xlsx alebo pdf a súbor obsahuje všetky opravy. Pridajte lang= pre preložený súbor: export potom číta v tomto jazyku, jeden riadok na vetu, a každá veta, ktorá ešte nie je preložená, sa najskôr preloží.
SRT stojí za to poznať na pohľad, pretože je to formát titulkov, ktorý prijíma takmer každý prehrávač, a je to čistý text. Súbor je zoznam blokov oddelených prázdnymi riadkami a každý blok je poradové číslo, časový rozsah a text. Časy používajú čiarku pred milisekundami, francúzsky zvyk, ktorý formát nikdy nezrušil:
1
00:00:01,000 --> 00:00:04,200
Welcome, everyone. Let's get started.
Jeden blok na vetu je správna zrnitosť: každý blok sa dá prečítať v čase, kým je na obrazovke, a žiadny sa neprerušuje uprostred myšlienky. Ak mala nahrávka označenia rečníkov, text každého bloku začína menom rečníka a dvojbodkou.
V TypeScript SDK vs.recordings.export vracia Response; jeho bajty prečítate cez arrayBuffer(). V Pythone vracia 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
Prepis je uložený, takže sa zobrazí pod Transcriptions v nástenke, dá sa zdieľať odkazom a dá sa znovu prečítať kedykoľvek neskôr. To je jadro: vytvoriť, počkať, prečítať, exportovať. Zvyšok návodu pokrýva variácie: médiá, ktoré sú už online, chyby, na ktoré narazíte, a čo súbor stojí.
Ak sú médiá už online, dajte API odkaz a ono si súbor stiahne samo: verejnú adresu http(s) (mp3, video, epizódu podcastu) alebo video na YouTube. Volanie je rovnaké POST /v1/transcriptions, s telom JSON { "url": "..." } namiesto nahrávania multipart, a name a settings ako predtým. V SDK vs.transcriptions.createFromUrl(url) (TypeScript) a vs.transcriptions.create(url=...) (Python) odošlú rovnakú požiadavku.
Pred vytvorením čohokoľvek GET /v1/transcriptions/probe?url= povie, čo odkaz je. Jeho kind je jeden z troch:
file: nahrávka s koncom; prepíšte ju tu.live: živý stream na YouTube, nekonečný rozhlasový stream alebo HLS playlist bez konca. Živý odkaz nikdy nekončí, takže sa nedá prepísať ako súbor; namiesto toho ho použite ako zdroj živého prepisu.unknown: server nevedel rozhodnúť.Keď sa odkaz neotvorí ako médium vôbec (napríklad webová stránka), ok je false a problem vysvetlí dôvod jednou vetou. Probe je voliteľný a obmedzený na 60 volaní za minútu.
Odkazová úloha začína v stave fetching a až potom prechádza do transcribing a done. Počas sťahovania progress.phase hovorí, čo server robí: fetching, kým prichádzajú bajty (s fetchedBytes a fetchTotalBytes, keď druhá strana povie, koľko ich je), alebo extracting, keď sa zvuk vyťahuje z videa. progress.phasePct ukazuje, ako ďaleko je úloha v tejto fáze. Dlhé video trvá, takže dotazujte každých pár sekúnd; vs.jobs.wait na to berie intervalMs (interval_s v Pythone).
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}%`);
},
});
Sprievodca Prepis odkazu v referencii obsahuje úplný program v oboch jazykoch a v HTTP. Sťahovanie z odkazov pridáva ďalšie spôsoby zlyhania, preto tu sú chyby, ktoré naozaj uvidíte.
Každé neúspešné volanie odpovie jednou JSON obálkou. code je reťazec, podľa ktorého váš program rozhoduje, a status je HTTP stav; message je pre ľudí a requestId pre podporu. Pod data hovorí retryable, či môže pomôcť poslať rovnakú požiadavku znovu, a details obsahuje, čo daný kód potrebuje. SDK vyvolávajú jeden typ chyby, VocalStackError, s rovnakými poľami.
Tri kódy stojí za to ošetriť hneď od prvého dňa.
402 insufficient_credit: kredit účtu je vyčerpaný a nič sa neúčtovalo. details.balance je zostatok v dolároch a details.topUpUrl je miesto, kde pridať kredit. Keď bola cena známa vopred, ako pri súbore so známou dĺžkou, details.required povie, koľko by to bolo.429 rate_limited: počkajte Retry-After sekúnd. Keď je details.metric creates_per_hour, narazili ste na rýchlosť vytvárania plánu (nové prepisy za hodinu), nie na krátkodobý limit; limit a resetsAt povedia, aký je strop a kedy sa uvoľní.503 unavailable: služba je na chvíľu nedostupná. Pošlite požiadavku znovu po Retry-After.SDK opakujú 429 a 503 po Retry-After, predvolene dvakrát (maxRetries / max_retries na klientovi). 503 sa však opakuje iba pri volaní, ktoré je bezpečné poslať dvakrát: GET alebo vytvorenie s Idempotency-Key. Vytvorenie bez neho je na vás, lebo SDK nemôže vedieť, či prvý pokus prešiel. Práve preto dajte kľúč na každé nahratie.
Každá odpoveď tiež nesie RateLimit-Limit, RateLimit-Remaining a RateLimit-Reset, takže rušný pipeline môže spomaliť ešte pred 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…"
}
Úplná tabuľka kódov je v Chyby, opakovania a limity. Toto 402 je jediná chyba spojená s peniazmi, preto sa pozrime, čo súbor stojí.
Súbory stoja $0.20 za hodinu audia, účtované podľa sekúnd ($0.0033 za minútu) s minimom 5 minút: 30-minútový súbor stojí $0.10. Je to desať centov, menej než káva, ktorú si popijete, kým sa ukazovateľ priebehu naplní. Sadzba je rovnaká na každom pláne a rovnaká cez API ako v nástenke. Nový účet začína s jednorazovým registračným kreditom $5 a nevyžaduje sa karta.
Tri limity plánu sa dotýkajú tohto návodu.
GET /v1/realtime/capabilities hlási ako limits.maxUploadBytes. Platí menší z oboch a súbor nad ním je odmietnutý s 413 skôr, než sa čokoľvek prepíše. Pri dlhej nahrávke je to ďalší dôvod nahrávať skomprimovaný mono súbor namiesto WAV.GET /v1/billing kedykoľvek hlási váš plán, zostatok a limity; úplná tabuľka je na stránke cenníka.
Odtiaľto ďalšie kroky sú streamovanie zvuku v reálnom čase, keď nemôžete čakať, kým súbor dobehne, alebo pripojenie AI agenta k tomuto účtu cez MCP server, aby prepisoval a vyhľadával za vás bez akéhokoľvek kódu vyššie.