Guide · async
Transcribe a link
The server fetches a public URL or a YouTube video, then transcribes it like a file. Asynchronous, with a fetch phase first.
Give the API a link instead of a file and it fetches the media itself: a public http(s) address (an mp3, a video, a podcast episode) or a YouTube video. The rest is Transcribe a file: a job to wait on, then the transcript.
Check the link
GET /v1/transcriptions/probesays what a link is before anything is stored:kindisfile(transcribe it here),live(a YouTube live, an endless radio stream, an HLS playlist with no end) orunknown, andokisfalsewith aproblemsentence when the link did not open as media (a web page, an error). A live link never ends, so it is not a file: record it with a live transcription whose source is the link instead (Go live).The probe is optional, and limited to 60 calls a minute. Links to private, loopback or cloud metadata addresses are refused.
Create the transcription
POST /v1/transcriptionswith JSON{ "url": "…" }(and optionalnameandsettings, as for a file) answers201at once; the server fetches the link afterwards.vs.transcriptions.createFromUrl(url)is the same call. AnIdempotency-Keymakes a retried create return the first transcription instead of a second one.Wait for the fetch and the transcription
A link job starts in
fetching, then moves totranscribinganddone(orfailed, witherror). While it fetches,progress.phaseisfetching(bytes arriving:progress.fetchedBytesofprogress.fetchTotalBytes, when the other end says) orextracting(the audio is being taken out of a video), andprogress.phasePctis how far that phase is. A long video takes a while to fetch; pollGET /v1/recordings/{id}/jobevery few seconds (vs.jobs.waitdoes) rather than in a tight loop.Read the transcript
GET /v1/recordings/{id}/transcript, as for a file:sentences,segmentswith word timings,speakers, andlang=for a translation. The transcription keeps the link it came from (job.source).
TypeScript SDK
import { VocalStack } from '@vocalstack/sdk';
const vs = new VocalStack({ baseUrl: 'https://api.vocalstack.com', apiKey: process.env.VOCALSTACK_API_KEY });
const url = process.argv[2] ?? 'https://raw.githubusercontent.com/openai/whisper/main/tests/jfk.flac';
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 tx = await vs.transcriptions.createFromUrl(url, { name: 'From a link' }, { idempotencyKey: crypto.randomUUID() });
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}%`);
},
});
if (job.state !== 'done') throw new Error(`transcription ${job.state}: ${job.error ?? ''}`);
const { sentences } = await vs.recordings.transcript(job.recordingId);
console.log(sentences.map((s) => s.text).join(' '));
Python SDK
import asyncio
import os
import sys
import uuid
from vocalstack import VocalStack
URL = sys.argv[1] if len(sys.argv) > 1 else "https://raw.githubusercontent.com/openai/whisper/main/tests/jfk.flac"
def show(job) -> None:
p = job.progress
if job.state == "fetching":
print(f"{p.phase or 'fetching'} {p.phase_pct or 0}%")
else:
print(f"{job.state} {p.pct}%")
async def main() -> None:
async with VocalStack("https://api.vocalstack.com", api_key=os.environ["VOCALSTACK_API_KEY"]) as vs:
probe = await vs.transcriptions.probe(URL)
if not probe.ok:
raise SystemExit(f"that link did not open: {probe.problem}")
if probe.kind == "live":
raise SystemExit("a live link: record it with a live transcription instead")
tx = await vs.transcriptions.create(url=URL, name="From a link", idempotency_key=str(uuid.uuid4()))
assert tx.job is not None
job = await vs.jobs.wait(tx.job.recording_id, interval_s=3, on_progress=show)
if job.state != "done":
raise SystemExit(f"transcription {job.state}: {job.error}")
snapshot = await vs.recordings.transcript(job.recording_id)
print(" ".join(s.text for s in snapshot.sentences))
asyncio.run(main())
HTTP + WebSocket
#!/usr/bin/env bash
# curl and jq, no SDK.
set -euo pipefail
API=https://api.vocalstack.com
AUTH="Authorization: Bearer $VOCALSTACK_API_KEY"
URL=${1:-https://raw.githubusercontent.com/openai/whisper/main/tests/jfk.flac}
curl -sSfG "$API/v1/transcriptions/probe" -H "$AUTH" --data-urlencode "url=$URL" | jq '{kind, ok, problem}'
REC=$(jq -n --arg url "$URL" '{url: $url, name: "From a link"}' \
| curl -sSf "$API/v1/transcriptions" -H "$AUTH" -H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" -d @- \
| jq -r .job.recordingId)
while :; do
JOB=$(curl -sSf "$API/v1/recordings/$REC/job" -H "$AUTH")
STATE=$(jq -r .state <<<"$JOB")
jq -r '"\(.state) \(.progress.phase // "") \(.progress.phasePct // .progress.pct)%"' <<<"$JOB"
case $STATE in
done) break ;;
failed) jq -r .error <<<"$JOB" >&2; exit 1 ;;
esac
sleep 3
done
curl -sSf "$API/v1/recordings/$REC/transcript" -H "$AUTH" | jq -r '[.sentences[].text] | join(" ")'