اپ لوڈ ٹرانسکرپٹ نہیں، ایک جاب واپس دیتا ہے۔ ہر create پر Idempotency-Key رکھیں تاکہ retry کبھی دوسری ٹرانسکرپشن نہ بنائے اور نہ اس کا بل بنائے؛ پھر جاب کے مکمل ہونے تک پولنگ کریں، اور اسی id سے جملے، الفاظ یا ایکسپورٹ پڑھیں۔
فائل ٹرانسکرپشن VocalStack API کا سادہ ترین کام ہے، اور اس کی شکل کسی بھی غیر ہم وقت (asynchronous) جاب جیسی ہے: اپ لوڈ کریں، انتظار کریں، پڑھیں۔ اپ لوڈ کا جواب فوراً ایک ٹرانسکرپشن اور اس کی جاب دے دیتا ہے، لیکن ابھی کوئی لفظ نہیں ہوتا۔ جب آپ کا پروگرام جاب کو پولنگ کرتا ہے، سرور فائل کو آڈیو کی رفتار سے کئی گنا تیزی سے ٹرانسکرائب کرتا ہے۔ جب state done ہو جائے تو آپ ٹرانسکرپٹ کو اسپیکرز اور وقت کے ساتھ جملوں کی شکل میں پڑھ سکتے ہیں، یا چار فارمیٹس میں سے کسی ایک میں فائل کے طور پر ایکسپورٹ کر سکتے ہیں، بولی گئی زبان میں یا ترجمے کے ساتھ۔
اس شکل کی ہر چیز کسی بھی batch API پر لاگو ہوتی ہے: ایک create جو id لوٹاتا ہے، ایک status جسے آپ پولنگ کرتے ہیں، اور ایک نتیجہ جسے بعد میں حاصل کرتے ہیں۔ صرف فیلڈ کے نام VocalStack کے لیے مخصوص ہیں، اور یہ ٹیوٹوریل TypeScript اور Python SDKs کے ساتھ اور جہاں مددگار ہو وہاں HTTP کالز کے ساتھ ان سے گزرتا ہے۔
ڈیش بورڈ میں Developers → API keys کھولیں اور Read اور Write اجازتوں والی ایک کلید بنائیں۔ کلید صرف ایک بار دکھائی جاتی ہے؛ اسے ایک environment variable میں کاپی کریں:
export VOCALSTACK_API_KEY="<your API key>"
کلید سرورز اور اسکرپٹس کے لیے ہے۔ جو کچھ بھی براؤزر تک جاتا ہے، اس کے ساتھ کلید بھی جاتی ہے، اس لیے کلید کو پیج کے کوڈ اور URLs سے دور رکھیں، کیونکہ URLs لاگز میں پہنچ جاتے ہیں۔ براؤزر سے اسٹریم کرنے کے لیے آپ کا سرور اس کے لیے ایک ریئل ٹائم سیشن بناتا ہے؛ یہ ایک الگ ٹیوٹوریل ہے۔
ہر کال کلید کو بطور bearer token استعمال کر کے خود کو تصدیق کراتی ہے:
Authorization: Bearer vs_key_...
کلید آپ کے environment میں آ جانے کے بعد، اگلا مرحلہ اسے بھیجنے کے لیے ایک کلائنٹ ہے۔
دو SDKs ہیں۔ ہر ایک میں ایک کلائنٹ ہے، VocalStack، جس کے میتھڈز ریسورس کے لحاظ سے گروپ ہیں: vs.transcriptions، vs.jobs، vs.recordings، vs.realtime اور کچھ اور۔ TypeScript SDK Node 20 یا اس سے نئے ورژن اور براؤزرز میں چلتا ہے؛ Python SDK asyncio پر بنا ہے اور Python 3.10 یا اس سے نئے ورژن کا تقاضا کرتا ہے۔
SDKs npm یا PyPI پر موجود نہیں ہیں۔ API ہر اکاؤنٹ کو بلڈز خود فراہم کرتا ہے۔ آپ کے ماحول کے مطابق بھرے ہوئے انسٹالیشن کمانڈز API ریفرنس میں کوئیک اسٹارٹ کے Install سیکشن میں ہیں، اور وہ آپ کی کلید صرف API ہوسٹ کو بھیجتی ہیں۔ اگر آپ کچھ بھی انسٹال نہیں کرنا چاہتے تو نیچے کی HTTP کالز کے لیے صرف curl اور jq درکار ہیں۔
آپ جو بھی راستہ چنیں، پہلی کال اپ لوڈ ہے۔
POST /v1/transcriptions فائل کو multipart/form-data کی شکل میں لیتا ہے: ایک file حصہ، اور JSON رکھنے والا ایک اختیاری options حصہ۔ یہ 201 کے ساتھ ٹرانسکرپشن کا جواب دیتا ہے۔ اس کا job.recordingId اس ریکارڈنگ کی id ہے جو فائل سے بنی، اور اس کے بعد ہونے والی ہر کال اسی id سے مخاطب ہوتی ہے۔
پہلی بار چلانے کے لیے دو سیٹنگز اہم ہیں، دونوں options.settings کے اندر:
spokenLanguage: en جیسا کوڈ۔ اسے چھوڑ دیں تو زبان خود پہچانی جاتی ہے۔ نام لینے سے شور والی ریکارڈنگز اور بھاری لہجوں میں درستگی بہتر ہوتی ہے۔speakerLabels: auto، on یا off۔ on کے ساتھ ہر جملے پر اسپیکر کا نمبر ہوتا ہے؛ auto کے ساتھ لیبل تبھی ظاہر ہوتے ہیں جب دوسرا اسپیکر سنائی دے۔زیادہ تر آڈیو اور ویڈیو فائلیں قبول ہوتی ہیں: mp3، wav، flac، ogg، m4a، mp4 اور دوسری۔ ویڈیو صرف اپنی آڈیو رکھتی ہے۔ فارمیٹ درستگی پر شاذ ہی اثر ڈالتا ہے، کیونکہ اسپیچ ریکگنیشن 16 kHz mono پر کام کرتی ہے اور ہر چیز پہنچتے ہی اسی ریٹ پر دوبارہ سیمپل کی جاتی ہے۔ ایک شخص کے بولنے والی 48 kHz stereo WAV فائل اپنے زیادہ تر بائٹس اعلیٰ معیار میں خاموشی بیان کرنے پر خرچ کرتی ہے۔ یہ اسی تقریر کی 64 kbps mono MP3 یا Opus فائل جتنی ہی درست ٹرانسکرائب ہوتی ہے، لیکن تقریباً بیس گنا بڑی ہوتی ہے، اس لیے اپ لوڈ میں بھی بیس گنا وقت لیتی ہے۔ اگر آپ اپ لوڈ سے پہلے تبدیل کرتے ہیں تو صرف دو سیٹنگز بدلنے کے قابل ہیں: mono اور معتدل bitrate۔
ہر اپ لوڈ کے ساتھ Idempotency-Key بھیجیں، اور اس کی وجہ سمجھیں۔ نیٹ ورک ٹائم آؤٹ یہ نہیں بتاتا کہ سرور نے کیا کیا: ہو سکتا ہے اس نے درخواست دیکھی ہی نہ ہو، یا جواب کھونے سے پہلے فائل محفوظ کر کے جاب قطار میں ڈال دی ہو۔ اس لیے اندھادھند retry دوسری ٹرانسکرپشن اور دوسرے چارج کا خطرہ رکھتی ہے۔ کلید یہی مسئلہ حل کرتی ہے۔ جب 24 گھنٹوں کے اندر وہی کلید اور باڈی دوبارہ بھیجی جائے تو سرور کچھ نیا بنانے کی بجائے پہلا جواب لوٹا دیتا ہے، یوں "کم از کم ایک بار" کو "عملاً ایک بار" میں بدل دیتا ہے۔ اصول یہ ہے کہ ایک منطقی اپ لوڈ کے لیے ایک کلید ہو، جو پہلی کوشش سے پہلے بنے اور ہر retry میں دوبارہ استعمال ہو۔ ہر کوشش کے لیے نئی کلید ہو تو ہر retry نیا اپ لوڈ بن جائے گی، اور ہیڈر کچھ بھی محفوظ نہیں کرے گا۔
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
وہی کال 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 ایک لفظ ٹرانسکرائب ہونے سے پہلے واپس آ جاتا ہے؛ آپ کے ہاتھ میں جاب کی id ہوتی ہے، اس لیے اگلا مرحلہ اس کا انتظار ہے۔
جاب queued → transcribing → done کی طرف بڑھتی ہے، یا failed پر ختم ہوتی ہے جس میں error پیغام ہوتا ہے اور، جب وجہ مشین کے لیے پڑھنے کے قابل ہو، errorCode بھی۔ چوتھی حالت cropping ہے، جس کا مطلب ہے کہ جاب اس وقت تک رکی رہتی ہے جب تک کوئی ریکارڈنگ کا وہ حصہ نہ چنے جسے ٹرانسکرائب کرنا ہے، جیسا کہ ڈیش بورڈ کا trim مرحلہ کرتا ہے۔ عام اپ لوڈ کبھی اس حالت میں نہیں جاتا، اگرچہ jobs.wait اس حالت تک پہنچنے پر بھی واپس آ جاتا ہے۔ state done یا failed ہونے تک GET /v1/recordings/{id}/job پڑھیں۔
یہاں webhooks نہیں ہیں، اور یہ جان بوجھ کر کیا گیا سودا ہے۔ webhook کے لیے آپ کی طرف ایک عوامی HTTPS endpoint، ایک دستخط کی جانچ تاکہ کوئی اور اسے کال نہ کر سکے، اور آپ کا endpoint بند ہونے کی صورت کے لیے retry پالیسی درکار ہوتی ہے، جو ایک منٹ میں ہونے والی جاب کے لیے خاصا جھمیلا ہے۔ پولنگ ان میں سے کسی چیز کی محتاج نہیں۔ آپ کا اسکرپٹ ہر دو سیکنڈ بعد پوچھتا رہے گا "کیا یہ ہو گیا؟"، پچھلی سیٹ پر بیٹھے بچے کی طرح، اور بچے کے برعکس اسے فیصد بھی ملتا ہے۔
جو ویب پیج پہلے سے کھلا ہے وہ اس کے بجائے ٹرانسکرپشن کے events socket کی پیروی کر سکتا ہے (WS /v1/transcriptions/{id}/events، جاب آگے بڑھتے ہوئے job.progress فریم)۔ اسکرپٹ کے لیے پولنگ ہی درست ذریعہ ہے۔
SDKs پولنگ خود کرتے ہیں: vs.jobs.wait ہر 2 سیکنڈ میں جاب پڑھتا ہے اور ہر نتیجہ onProgress کو دیتا ہے (Python میں on_progress)۔ جاب کے progress میں pct (0 سے 100) اور processedS ہوتا ہے، یعنی اب تک ٹرانسکرائب ہونے والے میڈیا کے سیکنڈ۔ نیچے کے پروگرام صرف اس وقت ایک سطر چھاپتے ہیں جب state یا فیصد بدلے۔
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}")
یاد رہے کہ wait failed پر exception نہیں اٹھاتا؛ وہ جاب واپس کر دیتا ہے اور جانچ آپ پر چھوڑ دیتا ہے۔ ایک تقریر کے لیے دو سیکنڈ مناسب ہیں۔ تین گھنٹے کی ریکارڈنگ یا سینکڑوں کے بیچ کے لیے وقفہ بڑھائیں (intervalMs، interval_s) یا جاب چلنے کے ساتھ وقفہ بڑھاتے جائیں: شروع کی ریڈنگز سب سے زیادہ بتاتی ہیں، اور بعد کی زیادہ تر صرف اس بات کی تصدیق کرتی ہیں کہ سرور اب بھی مصروف ہے۔ SDK کے بغیر، آپ یہی لوپ خود لکھتے ہیں:
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
جب state done دکھائے تو ٹرانسکرپٹ تیار ہے۔
GET /v1/recordings/{id}/transcript پورا ٹرانسکرپٹ ایک دستاویز میں واپس دیتا ہے:
sentences: وہ جو لوگوں کو دکھانا ہے۔ ہر ایک میں text، speaker، اور سیکنڈوں میں start اور end ہوتے ہیں۔segments: انجن کی سطریں، جن میں ہر ایک کے words میں لفظ کی سطح کے اوقات ہیں (ہر لفظ کا text، start اور end ہوتا ہے)۔speakers: ہر اسپیکر نمبر کے لیے ایک اندراج، اس کے name کے ساتھ۔اسپیکرز 0 سے شمار ہوتے ہیں۔ speaker کی قدر -1 کا مطلب ہے کہ اس ریکارڈنگ کے لیے اسپیکر لیبل بند تھے۔ ٹرانسکرپٹ کا ہسپانوی ترجمہ پڑھنے کے لیے ?lang=es شامل کریں؛ جملے اسی زبان میں واپس آتے ہیں۔
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'
وقت کی دو سطحیں دو کام کرتی ہیں۔ جملے پڑھنے کی اکائی ہیں، اس لیے سب ٹائٹل یا ٹرانسکرپٹ کا صفحہ وہی دکھاتا ہے۔ الفاظ اشارہ کرنے کی اکائی ہیں، اس لیے karaoke طرز کی ہائی لائٹ، سرچ کا نتیجہ یا کلپ کا کٹ segments[].words کے ذریعے چلتا ہے۔ ایکسپورٹ فارمیٹس جملوں سے بنتے ہیں۔
GET /v1/recordings/{id}/export ایک حقیقی فائل واپس دیتا ہے۔ format ان میں سے ایک ہوتا ہے: srt، json، xlsx یا pdf، اور فائل میں تمام اصلاحات شامل ہوتی ہیں۔ ترجمہ شدہ فائل کے لیے lang= شامل کریں: پھر ایکسپورٹ اس زبان میں پڑھا جاتا ہے، ہر جملے کی ایک سطر کے ساتھ، اور جو جملہ ابھی ترجمہ نہیں ہوا وہ پہلے ترجمہ کیا جاتا ہے۔
SRT کو صورت سے پہچاننا فائدہ مند ہے، کیونکہ یہ وہ سب ٹائٹل فارمیٹ ہے جسے تقریباً ہر پلیئر قبول کرتا ہے اور یہ سادہ متن ہے۔ فائل خالی سطروں سے الگ کیے گئے cues کی فہرست ہے، اور ہر cue ایک شمار کنندہ، ایک وقت کا دورانیہ اور متن ہے۔ ٹائم اسٹیمپ میں ملی سیکنڈ سے پہلے کامہ آتا ہے، ایک فرانسیسی عادت جو یہ فارمیٹ آج تک نہیں چھوڑ سکا:
1
00:00:01,000 --> 00:00:04,200
Welcome, everyone. Let's get started.
ہر cue میں ایک جملہ ہی درست اکائی ہے: ہر cue اتنے وقت میں پڑھا جا سکتا ہے جتنی دیر وہ اسکرین پر ہے، اور کوئی بھی خیال کے درمیان نہیں کٹتا۔ جب ریکارڈنگ میں اسپیکر لیبل تھے، تو ہر cue کا متن اسپیکر کے نام اور ایک کولن سے شروع ہوتا ہے۔
TypeScript SDK میں vs.recordings.export ایک Response واپس دیتا ہے؛ اس کے بائٹس arrayBuffer() سے پڑھیں۔ Python میں یہ 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
ٹرانسکرپشن محفوظ ہو جاتی ہے، اس لیے وہ ڈیش بورڈ میں Transcriptions کے تحت ظاہر ہوتی ہے، لنک کے ذریعے شیئر کی جا سکتی ہے، اور بعد میں کسی بھی وقت دوبارہ پڑھی جا سکتی ہے۔ یہی بنیادی چکر ہے: بنائیں، انتظار کریں، پڑھیں، ایکسپورٹ کریں۔ باقی ٹیوٹوریل ان تبدیلیوں کا احاطہ کرتا ہے: وہ میڈیا جو پہلے سے آن لائن ہے، وہ خرابیاں جو پیش آئیں گی، اور فائل کی لاگت۔
اگر میڈیا پہلے ہی آن لائن ہے تو API کو لنک دیں اور وہ فائل خود حاصل کر لیتا ہے: ایک عوامی http(s) پتہ (mp3، ویڈیو، پوڈکاسٹ کی قسط) یا YouTube ویڈیو۔ کال وہی POST /v1/transcriptions ہے، جس میں multipart اپ لوڈ کی جگہ JSON باڈی { "url": "..." } ہوتی ہے، اور name اور settings پہلے کی طرح۔ SDKs میں vs.transcriptions.createFromUrl(url) (TypeScript) اور vs.transcriptions.create(url=...) (Python) وہی درخواست بھیجتے ہیں۔
کچھ بنانے سے پہلے، GET /v1/transcriptions/probe?url= بتاتا ہے کہ لنک کیا ہے۔ اس کا kind تین میں سے ایک ہوتا ہے:
file: ایک ریکارڈنگ جس کا اختتام ہو؛ اسے یہیں ٹرانسکرائب کریں۔live: YouTube کی لائیو اسٹریم، بے انت ریڈیو اسٹریم یا بغیر اختتام کی HLS پلے لسٹ۔ لائیو لنک کبھی ختم نہیں ہوتا، اس لیے اسے فائل کے طور پر ٹرانسکرائب نہیں کیا جا سکتا؛ اسے اس کے بجائے لائیو ٹرانسکرپشن کے ماخذ کے طور پر استعمال کریں۔unknown: سرور فیصلہ نہیں کر سکا۔جب لنک میڈیا کے طور پر کھل ہی نہ سکے (مثلاً ایک ویب پیج)، تو ok false ہوتا ہے اور problem ایک جملے میں وجہ بتاتا ہے۔ probe اختیاری ہے، اور اسے ایک منٹ میں 60 کالز تک محدود ہے۔
لنک کی جاب transcribing اور done تک جانے سے پہلے fetching سے شروع ہوتی ہے۔ جب تک وہ حاصل کر رہی ہے، progress.phase بتاتا ہے کہ سرور کیا کر رہا ہے: بائٹس آتے وقت fetching (جب دوسرا فریق بتائے کہ کتنے، تو fetchedBytes اور fetchTotalBytes کے ساتھ)، یا ویڈیو سے آڈیو نکالتے وقت extracting۔ progress.phasePct بتاتا ہے کہ جاب اس مرحلے میں کتنی آگے ہے۔ لمبی ویڈیو میں وقت لگتا ہے، اس لیے ہر چند سیکنڈ بعد پولنگ کریں؛ اس کے لیے vs.jobs.wait intervalMs (Python میں interval_s) لیتا ہے۔
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}%`);
},
});
ریفرنس کی گائیڈ لنک کو ٹرانسکرائب کریں میں دونوں زبانوں اور HTTP میں مکمل پروگرام ہے۔ لنکس سے حاصل کرنے میں ناکامی کے مزید طریقے ہیں، اس لیے یہ وہ خرابیاں ہیں جو آپ کو واقعی نظر آئیں گی۔
ہر ناکام کال ایک JSON لفافے میں جواب دیتی ہے۔ code وہ سٹرنگ ہے جس پر فیصلہ کرنا ہے اور status HTTP اسٹیٹس؛ message لوگوں کے لیے ہے اور requestId سپورٹ کے لیے۔ data کے تحت، retryable بتاتا ہے کہ وہی درخواست دوبارہ بھیجنے سے مدد مل سکتی ہے یا نہیں، اور details میں وہ سب ہے جو کوڈ کو درکار ہو۔ SDKs ایک ہی خرابی کی قسم، VocalStackError، اسی فیلڈز کے ساتھ اٹھاتے ہیں۔
تین کوڈز ایسے ہیں جنہیں پہلے ہی دن سنبھالنا فائدہ مند ہے۔
402 insufficient_credit: اکاؤنٹ کا کریڈٹ ختم ہو چکا ہے، اور کوئی چارج نہیں ہوا۔ details.balance ڈالرز میں بیلنس ہے اور details.topUpUrl وہ جگہ ہے جہاں کریڈٹ شامل کیا جاتا ہے۔ جب لاگت پہلے سے معلوم ہو، جیسا کہ معلوم لمبائی کی فائل کے لیے ہوتا ہے، تو details.required بتاتا ہے کہ وہ کتنی ہوتی۔429 rate_limited: Retry-After سیکنڈ انتظار کریں۔ جب details.metric creates_per_hour ہو، تو آپ نے پلان کی تخلیق کی رفتار (ایک گھنٹے میں نئی ٹرانسکرپشنز) کی حد چھو لی ہے، نہ کہ اچانک بھیڑ کی حد؛ limit اور resetsAt بتاتے ہیں کہ حد کیا ہے اور کب ختم ہوتی ہے۔503 unavailable: سروس تھوڑی دیر کے لیے دستیاب نہیں۔ Retry-After کے بعد درخواست دوبارہ بھیجیں۔SDKs 429 اور 503 کو Retry-After کے بعد دوبارہ کوشش کرتے ہیں، پہلے سے طے شدہ طور پر دو بار (کلائنٹ پر maxRetries / max_retries)۔ البتہ 503 صرف اس کال پر دوبارہ کی جاتی ہے جسے دو بار بھیجنا محفوظ ہو: ایک GET، یا وہ create جو Idempotency-Key رکھتا ہو۔ بغیر کلید کا create آپ کے ذمے چھوڑ دیا جاتا ہے، کیونکہ SDK نہیں جان سکتا کہ پہلی کوشش پہنچی یا نہیں۔ ہر اپ لوڈ پر کلید رکھنے کی عملی وجہ یہی ہے۔
ہر جواب میں RateLimit-Limit، RateLimit-Remaining اور RateLimit-Reset بھی آتے ہیں، تاکہ مصروف پائپ لائن 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…"
}
کوڈز کی پوری فہرست خرابیاں، retries اور حدیں میں ہے۔ وہ 402 واحد خرابی ہے جس کا تعلق پیسوں سے ہے، اس لیے دیکھیں کہ ایک فائل پر کتنا خرچ آتا ہے۔
فائلوں کا نرخ آڈیو کے ہر گھنٹے کے لیے $0.20 ہے، سیکنڈ کے حساب سے بل ($0.0033 فی منٹ) کے ساتھ، کم از کم 5 منٹ: 30 منٹ کی فائل $0.10 کی ہے۔ یہ دس سینٹ ہیں، اس کافی سے بھی کم جو آپ پراگریس بار بھرنے کے دوران پئیں گے۔ یہ نرخ ہر پلان پر یکساں ہے، اور API کے ذریعے بھی وہی ہے جو ڈیش بورڈ میں ہے۔ نیا اکاؤنٹ ایک بار کے $5 سائن اپ کریڈٹ کے ساتھ شروع ہوتا ہے، اور کارڈ درکار نہیں۔
تین پلان کی حدیں اس ٹیوٹوریل کو چھوتی ہیں۔
GET /v1/realtime/capabilities limits.maxUploadBytes کے طور پر بتاتا ہے۔ دونوں میں سے چھوٹی حد لاگو ہوتی ہے، اور اس سے بڑی فائل کو کچھ بھی ٹرانسکرائب ہونے سے پہلے 413 کے ساتھ رد کر دیا جاتا ہے۔ لمبی ریکارڈنگ کے لیے یہ WAV کی بجائے کمپریس شدہ mono فائل اپ لوڈ کرنے کی ایک اور وجہ ہے۔GET /v1/billing کسی بھی وقت آپ کا پلان، بیلنس اور حدیں بتاتا ہے؛ مکمل جدول قیمتوں کے صفحے پر ہے۔
یہاں سے اگلے مراحل یہ ہیں: جب آپ فائل مکمل ہونے کا انتظار نہ کر سکیں تو آڈیو کو حقیقی وقت میں اسٹریم کرنا، یا MCP سرور کے ذریعے اسی اکاؤنٹ سے ایک AI ایجنٹ کو جوڑنا، تاکہ وہ اوپر کے کوڈ کے بغیر آپ کے لیے ٹرانسکرائب بھی کرے اور تلاش بھی۔