{"servers":[{"url":"https://api.vocalstack.com"}],"components":{"schemas":{"Access":{"enum":["public","private"],"type":"string"},"Actor":{"properties":{"apiKeyId":{"type":["string","null"]},"displayName":{"type":["string","null"]},"userId":{"type":["string","null"]}},"type":"object"},"AmendTail":{"description":"The words an amend gave a segment's end: `words` replace its last\n`replaced` words.","properties":{"replaced":{"format":"uint32","minimum":0,"type":"integer"},"words":{"items":{"$ref":"#/components/schemas/Word"},"type":"array"}},"required":["replaced","words"],"type":"object"},"ApiErrorBody":{"properties":{"code":{"$ref":"#/components/schemas/ErrorCode"},"data":{"$ref":"#/components/schemas/ErrorData"},"defined":{"type":"boolean"},"message":{"type":"string"},"requestId":{"description":"The `X-Request-Id` of the HTTP request that failed, for support and\nlog correlation.  Absent outside a request (frames on a socket).","type":["string","null"]},"status":{"format":"uint16","maximum":65535,"minimum":0,"type":"integer"}},"required":["defined","code","status","message","data"],"type":"object"},"AudioCapabilities":{"properties":{"channels":{"items":{"format":"uint8","maximum":255,"minimum":0,"type":"integer"},"type":"array"},"encodings":{"items":{"$ref":"#/components/schemas/AudioEncoding"},"type":"array"},"sampleRates":{"items":{"format":"uint32","minimum":0,"type":"integer"},"type":"array"}},"required":["encodings","sampleRates","channels"],"type":"object"},"AudioEncoding":{"enum":["pcm_f32le"],"type":"string"},"AudioFormat":{"additionalProperties":false,"properties":{"channels":{"default":1,"format":"uint8","maximum":255,"minimum":0,"type":"integer"},"encoding":{"$ref":"#/components/schemas/AudioEncoding","default":"pcm_f32le"},"sampleRate":{"default":16000,"format":"uint32","minimum":0,"type":"integer"}},"type":"object"},"AudioSource":{"description":"A stream a broadcast can record from (`Broadcast.audioSources`, design\n78): a YouTube live or an internet radio address kept with the\nbroadcast.  `POST /v1/broadcasts/{id}/recordings` with `audioSource` set\nto its id starts a recording of it.","properties":{"id":{"type":"string"},"name":{"type":"string"},"url":{"type":"string"}},"required":["id","name","url"],"type":"object"},"AudioSourceCheck":{"description":"`GET /v1/broadcasts/{id}/audio-sources/{source}/check`.","properties":{"checkedAt":{"description":"When it was checked (RFC 3339).","type":"string"},"fix":{"anyOf":[{"$ref":"#/components/schemas/AudioSourceFix"},{"type":"null"}],"description":"A corrected address that was opened and gave audio."},"httpStatus":{"description":"The HTTP status the source answered, when that is what failed.","format":"uint16","maximum":65535,"minimum":0,"type":["integer","null"]},"message":{"description":"The failure in the server's own words.","type":["string","null"]},"problem":{"anyOf":[{"$ref":"#/components/schemas/SourceProblem"},{"type":"null"}],"description":"What went wrong (`state = error`)."},"state":{"$ref":"#/components/schemas/AudioSourceState"}},"required":["state","checkedAt"],"type":"object"},"AudioSourceFix":{"description":"An address that was opened and gave audio, offered in place of the one\nthat did not.","properties":{"kind":{"$ref":"#/components/schemas/FixKind"},"label":{"description":"What changed, short: `.m3u`, `HTTPS`, `stream.example.com`.","type":"string"},"url":{"type":"string"}},"required":["url","kind","label"],"type":"object"},"AudioSourceState":{"description":"Whether an audio source can be recorded right now.","oneOf":[{"const":"ready","description":"The address was opened and audio came back.","type":"string"},{"const":"error","description":"It could not be opened, or nothing playable came back.","type":"string"}]},"AutoTopUpAttempt":{"properties":{"amount":{"description":"Dollars (USD), a decimal number with at most six decimals.","type":"number"},"at":{"type":"string"},"outcome":{"description":"`paid`, `declined` or `pending` (the provider has not answered yet).","type":"string"}},"required":["at","amount","outcome"],"type":"object"},"AutoTopUpChoices":{"properties":{"amountRange":{"$ref":"#/components/schemas/MoneyRange"},"amounts":{"description":"Presets; any whole-cent value inside `amountRange` is taken.","items":{"description":"Dollars (USD), a decimal number with at most six decimals.","type":"number"},"type":"array"},"thresholdRange":{"$ref":"#/components/schemas/MoneyRange"},"thresholds":{"description":"Presets; any whole-cent value inside `thresholdRange` is taken.","items":{"description":"Dollars (USD), a decimal number with at most six decimals.","type":"number"},"type":"array"}},"required":["thresholds","amounts","thresholdRange","amountRange"],"type":"object"},"AutoTopUpSettings":{"description":"`PUT /v1/billing/auto-top-up`.","properties":{"amount":{"default":null,"description":"How much to add, inside `autoTopUp.amountRange`; required when\nenabling.","type":["number","null"]},"enabled":{"type":"boolean"},"threshold":{"default":null,"description":"Top up when the balance falls to this: any whole-cent amount inside\nthe plan list's `autoTopUp.thresholdRange`; required when enabling.","type":["number","null"]}},"required":["enabled"],"type":"object"},"AutoTopUpView":{"properties":{"amount":{"description":"Dollars (USD), a decimal number with at most six decimals.","type":["number","null"]},"available":{"description":"Whether it can be turned on (a saved payment method exists).","type":"boolean"},"enabled":{"type":"boolean"},"last":{"anyOf":[{"$ref":"#/components/schemas/AutoTopUpAttempt"},{"type":"null"}],"description":"The last attempt."},"threshold":{"description":"Dollars (USD), a decimal number with at most six decimals.","type":["number","null"]}},"required":["enabled","available"],"type":"object"},"BillingDetails":{"description":"`GET / PUT /v1/billing/details`.","properties":{"city":{"default":"","type":"string"},"country":{"default":null,"description":"ISO 3166-1 alpha-2 (`DE`, `US`).","type":["string","null"]},"email":{"default":"","description":"Where invoices go; the sign-in email until one is saved.","type":"string"},"line1":{"default":"","type":"string"},"name":{"default":"","description":"The name invoices are made out to (a person or a company).","type":"string"},"postalCode":{"default":"","type":"string"},"taxId":{"default":null,"description":"A VAT id or other tax id, normalised (`DE298765432`).","type":["string","null"]}},"type":"object"},"BillingView":{"description":"`GET /v1/billing`: the account as the Dashboard shows it.  The plan's\ndebt cap (the grace amount) is never on it: `inGrace` says whether\nrunning transcriptions go on below zero, never how far.","properties":{"accountCreated":{"description":"The account's creation day (UTC): usage starts here.","type":["string","null"]},"autoTopUp":{"$ref":"#/components/schemas/AutoTopUpView"},"balance":{"description":"The credit left; negative while a plan with a grace period runs on.","type":"number"},"canStart":{"description":"Whether a new transcription would start now.","type":"boolean"},"card":{"anyOf":[{"$ref":"#/components/schemas/CardView"},{"type":"null"}],"description":"The default payment method."},"enabled":{"description":"False when this server does not bill (every check passes); the\nplan, rates and limits are then null.","type":"boolean"},"hasPaid":{"description":"Whether the account has ever paid (the creation rate it gets).","type":"boolean"},"inGrace":{"description":"The balance is below zero and the plan lets running transcriptions\ncontinue (a grace period); new ones are refused until credit is added.","type":"boolean"},"limits":{"anyOf":[{"$ref":"#/components/schemas/Limits2"},{"type":"null"}]},"limitsUsage":{"anyOf":[{"$ref":"#/components/schemas/LimitsUsage"},{"type":"null"}]},"lowBalanceAlert":{"$ref":"#/components/schemas/LowBalanceAlertView"},"monthlyCredit":{"anyOf":[{"$ref":"#/components/schemas/MonthlyCreditView"},{"type":"null"}],"description":"The subscription's monthly credit while its grant is live."},"pendingChange":{"anyOf":[{"$ref":"#/components/schemas/PendingChange"},{"type":"null"}]},"pendingPayment":{"anyOf":[{"$ref":"#/components/schemas/PendingPayment"},{"type":"null"}],"description":"A payment the provider took whose order has not landed yet."},"period":{"anyOf":[{"$ref":"#/components/schemas/DateRange"},{"type":"null"}],"description":"The current billing period: the subscription's (for a yearly one,\nits monthly credit's month), else the calendar month (UTC)."},"plan":{"anyOf":[{"$ref":"#/components/schemas/PlanRef"},{"type":"null"}]},"previousPeriod":{"anyOf":[{"$ref":"#/components/schemas/DateRange"},{"type":"null"}]},"rates":{"anyOf":[{"$ref":"#/components/schemas/Rates"},{"type":"null"}]},"retentionDeadline":{"description":"When data older than the plan's retention is deleted after a\ndowngrade; null otherwise.","type":["string","null"]},"startBlock":{"description":"What would refuse it: `no_credit` (the balance is at or below zero),\n`concurrency` (the plan's transcriptions at once are running) or\n`hourly_limit` (the hour's creations are used); null when it starts.","type":["string","null"]},"subscription":{"anyOf":[{"$ref":"#/components/schemas/SubscriptionView"},{"type":"null"}]},"topUpUrl":{"description":"Where to add credit.","type":"string"}},"required":["enabled","balance","inGrace","canStart","autoTopUp","lowBalanceAlert","hasPaid","topUpUrl"],"type":"object"},"Broadcast":{"description":"A broadcast: the link listeners open (`code`), fed by the live\ntranscriptions attached to it (`transcriptions`) or recording one of\nits own audio sources (`audioSources`). Its recordings are made in\nthose live transcriptions.","properties":{"access":{"$ref":"#/components/schemas/Access"},"archivedAt":{"type":["string","null"]},"audioSources":{"description":"The streams it can record from besides the microphone.","items":{"$ref":"#/components/schemas/AudioSource"},"type":"array"},"code":{"type":"string"},"createdAt":{"type":"string"},"currentRecording":{"anyOf":[{"$ref":"#/components/schemas/CurrentRecording"},{"type":"null"}],"description":"The recording in flight in a transcription feeding it: what the\nlink is showing."},"id":{"type":"string"},"kind":{"const":"broadcast","description":"Always `broadcast`: which resource this is, for a client holding both.","type":"string"},"lastRecordedAt":{"description":"When it last carried a recording (the newest start among the\ntranscriptions feeding it), if it ever has.","type":["string","null"]},"lastRecording":{"anyOf":[{"$ref":"#/components/schemas/LastRecording"},{"type":"null"}],"description":"The newest recording among those of what feeds it."},"lifecycle":{"$ref":"#/components/schemas/Lifecycle"},"listenersNow":{"description":"People on its link right now: reading it while it is on air,\nwaiting on it otherwise.  Left out when there is nobody.","format":"int64","type":["integer","null"]},"name":{"type":"string"},"ownerPrincipalId":{"type":"string"},"purgeAfter":{"type":["string","null"]},"recordingCount":{"description":"The recordings made in the transcriptions feeding it.","format":"int64","type":"integer"},"settings":{"$ref":"#/components/schemas/Settings"},"shareUrl":{"type":"string"},"source":{"$ref":"#/components/schemas/CaptureSource","description":"Microphone on the host's device, or a stream URL the server pulls."},"transcriptions":{"description":"The live transcriptions feeding it.","items":{"$ref":"#/components/schemas/TranscriptionRef"},"type":"array"},"updatedAt":{"type":"string"}},"required":["id","code","name","kind","source","access","lifecycle","ownerPrincipalId","shareUrl","settings","recordingCount","createdAt","updatedAt"],"type":"object"},"BroadcastRef":{"description":"A broadcast a live transcription feeds (`Transcription.broadcasts`).","properties":{"access":{"$ref":"#/components/schemas/Access"},"code":{"type":"string"},"id":{"type":"string"},"name":{"type":"string"},"shareUrl":{"type":"string"}},"required":["id","name","code","access","shareUrl"],"type":"object"},"Capabilities":{"properties":{"audio":{"$ref":"#/components/schemas/AudioCapabilities"},"languages":{"items":{"type":"string"},"type":"array"},"latencyModes":{"items":{"$ref":"#/components/schemas/LatencyMode"},"type":"array"},"limits":{"$ref":"#/components/schemas/Limits"},"speakerLabelModes":{"description":"The speaker-label positions this server offers for\n`SessionOptions.speakerLabels` (empty: speaker labels are not offered),\nas `latencyModes` lists the latency modes.","items":{"$ref":"#/components/schemas/SpeakerLabels"},"type":"array"}},"required":["languages","speakerLabelModes","latencyModes","audio","limits"],"type":"object"},"CaptureSource":{"description":"Where a live transcription's audio comes from (design 17): the host's\ndevice, or a live stream URL the server pulls itself — \"the URL is the capture,\nso nothing has to run on this device\".  Set at create time, never changed.","oneOf":[{"description":"The browser captures the microphone and publishes PCM (the default).","properties":{"kind":{"const":"mic","type":"string"}},"required":["kind"],"type":"object"},{"description":"The server opens `url` (a live audio stream: Icecast MP3 / AAC /\nOgg over HTTP, a YouTube live, an RTMP ingest address), decodes it\nand publishes it when the host presses record.\nStarting a recording then returns no `streamUrl`:\nnothing on the device sends audio.","properties":{"kind":{"const":"stream","type":"string"},"url":{"type":"string"}},"required":["kind","url"],"type":"object"}]},"CaptureSpan":{"properties":{"idx":{"format":"int32","type":"integer"},"mediaEnd":{"format":"double","type":["number","null"]},"mediaStart":{"format":"double","type":"number"},"reason":{"type":"string"},"wallEnd":{"format":"double","type":["number","null"]},"wallStart":{"format":"double","type":"number"}},"required":["idx","wallStart","mediaStart","reason"],"type":"object"},"CardView":{"properties":{"brand":{"type":"string"},"declined":{"description":"The last automatic charge or renewal on it was declined.","type":"boolean"},"expMonth":{"format":"int32","type":["integer","null"]},"expYear":{"format":"int32","type":["integer","null"]},"last4":{"type":"string"}},"required":["brand","last4","declined"],"type":"object"},"ChargePage":{"description":"`GET /v1/billing/charges`.","properties":{"charges":{"items":{"$ref":"#/components/schemas/RecordingCharge"},"type":"array"},"nextCursor":{"type":["string","null"]}},"required":["charges"],"type":"object"},"CheckPromoCode":{"description":"`POST /v1/billing/promo-codes/check`.","properties":{"code":{"type":"string"},"for":{"description":"`topup` or `subscribe`: the purchase it is for.","type":"string"}},"required":["code","for"],"type":"object"},"CheckoutView":{"description":"A checkout as the Dashboard sees it.","properties":{"amount":{"description":"A top-up's amount; a subscription's first payment before tax (less\na promo code's discount).","type":["number","null"]},"checkoutId":{"type":"string"},"completedAt":{"type":["string","null"]},"createdAt":{"type":"string"},"expiresAt":{"type":["string","null"]},"interval":{"type":["string","null"]},"kind":{"description":"`topup` or `subscribe`.","type":"string"},"planId":{"type":["string","null"]},"status":{"description":"`open`, `succeeded`, `expired` or `failed`.","type":"string"},"url":{"description":"Where to send the customer to pay (while `open`).","type":["string","null"]}},"required":["checkoutId","kind","status","createdAt"],"type":"object"},"ClientFrame":{"oneOf":[{"properties":{"type":{"const":"eof","type":"string"}},"required":["type"],"type":"object"},{"properties":{"type":{"const":"cancel","type":"string"}},"required":["type"],"type":"object"},{"description":"Options update during a realtime session; fields left out are unchanged.\n`latencyMode` switches the decode model policy from the\nnext engine step on. `spokenLanguage` changes the transcription language\nfrom the next engine step (`\"\"` or `\"auto\"` = auto-detect; an\nunsupported code is ignored). `speakerLabels` turns speaker\nlabelling on (`on` or `auto`) or off (`off`) from the next engine step —\nenabling labels speakers from that point; earlier segments are not\nre-attributed. `translate` replaces the whole target-language list\n(subject to the same cap as at creation); `[]` turns\ntranslation off. Already-delivered segments and settled sentences\nare not re-transcribed or re-translated.","properties":{"latencyMode":{"anyOf":[{"$ref":"#/components/schemas/LatencyMode"},{"type":"null"}]},"llmFix":{"description":"Turn AI-assisted auto-fix on or off (see `SessionOptions::llmFix`)\nfrom the next line on.","type":["boolean","null"]},"speakerLabels":{"anyOf":[{"$ref":"#/components/schemas/SpeakerLabels"},{"type":"null"}]},"spokenLanguage":{"type":["string","null"]},"translate":{"items":{"type":"string"},"type":["array","null"]},"type":{"const":"session.update","type":"string"},"voice":{"description":"Replace the spoken list — languages or `lang:voice` pairs\n(see `SessionOptions::voice`).","items":{"type":"string"},"type":["array","null"]},"voiceByLang":{"additionalProperties":{"type":"string"},"description":"Replace the whole per-language voice map (see\n`SessionOptions::voiceByLang`); `{}` clears every pin.","type":["object","null"]}},"required":["type"],"type":"object"},{"description":"Capture clock heartbeat (live sources): `pcm` = cumulative samples\nthe client captured (16kHz-equivalent), `wall` = its monotonic\nseconds since capture start (excluding system sleep).  Lets the\nserver detect capture loss and keep the timeline honest; optional\nand backward compatible.","properties":{"pcm":{"format":"uint64","minimum":0,"type":"integer"},"type":{"const":"clock","type":"string"},"wall":{"format":"double","type":"number"}},"required":["type","pcm","wall"],"type":"object"}]},"CommittedSegment":{"properties":{"catchup":{"description":"`true` when the segment was decoded by the fast model: the `auto`\ntranscription method catching up after falling behind real time, or\nthe `fast` method. Absent when the accurate model decoded it.","type":["boolean","null"]},"confidence":{"description":"Segment-level recognition confidence, 0..1 (mean of word probs).","format":"double","type":["number","null"]},"decoder":{"anyOf":[{"$ref":"#/components/schemas/Decoder"},{"type":"null"}],"description":"Which model decoded this segment (`fast` or `accuracy`).\nAlways present on current servers; optional only for\nwire compatibility.  `catchup` is the same signal as a boolean."},"end":{"format":"double","type":"number"},"gapBefore":{"description":"Seconds of capture audio the CLIENT reported lost immediately\nbefore this segment (from `{\"op\":\"clock\"}` heartbeat accounting).\nThe words spoken in that span never reached the engine. Absent\nwhen no loss was detected.","format":"double","type":["number","null"]},"language":{"description":"Language this segment was transcribed in (detected or forced),\nas a whisper language code, when known.","type":["string","null"]},"latencyS":{"description":"Emit latency in seconds (wall clock at emit minus segment end on the\naudio timeline): how far behind the speaker this line was COMMITTED.\nA line a correction pass then holds (a doubted word's re-decode, the\nlanguage model's reading) gets a `transcript.latency` frame when the\nhold releases, with the time to its FINAL text — the figure to keep.\nIt does NOT include the sentence closing, the translation, delivery\nor render — a reader waiting on a translated line waits longer, and\n`SentenceView.translationLatencyS` is the figure for that.\n\nOnly present on live-input realtime sessions, and absent for a line the two\nclocks cannot be compared for — audio that arrived faster than\nrealtime puts a line's end ahead of the wall clock. Absent means\n\"not measured\"; it is never reported as 0 for unknown.","format":"double","type":["number","null"]},"recognizedText":{"description":"When the recording follows a script and the engine took the\nscript's words for some of this segment's (beyond case and\npunctuation): what the recognizer heard before it did.  `text` is\nwhat the reader sees; the segment's voice (voice clips, voice\ntracks, the live voice) says `text` only when the audio supports\nevery word of it, and this otherwise.  Absent when `text` is the\nrecognizer's own.","type":["string","null"]},"scriptFrom":{"description":"When this segment matched the script: the first script word it\nread, so `scriptFrom..scriptPos` is the stretch of script it covers.\nAbsent when it did not match (an aside, a held line).","format":"uint64","minimum":0,"type":["integer","null"]},"scriptPos":{"description":"When the recording follows a script (`Recording.script`): how many\nof the script's words the engine has matched up to and including\nthis segment — where the teleprompter stands.  Words are the\nscript's whitespace-separated tokens that carry a letter or digit.\nAbsent without a script.","format":"uint64","minimum":0,"type":["integer","null"]},"seq":{"format":"uint64","minimum":0,"type":"integer"},"speaker":{"format":"int32","type":"integer"},"speakerConfidence":{"description":"Diarization confidence that the segment belongs to `speaker`, 0..1:\nhow clearly the segment's voice matches that speaker rather than any\nother tracked speaker. Absent without diarization.","format":"double","type":["number","null"]},"speakerProvisional":{"description":"`true` while the speaker label is tentative (low diarization\nconfidence): a later `transcript.speaker` frame settles it, possibly\nafter a `transcript.amended` that moved it. Absent when final.","type":["boolean","null"]},"start":{"format":"double","type":"number"},"text":{"type":"string"},"translationSources":{"additionalProperties":{"type":"string"},"description":"Where each language's translation came from: \"cache\", \"google\" or \"llm\".","type":["object","null"]},"translations":{"additionalProperties":{"type":"string"},"type":["object","null"]},"words":{"items":{"$ref":"#/components/schemas/Word"},"type":"array"}},"required":["seq","speaker","start","end","text","words"],"type":"object"},"ConfidencePolicy":{"additionalProperties":false,"description":"Env defaults, overridden per broadcast, transcription or recording.","properties":{"hideBelow":{"description":"A line scoring below this is hidden as \"Audio unclear\".  The\nscore is the line's own confidence — the mean of its word\nprobabilities — so this is the one number that decides the gap.\n0..1; the default is 0.60.","format":"double","type":["number","null"]},"lowThreshold":{"description":"Words with `prob` below this are *low confidence*.","format":"double","type":["number","null"]},"suppressCount":{"description":"This many low-confidence words suppress a line for listeners.\n\n**No longer read.**  Suppression is `hide_below` — the line's own\nscore — since 2026-09-12; the field is kept so settings carrying it\nstill round-trip rather than being rejected.","format":"uint32","minimum":0,"type":["integer","null"]}},"type":"object"},"Correction":{"properties":{"actor":{"$ref":"#/components/schemas/Actor"},"appliedAt":{"type":"string"},"batchId":{"type":"string"},"from":{"type":"string"},"id":{"type":"string"},"kind":{"type":"string"},"notVoicedReason":{"anyOf":[{"$ref":"#/components/schemas/NotVoicedReason"},{"type":"null"}]},"revertedAt":{"type":["string","null"]},"seq":{"format":"int64","type":["integer","null"]},"speaker":{"format":"int32","type":["integer","null"]},"to":{"type":"string"},"voiced":{"description":"Whether this correction's line is voiced (voice clips and voice\ntracks) as corrected, decided when the correction was applied.\nPresent on a word or line correction that stands; absent on a\nspeaker name and on a reverted correction. `false` means the\ncorrected text has words the audio does not support: the\ntranscript, reading and export show it, but the line is voiced from\nwhat the recognizer heard (`notVoicedReason` says why).","type":["boolean","null"]},"wordIdx":{"format":"int32","type":["integer","null"]}},"required":["id","batchId","kind","from","to","actor","appliedAt"],"type":"object"},"CorrectionBatch":{"additionalProperties":false,"properties":{"edits":{"description":"Applied atomically (\"Apply changes\").","items":{"$ref":"#/components/schemas/WordEdit"},"type":"array"},"lines":{"description":"Whole-line rewrites, applied in the same batch after `edits`.","items":{"$ref":"#/components/schemas/LineEdit"},"type":"array"}},"type":"object"},"CreateAudioSource":{"additionalProperties":false,"description":"`POST /v1/broadcasts/{id}/audio-sources`.","properties":{"name":{"default":null,"description":"What to call it; the address's host when left out.","type":["string","null"]},"url":{"description":"The address of the stream: http(s), as a live transcription's stream source takes it.","type":"string"}},"required":["url"],"type":"object"},"CreateBroadcast":{"additionalProperties":false,"description":"`POST /v1/broadcasts`.","properties":{"access":{"anyOf":[{"$ref":"#/components/schemas/Access"},{"type":"null"}],"default":null},"code":{"default":null,"description":"Creator-chosen, globally unique (#3, #5).  Derived from `name` when\nomitted.  It can be changed later (`PATCH`).","type":["string","null"]},"name":{"type":"string"},"password":{"default":null,"description":"Required for `private`.","type":["string","null"]},"settings":{"anyOf":[{"$ref":"#/components/schemas/Settings"},{"type":"null"}],"default":null},"source":{"anyOf":[{"$ref":"#/components/schemas/CaptureSource"},{"type":"null"}],"default":null,"description":"`{kind:\"mic\"}` (default) or `{kind:\"stream\", url}`: audio the server\npulls from a URL instead of the device."}},"required":["name"],"type":"object"},"CreateCheckout":{"description":"`POST /v1/billing/checkouts`.","properties":{"acceptTerms":{"default":false,"description":"The terms of service were accepted (required).","type":"boolean"},"amount":{"default":null,"description":"A top-up: any whole-cent amount inside `GET /v1/billing/plans`'s\n`topUpRange` (its `topUpAmounts` are the presets).","type":["number","null"]},"interval":{"default":null,"description":"A subscription: `month` or `year`.","type":["string","null"]},"kind":{"description":"`topup` (add credit) or `subscribe` (start a subscription).","type":"string"},"planId":{"default":null,"description":"A subscription: the plan.","type":["string","null"]},"promoCode":{"default":null,"description":"A promo code (`POST /v1/billing/promo-codes/check` tells what it\ngives): a top-up's bonus credit, or a discount off a\nsubscription's first payment.  `404 promo_code_invalid` when it\ndoes not apply.","type":["string","null"]},"waiveWithdrawal":{"default":false,"description":"The customer asked for immediate performance and waives the 14-day\nwithdrawal right (required: credit is not refundable).","type":"boolean"}},"required":["kind"],"type":"object"},"CreateLiveTranscription":{"additionalProperties":false,"description":"`POST /v1/transcriptions/live`: a transcription captured as it is\nspoken — this device's microphone, or a stream the server pulls.\nIt is created idle; `POST /v1/transcriptions/{id}/recordings` starts it.","properties":{"broadcasts":{"default":[],"description":"Broadcasts (their ids) to feed from the start.","items":{"type":"string"},"type":"array"},"name":{"type":"string"},"settings":{"anyOf":[{"$ref":"#/components/schemas/Settings"},{"type":"null"}],"default":null},"source":{"anyOf":[{"$ref":"#/components/schemas/CaptureSource"},{"type":"null"}],"default":null,"description":"`{kind:\"mic\"}` (default) or `{kind:\"stream\", url}`."}},"required":["name"],"type":"object"},"CreateSessionResponse":{"properties":{"expiresAt":{"type":"string"},"sessionId":{"type":"string"},"streamUrl":{"type":"string"}},"required":["sessionId","streamUrl","expiresAt"],"type":"object"},"CreateShareLink":{"additionalProperties":false,"properties":{"allowDownload":{"default":null,"description":"Both default to on (design: \"on by default and can be turned off per link\").","type":["boolean","null"]},"allowExport":{"default":null,"type":["boolean","null"]},"allowTranslate":{"default":null,"description":"Design 24's `k.tr`: may this link be read (and heard) translated?\nDefaults to `DEFAULT_ALLOW_TRANSLATE` — on, so a link keeps doing\nwhat share links have always done; the owner turns it off per link\nwhen they want to stop paying the provider for that link.","type":["boolean","null"]},"label":{"description":"Who the link is for (\"Board pack — Nadia\").","type":"string"}},"required":["label"],"type":"object"},"CreateTranscription":{"additionalProperties":false,"description":"`POST /v1/transcriptions`: JSON for a URL, or the `options` part of a\nmultipart upload whose `file` part carries the media.","properties":{"crop":{"default":false,"description":"Design 57's crop step: once the source is stored and measured the\njob waits in `cropping` (with `peaks` for a waveform) until\n`POST /v1/recordings/{id}/job/start` names the window to\ntranscribe.  Default false: transcribe the whole file right away.","type":"boolean"},"durationS":{"default":null,"description":"The source's length as the browser measured it, kept for the list\nand the reader until the engine reads its own.","format":"double","type":["number","null"]},"endS":{"default":null,"format":"double","type":["number","null"]},"name":{"default":null,"description":"Display name; the file name or the URL when omitted.","type":["string","null"]},"settings":{"anyOf":[{"$ref":"#/components/schemas/SettingsPatch"},{"type":"null"}]},"shareLink":{"default":null,"description":"Whether the transcription is created with its default share link\n(download, export and translation on).  Default true, as it has\nalways been; `false` creates none, and links are added later with\n`POST /v1/broadcasts/{id}/share-links`.","type":["boolean","null"]},"startS":{"default":null,"description":"The window to transcribe, when it was chosen before the job was\ncreated: an upload is trimmed in the browser on the host's own copy\nand sent only at Start (Alex, 2026-10-02), so it arrives already\ncropped.  Not with `crop`.  Absent = from the start / to the end.","format":"double","type":["number","null"]},"url":{"default":null,"description":"Fetched server-side.  Absent for a file upload.","type":["string","null"]}},"type":"object"},"CreateVoice":{"additionalProperties":false,"properties":{"color":{"type":["string","null"]},"consent":{"default":false,"type":"boolean"},"draft":{"default":false,"type":"boolean"},"gender":{"type":["string","null"]},"name":{"type":"string"},"sampleUrl":{"type":["string","null"]}},"required":["name"],"type":"object"},"CreateVoiceFromSpeaker":{"additionalProperties":false,"properties":{"color":{"type":["string","null"]},"consent":{"default":false,"type":"boolean"},"name":{"type":"string"},"recordingId":{"type":"string"},"speaker":{"format":"int32","type":"integer"}},"required":["recordingId","speaker","name"],"type":"object"},"CropRange":{"additionalProperties":false,"description":"A window of a file, in seconds into it.  `end_s` absent = to the end.","properties":{"endS":{"format":"double","type":["number","null"]},"startS":{"default":0.0,"format":"double","type":"number"}},"type":"object"},"CropWindow":{"description":"A window of the source, in seconds into it.  `end_s` absent = to the end.","properties":{"endS":{"format":"double","type":["number","null"]},"startS":{"format":"double","type":"number"}},"required":["startS"],"type":"object"},"CurrentRecording":{"description":"The recording in flight (`currentRecording`): on a transcription, its\nopen recording; on a broadcast, the open recording\nof a live transcription feeding it, which is what its listeners are sent.","properties":{"recordingId":{"type":"string"},"startedAt":{"type":["string","null"]},"state":{"$ref":"#/components/schemas/RecordingState"},"transcriptionId":{"description":"On a broadcast's: the live transcription it is being made in (one\nof `Broadcast.transcriptions`).","type":["string","null"]}},"required":["recordingId","state"],"type":"object"},"DateRange":{"description":"Two days, both inclusive (`YYYY-MM-DD`).","properties":{"end":{"type":"string"},"start":{"type":"string"}},"required":["start","end"],"type":"object"},"Decoder":{"description":"Which model actually decoded a committed segment.","oneOf":[{"const":"fast","description":"The fast model: the `fast` method, or the `auto` method\ncatching up after falling behind real time.","type":"string"},{"const":"accuracy","description":"The accurate model.","type":"string"}]},"DoneReason":{"enum":["eof","max_duration","cancelled"],"type":"string"},"DraftSegment":{"properties":{"speaker":{"format":"int32","type":"integer"},"start":{"format":"double","type":"number"},"tentative":{"type":"boolean"},"text":{"type":"string"},"translations":{"additionalProperties":{"type":"string"},"type":["object","null"]}},"required":["speaker","start","text","tentative"],"type":"object"},"ErrorCode":{"oneOf":[{"enum":["invalid_audio","invalid_options","invalid_token","capacity_exceeded","slow_consumer","engine_unavailable","internal"],"type":"string"},{"const":"session_expired","description":"A realtime session's token passed its connect-by deadline\n(`expiresAt`): create a new realtime session.","type":"string"},{"const":"forbidden","description":"The authenticated actor may not act on this resource.","type":"string"},{"const":"broadcast_archived","description":"The broadcast exists but its host archived it: the link is retired,\nand that is a different thing from a link that was never real.\nDesign 43 says so to whoever pasted it, because \"no broadcast found\"\nsends them to check a code that was perfectly correct.","type":"string"},{"const":"not_found","description":"No such broadcast / transcription / recording / correction, or it is archived.","type":"string"},{"const":"conflict","description":"A uniqueness or concurrency conflict: code taken, already\nrecording on another device.","type":"string"},{"const":"invalid_state","description":"The action is not valid from the resource's current state, e.g.\nstop on a live (not paused) recording, correct an unsettled line.","type":"string"},{"const":"password_required","description":"A private broadcast: exchange the password for a listener token.","type":"string"},{"const":"rate_limited","description":"An abuse control fired; retry later.","type":"string"},{"const":"preview_pending","description":"A preview is being rendered right now: the ask started it, the\nrender files the clip, and the next ask is a cache hit.  Answered\n`202 Accepted`, not `503` — the engine is not unavailable, the clip\nis simply not finished, and saying \"service unavailable\" about a\nperfectly healthy first play puts a red line in the caller's\nconsole and sends people looking for a fault that is not there.","type":"string"},{"const":"language_unavailable","description":"Nothing on this deployment can speak the language that was asked\nfor: no voice asset for it in the bucket, or no preview line\nwritten for it.  Permanent as far as the caller is concerned —\nthis is what `preview_pending` must never be used to say, because\na client polling for something that is not coming eventually tells\nsomebody to try again in a moment, forever.","type":"string"},{"const":"suspended","description":"The account or API key was suspended by an administrator (a kill\nswitch, reversible; a revoked key is `invalid_token`).  Retrying\ndoes not help; contacting support does.","type":"string"},{"const":"key_expired","description":"The API key passed its expiry (`expiresInDays` at creation, or the\n24 h grace after a rotation).  Use its replacement or mint a new one.","type":"string"},{"const":"key_revoked","description":"The API key was revoked.  Final: mint a new key.","type":"string"},{"const":"key_from_browser","description":"An API key was sent from a web page (the request carries a browser\n`Origin`).  Keys are for servers: keep the key on your server and\nhave your page call your server.  The API offers no browser\ncredential for keys; a person signed in to VocalStack itself uses\ntheir own access token.","type":"string"},{"const":"insufficient_credit","description":"The account has no credit left (HTTP 402): the balance is at or\nbelow zero, or a file of known length costs more than it.\n`data.details` carries `balance`, `currency` (`usd`), `plan`,\n`topUpUrl`, and `required` when the cost is known up front.  Not\nretryable until credit is added.","type":"string"},{"const":"payment_method_required","description":"Auto top-up and the customer portal need a saved payment method,\nand the account has made no payment yet (HTTP 409).  Make a\npayment (a top-up or a subscription) first.","type":"string"},{"const":"payment_method_in_use","description":"The payment method pays for an active subscription and cannot be\nremoved (HTTP 409).  Add another method and make it the default\nfirst (`POST /v1/billing/payment-methods/manage`).","type":"string"},{"const":"billing_details_required","description":"The payment provider needs a billing name and address before it can\nmake this invoice or receipt (HTTP 409).  Save the invoice details\n(`PUT /v1/billing/details`) and ask again.","type":"string"},{"const":"promo_code_invalid","description":"The promo code does not exist, has expired, is used up, was already\nredeemed by this account, or does not apply to this purchase (HTTP\n404).","type":"string"},{"const":"auto_top_up_off","description":"Auto top-up is off for this account (HTTP 409): turn it on with\n`PUT /v1/billing/auto-top-up` first.","type":"string"},{"const":"range_too_long","description":"A date range is longer than the route allows (HTTP 400):\n`GET /v1/billing/usage` takes at most 92 days.","type":"string"},{"const":"quota_exceeded","description":"A plan limit is used up (HTTP 402): `concurrent_transcriptions`\n(realtime sessions, live recordings and running jobs at once).\n`data.details` carries `metric`, `limit`, `used`, `resetsAt` (null\nfor a concurrency limit, which frees up as they finish), `plan` and\n`topUpUrl`.","type":"string"},{"const":"idempotency_key_reused","description":"The `Idempotency-Key` was already used, in the last 24 hours, for a\ndifferent request (another route or another body) (HTTP 422).  Use a\nfresh key for a new request.","type":"string"},{"const":"method_not_allowed","description":"No route has this method: the path exists, the method does not\n(HTTP 405).  The `Allow` header lists the methods it takes.","type":"string"},{"const":"payload_too_large","description":"The request body is over the route's size cap (HTTP 413).","type":"string"},{"const":"unsupported_media_type","description":"The body's `Content-Type` is not one the route reads (HTTP 415);\nJSON routes need `application/json`.","type":"string"},{"const":"invalid_body","description":"The JSON body parsed but does not match the route's schema: a\nmissing field or a wrong type (HTTP 422).  The message names it.","type":"string"},{"const":"timeout","description":"The request ran past its route's time cap (HTTP 504).  Retrying\nmay succeed; long work belongs in the async (file) routes.","type":"string"},{"const":"unavailable","description":"The service is starting up or briefly cannot reach its database\n(HTTP 503, with `Retry-After`).  Nothing was wrong with the\nrequest: send it again after `Retry-After` seconds.","type":"string"}]},"ErrorData":{"properties":{"details":{"description":"Error-specific detail: `conflict` on broadcast creation carries\n`{ suggestions: [...] }`; `conflict` on recording start carries\n`{ recordingId, takeover: true }`."},"retryable":{"type":"boolean"}},"required":["retryable"],"type":"object"},"EventOptions":{"additionalProperties":false,"properties":{"drafts":{"default":true,"type":"boolean"},"words":{"default":true,"type":"boolean"}},"type":"object"},"ExportFormat":{"enum":["json","srt","xlsx","pdf"],"type":"string"},"Fix":{"description":"One word-level correction inside a `transcript.amended` frame.","properties":{"kind":{"description":"Which engine pass produced it, namespaced `amend:<mechanism>`:\n`amend:redo:doubt` (doubted words re-decoded), `amend:redo:span`\n(doubted span re-decoded whole), `amend:redo:vote` (two consecutive\nre-decodes agreed on a different word), `amend:redo:insert`,\n`amend:redo:tail` (word across the step cut; `repl` empty = dropped),\n`amend:redo:dot`, `amend:dict` / `amend:bert:spell` / `amend:bert:context`\n(deferred rescorer), `amend:spk:doubt` / `amend:spk:diar` /\n`amend:spk:alias` / `amend:spk:reattr` (speaker moves,\n`orig`/`repl` are `Spk N` labels), `amend:script` (the segment's last\nwords were the script, heard as something else: the next segment\npicked the script up right after them).","type":"string"},"orig":{"type":"string"},"repl":{"type":"string"}},"required":["orig","repl","kind"],"type":"object"},"FixKind":{"description":"How the working address differs from the one given.","oneOf":[{"const":"extension","description":"Another file extension (`label` is it, e.g. `.m3u`).","type":"string"},{"const":"scheme","description":"The other scheme (`label` is `HTTPS` or `HTTP`).","type":"string"},{"const":"page_stream","description":"The stream the web page at that address plays (`label` is its host).","type":"string"}]},"Health":{"properties":{"status":{"$ref":"#/components/schemas/HealthStatus"}},"required":["status"],"type":"object"},"HealthStatus":{"enum":["ok"],"type":"string"},"IngestState":{"oneOf":[{"const":"connecting","description":"Claimed (or about to be): opening the stream and the engine.","type":"string"},{"const":"streaming","description":"PCM is reaching the engine.","type":"string"},{"const":"reconnecting","description":"The stream or the engine dropped; retrying with backoff.","type":"string"},{"const":"stopped","description":"The recording ended (host stop, pause timeout, max duration).","type":"string"},{"const":"failed","description":"Gave up; the recording was ended with reason `stream_failed`.","type":"string"}]},"InputKind":{"oneOf":[{"enum":["live","file","url"],"type":"string"},{"const":"stream","description":"A live audio stream the engine pulls itself (`input.url`: an HLS /\nDASH playlist, an Icecast / Shoutcast stream, or a YouTube live):\ndecoded and transcribed as it plays, as if it arrived over the\nsocket, which carries only control frames and `stream.pull` status.","type":"string"}]},"InputOptions":{"additionalProperties":false,"properties":{"crop":{"anyOf":[{"$ref":"#/components/schemas/CropRange"},{"type":"null"}],"description":"File / URL input only: transcribe this window of the media and skip\nthe rest.  Timestamps count from the window's start."},"kind":{"$ref":"#/components/schemas/InputKind","default":"live"},"prepare":{"description":"The server's own realtime sessions only (a token minted for a recording):\nfetch and prepare the recording's source — the link fetched or the\nupload read, a video's audio extracted, the waveform for the trim\nstep — and store it, reporting `source.progress` and ending with\n`source.prepared`.  Nothing is transcribed.","type":"boolean"},"url":{"type":["string","null"]}},"type":"object"},"JobProgress":{"properties":{"fetchTotalBytes":{"format":"uint64","minimum":0,"type":["integer","null"]},"fetchedBytes":{"description":"While the job is `fetching`: bytes of the source that have arrived,\nand the total once the other end has said (a YouTube video, a URL\nwith a Content-Length).","format":"uint64","minimum":0,"type":["integer","null"]},"pct":{"description":"0..100.","format":"uint8","maximum":255,"minimum":0,"type":"integer"},"phase":{"description":"While the job is `fetching`: `fetching` (the file is on its way) or\n`extracting` (the audio is being taken out of a video).","type":["string","null"]},"phasePct":{"description":"How far the current phase is, 0..100, when it can be told: bytes\nagainst the total while fetching, media seconds decoded against the\nlength while extracting.","format":"uint8","maximum":255,"minimum":0,"type":["integer","null"]},"processedS":{"description":"Media seconds committed so far.","format":"double","type":"number"},"segmentsDone":{"description":"Committed segments so far.","format":"int32","type":"integer"},"segmentsTotal":{"description":"Expected segment count: exact once done, estimated from the media\nduration and the segment rate so far while running.","format":"int32","type":["integer","null"]},"totalS":{"format":"double","type":["number","null"]}},"required":["processedS","segmentsDone","pct"],"type":"object"},"JobState":{"oneOf":[{"enum":["queued","fetching","transcribing","done","failed"],"type":"string"},{"const":"cropping","description":"Design 57: the source is stored and measured, and the job waits for\nthe host to say which part of it to transcribe\n(`POST /v1/recordings/{id}/job/start`).","type":"string"}]},"KnownVoice":{"description":"One voice a realtime session may recognise a speaker as.\n\nThe `id` is a cloned voice's id; the engine loads its embedding from\nthe same place it loads the voice itself, so a caller names the voice\nand never carries a vector over the wire.  `name` is what to call the\nperson when they are recognised — the voice's own name, ordinarily,\nbut the caller's to choose, because a voice called \"Marcus —\nnarration\" belongs to a person called Marcus Reed.","properties":{"color":{"description":"What colour to say this person reads in, echoed back in the\n`transcript.speakerIdentified` frame when they are recognised.\n\nThe engine does not interpret it — exactly as it does not\ninterpret `name`. Both are presentation the caller supplies and\nthe engine carries, and it is carried rather than looked up so\nthat a listener who cannot see the caller's voices still gets the\nsame colour for the same person as everybody else. That is the\nwhole point of a colour belonging to a person.","type":["string","null"]},"id":{"type":"string"},"name":{"type":"string"}},"required":["id","name"],"type":"object"},"LanguageInfo":{"properties":{"code":{"type":"string"},"flag":{"type":["string","null"]},"name":{"type":"string"},"native":{"type":"string"},"previewLine":{"description":"The frozen line a voice preview speaks in this language\n(`tts::preview::LINES`).  Present exactly when `voice` is, because\nthe two cover the same set: a language with no line refuses the\npreview rather than auditioning a voice on English text.  The app\nshows it beside the play button so someone previewing a clone in\nJapanese can read what they are about to hear.","type":["string","null"]},"speakersM":{"description":"Approximate speakers, millions (picker subtitle); absent when unknown.","format":"uint32","minimum":0,"type":["integer","null"]},"transcription":{"type":"boolean"},"translation":{"type":"boolean"},"voice":{"description":"TTS — always false today.","type":"boolean"}},"required":["code","name","native","transcription","translation","voice"],"type":"object"},"LanguageLists":{"properties":{"languages":{"items":{"$ref":"#/components/schemas/LanguageInfo"},"type":"array"},"transcription":{"items":{"type":"string"},"type":"array"},"translation":{"items":{"type":"string"},"type":"array"},"voice":{"items":{"type":"string"},"type":"array"}},"required":["languages","transcription","translation","voice"],"type":"object"},"LastRecording":{"description":"A live transcription's newest recording (`lastRecording`): what\nits row in a list reads from — how long it ran, when, and whether it is\nstill running — and the recording a tap on that row opens.","properties":{"durationS":{"description":"Seconds of audio captured so far, or in all once it has ended.","format":"double","type":["number","null"]},"recordingId":{"type":"string"},"startedAt":{"type":["string","null"]},"state":{"$ref":"#/components/schemas/RecordingState"},"title":{"description":"The title written for it once it finished, when there is one.","type":["string","null"]},"transcriptionId":{"description":"On a broadcast: the live transcription it was made in (one of\n`Broadcast.transcriptions`), which says what it was recorded from.","type":["string","null"]}},"required":["recordingId","state"],"type":"object"},"LatencyMode":{"description":"The latency-mode switch: which model transcribes.","enum":["auto","fast","accuracy"],"type":"string"},"LedgerEntry":{"description":"A ledger entry on the wire (`GET /v1/billing/ledger`).","properties":{"amount":{"description":"Signed: credit positive, a charge negative.","type":"number"},"balanceAfter":{"description":"The balance once this entry landed.","type":"number"},"createdAt":{"type":"string"},"description":{"type":"string"},"id":{"type":"string"},"kind":{"description":"`signup_credit`, `topup`, `auto_topup`, `monthly_credit`,\n`monthly_credit_expiry`, `charge`, `charge_correction`, `refund`,\n`chargeback`, `manual_adjustment` or `promo_credit`.","type":"string"},"metadata":{"description":"`promotional: true` on credit that was never paid for."},"providerOrderId":{"description":"The provider's order that credited (a top-up, an auto top-up, a\nmonthly credit) or was refunded.","type":["string","null"]},"realtimeSessionId":{"type":["string","null"]},"recordingId":{"type":["string","null"]},"staffUser":{"description":"The staff member behind an adjustment or a waiver (the admin's\nview only).","type":["string","null"]},"subscriptionId":{"type":["string","null"]}},"required":["id","kind","amount","balanceAfter","description","metadata","createdAt"],"type":"object"},"LedgerPage":{"description":"`GET /v1/billing/ledger`.","properties":{"entries":{"items":{"$ref":"#/components/schemas/LedgerEntry"},"type":"array"},"nextCursor":{"description":"Pass as `cursor` for the next page; absent on the last.","type":["string","null"]}},"required":["entries"],"type":"object"},"Lifecycle":{"description":"Whether a broadcast, transcription or recording is in use, in the\narchive, or purged.","enum":["active","archived","purged"],"type":"string"},"Limits":{"properties":{"audioBufferS":{"format":"uint64","minimum":0,"type":"integer"},"maxDurationS":{"description":"Stateless `/v1` streams end here.","format":"uint64","minimum":0,"type":"integer"},"maxUploadBytes":{"format":"uint64","minimum":0,"type":"integer"},"recordingMaxDurationS":{"description":"Recordings (transcriptions and broadcasts): the upper bound a\nrecording may set for `maxDurationS` (#32, 8 h). Absent on a server\nwithout a database.","format":"uint64","minimum":0,"type":["integer","null"]},"sessionTokenTtlS":{"format":"uint64","minimum":0,"type":"integer"}},"required":["maxDurationS","audioBufferS","sessionTokenTtlS","maxUploadBytes"],"type":"object"},"Limits2":{"description":"The limits of a version.  `null` is unlimited.","properties":{"concurrentTranscriptions":{"description":"Transcriptions running at once (open recordings, running jobs,\nactive realtime sessions).","format":"int32","type":["integer","null"]},"createsPerHour":{"description":"Transcriptions created per rolling hour, once the account has paid.","format":"int32","type":["integer","null"]},"createsPerHourUnpaid":{"description":"Transcriptions created per rolling hour before the first payment.","format":"int32","type":["integer","null"]},"maxFileBytes":{"description":"The largest file accepted, in bytes.","format":"int64","type":["integer","null"]},"maxLiveSeconds":{"description":"The longest live recording or realtime session, in seconds.","format":"int32","type":["integer","null"]},"requestsPerMin":{"description":"API requests a minute across the account's keys.","format":"int32","type":["integer","null"]},"retentionAudioDays":{"description":"Days a recording's source audio is kept.","format":"int32","type":["integer","null"]},"retentionTranscriptionsDays":{"description":"Days a transcription stays in the library before it is archived.","format":"int32","type":["integer","null"]},"retentionVoicesDays":{"description":"Days a voice is kept.","format":"int32","type":["integer","null"]}},"type":"object"},"LimitsUsage":{"description":"The plan's counted limits, as used now.","properties":{"concurrentLimit":{"description":"Null: unlimited.","format":"int64","type":["integer","null"]},"createLimit":{"description":"Null: unlimited.  The unpaid figure until the first payment.","format":"int64","type":["integer","null"]},"createdLastHour":{"description":"Transcriptions created in the rolling hour.","format":"int64","type":"integer"},"resetsAt":{"description":"When the next creation fits, while the hour's creations are used up.","type":["string","null"]},"running":{"description":"Transcriptions running now (realtime sessions, live recordings,\nrunning jobs).","format":"int64","type":"integer"}},"required":["running","createdLastHour"],"type":"object"},"LineEdit":{"additionalProperties":false,"description":"Retype a whole segment. The word count may change; the new words are\ntimed evenly across the segment's span and all count as settled.","properties":{"seq":{"format":"int64","type":"integer"},"to":{"description":"Empty clears the segment (a sentence spanning several segments is\nrewritten into its first one).","type":"string"}},"required":["seq","to"],"type":"object"},"LinkKind":{"description":"What a link pasted as \"From URL\" turns out to be (`GET\n/v1/transcriptions/probe`): a live stream belongs in the live form.","oneOf":[{"const":"live","description":"On air, or a YouTube live due to start: record it live.","type":"string"},{"const":"file","description":"A finished recording or a file: transcribe it as one.","type":"string"},{"const":"unknown","description":"Could not be told (it did not answer, or answered with something else).","type":"string"}]},"LinkProbe":{"properties":{"kind":{"$ref":"#/components/schemas/LinkKind"},"ok":{"description":"Did the link open as something to transcribe — a stream, a playlist,\na media file, a YouTube video?  `false` when it did not answer,\nanswered an error, or is a web page (design 81's \"That link didn't\nwork\").  A YouTube refusal that may pass (a bot wall, a timeout)\ncounts as working: it cannot be told from here.","type":"boolean"},"problem":{"description":"Why it did not, as a sentence; absent when `ok`.","type":["string","null"]},"source":{"description":"`youtube` or `radio` — the live form's source for it.","type":"string"}},"required":["kind","source","ok"],"type":"object"},"LoadingStage":{"enum":["download","model"],"type":"string"},"LowBalanceAlertChoices":{"properties":{"default":{"description":"The line of an account that set none.","type":"number"},"presets":{"items":{"description":"Dollars (USD), a decimal number with at most six decimals.","type":"number"},"type":"array"},"range":{"$ref":"#/components/schemas/MoneyRange"}},"required":["presets","range","default"],"type":"object"},"LowBalanceAlertSettings":{"description":"`PUT /v1/billing/low-balance-alert`.","properties":{"enabled":{"type":"boolean"},"threshold":{"default":null,"description":"Email when the balance falls to this: inside the plan list's\n`lowBalanceAlert.range`; the current line when left out.","type":["number","null"]}},"required":["enabled"],"type":"object"},"LowBalanceAlertView":{"properties":{"enabled":{"type":"boolean"},"threshold":{"description":"The email goes out once each time the balance falls to this.","type":"number"}},"required":["enabled","threshold"],"type":"object"},"MediaSummary":{"properties":{"bytes":{"format":"int64","type":"integer"},"codec":{"type":"string"},"container":{"type":"string"},"durationS":{"format":"double","type":["number","null"]},"kind":{"type":"string"},"state":{"type":"string"}},"required":["kind","codec","container","bytes","state"],"type":"object"},"MoneyRange":{"description":"Two amounts, both inclusive.","properties":{"max":{"description":"Dollars (USD), a decimal number with at most six decimals.","type":"number"},"min":{"description":"Dollars (USD), a decimal number with at most six decimals.","type":"number"}},"required":["min","max"],"type":"object"},"MonthlyCreditView":{"properties":{"granted":{"description":"Dollars (USD), a decimal number with at most six decimals.","type":"number"},"grantedAt":{"type":"string"},"remaining":{"description":"Dollars (USD), a decimal number with at most six decimals.","type":"number"},"renewsAt":{"description":"The next grant; null when the subscription ends at its period end.","type":["string","null"]},"used":{"description":"Spent since the grant, at most the grant (monthly credit is spent first).","type":"number"}},"required":["granted","used","remaining","grantedAt"],"type":"object"},"NameSpeaker":{"additionalProperties":false,"properties":{"name":{"default":null,"description":"Empty or absent clears the label.","type":["string","null"]}},"type":"object"},"NextPayment":{"properties":{"amount":{"description":"Dollars (USD), a decimal number with at most six decimals.","type":"number"},"at":{"type":"string"}},"required":["at","amount"],"type":"object"},"NotVoicedReason":{"description":"Why a corrected line is not voiced as corrected.","oneOf":[{"const":"rewrite","description":"The corrected text says words the line's audio does not: neither\nthe recognizer's text nor a fresh reading of the audio (taken when\na correction adds words) has them. VocalStack voices only what was\nsaid, so the line keeps the recognizer's words in voice clips and\nvoice tracks.","type":"string"}]},"PatchVoice":{"additionalProperties":false,"properties":{"color":{"type":["string","null"]},"consent":{"type":["boolean","null"]},"draft":{"type":["boolean","null"]},"enabled":{"type":["boolean","null"]},"gender":{"type":["string","null"]},"name":{"type":["string","null"]}},"type":"object"},"Payment":{"description":"One line of the history.","properties":{"amount":{"description":"What was charged (tax included) or credited; a refund's amount is\nwhat went back, as a positive number.","type":"number"},"at":{"type":"string"},"description":{"description":"`Top-up`, `Auto top-up`, `Premium, monthly`, `Signup credit`, ...","type":"string"},"document":{"description":"`invoice` (a subscription), `receipt` (a top-up), or null when there\nis none (credit, pending, failed).","type":["string","null"]},"id":{"type":"string"},"kind":{"description":"`topup`, `auto_topup`, `subscription`, `signup_credit`, `promo`,\n`adjustment` or `refund`.","type":"string"},"status":{"description":"`paid`, `pending`, `failed`, `refunded` or `credit` (never paid for).","type":"string"}},"required":["id","at","description","amount","status","kind"],"type":"object"},"PaymentDocument":{"description":"`GET /v1/billing/payments/:id/document`: `url` when it is ready,\n`pending: true` (with `202`) while the provider makes it.","properties":{"pending":{"type":["boolean","null"]},"url":{"type":["string","null"]}},"type":"object"},"PaymentMethodList":{"description":"`GET /v1/billing/payment-methods`.","properties":{"methods":{"items":{"$ref":"#/components/schemas/PaymentMethodView"},"type":"array"}},"required":["methods"],"type":"object"},"PaymentMethodView":{"description":"One saved method.","properties":{"brand":{"description":"`visa`, `mastercard`, `amex`, ... as the provider names it.","type":"string"},"declined":{"description":"The last automatic charge or renewal on it was declined.","type":"boolean"},"expMonth":{"format":"int32","type":["integer","null"]},"expYear":{"format":"int32","type":["integer","null"]},"id":{"type":"string"},"isDefault":{"type":"boolean"},"last4":{"type":"string"},"wallet":{"description":"`apple_pay`, `google_pay`, ... when the card sits in a wallet.","type":["string","null"]}},"required":["id","brand","last4","isDefault","declined"],"type":"object"},"PaymentPage":{"description":"`GET /v1/billing/payments`.","properties":{"nextCursor":{"type":["string","null"]},"payments":{"items":{"$ref":"#/components/schemas/Payment"},"type":"array"}},"required":["payments"],"type":"object"},"PendingChange":{"properties":{"appliesAt":{"type":"string"},"rates":{"$ref":"#/components/schemas/Rates"}},"required":["appliesAt","rates"],"type":"object"},"PendingInterval":{"properties":{"appliesAt":{"type":"string"},"interval":{"description":"`month` or `year`.","type":"string"}},"required":["interval","appliesAt"],"type":"object"},"PendingPayment":{"properties":{"amount":{"description":"Dollars (USD), a decimal number with at most six decimals.","type":"number"},"kind":{"description":"`topup` or `subscribe`.","type":"string"},"since":{"type":"string"}},"required":["amount","kind","since"],"type":"object"},"PlanList":{"description":"`GET /v1/billing/plans`.","properties":{"autoTopUp":{"$ref":"#/components/schemas/AutoTopUpChoices"},"downgradeGraceDays":{"description":"After a subscription ends, data older than the default plan's\nretention is kept this many days more.","format":"int32","type":"integer"},"enabled":{"description":"False when this server does not bill.","type":"boolean"},"lowBalanceAlert":{"$ref":"#/components/schemas/LowBalanceAlertChoices"},"plans":{"items":{"$ref":"#/components/schemas/PlanListing"},"type":"array"},"signupCredit":{"description":"The credit a new account starts with.","type":"number"},"topUpAmounts":{"description":"The top-up amounts a checkout offers (presets).","items":{"description":"Dollars (USD), a decimal number with at most six decimals.","type":"number"},"type":"array"},"topUpRange":{"$ref":"#/components/schemas/MoneyRange","description":"Any whole-cent top-up inside this is taken."}},"required":["enabled","plans","topUpAmounts","topUpRange","autoTopUp","lowBalanceAlert","signupCredit","downgradeGraceDays"],"type":"object"},"PlanListing":{"description":"One plan on the price list: its current version's rates, prices,\ncredit, limits and features.","properties":{"billedExternally":{"description":"An enterprise-style plan: no checkout, billed by hand.","type":"boolean"},"features":true,"graceOnZero":{"description":"When the credit runs out, running transcriptions go on for a while\n(a grace period) instead of stopping at once.  New ones are refused\nat zero on every plan.","type":"boolean"},"id":{"type":"string"},"kind":{"description":"`prepaid`, `subscription` or `custom`.","type":"string"},"limits":{"$ref":"#/components/schemas/Limits2"},"monthlyCredit":{"description":"Dollars (USD), a decimal number with at most six decimals.","type":"number"},"name":{"type":"string"},"priceMonth":{"description":"Dollars (USD), a decimal number with at most six decimals.","type":"number"},"priceYear":{"description":"Dollars (USD), a decimal number with at most six decimals.","type":"number"},"rates":{"$ref":"#/components/schemas/Rates"},"warmStart":{"description":"`cold`, `warm_offpeak` or `warm`.","type":"string"}},"required":["id","name","kind","billedExternally","rates","priceMonth","priceYear","monthlyCredit","graceOnZero","limits","warmStart","features"],"type":"object"},"PlanRef":{"properties":{"id":{"type":"string"},"kind":{"description":"`prepaid`, `subscription` or `custom`.","type":"string"},"name":{"type":"string"}},"required":["id","name","kind"],"type":"object"},"PortalLink":{"description":"`GET /v1/billing/portal`.","properties":{"url":{"description":"The provider's customer portal: payment methods, invoices, receipts.","type":"string"}},"required":["url"],"type":"object"},"PromoCodeView":{"description":"What a promo code gives for the purchase it was checked for.","properties":{"bonusCredit":{"description":"Credit added with a top-up (promotional); null for a subscription.","type":["number","null"]},"code":{"description":"The code, upper-cased.","type":"string"},"description":{"type":"string"},"firstPaymentDiscount":{"description":"Off a subscription's first payment; null for a top-up.","type":["number","null"]}},"required":["code","description"],"type":"object"},"PublishGrant":{"properties":{"expiresAt":{"type":["string","null"]},"recording":{"$ref":"#/components/schemas/Recording"},"sessionId":{"description":"The realtime session `streamUrl` opens (as\n`CreateSessionResponse.sessionId`).","type":["string","null"]},"streamUrl":{"description":"Connect here (the realtime socket; treat the URL as opaque) before\n`expiresAt`.  Absent for a stream source: the server is the\npublisher there (see `recording.ingest`), nothing on the device sends\naudio.","type":["string","null"]}},"required":["recording"],"type":"object"},"PublisherInfo":{"properties":{"attached":{"type":"boolean"},"leaseExpiresAt":{"type":["string","null"]},"machineId":{"type":["string","null"]}},"required":["attached"],"type":"object"},"PullState":{"description":"Where an engine's pull of a live stream stands (`stream.pull`).","oneOf":[{"enum":["connecting","streaming","reconnecting"],"type":"string"},{"const":"failed","description":"The stream will not play (gone, refused, not audio): the recording ends.","type":"string"}]},"Rates":{"description":"The rates of a version, in dollars per hour.","properties":{"filePerHour":{"description":"Per hour of a file or link transcription.","type":"number"},"livePerHour":{"description":"Per hour of live audio (a live recording or a realtime session).","type":"number"}},"required":["filePerHour","livePerHour"],"type":"object"},"Recording":{"properties":{"createdAt":{"type":"string"},"endReason":{"type":["string","null"]},"endedAt":{"type":["string","null"]},"finalizedAt":{"type":["string","null"]},"id":{"type":"string"},"ingest":{"anyOf":[{"$ref":"#/components/schemas/StreamIngest"},{"type":"null"}],"description":"A stream source's server-side publisher: where it stands with the\nURL and the last error.  Absent on microphone and upload recordings."},"kind":{"$ref":"#/components/schemas/RecordingKind"},"lastSeq":{"description":"Highest published frame sequence (-1 = none yet).","format":"int64","type":"integer"},"lifecycle":{"$ref":"#/components/schemas/Lifecycle","description":"Active, or archived (in the trash until it is restored or purged)."},"maxDurationS":{"format":"int32","type":"integer"},"maxPauseS":{"format":"int32","type":"integer"},"media":{"description":"Which media artifacts exist and are sealed.","items":{"$ref":"#/components/schemas/MediaSummary"},"type":"array"},"mediaDurationS":{"format":"double","type":["number","null"]},"metrics":{"anyOf":[{"$ref":"#/components/schemas/RecordingMetrics"},{"type":"null"}],"description":"Operational instrumentation, filled at finalize."},"peakLatencyS":{"format":"double","type":["number","null"]},"peakListeners":{"format":"int32","type":["integer","null"]},"publisher":{"$ref":"#/components/schemas/PublisherInfo"},"scriptId":{"description":"The script this recording was started from (made in the VocalStack\napp), while that script exists.","type":["string","null"]},"scriptText":{"description":"The words this recording follows (HTML subset: `<b>`, `<i>`, `<u>`,\n`<br>`): its own copy, given with the start or taken from the script\nit was started from.  The engine matches the transcript against\nthem as the speaker goes and reports how far along it is on each\ncommitted segment (`scriptPos`).","type":["string","null"]},"settings":{"$ref":"#/components/schemas/Settings"},"source":{"anyOf":[{"$ref":"#/components/schemas/SourceInfo"},{"type":"null"}],"description":"What an upload was made from (file name / size / type, or URL)."},"spans":{"items":{"$ref":"#/components/schemas/CaptureSpan"},"type":"array"},"speakerCount":{"format":"int32","type":["integer","null"]},"spokenLanguage":{"type":["string","null"]},"startedAt":{"type":["string","null"]},"state":{"$ref":"#/components/schemas/RecordingState"},"summarizedAt":{"type":["string","null"]},"summary":{"description":"The summary that comes with the title, a paragraph or two.","type":["string","null"]},"summaryLanguage":{"description":"Language code the title and summary are written in.","type":["string","null"]},"title":{"description":"An AI-written title, once the recording has ended and the engine's\npost-processing pass has run: a few words on what it was about, in\nthe language spoken.  Read-only; absent until then.","type":["string","null"]},"transcriptionId":{"description":"The transcription it was made in.  A recording started on a\nbroadcast is made in a live transcription of its own, attached to\nthe broadcast (design 77).","type":"string"},"wallDurationS":{"format":"double","type":["number","null"]}},"required":["id","transcriptionId","kind","state","lifecycle","maxDurationS","maxPauseS","settings","publisher","lastSeq","spans","media","createdAt"],"type":"object"},"RecordingCharge":{"description":"A charge on the wire (`GET /v1/billing/charges`).","properties":{"amount":{"description":"Dollars (USD), a decimal number with at most six decimals.","type":"number"},"kind":{"description":"`file`, `live` or `realtime`.","type":"string"},"name":{"description":"The transcription's name.","type":["string","null"]},"openedAt":{"type":"string"},"ratePerHour":{"description":"The pinned rate, dollars an hour.","type":"number"},"recordingId":{"type":"string"},"secondsBilled":{"description":"Audio seconds billed so far (the final figure once settled).","format":"double","type":"number"},"settledAt":{"type":["string","null"]},"state":{"description":"`open` while the audio runs, `settled` once charged, `void` when\nwaived.","type":"string"},"transcriptionId":{"type":["string","null"]}},"required":["recordingId","kind","ratePerHour","secondsBilled","amount","state","openedAt"],"type":"object"},"RecordingEvent":{"description":"One durable frame of a recording, as the host's screen received it.","properties":{"at":{"description":"Wall time the frame was written (RFC 3339).","type":"string"},"kind":{"type":"string"},"payload":{"description":"The frame itself, exactly as published to subscribers."},"seq":{"format":"int64","type":"integer"}},"required":["seq","kind","at","payload"],"type":"object"},"RecordingEvents":{"properties":{"events":{"items":{"$ref":"#/components/schemas/RecordingEvent"},"type":"array"}},"required":["events"],"type":"object"},"RecordingKind":{"enum":["live","upload"],"type":"string"},"RecordingMetrics":{"properties":{"settleP50S":{"description":"Sentence close → settled, seconds.","format":"double","type":["number","null"]},"settleP95S":{"format":"double","type":["number","null"]},"silencePaused":{"description":"Times the silence watchdog paused this recording.","format":"int32","type":"integer"},"suppressedSegments":{"description":"Segments hidden from listeners as \"Audio unclear\".","format":"int32","type":["integer","null"]},"translationLatencyP50S":{"description":"Sentence close → definitive translation, seconds.","format":"double","type":["number","null"]}},"required":["silencePaused"],"type":"object"},"RecordingState":{"description":"Recording lifecycle states.","enum":["pending","live","paused","processing","ended","finalized"],"type":"string"},"RetranscribeLine":{"additionalProperties":false,"description":"Design 50: read one line's audio again in a named language.\n\nNot an edit the caller writes — an edit the caller ASKS FOR: the text\ncomes from the recognizer, and lands as the same `line` corrections a\nhand-typed rewrite would.","properties":{"lang":{"description":"The language to force on the decoder, as a language code.","type":"string"},"seqs":{"description":"The segments the line is assembled from: neighbouring segments,\nevery one from the first to the last (400 `invalid_options`\notherwise). Their audio is re-read as one stretch, and each keeps\nthe words said in its own.","items":{"format":"int64","type":"integer"},"type":"array"}},"required":["seqs","lang"],"type":"object"},"RtmpStatus":{"description":"`GET /v1/rtmp`: what the setup panel shows.","properties":{"audio":{"description":"What it sends, e.g. \"48 kHz AAC\" — or, with `live` false, why an\nencoder that is connected cannot be used (\"Speex audio is not\nsupported — set the encoder to AAC\").","type":["string","null"]},"enabled":{"description":"Whether this server has an RTMP ingest to publish to at all.","type":"boolean"},"live":{"description":"An encoder is publishing audio on this key right now.","type":"boolean"},"monitorUrl":{"description":"The incoming audio, for the panel's monitor (an hour's signed link).","type":"string"},"serverUrl":{"description":"Paste into the encoder as the server URL.","type":"string"},"streamKey":{"description":"Paste into the encoder as the stream key.  One per account, kept\nuntil the account holder resets it in the VocalStack app (a reset\ndisconnects whatever publishes on the old key).","type":"string"}},"required":["enabled","serverUrl","streamKey","live","monitorUrl"],"type":"object"},"SearchHit":{"properties":{"linkCode":{"description":"The link that opens the hit: the code of the broadcast it was found\nthrough when a listener searched broadcasts, otherwise its\ntranscription's own.","type":"string"},"linkName":{"description":"The name of what `linkCode` opens.","type":"string"},"live":{"type":"boolean"},"mediaStart":{"format":"double","type":"number"},"recordedAt":{"type":["string","null"]},"recordingId":{"type":"string"},"rendered":{"description":"In the requested `lang`, when it differs and could be rendered.","type":["string","null"]},"score":{"format":"double","type":"number"},"sentenceId":{"format":"int64","type":"integer"},"speaker":{"format":"int32","type":"integer"},"text":{"description":"As spoken.","type":"string"},"transcriptionId":{"description":"The transcription the recording is in.","type":"string"},"wallStart":{"format":"double","type":"number"}},"required":["recordingId","transcriptionId","linkCode","linkName","sentenceId","speaker","mediaStart","wallStart","text","score","live"],"type":"object"},"SearchResults":{"properties":{"hits":{"items":{"$ref":"#/components/schemas/SearchHit"},"type":"array"},"query":{"type":"string"},"queryEn":{"description":"The English form the index was matched against.","type":"string"}},"required":["query","queryEn","hits"],"type":"object"},"SegmentView":{"properties":{"catchup":{"type":"boolean"},"confidence":{"format":"double","type":["number","null"]},"corrected":{"type":"boolean"},"decoder":{"type":["string","null"]},"end":{"format":"double","type":"number"},"gapBefore":{"format":"double","type":["number","null"]},"language":{"type":["string","null"]},"latencyS":{"format":"double","type":["number","null"]},"scriptFrom":{"description":"The first script word this line read, when it matched the script\n(`CommittedSegment.scriptFrom`).","format":"int64","type":["integer","null"]},"scriptPos":{"description":"When the recording follows a script: how many of its words the\nengine had matched by the end of this line (`CommittedSegment.scriptPos`).","format":"int64","type":["integer","null"]},"seq":{"format":"int64","type":"integer"},"settled":{"type":"boolean"},"speaker":{"format":"int32","type":"integer"},"speakerConfidence":{"format":"double","type":["number","null"]},"start":{"format":"double","type":"number"},"suppressed":{"description":"Hidden from listeners as \"Audio unclear\" — the line scored\nunder `confidence.hideBelow` (0.60 by default).","type":"boolean"},"text":{"type":"string"},"translations":{"additionalProperties":{"type":"string"},"type":"object"},"unclear":{"description":"Design 35: this line IS being hidden from listeners, and the\nreader is being told so while keeping the text.\n\nThe other side of `suppressed`, and never set with it: a listener\ngets `suppressed` and no text, everyone who is shown the text — the\nhost, and a share-link reader — gets `unclear` and all of it. Set by\nthe relay, which is the only place that knows the rule: the floor is\na per-recording setting over a deployment default, so a client\napplying its own number would mark a different set of lines from the\nones its audience is missing.\n\nIt is also what tells a reader NOT to ask for the line's audio: a\nhidden line is spoken to nobody, the owner included, and the clip\nroute answers 404 for it.","type":"boolean"},"wallStart":{"description":"Wall-clock offset of `start`, via the span map.","format":"double","type":"number"},"words":{"items":{"$ref":"#/components/schemas/Word"},"type":"array"}},"required":["seq","speaker","start","end","wallStart","text","catchup","corrected","settled"],"type":"object"},"SentenceOptions":{"additionalProperties":false,"properties":{"enabled":{"default":true,"description":"Emit `transcript.sentence` frames (and, with `translate`, translate\nsentences rather than committed segments). Default true (#46):\nrows, search and corrections are sentence-granular.","type":"boolean"},"idleCloseS":{"default":3.0,"description":"Close an open sentence after this many seconds without a new\ncommitted segment, even when no sentence-final punctuation arrived.","format":"double","type":"number"},"provisionalTranslations":{"default":true,"description":"Also translate a sentence while it is still open (debounced); the\nresult is flagged `provisional` and replaced when the sentence closes.","type":"boolean"}},"type":"object"},"SentenceSegment":{"properties":{"closeReason":{"description":"Why a closed sentence closed: \"punct\", \"idle\", \"speaker\", \"cap\", \"eof\".","type":["string","null"]},"end":{"format":"double","type":"number"},"language":{"type":["string","null"]},"provisional":{"description":"True when `translations` were made from an open (incomplete)\nsentence and will be replaced once it closes.","type":["boolean","null"]},"sentenceId":{"description":"Per realtime session (or recording), monotonic, 0-based. Stable for the sentence's lifetime.","format":"uint64","minimum":0,"type":"integer"},"seqs":{"description":"`seq` of every committed segment in this sentence, in order.","items":{"format":"uint64","minimum":0,"type":"integer"},"type":"array"},"settled":{"default":false,"description":"Closed and past the engine's correction window: the text is final\n(short of a rare late amend) and `translations`, when present, are\ndefinitive rather than provisional.","type":"boolean"},"speaker":{"format":"int32","type":"integer"},"start":{"format":"double","type":"number"},"status":{"$ref":"#/components/schemas/SentenceStatus"},"text":{"type":"string"},"translationLatencyS":{"additionalProperties":{"format":"double","type":"number"},"description":"How far behind the SPEAKER each language's translation is: seconds\nfrom the end of this line's speech to the translation being attached\n(how far the translation trails the speaker).\n\nIt measured from the sentence CLOSING until 2026-09-12, which is a\nlate leg of the pipeline and omitted everything before it, including\nthe close wait — it read 2.6 s where a listener had waited 10.6 s.\nThis is the whole wait, and it is the number to show a reader asking\nhow far behind they are.\n\nPresent on definitive translations of live input only; a file job\ntranscodes faster than realtime and has no speaker to be behind.","type":["object","null"]},"translationSources":{"additionalProperties":{"type":"string"},"type":["object","null"]},"translations":{"additionalProperties":{"type":"string"},"type":["object","null"]}},"required":["sentenceId","speaker","start","end","text","status","seqs"],"type":"object"},"SentenceStatus":{"oneOf":[{"const":"open","description":"Still growing; `text` may change and `seqs` may extend.","type":"string"},{"const":"closed","description":"Complete. `text` changes only if one of its segments is amended.","type":"string"},{"const":"dropped","description":"Its segments were regrouped into a neighbouring sentence; discard.","type":"string"}]},"SentenceView":{"properties":{"end":{"format":"double","type":"number"},"language":{"type":["string","null"]},"provisional":{"type":["boolean","null"]},"sentenceId":{"format":"int64","type":"integer"},"seqs":{"items":{"format":"int64","type":"integer"},"type":"array"},"settled":{"type":"boolean"},"speaker":{"format":"int32","type":"integer"},"start":{"format":"double","type":"number"},"status":{"type":"string"},"suppressed":{"type":"boolean"},"text":{"type":"string"},"translations":{"additionalProperties":{"type":"string"},"type":"object"},"unclear":{"description":"See `SegmentView::unclear`.","type":"boolean"},"wallStart":{"format":"double","type":"number"}},"required":["sentenceId","speaker","start","end","wallStart","text","status","settled","seqs"],"type":"object"},"ServerFrame":{"oneOf":[{"description":"A heartbeat while the machine gets ready (`stage`: `download`, then\n`model`); each one re-arms the client's ready deadline.  Send no\naudio yet.","properties":{"stage":{"$ref":"#/components/schemas/LoadingStage"},"type":{"const":"session.loading","type":"string"}},"required":["type","stage"],"type":"object"},{"description":"The realtime session is ready: send audio from now on.","properties":{"type":{"const":"session.ready","type":"string"}},"required":["type"],"type":"object"},{"description":"One final segment, with word timings, numbered by `seq` (0, 1, 2…,\ngapless).  Append it; the draft tail it covers is gone.  Its text or\nspeaker can later be corrected in place by `transcript.amended`.","properties":{"segment":{"$ref":"#/components/schemas/CommittedSegment"},"type":{"const":"transcript.committed","type":"string"}},"required":["type","segment"],"type":"object"},{"description":"The live tail: the words heard since the last commit, still\nchanging.  A full replacement of the previous draft: redraw the tail\nwith `segments`, never append it.  It begins where the last committed\nsegment ended; an empty list clears it.  Live input only (a file\nsends none unless `events.drafts` asks), and never to a recording's\nsubscribers.","properties":{"segments":{"items":{"$ref":"#/components/schemas/DraftSegment"},"type":"array"},"type":{"const":"transcript.draft","type":"string"}},"required":["type","segments"],"type":"object"},{"description":"Word timings for the live tail as they firm up (`events.words`);\nephemeral, carries no `seq`.","properties":{"type":{"const":"transcript.words","type":"string"},"words":{"items":{"$ref":"#/components/schemas/Word"},"type":"array"}},"required":["type","words"],"type":"object"},{"description":"An already-delivered committed segment was rewritten by the engine\n(a later decode with more context corrected a word, punctuation or\nthe speaker). `text` is the segment's full new text.","properties":{"fixes":{"items":{"$ref":"#/components/schemas/Fix"},"type":"array"},"scriptWords":{"description":"Words of the new text the script wrote (a misheard word the\nrescorer swapped for a near-identical script word), by position\nin `text`'s words; they take `Word.script`.  Absent when none.","items":{"format":"uint32","minimum":0,"type":"integer"},"type":"array"},"seq":{"format":"uint64","minimum":0,"type":"integer"},"speaker":{"format":"int32","type":"integer"},"speakerConfidence":{"description":"New diarization confidence for `speaker` when the amend\nre-attributed the segment (see `CommittedSegment.speakerConfidence`);\nabsent when unchanged.","format":"double","type":["number","null"]},"tail":{"anyOf":[{"$ref":"#/components/schemas/AmendTail"},{"type":"null"}],"description":"When the amend rewrote the segment's last words: they replace\nits last `replaced` words (with their times and marks); absent\nfor a text-only amend."},"text":{"type":"string"},"type":{"const":"transcript.amended","type":"string"}},"required":["type","seq","speaker","text"],"type":"object"},{"description":"A delivered segment's latency, measured again now that the engine's\ncorrection passes are done with it.  `latencyS` is the figure from\n`CommittedSegment.latencyS` plus the time the segment then spent\nunder a hold (a doubted word's re-decode, the language model's\nreading): how far behind the speaker this line was FINAL, which is\nwhat the harness's μ Lat counts (a doubted row at its settle time).\nReplaces the segment's `latencyS`.  Sent only for a line a hold was\non, and only on live input (a realtime session, never a file); a\nline no pass held keeps the latency its commit carried.","properties":{"latencyS":{"format":"double","type":"number"},"seq":{"format":"uint64","minimum":0,"type":"integer"},"type":{"const":"transcript.latency","type":"string"}},"required":["type","seq","latencyS"],"type":"object"},{"description":"A tentative speaker label on a delivered segment was settled:\n`verdict` is \"confirmed\" (later evidence agrees), \"amended\" (the\nsegment was re-attributed; the `transcript.amended` frame with the\nnew speaker precedes this) or \"unverified\" (no better evidence\narrived, the label stands). `speaker`/`speakerConfidence` are the\nsegment's current values; `speakerProvisional` is now false.","properties":{"seq":{"format":"uint64","minimum":0,"type":"integer"},"speaker":{"format":"int32","type":"integer"},"speakerConfidence":{"format":"double","type":["number","null"]},"type":{"const":"transcript.speaker","type":"string"},"verdict":{"type":"string"}},"required":["type","seq","speaker","verdict"],"type":"object"},{"description":"A speaker's gender, as the pitch band heard it, once it settles\n(`casting::roster`).  Sent once per speaker per realtime session (or recording) — twice\nonly if a verdict changes as more of their audio arrives.\n\n`gender` is `\"male\"` or `\"female\"`; a speaker the band refuses to\njudge is never the subject of this frame at all, because \"not\nknown\" is not a verdict to publish.  `confidence` is the share of\ntheir pitch that backs it.\n\nWhat it is FOR: the voice sheet marks a speaker's chip with it and\nopens the group of voices that suits them.  It is deliberately not\nused to pick a voice — that is `voice_cast`'s job, on the engine,\nwhere the verdict comes from.","properties":{"confidence":{"format":"double","type":["number","null"]},"gender":{"type":"string"},"speaker":{"format":"int32","type":"integer"},"type":{"const":"transcript.speakerGender","type":"string"}},"required":["type","speaker","gender"],"type":"object"},{"description":"A diarized speaker turned out to be somebody the caller has a\nsaved voice for (`speaker_id`).  Sent the moment the match lands,\nwhich is a few seconds into that person talking — never with the\nfirst row, because a second of audio is not enough to put a name\nto and a wrong name is worse than none.\n\nSo a transcript reads \"Speaker 2\" for a line or two and then\nbecomes \"Marcus Reed\", retroactively, for every row already shown.\nClients are expected to re-label what they have rather than only\nwhat arrives next — that is the whole shape of this frame.\n\nAn empty `name` withdraws a claim: the end of a realtime session re-decides\nthe whole room at once with more of everybody, and it can take a\nname back as well as give one.  `voiceId` is the saved voice that\nrecognised them.  `confidence` is a cosine similarity, not a\nprobability — comparable between speakers, not a percentage.\n\n`tone` marks a name that came from a clone's tone colour instead\nof a verification embedding: weaker evidence, and worth showing\ndifferently if a client shows it at all.","properties":{"color":{"description":"The colour this person reads in, as the caller stated it in\n`identifyVoices` — a palette slot `\"1\"`–`\"6\"` or `\"#rrggbb\"`.","type":["string","null"]},"confidence":{"format":"double","type":["number","null"]},"name":{"type":"string"},"speaker":{"format":"int32","type":"integer"},"tone":{"type":"boolean"},"type":{"const":"transcript.speakerIdentified","type":"string"},"voiceId":{"type":["string","null"]}},"required":["type","speaker","name"],"type":"object"},{"description":"Full-replacement upsert of one sentence, keyed by `sentenceId`.\nSent whenever the sentence grows, changes, closes, or gains a\ntranslation. Only with `sentences.enabled`.","properties":{"sentence":{"$ref":"#/components/schemas/SentenceSegment"},"type":{"const":"transcript.sentence","type":"string"}},"required":["type","sentence"],"type":"object"},{"properties":{"seq":{"format":"uint64","minimum":0,"type":"integer"},"sources":{"additionalProperties":{"type":"string"},"description":"Where each language's translation came from: \"cache\", \"google\" or \"llm\".","type":["object","null"]},"translations":{"additionalProperties":{"type":"string"},"type":"object"},"type":{"const":"transcript.translated","type":"string"}},"required":["type","seq","translations"],"type":"object"},{"description":"A spoken rendering of one definitive sentence (`SessionOptions::voice`).\nThe engine sends the clip inline (`audio`, base64 Ogg/Opus, 24 kHz\nmono); the API stores it and replaces `audio` with `url`\n(`/v1/recordings/{id}/voice/{sentenceId}?lang=…`, `&format=caf` for\nApple) before relaying.  `latencyS` is sentence close → clip ready.","properties":{"audio":{"type":["string","null"]},"cloned":{"description":"True when `voice` names a cloned voice: the listener must be\ntold the speech is synthetic and whose it imitates.","type":"boolean"},"durationS":{"format":"double","type":"number"},"format":{"description":"Container of `audio` / the object behind `url`: `ogg`.","type":"string"},"generation":{"format":"uint64","minimum":0,"type":"integer"},"lang":{"type":"string"},"latencyS":{"description":"Sentence CLOSE → this clip. Kept for what already reads it;\n`speakerToAudioS` is the number that describes the listener's\nexperience, and this one is always smaller.","format":"double","type":["number","null"]},"part":{"default":0,"description":"Which piece of the line this clip is, and how many pieces the\nline was spoken in (`tts::split`).  A long line is cut at its\nclause boundaries so the first piece can be heard while the\nrest are still being made; the pieces are played back to back\nand are ONE line on screen, with one id and one translation.\n`0` and `1` for a line spoken whole, which is most of them.","format":"uint32","minimum":0,"type":"integer"},"parts":{"default":1,"format":"uint32","minimum":0,"type":"integer"},"queueWaitS":{"description":"How long the clip waited for a synthesis slot before any work\non it started. `synth_s` alone cannot tell an engine that is\nslow from an engine that is BUSY, and those want opposite\nfixes: a slow one wants a smaller model, a busy one wants more\nlanes. In the tail of a backed-up realtime session every clip has a\nsmall `synthS` and a large one of these.","format":"double","type":"number"},"sentenceId":{"format":"uint64","minimum":0,"type":"integer"},"speakerToAudioS":{"description":"The one honest clock: how long after the speaker finished\nsaying this line did audio of it exist.\n\nEvery other latency here measures one leg, and they start and\nend in different places, so none of them can be added to\nanother and all of them flatter the system. This measures the\nwhole journey — decode, close, settle, translate, queue,\nsynthesis — because that is the only one anybody hears.","format":"double","type":["number","null"]},"synthS":{"description":"Synthesis wall time, seconds — the work, not the waiting.","format":"double","type":"number"},"translationLatencyS":{"format":"double","type":["number","null"]},"type":{"const":"transcript.voice","type":"string"},"url":{"type":["string","null"]},"voice":{"description":"The voice that spoke it, by its catalogue id (`GET\n/v1/voices/available`) — or `clone:<id>` for a cloned voice.\nAbsent only on frames from before the engine reported it.","type":["string","null"]},"voiceKey":{"description":"Which audience choice this clip was made for: the voice half\nof the `SessionOptions::voice` entry behind it, empty (and\nabsent on the wire) for \"the engine's default\".  `voice` says\nwhat spoke; this says what was *asked for*, and it is what a\nsubscriber's `?voice=` is matched against — the two differ\nwhenever the asker named no voice.","type":"string"}},"required":["type","sentenceId","generation","lang","format","durationS","synthS"],"type":"object"},{"description":"Why a listener who picked a voice is not hearing it yet — or will\nnot hear it at all.  Sent once per (language, voice) and state,\nand filtered to the listeners on that pair exactly as\n`transcript.voice` is.\n\nBefore this a listener who picked a voice whose assets were not on\nthe machine got silence and a server log line: the sentences went\nby unspoken and nothing said why.  `preparing` means the assets\nare being fetched and the clips will follow (the engine holds each\nsentence for them rather than dropping it); `unavailable` means\nthis voice cannot be had here, and `detail` says why in the\nengine's own words.","properties":{"detail":{"description":"What is being fetched, or why it cannot be had.","type":"string"},"lang":{"type":"string"},"state":{"$ref":"#/components/schemas/VoiceStatusState"},"type":{"const":"transcript.voiceStatus","type":"string"},"voiceKey":{"description":"The audience choice this is about — the same value\n`transcript.voice.voiceKey` carries, empty for the engine's\ndefault.","type":"string"}},"required":["type","lang","state","detail"],"type":"object"},{"properties":{"processedS":{"format":"double","type":"number"},"totalS":{"format":"double","type":["number","null"]},"type":{"const":"transcript.progress","type":"string"}},"required":["type","processedS"],"type":"object"},{"description":"`input.kind = \"stream\"`: how the engine's pull of the stream is\ngoing.  Sent on every change of state and every few seconds while\nstreaming (the counters move).","properties":{"bytesIn":{"description":"Bytes read from the source and seconds of audio decoded, this run.","format":"uint64","minimum":0,"type":"integer"},"detected":{"description":"What the source turned out to be (\"HLS · AAC-LC 22.05 kHz …\").","type":["string","null"]},"error":{"description":"Why the last connection failed or dropped.","type":["string","null"]},"failures":{"description":"Consecutive failed connections so far.","format":"int32","type":"integer"},"mediaS":{"format":"double","type":"number"},"programAtMs":{"description":"Wall clock (ms) of media second 0, when the playlist says\n(`EXT-X-PROGRAM-DATE-TIME`).","format":"int64","type":["integer","null"]},"sequenceOffsetMs":{"description":"How far into the broadcast media second 0 is (ms), when the\nplaylist's numbering says (a YouTube live).","format":"int64","type":["integer","null"]},"state":{"$ref":"#/components/schemas/PullState"},"type":{"const":"stream.pull","type":"string"}},"required":["type","state","failures","bytesIn","mediaS"],"type":"object"},{"description":"`input.prepare`: the source on its way in.  `phase` \"fetching\"\ncounts bytes, \"extracting\" media seconds decoded.","properties":{"bytes":{"format":"uint64","minimum":0,"type":"integer"},"doneS":{"format":"double","type":"number"},"phase":{"type":"string"},"total":{"format":"uint64","minimum":0,"type":["integer","null"]},"totalS":{"format":"double","type":["number","null"]},"type":{"const":"source.progress","type":"string"}},"required":["type","phase","bytes","doneS"],"type":"object"},{"description":"`input.prepare`: the source is stored and ready to transcribe.","properties":{"durationS":{"description":"The media's length, when it could be read.","format":"double","type":["number","null"]},"peaks":{"description":"The waveform for the trim step (0..1), when the recording waits for one.","items":{"format":"float","type":"number"},"type":["array","null"]},"type":{"const":"source.prepared","type":"string"}},"required":["type"],"type":"object"},{"properties":{"reason":{"$ref":"#/components/schemas/DoneReason"},"stats":{"$ref":"#/components/schemas/SessionStats"},"type":{"const":"session.done","type":"string"}},"required":["type","reason","stats"],"type":"object"},{"properties":{"code":{"$ref":"#/components/schemas/ErrorCode"},"message":{"type":"string"},"retryable":{"type":"boolean"},"type":{"const":"error","type":"string"}},"required":["type","code","message","retryable"],"type":"object"}]},"SessionOptions":{"additionalProperties":false,"properties":{"audio":{"$ref":"#/components/schemas/AudioFormat","default":{"channels":1,"encoding":"pcm_f32le","sampleRate":16000}},"events":{"$ref":"#/components/schemas/EventOptions","default":{"drafts":true,"words":true}},"identifyVoices":{"description":"Voices the realtime session may recognise its speakers as.\n\nDiarization says *that* two people are different, never who they\nare.  Give it the saved voices belonging to whoever is in the\nroom and it can say: each one carries a speaker embedding, the\nrealtime session takes the same embedding off every speaker it hears, and a\nmatch becomes a `transcript.speakerIdentified` frame.\n\nEmpty (the default) means nobody is recognised and no such frame\nis ever sent.  It is deliberately an explicit list and not \"all\nthe caller's voices\": one server holds many realtime sessions at once, and\nthe set of people who might be in THIS room is the caller's to\nstate.\n\nNeeds `speakerLabels` on — with one speaker there is nobody to tell\napart — and costs nothing on a realtime session that leaves it empty.","items":{"$ref":"#/components/schemas/KnownVoice"},"type":"array"},"input":{"$ref":"#/components/schemas/InputOptions","default":{"kind":"live"}},"latencyMode":{"$ref":"#/components/schemas/LatencyMode","default":"auto","description":"Which model transcribes: `auto` (default) runs the accurate model\nand switches to the fast one only while the realtime session lags behind\nreal time; `fast` always uses the fast model (lower latency, lower\naccuracy on noisy or non-English audio); `accuracy` never switches."},"llmFix":{"default":true,"description":"AI-assisted auto-fix (default **on**): a language model reads each\nsettled line for likely mishearings and corrects them, as\n`transcript.amended` frames whose words carry `llm` and\n`recognized`.  Off means no new fixes from then on; fixes already\nmade stay.  Only runs where the server has a model configured.","type":"boolean"},"sentences":{"$ref":"#/components/schemas/SentenceOptions","default":{"enabled":true,"idleCloseS":3.0,"provisionalTranslations":true},"description":"Sentence assembly over committed segments. When enabled the realtime session\nalso emits `transcript.sentence` upserts, and `translate` applies to\nwhole sentences instead of individual committed segments."},"speakerLabels":{"$ref":"#/components/schemas/SpeakerLabels","default":"off","description":"Speaker labels: `off` (default), `on`, or `auto` (diarized like\n`on`; a client that shows labels waits for a second speaker)."},"spokenLanguage":{"description":"Whisper language code of the speech; absent = auto-detect.","type":["string","null"]},"translate":{"description":"Target languages for async translation of committed segments.\nEach commit is followed (later) by a `transcript.translated` frame\nreferencing its `seq`. Empty = translations disabled.","items":{"type":"string"},"type":"array"},"voice":{"description":"What to speak: each definitive translated sentence in one of these\nlanguages (or the original sentence when its own language is\nlisted) is synthesised and delivered as a `transcript.voice`\nframe.  Requires `sentences`.  Empty = no speech.\n\nAn entry is `\"<lang>\"` — speak it in whatever voice the engine\npicks (`voiceByLang`, else its default) — or `\"<lang>:<voice>\"`,\nnaming the voice outright.  The same language may appear several\ntimes with different voices, and each is synthesised once, however\nmany listeners asked for it: this list is the union of what the\naudience chose, and it is what keeps a clip from being rendered\nper listener.  `\"es\"` and `\"es:F3\"` are different entries and cost\ntwo syntheses, so a caller that means the default says only `\"es\"`.","items":{"type":"string"},"type":"array"},"voiceByLang":{"additionalProperties":{"type":"string"},"description":"Which voice speaks each language in `voice` (`{\"es\": \"F3\"}`), by\nthe engine's own voice name.  A language absent from the map gets\nthe engine's default for it — and the clip says which that was.","type":"object"},"voiceCast":{"default":true,"description":"Cast a distinct, gender-matched voice to each diarized speaker\n(default **on**).\n\nThe listener still picks one voice per language; with this on it\nbecomes the voice of the first speaker of its own gender, and\neverybody else in the room is cast around it from the same\nengine's catalogue — so two men are read in two male voices and a\nwoman in a female one, without anyone configuring anything.\n\nOff means the listener's pick reads every speaker, which is what\nhappened before this existed.  It is also what a caller wants when\nthe choice is editorial rather than practical: a single narrator\nfor the whole room.\n\nNeeds `speakerLabels` on to do anything — with one speaker there is\nnothing to cast — and never delays a clip: a speaker whose gender\nhas not settled yet is read in the listener's pick and moves to\ntheir own voice once it does (see `casting`).","type":"boolean"}},"type":"object"},"SessionStats":{"properties":{"audioS":{"format":"double","type":"number"},"processingS":{"format":"double","type":"number"},"realtimeFactor":{"format":"double","type":"number"}},"required":["audioS","processingS","realtimeFactor"],"type":"object"},"SetInterval":{"description":"`POST /v1/billing/subscription/interval`.","properties":{"interval":{"description":"`month` or `year`: from the next period.","type":"string"}},"required":["interval"],"type":"object"},"Settings":{"additionalProperties":false,"description":"A broadcast's, transcription's or recording's capture settings (the\ndesign's settings sheet): what a recording made in it starts with.","properties":{"confidence":{"$ref":"#/components/schemas/ConfidencePolicy"},"latencyMode":{"$ref":"#/components/schemas/LatencyMode","default":"auto","description":"`auto` / `fast` / `accuracy` — the latency-mode switch."},"llmFix":{"default":true,"description":"AI-assisted auto-fix (the Fix tab's switch; on by default): a\nlanguage model corrects likely mishearings as lines settle, every\nfix marked on its words (`Word.llm`) and undoable.  Switched off\nmid-recording it stops new fixes from the next line on.","type":"boolean"},"maxDurationS":{"default":7200,"format":"uint32","minimum":0,"title":"32: user-chosen, bounded 1 min .. 8 h.","type":"integer"},"maxPauseS":{"default":300,"description":"A paused recording auto-ends after this long.","format":"uint32","minimum":0,"type":"integer"},"silenceAutoPauseS":{"description":"Auto-pause after this much continuous silence (seconds,\n≥ 120); absent = the server default (`SILENCE_AUTO_PAUSE_S`, 0 = off).","format":"uint32","minimum":0,"type":["integer","null"]},"speakerLabels":{"$ref":"#/components/schemas/SpeakerLabels","default":"off","description":"Separate the transcript by who is talking (#39: `off` by default):\n`auto` and `on` diarize, `auto` labels lines once a second speaker\nhas been heard."},"spokenLanguage":{"description":"Whisper language code; absent = auto-detect.","type":["string","null"]},"voice":{"default":true,"description":"(`transcript.voice` clips).  On by default: the design offers the\nhost no switch for it, and marks a language as spoken purely by\nwhether the engine has a voice for it, so a recording that could\nspeak does.  Synthesis is still only paid for languages a\nlistener actually chose; set it false to keep a recording silent.","title":"48: speak each audience language's translation as it lands","type":"boolean"},"voiceByLang":{"additionalProperties":{"type":"string"},"description":"Which voice speaks each audience language (`{\"es\": \"F3\"}`), by the\nengine's own voice name or `clone:<id>`.  A language that is not\nlisted gets the engine's default for it.\n\nThis is the host's setting, not a listener's: a clip is\nsynthesised once per language and fanned out to everyone\nlistening in it (`relay.rs` filters by language alone), so a\nper-listener choice could not be honoured without synthesising\nper listener.  The listener's picker therefore offers what is\npinned here — usually the one voice actually being rendered.","type":"object"}},"type":"object"},"SettingsPatch":{"additionalProperties":false,"description":"Partial update (PATCH) — every field optional, `null` for\n`spokenLanguage` means back to auto-detect.","properties":{"confidence":{"anyOf":[{"$ref":"#/components/schemas/ConfidencePolicy"},{"type":"null"}],"default":null},"latencyMode":{"anyOf":[{"$ref":"#/components/schemas/LatencyMode"},{"type":"null"}],"default":null},"llmFix":{"default":null,"type":["boolean","null"]},"maxDurationS":{"default":null,"format":"uint32","minimum":0,"type":["integer","null"]},"maxPauseS":{"default":null,"format":"uint32","minimum":0,"type":["integer","null"]},"silenceAutoPauseS":{"default":null,"description":"`0` turns the watchdog off.","format":"uint32","minimum":0,"type":["integer","null"]},"speakerLabels":{"anyOf":[{"$ref":"#/components/schemas/SpeakerLabels"},{"type":"null"}],"default":null},"spokenLanguage":{"default":null,"type":["string","null"]},"voice":{"default":null,"type":["boolean","null"]},"voiceByLang":{"additionalProperties":{"type":"string"},"default":null,"description":"Replaces the whole map; `{}` clears every pin.","type":["object","null"]}},"type":"object"},"ShareLink":{"description":"The broadcast or transcription something belongs to (a script, a share\nlink): exactly one of `broadcastId` and `transcriptionId` is set.\nFlattened into its owner.","properties":{"allowDownload":{"type":"boolean"},"allowExport":{"type":"boolean"},"allowTranslate":{"type":"boolean"},"broadcastId":{"description":"Set when it belongs to a broadcast.","type":["string","null"]},"code":{"type":"string"},"createdAt":{"type":"string"},"id":{"type":"string"},"label":{"type":"string"},"lastViewedAt":{"type":["string","null"]},"revokedAt":{"type":["string","null"]},"transcriptionId":{"description":"Set when it belongs to a transcription.","type":["string","null"]},"url":{"description":"The public page: `{public_base_url}/t/{code}`.","type":"string"},"views":{"description":"Public page opens, counted server-side.","format":"int64","type":"integer"}},"required":["id","code","url","label","allowDownload","allowExport","allowTranslate","views","createdAt"],"type":"object"},"SourceInfo":{"description":"What the recording was made from (design `j.meta`: \"M4A · 34.2 MB\",\n\"Video URL\").","properties":{"bytes":{"format":"int64","type":["integer","null"]},"durationS":{"description":"Probed media duration, known before the transcript is.","format":"double","type":["number","null"]},"format":{"description":"Upper-case container label (\"M4A\", \"MP4\"), from the name or the mime type.","type":["string","null"]},"kind":{"$ref":"#/components/schemas/SourceKind"},"mime":{"type":["string","null"]},"name":{"description":"The file's name, or the URL without its scheme.","type":"string"},"url":{"type":["string","null"]}},"required":["kind","name"],"type":"object"},"SourceKind":{"enum":["file","url"],"type":"string"},"SourceProblem":{"description":"What went wrong, as the app explains it.","oneOf":[{"const":"not_found","description":"The server says there is nothing at that address (HTTP 404 / 410).","type":"string"},{"const":"refused","description":"The server refused it (401, 403 and other 4xx).","type":"string"},{"const":"server_error","description":"The server is failing (5xx, 429): usually passes.","type":"string"},{"const":"unknown_host","description":"No such host: the name does not resolve.","type":"string"},{"const":"unreachable","description":"The host would not take the connection (refused, TLS, network).","type":"string"},{"const":"private_address","description":"A private or internal address, which the server does not fetch.","type":"string"},{"const":"timeout","description":"It answered, but no audio came within the time allowed.","type":"string"},{"const":"web_page","description":"A web page, not a stream.","type":"string"},{"const":"bad_playlist","description":"A playlist whose entries gave no audio, or that lists none.","type":"string"},{"const":"not_audio","description":"Data came back, but not audio that can be decoded.","type":"string"},{"const":"unsupported","description":"Audio in a format or with a protection the server does not read.","type":"string"},{"const":"youtube_upcoming","description":"A YouTube live that has not started yet.","type":"string"},{"const":"youtube_not_live","description":"A YouTube video that is not live (a finished broadcast, an upload).","type":"string"},{"const":"youtube_unavailable","description":"YouTube will not play it (private, removed, region or age locked).","type":"string"},{"const":"youtube_busy","description":"YouTube is turning this server away for now.","type":"string"}]},"SpeakerLabels":{"description":"The three positions of the speaker-labels switch (design 77), on a\nrealtime session's options and on a broadcast's, transcription's or\nrecording's settings alike.\n\n`auto` (\"When Needed\") diarizes like `on`, and its lines are labelled\nonly once a second speaker has been heard, so one person talking reads\nas plain text.  `on` labels every line; `off` (the default, #39)\nneither separates nor labels.","enum":["auto","on","off"],"type":"string"},"SpeakerView":{"properties":{"gender":{"description":"`\"male\"` / `\"female\"` once the pitch band settles on this speaker,\nabsent while it has not or when it refused to judge (§casting).\nAbsent is \"not known\" and never \"neither\": nothing may infer a\ngender from its absence.","type":["string","null"]},"genderConfidence":{"format":"double","type":["number","null"]},"identified":{"description":"This speaker is somebody the account holds a voice for.\n\nSaid to EVERYONE, unlike `voiceId` below. The mark beside a\nspeaker's name is about the person — they are not a numbered\ncluster, they are somebody who can be read in their own voice —\nand a listener is entitled to know that much. Which voice, and\nhow sure, is the detail they are denied.\n\nWithout this the mark depended on `voiceId` or a colour, and a\nvoice whose owner never chose a colour gave a listener neither:\nthe speaker was identified and looked exactly like one who was\nnot.","type":"boolean"},"identifiedConfidence":{"description":"The match's cosine, when a model made it. Absent when a person\ndid: nobody guessed.","format":"double","type":["number","null"]},"name":{"type":"string"},"speaker":{"format":"int32","type":"integer"},"voiceColor":{"description":"That voice's colour, so the person reads the same here as\neverywhere else.","type":["string","null"]},"voiceId":{"description":"The saved voice this speaker turned out to be — matched by the\nengine, or asserted by a host who enrolled them mid-recording\n(design 34).\n\nHere as well as on the live frame because the live frame is heard\nonce: a reader who joins later, or reloads, or opens the\nrecording next week, learns it only from the snapshot. Without it\nthe mark beside a speaker's name survived exactly as long as the\nsocket did.","type":["string","null"]}},"required":["speaker","name"],"type":"object"},"StartJob":{"additionalProperties":false,"description":"`POST /v1/recordings/{id}/job/start`: the window a job in\n`cropping` should transcribe.  Both bounds optional: the start of the\nfile and its end.","properties":{"endS":{"default":null,"format":"double","type":["number","null"]},"startS":{"default":null,"format":"double","type":["number","null"]}},"type":"object"},"StartRecording":{"additionalProperties":false,"properties":{"audioSource":{"default":null,"description":"On a broadcast: record this one of its audio sources\n(`Broadcast.audioSources[].id`) instead of the microphone.","type":["string","null"]},"deviceId":{"default":null,"description":"Correlation only.","type":["string","null"]},"scriptId":{"default":null,"description":"Start the recording a script (made in the VocalStack app) was\nwritten for: its text is copied onto the recording and the engine\nfollows it (`Recording.scriptText`).","type":["string","null"]},"scriptText":{"default":null,"description":"The words this recording follows (HTML subset: `<b>`, `<i>`, `<u>`,\n`<br>`; anything else is reduced to that): the engine matches the\ntranscript against them like a teleprompter.  With `scriptId`, the\nscript's own text wins.","type":["string","null"]},"settings":{"anyOf":[{"$ref":"#/components/schemas/SettingsPatch"},{"type":"null"}],"description":"Overrides for this recording only; its broadcast's or transcription's settings otherwise."},"takeover":{"default":false,"description":"Take the mic from a publisher that is attached elsewhere.","type":"boolean"}},"type":"object"},"StopRequest":{"additionalProperties":false,"properties":{"reason":{"default":null,"type":["string","null"]}},"type":"object"},"StreamIngest":{"description":"The server-side publisher of a stream source's recording, on the\n`Recording` DTO as `ingest`.","properties":{"attempts":{"description":"Worker runs so far (1 = never re-claimed).","format":"int32","type":"integer"},"bytesIn":{"format":"int64","type":"integer"},"detected":{"description":"What the stream turned out to be, e.g. `progressive mp3 (audio/mpeg)`.","type":["string","null"]},"error":{"description":"The last thing that went wrong (kept while reconnecting; the reason\nonce failed).","type":["string","null"]},"failures":{"description":"Consecutive failed connects in the current run.","format":"int32","type":"integer"},"finishedAt":{"type":["string","null"]},"lastAudioAt":{"description":"When the source last delivered audio, once it has stopped (design\n91): a stream that is still connected but silent — an RTMP encoder\nthat stopped, a YouTube live that ended — is `reconnecting` from\n[`STALL_AFTER`] on, with this.","type":["string","null"]},"mediaAt":{"description":"Where the recording's media clock starts in wall-clock time: the\nHLS program date-time of the first segment pulled, when the stream\ncarried one (YouTube does).  Media second `t` of the recording was\nbroadcast at `media_at + t`.","type":["string","null"]},"mediaS":{"description":"Seconds of audio published.","format":"double","type":"number"},"startedAt":{"description":"When the first audio reached the engine.","type":["string","null"]},"state":{"$ref":"#/components/schemas/IngestState"},"streamStartedAt":{"description":"When the stream behind a YouTube link began, read off the\nstream's segment numbering: second 0 of the archive YouTube keeps\nonce the stream ends.  Media second `t` of the recording is second\n`media_at - stream_started_at + t` of that video, which is how a\nreplay shows the picture at the moment being played.","type":["string","null"]},"updatedAt":{"type":"string"},"url":{"type":"string"}},"required":["state","url","attempts","failures","bytesIn","mediaS","updatedAt"],"type":"object"},"SubscriberClientFrame":{"description":"Client → subscriber-socket frames.","oneOf":[{"description":"Switch target language (a property of this stream).","properties":{"audio":{"description":"Opt in/out of live original audio.","type":["boolean","null"]},"lang":{"type":["string","null"]},"type":{"const":"subscribe.update","type":"string"}},"required":["type"],"type":"object"},{"properties":{"type":{"const":"ping","type":"string"}},"required":["type"],"type":"object"}]},"SubscriberFrame":{"description":"Frames a subscriber can receive in addition to the engine's\n`ServerFrame`s.  Documented in the contract's `x-websocket.subscribe`.","oneOf":[{"description":"First frame after the upgrade: where the live sequence stands, so\nthe client fetches the snapshot up to `lastSeq` and discards\nbuffered frames at or below it.","properties":{"instance":{"type":"string"},"lastSeq":{"format":"int64","type":"integer"},"recordingId":{"type":"string"},"state":{"$ref":"#/components/schemas/RecordingState"},"type":{"const":"subscribe.ready","type":"string"}},"required":["type","recordingId","lastSeq","state","instance"],"type":"object"},{"description":"Recording state changed (LIVE ⇄ PAUSED → ENDED → FINALIZED).","properties":{"mediaS":{"description":"Media seconds captured so far.","format":"double","type":["number","null"]},"reason":{"type":["string","null"]},"recordingId":{"type":"string"},"state":{"$ref":"#/components/schemas/RecordingState"},"type":{"const":"recording.state","type":"string"}},"required":["type","recordingId","state"],"type":"object"},{"description":"Capture settings changed mid-recording (mode, language, labels).","properties":{"recordingId":{"type":"string"},"settings":true,"type":{"const":"recording.settings","type":"string"}},"required":["type","recordingId","settings"],"type":"object"},{"description":"The silence watchdog will pause the recording soon.","properties":{"pausesInS":{"format":"double","type":"number"},"recordingId":{"type":"string"},"silentS":{"format":"double","type":"number"},"type":{"const":"recording.silence","type":"string"}},"required":["type","recordingId","silentS","pausesInS"],"type":"object"},{"description":"The max-duration cutoff is near.","properties":{"endsInS":{"format":"double","type":"number"},"recordingId":{"type":"string"},"type":{"const":"recording.expiring","type":"string"}},"required":["type","recordingId","endsInS"],"type":"object"},{"description":"A human correction was applied — distinct from an engine amend.","properties":{"actor":{"anyOf":[{"$ref":"#/components/schemas/Actor"},{"type":"null"}],"description":"Admin subscribers only."},"from":{"type":"string"},"reverted":{"type":"boolean"},"sentenceId":{"format":"int64","type":["integer","null"]},"sentenceText":{"type":["string","null"]},"seq":{"format":"int64","type":"integer"},"text":{"description":"The segment's full text after the correction.","type":"string"},"to":{"type":"string"},"type":{"const":"transcript.corrected","type":"string"},"wordIdx":{"description":"`-1` for a line rewrite, which carries `words` instead.","format":"int32","type":"integer"},"words":{"description":"The segment's full word list whenever the count changed: a line\nrewrite, or a word replaced by several words (and the undo of\neither). Absent when one word became one word.","items":{"$ref":"#/components/schemas/Word"},"type":["array","null"]}},"required":["type","seq","wordIdx","from","to","text","reverted"],"type":"object"},{"description":"A speaker's gender, once the pitch band settles on it\n(`casting::roster`).  Engine → control; the voice sheet marks a\nspeaker's chip with it and opens the group of voices that suits\nthem.  Never used to pick a voice — `voice_cast` does that, on the\nengine, where the verdict is made.","properties":{"confidence":{"format":"double","type":["number","null"]},"gender":{"type":"string"},"speaker":{"format":"int32","type":"integer"},"type":{"const":"transcript.speakerGender","type":"string"}},"required":["type","speaker","gender"],"type":"object"},{"description":"A speaker and the saved voice they turn out to be.\n\nTwo things produce this and they are the same claim: the engine\nmatching somebody against a saved voice, and the host enrolling\nthem from inside the recording (design 34) — the second being the\nstronger of the two, since a person said it rather than a model.\nEither way the reader learns the same thing: this speaker is that\nperson, their voice exists, and it is this colour.\n\nAn empty `name` withdraws the claim.","properties":{"color":{"type":["string","null"]},"confidence":{"format":"double","type":["number","null"]},"name":{"type":"string"},"speaker":{"format":"int32","type":"integer"},"type":{"const":"transcript.speakerIdentified","type":"string"},"voiceId":{"type":["string","null"]}},"required":["type","speaker","name"],"type":"object"},{"description":"A per-recording speaker label.","properties":{"name":{"type":["string","null"]},"speaker":{"format":"int32","type":"integer"},"type":{"const":"transcript.speakerNamed","type":"string"}},"required":["type","speaker"],"type":"object"},{"description":"Broadcast or transcription topic: a recording started in it (it is\nlive).  `transcriptionId` is the transcription the recording is made\nin: on a broadcast's topic, the live transcription feeding it.","properties":{"recordingId":{"type":"string"},"transcriptionId":{"type":"string"},"type":{"const":"recording.started","type":"string"}},"required":["type","transcriptionId","recordingId"],"type":"object"},{"description":"A file / URL transcription job moved (both topics: the recording's\nand its broadcast's or transcription's).  `state` is the job state; the counts feed the\ndesign's `x / N SEGMENTS`.","properties":{"error":{"type":["string","null"]},"fetchTotalBytes":{"format":"uint64","minimum":0,"type":["integer","null"]},"fetchedBytes":{"description":"While `state` is `fetching`: bytes of the source in so far and\nthe total once known, and the phase — `fetching` or\n`extracting` (the audio being taken out of a video).","format":"uint64","minimum":0,"type":["integer","null"]},"pct":{"format":"uint8","maximum":255,"minimum":0,"type":"integer"},"phase":{"type":["string","null"]},"phasePct":{"description":"How far that phase is, 0..100, when it can be told.","format":"uint8","maximum":255,"minimum":0,"type":["integer","null"]},"preview":{"description":"The server holds a fetched link's copy for the trim step to read\n(`TranscriptionJob.preview`).","type":"boolean"},"processedS":{"format":"double","type":"number"},"recordingId":{"type":"string"},"segmentsDone":{"format":"int32","type":"integer"},"segmentsTotal":{"format":"int32","type":["integer","null"]},"state":{"$ref":"#/components/schemas/JobState"},"totalS":{"format":"double","type":["number","null"]},"transcriptionId":{"type":"string"},"type":{"const":"job.progress","type":"string"}},"required":["type","transcriptionId","recordingId","state","processedS","segmentsDone","pct"],"type":"object"},{"description":"A stream source's server-side publisher moved (both topics): what\nthe server is doing with the URL — connecting, streaming,\nreconnecting after a drop, stopped, or failed with `error`.","properties":{"detected":{"type":["string","null"]},"error":{"type":["string","null"]},"failures":{"default":0,"description":"Consecutive failed connects (the retry cadence follows from it).","format":"int32","type":"integer"},"lastAudioAt":{"description":"When the source last delivered audio (design 91: \"last audio\n00:14 ago\"), set once it stops.","type":["string","null"]},"recordingId":{"type":"string"},"state":{"$ref":"#/components/schemas/IngestState"},"transcriptionId":{"type":"string"},"type":{"const":"stream.ingest","type":"string"},"url":{"type":"string"}},"required":["type","transcriptionId","recordingId","state","url"],"type":"object"},{"description":"Broadcast or transcription topic: the recording that was live in it\nended (on a broadcast's topic, also: the live transcription making\nit was detached).","properties":{"recordingId":{"type":"string"},"transcriptionId":{"type":"string"},"type":{"const":"recording.ended","type":"string"}},"required":["type","transcriptionId","recordingId"],"type":"object"},{"description":"Admin subscribers: the audience aggregate, every few seconds.","properties":{"languages":true,"listeners":{"format":"int64","type":"integer"},"type":{"const":"audience","type":"string"}},"required":["type","listeners","languages"],"type":"object"},{"description":"The frame sequence jumped; the client should re-snapshot from its cursor.","properties":{"expected":{"format":"int64","type":"integer"},"got":{"format":"int64","type":"integer"},"type":{"const":"subscribe.gap","type":"string"}},"required":["type","expected","got"],"type":"object"}]},"SubscriptionView":{"properties":{"cancelAtPeriodEnd":{"type":"boolean"},"currentPeriodEnd":{"type":["string","null"]},"currentPeriodStart":{"type":["string","null"]},"interval":{"description":"`month` or `year`.","type":"string"},"nextPayment":{"anyOf":[{"$ref":"#/components/schemas/NextPayment"},{"type":"null"}],"description":"The renewal: when and how much (before tax); null when it ends at\nthe period end."},"pastDueSince":{"description":"When the renewal first failed (the provider retries on its own).","type":["string","null"]},"pendingInterval":{"anyOf":[{"$ref":"#/components/schemas/PendingInterval"},{"type":"null"}],"description":"A switch to the other interval scheduled for the period end."},"status":{"description":"`active`, `past_due`, `canceled` (ends at the period end) or `ended`.","type":"string"}},"required":["interval","status","cancelAtPeriodEnd"],"type":"object"},"SummaryStatus":{"description":"Where a recording's summary stands, for `POST /v1/recordings/:id/summarize`.","properties":{"lang":{"description":"The language the title and summary read in when they were asked\nfor in one (`GET .../summary?lang=`) and could be translated into\nit; absent, they read as spoken.","type":["string","null"]},"state":{"description":"`ready`: the recording carries a title and summary.  `pending`: the\npass is queued or running; read the recording again shortly.\n`unavailable`: this server has no summariser configured.","type":"string"},"summary":{"type":["string","null"]},"title":{"type":["string","null"]}},"required":["state"],"type":"object"},"TranscriptSnapshot":{"properties":{"lang":{"description":"The language translations were filtered to, when one was asked for.","type":["string","null"]},"lastSeq":{"description":"Every frame at or below this is reflected in the snapshot.","format":"int64","type":"integer"},"recordingId":{"type":"string"},"segments":{"items":{"$ref":"#/components/schemas/SegmentView"},"type":"array"},"sentences":{"items":{"$ref":"#/components/schemas/SentenceView"},"type":"array"},"spans":{"items":{"$ref":"#/components/schemas/CaptureSpan"},"type":"array"},"speakerLabels":{"anyOf":[{"$ref":"#/components/schemas/SpeakerLabels"},{"type":"null"}],"description":"How this recording labels its speakers (design 77): `on` names\nevery line's speaker, `off` none, `auto` only once a second speaker\nhas been heard.  A reader with no settings to look at — a listener,\na share link — follows this."},"speakers":{"items":{"$ref":"#/components/schemas/SpeakerView"},"type":"array"},"state":{"$ref":"#/components/schemas/RecordingState"}},"required":["recordingId","state","lastSeq","segments","sentences","speakers","spans"],"type":"object"},"Transcription":{"description":"A transcription: a file, a link (with its `transcription` job), or\nsomething captured as it is spoken (`live: true`: a microphone, a\nstream, an RTMP encoder), which may feed broadcasts (`broadcasts`).","properties":{"archivedAt":{"type":["string","null"]},"broadcasts":{"description":"The broadcasts a live transcription feeds.","items":{"$ref":"#/components/schemas/BroadcastRef"},"type":"array"},"code":{"type":"string"},"createdAt":{"type":"string"},"createdByApiKeyId":{"description":"The API key that created it, when it was created with one (through\nthe API, not in the web app); absent otherwise, and once that key\nhas been deleted.","type":["string","null"]},"currentRecording":{"anyOf":[{"$ref":"#/components/schemas/CurrentRecording"},{"type":"null"}],"description":"The recording in flight, if one is."},"id":{"type":"string"},"job":{"anyOf":[{"$ref":"#/components/schemas/TranscriptionJob"},{"type":"null"}],"description":"A file or link transcription's job: state, progress, source."},"kind":{"const":"transcription","description":"Always `transcription`: which resource this is, for a client holding both.","type":"string"},"lastRecordedAt":{"type":["string","null"]},"lastRecording":{"anyOf":[{"$ref":"#/components/schemas/LastRecording"},{"type":"null"}],"description":"A live transcription's newest recording, once it has one."},"lifecycle":{"$ref":"#/components/schemas/Lifecycle"},"live":{"default":false,"description":"Captured live (a microphone or a stream) rather than read from a\nfile or a link: it has recordings to start and stop, no job, and\ncan feed broadcasts.","type":"boolean"},"name":{"type":"string"},"ownerPrincipalId":{"type":"string"},"purgeAfter":{"type":["string","null"]},"recordingCount":{"format":"int64","type":"integer"},"settings":{"$ref":"#/components/schemas/Settings"},"shareUrl":{"type":"string"},"source":{"$ref":"#/components/schemas/CaptureSource","description":"A live transcription's microphone, or the stream URL the server\npulls; a file or link transcription says `mic` (its source is the\njob's)."},"updatedAt":{"type":"string"}},"required":["id","code","name","kind","source","lifecycle","ownerPrincipalId","shareUrl","settings","recordingCount","createdAt","updatedAt"],"type":"object"},"TranscriptionJob":{"properties":{"attempts":{"format":"int32","type":"integer"},"createdAt":{"type":"string"},"crop":{"anyOf":[{"$ref":"#/components/schemas/CropWindow"},{"type":"null"}],"description":"The window transcribed, chosen in the crop step; absent = the whole file."},"error":{"type":["string","null"]},"errorCode":{"description":"Why a `failed` job failed, when it is machine-readable:\n`quota_exceeded` — the source, once its length was known, did not\nfit the minutes left on the owner's tier (it is not retried; retry\nit once minutes are free or the tier is raised).  Absent otherwise.","type":["string","null"]},"finishedAt":{"type":["string","null"]},"hasAudio":{"description":"Whether `GET /v1/recordings/{recordingId}/audio` has audio to play:\nthe delivery artifact — the engine's encode of exactly what it\ntranscribed — is sealed.  True once the job is `done` (the job is not\nreported done before its audio is sealed); false while it runs, after\na failure, and for a job whose engine could not persist audio.","type":"boolean"},"peaks":{"description":"The source's loudness over time — `peaks::PEAK_BARS` values in 0..1,\none per equal slice of the file — for the crop step's waveform.\nAbsent until the source has been decoded for it, or when it could\nnot be (the crop step then draws a flat strip).","items":{"format":"float","type":"number"},"type":["array","null"]},"preview":{"description":"A job from a link, waiting in `cropping`: the server holds the file\nit fetched for the trim step to read once\n(`GET /v1/recordings/{recordingId}/job/preview`) and draw\nthe waveform from.  It is kept until the waveform is posted\n(`POST …/transcription/peaks`) or for ten minutes, never stored.","type":"boolean"},"progress":{"$ref":"#/components/schemas/JobProgress"},"recordingId":{"type":"string"},"source":{"anyOf":[{"$ref":"#/components/schemas/SourceInfo"},{"type":"null"}]},"startedAt":{"type":["string","null"]},"state":{"$ref":"#/components/schemas/JobState"},"transcriptionId":{"description":"The transcription the recording is in.","type":"string"}},"required":["recordingId","transcriptionId","state","progress","attempts","createdAt","hasAudio"],"type":"object"},"TranscriptionRef":{"description":"A live transcription feeding a broadcast (`Broadcast.transcriptions`).","properties":{"id":{"type":"string"},"lastRecordedAt":{"type":["string","null"]},"made":{"default":false,"description":"Made by starting a recording on the broadcast (its microphone, or\none of its audio sources), rather than attached by hand.","type":"boolean"},"name":{"type":"string"},"recording":{"description":"Whether it is recording now.","type":"boolean"},"source":{"$ref":"#/components/schemas/CaptureSource","description":"The microphone, or the stream it reads."}},"required":["id","name","source","recording"],"type":"object"},"UpdateBroadcast":{"additionalProperties":false,"description":"`PATCH /v1/broadcasts/{id}`: fields left out are unchanged.","properties":{"access":{"anyOf":[{"$ref":"#/components/schemas/Access"},{"type":"null"}],"default":null},"code":{"default":null,"description":"A new link (design 82). The links it had keep opening it, and stay\nits own until the host gives one of them to another of their\nbroadcasts.","type":["string","null"]},"name":{"default":null,"type":["string","null"]},"password":{"default":null,"description":"Setting a password bumps the epoch: every listener token issued\nbefore it stops working.","type":["string","null"]},"settings":{"anyOf":[{"$ref":"#/components/schemas/SettingsPatch"},{"type":"null"}]}},"type":"object"},"UpdateShareLink":{"additionalProperties":false,"properties":{"allowDownload":{"default":null,"type":["boolean","null"]},"allowExport":{"default":null,"type":["boolean","null"]},"allowTranslate":{"default":null,"type":["boolean","null"]},"label":{"default":null,"type":["string","null"]}},"type":"object"},"UpdateTranscription":{"additionalProperties":false,"description":"`PATCH /v1/transcriptions/{id}`: fields left out are unchanged. A\ntranscription is shared through its share links, so it has no access,\npassword or link to change here.","properties":{"name":{"default":null,"type":["string","null"]},"settings":{"anyOf":[{"$ref":"#/components/schemas/SettingsPatch"},{"type":"null"}]}},"type":"object"},"UploadRecording":{"additionalProperties":false,"properties":{"settings":{"anyOf":[{"$ref":"#/components/schemas/SettingsPatch"},{"type":"null"}]},"url":{"default":null,"description":"`file` (send bytes over the stream) or a URL to fetch.","type":["string","null"]}},"type":"object"},"UsageReport":{"description":"`GET /v1/billing/usage`.","properties":{"endDate":{"description":"The last day reported, at most today.","type":"string"},"rows":{"items":{"$ref":"#/components/schemas/UsageRow"},"type":"array"},"sources":{"items":{"$ref":"#/components/schemas/UsageSource"},"type":"array"},"startDate":{"description":"The first day reported (`YYYY-MM-DD`): the asked one, or the\naccount's first day when it was earlier.","type":"string"},"total":{"description":"Dollars (USD), a decimal number with at most six decimals.","type":"number"},"tz":{"description":"The IANA time zone the days are in.","type":"string"}},"required":["startDate","endDate","tz","sources","rows","total"],"type":"object"},"UsageRow":{"description":"One day, source and kind.","properties":{"amount":{"description":"Dollars (USD), a decimal number with at most six decimals.","type":"number"},"date":{"description":"`YYYY-MM-DD` in `tz`.","type":"string"},"kind":{"description":"`file` or `live`, as the charges name it.","type":"string"},"seconds":{"description":"Audio seconds billed.","format":"double","type":"number"},"source":{"description":"A `sources[].id`.","type":"string"}},"required":["date","source","kind","seconds","amount"],"type":"object"},"UsageSource":{"description":"Where usage came from.","properties":{"id":{"description":"`dashboard`, or the API key's id.","type":"string"},"name":{"type":"string"},"prefix":{"description":"The key's prefix (`vs_key_abcdefghi`); null for the Dashboard.","type":["string","null"]},"revoked":{"type":"boolean"}},"required":["id","name","revoked"],"type":"object"},"Voice":{"properties":{"color":{"type":["string","null"]},"consentAt":{"type":["string","null"]},"createdAt":{"type":"string"},"draft":{"type":"boolean"},"enabled":{"type":"boolean"},"error":{"type":["string","null"]},"gender":{"type":["string","null"]},"id":{"type":"string"},"name":{"type":"string"},"previewUrl":{"type":["string","null"]},"progress":{"format":"int16","maximum":32767,"minimum":-32768,"type":"integer"},"sampleS":{"format":"double","type":"number"},"speechS":{"default":0.0,"format":"double","type":"number"},"state":{"type":"string"},"updatedAt":{"type":"string"},"uses":{"format":"int64","type":"integer"}},"required":["id","name","state","progress","sampleS","uses","enabled","draft","createdAt","updatedAt"],"type":"object"},"VoiceCatalogue":{"properties":{"lang":{"type":"string"},"voices":{"items":{"$ref":"#/components/schemas/VoiceOption"},"type":"array"}},"required":["lang","voices"],"type":"object"},"VoiceOption":{"properties":{"accent":{"type":["string","null"]},"cloned":{"type":"boolean"},"default":{"type":"boolean"},"gender":{"type":["string","null"]},"id":{"type":"string"},"lagS":{"format":"double","type":["number","null"]},"name":{"type":"string"},"previewUrl":{"type":["string","null"]},"quality":{"format":"uint8","maximum":255,"minimum":0,"type":"integer"},"ready":{"default":true,"type":"boolean"},"speakers":{"format":"uint32","minimum":0,"type":"integer"}},"required":["id","name","cloned","default","quality"],"type":"object"},"VoiceStatusState":{"description":"The two things a listener can be told about a voice that is not\nspeaking: it is coming, or it is not.","oneOf":[{"const":"preparing","description":"Assets are being fetched; the sentences spoken meanwhile are held\nand their clips arrive when the voice does.","type":"string"},{"const":"unavailable","description":"This machine cannot speak in this voice at all.","type":"string"}]},"VoiceTrack":{"properties":{"clipDur":{"additionalProperties":false,"patternProperties":{"^-?\\d+$":{"format":"double","type":"number"}},"type":"object"},"clipParts":{"additionalProperties":false,"description":"Lines the engine spoke in more than one piece (`tts::split`), and\nhow many.  A line absent here was spoken whole.  `clipDur` is the\nwhole line either way, and so are `wordTimes` — the pieces' own\ntimes are joined here, each shifted by the pieces before it, so a\nreader follows one line and never learns it was cut.","patternProperties":{"^-?\\d+$":{"format":"uint32","minimum":0,"type":"integer"}},"type":"object"},"clipRev":{"additionalProperties":false,"patternProperties":{"^-?\\d+$":{"type":"string"}},"type":"object"},"clips":{"items":{"format":"int64","type":"integer"},"type":"array"},"durationS":{"format":"double","type":["number","null"]},"error":{"type":["string","null"]},"lang":{"type":"string"},"state":{"type":"string"},"voice":{"type":["string","null"]},"voiceKey":{"type":"string"},"wordTimes":{"additionalProperties":false,"patternProperties":{"^-?\\d+$":{"items":{"items":{"format":"double","type":"number"},"maxItems":2,"minItems":2,"type":"array"},"type":"array"}},"type":"object"}},"required":["lang","voiceKey","state","clips","wordTimes","clipRev","clipDur","clipParts"],"type":"object"},"Word":{"properties":{"alts":{"description":"Ranked alternatives from the rescorer, populated only for\nlow-confidence words (the correction sheet's SUGGESTED chips).\n`prob` here is a softmax over the language-model scores of the\noriginal and its candidates, not a recognition probability.","items":{"$ref":"#/components/schemas/WordAlt"},"type":["array","null"]},"end":{"format":"double","type":"number"},"llm":{"description":"The word was corrected by the AI auto-fix (the language-model pass\ntook it for a mishearing).","type":["boolean","null"]},"prob":{"description":"Recognition confidence for this word, 0..1.","format":"double","type":["number","null"]},"recognized":{"description":"What the recognizer heard where a correction pass wrote this word\n(`llm` or `script`), when known: undoing the fix puts it back.","type":["string","null"]},"script":{"description":"The word was corrected from the recording's script (the\nteleprompter pass took the script's word for what it heard).","type":["boolean","null"]},"start":{"format":"double","type":"number"},"text":{"type":"string"}},"required":["start","end","text"],"type":"object"},"WordAlt":{"properties":{"prob":{"format":"double","type":"number"},"text":{"type":"string"}},"required":["text","prob"],"type":"object"},"WordEdit":{"additionalProperties":false,"properties":{"seq":{"format":"int64","type":"integer"},"to":{"type":"string"},"wordIdx":{"format":"int32","type":"integer"}},"required":["seq","wordIdx","to"],"type":"object"}},"securitySchemes":{"bearerAuth":{"scheme":"bearer","type":"http"}}},"info":{"description":"Speech to text, live translation and spoken translations, as one API.\n\n## Install\n\nTwo SDKs, one client each (`VocalStack`), grouped by resource. They are not on npm or PyPI yet: this API serves its own builds to any account. Set `VOCALSTACK_API_KEY` to one of your API keys (any scope); the commands send it to `api.vocalstack.com` only. The versions below are the ones this environment runs now; `https://api.vocalstack.com/sdk/latest` lists them with their SHA-256.\n\n**TypeScript SDK** `1.0.0-alpha.0.dev.20261008174440.gdafcde5` (`@vocalstack/sdk`)\n\n```sh\necho '//api.vocalstack.com/:_authToken=${VOCALSTACK_API_KEY}' >> .npmrc\nnpm install https://api.vocalstack.com/sdk/typescript/vocalstack-sdk-1.0.0-alpha.0.dev.20261008174440.gdafcde5.tgz\n```\n\nThe `.npmrc` line makes npm send the key to this host (and nowhere else); keep `.npmrc` out of version control or keep the `${VOCALSTACK_API_KEY}` reference as written. The package bundles `@vocalstack/contract`.\n\n**Python SDK** `1.0.0a0.dev20261008174440+gdafcde5` (`vocalstack`)\n\n```sh\npip install \"https://__token__:${VOCALSTACK_API_KEY}@api.vocalstack.com/sdk/python/vocalstack-1.0.0a0.dev20261008174440+gdafcde5-py3-none-any.whl\"\n```\n\n\n## Transcribe a file\n\nUpload a file, wait for its job, read the transcript and export it. Asynchronous; nothing to keep open.\n\nA file transcription is stored: it is listed under Transcriptions, can be shared and exported, and its transcript can be read any time later. The work is asynchronous. The upload answers at once; the server transcribes at full speed (much faster than the audio plays) while you wait on its job: upload, then read the job until it is `done`, then read the transcript.\n\nTo transcribe without storing anything, stream the file through a realtime session instead (Stream a file at full speed).\n\n### 1. Upload the file\n\n`POST /v1/transcriptions` takes the file as `multipart/form-data` (`file`, plus an optional `options` part, JSON) and answers `201` with the transcription. Its `job.recordingId` names the recording the file became: everything that follows is addressed by it.\n\n`settings` are optional: `spokenLanguage` (a code such as `en`; absent, the language is detected), `speakerLabels` (`auto`, `on`, `off`). Most audio and video files (mp3, wav, flac, ogg, m4a, mp4…) up to the size in `GET /v1/realtime/capabilities` (`limits.maxUploadBytes`) are accepted; a video keeps only its audio.\n\nSend an `Idempotency-Key` with the upload. If the API answers `503 unavailable` while it wakes, the SDKs retry a create only when it carries one, and a retry with the same key returns the first answer instead of a second transcription.\n\n### 2. Wait for the job\n\nA job moves `queued` → `transcribing` → `done`, or `failed` with `error` and, when it is machine-readable, `errorCode` (`insufficient_credit`: the file costs more than the account's credit). A link job passes through `fetching` first (Transcribe a link).\n\nThere are no webhooks. Read `GET /v1/recordings/{id}/job` until the state is `done` or `failed`: `vs.jobs.wait` does it every 2 s, and hands each read to `onProgress` (`on_progress` in Python) with `progress.pct` (0 to 100) and `progress.processedS`, the media seconds done so far; the program prints a line only when the state or the percentage changes. A page that is open anyway can follow the transcription's events socket instead (`WS /v1/transcriptions/{id}/events`, a `job.progress` frame per step).\n\n### 3. Read the transcript\n\n`GET /v1/recordings/{id}/transcript` answers with the whole transcript: `sentences` (what to show people, with `speaker`, `start` and `end` in seconds), `segments` (with word timings in `words`) and `speakers`. Speakers are numbered from 0; `-1` means speaker labels were off. Add `lang=es` to read it translated.\n\n### 4. Export it\n\n`GET /v1/recordings/{id}/export` returns a file: `format=srt`, `json`, `xlsx` or `pdf`, with `lang=` for a translated one. In the SDKs, `vs.recordings.export` returns the response; read its bytes.\n\n**TypeScript SDK**\n\n```typescript\nimport { writeFile } from 'node:fs/promises';\nimport { VocalStack } from '@vocalstack/sdk';\n\nconst vs = new VocalStack({ baseUrl: 'https://api.vocalstack.com', apiKey: process.env.VOCALSTACK_API_KEY });\n\nconst tx = await vs.transcriptions.create(\n  { file: './talk.mp3', name: 'Talk', settings: { spokenLanguage: 'en', speakerLabels: 'on' } },\n  { idempotencyKey: crypto.randomUUID() }, // makes a retried upload safe\n);\nconst recordingId = tx.job!.recordingId;\n\nlet last = '';\nconst job = await vs.jobs.wait(recordingId, {\n  // Called on every read of the job: print it when it changes.\n  onProgress: (j) => {\n    const line = `${j.state} ${j.progress.pct}%`;\n    if (line !== last) console.log((last = line));\n  },\n});\nif (job.state !== 'done') throw new Error(`transcription ${job.state}: ${job.error ?? ''}`);\n\nconst { sentences } = await vs.recordings.transcript(recordingId);\nfor (const s of sentences) console.log(s.speaker >= 0 ? `[speaker ${s.speaker}] ${s.text}` : s.text);\n\nconst srt = await vs.recordings.export(recordingId, 'srt');\nawait writeFile('talk.srt', Buffer.from(await srt.arrayBuffer()));\nconsole.log('wrote talk.srt');\n```\n\n**Python SDK**\n\n```python\nimport asyncio\nimport os\nimport uuid\n\nfrom vocalstack import TranscriptionJob, VocalStack\n\n\nasync def main() -> None:\n    async with VocalStack(\"https://api.vocalstack.com\", api_key=os.environ[\"VOCALSTACK_API_KEY\"]) as vs:\n        tx = await vs.transcriptions.create(\n            file=\"./talk.mp3\",\n            name=\"Talk\",\n            settings={\"spokenLanguage\": \"en\", \"speakerLabels\": \"on\"},\n            idempotency_key=str(uuid.uuid4()),  # makes a retried upload safe\n        )\n        assert tx.job is not None\n        recording_id = tx.job.recording_id\n\n        last = \"\"\n\n        def on_progress(j: TranscriptionJob) -> None:\n            # Called on every read of the job: print it when it changes.\n            nonlocal last\n            line = f\"{j.state} {j.progress.pct}%\"\n            if line != last:\n                last = line\n                print(line)\n\n        job = await vs.jobs.wait(recording_id, on_progress=on_progress)\n        if job.state != \"done\":\n            raise RuntimeError(f\"transcription {job.state}: {job.error}\")\n\n        snapshot = await vs.recordings.transcript(recording_id)\n        for s in snapshot.sentences:\n            print(f\"[speaker {s.speaker}] {s.text}\" if s.speaker >= 0 else s.text)\n\n        srt = await vs.recordings.export(recording_id, \"srt\")\n        with open(\"talk.srt\", \"wb\") as f:\n            f.write(srt)\n        print(\"wrote talk.srt\")\n\n\nasyncio.run(main())\n```\n\n## Transcribe a link\n\nThe server fetches a public URL or a YouTube video, then transcribes it like a file. Asynchronous, with a fetch phase first.\n\nGive 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.\n\n### 1. Check the link\n\n`GET /v1/transcriptions/probe` says what a link is before anything is stored: `kind` is `file` (transcribe it here), `live` (a YouTube live, an endless radio stream, an HLS playlist with no end) or `unknown`, and `ok` is `false` with a `problem` sentence 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).\n\nThe probe is optional, and limited to 60 calls a minute. Links to private, loopback or cloud metadata addresses are refused.\n\n### 2. Create the transcription\n\n`POST /v1/transcriptions` with JSON `{ \"url\": \"…\" }` (and optional `name` and `settings`, as for a file) answers `201` at once; the server fetches the link afterwards. `vs.transcriptions.createFromUrl(url)` is the same call. An `Idempotency-Key` makes a retried create return the first transcription instead of a second one.\n\n### 3. Wait for the fetch and the transcription\n\nA link job starts in `fetching`, then moves to `transcribing` and `done` (or `failed`, with `error`). While it fetches, `progress.phase` is `fetching` (bytes arriving: `progress.fetchedBytes` of `progress.fetchTotalBytes`, when the other end says) or `extracting` (the audio is being taken out of a video), and `progress.phasePct` is how far that phase is. A long video takes a while to fetch; poll `GET /v1/recordings/{id}/job` every few seconds (`vs.jobs.wait` does) rather than in a tight loop.\n\n### 4. Read the transcript\n\n`GET /v1/recordings/{id}/transcript`, as for a file: `sentences`, `segments` with word timings, `speakers`, and `lang=` for a translation. The transcription keeps the link it came from (`job.source`).\n\n**TypeScript SDK**\n\n```typescript\nimport { VocalStack } from '@vocalstack/sdk';\n\nconst vs = new VocalStack({ baseUrl: 'https://api.vocalstack.com', apiKey: process.env.VOCALSTACK_API_KEY });\nconst url = process.argv[2] ?? 'https://raw.githubusercontent.com/openai/whisper/main/tests/jfk.flac';\n\nconst probe = await vs.transcriptions.probe(url);\nif (!probe.ok) throw new Error(`that link did not open: ${probe.problem}`);\nif (probe.kind === 'live') throw new Error('a live link: record it with a live transcription instead');\n\nconst tx = await vs.transcriptions.createFromUrl(url, { name: 'From a link' }, { idempotencyKey: crypto.randomUUID() });\n\nconst job = await vs.jobs.wait(tx.job!.recordingId, {\n  intervalMs: 3000,\n  onProgress: (j) => {\n    const p = j.progress;\n    if (j.state === 'fetching') console.log(`${p.phase ?? 'fetching'} ${p.phasePct ?? 0}%`);\n    else console.log(`${j.state} ${p.pct}%`);\n  },\n});\nif (job.state !== 'done') throw new Error(`transcription ${job.state}: ${job.error ?? ''}`);\n\nconst { sentences } = await vs.recordings.transcript(job.recordingId);\nconsole.log(sentences.map((s) => s.text).join(' '));\n```\n\n**Python SDK**\n\n```python\nimport asyncio\nimport os\nimport sys\nimport uuid\n\nfrom vocalstack import VocalStack\n\nURL = sys.argv[1] if len(sys.argv) > 1 else \"https://raw.githubusercontent.com/openai/whisper/main/tests/jfk.flac\"\n\n\ndef show(job) -> None:\n    p = job.progress\n    if job.state == \"fetching\":\n        print(f\"{p.phase or 'fetching'} {p.phase_pct or 0}%\")\n    else:\n        print(f\"{job.state} {p.pct}%\")\n\n\nasync def main() -> None:\n    async with VocalStack(\"https://api.vocalstack.com\", api_key=os.environ[\"VOCALSTACK_API_KEY\"]) as vs:\n        probe = await vs.transcriptions.probe(URL)\n        if not probe.ok:\n            raise SystemExit(f\"that link did not open: {probe.problem}\")\n        if probe.kind == \"live\":\n            raise SystemExit(\"a live link: record it with a live transcription instead\")\n\n        tx = await vs.transcriptions.create(url=URL, name=\"From a link\", idempotency_key=str(uuid.uuid4()))\n        assert tx.job is not None\n\n        job = await vs.jobs.wait(tx.job.recording_id, interval_s=3, on_progress=show)\n        if job.state != \"done\":\n            raise SystemExit(f\"transcription {job.state}: {job.error}\")\n\n        snapshot = await vs.recordings.transcript(job.recording_id)\n        print(\" \".join(s.text for s in snapshot.sentences))\n\n\nasyncio.run(main())\n```\n\n## Stream a microphone\n\nLive audio in, transcript events out, over one WebSocket. Drafts replace the live tail; commits append.\n\nA realtime session is a signed `streamUrl` and one WebSocket. You send raw audio as it is captured; the server sends the transcript back as it is spoken, a few hundred milliseconds behind. Nothing is stored: the transcript is what the socket sends. To keep it, publish to a live transcription instead (Go live).\n\n1. You → VocalStack: `POST /v1/realtime/sessions` (with your API key, on your server)\n2. VocalStack → you: `{ sessionId, streamUrl }`\n3. You → VocalStack: `open a WebSocket to streamUrl`\n4. VocalStack → you: `session.loading … session.ready` (a cold machine warms up first)\n5. You → VocalStack: `binary PCM, every ~100 ms`\n6. VocalStack → you: `transcript.draft` (replaces the live tail)\n7. VocalStack → you: `transcript.committed` (appends a final segment)\n8. You → VocalStack: `{\"type\":\"eof\"}`\n9. VocalStack → you: `session.done` (then the socket closes)\n\n### 1. Create a realtime session\n\n`POST /v1/realtime/sessions` takes the session's options and answers `{ sessionId, streamUrl, expiresAt }`. `streamUrl` carries its own signed token; connect before `expiresAt`. Options worth knowing: `spokenLanguage` (`en`, or absent to detect it), `translate` (target languages, such as `[\"es\"]`), `speakerLabels` (`auto`, `on`, `off`) and `latencyMode` (`auto`, `fast`, `accuracy`).\n\nThis call takes an API key, so it belongs on your server. A web page asks your server for a session and connects to the `streamUrl` it gets back: the key never reaches the browser (the TypeScript tab's `browser.ts`).\n\n### 2. Open the socket\n\nConnect to `streamUrl` as it is, with no header: the token is in the URL. While a machine warms up the server sends `session.loading` heartbeats (`stage`: `download`, then `model`), then `session.ready`. Send audio only after `session.ready`.\n\nThe SDKs' `connect` resolves at `session.ready` and fails if the server goes quiet for 30 s (`readyDeadlineMs`): with no frame at all that is `capacity_exceeded` (every machine is busy), after loading frames `engine_unavailable`. A refused upgrade carries the error envelope: `401 invalid_token`, `410 session_expired`, `503 unavailable` (retry after `Retry-After`).\n\n### 3. Send audio\n\nAudio goes in as binary frames of raw PCM: 32-bit float little-endian, 16 kHz, mono (`pcm_f32le`). Send it as it is captured, in chunks of about 100 to 250 ms (6,400 to 16,000 bytes); there is no need to wait for the transcript between chunks. In a browser, `streamMicrophone` (`@vocalstack/sdk/mic`) asks for the microphone, resamples to 16 kHz, sends the chunks and the `clock` frames that let the server mark gaps when a tab is throttled.\n\nYou can change `spokenLanguage`, `translate`, `speakerLabels` or `latencyMode` mid-stream with a `session.update` text frame (`session.update(…)` in the SDKs); it applies to what follows.\n\n### 4. Read the transcript\n\nText frames come back as JSON with a `type`. Two of them build the transcript, and mixing them up is the most common mistake:\n\n- `transcript.draft` is the **live tail**: the words heard since the last commit, still changing. Each draft is a full replacement. Redraw the tail with it; never append it.\n- `transcript.committed` is **final**: one segment, with word timings, numbered by `seq` (0, 1, 2… gapless). Append it, and drop the draft it covers: the next draft starts where the commit ended.\n\nAround them: `transcript.translated` attaches translations to a committed `seq`; `transcript.amended` corrects a committed segment's text or speaker in place (same `seq`); `transcript.words` streams word timings for the tail. Ignore frame types you do not know: new ones are added without notice.\n\n```text\n+0.0s {\"type\":\"session.loading\",\"stage\":\"model\"}\n+1.2s {\"type\":\"session.ready\"}\n+2.0s {\"type\":\"transcript.draft\",\"segments\":[{\"start\":0.0,\"text\":\"Good\",\"speaker\":0,\"tentative\":true}]}\n+2.5s {\"type\":\"transcript.draft\",\"segments\":[{\"start\":0.0,\"text\":\"Good morning every\",\"speaker\":0,\"tentative\":true}]}\n+3.1s {\"type\":\"transcript.committed\",\"segment\":{\"seq\":0,\"start\":0.0,\"end\":1.4,\"text\":\"Good morning, everyone.\",\"speaker\":0,\"words\":[{\"start\":0.0,\"end\":0.3,\"text\":\"Good\"},{\"start\":0.3,\"end\":0.8,\"text\":\"morning,\"},{\"start\":0.8,\"end\":1.4,\"text\":\"everyone.\"}]}}\n+3.4s {\"type\":\"transcript.translated\",\"seq\":0,\"translations\":{\"es\":\"Buenos días a todos.\"}}\n+3.9s {\"type\":\"transcript.draft\",\"segments\":[{\"start\":1.6,\"text\":\"Thanks for\",\"speaker\":0,\"tentative\":true}]}\n+4.6s {\"type\":\"transcript.draft\",\"segments\":[{\"start\":1.6,\"text\":\"Thanks for coming in\",\"speaker\":0,\"tentative\":true}]}\n+5.3s {\"type\":\"transcript.committed\",\"segment\":{\"seq\":1,\"start\":1.6,\"end\":3.0,\"text\":\"Thanks for coming in today.\",\"speaker\":0,\"words\":[{\"start\":1.6,\"end\":1.9,\"text\":\"Thanks\"},{\"start\":1.9,\"end\":2.1,\"text\":\"for\"},{\"start\":2.1,\"end\":2.5,\"text\":\"coming\"},{\"start\":2.5,\"end\":2.6,\"text\":\"in\"},{\"start\":2.6,\"end\":3.0,\"text\":\"today.\"}]}}\n+5.5s {\"type\":\"transcript.translated\",\"seq\":1,\"translations\":{\"es\":\"Gracias por venir hoy.\"}}\n+5.9s {\"type\":\"session.done\",\"reason\":\"eof\",\"stats\":{\"audioS\":3.2,\"processingS\":1.4,\"realtimeFactor\":2.3}}\n```\n\n### 5. Stop\n\nSend `{\"type\":\"eof\"}` when the audio ends (`session.eof()` in the SDKs). The server transcribes what it still holds, sends the last commits, then `session.done` with `reason` (`eof`, `max_duration`, `cancelled`) and `stats` (`audioS`, `processingS`, `realtimeFactor`), and closes. `cancel` stops at once and drops the rest.\n\nThere is no resume: a dropped connection ends the session. To go on, create a new one. A session lasts at most `limits.maxDurationS` (`GET /v1/realtime/capabilities`), and its audio spends the account's credit at the plan's live rate (`GET /v1/billing`).\n\n**TypeScript SDK**\n\n```typescript\n// Node: ffmpeg captures the microphone, the SDK streams it.\nimport { spawn } from 'node:child_process';\nimport { RealtimeSession, VocalStack } from '@vocalstack/sdk';\n\nconst vs = new VocalStack({ baseUrl: 'https://api.vocalstack.com', apiKey: process.env.VOCALSTACK_API_KEY });\n\nconst { sessionId, streamUrl } = await vs.realtime.createSession({ spokenLanguage: 'en', translate: ['es'] });\n\nconst session = await RealtimeSession.connect({\n  sessionId,\n  streamUrl,\n  onLoading: (stage) => console.error(`warming up (${stage})…`),\n});\n\n// Raw PCM: f32le, 16 kHz, mono. macOS: -f avfoundation -i :0, Linux: -f pulse -i default.\nconst mic = spawn('ffmpeg', ['-loglevel', 'error', '-f', 'avfoundation', '-i', ':0', '-f', 'f32le', '-ac', '1', '-ar', '16000', '-']);\nmic.stdout.on('data', (chunk: Buffer) => session.sendAudio(new Uint8Array(chunk)));\n\nmic.on('close', () => session.eof()); // the server flushes, then sends session.done\nprocess.once('SIGINT', () => mic.kill()); // Ctrl-C ends the audio\n\nfor await (const ev of session) {\n  if (ev.type === 'transcript.draft') {\n    // The live tail: replace it, never append it.\n    process.stdout.write(`\\r\\x1b[K\\x1b[2m${ev.segments.map((s) => s.text).join(' ')}\\x1b[0m`);\n  } else if (ev.type === 'transcript.committed') {\n    // Final: append it (seq 0, 1, 2…), and the tail it covers is gone.\n    process.stdout.write(`\\r\\x1b[K${ev.segment.text}\\n`);\n  } else if (ev.type === 'transcript.translated') {\n    console.log(`  es: ${ev.translations.es}`);\n  }\n}\n\nconsole.log(`done (${session.doneReason})`, session.stats);\n```\n\n**TypeScript SDK**\n\n```typescript\n// In a web page: your server creates the session (the API key stays\n// there), and the page streams the microphone to its streamUrl.\nimport { RealtimeSession, VocalStack } from '@vocalstack/sdk';\nimport { streamMicrophone } from '@vocalstack/sdk/mic';\n\n// On your server, behind a route of yours (POST /api/transcribe, say):\nconst vs = new VocalStack({ baseUrl: 'https://api.vocalstack.com', apiKey: process.env.VOCALSTACK_API_KEY });\nexport async function createStream(): Promise<{ sessionId: string; streamUrl: string }> {\n  const { sessionId, streamUrl } = await vs.realtime.createSession({ spokenLanguage: 'en' });\n  return { sessionId, streamUrl };\n}\n\n// In the page, with what your route returned:\nexport async function transcribeMicrophone(\n  stream: { sessionId: string; streamUrl: string },\n  render: (committed: string, tail: string) => void,\n): Promise<void> {\n  const session = await RealtimeSession.connect(stream);\n  const mic = await streamMicrophone(session); // asks for the microphone, sends 16 kHz PCM\n  document.querySelector('#stop')?.addEventListener('click', () => {\n    void mic.stop();\n    session.eof();\n  });\n  let committed = '';\n  for await (const ev of session) {\n    if (ev.type === 'transcript.draft') render(committed, ev.segments.map((s) => s.text).join(' '));\n    if (ev.type === 'transcript.committed') {\n      committed += `${ev.segment.text} `;\n      render(committed, '');\n    }\n  }\n}\n```\n\n**Python SDK**\n\n```python\nimport asyncio\nimport os\nimport signal\n\nfrom vocalstack import RealtimeSession, VocalStack\n\n# Raw PCM: f32le, 16 kHz, mono. macOS: -f avfoundation -i :0, Linux: -f pulse -i default.\nMIC = [\"ffmpeg\", \"-loglevel\", \"error\", \"-f\", \"avfoundation\", \"-i\", \":0\",\n       \"-f\", \"f32le\", \"-ac\", \"1\", \"-ar\", \"16000\", \"-\"]\n\n\nasync def main() -> None:\n    async with VocalStack(\"https://api.vocalstack.com\", api_key=os.environ[\"VOCALSTACK_API_KEY\"]) as vs:\n        created = await vs.realtime.create_session(spoken_language=\"en\", translate=[\"es\"])\n\n        session = await RealtimeSession.connect(created.session_id, created.stream_url)\n\n        mic = await asyncio.create_subprocess_exec(*MIC, stdout=asyncio.subprocess.PIPE)\n\n        async def feed() -> None:\n            assert mic.stdout is not None\n            while chunk := await mic.stdout.read(8000):  # 125 ms of audio\n                await session.send_audio(chunk)\n            await session.eof()  # the server flushes, then sends session.done\n\n        feeder = asyncio.create_task(feed())\n        asyncio.get_running_loop().add_signal_handler(signal.SIGINT, mic.terminate)  # Ctrl-C ends the audio\n\n        async with session:\n            async for event in session:\n                if event.type == \"transcript.draft\":\n                    # The live tail: replace it, never append it.\n                    tail = \" \".join(s.text for s in event.segments)\n                    print(f\"\\r\\x1b[K\\x1b[2m{tail}\\x1b[0m\", end=\"\", flush=True)\n                elif event.type == \"transcript.committed\":\n                    # Final: append it (seq 0, 1, 2...), and the tail it covers is gone.\n                    print(f\"\\r\\x1b[K{event.segment.text}\", flush=True)\n                elif event.type == \"transcript.translated\":\n                    print(f\"  es: {event.translations.get('es')}\")\n            await feeder\n            print(f\"done ({session.done_reason})\", session.stats)\n\n\nasyncio.run(main())\n```\n\n## Stream a file at full speed\n\nSend a file's bytes through a realtime session and read the transcript as fast as the server makes it. Nothing is stored.\n\nA realtime session can take a whole file instead of a live microphone: you send the file's own bytes (mp3, wav, flac, ogg, m4a, a video…), the server decodes them and transcribes at full speed, many times faster than the audio plays, and the transcript comes back over the same socket. Nothing is stored. When you want the transcript kept, shareable and exportable, create a transcription instead (Transcribe a file).\n\n1. You → VocalStack: `POST /v1/realtime/sessions` (input: { kind: \"file\" })\n2. VocalStack → you: `{ streamUrl }`\n3. You → VocalStack: `open a WebSocket to streamUrl`\n4. VocalStack → you: `session.ready`\n5. You → VocalStack: `the file's bytes` (binary frames, as fast as the socket takes them)\n6. You → VocalStack: `{\"type\":\"eof\"}` (transcription starts here)\n7. VocalStack → you: `transcript.progress` (every few seconds)\n8. VocalStack → you: `transcript.committed` (appends a final segment)\n9. VocalStack → you: `session.done`\n\n### 1. Create a file session\n\n`POST /v1/realtime/sessions` with `input: { kind: \"file\" }` and the usual options (`spokenLanguage`, `translate`, `speakerLabels`). A file session sends no drafts unless you ask (`events: { drafts: true }`): a draft previews a live tail, and a file has none. The file's decoded length must fit `limits.maxDurationS` (`GET /v1/realtime/capabilities`); a longer one is refused with `invalid_audio`.\n\n### 2. Send the bytes\n\nOpen the socket, wait for `session.ready`, then send the file as binary frames in chunks (the SDKs send 256 KB at a time and hold back while the socket's buffer is full), followed by `{\"type\":\"eof\"}`. Transcription starts at `eof`: no transcript frame arrives before it. `vs.realtime.transcribeFile` (TypeScript) and `vs.realtime.transcribe_file` (Python) do all of this in one call.\n\n### 3. Follow the progress\n\n`transcript.progress` arrives on a timer (it is also the keep-alive) with `processedS`, the media seconds transcribed so far, and `totalS` once the file's length is known. Commits arrive as they are made, in order, with gapless `seq`; append them.\n\n```text\n+0.0s {\"type\":\"session.ready\"}\n+2.1s {\"type\":\"transcript.progress\",\"processedS\":0,\"totalS\":184.2}\n+4.1s {\"type\":\"transcript.progress\",\"processedS\":31.5,\"totalS\":184.2}\n+4.3s {\"type\":\"transcript.committed\",\"segment\":{\"seq\":0,\"start\":0.0,\"end\":4.2,\"text\":\"Welcome back to the show.\",\"speaker\":0,\"words\":[{\"start\":0.0,\"end\":0.4,\"text\":\"Welcome\"},{\"start\":0.4,\"end\":0.7,\"text\":\"back\"},{\"start\":0.7,\"end\":0.9,\"text\":\"to\"},{\"start\":0.9,\"end\":1.0,\"text\":\"the\"},{\"start\":1.0,\"end\":1.4,\"text\":\"show.\"}]}}\n+4.4s {\"type\":\"transcript.committed\",\"segment\":{\"seq\":1,\"start\":4.6,\"end\":9.8,\"text\":\"Today we are talking about rivers.\",\"speaker\":0,\"words\":[{\"start\":4.6,\"end\":4.9,\"text\":\"Today\"},{\"start\":4.9,\"end\":5.1,\"text\":\"we\"},{\"start\":5.1,\"end\":5.3,\"text\":\"are\"},{\"start\":5.3,\"end\":5.8,\"text\":\"talking\"},{\"start\":5.8,\"end\":6.1,\"text\":\"about\"},{\"start\":6.1,\"end\":6.7,\"text\":\"rivers.\"}]}}\n+6.1s {\"type\":\"transcript.progress\",\"processedS\":96.0,\"totalS\":184.2}\n+8.0s {\"type\":\"transcript.progress\",\"processedS\":184.2,\"totalS\":184.2}\n+8.2s {\"type\":\"session.done\",\"reason\":\"eof\",\"stats\":{\"audioS\":184.2,\"processingS\":6.1,\"realtimeFactor\":30.2}}\n```\n\n### 4. Finish\n\nAfter the last commit the server sends `session.done`: `reason` is `eof`, or `max_duration` when a file with no declared length ran past the limit and was cut, and `stats.realtimeFactor` says how much faster than real time it ran. The socket then closes.\n\n**TypeScript SDK**\n\n```typescript\nimport { VocalStack } from '@vocalstack/sdk';\n\nconst vs = new VocalStack({ baseUrl: 'https://api.vocalstack.com', apiKey: process.env.VOCALSTACK_API_KEY });\n\n// transcribeFile creates the file session, sends the bytes and eof, and\n// collects what comes back. Nothing is stored.\nconst result = await vs.realtime.transcribeFile('./talk.mp3', {\n  spokenLanguage: 'en',\n  speakerLabels: 'on',\n  onProgress: ({ processedS, totalS }) => console.log(`${processedS.toFixed(0)} s of ${totalS?.toFixed(0) ?? '?'} s`),\n  onCommit: (segment) => console.log(`#${segment.seq} [${segment.speaker}] ${segment.text}`),\n});\n\nconsole.log(result.text);\nconsole.log(`${result.doneReason}: ${result.stats?.realtimeFactor}× real time`);\n```\n\n**Python SDK**\n\n```python\nimport asyncio\nimport os\n\nfrom vocalstack import VocalStack\n\n\nasync def main() -> None:\n    async with VocalStack(\"https://api.vocalstack.com\", api_key=os.environ[\"VOCALSTACK_API_KEY\"]) as vs:\n        # transcribe_file creates the file session, sends the bytes and eof,\n        # and collects what comes back. Nothing is stored.\n        result = await vs.realtime.transcribe_file(\n            \"./talk.mp3\",\n            spoken_language=\"en\",\n            speaker_labels=\"on\",\n            on_progress=lambda p: print(f\"{p.processed_s:.0f} s of {p.total_s or 0:.0f} s\"),\n            on_commit=lambda s: print(f\"#{s.seq} [{s.speaker}] {s.text}\"),\n        )\n\n        print(result.text)\n        print(result.done_reason, result.stats)\n\n\nasyncio.run(main())\n```\n\n## Go live\n\nPublish live audio to a stored transcription, and put it on a broadcast link listeners open. Control is HTTP; audio is one WebSocket.\n\nA realtime session is gone when its socket closes. To keep what is said, and let other people follow it as it happens, publish to a **live transcription**: it is stored as it is spoken, listed under Transcriptions, and its recordings can be read, translated, exported and shared afterwards. A **broadcast** is the link listeners open (`code`, `shareUrl`); it is fed by the live transcriptions attached to it. The order: create the live transcription, optionally put it on a broadcast, start a recording, publish audio to its `streamUrl`, and control it over HTTP.\n\n### 1. Create a live transcription\n\n`POST /v1/transcriptions/live` with a `name` and optional `settings` (`spokenLanguage`, `speakerLabels`, `latencyMode`…) creates it idle. Nothing is recorded until a recording starts. Its `source` can also be a stream the server pulls itself (a YouTube live, an internet radio address), in which case there is no audio for you to send.\n\n### 2. Put it on a broadcast\n\n`POST /v1/broadcasts` creates the link (`code`, and `shareUrl` to hand out; `access: \"private\"` and a `password` keep it to the people you give them). `PUT /v1/transcriptions/{id}/broadcasts/{broadcast}` feeds it from the live transcription. One transcription can feed several broadcasts and a broadcast several transcriptions. Listeners follow it with the subscribe socket (Listen).\n\n### 3. Start a recording\n\n`POST /v1/transcriptions/{id}/recordings` starts one and answers with a publish grant: `recording` (its `id` is what you control and read it by), `sessionId` and `streamUrl`. A `409 conflict` with `details.takeover` means another device is recording it now; repeat with `{ \"takeover\": true }` to record from here instead.\n\n### 4. Publish audio\n\nConnect to `streamUrl` and publish exactly as on a realtime session (Stream a microphone): wait for `session.ready`, send raw PCM (`pcm_f32le`, 16 kHz, mono) as binary frames, and read commits back. The difference is that every commit is also stored and fanned out to the recording's subscribers, translated into the languages they read. A cold machine can take a minute to become ready: give `connect` a long ready deadline (`readyDeadlineMs`, `ready_deadline_s`).\n\n### 5. Pause, resume and stop\n\nControl is HTTP, never a frame on the socket: `pause` (idempotent), `resume`, and `stop`, which is allowed only from paused. Sending `eof` on the publisher socket also ends the recording. A recording moves `live` ⇄ `paused` → `ended` → `finalized` (its transcript settled); listeners stay connected through `ended`. A recording left paused longer than `settings.maxPauseS` ends by itself.\n\n**TypeScript SDK**\n\n```typescript\nimport { spawn } from 'node:child_process';\nimport { VocalStack } from '@vocalstack/sdk';\n\nconst vs = new VocalStack({ baseUrl: 'https://api.vocalstack.com', apiKey: process.env.VOCALSTACK_API_KEY });\n\nconst tx = await vs.transcriptions.createLive({ name: 'Town hall', settings: { spokenLanguage: 'en' } });\n\nconst broadcast = await vs.broadcasts.create({ name: 'Town hall' });\nawait vs.transcriptions.attachBroadcast(tx.id, broadcast.id);\nconsole.log('listeners open', broadcast.shareUrl);\n\nconst grant = await vs.transcriptions.startRecording(tx.id);\nconst recordingId = grant.recording.id;\n\nconst stream = await vs.recordings.publish(grant, { readyDeadlineMs: 180_000 }); // a cold machine can take a while\n// -re reads the file at its own pace, as a microphone would arrive. For a\n// microphone: -f avfoundation -i :0 (macOS), -f pulse -i default (Linux).\nconst ffmpeg = spawn('ffmpeg', ['-loglevel', 'error', '-re', '-i', './talk.mp3', '-f', 'f32le', '-ac', '1', '-ar', '16000', '-']);\nffmpeg.stdout.on('data', (chunk: Buffer) => stream.sendAudio(new Uint8Array(chunk)));\n\n// eof ends the recording when the audio does; pause, resume and stop are HTTP.\nffmpeg.on('close', () => stream.eof());\nprocess.once('SIGINT', async () => {\n  ffmpeg.kill();\n  await vs.recordings.pause(recordingId);\n  await vs.recordings.stop(recordingId); // only from paused\n});\n\nfor await (const ev of stream) {\n  if (ev.type === 'transcript.committed') console.log(`#${ev.segment.seq} ${ev.segment.text}`);\n}\n\nconsole.log('ended:', (await vs.recordings.get(recordingId)).state);\n```\n\n**Python SDK**\n\n```python\nimport asyncio\nimport os\n\nfrom vocalstack import VocalStack, pace_realtime\n\n# -re is not needed here: pace_realtime sends the PCM at its own pace.\nDECODE = [\"ffmpeg\", \"-loglevel\", \"error\", \"-i\", \"./talk.mp3\", \"-f\", \"f32le\", \"-ac\", \"1\", \"-ar\", \"16000\", \"-\"]\n\n\nasync def main() -> None:\n    async with VocalStack(\"https://api.vocalstack.com\", api_key=os.environ[\"VOCALSTACK_API_KEY\"]) as vs:\n        tx = await vs.transcriptions.create_live(name=\"Town hall\", settings={\"spokenLanguage\": \"en\"})\n\n        broadcast = await vs.broadcasts.create(name=\"Town hall\")\n        await vs.transcriptions.attach_broadcast(tx.id, broadcast.id)\n        print(\"listeners open\", broadcast.share_url)\n\n        grant = await vs.transcriptions.start_recording(tx.id)\n        recording_id = grant.recording.id\n\n        ffmpeg = await asyncio.create_subprocess_exec(*DECODE, stdout=asyncio.subprocess.PIPE)\n        pcm, _ = await ffmpeg.communicate()\n        async with await vs.recordings.publish(grant, ready_deadline_s=180) as stream:\n            feeder = asyncio.create_task(pace_realtime(stream, pcm))  # real time, then eof(): the recording ends\n            async for event in stream:\n                if event.type == \"transcript.committed\":\n                    print(f\"#{event.segment.seq} {event.segment.text}\", flush=True)\n            await feeder\n\n        # To end without eof: await vs.recordings.pause(recording_id), then stop(recording_id).\n        print(\"ended:\", (await vs.recordings.get(recording_id)).state)\n\n\nasyncio.run(main())\n```\n\n## Listen\n\nFollow a recording as it happens, in any language: subscribe, catch up from a snapshot, then apply live frames in order.\n\nAnyone allowed to read a recording can follow it live over its subscribe socket: the owner with an API key, a reader with a share link's code, anyone at all on a public broadcast. The socket sends what changes from the moment it opens; what was said before is read once, from a snapshot. Getting the two to meet without a gap or a duplicate is the whole trick, and the cursor makes it exact.\n\n### 1. Find the recording\n\nSubscribing takes a recording id. The owner has it already: the publish grant's `recording.id`, a transcription's or broadcast's `currentRecording.recordingId`, or its list of recordings. A screen waiting for something to go live opens the events socket of the transcription or the broadcast (`WS /v1/transcriptions/{id}/events`, `/v1/broadcasts/{id}/events`) and gets `recording.started` with the `recordingId` the moment it does.\n\n### 2. Subscribe\n\nOpen `WS /v1/recordings/{id}/subscribe`. The owner sends the API key as `Authorization: Bearer …` (never in the URL); a share link's reader adds `shareCode=`; a public broadcast's listener sends nothing. `lang=es` reads it translated (through a share link, the link must allow translation), and `voice=` picks who speaks it (Live translation and spoken translations). The first frame is `subscribe.ready`: the recording's `state` and `lastSeq`, the cursor the live sequence is at.\n\n### 3. Catch up\n\nRead `GET /v1/recordings/{id}/transcript` (same `lang`, same credential): every sentence and segment up to the snapshot's own `lastSeq`. Every durable frame on the socket carries a `cursor`, gapless per recording; skip the frames whose `cursor` is at or below the snapshot's `lastSeq` (they are in it already) and apply the rest in order. A listener gets a `noop` frame in place of one withheld from it, so the cursor never skips. If it does jump (a dropped frame, `subscribe.gap`), read the transcript again with `after=<cursor>` and carry on from its `lastSeq`. `vs.recordings.subscribe` in the TypeScript SDK does all of this and hands you the snapshot first.\n\n```text\n+0.0s {\"type\":\"subscribe.ready\",\"recordingId\":\"5c0d…\",\"lastSeq\":41,\"state\":\"live\",\"instance\":\"api-1\"}\n+0.2s {\"type\":\"(snapshot)\",\"lastSeq\":42,\"text\":\"Good morning, everyone. Thanks for coming in today.\"}\n+0.6s {\"type\":\"transcript.committed\",\"cursor\":42,\"seq\":42,\"segment\":{\"seq\":17,\"start\":5.1,\"end\":6.0,\"text\":\"Thanks for coming in today.\",\"speaker\":0,\"words\":[]}}\n+1.9s {\"type\":\"transcript.committed\",\"cursor\":43,\"seq\":43,\"segment\":{\"seq\":18,\"start\":6.4,\"end\":8.2,\"text\":\"Let us start with the budget.\",\"speaker\":0,\"words\":[]}}\n+2.2s {\"type\":\"transcript.translated\",\"cursor\":44,\"seq\":18,\"translations\":{\"es\":\"Empecemos por el presupuesto.\"}}\n+3.0s {\"type\":\"noop\",\"cursor\":45,\"seq\":45}\n+4.4s {\"type\":\"recording.state\",\"cursor\":46,\"seq\":46,\"recordingId\":\"5c0d…\",\"state\":\"paused\"}\n```\n\n### 4. Follow it live\n\nFrom there, frames arrive as the recording goes: `transcript.committed` appends a segment (a subscriber gets no drafts), `transcript.sentence` replaces a whole sentence by `sentenceId`, `transcript.translated` fills a segment's translations, `transcript.amended` (the engine) and `transcript.corrected` (a person) rewrite one in place, and `recording.state` says `live`, `paused`, `ended`, `finalized`. Change language without reopening: send `{\"type\":\"subscribe.update\",\"lang\":\"fr\"}` (`setLanguage` in the SDK), then read the snapshot again in it.\n\n### 5. When it ends\n\nAt `ended` the socket stays open: corrections and the last translations can still arrive until `finalized`. Close it when you are done. Afterwards, the snapshot is the whole transcript, and `GET /v1/recordings/{id}` says how it ended.\n\n**TypeScript SDK**\n\n```typescript\nimport { VocalStack } from '@vocalstack/sdk';\n\nconst vs = new VocalStack({ baseUrl: 'https://api.vocalstack.com', apiKey: process.env.VOCALSTACK_API_KEY });\nconst recordingId = process.argv[2]!; // a grant's recording.id, or a transcription's currentRecording.recordingId\n\n// The owner, with the key. A share link's reader: new VocalStack({ baseUrl })\n// and { shareCode: 'CODE', asOwner: false }; a public broadcast's listener: { asOwner: false }.\nconst sub = await vs.recordings.subscribe(recordingId, { lang: 'es' });\n\n// subscribe() resolves once subscribe.ready and the snapshot are in: what\n// was said before you came, then only frames past its cursor, gaps healed.\nfor (const s of sub.snapshot?.sentences ?? []) console.log(s.text, s.translations?.es ?? '');\n\nfor await (const ev of sub) {\n  if (ev.type !== 'frame') continue;\n  const f = ev.frame;\n  if (f.type === 'transcript.committed') console.log(f.segment.text);\n  if (f.type === 'transcript.translated') console.log(`  es: ${f.translations.es}`);\n  if (f.type === 'recording.state') {\n    console.log(`[${f.state}]`);\n    if (f.state === 'finalized') sub.close();\n  }\n}\n```\n\n**Python SDK**\n\n```python\nimport asyncio\nimport json\nimport os\nimport sys\n\nfrom vocalstack import VocalStack\n\n\nasync def main() -> None:\n    async with VocalStack(\"https://api.vocalstack.com\", api_key=os.environ[\"VOCALSTACK_API_KEY\"]) as vs:\n        recording_id = sys.argv[1]  # a grant's recording.id, or a transcription's current_recording\n\n        # The owner, with the key. A share link's reader: share_code=\"CODE\", as_owner=False.\n        socket = await vs.recordings.subscribe(recording_id, lang=\"es\")\n        ready = json.loads(await socket.recv())  # subscribe.ready {lastSeq, state}\n        print(\"state:\", ready[\"state\"])\n\n        snapshot = await vs.recordings.transcript(recording_id, lang=\"es\")\n        for s in snapshot.sentences:\n            print(s.text, (s.translations or {}).get(\"es\", \"\"))\n        cursor = snapshot.last_seq\n\n        async for message in socket:\n            if isinstance(message, bytes):\n                continue  # live audio, when asked for\n            frame = json.loads(message)\n            at = frame.get(\"cursor\")\n            if at is not None:\n                if at <= cursor:\n                    continue  # already in the snapshot\n                if at > cursor + 1:  # a gap: read what was missed, then go on\n                    missed = await vs.recordings.transcript(recording_id, lang=\"es\", after=cursor)\n                    for seg in missed.segments:\n                        print(seg.text)\n                    cursor = max(cursor, missed.last_seq)\n                    if at <= cursor:\n                        continue\n                cursor = at\n            if frame[\"type\"] == \"transcript.committed\":\n                print(frame[\"segment\"][\"text\"], flush=True)\n            elif frame[\"type\"] == \"transcript.translated\":\n                print(\"  es:\", frame[\"translations\"].get(\"es\"))\n            elif frame[\"type\"] == \"recording.state\":\n                print(f\"[{frame['state']}]\")\n                if frame[\"state\"] == \"finalized\":\n                    await socket.close()\n\n\nasyncio.run(main())\n```\n\n## Live translation and spoken translations\n\nTranslate a stream as it is spoken, and have each translated sentence spoken back in a voice. Text frames and audio clips, over the same socket.\n\nA realtime session (and every recording, for its listeners) can translate what is said into other languages as it goes, and speak the translation: each finished sentence is synthesised in the language's voice and sent as a short audio clip. Translations follow the transcript by a second or two; a spoken sentence follows its translation.\n\n1. You → VocalStack: `options: translate [\"es\"], sentences { enabled: true }, voice [\"es\"]`\n2. VocalStack → you: `transcript.committed` (the words as spoken)\n3. VocalStack → you: `transcript.sentence (open)` (the sentence so far, a provisional translation)\n4. VocalStack → you: `transcript.sentence (closed)` (the sentence and its final translation)\n5. VocalStack → you: `transcript.voice` (that sentence, spoken in Spanish (Ogg/Opus))\n\n### 1. Pick the languages\n\n`GET /v1/languages` lists what can be transcribed (`transcription`), translated into (`translation`) and spoken (`voice`), with names. `GET /v1/voices/available?lang=es` lists the voices that speak a language, best first; `default` marks the one used when none is named, and with a key your own cloned voices are there too.\n\n### 2. Ask for translations\n\n`translate: [\"es\", \"fr\"]` on the session's options. Without sentences, each commit is followed by a `transcript.translated` frame naming its `seq`: attach `translations` to that segment. You can change the list mid-stream (`session.update`, `translate: […]`); it applies to what follows.\n\n### 3. Read them by sentence\n\nWith `sentences: { enabled: true }`, the server also assembles commits into sentences and translates whole sentences, which reads far better. `transcript.sentence` is a full replacement of one sentence, keyed by `sentenceId`: `status` goes `open` → `closed` (or `dropped`: discard it), `seqs` lists the commits it covers, and `translations` are provisional while it is open and final once it closes. Upsert by `sentenceId`; never append.\n\n### 4. Speak them\n\n`voice: [\"es\"]` (it needs `sentences`) speaks each closed sentence's Spanish translation in the language's default voice; `\"es:<voice id>\"` names a voice from the catalogue, and `voiceByLang: { \"es\": \"<voice id>\" }` sets one per language. With `voiceCast` on (the default), each speaker gets a distinct voice of matching gender. Each clip arrives as a `transcript.voice` frame: `sentenceId`, `lang`, `durationS`, and `audio`, the clip itself (base64 Ogg/Opus, 24 kHz mono). A long sentence comes in pieces (`part` of `parts`) so the first can play while the rest are made: play them back to back, in order.\n\n```text\n+0.0s {\"type\":\"session.ready\"}\n+2.6s {\"type\":\"transcript.committed\",\"segment\":{\"seq\":0,\"start\":0.0,\"end\":1.4,\"text\":\"Good morning, everyone.\",\"speaker\":0,\"words\":[]}}\n+2.7s {\"type\":\"transcript.sentence\",\"sentence\":{\"sentenceId\":0,\"status\":\"open\",\"text\":\"Good morning, everyone.\",\"seqs\":[0],\"speaker\":0,\"start\":0.0,\"end\":1.4,\"translations\":{\"es\":\"Buenos días a todos.\"}}}\n+3.9s {\"type\":\"transcript.sentence\",\"sentence\":{\"sentenceId\":0,\"status\":\"closed\",\"text\":\"Good morning, everyone.\",\"seqs\":[0],\"speaker\":0,\"start\":0.0,\"end\":1.4,\"translations\":{\"es\":\"Buenos días a todos.\"}}}\n+5.0s {\"type\":\"transcript.voice\",\"sentenceId\":0,\"generation\":0,\"lang\":\"es\",\"voice\":\"es_ES-davefx-medium\",\"format\":\"ogg\",\"durationS\":1.3,\"synthS\":0.4,\"part\":0,\"parts\":1,\"voiceKey\":\"\",\"audio\":\"T2dnUwACAAAA…\"}\n```\n\n### 5. For a recording's listeners\n\nA listener on the subscribe socket asks with `lang=es` (translations filtered to Spanish) and `voice=<voice id>` (Listen); a clip reaches every listener on that language and voice, synthesised once. On a recording, `transcript.voice` carries a `url` instead of `audio`: fetch it with `GET /v1/recordings/{id}/voice/{sentence}?lang=es` (Ogg/Opus; `format=caf` for Apple). `transcript.voiceStatus` says when a voice is still `preparing` (its clips are held and will come) or `unavailable`. After the recording, `GET /v1/recordings/{id}/voice` lists every clip left behind, language by language.\n\n**TypeScript SDK**\n\n```typescript\nimport { createReadStream } from 'node:fs';\nimport { writeFile } from 'node:fs/promises';\nimport { VocalStack, applySentence, type SentenceSegment } from '@vocalstack/sdk';\n\nconst vs = new VocalStack({ baseUrl: 'https://api.vocalstack.com', apiKey: process.env.VOCALSTACK_API_KEY });\n\nconst { voices } = await vs.voices.available('es');\nconst voice = voices.find((v) => v.default) ?? voices[0];\n\n// A file at full speed here; a microphone works the same (Stream a microphone).\nconst session = await vs.realtime.connect(\n  {\n    input: { kind: 'file' },\n    spokenLanguage: 'en',\n    translate: ['es'],\n    sentences: { enabled: true },\n    voice: [`es:${voice.id}`],\n  },\n  {\n    // The clips arrive as transcript.voice frames: onFrame sees every frame.\n    onFrame: (f) => {\n      if (f.type === 'transcript.voice' && f.audio) {\n        void writeFile(`es-${f.sentenceId}-${f.part ?? 0}.ogg`, Buffer.from(f.audio, 'base64'));\n      }\n    },\n  },\n);\nvoid session.sendFile(createReadStream('./talk.mp3'));\n\nlet sentences: SentenceSegment[] = [];\nfor await (const ev of session) {\n  if (ev.type !== 'transcript.sentence') continue;\n  sentences = applySentence(sentences, ev.sentence); // upsert by sentenceId\n  if (ev.sentence.status === 'closed') console.log(`${ev.sentence.text}\\n  es: ${ev.sentence.translations?.es}`);\n}\nconsole.log(`${sentences.length} sentences; the Spanish clips are es-<sentence>-<part>.ogg`);\n```\n\n**Python SDK**\n\n```python\nimport asyncio\nimport base64\nimport os\n\nfrom vocalstack import VocalStack\n\n\nasync def main() -> None:\n    async with VocalStack(\"https://api.vocalstack.com\", api_key=os.environ[\"VOCALSTACK_API_KEY\"]) as vs:\n        catalogue = await vs.voices.available(\"es\")\n        voice = next((v for v in catalogue.voices if v.default), catalogue.voices[0])\n\n        # A file at full speed here; a microphone works the same (Stream a microphone).\n        session = await vs.realtime.connect(\n            input={\"kind\": \"file\"},\n            spoken_language=\"en\",\n            translate=[\"es\"],\n            sentences={\"enabled\": True},\n            voice=[f\"es:{voice.id}\"],\n        )\n        async with session:\n            sender = asyncio.create_task(session.send_file(\"./talk.mp3\"))\n            async for event in session:\n                if event.type == \"transcript.sentence\" and event.sentence.status == \"closed\":\n                    print(event.sentence.text)\n                    print(\"  es:\", (event.sentence.translations or {}).get(\"es\"))\n                elif event.type == \"transcript.voice\" and event.audio:\n                    with open(f\"es-{event.sentence_id}-{event.part or 0}.ogg\", \"wb\") as f:\n                        f.write(base64.b64decode(event.audio))\n            await sender\n\n\nasyncio.run(main())\n```\n\n## Errors, retries and limits\n\nOne error envelope everywhere; what to retry (429, 503 unavailable) and how to make a retry safe (Idempotency-Key); credit, plan limits and the 402s.\n\nEvery failed call answers with the same JSON envelope, and the SDKs turn it into one error type (`VocalStackError`) with the same fields. Most errors are final; a few say \"try again\", and the SDKs already do that for you where it is safe.\n\n```json\n{\n  \"defined\": true,\n  \"code\": \"insufficient_credit\",\n  \"status\": 402,\n  \"message\": \"this account has no credit left; add credit to continue\",\n  \"data\": {\n    \"retryable\": false,\n    \"details\": {\n      \"balance\": -0.42,\n      \"required\": 0.075,\n      \"currency\": \"usd\",\n      \"plan\": \"payg\",\n      \"topUpUrl\": \"https://…\"\n    }\n  },\n  \"requestId\": \"01J…\"\n}\n```\n\n### 1. Read the error\n\n`code` is what to branch on; `message` is for people; `data.retryable` says whether sending the same request again can succeed; `data.details` carries what the code needs; `requestId` (also the `X-Request-Id` header) is what support asks for. In the SDKs: `err.code`, `err.status`, `err.message`, `err.retryable`, `err.details`, `err.retryAfter` (seconds, from `Retry-After`) and `err.headers`.\n\n| Status and code | What to do |\n|---|---|\n| 400, 422 `invalid_options`, `invalid_body`, `invalid_audio` | Fix the request: the message names the field. |\n| 401 `invalid_token`, `key_expired`, `key_revoked` | Check `VOCALSTACK_API_KEY`; use the replacement key, or create one. |\n| 402 `insufficient_credit` | The account's credit is out: `details.balance` (dollars), `plan`, `topUpUrl`, and `required` when the cost was known up front (a file of known length). Add credit; nothing was charged. |\n| 402 `quota_exceeded` | A plan limit is reached: `details.metric` (`concurrent_transcriptions`), `limit`, `used`, `resetsAt` (null: it frees up as transcriptions end), `plan`, `topUpUrl`. |\n| 403 `forbidden`, `key_from_browser`, `suspended` | A missing scope (the message names it), a key sent from a web page (keep it on your server), or a suspension. Retrying never helps. |\n| 404, 409 `not_found`, `conflict`, `invalid_state` | No such thing, a clash (a link code taken, another device recording), or not now (`stop` on a recording that is not paused; a second subscription checkout while one is active). |\n| 409 `payment_method_required` | Auto top-up (`PUT /v1/billing/auto-top-up`) and the customer portal need a saved payment method: make a payment (`POST /v1/billing/checkouts`) or add a card (`POST /v1/billing/payment-methods/manage`) first. |\n| 409 `payment_method_in_use`, `billing_details_required`, `auto_top_up_off` | A card that pays the subscription cannot be removed (add another and make it the default first); an invoice needs a billing name and address (`PUT /v1/billing/details`); auto top-up is off, so there is nothing to run now. |\n| 404 `promo_code_invalid` | The promo code does not exist, has expired, is used up, was redeemed by this account already, or gives nothing for this purchase. |\n| 400 `range_too_long` | A usage report covers at most 92 days: ask for a shorter range. |\n| 429 `rate_limited` | Wait `Retry-After` seconds. Retried for you. With `details.metric: \"creates_per_hour\"` it is the plan's creation rate (new transcriptions an hour), not a burst limit: `limit` and `resetsAt` say when. |\n| 503 `unavailable` | The service is waking (its database was asleep). Send it again after `Retry-After`. Retried for you when it is safe. |\n\n### 2. Retry what can be retried\n\nThe SDKs retry a `429 rate_limited` and a `503 unavailable` after `Retry-After`, twice by default (`maxRetries` / `max_retries` on the client), but a `503` only for a call that is safe to send twice: `GET`, `HEAD`, `DELETE`, or any call carrying an `Idempotency-Key`. A create without one is not retried, because the first attempt may have worked. Every response carries `RateLimit-Limit`, `RateLimit-Remaining` and `RateLimit-Reset`: slow down before the 429. `GET /v1/billing` shows the plan, its rates per hour of audio, the credit balance and the limits (transcriptions at once, new transcriptions an hour, requests a minute, the largest file, the longest live recording).\n\n### 3. Make creates safe to retry\n\nSend `Idempotency-Key: <a fresh UUID>` with a create (`POST /v1/broadcasts`, `POST /v1/transcriptions`, `POST /v1/transcriptions/live`, starting a recording…). Repeated within 24 hours with the same key and the same body, it returns the first answer (with `Idempotent-Replayed: true`) instead of creating a second thing; the same key with a different body is `422 idempotency_key_reused`, and a repeat while the first is still running is `409 conflict`. Keys are per account. In the SDKs: `{ idempotencyKey }` as the call options (TypeScript), `idempotency_key=` (Python).\n\n### 4. Sockets\n\nA refused WebSocket upgrade carries the same envelope over HTTP: `401 invalid_token`, `410 session_expired` (a realtime session's `expiresAt` passed: create a new one), `503 unavailable` (retry after `Retry-After`). A fleet with every machine busy can instead hang the connect or answer a bare 503 with no envelope: the SDKs report that as `capacity_exceeded`, and as `engine_unavailable` when the machine started warming up and then went quiet. On an open socket, an `error` frame carries `code`, `message` and `retryable`; `slow_consumer` means you read too slowly and were closed (commits are never dropped, drafts are). There is no resume: create a new session.\n\n**TypeScript SDK**\n\n```typescript\nimport { VocalStack, VocalStackError } from '@vocalstack/sdk';\n\n// 429s, and 503s on calls that are safe to repeat, are retried after Retry-After.\nconst vs = new VocalStack({ baseUrl: 'https://api.vocalstack.com', apiKey: process.env.VOCALSTACK_API_KEY, maxRetries: 4 });\n\ntry {\n  // The key makes this create safe to retry: a repeat returns the first broadcast.\n  const broadcast = await vs.broadcasts.create({ name: 'Town hall' }, { idempotencyKey: crypto.randomUUID() });\n  console.log(broadcast.shareUrl);\n} catch (err) {\n  if (!(err instanceof VocalStackError)) throw err;\n  switch (err.code) {\n    case 'insufficient_credit':\n    case 'quota_exceeded': {\n      const d = err.details as { metric?: string; balance?: number; required?: number; topUpUrl?: string } | undefined;\n      console.error(err.message, d?.metric ?? '', d?.balance ?? '', d?.required ?? '', d?.topUpUrl ?? '');\n      break;\n    }\n    case 'rate_limited':\n    case 'unavailable':\n      console.error(`still ${err.code} after the retries; try again in ${err.retryAfter ?? 1} s`);\n      break;\n    default:\n      console.error(err.status, err.code, err.message, err.headers?.get('x-request-id') ?? '');\n  }\n}\n\nconst billing = await vs.billing.get();\nconsole.log(billing.plan?.name, `$${billing.balance} of credit`, `${billing.limits?.concurrentTranscriptions ?? '∞'} at once`);\n```\n\n**Python SDK**\n\n```python\nimport asyncio\nimport os\nimport uuid\n\nfrom vocalstack import VocalStack, VocalStackError\n\n\nasync def main() -> None:\n    # 429s, and 503s on calls that are safe to repeat, are retried after Retry-After.\n    async with VocalStack(\"https://api.vocalstack.com\", api_key=os.environ[\"VOCALSTACK_API_KEY\"], max_retries=4) as vs:\n        try:\n            # The key makes this create safe to retry: a repeat returns the first broadcast.\n            broadcast = await vs.broadcasts.create(name=\"Town hall\", idempotency_key=str(uuid.uuid4()))\n            print(broadcast.share_url)\n        except VocalStackError as e:\n            if e.code in (\"insufficient_credit\", \"quota_exceeded\"):\n                print(e.message, e.details)\n            elif e.code in (\"rate_limited\", \"unavailable\"):\n                print(f\"still {e.code} after the retries; try again in {e.retry_after or 1} s\")\n            else:\n                print(e.status, e.code, e.message)\n\n        billing = await vs.billing.get()\n        print(billing.plan, f\"${billing.balance} of credit\", billing.limits)\n\n\nasyncio.run(main())\n```\n\n## AI agents (MCP)\n\nThe API is also an MCP server at `https://api.vocalstack.com/v1/mcp` (Streamable HTTP): an agent can create transcriptions, read transcripts, search and run broadcasts with the same API keys, or with OAuth through `https://api.vocalstack.com/.well-known/oauth-protected-resource/v1/mcp`.\n\n**Claude Code**\n\n```sh\nclaude mcp add --transport http vocalstack https://api.vocalstack.com/v1/mcp\n```\n\n**Cursor and other clients** (`mcp.json`)\n\n```json\n{\"mcpServers\":{\"vocalstack\":{\"url\":\"https://api.vocalstack.com/v1/mcp\"}}}\n```\n\nEach SDK also ships an Agent Skill that teaches a coding agent to use it: `node_modules/@vocalstack/sdk/SKILL.md` (TypeScript) and `vocalstack/SKILL.md` in site-packages (Python). `https://api.vocalstack.com/llms.txt` lists the guides (each as Markdown at `https://api.vocalstack.com/docs/guides/<slug>.md`) and every operation, and `https://api.vocalstack.com/llms-full.txt` adds the guides whole, parameters, auth and scopes.\n\n## What is what\n\n- **Realtime session**: a stateless token for one WebSocket. `POST /v1/realtime/sessions` returns a signed `streamUrl`; nothing is stored. Audio goes in as binary frames and transcript events come out (`x-websocket.realtimeSession`). The API uses the word for nothing else.\n- **Transcription**: a stored transcript of a file, a link, or something captured as it is spoken (a microphone, a stream, an RTMP encoder: `live: true`). `POST /v1/transcriptions` (a file or a link) and `POST /v1/transcriptions/live`.\n- **Broadcast**: the link listeners open (`code`). It is fed by the live transcriptions attached to it, or records one of its own audio sources. `POST /v1/broadcasts`.\n- **Recording**: one take, always inside a transcription (`transcriptionId`); a recording started on a broadcast is made in a live transcription attached to it. Its transcript, audio, translations, voice and summary hang off `/v1/recordings/{id}`.\n- **Job**: the server's work on a file or link recording: `GET /v1/recordings/{id}/job`.\n\nHosts authenticate with an API key (`vs_key_…`) or a signed-in person's access token: `Authorization: Bearer …`. Listeners authenticate with nothing (a public broadcast), a share link's code (`shareCode=`, from `transcriptions.createShareLink` or `broadcasts.createShareLink`), or the listener token VocalStack's own listener page holds for a private broadcast (`listenerToken=`). Each operation names its auth mode and key scopes in `x-vocalstack-auth` and `x-vocalstack-scopes`. Every operation is open to every plan: a transcription spends the account's credit per second of audio (`GET /v1/billing`), and a creation with no credit left is refused `402 insufficient_credit`.\n\n## Streaming in 30 seconds\n\n1. `POST /v1/realtime/sessions` → `{ sessionId, streamUrl, expiresAt }`. `streamUrl` embeds a signed token; `expiresAt` is a connect-by deadline only.\n2. Open a WebSocket to `streamUrl`. You may receive `session.loading` heartbeats while a cold machine spins up; wait for `session.ready`.\n3. Send binary frames of raw PCM (`pcm_f32le`, 16 kHz, mono — exactly `capabilities.audio`). To transcribe a **file** instead, create the realtime session with `input: { kind: \"file\" }` and send the file's own bytes (mp3, wav, flac, ogg, m4a…) — the server decodes it and transcribes at full speed, reporting `transcript.progress` as it goes.\n4. Receive `transcript.committed` (immutable, gapless `seq`, always includes word timings), `transcript.draft` (**full replacement** of the live tail — never append drafts), and `transcript.words` frames.\n5. At any time, send `{\"type\":\"session.update\", ...}` to change the `latencyMode`, `spokenLanguage`, `speakerLabels` and/or `translate` languages mid-stream; omitted fields are unchanged and already-delivered transcript is not re-translated.\n6. Send `{\"type\":\"eof\"}` at end of source; the server flushes final commits, sends `session.done`, and closes.\n\nThere is no resume: a dropped connection ends the realtime session — reconnect = create a new one. A saturated fleet surfaces as a hanging connect or a bare 503, not an error envelope; clients synthesize `capacity_exceeded` from a connect/ready deadline (the SDKs default to 30 s).\n\n## Stored transcriptions and broadcasts\n\n**Publishing:** `POST /v1/transcriptions/{id}/recordings` (or `/v1/broadcasts/{id}/recordings`) → a `streamUrl`; connect and send PCM exactly as on a realtime session. Control (pause/resume/stop/settings) is HTTP, never in-band. A live transcription whose source is a stream URL has no microphone behind it: the server opens the URL when recording starts and publishes it itself; the grant then has no `streamUrl`, and `recording.ingest` and the `stream.ingest` frame report `connecting` → `streaming` → `reconnecting` → `stopped` | `failed`.\n\n**Listening:** `WS /v1/recordings/{id}/subscribe?lang=es[&voice=F3][&listenerToken=…]` → first frame `subscribe.ready {lastSeq}`; then `GET /v1/recordings/{id}/transcript?lang=es` and discard buffered frames with `seq <= lastSeq`. Every durable frame carries a gapless `seq`; on a jump, re-snapshot with `after=<cursor>` (`x-websocket.subscribe`). `WS /v1/broadcasts/{id}/events` tells a waiting listener when the broadcast goes live, and `WS /v1/events` tells an owner's dashboard when its lists changed.\n\n## WebSocket frames\n\nThe sockets the API opens, and every frame on them. Text frames are JSON with a `type`; `?` marks an optional field. The SDKs parse all of this for you (`RealtimeSession`, `vs.recordings.subscribe`).\n\n### accountEvents: WS /v1/events\n\nThe owner's dashboard: `events.ready` once the socket is listening, then `lists.changed` whenever a list of transcriptions or broadcasts of any principal the caller may act for is out of date — a recording changed state, a transcription settled, a title was written, or the owner changed something from another device. It says that, not what: re-read `GET /v1/transcriptions` and `GET /v1/broadcasts`. It replaces polling those lists. A user may hold a few of these at once (5 by default): opening one more closes their oldest with code 4001. One is closed with code 4002 after a day. After either, do not reconnect at once — a client that does takes its place straight back; reconnect when the user next uses that screen.\n\n**Query:** access_token (a browser cannot set a header on a socket)\n\n**The server sends**\n\n- `events.ready`\n- `lists.changed`\n\n### broadcastEvents: WS /v1/broadcasts/{id}/events\n\nWhat a screen waiting on a broadcast needs: when a recording starts in it (`recording.started`: it is live), when that recording ends (`recording.ended`), a file job's progress (`job.progress`), and a pulled stream's state (`stream.ingest`). Each frame's `transcriptionId` is the transcription the recording is made in: on a broadcast's socket, the live transcription feeding it.\n\n**Query:** listenerToken / shareCode / access_token, as on subscribe\n\n**The server sends**\n\n- `recording.started` { `recordingId`: string, `transcriptionId`: string } — Broadcast or transcription topic: a recording started in it (it is live).\n- `recording.ended` { `recordingId`: string, `transcriptionId`: string } — Broadcast or transcription topic: the recording that was live in it ended (on a broadcast's topic, also: the live transcription making it was detached).\n- `job.progress` { `error?`: string, `fetchTotalBytes?`: integer, `fetchedBytes?`: integer, `pct`: integer, `phase?`: string, `phasePct?`: integer, `preview?`: boolean, `processedS`: number, `recordingId`: string, `segmentsDone`: integer, `segmentsTotal?`: integer, `state`: JobState, `totalS?`: number, `transcriptionId`: string } — A file / URL transcription job moved (both topics: the recording's and its broadcast's or transcription's).\n- `stream.ingest` { `detected?`: string, `error?`: string, `failures?`: integer, `lastAudioAt?`: string, `recordingId`: string, `state`: IngestState, `transcriptionId`: string, `url`: string } — A stream source's server-side publisher moved (both topics): what the server is doing with the URL — connecting, streaming, reconnecting after a drop, stopped, or failed with `error`.\n\n### realtimeSession: WS /v1/realtime/sessions/{token}/socket\n\nConnect to the `streamUrl` a realtime session returned (treat it as opaque).\n\n**Binary frames:** input.kind=live: raw PCM per SessionOptions.audio (pcm_f32le, 16 kHz, mono). input.kind=file: the encoded file's own bytes, decoded server-side\n\n**You send**\n\n- `eof`\n- `cancel`\n- `session.update` { `latencyMode?`: LatencyMode, `llmFix?`: boolean, `speakerLabels?`: SpeakerLabels, `spokenLanguage?`: string, `translate?`: array, `voice?`: array, `voiceByLang?`: object } — Options update during a realtime session; fields left out are unchanged.\n- `clock` { `pcm`: integer, `wall`: number } — Capture clock heartbeat (live sources): `pcm` = cumulative samples the client captured (16kHz-equivalent), `wall` = its monotonic seconds since capture start (excluding system sleep).\n\n**The server sends**\n\n- `session.loading` { `stage`: LoadingStage } — A heartbeat while the machine gets ready (`stage`: `download`, then `model`); each one re-arms the client's ready deadline.\n- `session.ready` — The realtime session is ready: send audio from now on.\n- `transcript.committed` { `segment`: CommittedSegment } — One final segment, with word timings, numbered by `seq` (0, 1, 2…, gapless).\n- `transcript.draft` { `segments`: DraftSegment[] } — The live tail: the words heard since the last commit, still changing.\n- `transcript.words` { `words`: Word[] } — Word timings for the live tail as they firm up (`events.words`); ephemeral, carries no `seq`.\n- `transcript.amended` { `fixes?`: Fix[], `scriptWords?`: integer[], `seq`: integer, `speaker`: integer, `speakerConfidence?`: number, `tail?`: AmendTail, `text`: string } — An already-delivered committed segment was rewritten by the engine (a later decode with more context corrected a word, punctuation or the speaker).\n- `transcript.latency` { `latencyS`: number, `seq`: integer } — A delivered segment's latency, measured again now that the engine's correction passes are done with it.\n- `transcript.speaker` { `seq`: integer, `speaker`: integer, `speakerConfidence?`: number, `verdict`: string } — A tentative speaker label on a delivered segment was settled: `verdict` is \"confirmed\" (later evidence agrees), \"amended\" (the segment was re-attributed; the `transcript.amended` frame with the new speaker precedes this) or \"unverified\" (no better evidence arrived, the label stands).\n- `transcript.speakerGender` { `confidence?`: number, `gender`: string, `speaker`: integer } — A speaker's gender, as the pitch band heard it, once it settles (`casting::roster`).\n- `transcript.speakerIdentified` { `color?`: string, `confidence?`: number, `name`: string, `speaker`: integer, `tone?`: boolean, `voiceId?`: string } — A diarized speaker turned out to be somebody the caller has a saved voice for (`speaker_id`).\n- `transcript.sentence` { `sentence`: SentenceSegment } — Full-replacement upsert of one sentence, keyed by `sentenceId`.\n- `transcript.translated` { `seq`: integer, `sources?`: object, `translations`: object }\n- `transcript.voice` { `audio?`: string, `cloned?`: boolean, `durationS`: number, `format`: string, `generation`: integer, `lang`: string, `latencyS?`: number, `part?`: integer, `parts?`: integer, `queueWaitS?`: number, `sentenceId`: integer, `speakerToAudioS?`: number, `synthS`: number, `translationLatencyS?`: number, `url?`: string, `voice?`: string, `voiceKey?`: string } — A spoken rendering of one definitive sentence (`SessionOptions::voice`).\n- `transcript.voiceStatus` { `detail`: string, `lang`: string, `state`: VoiceStatusState, `voiceKey?`: string } — Why a listener who picked a voice is not hearing it yet — or will not hear it at all.\n- `transcript.progress` { `processedS`: number, `totalS?`: number }\n- `stream.pull` { `bytesIn`: integer, `detected?`: string, `error?`: string, `failures`: integer, `mediaS`: number, `programAtMs?`: integer, `sequenceOffsetMs?`: integer, `state`: PullState } — `input.kind = \"stream\"`: how the engine's pull of the stream is going.\n- `source.progress` { `bytes`: integer, `doneS`: number, `phase`: string, `total?`: integer, `totalS?`: number } — `input.prepare`: the source on its way in.\n- `source.prepared` { `durationS?`: number, `peaks?`: array } — `input.prepare`: the source is stored and ready to transcribe.\n- `session.done` { `reason`: DoneReason, `stats`: SessionStats }\n- `error` { `code`: ErrorCode, `message`: string, `retryable`: boolean }\n\n**Refused upgrades**\n\nRejected upgrades carry the ApiErrorBody envelope over HTTP.\n\n- 401: invalid_token\n- 409: invalid_token (same-machine replay)\n- 410: session_expired\n\n**Rules**\n\n- transcript.draft is a FULL REPLACEMENT of the live tail (replace-not-append)\n- segment.seq is per realtime session, monotonic, 0-based, gapless; ordering only\n- single total order per realtime session; the draft tail begins at the last committed segment's end\n- committed segments always include words; their text/speaker may later be corrected by a transcript.amended frame (same seq) — apply it in place\n- sentences.enabled: transcript.sentence is a FULL REPLACEMENT upsert keyed by sentenceId (status open -> closed; dropped = discard); seqs lists the committed segments it covers\n- sentences.enabled + translate: translations attach to transcript.sentence (provisional=true while open, replaced on close) instead of transcript.translated\n- events.drafts / events.words govern live frames only\n- clients MUST ignore unknown server frame types; servers ignore unknown client text frames\n- no resume: a dropped connection ends the realtime session; reconnect = create a new one\n- backpressure: commits are never dropped; drafts/words coalesce-latest; slow consumers are closed with slow_consumer\n- saturation may surface OUTSIDE this envelope as a hanging connect or bare 503 from the load balancer; clients synthesize capacity_exceeded from a connect/ready deadline\n- file input (input.kind=file): binary frames carry the encoded file; transcription starts at eof, so no transcript frames arrive before it\n- file input emits transcript.progress on a timer (also the keep-alive); maxDurationS applies to the file's DECODED duration — a longer-than-allowed file is refused with invalid_audio, or truncated with session.done/max_duration when the container declared no duration\n- file input defaults events.drafts to false (a draft previews a live tail that a file does not have); ask explicitly to receive them\n- session.done carries stats {audioS, processingS, realtimeFactor} measured server-side; processingS excludes a file's upload and decode setup, and the object is the extension point for future metadata about the realtime session\n\n### subscribe: WS /v1/recordings/{id}/subscribe\n\nFollow a recording: first frame `subscribe.ready {lastSeq}`, then `GET /v1/recordings/{id}/transcript` and discard buffered frames with `seq <= lastSeq`. `voice` picks which voice speaks the language for this listener; two listeners on the same (lang, voice) share one synthesis, so changing voice means reopening the socket. Send `{\"type\":\"subscribe.update\",\"lang\":\"ja\"}` to switch language, `{\"type\":\"subscribe.update\",\"audio\":true}` to receive live Opus.\n\n**Query:** lang (translation target — through a share link it needs the link's allowTranslate, else the upgrade is refused 403 and a mid-socket subscribe.update{lang} closes the socket with 1008), voice (which voice speaks it — a voice id from GET /v1/voices/available?lang=…; omit for the recording's settings.voiceByLang pin, else the engine's default), listenerToken / shareCode / access_token, role, device\n\n**Binary frames:** Ogg/Opus pages of the original audio, only after subscribe.update{audio:true}; droppable under backpressure\n\n**You send**\n\n- `subscribe.update` { `audio?`: boolean, `lang?`: string } — Switch target language (a property of this stream).\n- `ping`\n\n**The server sends**\n\n- `session.loading` { `stage`: LoadingStage } — A heartbeat while the machine gets ready (`stage`: `download`, then `model`); each one re-arms the client's ready deadline.\n- `session.ready` — The realtime session is ready: send audio from now on.\n- `transcript.committed` { `segment`: CommittedSegment } — One final segment, with word timings, numbered by `seq` (0, 1, 2…, gapless).\n- `transcript.draft` { `segments`: DraftSegment[] } — The live tail: the words heard since the last commit, still changing.\n- `transcript.words` { `words`: Word[] } — Word timings for the live tail as they firm up (`events.words`); ephemeral, carries no `seq`.\n- `transcript.amended` { `fixes?`: Fix[], `scriptWords?`: integer[], `seq`: integer, `speaker`: integer, `speakerConfidence?`: number, `tail?`: AmendTail, `text`: string } — An already-delivered committed segment was rewritten by the engine (a later decode with more context corrected a word, punctuation or the speaker).\n- `transcript.latency` { `latencyS`: number, `seq`: integer } — A delivered segment's latency, measured again now that the engine's correction passes are done with it.\n- `transcript.speaker` { `seq`: integer, `speaker`: integer, `speakerConfidence?`: number, `verdict`: string } — A tentative speaker label on a delivered segment was settled: `verdict` is \"confirmed\" (later evidence agrees), \"amended\" (the segment was re-attributed; the `transcript.amended` frame with the new speaker precedes this) or \"unverified\" (no better evidence arrived, the label stands).\n- `transcript.speakerGender` { `confidence?`: number, `gender`: string, `speaker`: integer } — A speaker's gender, as the pitch band heard it, once it settles (`casting::roster`).\n- `transcript.speakerIdentified` { `color?`: string, `confidence?`: number, `name`: string, `speaker`: integer, `tone?`: boolean, `voiceId?`: string } — A diarized speaker turned out to be somebody the caller has a saved voice for (`speaker_id`).\n- `transcript.sentence` { `sentence`: SentenceSegment } — Full-replacement upsert of one sentence, keyed by `sentenceId`.\n- `transcript.translated` { `seq`: integer, `sources?`: object, `translations`: object }\n- `transcript.voice` { `audio?`: string, `cloned?`: boolean, `durationS`: number, `format`: string, `generation`: integer, `lang`: string, `latencyS?`: number, `part?`: integer, `parts?`: integer, `queueWaitS?`: number, `sentenceId`: integer, `speakerToAudioS?`: number, `synthS`: number, `translationLatencyS?`: number, `url?`: string, `voice?`: string, `voiceKey?`: string } — A spoken rendering of one definitive sentence (`SessionOptions::voice`).\n- `transcript.voiceStatus` { `detail`: string, `lang`: string, `state`: VoiceStatusState, `voiceKey?`: string } — Why a listener who picked a voice is not hearing it yet — or will not hear it at all.\n- `transcript.progress` { `processedS`: number, `totalS?`: number }\n- `stream.pull` { `bytesIn`: integer, `detected?`: string, `error?`: string, `failures`: integer, `mediaS`: number, `programAtMs?`: integer, `sequenceOffsetMs?`: integer, `state`: PullState } — `input.kind = \"stream\"`: how the engine's pull of the stream is going.\n- `source.progress` { `bytes`: integer, `doneS`: number, `phase`: string, `total?`: integer, `totalS?`: number } — `input.prepare`: the source on its way in.\n- `source.prepared` { `durationS?`: number, `peaks?`: array } — `input.prepare`: the source is stored and ready to transcribe.\n- `session.done` { `reason`: DoneReason, `stats`: SessionStats }\n- `error` { `code`: ErrorCode, `message`: string, `retryable`: boolean }\n- `subscribe.ready` { `instance`: string, `lastSeq`: integer, `recordingId`: string, `state`: RecordingState } — First frame after the upgrade: where the live sequence stands, so the client fetches the snapshot up to `lastSeq` and discards buffered frames at or below it.\n- `recording.state` { `mediaS?`: number, `reason?`: string, `recordingId`: string, `state`: RecordingState } — Recording state changed (LIVE ⇄ PAUSED → ENDED → FINALIZED).\n- `recording.settings` { `recordingId`: string, `settings`: object } — Capture settings changed mid-recording (mode, language, labels).\n- `recording.silence` { `pausesInS`: number, `recordingId`: string, `silentS`: number } — The silence watchdog will pause the recording soon.\n- `recording.expiring` { `endsInS`: number, `recordingId`: string } — The max-duration cutoff is near.\n- `transcript.corrected` { `actor?`: Actor, `from`: string, `reverted`: boolean, `sentenceId?`: integer, `sentenceText?`: string, `seq`: integer, `text`: string, `to`: string, `wordIdx`: integer, `words?`: array } — A human correction was applied — distinct from an engine amend.\n- `transcript.speakerGender` { `confidence?`: number, `gender`: string, `speaker`: integer } — A speaker's gender, once the pitch band settles on it (`casting::roster`).\n- `transcript.speakerIdentified` { `color?`: string, `confidence?`: number, `name`: string, `speaker`: integer, `voiceId?`: string } — A speaker and the saved voice they turn out to be.\n- `transcript.speakerNamed` { `name?`: string, `speaker`: integer } — A per-recording speaker label.\n- `recording.started` { `recordingId`: string, `transcriptionId`: string } — Broadcast or transcription topic: a recording started in it (it is live).\n- `job.progress` { `error?`: string, `fetchTotalBytes?`: integer, `fetchedBytes?`: integer, `pct`: integer, `phase?`: string, `phasePct?`: integer, `preview?`: boolean, `processedS`: number, `recordingId`: string, `segmentsDone`: integer, `segmentsTotal?`: integer, `state`: JobState, `totalS?`: number, `transcriptionId`: string } — A file / URL transcription job moved (both topics: the recording's and its broadcast's or transcription's).\n- `stream.ingest` { `detected?`: string, `error?`: string, `failures?`: integer, `lastAudioAt?`: string, `recordingId`: string, `state`: IngestState, `transcriptionId`: string, `url`: string } — A stream source's server-side publisher moved (both topics): what the server is doing with the URL — connecting, streaming, reconnecting after a drop, stopped, or failed with `error`.\n- `recording.ended` { `recordingId`: string, `transcriptionId`: string } — Broadcast or transcription topic: the recording that was live in it ended (on a broadcast's topic, also: the live transcription making it was detached).\n- `audience` { `languages`: object, `listeners`: integer } — Admin subscribers: the audience aggregate, every few seconds.\n- `subscribe.gap` { `expected`: integer, `got`: integer } — The frame sequence jumped; the client should re-snapshot from its cursor.\n\n**Rules**\n\n- durable frames carry seq (gapless per recording); ephemeral frames (transcript.words, audio.chunk) carry none; recordings emit no transcript.draft\n- a frame withheld from a role still consumes its seq: listeners receive {type:'noop', seq} in its place, so a real jump always means a missed frame\n- listeners receive translations filtered to their lang; suppressed lines arrive with text='' and suppressed=true\n- transcript.voice is filtered to the listener's (lang, voice): a clip reaches only subscribers whose voice= matches its voiceKey (both empty = the engine's default), and one clip serves every listener on that pair — the voice is chosen per listener, the synthesis is paid once\n- a long line is spoken in pieces (part, parts): the engine cuts it at its clause boundaries so the first piece can be heard while the rest are still being made. The pieces share the sentence id, arrive in order, and are played back to back — one line on screen, with one translation and one place in the reading. part=0/parts=1 is a line spoken whole\n- transcript.voiceStatus is filtered to the same (lang, voice) pair as transcript.voice: state 'preparing' means that voice's assets are downloading and the sentences spoken meanwhile are held and will arrive as clips; 'unavailable' means it cannot be spoken here and detail says why. A listener never hears a different voice than the one asked for\n- admin subscribers (owning-principal members) receive the full frame set plus audience aggregates\n- recording.state announces LIVE ⇄ PAUSED → ENDED → FINALIZED; listeners are not disconnected at ENDED\n- transcript.corrected is a human edit; transcript.amended is an engine rewrite — clients clear uncertainty marks only on the former\n\n### transcriptionEvents: WS /v1/transcriptions/{id}/events\n\nWhat a screen waiting on a transcription needs: when a recording starts in it (`recording.started`: it is live), when that recording ends (`recording.ended`), a file job's progress (`job.progress`), and a pulled stream's state (`stream.ingest`). Each frame's `transcriptionId` is the transcription the recording is made in.\n\n**Query:** listenerToken / shareCode / access_token, as on subscribe\n\n**The server sends**\n\n- `recording.started` { `recordingId`: string, `transcriptionId`: string } — Broadcast or transcription topic: a recording started in it (it is live).\n- `recording.ended` { `recordingId`: string, `transcriptionId`: string } — Broadcast or transcription topic: the recording that was live in it ended (on a broadcast's topic, also: the live transcription making it was detached).\n- `job.progress` { `error?`: string, `fetchTotalBytes?`: integer, `fetchedBytes?`: integer, `pct`: integer, `phase?`: string, `phasePct?`: integer, `preview?`: boolean, `processedS`: number, `recordingId`: string, `segmentsDone`: integer, `segmentsTotal?`: integer, `state`: JobState, `totalS?`: number, `transcriptionId`: string } — A file / URL transcription job moved (both topics: the recording's and its broadcast's or transcription's).\n- `stream.ingest` { `detected?`: string, `error?`: string, `failures?`: integer, `lastAudioAt?`: string, `recordingId`: string, `state`: IngestState, `transcriptionId`: string, `url`: string } — A stream source's server-side publisher moved (both topics): what the server is doing with the URL — connecting, streaming, reconnecting after a drop, stopped, or failed with `error`.\n\n","title":"VocalStack API","version":"1.0.0-alpha.0"},"jsonSchemaDialect":"https://json-schema.org/draft/2020-12/schema","openapi":"3.1.0","paths":{"/v1/billing":{"get":{"description":"The acting account's plan, rates (dollars an hour of audio), credit balance, whether it runs on in a grace period below zero (`inGrace`), whether a new transcription would start now and what would refuse it (`canStart`, `startBlock`), the billing period and the one before, the monthly credit's use, the counted limits in use, the subscription (next payment, a scheduled interval switch, past due), auto top-up with its last attempt, the low-balance alert, a payment still being processed, the default card, a scheduled rate change, the retention deadline after a downgrade, the plan's limits and whether it has paid. A creation that would overdraw the balance is refused 402 `insufficient_credit`; `topUpUrl` is where to add credit. `enabled: false` means this server does not bill.","operationId":"billing.get","responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BillingView"}}},"description":"OK"}},"security":[{"bearerAuth":[]}],"summary":"Get the account's billing","tags":["billing"],"x-codeSamples":[{"label":"TypeScript SDK","lang":"typescript","source":"import { VocalStack } from '@vocalstack/sdk';\n\nconst vs = new VocalStack({ baseUrl: 'https://api.vocalstack.com', apiKey: process.env.VOCALSTACK_API_KEY });\n\nconst billing = await vs.billing.get();\nconsole.log(billing.plan?.name, billing.balance, billing.limits?.concurrentTranscriptions);\n"},{"label":"Python SDK","lang":"python","source":"import asyncio\nimport os\n\nfrom vocalstack import VocalStack\n\n\nasync def main() -> None:\n    async with VocalStack(\"https://api.vocalstack.com\", api_key=os.environ[\"VOCALSTACK_API_KEY\"]) as vs:\n        billing = await vs.billing.get()\n        print(billing.plan, billing.balance)\n\n\nasyncio.run(main())\n"},{"label":"curl","lang":"shell","source":"curl \"https://api.vocalstack.com/v1/billing\" \\\n  -H \"Authorization: Bearer $VOCALSTACK_API_KEY\""}],"x-vocalstack-auth":"userOrKey","x-vocalstack-scopes":["read"]}},"/v1/billing/auto-top-up":{"put":{"description":"Turn auto top-up on (`threshold` and `amount`: any whole-cent values inside the plan list's `autoTopUp.thresholdRange` and `amountRange`; when the balance falls to the threshold, the amount is charged to the saved payment method, at most a few times a day) or off. `409 payment_method_required` until the account has a saved payment method. The consent (time, hashed address, terms version) is recorded.","operationId":"billing.setAutoTopUp","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AutoTopUpSettings"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AutoTopUpView"}}},"description":"The settings as stored."}},"security":[{"bearerAuth":[]}],"summary":"Set auto top-up","tags":["billing"],"x-codeSamples":[{"label":"TypeScript SDK","lang":"typescript","source":"import { VocalStack } from '@vocalstack/sdk';\n\nconst vs = new VocalStack({ baseUrl: 'https://api.vocalstack.com', apiKey: process.env.VOCALSTACK_API_KEY });\n\nconst autoTopUp = await vs.billing.setAutoTopUp({ enabled: true, threshold: 5, amount: 25 });\nconsole.log(autoTopUp.enabled);\n"},{"label":"Python SDK","lang":"python","source":"import asyncio\nimport os\n\nfrom vocalstack import VocalStack\n\n\nasync def main() -> None:\n    async with VocalStack(\"https://api.vocalstack.com\", api_key=os.environ[\"VOCALSTACK_API_KEY\"]) as vs:\n        auto_top_up = await vs.billing.set_auto_top_up(enabled=True, threshold=5, amount=25)\n        print(auto_top_up.enabled)\n\n\nasyncio.run(main())\n"},{"label":"curl","lang":"shell","source":"curl -X PUT \"https://api.vocalstack.com/v1/billing/auto-top-up\" \\\n  -H \"Authorization: Bearer $VOCALSTACK_API_KEY\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"enabled\":true}'"}],"x-errors":["invalid_options","payment_method_required","forbidden"],"x-vocalstack-auth":"user","x-vocalstack-scopes":[]}},"/v1/billing/auto-top-up/retry":{"post":{"description":"Charge the configured auto top-up amount to the saved payment method now, whatever the balance (after a declined attempt, once the card is fixed). Counts against the day's cap: `429 rate_limited` when it is used. `409 auto_top_up_off` when auto top-up is off. Returns the auto top-up with its `last` attempt (`paid`, `declined` or `pending`).","operationId":"billing.retryAutoTopUp","responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AutoTopUpView"}}},"description":"The auto top-up, `last` the attempt just made."}},"security":[{"bearerAuth":[]}],"summary":"Run auto top-up now","tags":["billing"],"x-codeSamples":[{"label":"TypeScript SDK","lang":"typescript","source":"import { VocalStack } from '@vocalstack/sdk';\n\nconst vs = new VocalStack({ baseUrl: 'https://api.vocalstack.com', apiKey: process.env.VOCALSTACK_API_KEY });\n\nconst autoTopUp = await vs.billing.retryAutoTopUp();\nconsole.log(autoTopUp.last?.outcome); // paid, declined or pending\n"},{"label":"Python SDK","lang":"python","source":"import asyncio\nimport os\n\nfrom vocalstack import VocalStack\n\n\nasync def main() -> None:\n    async with VocalStack(\"https://api.vocalstack.com\", api_key=os.environ[\"VOCALSTACK_API_KEY\"]) as vs:\n        auto_top_up = await vs.billing.retry_auto_top_up()\n        print(auto_top_up.last.outcome if auto_top_up.last else None)  # paid, declined or pending\n\n\nasyncio.run(main())\n"},{"label":"curl","lang":"shell","source":"curl -X POST \"https://api.vocalstack.com/v1/billing/auto-top-up/retry\" \\\n  -H \"Authorization: Bearer $VOCALSTACK_API_KEY\""}],"x-errors":["auto_top_up_off","rate_limited","invalid_state","forbidden","unavailable"],"x-vocalstack-auth":"user","x-vocalstack-scopes":[]}},"/v1/billing/charges":{"get":{"description":"The account's per-recording charges, newest first, with the transcription's name: open while the audio runs (metered every minute), settled once the recording ends, void when waived. Page with `cursor`.","operationId":"billing.charges","parameters":[{"description":"The `nextCursor` of the previous page.","in":"query","name":"cursor","schema":{"type":"string"}},{"in":"query","name":"limit","schema":{"default":50,"maximum":200,"minimum":1,"type":"integer"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChargePage"}}},"description":"OK"}},"security":[{"bearerAuth":[]}],"summary":"List the charges","tags":["billing"],"x-codeSamples":[{"label":"TypeScript SDK","lang":"typescript","source":"import { VocalStack } from '@vocalstack/sdk';\n\nconst vs = new VocalStack({ baseUrl: 'https://api.vocalstack.com', apiKey: process.env.VOCALSTACK_API_KEY });\n\nconst page = await vs.billing.charges();\nfor (const charge of page.charges) console.log(charge.name, charge.state, charge.amount);\n"},{"label":"Python SDK","lang":"python","source":"import asyncio\nimport os\n\nfrom vocalstack import VocalStack\n\n\nasync def main() -> None:\n    async with VocalStack(\"https://api.vocalstack.com\", api_key=os.environ[\"VOCALSTACK_API_KEY\"]) as vs:\n        page = await vs.billing.charges()\n        for charge in page.charges:\n            print(charge.name, charge.state, charge.amount)\n\n\nasyncio.run(main())\n"},{"label":"curl","lang":"shell","source":"curl \"https://api.vocalstack.com/v1/billing/charges\" \\\n  -H \"Authorization: Bearer $VOCALSTACK_API_KEY\""}],"x-errors":["invalid_options"],"x-vocalstack-auth":"userOrKey","x-vocalstack-scopes":["read"]}},"/v1/billing/checkouts":{"post":{"description":"Start a payment at the payment provider: `{kind: \"topup\", amount}` adds credit (any whole-cent amount inside the plan list's `topUpRange`), `{kind: \"subscribe\", planId, interval}` starts a subscription (`month` or `year`); `promoCode` adds a code's bonus credit to a top-up or its discount to a subscription's first payment (`404 promo_code_invalid` when it does not apply). Both need `acceptTerms: true` and `waiveWithdrawal: true` (credit is not refundable; performance starts at once) or the request is `422 invalid_body`. Send the customer to `url`; the provider returns them to the app's billing page with `?checkout=<id>`. A signed-in person only (for an organization, its owner or admin), never an API key. `409 invalid_state` with an active subscription (cancel it first) or a plan billed by hand. Idempotent with `Idempotency-Key`.","operationId":"billing.createCheckout","parameters":[{"description":"Makes a retry safe: a request repeated with the same key and the same body within 24 hours returns the first response (with `Idempotent-Replayed: true`) instead of running again. The same key with a different request is 422 `idempotency_key_reused`; a retry while the first is still running is 409 `conflict`. Only a successful (2xx) response is remembered. Keys are per account.","in":"header","name":"Idempotency-Key","required":false,"schema":{"maxLength":255,"minLength":1,"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateCheckout"}}},"required":true},"responses":{"201":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CheckoutView"}}},"description":"The checkout: send the customer to `url`."}},"security":[{"bearerAuth":[]}],"summary":"Open a checkout","tags":["billing"],"x-codeSamples":[{"label":"TypeScript SDK","lang":"typescript","source":"import { VocalStack } from '@vocalstack/sdk';\n\nconst vs = new VocalStack({ baseUrl: 'https://api.vocalstack.com', apiKey: process.env.VOCALSTACK_API_KEY });\n\nconst checkout = await vs.billing.createCheckout({ kind: 'topup', amount: 25, acceptTerms: true, waiveWithdrawal: true });\nconsole.log(checkout.checkoutId, checkout.url); // send the customer to url\n"},{"label":"Python SDK","lang":"python","source":"import asyncio\nimport os\n\nfrom vocalstack import VocalStack\n\n\nasync def main() -> None:\n    async with VocalStack(\"https://api.vocalstack.com\", api_key=os.environ[\"VOCALSTACK_API_KEY\"]) as vs:\n        checkout = await vs.billing.create_checkout(kind=\"topup\", amount=25, accept_terms=True, waive_withdrawal=True)\n        print(checkout.checkout_id, checkout.url)  # send the customer to url\n\n\nasyncio.run(main())\n"},{"label":"curl","lang":"shell","source":"curl -X POST \"https://api.vocalstack.com/v1/billing/checkouts\" \\\n  -H \"Authorization: Bearer $VOCALSTACK_API_KEY\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"kind\":\"<kind>\"}'"}],"x-errors":["invalid_body","invalid_options","invalid_state","forbidden","not_found","promo_code_invalid","unavailable","idempotency_key_reused"],"x-vocalstack-auth":"user","x-vocalstack-scopes":[]}},"/v1/billing/checkouts/{id}":{"get":{"description":"A checkout's status, for the page the customer comes back to: by its `checkoutId` or the provider's id (`?checkout=` on the return URL). `succeeded` once the payment landed (the credit or the subscription is then in `GET /v1/billing`).","operationId":"billing.getCheckout","parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CheckoutView"}}},"description":"OK"}},"security":[{"bearerAuth":[]}],"summary":"Get a checkout","tags":["billing"],"x-codeSamples":[{"label":"TypeScript SDK","lang":"typescript","source":"import { VocalStack } from '@vocalstack/sdk';\n\nconst vs = new VocalStack({ baseUrl: 'https://api.vocalstack.com', apiKey: process.env.VOCALSTACK_API_KEY });\n\nconst checkout = await vs.billing.getCheckout('ck_…');\nconsole.log(checkout.status);\n"},{"label":"Python SDK","lang":"python","source":"import asyncio\nimport os\n\nfrom vocalstack import VocalStack\n\n\nasync def main() -> None:\n    async with VocalStack(\"https://api.vocalstack.com\", api_key=os.environ[\"VOCALSTACK_API_KEY\"]) as vs:\n        checkout = await vs.billing.get_checkout(\"ck_…\")\n        print(checkout.status)\n\n\nasyncio.run(main())\n"},{"label":"curl","lang":"shell","source":"curl \"https://api.vocalstack.com/v1/billing/checkouts/{id}\" \\\n  -H \"Authorization: Bearer $VOCALSTACK_API_KEY\""}],"x-errors":["not_found"],"x-vocalstack-auth":"user","x-vocalstack-scopes":[]}},"/v1/billing/details":{"get":{"description":"The name, email, address and tax id invoices and receipts carry. `email` is the sign-in email until another is saved.","operationId":"billing.details","responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BillingDetails"}}},"description":"OK"}},"security":[{"bearerAuth":[]}],"summary":"Get the invoice details","tags":["billing"],"x-codeSamples":[{"label":"TypeScript SDK","lang":"typescript","source":"import { VocalStack } from '@vocalstack/sdk';\n\nconst vs = new VocalStack({ baseUrl: 'https://api.vocalstack.com', apiKey: process.env.VOCALSTACK_API_KEY });\n\nconst details = await vs.billing.details();\nconsole.log(details.name, details.country);\n"},{"label":"Python SDK","lang":"python","source":"import asyncio\nimport os\n\nfrom vocalstack import VocalStack\n\n\nasync def main() -> None:\n    async with VocalStack(\"https://api.vocalstack.com\", api_key=os.environ[\"VOCALSTACK_API_KEY\"]) as vs:\n        details = await vs.billing.details()\n        print(details.name, details.country)\n\n\nasyncio.run(main())\n"},{"label":"curl","lang":"shell","source":"curl \"https://api.vocalstack.com/v1/billing/details\" \\\n  -H \"Authorization: Bearer $VOCALSTACK_API_KEY\""}],"x-vocalstack-auth":"user","x-vocalstack-scopes":[]},"put":{"description":"Save the invoice details; they are written onto the provider's customer and prefill every checkout. `country` is ISO 3166-1 alpha-2; an EU VAT ID starts with its country's prefix (Greece: `EL`) and has 8 to 12 letters or digits (spaces, dots and dashes are dropped). `422 invalid_body` names the `field`.","operationId":"billing.setDetails","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BillingDetails"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BillingDetails"}}},"description":"The details as stored."}},"security":[{"bearerAuth":[]}],"summary":"Set the invoice details","tags":["billing"],"x-codeSamples":[{"label":"TypeScript SDK","lang":"typescript","source":"import { VocalStack } from '@vocalstack/sdk';\n\nconst vs = new VocalStack({ baseUrl: 'https://api.vocalstack.com', apiKey: process.env.VOCALSTACK_API_KEY });\n\nconst details = await vs.billing.setDetails({ name: 'Reed Studio GmbH', email: 'accounts@reed.studio', line1: 'Torstraße 109', city: 'Berlin', postalCode: '10119', country: 'DE', taxId: 'DE298765432' });\nconsole.log(details.taxId);\n"},{"label":"Python SDK","lang":"python","source":"import asyncio\nimport os\n\nfrom vocalstack import VocalStack\n\n\nasync def main() -> None:\n    async with VocalStack(\"https://api.vocalstack.com\", api_key=os.environ[\"VOCALSTACK_API_KEY\"]) as vs:\n        details = await vs.billing.set_details(name=\"Reed Studio GmbH\", email=\"accounts@reed.studio\", line1=\"Torstraße 109\", city=\"Berlin\", postal_code=\"10119\", country=\"DE\", tax_id=\"DE298765432\")\n        print(details.tax_id)\n\n\nasyncio.run(main())\n"},{"label":"curl","lang":"shell","source":"curl -X PUT \"https://api.vocalstack.com/v1/billing/details\" \\\n  -H \"Authorization: Bearer $VOCALSTACK_API_KEY\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"city\":\"<city>\"}'"}],"x-errors":["invalid_body","forbidden"],"x-vocalstack-auth":"user","x-vocalstack-scopes":[]}},"/v1/billing/ledger":{"get":{"description":"The account's credit movements, newest first: signup credit, top-ups, monthly credit, charges, corrections, refunds. Page with `cursor`.","operationId":"billing.ledger","parameters":[{"description":"The `nextCursor` of the previous page.","in":"query","name":"cursor","schema":{"type":"string"}},{"in":"query","name":"limit","schema":{"default":50,"maximum":200,"minimum":1,"type":"integer"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LedgerPage"}}},"description":"OK"}},"security":[{"bearerAuth":[]}],"summary":"List the ledger","tags":["billing"],"x-codeSamples":[{"label":"TypeScript SDK","lang":"typescript","source":"import { VocalStack } from '@vocalstack/sdk';\n\nconst vs = new VocalStack({ baseUrl: 'https://api.vocalstack.com', apiKey: process.env.VOCALSTACK_API_KEY });\n\nconst page = await vs.billing.ledger({ limit: 20 });\nfor (const entry of page.entries) console.log(entry.kind, entry.amount, entry.balanceAfter);\n"},{"label":"Python SDK","lang":"python","source":"import asyncio\nimport os\n\nfrom vocalstack import VocalStack\n\n\nasync def main() -> None:\n    async with VocalStack(\"https://api.vocalstack.com\", api_key=os.environ[\"VOCALSTACK_API_KEY\"]) as vs:\n        page = await vs.billing.ledger(limit=20)\n        for entry in page.entries:\n            print(entry.kind, entry.amount, entry.balance_after)\n\n\nasyncio.run(main())\n"},{"label":"curl","lang":"shell","source":"curl \"https://api.vocalstack.com/v1/billing/ledger\" \\\n  -H \"Authorization: Bearer $VOCALSTACK_API_KEY\""}],"x-errors":["invalid_options"],"x-vocalstack-auth":"userOrKey","x-vocalstack-scopes":["read"]}},"/v1/billing/low-balance-alert":{"put":{"description":"Turn the low-balance email on or off and set its line (`threshold`, inside the plan list's `lowBalanceAlert.range`): one email each time the balance falls to it.","operationId":"billing.setLowBalanceAlert","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LowBalanceAlertSettings"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LowBalanceAlertView"}}},"description":"The alert as stored."}},"security":[{"bearerAuth":[]}],"summary":"Set the low-balance alert","tags":["billing"],"x-codeSamples":[{"label":"TypeScript SDK","lang":"typescript","source":"import { VocalStack } from '@vocalstack/sdk';\n\nconst vs = new VocalStack({ baseUrl: 'https://api.vocalstack.com', apiKey: process.env.VOCALSTACK_API_KEY });\n\nconst alert = await vs.billing.setLowBalanceAlert({ enabled: true, threshold: 5 });\nconsole.log(alert.threshold);\n"},{"label":"Python SDK","lang":"python","source":"import asyncio\nimport os\n\nfrom vocalstack import VocalStack\n\n\nasync def main() -> None:\n    async with VocalStack(\"https://api.vocalstack.com\", api_key=os.environ[\"VOCALSTACK_API_KEY\"]) as vs:\n        alert = await vs.billing.set_low_balance_alert(enabled=True, threshold=5)\n        print(alert.threshold)\n\n\nasyncio.run(main())\n"},{"label":"curl","lang":"shell","source":"curl -X PUT \"https://api.vocalstack.com/v1/billing/low-balance-alert\" \\\n  -H \"Authorization: Bearer $VOCALSTACK_API_KEY\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"enabled\":true}'"}],"x-errors":["invalid_options","forbidden"],"x-vocalstack-auth":"user","x-vocalstack-scopes":[]}},"/v1/billing/payment-methods":{"get":{"description":"The cards saved at the payment provider: brand, last 4, expiry, wallet, which is the default, and whether the last automatic charge on it was declined. Empty before the first payment or card.","operationId":"billing.paymentMethods","responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PaymentMethodList"}}},"description":"OK"}},"security":[{"bearerAuth":[]}],"summary":"List the payment methods","tags":["billing"],"x-codeSamples":[{"label":"TypeScript SDK","lang":"typescript","source":"import { VocalStack } from '@vocalstack/sdk';\n\nconst vs = new VocalStack({ baseUrl: 'https://api.vocalstack.com', apiKey: process.env.VOCALSTACK_API_KEY });\n\nconst { methods } = await vs.billing.paymentMethods();\nfor (const m of methods) console.log(m.brand, m.last4, m.isDefault);\n"},{"label":"Python SDK","lang":"python","source":"import asyncio\nimport os\n\nfrom vocalstack import VocalStack\n\n\nasync def main() -> None:\n    async with VocalStack(\"https://api.vocalstack.com\", api_key=os.environ[\"VOCALSTACK_API_KEY\"]) as vs:\n        listed = await vs.billing.payment_methods()\n        for m in listed.methods:\n            print(m.brand, m.last4, m.is_default)\n\n\nasyncio.run(main())\n"},{"label":"curl","lang":"shell","source":"curl \"https://api.vocalstack.com/v1/billing/payment-methods\" \\\n  -H \"Authorization: Bearer $VOCALSTACK_API_KEY\""}],"x-errors":["unavailable"],"x-vocalstack-auth":"user","x-vocalstack-scopes":[]}},"/v1/billing/payment-methods/manage":{"post":{"description":"A link to the payment provider's portal, where a card is added, replaced or made the default (the provider has no API for those); it returns to the app's payment tab. Works before the first payment.","operationId":"billing.managePaymentMethods","responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PortalLink"}}},"description":"OK"}},"security":[{"bearerAuth":[]}],"summary":"Add or change a payment method","tags":["billing"],"x-codeSamples":[{"label":"TypeScript SDK","lang":"typescript","source":"import { VocalStack } from '@vocalstack/sdk';\n\nconst vs = new VocalStack({ baseUrl: 'https://api.vocalstack.com', apiKey: process.env.VOCALSTACK_API_KEY });\n\nconst { url } = await vs.billing.managePaymentMethods();\nconsole.log(url); // add a card or choose the default\n"},{"label":"Python SDK","lang":"python","source":"import asyncio\nimport os\n\nfrom vocalstack import VocalStack\n\n\nasync def main() -> None:\n    async with VocalStack(\"https://api.vocalstack.com\", api_key=os.environ[\"VOCALSTACK_API_KEY\"]) as vs:\n        link = await vs.billing.manage_payment_methods()\n        print(link.url)  # add a card or choose the default\n\n\nasyncio.run(main())\n"},{"label":"curl","lang":"shell","source":"curl -X POST \"https://api.vocalstack.com/v1/billing/payment-methods/manage\" \\\n  -H \"Authorization: Bearer $VOCALSTACK_API_KEY\""}],"x-errors":["forbidden","unavailable"],"x-vocalstack-auth":"user","x-vocalstack-scopes":[]}},"/v1/billing/payment-methods/{id}":{"delete":{"description":"Remove a saved card. `409 payment_method_in_use` while it pays for the subscription. Removing the last one turns auto top-up off.","operationId":"billing.deletePaymentMethod","parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"204":{"description":"Removed."}},"security":[{"bearerAuth":[]}],"summary":"Remove a payment method","tags":["billing"],"x-codeSamples":[{"label":"TypeScript SDK","lang":"typescript","source":"import { VocalStack } from '@vocalstack/sdk';\n\nconst vs = new VocalStack({ baseUrl: 'https://api.vocalstack.com', apiKey: process.env.VOCALSTACK_API_KEY });\n\nawait vs.billing.deletePaymentMethod('pm_123');\n"},{"label":"Python SDK","lang":"python","source":"import asyncio\nimport os\n\nfrom vocalstack import VocalStack\n\n\nasync def main() -> None:\n    async with VocalStack(\"https://api.vocalstack.com\", api_key=os.environ[\"VOCALSTACK_API_KEY\"]) as vs:\n        await vs.billing.delete_payment_method(\"pm_123\")\n\n\nasyncio.run(main())\n"},{"label":"curl","lang":"shell","source":"curl -X DELETE \"https://api.vocalstack.com/v1/billing/payment-methods/{id}\" \\\n  -H \"Authorization: Bearer $VOCALSTACK_API_KEY\""}],"x-errors":["not_found","payment_method_in_use","forbidden","unavailable"],"x-vocalstack-auth":"user","x-vocalstack-scopes":[]}},"/v1/billing/payments":{"get":{"description":"The account's payments history, newest first: top-ups, auto top-ups and subscription payments (`paid`, `pending` while the provider processes one, `failed` for a declined charge or renewal), refunds (`refunded`) and credit never paid for (`credit`: the signup credit, a promo code's bonus, an adjustment). `document` says whether an `invoice` or a `receipt` can be fetched. Page with `cursor`.","operationId":"billing.payments","parameters":[{"description":"The `nextCursor` of the previous page.","in":"query","name":"cursor","schema":{"type":"string"}},{"in":"query","name":"limit","schema":{"default":50,"maximum":200,"minimum":1,"type":"integer"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PaymentPage"}}},"description":"OK"}},"security":[{"bearerAuth":[]}],"summary":"List the payments","tags":["billing"],"x-codeSamples":[{"label":"TypeScript SDK","lang":"typescript","source":"import { VocalStack } from '@vocalstack/sdk';\n\nconst vs = new VocalStack({ baseUrl: 'https://api.vocalstack.com', apiKey: process.env.VOCALSTACK_API_KEY });\n\nconst page = await vs.billing.payments({ limit: 20 });\nfor (const p of page.payments) console.log(p.at, p.description, p.amount, p.status);\n"},{"label":"Python SDK","lang":"python","source":"import asyncio\nimport os\n\nfrom vocalstack import VocalStack\n\n\nasync def main() -> None:\n    async with VocalStack(\"https://api.vocalstack.com\", api_key=os.environ[\"VOCALSTACK_API_KEY\"]) as vs:\n        page = await vs.billing.payments(limit=20)\n        for p in page.payments:\n            print(p.at, p.description, p.amount, p.status)\n\n\nasyncio.run(main())\n"},{"label":"curl","lang":"shell","source":"curl \"https://api.vocalstack.com/v1/billing/payments\" \\\n  -H \"Authorization: Bearer $VOCALSTACK_API_KEY\""}],"x-errors":["invalid_options"],"x-vocalstack-auth":"userOrKey","x-vocalstack-scopes":["read"]}},"/v1/billing/payments/{id}/document":{"get":{"description":"A link to the payment's invoice (a subscription) or receipt (a top-up), valid for a short while. The provider makes it on the first ask: `202` with `{pending: true}` until then, ask again in a moment. `409 billing_details_required` when it needs a billing name and address (`PUT /v1/billing/details`).","operationId":"billing.paymentDocument","parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PaymentDocument"}}},"description":"`{url}`"},"202":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PaymentDocument"}}},"description":"`{pending: true}`: being made."}},"security":[{"bearerAuth":[]}],"summary":"Get a payment's invoice or receipt","tags":["billing"],"x-codeSamples":[{"label":"TypeScript SDK","lang":"typescript","source":"import { VocalStack } from '@vocalstack/sdk';\n\nconst vs = new VocalStack({ baseUrl: 'https://api.vocalstack.com', apiKey: process.env.VOCALSTACK_API_KEY });\n\nconst doc = await vs.billing.paymentDocument('0192f8a2-7c4e-7b1a-9d3e-5f6a7b8c9d0e');\nconsole.log(doc.url ?? 'being made: ask again in a moment');\n"},{"label":"Python SDK","lang":"python","source":"import asyncio\nimport os\n\nfrom vocalstack import VocalStack\n\n\nasync def main() -> None:\n    async with VocalStack(\"https://api.vocalstack.com\", api_key=os.environ[\"VOCALSTACK_API_KEY\"]) as vs:\n        doc = await vs.billing.payment_document(\"0192f8a2-7c4e-7b1a-9d3e-5f6a7b8c9d0e\")\n        print(doc.url or \"being made: ask again in a moment\")\n\n\nasyncio.run(main())\n"},{"label":"curl","lang":"shell","source":"curl \"https://api.vocalstack.com/v1/billing/payments/{id}/document\" \\\n  -H \"Authorization: Bearer $VOCALSTACK_API_KEY\""}],"x-errors":["not_found","billing_details_required","unavailable"],"x-vocalstack-auth":"user","x-vocalstack-scopes":[]}},"/v1/billing/plans":{"get":{"description":"The visible plans with their current rates, prices, included credit, whether running work goes on in a grace period when the credit runs out (`graceOnZero`), limits and features, plus the top-up presets and range, the auto top-up and low-balance alert choices and ranges, the signup credit and the downgrade grace in days. No credential needed.","operationId":"billing.plans","responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PlanList"}}},"description":"OK"}},"summary":"List the plans","tags":["billing"],"x-codeSamples":[{"label":"TypeScript SDK","lang":"typescript","source":"import { VocalStack } from '@vocalstack/sdk';\n\nconst vs = new VocalStack({ baseUrl: 'https://api.vocalstack.com', apiKey: process.env.VOCALSTACK_API_KEY });\n\nconst list = await vs.billing.plans();\nfor (const plan of list.plans) console.log(plan.id, plan.rates.filePerHour, plan.rates.livePerHour);\n"},{"label":"Python SDK","lang":"python","source":"import asyncio\nimport os\n\nfrom vocalstack import VocalStack\n\n\nasync def main() -> None:\n    async with VocalStack(\"https://api.vocalstack.com\", api_key=os.environ[\"VOCALSTACK_API_KEY\"]) as vs:\n        for plan in (await vs.billing.plans()).plans:\n            print(plan.id, plan.rates.file_per_hour, plan.rates.live_per_hour)\n\n\nasyncio.run(main())\n"},{"label":"curl","lang":"shell","source":"curl \"https://api.vocalstack.com/v1/billing/plans\" \\\n  -H \"Authorization: Bearer $VOCALSTACK_API_KEY\""}],"x-vocalstack-auth":"public","x-vocalstack-scopes":[]}},"/v1/billing/portal":{"get":{"description":"A link to the payment provider's customer portal (payment methods, invoices, receipts), valid for a short while. `409 payment_method_required` before the first checkout.","operationId":"billing.portal","responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PortalLink"}}},"description":"OK"}},"security":[{"bearerAuth":[]}],"summary":"Get the customer portal link","tags":["billing"],"x-codeSamples":[{"label":"TypeScript SDK","lang":"typescript","source":"import { VocalStack } from '@vocalstack/sdk';\n\nconst vs = new VocalStack({ baseUrl: 'https://api.vocalstack.com', apiKey: process.env.VOCALSTACK_API_KEY });\n\nconst { url } = await vs.billing.portal();\nconsole.log(url); // payment methods, invoices, receipts\n"},{"label":"Python SDK","lang":"python","source":"import asyncio\nimport os\n\nfrom vocalstack import VocalStack\n\n\nasync def main() -> None:\n    async with VocalStack(\"https://api.vocalstack.com\", api_key=os.environ[\"VOCALSTACK_API_KEY\"]) as vs:\n        link = await vs.billing.portal()\n        print(link.url)  # payment methods, invoices, receipts\n\n\nasyncio.run(main())\n"},{"label":"curl","lang":"shell","source":"curl \"https://api.vocalstack.com/v1/billing/portal\" \\\n  -H \"Authorization: Bearer $VOCALSTACK_API_KEY\""}],"x-errors":["payment_method_required","forbidden","unavailable"],"x-vocalstack-auth":"user","x-vocalstack-scopes":[]}},"/v1/billing/promo-codes/check":{"post":{"description":"What a code gives `for` a `topup` (a bonus credit) or a `subscribe` (a discount off the first payment), before the checkout. `404 promo_code_invalid` when it does not exist, has expired, is used up, was redeemed by this account or gives nothing for this purchase. Rate-limited per account.","operationId":"billing.checkPromoCode","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CheckPromoCode"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PromoCodeView"}}},"description":"OK"}},"security":[{"bearerAuth":[]}],"summary":"Check a promo code","tags":["billing"],"x-codeSamples":[{"label":"TypeScript SDK","lang":"typescript","source":"import { VocalStack } from '@vocalstack/sdk';\n\nconst vs = new VocalStack({ baseUrl: 'https://api.vocalstack.com', apiKey: process.env.VOCALSTACK_API_KEY });\n\nconst promo = await vs.billing.checkPromoCode({ code: 'LAUNCH', for: 'topup' });\nconsole.log(promo.bonusCredit);\n"},{"label":"Python SDK","lang":"python","source":"import asyncio\nimport os\n\nfrom vocalstack import VocalStack\n\n\nasync def main() -> None:\n    async with VocalStack(\"https://api.vocalstack.com\", api_key=os.environ[\"VOCALSTACK_API_KEY\"]) as vs:\n        promo = await vs.billing.check_promo_code(code=\"LAUNCH\", for_=\"topup\")\n        print(promo.bonus_credit)\n\n\nasyncio.run(main())\n"},{"label":"curl","lang":"shell","source":"curl -X POST \"https://api.vocalstack.com/v1/billing/promo-codes/check\" \\\n  -H \"Authorization: Bearer $VOCALSTACK_API_KEY\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"code\":\"<code>\",\"for\":\"<for>\"}'"}],"x-errors":["promo_code_invalid","invalid_options","rate_limited"],"x-vocalstack-auth":"user","x-vocalstack-scopes":[]}},"/v1/billing/subscription/cancel":{"post":{"description":"Cancel at the end of the current period: nothing changes until then; after it the account moves to the default plan and unspent monthly credit is removed. `409 invalid_state` without a subscription.","operationId":"billing.cancelSubscription","responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SubscriptionView"}}},"description":"The subscription, `cancelAtPeriodEnd: true`."}},"security":[{"bearerAuth":[]}],"summary":"Cancel the subscription","tags":["billing"],"x-codeSamples":[{"label":"TypeScript SDK","lang":"typescript","source":"import { VocalStack } from '@vocalstack/sdk';\n\nconst vs = new VocalStack({ baseUrl: 'https://api.vocalstack.com', apiKey: process.env.VOCALSTACK_API_KEY });\n\nconst subscription = await vs.billing.cancelSubscription();\nconsole.log(subscription.cancelAtPeriodEnd, subscription.currentPeriodEnd);\n"},{"label":"Python SDK","lang":"python","source":"import asyncio\nimport os\n\nfrom vocalstack import VocalStack\n\n\nasync def main() -> None:\n    async with VocalStack(\"https://api.vocalstack.com\", api_key=os.environ[\"VOCALSTACK_API_KEY\"]) as vs:\n        subscription = await vs.billing.cancel_subscription()\n        print(subscription.cancel_at_period_end, subscription.current_period_end)\n\n\nasyncio.run(main())\n"},{"label":"curl","lang":"shell","source":"curl -X POST \"https://api.vocalstack.com/v1/billing/subscription/cancel\" \\\n  -H \"Authorization: Bearer $VOCALSTACK_API_KEY\""}],"x-errors":["invalid_state","forbidden","unavailable"],"x-vocalstack-auth":"user","x-vocalstack-scopes":[]}},"/v1/billing/subscription/interval":{"delete":{"description":"Take back a scheduled interval switch.","operationId":"billing.unsetInterval","responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SubscriptionView"}}},"description":"The subscription, `pendingInterval: null`."}},"security":[{"bearerAuth":[]}],"summary":"Keep the billing interval","tags":["billing"],"x-codeSamples":[{"label":"TypeScript SDK","lang":"typescript","source":"import { VocalStack } from '@vocalstack/sdk';\n\nconst vs = new VocalStack({ baseUrl: 'https://api.vocalstack.com', apiKey: process.env.VOCALSTACK_API_KEY });\n\nconst subscription = await vs.billing.unsetInterval();\nconsole.log(subscription.interval);\n"},{"label":"Python SDK","lang":"python","source":"import asyncio\nimport os\n\nfrom vocalstack import VocalStack\n\n\nasync def main() -> None:\n    async with VocalStack(\"https://api.vocalstack.com\", api_key=os.environ[\"VOCALSTACK_API_KEY\"]) as vs:\n        subscription = await vs.billing.unset_interval()\n        print(subscription.interval)\n\n\nasyncio.run(main())\n"},{"label":"curl","lang":"shell","source":"curl -X DELETE \"https://api.vocalstack.com/v1/billing/subscription/interval\" \\\n  -H \"Authorization: Bearer $VOCALSTACK_API_KEY\""}],"x-errors":["invalid_state","forbidden","unavailable"],"x-vocalstack-auth":"user","x-vocalstack-scopes":[]},"post":{"description":"From the next period the subscription renews `month`ly or `year`ly (nothing is charged now); `subscription.pendingInterval` shows the switch until it applies. `409 invalid_state` without a subscription or when it ends at the period end.","operationId":"billing.setInterval","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SetInterval"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SubscriptionView"}}},"description":"The subscription, with `pendingInterval`."}},"security":[{"bearerAuth":[]}],"summary":"Switch the billing interval","tags":["billing"],"x-codeSamples":[{"label":"TypeScript SDK","lang":"typescript","source":"import { VocalStack } from '@vocalstack/sdk';\n\nconst vs = new VocalStack({ baseUrl: 'https://api.vocalstack.com', apiKey: process.env.VOCALSTACK_API_KEY });\n\nconst subscription = await vs.billing.setInterval({ interval: 'year' });\nconsole.log(subscription.pendingInterval); // from the next period\n"},{"label":"Python SDK","lang":"python","source":"import asyncio\nimport os\n\nfrom vocalstack import VocalStack\n\n\nasync def main() -> None:\n    async with VocalStack(\"https://api.vocalstack.com\", api_key=os.environ[\"VOCALSTACK_API_KEY\"]) as vs:\n        subscription = await vs.billing.set_interval(interval=\"year\")\n        print(subscription.pending_interval)  # from the next period\n\n\nasyncio.run(main())\n"},{"label":"curl","lang":"shell","source":"curl -X POST \"https://api.vocalstack.com/v1/billing/subscription/interval\" \\\n  -H \"Authorization: Bearer $VOCALSTACK_API_KEY\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"interval\":\"<interval>\"}'"}],"x-errors":["invalid_options","invalid_state","forbidden","unavailable"],"x-vocalstack-auth":"user","x-vocalstack-scopes":[]}},"/v1/billing/subscription/uncancel":{"post":{"description":"Take back a cancellation scheduled for the period end.","operationId":"billing.uncancelSubscription","responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SubscriptionView"}}},"description":"The subscription, `cancelAtPeriodEnd: false`."}},"security":[{"bearerAuth":[]}],"summary":"Keep the subscription","tags":["billing"],"x-codeSamples":[{"label":"TypeScript SDK","lang":"typescript","source":"import { VocalStack } from '@vocalstack/sdk';\n\nconst vs = new VocalStack({ baseUrl: 'https://api.vocalstack.com', apiKey: process.env.VOCALSTACK_API_KEY });\n\nconst subscription = await vs.billing.uncancelSubscription();\nconsole.log(subscription.status);\n"},{"label":"Python SDK","lang":"python","source":"import asyncio\nimport os\n\nfrom vocalstack import VocalStack\n\n\nasync def main() -> None:\n    async with VocalStack(\"https://api.vocalstack.com\", api_key=os.environ[\"VOCALSTACK_API_KEY\"]) as vs:\n        subscription = await vs.billing.uncancel_subscription()\n        print(subscription.status)\n\n\nasyncio.run(main())\n"},{"label":"curl","lang":"shell","source":"curl -X POST \"https://api.vocalstack.com/v1/billing/subscription/uncancel\" \\\n  -H \"Authorization: Bearer $VOCALSTACK_API_KEY\""}],"x-errors":["invalid_state","forbidden","unavailable"],"x-vocalstack-auth":"user","x-vocalstack-scopes":[]}},"/v1/billing/usage":{"get":{"description":"What the account spent per day (in `tz`), per source (`dashboard` or the API key that made the transcription, deleted recordings included) and per kind (`file`, `live`): audio seconds and dollars, with running transcriptions at their current amount. `startDate` and `endDate` are inclusive days (`YYYY-MM-DD`); a start before the account's first day is moved to it and an end after today to today (the answer's `startDate` and `endDate` say what was used); at most 92 days (`400 range_too_long`).","operationId":"billing.usage","parameters":[{"description":"The first day, `YYYY-MM-DD` (default: the first of this month).","in":"query","name":"startDate","schema":{"format":"date","type":"string"}},{"description":"The last day (default: today).","in":"query","name":"endDate","schema":{"format":"date","type":"string"}},{"description":"An IANA time zone.","in":"query","name":"tz","schema":{"default":"UTC","type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UsageReport"}}},"description":"OK"}},"security":[{"bearerAuth":[]}],"summary":"Get the usage report","tags":["billing"],"x-codeSamples":[{"label":"TypeScript SDK","lang":"typescript","source":"import { VocalStack } from '@vocalstack/sdk';\n\nconst vs = new VocalStack({ baseUrl: 'https://api.vocalstack.com', apiKey: process.env.VOCALSTACK_API_KEY });\n\nconst report = await vs.billing.usage({ startDate: '2026-10-01', endDate: '2026-10-11', tz: 'Europe/Berlin' });\nfor (const row of report.rows) console.log(row.date, row.source, row.kind, row.seconds, row.amount);\n"},{"label":"Python SDK","lang":"python","source":"import asyncio\nimport os\n\nfrom vocalstack import VocalStack\n\n\nasync def main() -> None:\n    async with VocalStack(\"https://api.vocalstack.com\", api_key=os.environ[\"VOCALSTACK_API_KEY\"]) as vs:\n        report = await vs.billing.usage(start_date=\"2026-10-01\", end_date=\"2026-10-11\", tz=\"Europe/Berlin\")\n        for row in report.rows:\n            print(row.date, row.source, row.kind, row.seconds, row.amount)\n\n\nasyncio.run(main())\n"},{"label":"curl","lang":"shell","source":"curl \"https://api.vocalstack.com/v1/billing/usage\" \\\n  -H \"Authorization: Bearer $VOCALSTACK_API_KEY\""}],"x-errors":["invalid_options","range_too_long"],"x-vocalstack-auth":"userOrKey","x-vocalstack-scopes":["read"]}},"/v1/broadcasts":{"get":{"description":"Broadcasts owned by the acting principal. `lifecycle=all` is the active and the archived in one answer (each says which it is in its `state`), up to 500 of each — what a dashboard that shows them all should ask, once.","operationId":"broadcasts.list","parameters":[{"in":"query","name":"lifecycle","schema":{"default":"active","enum":["active","archived","all"],"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"properties":{"broadcasts":{"items":{"$ref":"#/components/schemas/Broadcast"},"type":"array"}},"type":"object"}}},"description":"OK"}},"security":[{"bearerAuth":[]}],"summary":"List broadcasts","tags":["broadcasts"],"x-codeSamples":[{"label":"TypeScript SDK","lang":"typescript","source":"import { VocalStack } from '@vocalstack/sdk';\n\nconst vs = new VocalStack({ baseUrl: 'https://api.vocalstack.com', apiKey: process.env.VOCALSTACK_API_KEY });\n\nconst broadcasts = await vs.broadcasts.list(); // 'active' (default), 'archived' or 'all'\nfor (const b of broadcasts) console.log(b.id, b.code, b.name);\n"},{"label":"Python SDK","lang":"python","source":"import asyncio\nimport os\n\nfrom vocalstack import VocalStack\n\n\nasync def main() -> None:\n    async with VocalStack(\"https://api.vocalstack.com\", api_key=os.environ[\"VOCALSTACK_API_KEY\"]) as vs:\n        for b in await vs.broadcasts.list():  # \"active\" (default), \"archived\" or \"all\"\n            print(b.id, b.code, b.name)\n\n\nasyncio.run(main())\n"},{"label":"curl","lang":"shell","source":"curl \"https://api.vocalstack.com/v1/broadcasts\" \\\n  -H \"Authorization: Bearer $VOCALSTACK_API_KEY\""}],"x-vocalstack-auth":"userOrKey","x-vocalstack-scopes":["read"]},"post":{"description":"Create a broadcast: the link listeners open (`code`), which a host can change later (the old one keeps working).","operationId":"broadcasts.create","parameters":[{"description":"Makes a retry safe: a request repeated with the same key and the same body within 24 hours returns the first response (with `Idempotent-Replayed: true`) instead of running again. The same key with a different request is 422 `idempotency_key_reused`; a retry while the first is still running is 409 `conflict`. Only a successful (2xx) response is remembered. Keys are per account.","in":"header","name":"Idempotency-Key","required":false,"schema":{"maxLength":255,"minLength":1,"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateBroadcast"}}},"required":true},"responses":{"201":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Broadcast"}}},"description":"Created"}},"security":[{"bearerAuth":[]}],"summary":"Create a broadcast","tags":["broadcasts"],"x-codeSamples":[{"label":"TypeScript SDK","lang":"typescript","source":"import { VocalStack } from '@vocalstack/sdk';\n\nconst vs = new VocalStack({ baseUrl: 'https://api.vocalstack.com', apiKey: process.env.VOCALSTACK_API_KEY });\n\nconst broadcast = await vs.broadcasts.create({ name: 'Sunday service' });\nconsole.log(broadcast.id, broadcast.code);\n"},{"label":"Python SDK","lang":"python","source":"import asyncio\nimport os\n\nfrom vocalstack import VocalStack\n\n\nasync def main() -> None:\n    async with VocalStack(\"https://api.vocalstack.com\", api_key=os.environ[\"VOCALSTACK_API_KEY\"]) as vs:\n        broadcast = await vs.broadcasts.create(name=\"Sunday service\")\n        print(broadcast.id, broadcast.code)\n\n\nasyncio.run(main())\n"},{"label":"curl","lang":"shell","source":"curl -X POST \"https://api.vocalstack.com/v1/broadcasts\" \\\n  -H \"Authorization: Bearer $VOCALSTACK_API_KEY\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"name\":\"<name>\"}'"}],"x-errors":["invalid_options","conflict","rate_limited","idempotency_key_reused"],"x-vocalstack-auth":"userOrKey","x-vocalstack-scopes":["write"]}},"/v1/broadcasts/{id}":{"get":{"description":"One broadcast of the acting principal's, by id.","operationId":"broadcasts.get","parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Broadcast"}}},"description":"OK"}},"security":[{"bearerAuth":[]}],"summary":"Get a broadcast","tags":["broadcasts"],"x-codeSamples":[{"label":"TypeScript SDK","lang":"typescript","source":"import { VocalStack } from '@vocalstack/sdk';\n\nconst vs = new VocalStack({ baseUrl: 'https://api.vocalstack.com', apiKey: process.env.VOCALSTACK_API_KEY });\n\nconst broadcast = await vs.broadcasts.get('BROADCAST_ID');\nconsole.log(broadcast);\n"},{"label":"Python SDK","lang":"python","source":"import asyncio\nimport os\n\nfrom vocalstack import VocalStack\n\n\nasync def main() -> None:\n    async with VocalStack(\"https://api.vocalstack.com\", api_key=os.environ[\"VOCALSTACK_API_KEY\"]) as vs:\n        print(await vs.broadcasts.get(\"BROADCAST_ID\"))\n\n\nasyncio.run(main())\n"},{"label":"curl","lang":"shell","source":"curl \"https://api.vocalstack.com/v1/broadcasts/{id}\" \\\n  -H \"Authorization: Bearer $VOCALSTACK_API_KEY\""}],"x-vocalstack-auth":"userOrKey","x-vocalstack-scopes":["read"]},"patch":{"description":"Rename, change access/password, change the link (`code`; the links it had keep opening it), or update default capture settings.","operationId":"broadcasts.update","parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateBroadcast"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Broadcast"}}},"description":"OK"}},"security":[{"bearerAuth":[]}],"summary":"Update a broadcast","tags":["broadcasts"],"x-codeSamples":[{"label":"TypeScript SDK","lang":"typescript","source":"import { VocalStack } from '@vocalstack/sdk';\n\nconst vs = new VocalStack({ baseUrl: 'https://api.vocalstack.com', apiKey: process.env.VOCALSTACK_API_KEY });\n\nconst broadcast = await vs.broadcasts.update('BROADCAST_ID', { name: 'Sunday service (English)' });\nconsole.log(broadcast.name);\n"},{"label":"Python SDK","lang":"python","source":"import asyncio\nimport os\n\nfrom vocalstack import VocalStack\n\n\nasync def main() -> None:\n    async with VocalStack(\"https://api.vocalstack.com\", api_key=os.environ[\"VOCALSTACK_API_KEY\"]) as vs:\n        broadcast = await vs.broadcasts.update(\"BROADCAST_ID\", name=\"Sunday service (English)\")\n        print(broadcast.name)\n\n\nasyncio.run(main())\n"},{"label":"curl","lang":"shell","source":"curl -X PATCH \"https://api.vocalstack.com/v1/broadcasts/{id}\" \\\n  -H \"Authorization: Bearer $VOCALSTACK_API_KEY\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"access\":\"public\"}'"}],"x-vocalstack-auth":"userOrKey","x-vocalstack-scopes":["write"]}},"/v1/broadcasts/{id}/archive":{"post":{"description":"Archive the broadcast: hidden, its links dead, its data kept until it is restored or purged.","operationId":"broadcasts.archive","parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Broadcast"}}},"description":"OK"}},"security":[{"bearerAuth":[]}],"summary":"Archive a broadcast","tags":["broadcasts"],"x-codeSamples":[{"label":"TypeScript SDK","lang":"typescript","source":"import { VocalStack } from '@vocalstack/sdk';\n\nconst vs = new VocalStack({ baseUrl: 'https://api.vocalstack.com', apiKey: process.env.VOCALSTACK_API_KEY });\n\nawait vs.broadcasts.archive('BROADCAST_ID');\n"},{"label":"Python SDK","lang":"python","source":"import asyncio\nimport os\n\nfrom vocalstack import VocalStack\n\n\nasync def main() -> None:\n    async with VocalStack(\"https://api.vocalstack.com\", api_key=os.environ[\"VOCALSTACK_API_KEY\"]) as vs:\n        await vs.broadcasts.archive(\"BROADCAST_ID\")\n\n\nasyncio.run(main())\n"},{"label":"curl","lang":"shell","source":"curl -X POST \"https://api.vocalstack.com/v1/broadcasts/{id}/archive\" \\\n  -H \"Authorization: Bearer $VOCALSTACK_API_KEY\""}],"x-vocalstack-auth":"userOrKey","x-vocalstack-scopes":["write"]}},"/v1/broadcasts/{id}/audio-sources":{"post":{"description":"Give a broadcast a stream it can record from — a YouTube live, an internet radio address (`audioSources`). `broadcasts.startRecording` with `audioSource` set to its id records it: each such recording is a live transcription of its own, attached to the broadcast. Returns the broadcast.","operationId":"broadcasts.addAudioSource","parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateAudioSource"}}},"required":true},"responses":{"201":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Broadcast"}}},"description":"Created"}},"security":[{"bearerAuth":[]}],"summary":"Add an audio source","tags":["broadcasts"],"x-codeSamples":[{"label":"TypeScript SDK","lang":"typescript","source":"import { VocalStack } from '@vocalstack/sdk';\n\nconst vs = new VocalStack({ baseUrl: 'https://api.vocalstack.com', apiKey: process.env.VOCALSTACK_API_KEY });\n\nconst broadcast = await vs.broadcasts.addAudioSource('BROADCAST_ID', { url: 'https://example.com/live/radio.mp3', name: 'Radio' });\nconsole.log(broadcast);\n"},{"label":"Python SDK","lang":"python","source":"import asyncio\nimport os\n\nfrom vocalstack import VocalStack\n\n\nasync def main() -> None:\n    async with VocalStack(\"https://api.vocalstack.com\", api_key=os.environ[\"VOCALSTACK_API_KEY\"]) as vs:\n        broadcast = await vs.broadcasts.add_audio_source(\"BROADCAST_ID\", url=\"https://example.com/live/radio.mp3\", name=\"Radio\")\n        print(broadcast)\n\n\nasyncio.run(main())\n"},{"label":"curl","lang":"shell","source":"curl -X POST \"https://api.vocalstack.com/v1/broadcasts/{id}/audio-sources\" \\\n  -H \"Authorization: Bearer $VOCALSTACK_API_KEY\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"url\":\"<url>\"}'"}],"x-errors":["invalid_options","invalid_state","not_found"],"x-vocalstack-auth":"userOrKey","x-vocalstack-scopes":["write"]}},"/v1/broadcasts/{id}/audio-sources/{source}":{"delete":{"description":"Take an audio source off a broadcast. What was recorded from it stays. Returns the broadcast.","operationId":"broadcasts.removeAudioSource","parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string"}},{"in":"path","name":"source","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Broadcast"}}},"description":"OK"}},"security":[{"bearerAuth":[]}],"summary":"Remove an audio source","tags":["broadcasts"],"x-codeSamples":[{"label":"TypeScript SDK","lang":"typescript","source":"import { VocalStack } from '@vocalstack/sdk';\n\nconst vs = new VocalStack({ baseUrl: 'https://api.vocalstack.com', apiKey: process.env.VOCALSTACK_API_KEY });\n\nawait vs.broadcasts.removeAudioSource('BROADCAST_ID', 'SOURCE_ID');\n"},{"label":"Python SDK","lang":"python","source":"import asyncio\nimport os\n\nfrom vocalstack import VocalStack\n\n\nasync def main() -> None:\n    async with VocalStack(\"https://api.vocalstack.com\", api_key=os.environ[\"VOCALSTACK_API_KEY\"]) as vs:\n        await vs.broadcasts.remove_audio_source(\"BROADCAST_ID\", \"SOURCE_ID\")\n\n\nasyncio.run(main())\n"},{"label":"curl","lang":"shell","source":"curl -X DELETE \"https://api.vocalstack.com/v1/broadcasts/{id}/audio-sources/{source}\" \\\n  -H \"Authorization: Bearer $VOCALSTACK_API_KEY\""}],"x-errors":["not_found"],"x-vocalstack-auth":"userOrKey","x-vocalstack-scopes":["write"]}},"/v1/broadcasts/{id}/audio-sources/{source}/check":{"get":{"description":"Whether an audio source can be recorded right now: the server opens it as a recording would — a YouTube link resolved to its live stream, a radio address connected, a playlist followed, the first audio decoded — and says whether audio came back (`state`). When it did not: what went wrong (`problem`, `message`, `httpStatus`), and, when one of the likely mistakes — an extension a letter off, the other scheme, the stream a web page plays — was opened and gave audio, that address (`fix`). Nothing is recorded. A ready answer stands for a minute and a failed one for 20 s, unless `fresh` asks again.","operationId":"broadcasts.checkAudioSource","parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string"}},{"in":"path","name":"source","required":true,"schema":{"type":"string"}},{"in":"query","name":"fresh","required":false,"schema":{"type":"boolean"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AudioSourceCheck"}}},"description":"OK"}},"security":[{"bearerAuth":[]}],"summary":"Check an audio source","tags":["broadcasts"],"x-codeSamples":[{"label":"TypeScript SDK","lang":"typescript","source":"import { VocalStack } from '@vocalstack/sdk';\n\nconst vs = new VocalStack({ baseUrl: 'https://api.vocalstack.com', apiKey: process.env.VOCALSTACK_API_KEY });\n\nconst check = await vs.broadcasts.checkAudioSource('BROADCAST_ID', 'SOURCE_ID', { fresh: true });\nconsole.log(check);\n"},{"label":"Python SDK","lang":"python","source":"import asyncio\nimport os\n\nfrom vocalstack import VocalStack\n\n\nasync def main() -> None:\n    async with VocalStack(\"https://api.vocalstack.com\", api_key=os.environ[\"VOCALSTACK_API_KEY\"]) as vs:\n        print(await vs.broadcasts.check_audio_source(\"BROADCAST_ID\", \"SOURCE_ID\", fresh=True))\n\n\nasyncio.run(main())\n"},{"label":"curl","lang":"shell","source":"curl \"https://api.vocalstack.com/v1/broadcasts/{id}/audio-sources/{source}/check\" \\\n  -H \"Authorization: Bearer $VOCALSTACK_API_KEY\""}],"x-errors":["not_found"],"x-vocalstack-auth":"userOrKey","x-vocalstack-scopes":["read"]}},"/v1/broadcasts/{id}/purge":{"post":{"description":"Delete an archived broadcast and everything in it, for good.","operationId":"broadcasts.purge","parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK"}},"security":[{"bearerAuth":[]}],"summary":"Purge a broadcast","tags":["broadcasts"],"x-codeSamples":[{"label":"TypeScript SDK","lang":"typescript","source":"import { VocalStack } from '@vocalstack/sdk';\n\nconst vs = new VocalStack({ baseUrl: 'https://api.vocalstack.com', apiKey: process.env.VOCALSTACK_API_KEY });\n\nawait vs.broadcasts.purge('BROADCAST_ID'); // only an archived broadcast\n"},{"label":"Python SDK","lang":"python","source":"import asyncio\nimport os\n\nfrom vocalstack import VocalStack\n\n\nasync def main() -> None:\n    async with VocalStack(\"https://api.vocalstack.com\", api_key=os.environ[\"VOCALSTACK_API_KEY\"]) as vs:\n        await vs.broadcasts.purge(\"BROADCAST_ID\")  # only an archived broadcast\n\n\nasyncio.run(main())\n"},{"label":"curl","lang":"shell","source":"curl -X POST \"https://api.vocalstack.com/v1/broadcasts/{id}/purge\" \\\n  -H \"Authorization: Bearer $VOCALSTACK_API_KEY\""}],"x-vocalstack-auth":"userOrKey","x-vocalstack-scopes":["write"]}},"/v1/broadcasts/{id}/recordings":{"get":{"description":"The broadcast's recordings.","operationId":"broadcasts.listRecordings","parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string"}},{"in":"query","name":"lifecycle","schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"properties":{"recordings":{"items":{"$ref":"#/components/schemas/Recording"},"type":"array"}},"type":"object"}}},"description":"OK"}},"security":[{"bearerAuth":[]}],"summary":"List a broadcast's recordings","tags":["broadcasts"],"x-codeSamples":[{"label":"TypeScript SDK","lang":"typescript","source":"import { VocalStack } from '@vocalstack/sdk';\n\nconst vs = new VocalStack({ baseUrl: 'https://api.vocalstack.com', apiKey: process.env.VOCALSTACK_API_KEY });\n\nconst recordings = await vs.broadcasts.listRecordings('BROADCAST_ID');\nfor (const r of recordings) console.log(r.id, r.state);\n"},{"label":"Python SDK","lang":"python","source":"import asyncio\nimport os\n\nfrom vocalstack import VocalStack\n\n\nasync def main() -> None:\n    async with VocalStack(\"https://api.vocalstack.com\", api_key=os.environ[\"VOCALSTACK_API_KEY\"]) as vs:\n        for r in await vs.broadcasts.list_recordings(\"BROADCAST_ID\"):\n            print(r.id, r.state)\n\n\nasyncio.run(main())\n"},{"label":"curl","lang":"shell","source":"curl \"https://api.vocalstack.com/v1/broadcasts/{id}/recordings\" \\\n  -H \"Authorization: Bearer $VOCALSTACK_API_KEY\""}],"x-vocalstack-auth":"userOrKey","x-vocalstack-scopes":["read"]},"post":{"description":"Start recording on the broadcast. The recording is made in a new live transcription that hears the broadcast's microphone — or, with `audioSource`, one of its audio sources — and is attached to it: `recording.transcriptionId` names that transcription, the broadcast's link shows it (`currentRecording`), and it is listed with the transcriptions. Mints a publisher realtime session (`streamUrl`, `sessionId`); 409 conflict with details.takeover when another device is recording. `scriptText` gives the recording words to follow like a teleprompter (`Recording.scriptText`).","operationId":"broadcasts.startRecording","parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string"}},{"description":"Makes a retry safe: a request repeated with the same key and the same body within 24 hours returns the first response (with `Idempotent-Replayed: true`) instead of running again. The same key with a different request is 422 `idempotency_key_reused`; a retry while the first is still running is 409 `conflict`. Only a successful (2xx) response is remembered. Keys are per account.","in":"header","name":"Idempotency-Key","required":false,"schema":{"maxLength":255,"minLength":1,"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/StartRecording"}}},"required":false},"responses":{"201":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublishGrant"}}},"description":"Created"}},"security":[{"bearerAuth":[]}],"summary":"Start a recording","tags":["broadcasts"],"x-codeSamples":[{"label":"TypeScript SDK","lang":"typescript","source":"import { VocalStack } from '@vocalstack/sdk';\n\nconst vs = new VocalStack({ baseUrl: 'https://api.vocalstack.com', apiKey: process.env.VOCALSTACK_API_KEY });\n\n// A recording on a broadcast is made in a live transcription attached to it.\nconst grant = await vs.broadcasts.startRecording('BROADCAST_ID');\nconst stream = await vs.recordings.publish(grant); // send PCM with stream.sendAudio(), end with stream.eof()\nconsole.log(grant.recording.id, stream.sessionId);\n"},{"label":"Python SDK","lang":"python","source":"import asyncio\nimport os\n\nfrom vocalstack import VocalStack\n\n\nasync def main() -> None:\n    async with VocalStack(\"https://api.vocalstack.com\", api_key=os.environ[\"VOCALSTACK_API_KEY\"]) as vs:\n        # A recording on a broadcast is made in a live transcription attached to it.\n        grant = await vs.broadcasts.start_recording(\"BROADCAST_ID\")\n        async with await vs.recordings.publish(grant) as stream:\n            ...  # await stream.send_audio(pcm) (f32le, 16 kHz, mono), then await stream.eof()\n\n\nasyncio.run(main())\n"},{"label":"curl","lang":"shell","source":"curl -X POST \"https://api.vocalstack.com/v1/broadcasts/{id}/recordings\" \\\n  -H \"Authorization: Bearer $VOCALSTACK_API_KEY\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"audioSource\":\"<audioSource>\"}'"}],"x-errors":["invalid_state","conflict","insufficient_credit","quota_exceeded","rate_limited","idempotency_key_reused"],"x-vocalstack-auth":"userOrKey","x-vocalstack-scopes":["recordings:publish"]}},"/v1/broadcasts/{id}/restore":{"post":{"description":"Bring an archived broadcast back.","operationId":"broadcasts.restore","parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Broadcast"}}},"description":"OK"}},"security":[{"bearerAuth":[]}],"summary":"Restore a broadcast","tags":["broadcasts"],"x-codeSamples":[{"label":"TypeScript SDK","lang":"typescript","source":"import { VocalStack } from '@vocalstack/sdk';\n\nconst vs = new VocalStack({ baseUrl: 'https://api.vocalstack.com', apiKey: process.env.VOCALSTACK_API_KEY });\n\nawait vs.broadcasts.restore('BROADCAST_ID');\n"},{"label":"Python SDK","lang":"python","source":"import asyncio\nimport os\n\nfrom vocalstack import VocalStack\n\n\nasync def main() -> None:\n    async with VocalStack(\"https://api.vocalstack.com\", api_key=os.environ[\"VOCALSTACK_API_KEY\"]) as vs:\n        await vs.broadcasts.restore(\"BROADCAST_ID\")\n\n\nasyncio.run(main())\n"},{"label":"curl","lang":"shell","source":"curl -X POST \"https://api.vocalstack.com/v1/broadcasts/{id}/restore\" \\\n  -H \"Authorization: Bearer $VOCALSTACK_API_KEY\""}],"x-vocalstack-auth":"userOrKey","x-vocalstack-scopes":["write"]}},"/v1/broadcasts/{id}/share-links":{"get":{"description":"Active share links of the broadcast.","operationId":"broadcasts.listShareLinks","parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"properties":{"links":{"items":{"$ref":"#/components/schemas/ShareLink"},"type":"array"}},"type":"object"}}},"description":"OK"}},"security":[{"bearerAuth":[]}],"summary":"List a broadcast's share links","tags":["broadcasts"],"x-codeSamples":[{"label":"TypeScript SDK","lang":"typescript","source":"import { VocalStack } from '@vocalstack/sdk';\n\nconst vs = new VocalStack({ baseUrl: 'https://api.vocalstack.com', apiKey: process.env.VOCALSTACK_API_KEY });\n\nconst links = await vs.broadcasts.listShareLinks('BROADCAST_ID');\nconsole.log(links);\n"},{"label":"Python SDK","lang":"python","source":"import asyncio\nimport os\n\nfrom vocalstack import VocalStack\n\n\nasync def main() -> None:\n    async with VocalStack(\"https://api.vocalstack.com\", api_key=os.environ[\"VOCALSTACK_API_KEY\"]) as vs:\n        print(await vs.broadcasts.list_share_links(\"BROADCAST_ID\"))\n\n\nasyncio.run(main())\n"},{"label":"curl","lang":"shell","source":"curl \"https://api.vocalstack.com/v1/broadcasts/{id}/share-links\" \\\n  -H \"Authorization: Bearer $VOCALSTACK_API_KEY\""}],"x-vocalstack-auth":"userOrKey","x-vocalstack-scopes":["read"]},"post":{"description":"A named link (\"Who is it for?\") to the read-only public page. `allowDownload`, `allowExport` and `allowTranslate` are all on by default and per link. Send `allowTranslate: false` to close a link to translation; it takes effect from the next request.","operationId":"broadcasts.createShareLink","parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateShareLink"}}},"required":true},"responses":{"201":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ShareLink"}}},"description":"Created"}},"security":[{"bearerAuth":[]}],"summary":"Create a share link","tags":["broadcasts"],"x-codeSamples":[{"label":"TypeScript SDK","lang":"typescript","source":"import { VocalStack } from '@vocalstack/sdk';\n\nconst vs = new VocalStack({ baseUrl: 'https://api.vocalstack.com', apiKey: process.env.VOCALSTACK_API_KEY });\n\nconst link = await vs.broadcasts.createShareLink('BROADCAST_ID', { label: 'Press' });\nconsole.log(link);\n"},{"label":"Python SDK","lang":"python","source":"import asyncio\nimport os\n\nfrom vocalstack import VocalStack\n\n\nasync def main() -> None:\n    async with VocalStack(\"https://api.vocalstack.com\", api_key=os.environ[\"VOCALSTACK_API_KEY\"]) as vs:\n        print(await vs.broadcasts.create_share_link(\"BROADCAST_ID\", label=\"Press\"))\n\n\nasyncio.run(main())\n"},{"label":"curl","lang":"shell","source":"curl -X POST \"https://api.vocalstack.com/v1/broadcasts/{id}/share-links\" \\\n  -H \"Authorization: Bearer $VOCALSTACK_API_KEY\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"label\":\"<label>\"}'"}],"x-vocalstack-auth":"userOrKey","x-vocalstack-scopes":["write"]}},"/v1/corrections/{id}/revert":{"post":{"description":"Undo one correction.","operationId":"corrections.revert","parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Correction"}}},"description":"OK"}},"security":[{"bearerAuth":[]}],"summary":"Revert a correction","tags":["corrections"],"x-codeSamples":[{"label":"TypeScript SDK","lang":"typescript","source":"import { VocalStack } from '@vocalstack/sdk';\n\nconst vs = new VocalStack({ baseUrl: 'https://api.vocalstack.com', apiKey: process.env.VOCALSTACK_API_KEY });\n\nawait vs.corrections.revert('CORRECTION_ID');\n"},{"label":"Python SDK","lang":"python","source":"import asyncio\nimport os\n\nfrom vocalstack import VocalStack\n\n\nasync def main() -> None:\n    async with VocalStack(\"https://api.vocalstack.com\", api_key=os.environ[\"VOCALSTACK_API_KEY\"]) as vs:\n        await vs.corrections.revert(\"CORRECTION_ID\")\n\n\nasyncio.run(main())\n"},{"label":"curl","lang":"shell","source":"curl -X POST \"https://api.vocalstack.com/v1/corrections/{id}/revert\" \\\n  -H \"Authorization: Bearer $VOCALSTACK_API_KEY\""}],"x-vocalstack-auth":"userOrKey","x-vocalstack-scopes":["write"]}},"/v1/health":{"get":{"description":"Liveness: 200 while this server is up.","operationId":"meta.health","responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Health"}}},"description":"OK"}},"summary":"Check health","tags":["meta"],"x-codeSamples":[{"label":"TypeScript SDK","lang":"typescript","source":"import { VocalStack } from '@vocalstack/sdk';\n\nconst vs = new VocalStack({ baseUrl: 'https://api.vocalstack.com', apiKey: process.env.VOCALSTACK_API_KEY });\n\nconst health = await vs.meta.health();\nconsole.log(health);\n"},{"label":"Python SDK","lang":"python","source":"import asyncio\nimport os\n\nfrom vocalstack import VocalStack\n\n\nasync def main() -> None:\n    async with VocalStack(\"https://api.vocalstack.com\", api_key=os.environ[\"VOCALSTACK_API_KEY\"]) as vs:\n        print(await vs.meta.health())\n\n\nasyncio.run(main())\n"},{"label":"curl","lang":"shell","source":"curl \"https://api.vocalstack.com/v1/health\" \\\n  -H \"Authorization: Bearer $VOCALSTACK_API_KEY\""}],"x-vocalstack-auth":"public","x-vocalstack-scopes":[]}},"/v1/languages":{"get":{"description":"Transcription, translation and voice lists with picker metadata.","operationId":"languages.list","responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LanguageLists"}}},"description":"OK"}},"summary":"List languages","tags":["languages"],"x-codeSamples":[{"label":"TypeScript SDK","lang":"typescript","source":"import { VocalStack } from '@vocalstack/sdk';\n\nconst vs = new VocalStack({ baseUrl: 'https://api.vocalstack.com', apiKey: process.env.VOCALSTACK_API_KEY });\n\nconst languages = await vs.languages.list();\nconsole.log(languages);\n"},{"label":"Python SDK","lang":"python","source":"import asyncio\nimport os\n\nfrom vocalstack import VocalStack\n\n\nasync def main() -> None:\n    async with VocalStack(\"https://api.vocalstack.com\", api_key=os.environ[\"VOCALSTACK_API_KEY\"]) as vs:\n        print(await vs.languages.list())\n\n\nasyncio.run(main())\n"},{"label":"curl","lang":"shell","source":"curl \"https://api.vocalstack.com/v1/languages\" \\\n  -H \"Authorization: Bearer $VOCALSTACK_API_KEY\""}],"x-vocalstack-auth":"public","x-vocalstack-scopes":[]}},"/v1/me":{"get":{"description":"Who the credential is: the acting principal, the person (`userId`) or API key (`apiKeyId`) behind it, the principals it may act for and a key's scopes.","operationId":"me.get","responses":{"200":{"content":{"application/json":{"schema":{"properties":{"apiKeyId":{"format":"uuid","type":["string","null"]},"displayName":{"type":"string"},"principalId":{"format":"uuid","type":"string"},"principals":{"items":{"format":"uuid","type":"string"},"type":"array"},"scopes":{"items":{"type":"string"},"type":"array"},"userId":{"format":"uuid","type":["string","null"]}},"required":["principalId","displayName","principals","scopes"],"type":"object"}}},"description":"OK"}},"security":[{"bearerAuth":[]}],"summary":"Get the current account","tags":["me"],"x-codeSamples":[{"label":"TypeScript SDK","lang":"typescript","source":"import { VocalStack } from '@vocalstack/sdk';\n\nconst vs = new VocalStack({ baseUrl: 'https://api.vocalstack.com', apiKey: process.env.VOCALSTACK_API_KEY });\n\nconst me = await vs.me.get();\nconsole.log(me.principalId, me.scopes);\n"},{"label":"Python SDK","lang":"python","source":"import asyncio\nimport os\n\nfrom vocalstack import VocalStack\n\n\nasync def main() -> None:\n    async with VocalStack(\"https://api.vocalstack.com\", api_key=os.environ[\"VOCALSTACK_API_KEY\"]) as vs:\n        me = await vs.me.get()\n        print(me[\"principalId\"], me[\"scopes\"])\n\n\nasyncio.run(main())\n"},{"label":"curl","lang":"shell","source":"curl \"https://api.vocalstack.com/v1/me\" \\\n  -H \"Authorization: Bearer $VOCALSTACK_API_KEY\""}],"x-vocalstack-auth":"userOrKey","x-vocalstack-scopes":[]}},"/v1/openapi.json":{"get":{"description":"This API's OpenAPI document, with this host in `servers`.","operationId":"meta.openapi","responses":{"200":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"OK"}},"summary":"Get the OpenAPI document","tags":["meta"],"x-codeSamples":[{"label":"TypeScript SDK","lang":"typescript","source":"import { VocalStack } from '@vocalstack/sdk';\n\nconst vs = new VocalStack({ baseUrl: 'https://api.vocalstack.com', apiKey: process.env.VOCALSTACK_API_KEY });\n\nconst doc = await vs.meta.openapi();\nconsole.log(Object.keys(doc.paths as object).length, 'paths');\n"},{"label":"Python SDK","lang":"python","source":"import asyncio\nimport os\n\nfrom vocalstack import VocalStack\n\n\nasync def main() -> None:\n    async with VocalStack(\"https://api.vocalstack.com\", api_key=os.environ[\"VOCALSTACK_API_KEY\"]) as vs:\n        doc = await vs.meta.openapi()\n        print(len(doc[\"paths\"]), \"paths\")\n\n\nasyncio.run(main())\n"},{"label":"curl","lang":"shell","source":"curl \"https://api.vocalstack.com/v1/openapi.json\" \\\n  -H \"Authorization: Bearer $VOCALSTACK_API_KEY\""}],"x-vocalstack-auth":"public","x-vocalstack-scopes":[]}},"/v1/realtime/capabilities":{"get":{"description":"What the realtime socket takes: supported languages, audio formats, and limits.","operationId":"realtime.capabilities","responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Capabilities"}}},"description":"OK"}},"summary":"Get realtime capabilities","tags":["realtime"],"x-codeSamples":[{"label":"TypeScript SDK","lang":"typescript","source":"import { VocalStack } from '@vocalstack/sdk';\n\nconst vs = new VocalStack({ baseUrl: 'https://api.vocalstack.com', apiKey: process.env.VOCALSTACK_API_KEY });\n\nconst caps = await vs.realtime.capabilities();\nconsole.log(caps.audio, caps.languages);\n"},{"label":"Python SDK","lang":"python","source":"import asyncio\nimport os\n\nfrom vocalstack import VocalStack\n\n\nasync def main() -> None:\n    async with VocalStack(\"https://api.vocalstack.com\", api_key=os.environ[\"VOCALSTACK_API_KEY\"]) as vs:\n        caps = await vs.realtime.capabilities()\n        print(caps.audio, caps.languages)\n\n\nasyncio.run(main())\n"},{"label":"curl","lang":"shell","source":"curl \"https://api.vocalstack.com/v1/realtime/capabilities\" \\\n  -H \"Authorization: Bearer $VOCALSTACK_API_KEY\""}],"x-vocalstack-auth":"public","x-vocalstack-scopes":[]}},"/v1/realtime/sessions":{"post":{"description":"Create a realtime session: a signed, stateless token for one WebSocket (`streamUrl`). Nothing is stored server-side.","operationId":"realtime.createSession","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SessionOptions"}}},"required":false},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateSessionResponse"}}},"description":"Created; connect to streamUrl before expiresAt."},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorBody"}}},"description":"invalid_options"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorBody"}}},"description":"internal"}},"security":[{"bearerAuth":[]}],"summary":"Create a realtime session","tags":["realtime"],"x-codeSamples":[{"label":"TypeScript SDK","lang":"typescript","source":"import { VocalStack } from '@vocalstack/sdk';\n\nconst vs = new VocalStack({ baseUrl: 'https://api.vocalstack.com', apiKey: process.env.VOCALSTACK_API_KEY });\n\n// Or vs.realtime.connect(options): creates the session and opens its socket.\nconst { sessionId, streamUrl, expiresAt } = await vs.realtime.createSession({\n  spokenLanguage: 'en',\n  translate: ['es'],\n});\nconsole.log(sessionId, streamUrl, expiresAt);\n"},{"label":"Python SDK","lang":"python","source":"import asyncio\nimport os\n\nfrom vocalstack import VocalStack\n\n\nasync def main() -> None:\n    async with VocalStack(\"https://api.vocalstack.com\", api_key=os.environ[\"VOCALSTACK_API_KEY\"]) as vs:\n        # Or vs.realtime.connect(...): creates the session and opens its socket.\n        session = await vs.realtime.create_session(spoken_language=\"en\", translate=[\"es\"])\n        print(session.session_id, session.stream_url, session.expires_at)\n\n\nasyncio.run(main())\n"},{"label":"curl","lang":"shell","source":"curl -X POST \"https://api.vocalstack.com/v1/realtime/sessions\" \\\n  -H \"Authorization: Bearer $VOCALSTACK_API_KEY\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"audio\":{\"channels\":0}}'"}],"x-errors":["invalid_options","internal","insufficient_credit","quota_exceeded","rate_limited"],"x-vocalstack-auth":"key","x-vocalstack-scopes":["write"]}},"/v1/recordings/{id}":{"get":{"description":"One recording, by id: for its owner, or any listener who can reach its broadcast or transcription.","operationId":"recordings.get","parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Recording"}}},"description":"OK"}},"summary":"Get a recording","tags":["recordings"],"x-codeSamples":[{"label":"TypeScript SDK","lang":"typescript","source":"import { VocalStack } from '@vocalstack/sdk';\n\nconst vs = new VocalStack({ baseUrl: 'https://api.vocalstack.com', apiKey: process.env.VOCALSTACK_API_KEY });\n\nconst recording = await vs.recordings.get('RECORDING_ID');\nconsole.log(recording.state);\n"},{"label":"Python SDK","lang":"python","source":"import asyncio\nimport os\n\nfrom vocalstack import VocalStack\n\n\nasync def main() -> None:\n    async with VocalStack(\"https://api.vocalstack.com\", api_key=os.environ[\"VOCALSTACK_API_KEY\"]) as vs:\n        recording = await vs.recordings.get(\"RECORDING_ID\")\n        print(recording.state)\n\n\nasyncio.run(main())\n"},{"label":"curl","lang":"shell","source":"curl \"https://api.vocalstack.com/v1/recordings/{id}\" \\\n  -H \"Authorization: Bearer $VOCALSTACK_API_KEY\""}],"x-vocalstack-auth":"listener","x-vocalstack-scopes":["read"]}},"/v1/recordings/{id}/archive":{"post":{"description":"Archive the recording: hidden, its data kept.","operationId":"recordings.archive","parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Recording"}}},"description":"OK"}},"security":[{"bearerAuth":[]}],"summary":"Archive a recording","tags":["recordings"],"x-codeSamples":[{"label":"TypeScript SDK","lang":"typescript","source":"import { VocalStack } from '@vocalstack/sdk';\n\nconst vs = new VocalStack({ baseUrl: 'https://api.vocalstack.com', apiKey: process.env.VOCALSTACK_API_KEY });\n\nawait vs.recordings.archive('RECORDING_ID');\n"},{"label":"Python SDK","lang":"python","source":"import asyncio\nimport os\n\nfrom vocalstack import VocalStack\n\n\nasync def main() -> None:\n    async with VocalStack(\"https://api.vocalstack.com\", api_key=os.environ[\"VOCALSTACK_API_KEY\"]) as vs:\n        await vs.recordings.archive(\"RECORDING_ID\")\n\n\nasyncio.run(main())\n"},{"label":"curl","lang":"shell","source":"curl -X POST \"https://api.vocalstack.com/v1/recordings/{id}/archive\" \\\n  -H \"Authorization: Bearer $VOCALSTACK_API_KEY\""}],"x-vocalstack-auth":"userOrKey","x-vocalstack-scopes":["write"]}},"/v1/recordings/{id}/audio":{"get":{"description":"302 to a freshly signed URL. kind=delivery (owner + listeners, 1 h), voice (a rendered spoken track, needs lang=, owner + listeners, 1 h; through a share link a language other than the spoken one needs the link's `allowTranslate`), source (owner or a link that allows downloads) or master (owner, 5 min). Through a share link EVERY kind needs the link's `allowDownload` — streaming a file and saving it are the same request, so a link with audio off plays nothing either. The delivery is Opus in two containers: format=ogg (default) and format=caf, the one Safari/iOS plays (WebKit rejects or mis-times Ogg); pick by canPlayType('audio/x-caf; codecs=opus'). format=mp3 is for SAVING (delivery and voice): the one container every player opens, encoded on first request and kept (kinds delivery_mp3, voice_mp3:<lang>). The recording's `media` lists which renditions are sealed (kinds delivery, delivery_caf, delivery_mp3); the CAF is remuxed on first request when it is not there yet.","operationId":"recordings.audio","parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string"}},{"in":"query","name":"kind","schema":{"enum":["delivery","voice","source","master"],"type":"string"}},{"description":"kind=voice: which spoken track","in":"query","name":"lang","schema":{"type":"string"}},{"in":"query","name":"format","schema":{"default":"ogg","enum":["ogg","caf","mp3"],"type":"string"}}],"responses":{"302":{"description":"Redirect"}},"summary":"Download a recording's audio","tags":["recordings"],"x-codeSamples":[{"label":"TypeScript SDK","lang":"typescript","source":"import { VocalStack } from '@vocalstack/sdk';\n\nconst vs = new VocalStack({ baseUrl: 'https://api.vocalstack.com', apiKey: process.env.VOCALSTACK_API_KEY });\n\nconst url = await vs.recordings.audioUrl('RECORDING_ID'); // a signed, short-lived media URL\nconsole.log(url);\n"},{"label":"Python SDK","lang":"python","source":"import asyncio\nimport os\n\nfrom vocalstack import VocalStack\n\n\nasync def main() -> None:\n    async with VocalStack(\"https://api.vocalstack.com\", api_key=os.environ[\"VOCALSTACK_API_KEY\"]) as vs:\n        print(await vs.recordings.audio_url(\"RECORDING_ID\"))  # a signed, short-lived media URL\n\n\nasyncio.run(main())\n"},{"label":"curl","lang":"shell","source":"curl \"https://api.vocalstack.com/v1/recordings/{id}/audio\" \\\n  -H \"Authorization: Bearer $VOCALSTACK_API_KEY\""}],"x-vocalstack-auth":"listener","x-vocalstack-scopes":["read"]}},"/v1/recordings/{id}/audio/clip":{"get":{"description":"A stretch of the audio as WAV (16-bit, 16 kHz, mono), available while the recording is still open — for replaying a line during live correction. Owner + listeners; at most 120 s. Through a share link it needs the link's `allowDownload`, like every other way of asking for the audio.","operationId":"recordings.clip","parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string"}},{"description":"media seconds","in":"query","name":"startS","required":true,"schema":{"type":"number"}},{"description":"media seconds, > startS","in":"query","name":"endS","required":true,"schema":{"type":"number"}}],"responses":{"200":{"description":"audio/wav"}},"summary":"Get an audio clip","tags":["recordings"],"x-codeSamples":[{"label":"TypeScript SDK","lang":"typescript","source":"import { VocalStack } from '@vocalstack/sdk';\n\nconst vs = new VocalStack({ baseUrl: 'https://api.vocalstack.com', apiKey: process.env.VOCALSTACK_API_KEY });\n\nconst res = await vs.recordings.clip('RECORDING_ID', 12.5, 20);\nconsole.log(res.headers.get('content-type'), (await res.arrayBuffer()).byteLength);\n"},{"label":"Python SDK","lang":"python","source":"import asyncio\nimport os\n\nfrom vocalstack import VocalStack\n\n\nasync def main() -> None:\n    async with VocalStack(\"https://api.vocalstack.com\", api_key=os.environ[\"VOCALSTACK_API_KEY\"]) as vs:\n        audio = await vs.recordings.clip(\"RECORDING_ID\", 12.5, 20)\n        print(len(audio), \"bytes\")\n\n\nasyncio.run(main())\n"},{"label":"curl","lang":"shell","source":"curl \"https://api.vocalstack.com/v1/recordings/{id}/audio/clip?startS=<startS>&endS=<endS>\" \\\n  -H \"Authorization: Bearer $VOCALSTACK_API_KEY\""}],"x-errors":["not_found","invalid_options"],"x-vocalstack-auth":"listener","x-vocalstack-scopes":["read"]}},"/v1/recordings/{id}/corrections":{"get":{"description":"Every correction made to the recording's transcript.","operationId":"corrections.list","parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"properties":{"corrections":{"items":{"$ref":"#/components/schemas/Correction"},"type":"array"}},"type":"object"}}},"description":"OK"}},"security":[{"bearerAuth":[]}],"summary":"List corrections","tags":["corrections"],"x-codeSamples":[{"label":"TypeScript SDK","lang":"typescript","source":"import { VocalStack } from '@vocalstack/sdk';\n\nconst vs = new VocalStack({ baseUrl: 'https://api.vocalstack.com', apiKey: process.env.VOCALSTACK_API_KEY });\n\nconst corrections = await vs.corrections.list('RECORDING_ID');\nconsole.log(corrections);\n"},{"label":"Python SDK","lang":"python","source":"import asyncio\nimport os\n\nfrom vocalstack import VocalStack\n\n\nasync def main() -> None:\n    async with VocalStack(\"https://api.vocalstack.com\", api_key=os.environ[\"VOCALSTACK_API_KEY\"]) as vs:\n        print(await vs.corrections.list(\"RECORDING_ID\"))\n\n\nasyncio.run(main())\n"},{"label":"curl","lang":"shell","source":"curl \"https://api.vocalstack.com/v1/recordings/{id}/corrections\" \\\n  -H \"Authorization: Bearer $VOCALSTACK_API_KEY\""}],"x-vocalstack-auth":"userOrKey","x-vocalstack-scopes":["read"]},"post":{"description":"Apply a batch of word edits and line rewrites atomically; every target must be settled. Each correction says whether its line is voiced as corrected (`voiced`): the voice may only say what was said, so a correction is voiced only when every word it says is a word of the recognizer's text (punctuation, case, Latin accents and number formatting aside) or of a fresh reading of the line's audio, taken automatically when the recognizer's text is not enough, and it drops no negation the audio has. A line spanning several segments lists neighbouring segments only (400 `invalid_options` otherwise). Any other correction is saved and read, but voice clips and voice tracks keep speaking what the recognizer heard (`notVoicedReason: rewrite`). VocalStack speaks transcripts, never text a caller wrote.","operationId":"corrections.apply","parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CorrectionBatch"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"properties":{"corrections":{"items":{"$ref":"#/components/schemas/Correction"},"type":"array"}},"type":"object"}}},"description":"OK"}},"security":[{"bearerAuth":[]}],"summary":"Apply corrections","tags":["corrections"],"x-codeSamples":[{"label":"TypeScript SDK","lang":"typescript","source":"import { VocalStack } from '@vocalstack/sdk';\n\nconst vs = new VocalStack({ baseUrl: 'https://api.vocalstack.com', apiKey: process.env.VOCALSTACK_API_KEY });\n\nconst applied = await vs.corrections.apply('RECORDING_ID', {\n  edits: [{ seq: 4, wordIdx: 2, to: 'VocalStack' }],\n  lines: [{ seq: 7, to: 'The meeting starts at nine.' }],\n});\nconsole.log(applied);\n"},{"label":"Python SDK","lang":"python","source":"import asyncio\nimport os\n\nfrom vocalstack import VocalStack\n\n\nasync def main() -> None:\n    async with VocalStack(\"https://api.vocalstack.com\", api_key=os.environ[\"VOCALSTACK_API_KEY\"]) as vs:\n        applied = await vs.corrections.apply(\n            \"RECORDING_ID\",\n            edits=[{\"seq\": 4, \"wordIdx\": 2, \"to\": \"VocalStack\"}],\n            lines=[{\"seq\": 7, \"to\": \"The meeting starts at nine.\"}],\n        )\n        print(applied)\n\n\nasyncio.run(main())\n"},{"label":"curl","lang":"shell","source":"curl -X POST \"https://api.vocalstack.com/v1/recordings/{id}/corrections\" \\\n  -H \"Authorization: Bearer $VOCALSTACK_API_KEY\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"edits\":[{\"seq\":0,\"to\":\"<to>\",\"wordIdx\":0}]}'"}],"x-errors":["invalid_state","invalid_options"],"x-vocalstack-auth":"userOrKey","x-vocalstack-scopes":["write"]}},"/v1/recordings/{id}/events":{"get":{"description":"The recording's durable event log in order, each frame stamped with the wall time it was written — replays the recording exactly as the host saw it. Owner only.","operationId":"recordings.events","parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string"}},{"in":"query","name":"after","schema":{"type":"integer"}},{"in":"query","name":"limit","schema":{"default":5000,"maximum":20000,"type":"integer"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RecordingEvents"}}},"description":"OK"}},"security":[{"bearerAuth":[]}],"summary":"Get a recording's event log","tags":["recordings"],"x-codeSamples":[{"label":"TypeScript SDK","lang":"typescript","source":"import { VocalStack } from '@vocalstack/sdk';\n\nconst vs = new VocalStack({ baseUrl: 'https://api.vocalstack.com', apiKey: process.env.VOCALSTACK_API_KEY });\n\nconst events = await vs.recordings.events('RECORDING_ID', { limit: 50 });\nconsole.log(events);\n"},{"label":"Python SDK","lang":"python","source":"import asyncio\nimport os\n\nfrom vocalstack import VocalStack\n\n\nasync def main() -> None:\n    async with VocalStack(\"https://api.vocalstack.com\", api_key=os.environ[\"VOCALSTACK_API_KEY\"]) as vs:\n        print(await vs.recordings.events(\"RECORDING_ID\", limit=50))\n\n\nasyncio.run(main())\n"},{"label":"curl","lang":"shell","source":"curl \"https://api.vocalstack.com/v1/recordings/{id}/events\" \\\n  -H \"Authorization: Bearer $VOCALSTACK_API_KEY\""}],"x-vocalstack-auth":"userOrKey","x-vocalstack-scopes":["read"]}},"/v1/recordings/{id}/export":{"get":{"description":"A real file: JSON, SRT, XLSX (Excel workbook) or PDF, corrections included. The PDF of a finished recording reads in paragraphs (built by the engine after finalize; a download that finds them missing or stale has them rebuilt first, waiting up to a minute). Owner, or a share link (`shareCode`) that allows export. While the job runs the export is partial (file name suffixed, `partial` flagged). `lang` makes the file read in that language — the translation the reader sees on screen, one line per sentence, sentences not yet translated are translated first; the file name carries the language and JSON's `translated` is true. The spoken language, or no `lang`, is the transcript as spoken. A share link with Translate off may only have that.","operationId":"recordings.export","parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string"}},{"in":"query","name":"format","required":true,"schema":{"$ref":"#/components/schemas/ExportFormat"}},{"in":"query","name":"lang","schema":{"type":"string"}},{"in":"query","name":"shareCode","schema":{"type":"string"}}],"responses":{"200":{"description":"The file (Content-Disposition: attachment)"}},"summary":"Export a recording","tags":["recordings"],"x-codeSamples":[{"label":"TypeScript SDK","lang":"typescript","source":"import { VocalStack } from '@vocalstack/sdk';\n\nconst vs = new VocalStack({ baseUrl: 'https://api.vocalstack.com', apiKey: process.env.VOCALSTACK_API_KEY });\n\nconst res = await vs.recordings.export('RECORDING_ID', 'srt', { lang: 'es' });\nconsole.log(await res.text());\n"},{"label":"Python SDK","lang":"python","source":"import asyncio\nimport os\n\nfrom vocalstack import VocalStack\n\n\nasync def main() -> None:\n    async with VocalStack(\"https://api.vocalstack.com\", api_key=os.environ[\"VOCALSTACK_API_KEY\"]) as vs:\n        srt = await vs.recordings.export(\"RECORDING_ID\", \"srt\", lang=\"es\")\n        print(srt.decode())\n\n\nasyncio.run(main())\n"},{"label":"curl","lang":"shell","source":"curl \"https://api.vocalstack.com/v1/recordings/{id}/export?format=<format>\" \\\n  -H \"Authorization: Bearer $VOCALSTACK_API_KEY\""}],"x-errors":["forbidden","invalid_options"],"x-vocalstack-auth":"listener","x-vocalstack-scopes":["read"]}},"/v1/recordings/{id}/job":{"get":{"description":"The recording's transcription job: its state and progress (done/total segments, media seconds, pct).","operationId":"jobs.get","parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TranscriptionJob"}}},"description":"OK"}},"security":[{"bearerAuth":[]}],"summary":"Get a job","tags":["jobs"],"x-codeSamples":[{"label":"TypeScript SDK","lang":"typescript","source":"import { VocalStack } from '@vocalstack/sdk';\n\nconst vs = new VocalStack({ baseUrl: 'https://api.vocalstack.com', apiKey: process.env.VOCALSTACK_API_KEY });\n\nconst job = await vs.jobs.get('RECORDING_ID');\nconsole.log(job.state); // queued, fetching, transcribing, done, failed or cropping\n"},{"label":"Python SDK","lang":"python","source":"import asyncio\nimport os\n\nfrom vocalstack import VocalStack\n\n\nasync def main() -> None:\n    async with VocalStack(\"https://api.vocalstack.com\", api_key=os.environ[\"VOCALSTACK_API_KEY\"]) as vs:\n        job = await vs.jobs.get(\"RECORDING_ID\")\n        print(job.state)  # queued, fetching, transcribing, done, failed or cropping\n\n\nasyncio.run(main())\n"},{"label":"curl","lang":"shell","source":"curl \"https://api.vocalstack.com/v1/recordings/{id}/job\" \\\n  -H \"Authorization: Bearer $VOCALSTACK_API_KEY\""}],"x-vocalstack-auth":"userOrKey","x-vocalstack-scopes":["read"]}},"/v1/recordings/{id}/job/preview":{"get":{"description":"A job waiting in `cropping`: the media for the trim step to draw the waveform from in the browser. From a link (`preview: true`), the file the server fetched — read once, kept until the job is started or for ten minutes, never stored; from an upload, its stored source streamed through. 404 once a link's copy is gone.","operationId":"jobs.preview","parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"The fetched media, as it came."}},"security":[{"bearerAuth":[]}],"summary":"Get a job's preview media","tags":["jobs"],"x-codeSamples":[{"label":"TypeScript SDK","lang":"typescript","source":"import { VocalStack } from '@vocalstack/sdk';\n\nconst vs = new VocalStack({ baseUrl: 'https://api.vocalstack.com', apiKey: process.env.VOCALSTACK_API_KEY });\n\nconst res = await vs.jobs.preview('RECORDING_ID');\nconsole.log(res.headers.get('content-type'));\n"},{"label":"Python SDK","lang":"python","source":"import asyncio\nimport os\n\nfrom vocalstack import VocalStack\n\n\nasync def main() -> None:\n    async with VocalStack(\"https://api.vocalstack.com\", api_key=os.environ[\"VOCALSTACK_API_KEY\"]) as vs:\n        audio = await vs.jobs.preview(\"RECORDING_ID\")\n        print(len(audio), \"bytes\")\n\n\nasyncio.run(main())\n"},{"label":"curl","lang":"shell","source":"curl \"https://api.vocalstack.com/v1/recordings/{id}/job/preview\" \\\n  -H \"Authorization: Bearer $VOCALSTACK_API_KEY\""}],"x-errors":["not_found"],"x-vocalstack-auth":"userOrKey","x-vocalstack-scopes":["read"]}},"/v1/recordings/{id}/job/retry":{"post":{"description":"Re-queue a failed job from its stored source.","operationId":"jobs.retry","parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TranscriptionJob"}}},"description":"OK"}},"security":[{"bearerAuth":[]}],"summary":"Retry a job","tags":["jobs"],"x-codeSamples":[{"label":"TypeScript SDK","lang":"typescript","source":"import { VocalStack } from '@vocalstack/sdk';\n\nconst vs = new VocalStack({ baseUrl: 'https://api.vocalstack.com', apiKey: process.env.VOCALSTACK_API_KEY });\n\nconst job = await vs.jobs.retry('RECORDING_ID');\nconsole.log(job.state);\n"},{"label":"Python SDK","lang":"python","source":"import asyncio\nimport os\n\nfrom vocalstack import VocalStack\n\n\nasync def main() -> None:\n    async with VocalStack(\"https://api.vocalstack.com\", api_key=os.environ[\"VOCALSTACK_API_KEY\"]) as vs:\n        print((await vs.jobs.retry(\"RECORDING_ID\")).state)\n\n\nasyncio.run(main())\n"},{"label":"curl","lang":"shell","source":"curl -X POST \"https://api.vocalstack.com/v1/recordings/{id}/job/retry\" \\\n  -H \"Authorization: Bearer $VOCALSTACK_API_KEY\""}],"x-errors":["invalid_state","insufficient_credit","quota_exceeded","rate_limited"],"x-vocalstack-auth":"userOrKey","x-vocalstack-scopes":["recordings:publish"]}},"/v1/recordings/{id}/job/start":{"post":{"description":"Release a job waiting in `cropping` (created with `crop: true`): the window of the source to transcribe, in seconds — an omitted bound is the start or the end of the file — then the job is queued. Timestamps in the transcript count from the window's start.","operationId":"jobs.start","parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/StartJob"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TranscriptionJob"}}},"description":"OK"}},"security":[{"bearerAuth":[]}],"summary":"Start a cropped job","tags":["jobs"],"x-codeSamples":[{"label":"TypeScript SDK","lang":"typescript","source":"import { VocalStack } from '@vocalstack/sdk';\n\nconst vs = new VocalStack({ baseUrl: 'https://api.vocalstack.com', apiKey: process.env.VOCALSTACK_API_KEY });\n\n// Release a job waiting in `cropping`: transcribe 0:30 to 5:00.\nconst job = await vs.jobs.start('RECORDING_ID', { startS: 30, endS: 300 });\nconsole.log(job.state);\n"},{"label":"Python SDK","lang":"python","source":"import asyncio\nimport os\n\nfrom vocalstack import VocalStack\n\n\nasync def main() -> None:\n    async with VocalStack(\"https://api.vocalstack.com\", api_key=os.environ[\"VOCALSTACK_API_KEY\"]) as vs:\n        # Release a job waiting in `cropping`: transcribe 0:30 to 5:00.\n        job = await vs.jobs.start(\"RECORDING_ID\", start_s=30, end_s=300)\n        print(job.state)\n\n\nasyncio.run(main())\n"},{"label":"curl","lang":"shell","source":"curl -X POST \"https://api.vocalstack.com/v1/recordings/{id}/job/start\" \\\n  -H \"Authorization: Bearer $VOCALSTACK_API_KEY\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"endS\":0}'"}],"x-errors":["invalid_options","invalid_state","insufficient_credit","quota_exceeded","rate_limited"],"x-vocalstack-auth":"userOrKey","x-vocalstack-scopes":["recordings:publish"]}},"/v1/recordings/{id}/media-token":{"get":{"description":"The owner's credential for this recording's media URLs: pass it as `media=` to `/audio`, `/audio/clip` and `/voice/{sentence}` in place of `access_token`. It reads this recording's media and nothing else, and it is the same string for a whole UTC day (valid until the end of the next one) — an access token rotates every few minutes, and a credential in the URL is part of the browser's cache key, so clips fetched with one were fetched again at every rotation.","operationId":"recordings.mediaToken","parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"properties":{"expiresAt":{"format":"date-time","type":"string"},"token":{"type":"string"}},"required":["token","expiresAt"],"type":"object"}}},"description":"OK"}},"security":[{"bearerAuth":[]}],"summary":"Get a media token","tags":["recordings"],"x-codeSamples":[{"label":"TypeScript SDK","lang":"typescript","source":"import { VocalStack } from '@vocalstack/sdk';\n\nconst vs = new VocalStack({ baseUrl: 'https://api.vocalstack.com', apiKey: process.env.VOCALSTACK_API_KEY });\n\nconst { token, expiresAt } = await vs.recordings.mediaToken('RECORDING_ID');\nconsole.log(token, expiresAt);\n"},{"label":"Python SDK","lang":"python","source":"import asyncio\nimport os\n\nfrom vocalstack import VocalStack\n\n\nasync def main() -> None:\n    async with VocalStack(\"https://api.vocalstack.com\", api_key=os.environ[\"VOCALSTACK_API_KEY\"]) as vs:\n        print(await vs.recordings.media_token(\"RECORDING_ID\"))\n\n\nasyncio.run(main())\n"},{"label":"curl","lang":"shell","source":"curl \"https://api.vocalstack.com/v1/recordings/{id}/media-token\" \\\n  -H \"Authorization: Bearer $VOCALSTACK_API_KEY\""}],"x-vocalstack-auth":"userOrKey","x-vocalstack-scopes":["read"]}},"/v1/recordings/{id}/pause":{"post":{"description":"Pause capture. Idempotent.","operationId":"recordings.pause","parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Recording"}}},"description":"OK"}},"security":[{"bearerAuth":[]}],"summary":"Pause a recording","tags":["recordings"],"x-codeSamples":[{"label":"TypeScript SDK","lang":"typescript","source":"import { VocalStack } from '@vocalstack/sdk';\n\nconst vs = new VocalStack({ baseUrl: 'https://api.vocalstack.com', apiKey: process.env.VOCALSTACK_API_KEY });\n\nawait vs.recordings.pause('RECORDING_ID');\n"},{"label":"Python SDK","lang":"python","source":"import asyncio\nimport os\n\nfrom vocalstack import VocalStack\n\n\nasync def main() -> None:\n    async with VocalStack(\"https://api.vocalstack.com\", api_key=os.environ[\"VOCALSTACK_API_KEY\"]) as vs:\n        await vs.recordings.pause(\"RECORDING_ID\")\n\n\nasyncio.run(main())\n"},{"label":"curl","lang":"shell","source":"curl -X POST \"https://api.vocalstack.com/v1/recordings/{id}/pause\" \\\n  -H \"Authorization: Bearer $VOCALSTACK_API_KEY\""}],"x-vocalstack-auth":"userOrKey","x-vocalstack-scopes":["recordings:publish"]}},"/v1/recordings/{id}/publish-token":{"post":{"description":"Re-attach a publisher (reconnect or another device; takeover=true demotes the current one).","operationId":"recordings.publishToken","parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublishGrant"}}},"description":"OK"}},"security":[{"bearerAuth":[]}],"summary":"Create a publish token","tags":["recordings"],"x-codeSamples":[{"label":"TypeScript SDK","lang":"typescript","source":"import { VocalStack } from '@vocalstack/sdk';\n\nconst vs = new VocalStack({ baseUrl: 'https://api.vocalstack.com', apiKey: process.env.VOCALSTACK_API_KEY });\n\n// A new publisher socket for a recording that is already live.\nconst grant = await vs.recordings.publishToken('RECORDING_ID', { takeover: true });\nconst stream = await vs.recordings.publish(grant);\nconsole.log(stream.sessionId);\n"},{"label":"Python SDK","lang":"python","source":"import asyncio\nimport os\n\nfrom vocalstack import VocalStack\n\n\nasync def main() -> None:\n    async with VocalStack(\"https://api.vocalstack.com\", api_key=os.environ[\"VOCALSTACK_API_KEY\"]) as vs:\n        # A new publisher socket for a recording that is already live.\n        grant = await vs.recordings.publish_token(\"RECORDING_ID\", takeover=True)\n        stream = await vs.recordings.publish(grant)\n        print(stream.session_id)\n\n\nasyncio.run(main())\n"},{"label":"curl","lang":"shell","source":"curl -X POST \"https://api.vocalstack.com/v1/recordings/{id}/publish-token\" \\\n  -H \"Authorization: Bearer $VOCALSTACK_API_KEY\""}],"x-errors":["conflict","invalid_state"],"x-vocalstack-auth":"userOrKey","x-vocalstack-scopes":["recordings:publish"]}},"/v1/recordings/{id}/purge":{"post":{"description":"Delete an archived recording for good.","operationId":"recordings.purge","parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK"}},"security":[{"bearerAuth":[]}],"summary":"Purge a recording","tags":["recordings"],"x-codeSamples":[{"label":"TypeScript SDK","lang":"typescript","source":"import { VocalStack } from '@vocalstack/sdk';\n\nconst vs = new VocalStack({ baseUrl: 'https://api.vocalstack.com', apiKey: process.env.VOCALSTACK_API_KEY });\n\nawait vs.recordings.purge('RECORDING_ID');\n"},{"label":"Python SDK","lang":"python","source":"import asyncio\nimport os\n\nfrom vocalstack import VocalStack\n\n\nasync def main() -> None:\n    async with VocalStack(\"https://api.vocalstack.com\", api_key=os.environ[\"VOCALSTACK_API_KEY\"]) as vs:\n        await vs.recordings.purge(\"RECORDING_ID\")\n\n\nasyncio.run(main())\n"},{"label":"curl","lang":"shell","source":"curl -X POST \"https://api.vocalstack.com/v1/recordings/{id}/purge\" \\\n  -H \"Authorization: Bearer $VOCALSTACK_API_KEY\""}],"x-vocalstack-auth":"userOrKey","x-vocalstack-scopes":["write"]}},"/v1/recordings/{id}/restore":{"post":{"description":"Bring an archived recording back.","operationId":"recordings.restore","parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Recording"}}},"description":"OK"}},"security":[{"bearerAuth":[]}],"summary":"Restore a recording","tags":["recordings"],"x-codeSamples":[{"label":"TypeScript SDK","lang":"typescript","source":"import { VocalStack } from '@vocalstack/sdk';\n\nconst vs = new VocalStack({ baseUrl: 'https://api.vocalstack.com', apiKey: process.env.VOCALSTACK_API_KEY });\n\nawait vs.recordings.restore('RECORDING_ID');\n"},{"label":"Python SDK","lang":"python","source":"import asyncio\nimport os\n\nfrom vocalstack import VocalStack\n\n\nasync def main() -> None:\n    async with VocalStack(\"https://api.vocalstack.com\", api_key=os.environ[\"VOCALSTACK_API_KEY\"]) as vs:\n        await vs.recordings.restore(\"RECORDING_ID\")\n\n\nasyncio.run(main())\n"},{"label":"curl","lang":"shell","source":"curl -X POST \"https://api.vocalstack.com/v1/recordings/{id}/restore\" \\\n  -H \"Authorization: Bearer $VOCALSTACK_API_KEY\""}],"x-vocalstack-auth":"userOrKey","x-vocalstack-scopes":["write"]}},"/v1/recordings/{id}/resume":{"post":{"description":"Resume a paused recording.","operationId":"recordings.resume","parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Recording"}}},"description":"OK"}},"security":[{"bearerAuth":[]}],"summary":"Resume a recording","tags":["recordings"],"x-codeSamples":[{"label":"TypeScript SDK","lang":"typescript","source":"import { VocalStack } from '@vocalstack/sdk';\n\nconst vs = new VocalStack({ baseUrl: 'https://api.vocalstack.com', apiKey: process.env.VOCALSTACK_API_KEY });\n\nawait vs.recordings.resume('RECORDING_ID');\n"},{"label":"Python SDK","lang":"python","source":"import asyncio\nimport os\n\nfrom vocalstack import VocalStack\n\n\nasync def main() -> None:\n    async with VocalStack(\"https://api.vocalstack.com\", api_key=os.environ[\"VOCALSTACK_API_KEY\"]) as vs:\n        await vs.recordings.resume(\"RECORDING_ID\")\n\n\nasyncio.run(main())\n"},{"label":"curl","lang":"shell","source":"curl -X POST \"https://api.vocalstack.com/v1/recordings/{id}/resume\" \\\n  -H \"Authorization: Bearer $VOCALSTACK_API_KEY\""}],"x-vocalstack-auth":"userOrKey","x-vocalstack-scopes":["recordings:publish"]}},"/v1/recordings/{id}/retranscribe":{"post":{"description":"Read one line's audio again with the language forced, and apply what the recognizer hears as a line rewrite. A line heard in the wrong language cannot be repaired word by word — every word is wrong together — so this is the repair for it. The segments must be settled, like every other correction; the answer is the same `line` corrections a hand-typed rewrite produces, with the same revert behind them. 503 `engine_unavailable` means the audio for that line has not reached this machine yet and the ask is worth repeating.","operationId":"corrections.retranscribe","parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RetranscribeLine"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"properties":{"corrections":{"items":{"$ref":"#/components/schemas/Correction"},"type":"array"}},"type":"object"}}},"description":"OK"}},"security":[{"bearerAuth":[]}],"summary":"Retranscribe a line","tags":["corrections"],"x-codeSamples":[{"label":"TypeScript SDK","lang":"typescript","source":"import { VocalStack } from '@vocalstack/sdk';\n\nconst vs = new VocalStack({ baseUrl: 'https://api.vocalstack.com', apiKey: process.env.VOCALSTACK_API_KEY });\n\nconst corrections = await vs.corrections.retranscribe('RECORDING_ID', { seqs: [12, 13], lang: 'ro' });\nconsole.log(corrections);\n"},{"label":"Python SDK","lang":"python","source":"import asyncio\nimport os\n\nfrom vocalstack import VocalStack\n\n\nasync def main() -> None:\n    async with VocalStack(\"https://api.vocalstack.com\", api_key=os.environ[\"VOCALSTACK_API_KEY\"]) as vs:\n        print(await vs.corrections.retranscribe(\"RECORDING_ID\", seqs=[12, 13], lang=\"ro\"))\n\n\nasyncio.run(main())\n"},{"label":"curl","lang":"shell","source":"curl -X POST \"https://api.vocalstack.com/v1/recordings/{id}/retranscribe\" \\\n  -H \"Authorization: Bearer $VOCALSTACK_API_KEY\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"lang\":\"<lang>\",\"seqs\":[0]}'"}],"x-errors":["invalid_state","invalid_options","engine_unavailable","not_found","insufficient_credit"],"x-vocalstack-auth":"userOrKey","x-vocalstack-scopes":["write"]}},"/v1/recordings/{id}/settings":{"patch":{"description":"Mid-recording capture settings (mode, language, labels).","operationId":"recordings.settings","parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SettingsPatch"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Recording"}}},"description":"OK"}},"security":[{"bearerAuth":[]}],"summary":"Update recording settings","tags":["recordings"],"x-codeSamples":[{"label":"TypeScript SDK","lang":"typescript","source":"import { VocalStack } from '@vocalstack/sdk';\n\nconst vs = new VocalStack({ baseUrl: 'https://api.vocalstack.com', apiKey: process.env.VOCALSTACK_API_KEY });\n\nconst recording = await vs.recordings.settings('RECORDING_ID', { spokenLanguage: 'es', speakerLabels: 'on' });\nconsole.log(recording);\n"},{"label":"Python SDK","lang":"python","source":"import asyncio\nimport os\n\nfrom vocalstack import VocalStack\n\n\nasync def main() -> None:\n    async with VocalStack(\"https://api.vocalstack.com\", api_key=os.environ[\"VOCALSTACK_API_KEY\"]) as vs:\n        print(await vs.recordings.settings(\"RECORDING_ID\", spoken_language=\"es\", speaker_labels=\"on\"))\n\n\nasyncio.run(main())\n"},{"label":"curl","lang":"shell","source":"curl -X PATCH \"https://api.vocalstack.com/v1/recordings/{id}/settings\" \\\n  -H \"Authorization: Bearer $VOCALSTACK_API_KEY\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"confidence\":{\"hideBelow\":0}}'"}],"x-vocalstack-auth":"userOrKey","x-vocalstack-scopes":["recordings:publish"]}},"/v1/recordings/{id}/speakers/{idx}":{"put":{"description":"Name a speaker of this recording.","operationId":"recordings.nameSpeaker","parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string"}},{"in":"path","name":"idx","required":true,"schema":{"type":"integer"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/NameSpeaker"}}},"required":true},"responses":{"200":{"description":"OK"}},"security":[{"bearerAuth":[]}],"summary":"Name a speaker","tags":["recordings"],"x-codeSamples":[{"label":"TypeScript SDK","lang":"typescript","source":"import { VocalStack } from '@vocalstack/sdk';\n\nconst vs = new VocalStack({ baseUrl: 'https://api.vocalstack.com', apiKey: process.env.VOCALSTACK_API_KEY });\n\nawait vs.recordings.nameSpeaker('RECORDING_ID', 0, 'Ana');\n"},{"label":"Python SDK","lang":"python","source":"import asyncio\nimport os\n\nfrom vocalstack import VocalStack\n\n\nasync def main() -> None:\n    async with VocalStack(\"https://api.vocalstack.com\", api_key=os.environ[\"VOCALSTACK_API_KEY\"]) as vs:\n        await vs.recordings.name_speaker(\"RECORDING_ID\", 0, \"Ana\")\n\n\nasyncio.run(main())\n"},{"label":"curl","lang":"shell","source":"curl -X PUT \"https://api.vocalstack.com/v1/recordings/{id}/speakers/{idx}\" \\\n  -H \"Authorization: Bearer $VOCALSTACK_API_KEY\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"name\":\"<name>\"}'"}],"x-vocalstack-auth":"userOrKey","x-vocalstack-scopes":["write"]}},"/v1/recordings/{id}/stop":{"post":{"description":"End capture; finalize runs asynchronously. Only from paused.","operationId":"recordings.stop","parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/StopRequest"}}},"required":false},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Recording"}}},"description":"OK"}},"security":[{"bearerAuth":[]}],"summary":"Stop a recording","tags":["recordings"],"x-codeSamples":[{"label":"TypeScript SDK","lang":"typescript","source":"import { VocalStack } from '@vocalstack/sdk';\n\nconst vs = new VocalStack({ baseUrl: 'https://api.vocalstack.com', apiKey: process.env.VOCALSTACK_API_KEY });\n\nawait vs.recordings.stop('RECORDING_ID'); // only from paused\n"},{"label":"Python SDK","lang":"python","source":"import asyncio\nimport os\n\nfrom vocalstack import VocalStack\n\n\nasync def main() -> None:\n    async with VocalStack(\"https://api.vocalstack.com\", api_key=os.environ[\"VOCALSTACK_API_KEY\"]) as vs:\n        await vs.recordings.stop(\"RECORDING_ID\")  # only from paused\n\n\nasyncio.run(main())\n"},{"label":"curl","lang":"shell","source":"curl -X POST \"https://api.vocalstack.com/v1/recordings/{id}/stop\" \\\n  -H \"Authorization: Bearer $VOCALSTACK_API_KEY\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"reason\":\"<reason>\"}'"}],"x-errors":["invalid_state"],"x-vocalstack-auth":"userOrKey","x-vocalstack-scopes":["recordings:publish"]}},"/v1/recordings/{id}/summarize":{"post":{"description":"Ask for the recording's AI title and summary. The engine writes them after finalize on its own; call this when a finished recording has none (or its pass failed) and poll the recording: `ready` carries them, `pending` means the pass is queued or running, `unavailable` means this server has no summariser.","operationId":"recordings.summarize","parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string"}},{"description":"Makes a retry safe: a request repeated with the same key and the same body within 24 hours returns the first response (with `Idempotent-Replayed: true`) instead of running again. The same key with a different request is 422 `idempotency_key_reused`; a retry while the first is still running is 409 `conflict`. Only a successful (2xx) response is remembered. Keys are per account.","in":"header","name":"Idempotency-Key","required":false,"schema":{"maxLength":255,"minLength":1,"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SummaryStatus"}}},"description":"Where the summary stands"}},"security":[{"bearerAuth":[]}],"summary":"Request a summary","tags":["recordings"],"x-codeSamples":[{"label":"TypeScript SDK","lang":"typescript","source":"import { VocalStack } from '@vocalstack/sdk';\n\nconst vs = new VocalStack({ baseUrl: 'https://api.vocalstack.com', apiKey: process.env.VOCALSTACK_API_KEY });\n\nawait vs.recordings.summarize('RECORDING_ID');\nconst summary = await vs.recordings.summary('RECORDING_ID');\nconsole.log(summary);\n"},{"label":"Python SDK","lang":"python","source":"import asyncio\nimport os\n\nfrom vocalstack import VocalStack\n\n\nasync def main() -> None:\n    async with VocalStack(\"https://api.vocalstack.com\", api_key=os.environ[\"VOCALSTACK_API_KEY\"]) as vs:\n        await vs.recordings.summarize(\"RECORDING_ID\")\n        print(await vs.recordings.summary(\"RECORDING_ID\"))\n\n\nasyncio.run(main())\n"},{"label":"curl","lang":"shell","source":"curl -X POST \"https://api.vocalstack.com/v1/recordings/{id}/summarize\" \\\n  -H \"Authorization: Bearer $VOCALSTACK_API_KEY\""}],"x-errors":["invalid_state","not_found","idempotency_key_reused"],"x-vocalstack-auth":"userOrKey","x-vocalstack-scopes":["write"]}},"/v1/recordings/{id}/summary":{"get":{"description":"The title and summary as they stand — `ready` with both, `pending` while the engine is still writing them, `unavailable` on a server without a summariser. Nothing is queued (that is `summarize`), so a share-link viewer may ask too. `lang` reads them in that language: the one the reader picked on screen, which the transcript and the downloads follow. The engine translates the summary into every language the recording was listened to or read in as it writes it, so those are ready; any other language is translated once, on the first ask, and kept. `lang` in the answer says the translation was had. The spoken language, or no `lang`, is the summary as written. Through a share link a language other than the spoken one needs the link's `allowTranslate`.","operationId":"recordings.summary","parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string"}},{"in":"query","name":"lang","schema":{"type":"string"}},{"in":"query","name":"shareCode","schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SummaryStatus"}}},"description":"SummaryStatus"}},"summary":"Get a recording's summary","tags":["recordings"],"x-codeSamples":[{"label":"TypeScript SDK","lang":"typescript","source":"import { VocalStack } from '@vocalstack/sdk';\n\nconst vs = new VocalStack({ baseUrl: 'https://api.vocalstack.com', apiKey: process.env.VOCALSTACK_API_KEY });\n\nconst summary = await vs.recordings.summary('RECORDING_ID', { lang: 'fr' });\nconsole.log(summary);\n"},{"label":"Python SDK","lang":"python","source":"import asyncio\nimport os\n\nfrom vocalstack import VocalStack\n\n\nasync def main() -> None:\n    async with VocalStack(\"https://api.vocalstack.com\", api_key=os.environ[\"VOCALSTACK_API_KEY\"]) as vs:\n        print(await vs.recordings.summary(\"RECORDING_ID\", lang=\"fr\"))\n\n\nasyncio.run(main())\n"},{"label":"curl","lang":"shell","source":"curl \"https://api.vocalstack.com/v1/recordings/{id}/summary\" \\\n  -H \"Authorization: Bearer $VOCALSTACK_API_KEY\""}],"x-errors":["forbidden","invalid_options","not_found"],"x-vocalstack-auth":"listener","x-vocalstack-scopes":["read"]}},"/v1/recordings/{id}/transcript":{"get":{"description":"Snapshot up to lastSeq; the backfill for subscribers (after=cursor). A line scoring under `settings.confidence.hideBelow` (0.60 by default — the line's own mean word probability, not a count of bad words) is hidden as \"Audio unclear\". Listeners see it blanked (`suppressed`, text=''); anyone shown the text — the owner, a share-link reader — gets `unclear` instead and keeps every word. Either way the line is spoken to nobody: `GET .../voice/{sentence}` answers 404 for it. Listeners also see translations filtered to lang. Through a share link (`shareCode`), `lang` needs the link's `allowTranslate`: without it any language other than the one spoken is `forbidden`, never a silently untranslated snapshot.","operationId":"recordings.transcript","parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string"}},{"in":"query","name":"after","schema":{"type":"integer"}},{"in":"query","name":"lang","schema":{"type":"string"}},{"description":"listener token (a private broadcast)","in":"query","name":"listenerToken","schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TranscriptSnapshot"}}},"description":"OK"}},"summary":"Get a transcript","tags":["recordings"],"x-codeSamples":[{"label":"TypeScript SDK","lang":"typescript","source":"import { VocalStack } from '@vocalstack/sdk';\n\nconst vs = new VocalStack({ baseUrl: 'https://api.vocalstack.com', apiKey: process.env.VOCALSTACK_API_KEY });\n\nconst snap = await vs.recordings.transcript('RECORDING_ID', { lang: 'es' });\nfor (const s of snap.sentences) console.log(s.speaker, s.text);\n"},{"label":"Python SDK","lang":"python","source":"import asyncio\nimport os\n\nfrom vocalstack import VocalStack\n\n\nasync def main() -> None:\n    async with VocalStack(\"https://api.vocalstack.com\", api_key=os.environ[\"VOCALSTACK_API_KEY\"]) as vs:\n        snap = await vs.recordings.transcript(\"RECORDING_ID\", lang=\"es\")\n        for s in snap.sentences:\n            print(s.speaker, s.text)\n\n\nasyncio.run(main())\n"},{"label":"curl","lang":"shell","source":"curl \"https://api.vocalstack.com/v1/recordings/{id}/transcript\" \\\n  -H \"Authorization: Bearer $VOCALSTACK_API_KEY\""}],"x-vocalstack-auth":"listener","x-vocalstack-scopes":["read"]}},"/v1/recordings/{id}/translation":{"get":{"description":"Sentences of the transcript translated into `lang` (a translation target), by sentence id (`sentences`, comma separated): what a reader in another language pages through. The owner, or any listener who can reach its broadcast or transcription; through a share link it needs `allowTranslate`. A listener's new (recording, language) pairs are limited per network an hour.","operationId":"recordings.translation","parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string"}},{"in":"query","name":"lang","required":true,"schema":{"type":"string"}},{"in":"query","name":"sentences","schema":{"type":"string"}},{"in":"query","name":"shareCode","schema":{"type":"string"}},{"in":"query","name":"listenerToken","schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"properties":{"lang":{"type":"string"},"translations":{"additionalProperties":{"type":"string"},"type":"object"}},"required":["lang","translations"],"type":"object"}}},"description":"OK"}},"summary":"Get a translation","tags":["recordings"],"x-codeSamples":[{"label":"TypeScript SDK","lang":"typescript","source":"import { VocalStack } from '@vocalstack/sdk';\n\nconst vs = new VocalStack({ baseUrl: 'https://api.vocalstack.com', apiKey: process.env.VOCALSTACK_API_KEY });\n\nconst { translations } = await vs.recordings.translation('RECORDING_ID', 'de', [0, 1, 2]);\nconsole.log(translations);\n"},{"label":"Python SDK","lang":"python","source":"import asyncio\nimport os\n\nfrom vocalstack import VocalStack\n\n\nasync def main() -> None:\n    async with VocalStack(\"https://api.vocalstack.com\", api_key=os.environ[\"VOCALSTACK_API_KEY\"]) as vs:\n        res = await vs.recordings.translation(\"RECORDING_ID\", \"de\", sentences=[0, 1, 2])\n        print(res[\"translations\"])\n\n\nasyncio.run(main())\n"},{"label":"curl","lang":"shell","source":"curl \"https://api.vocalstack.com/v1/recordings/{id}/translation?lang=<lang>\" \\\n  -H \"Authorization: Bearer $VOCALSTACK_API_KEY\""}],"x-errors":["invalid_options","forbidden","rate_limited"],"x-vocalstack-auth":"listener","x-vocalstack-scopes":["read"]}},"/v1/recordings/{id}/voice":{"get":{"description":"What this recording can be heard speaking, one row per language: rendered tracks (`sealed`) and their jobs, and the per-sentence clips a live audience left behind (`clips`, with the sentence ids and the voice that spoke them). An ended recording has only clips — this is how a listener finds them again and plays its spoken translation back a line at a time. The owner, or any listener who can reach its broadcast or transcription; `lang` narrows it to one. A spoken translation needs the link's `allowTranslate` when reading through a share link — without it the listing carries only the spoken language, and asking for another is `forbidden`.","operationId":"recordings.voice","parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string"}},{"in":"query","name":"lang","schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/VoiceTrack"},"type":"array"}}},"description":"OK"}},"summary":"Get a recording's voice tracks","tags":["recordings"],"x-codeSamples":[{"label":"TypeScript SDK","lang":"typescript","source":"import { VocalStack } from '@vocalstack/sdk';\n\nconst vs = new VocalStack({ baseUrl: 'https://api.vocalstack.com', apiKey: process.env.VOCALSTACK_API_KEY });\n\nconst tracks = await vs.recordings.voice('RECORDING_ID', 'es');\nconsole.log(tracks);\n"},{"label":"Python SDK","lang":"python","source":"import asyncio\nimport os\n\nfrom vocalstack import VocalStack\n\n\nasync def main() -> None:\n    async with VocalStack(\"https://api.vocalstack.com\", api_key=os.environ[\"VOCALSTACK_API_KEY\"]) as vs:\n        print(await vs.recordings.voice(\"RECORDING_ID\", \"es\"))\n\n\nasyncio.run(main())\n"},{"label":"curl","lang":"shell","source":"curl \"https://api.vocalstack.com/v1/recordings/{id}/voice\" \\\n  -H \"Authorization: Bearer $VOCALSTACK_API_KEY\""}],"x-vocalstack-auth":"listener","x-vocalstack-scopes":["read"]},"post":{"description":"Render spoken tracks for a finished recording, one job per language (1–10 codes, each one /v1/languages says has a voice). Owner only.","operationId":"recordings.renderVoice","parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string"}},{"description":"Makes a retry safe: a request repeated with the same key and the same body within 24 hours returns the first response (with `Idempotent-Replayed: true`) instead of running again. The same key with a different request is 422 `idempotency_key_reused`; a retry while the first is still running is 409 `conflict`. Only a successful (2xx) response is remembered. Keys are per account.","in":"header","name":"Idempotency-Key","required":false,"schema":{"maxLength":255,"minLength":1,"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"properties":{"langs":{"items":{"type":"string"},"type":"array"}},"required":["langs"],"type":"object"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/VoiceTrack"},"type":"array"}}},"description":"OK"}},"security":[{"bearerAuth":[]}],"summary":"Render voice tracks","tags":["recordings"],"x-codeSamples":[{"label":"TypeScript SDK","lang":"typescript","source":"import { VocalStack } from '@vocalstack/sdk';\n\nconst vs = new VocalStack({ baseUrl: 'https://api.vocalstack.com', apiKey: process.env.VOCALSTACK_API_KEY });\n\nconst tracks = await vs.recordings.renderVoice('RECORDING_ID', ['es', 'fr']);\nconsole.log(tracks);\n"},{"label":"Python SDK","lang":"python","source":"import asyncio\nimport os\n\nfrom vocalstack import VocalStack\n\n\nasync def main() -> None:\n    async with VocalStack(\"https://api.vocalstack.com\", api_key=os.environ[\"VOCALSTACK_API_KEY\"]) as vs:\n        print(await vs.recordings.render_voice(\"RECORDING_ID\", [\"es\", \"fr\"]))\n\n\nasyncio.run(main())\n"},{"label":"curl","lang":"shell","source":"curl -X POST \"https://api.vocalstack.com/v1/recordings/{id}/voice\" \\\n  -H \"Authorization: Bearer $VOCALSTACK_API_KEY\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"langs\":[\"<langs>\"]}'"}],"x-errors":["invalid_options","not_found","idempotency_key_reused"],"x-vocalstack-auth":"listener","x-vocalstack-scopes":["read"]}},"/v1/recordings/{id}/voice/{sentence}":{"get":{"description":"One spoken sentence, Ogg/Opus (`format=caf` for Apple's media stack). The owner, or any listener who can reach its broadcast or transcription. A clip in a language the recording was not spoken in is a spoken translation: through a share link it needs `allowTranslate`.","operationId":"recordings.voiceClip","parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string"}},{"in":"path","name":"sentence","required":true,"schema":{"type":"integer"}},{"in":"query","name":"lang","required":true,"schema":{"type":"string"}},{"in":"query","name":"format","schema":{"default":"ogg","enum":["ogg","caf"],"type":"string"}}],"responses":{"200":{"description":"audio/ogg"}},"summary":"Get a spoken sentence","tags":["recordings"],"x-codeSamples":[{"label":"TypeScript SDK","lang":"typescript","source":"import { VocalStack } from '@vocalstack/sdk';\n\nconst vs = new VocalStack({ baseUrl: 'https://api.vocalstack.com', apiKey: process.env.VOCALSTACK_API_KEY });\n\nconst res = await vs.recordings.voiceClip('RECORDING_ID', 3, 'es');\nconsole.log((await res.arrayBuffer()).byteLength, 'bytes of Ogg/Opus');\n"},{"label":"Python SDK","lang":"python","source":"import asyncio\nimport os\n\nfrom vocalstack import VocalStack\n\n\nasync def main() -> None:\n    async with VocalStack(\"https://api.vocalstack.com\", api_key=os.environ[\"VOCALSTACK_API_KEY\"]) as vs:\n        ogg = await vs.recordings.voice_clip(\"RECORDING_ID\", 3, \"es\")\n        print(len(ogg), \"bytes of Ogg/Opus\")\n\n\nasyncio.run(main())\n"},{"label":"curl","lang":"shell","source":"curl \"https://api.vocalstack.com/v1/recordings/{id}/voice/{sentence}?lang=<lang>\" \\\n  -H \"Authorization: Bearer $VOCALSTACK_API_KEY\""}],"x-errors":["not_found"],"x-vocalstack-auth":"listener","x-vocalstack-scopes":["read"]}},"/v1/rtmp":{"get":{"description":"The account's RTMP server URL and stream key for an encoder, whether one is sending audio on it right now and what (`audio`, e.g. \"48 kHz AAC\"), and a signed link to listen to it (`monitorUrl`, an hour). A live transcription takes it as its source with `source.url` = `serverUrl`. The key is made on first ask.","operationId":"rtmp.status","responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RtmpStatus"}}},"description":"OK"}},"security":[{"bearerAuth":[]}],"summary":"Get RTMP status","tags":["rtmp"],"x-codeSamples":[{"label":"TypeScript SDK","lang":"typescript","source":"import { VocalStack } from '@vocalstack/sdk';\n\nconst vs = new VocalStack({ baseUrl: 'https://api.vocalstack.com', apiKey: process.env.VOCALSTACK_API_KEY });\n\nconst rtmp = await vs.rtmp.status();\nconsole.log(rtmp);\n"},{"label":"Python SDK","lang":"python","source":"import asyncio\nimport os\n\nfrom vocalstack import VocalStack\n\n\nasync def main() -> None:\n    async with VocalStack(\"https://api.vocalstack.com\", api_key=os.environ[\"VOCALSTACK_API_KEY\"]) as vs:\n        print(await vs.rtmp.status())\n\n\nasyncio.run(main())\n"},{"label":"curl","lang":"shell","source":"curl \"https://api.vocalstack.com/v1/rtmp\" \\\n  -H \"Authorization: Bearer $VOCALSTACK_API_KEY\""}],"x-errors":["forbidden"],"x-vocalstack-auth":"userOrKey","x-vocalstack-scopes":["read"]}},"/v1/search":{"get":{"description":"Sentence search via the English pivot ∪ literal match; results rendered into lang on demand. An owner searches their own transcriptions and broadcasts; a listener names the broadcasts to search by their codes.","operationId":"search.query","parameters":[{"in":"query","name":"q","required":true,"schema":{"type":"string"}},{"description":"Listeners: the broadcasts to search, by code or id, comma-separated.","in":"query","name":"broadcasts","schema":{"type":"string"}},{"description":"Listeners: listener tokens for the private broadcasts among `broadcasts`, comma-separated.","in":"query","name":"listenerTokens","schema":{"type":"string"}},{"in":"query","name":"recording","schema":{"type":"string"}},{"in":"query","name":"lang","schema":{"type":"string"}},{"in":"query","name":"limit","schema":{"type":"integer"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SearchResults"}}},"description":"OK"}},"summary":"Search transcripts","tags":["search"],"x-codeSamples":[{"label":"TypeScript SDK","lang":"typescript","source":"import { VocalStack } from '@vocalstack/sdk';\n\nconst vs = new VocalStack({ baseUrl: 'https://api.vocalstack.com', apiKey: process.env.VOCALSTACK_API_KEY });\n\nconst { hits } = await vs.search.query('quarterly results', { limit: 5 });\nfor (const h of hits) console.log(h.recordingId, h.mediaStart, h.text);\n"},{"label":"Python SDK","lang":"python","source":"import asyncio\nimport os\n\nfrom vocalstack import VocalStack\n\n\nasync def main() -> None:\n    async with VocalStack(\"https://api.vocalstack.com\", api_key=os.environ[\"VOCALSTACK_API_KEY\"]) as vs:\n        res = await vs.search.query(\"quarterly results\", limit=5)\n        for h in res.hits:\n            print(h.recording_id, h.media_start, h.text)\n\n\nasyncio.run(main())\n"},{"label":"curl","lang":"shell","source":"curl \"https://api.vocalstack.com/v1/search?q=<q>\" \\\n  -H \"Authorization: Bearer $VOCALSTACK_API_KEY\""}],"x-errors":["rate_limited"],"x-vocalstack-auth":"optional","x-vocalstack-scopes":["read"]}},"/v1/share-links/{id}":{"delete":{"description":"Revoke: the public page 404s at once.","operationId":"shareLinks.revoke","parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ShareLink"}}},"description":"OK"}},"security":[{"bearerAuth":[]}],"summary":"Revoke a share link","tags":["share-links"],"x-codeSamples":[{"label":"TypeScript SDK","lang":"typescript","source":"import { VocalStack } from '@vocalstack/sdk';\n\nconst vs = new VocalStack({ baseUrl: 'https://api.vocalstack.com', apiKey: process.env.VOCALSTACK_API_KEY });\n\nawait vs.shareLinks.revoke('SHARE_LINK_ID');\n"},{"label":"Python SDK","lang":"python","source":"import asyncio\nimport os\n\nfrom vocalstack import VocalStack\n\n\nasync def main() -> None:\n    async with VocalStack(\"https://api.vocalstack.com\", api_key=os.environ[\"VOCALSTACK_API_KEY\"]) as vs:\n        await vs.share_links.revoke(\"SHARE_LINK_ID\")\n\n\nasyncio.run(main())\n"},{"label":"curl","lang":"shell","source":"curl -X DELETE \"https://api.vocalstack.com/v1/share-links/{id}\" \\\n  -H \"Authorization: Bearer $VOCALSTACK_API_KEY\""}],"x-vocalstack-auth":"userOrKey","x-vocalstack-scopes":["write"]},"patch":{"description":"The share sheet's toggles: any of `label`, `allowDownload`, `allowExport`, `allowTranslate`. Omitted fields are left as they are.","operationId":"shareLinks.update","parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateShareLink"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ShareLink"}}},"description":"OK"}},"security":[{"bearerAuth":[]}],"summary":"Update a share link","tags":["share-links"],"x-codeSamples":[{"label":"TypeScript SDK","lang":"typescript","source":"import { VocalStack } from '@vocalstack/sdk';\n\nconst vs = new VocalStack({ baseUrl: 'https://api.vocalstack.com', apiKey: process.env.VOCALSTACK_API_KEY });\n\nconst link = await vs.shareLinks.update('SHARE_LINK_ID', { allowDownload: false });\nconsole.log(link);\n"},{"label":"Python SDK","lang":"python","source":"import asyncio\nimport os\n\nfrom vocalstack import VocalStack\n\n\nasync def main() -> None:\n    async with VocalStack(\"https://api.vocalstack.com\", api_key=os.environ[\"VOCALSTACK_API_KEY\"]) as vs:\n        print(await vs.share_links.update(\"SHARE_LINK_ID\", allow_download=False))\n\n\nasyncio.run(main())\n"},{"label":"curl","lang":"shell","source":"curl -X PATCH \"https://api.vocalstack.com/v1/share-links/{id}\" \\\n  -H \"Authorization: Bearer $VOCALSTACK_API_KEY\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"allowDownload\":true}'"}],"x-vocalstack-auth":"userOrKey","x-vocalstack-scopes":["write"]}},"/v1/transcriptions":{"get":{"description":"Transcriptions owned by the acting principal (file, link and live). `lifecycle=all` is the active and the archived in one answer, up to 500 of each.","operationId":"transcriptions.list","parameters":[{"in":"query","name":"lifecycle","schema":{"default":"active","enum":["active","archived","all"],"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"properties":{"transcriptions":{"items":{"$ref":"#/components/schemas/Transcription"},"type":"array"}},"type":"object"}}},"description":"OK"}},"security":[{"bearerAuth":[]}],"summary":"List transcriptions","tags":["transcriptions"],"x-codeSamples":[{"label":"TypeScript SDK","lang":"typescript","source":"import { VocalStack } from '@vocalstack/sdk';\n\nconst vs = new VocalStack({ baseUrl: 'https://api.vocalstack.com', apiKey: process.env.VOCALSTACK_API_KEY });\n\nconst transcriptions = await vs.transcriptions.list();\nfor (const t of transcriptions) console.log(t.id, t.name);\n"},{"label":"Python SDK","lang":"python","source":"import asyncio\nimport os\n\nfrom vocalstack import VocalStack\n\n\nasync def main() -> None:\n    async with VocalStack(\"https://api.vocalstack.com\", api_key=os.environ[\"VOCALSTACK_API_KEY\"]) as vs:\n        for t in await vs.transcriptions.list():\n            print(t.id, t.name)\n\n\nasyncio.run(main())\n"},{"label":"curl","lang":"shell","source":"curl \"https://api.vocalstack.com/v1/transcriptions\" \\\n  -H \"Authorization: Bearer $VOCALSTACK_API_KEY\""}],"x-vocalstack-auth":"userOrKey","x-vocalstack-scopes":["read"]},"post":{"description":"Transcribe a file or a link: creates the transcription, its recording and a server-owned job. multipart/form-data with a `file` part (+ optional `options` JSON part), or JSON with `url` (fetched server-side). The source is kept in the media store. Follow the job with `jobs.get`.","operationId":"transcriptions.create","parameters":[{"description":"Makes a retry safe: a request repeated with the same key and the same body within 24 hours returns the first response (with `Idempotent-Replayed: true`) instead of running again. The same key with a different request is 422 `idempotency_key_reused`; a retry while the first is still running is 409 `conflict`. Only a successful (2xx) response is remembered. Keys are per account.","in":"header","name":"Idempotency-Key","required":false,"schema":{"maxLength":255,"minLength":1,"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateTranscription"}},"multipart/form-data":{"schema":{"properties":{"file":{"format":"binary","type":"string"},"options":{"$ref":"#/components/schemas/CreateTranscription"}},"required":["file"],"type":"object"}}},"required":true},"responses":{"201":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Transcription"}}},"description":"Created (with `transcription`)"}},"security":[{"bearerAuth":[]}],"summary":"Create a transcription","tags":["transcriptions"],"x-codeSamples":[{"label":"TypeScript SDK","lang":"typescript","source":"import { VocalStack } from '@vocalstack/sdk';\n\nconst vs = new VocalStack({ baseUrl: 'https://api.vocalstack.com', apiKey: process.env.VOCALSTACK_API_KEY });\n\n// A public link (or `file`: a path, a Blob or bytes).\nconst tx = await vs.transcriptions.create({ url: 'https://example.com/talk.mp3', name: 'Talk' });\nconst job = await vs.jobs.wait(tx.job!.recordingId);\nconst { sentences } = await vs.recordings.transcript(job.recordingId);\nconsole.log(sentences.map((s) => s.text).join(' '));\n"},{"label":"Python SDK","lang":"python","source":"import asyncio\nimport os\n\nfrom vocalstack import VocalStack\n\n\nasync def main() -> None:\n    async with VocalStack(\"https://api.vocalstack.com\", api_key=os.environ[\"VOCALSTACK_API_KEY\"]) as vs:\n        # A public link (or file=: a path, bytes or an open binary file).\n        tx = await vs.transcriptions.create(url=\"https://example.com/talk.mp3\", name=\"Talk\")\n        job = await vs.jobs.wait(tx.job.recording_id)\n        snap = await vs.recordings.transcript(job.recording_id)\n        print(\" \".join(s.text for s in snap.sentences))\n\n\nasyncio.run(main())\n"},{"label":"curl","lang":"shell","source":"curl -X POST \"https://api.vocalstack.com/v1/transcriptions\" \\\n  -H \"Authorization: Bearer $VOCALSTACK_API_KEY\" \\\n  -F file=@talk.mp3 \\\n  -F 'options={\"name\":\"Talk\"}'"}],"x-errors":["invalid_options","invalid_audio","invalid_state","insufficient_credit","quota_exceeded","rate_limited","idempotency_key_reused"],"x-vocalstack-auth":"userOrKey","x-vocalstack-scopes":["write","recordings:publish"]}},"/v1/transcriptions/live":{"post":{"description":"Create a live transcription: captured as it is spoken — this device's microphone, or a stream the server pulls — rather than read from a file (`live: true`). It is created idle; `transcriptions.startRecording` starts it, and the recording's pause / resume / stop calls drive it. `broadcasts` attaches it to those broadcasts from the start.","operationId":"transcriptions.createLive","parameters":[{"description":"Makes a retry safe: a request repeated with the same key and the same body within 24 hours returns the first response (with `Idempotent-Replayed: true`) instead of running again. The same key with a different request is 422 `idempotency_key_reused`; a retry while the first is still running is 409 `conflict`. Only a successful (2xx) response is remembered. Keys are per account.","in":"header","name":"Idempotency-Key","required":false,"schema":{"maxLength":255,"minLength":1,"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateLiveTranscription"}}},"required":true},"responses":{"201":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Transcription"}}},"description":"Created"}},"security":[{"bearerAuth":[]}],"summary":"Create a live transcription","tags":["transcriptions"],"x-codeSamples":[{"label":"TypeScript SDK","lang":"typescript","source":"import { VocalStack } from '@vocalstack/sdk';\n\nconst vs = new VocalStack({ baseUrl: 'https://api.vocalstack.com', apiKey: process.env.VOCALSTACK_API_KEY });\n\nconst tx = await vs.transcriptions.createLive({ name: 'Standup', settings: { spokenLanguage: 'en' } });\nconst grant = await vs.transcriptions.startRecording(tx.id);\nconsole.log(grant.streamUrl);\n"},{"label":"Python SDK","lang":"python","source":"import asyncio\nimport os\n\nfrom vocalstack import VocalStack\n\n\nasync def main() -> None:\n    async with VocalStack(\"https://api.vocalstack.com\", api_key=os.environ[\"VOCALSTACK_API_KEY\"]) as vs:\n        tx = await vs.transcriptions.create_live(name=\"Standup\", settings={\"spokenLanguage\": \"en\"})\n        grant = await vs.transcriptions.start_recording(tx.id)\n        print(grant.stream_url)\n\n\nasyncio.run(main())\n"},{"label":"curl","lang":"shell","source":"curl -X POST \"https://api.vocalstack.com/v1/transcriptions/live\" \\\n  -H \"Authorization: Bearer $VOCALSTACK_API_KEY\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"name\":\"<name>\"}'"}],"x-errors":["invalid_options","not_found","rate_limited","idempotency_key_reused"],"x-vocalstack-auth":"userOrKey","x-vocalstack-scopes":["write"]}},"/v1/transcriptions/probe":{"get":{"description":"What a link is before it is transcribed: `live` — a YouTube live on air or scheduled, an endless HTTP stream, an HLS playlist with no end, a dynamic DASH manifest — belongs in a live transcription; `file` is transcribed as one; `unknown` could not be told. `source` names the live form's source (`youtube`, `radio`). Nothing is kept. 60 a minute.","operationId":"transcriptions.probe","parameters":[{"in":"query","name":"url","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LinkProbe"}}},"description":"OK"}},"security":[{"bearerAuth":[]}],"summary":"Probe a link","tags":["transcriptions"],"x-codeSamples":[{"label":"TypeScript SDK","lang":"typescript","source":"import { VocalStack } from '@vocalstack/sdk';\n\nconst vs = new VocalStack({ baseUrl: 'https://api.vocalstack.com', apiKey: process.env.VOCALSTACK_API_KEY });\n\nconst probe = await vs.transcriptions.probe('https://www.youtube.com/watch?v=VIDEO_ID');\nconsole.log(probe);\n"},{"label":"Python SDK","lang":"python","source":"import asyncio\nimport os\n\nfrom vocalstack import VocalStack\n\n\nasync def main() -> None:\n    async with VocalStack(\"https://api.vocalstack.com\", api_key=os.environ[\"VOCALSTACK_API_KEY\"]) as vs:\n        print(await vs.transcriptions.probe(\"https://www.youtube.com/watch?v=VIDEO_ID\"))\n\n\nasyncio.run(main())\n"},{"label":"curl","lang":"shell","source":"curl \"https://api.vocalstack.com/v1/transcriptions/probe?url=<url>\" \\\n  -H \"Authorization: Bearer $VOCALSTACK_API_KEY\""}],"x-errors":["invalid_options","rate_limited"],"x-vocalstack-auth":"userOrKey","x-vocalstack-scopes":["read"]}},"/v1/transcriptions/{id}":{"get":{"description":"One transcription of the acting principal's, by id.","operationId":"transcriptions.get","parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Transcription"}}},"description":"OK"}},"security":[{"bearerAuth":[]}],"summary":"Get a transcription","tags":["transcriptions"],"x-codeSamples":[{"label":"TypeScript SDK","lang":"typescript","source":"import { VocalStack } from '@vocalstack/sdk';\n\nconst vs = new VocalStack({ baseUrl: 'https://api.vocalstack.com', apiKey: process.env.VOCALSTACK_API_KEY });\n\nconst tx = await vs.transcriptions.get('TRANSCRIPTION_ID');\nconsole.log(tx);\n"},{"label":"Python SDK","lang":"python","source":"import asyncio\nimport os\n\nfrom vocalstack import VocalStack\n\n\nasync def main() -> None:\n    async with VocalStack(\"https://api.vocalstack.com\", api_key=os.environ[\"VOCALSTACK_API_KEY\"]) as vs:\n        print(await vs.transcriptions.get(\"TRANSCRIPTION_ID\"))\n\n\nasyncio.run(main())\n"},{"label":"curl","lang":"shell","source":"curl \"https://api.vocalstack.com/v1/transcriptions/{id}\" \\\n  -H \"Authorization: Bearer $VOCALSTACK_API_KEY\""}],"x-vocalstack-auth":"userOrKey","x-vocalstack-scopes":["read"]},"patch":{"description":"Rename, or update default capture settings.","operationId":"transcriptions.update","parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateTranscription"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Transcription"}}},"description":"OK"}},"security":[{"bearerAuth":[]}],"summary":"Update a transcription","tags":["transcriptions"],"x-codeSamples":[{"label":"TypeScript SDK","lang":"typescript","source":"import { VocalStack } from '@vocalstack/sdk';\n\nconst vs = new VocalStack({ baseUrl: 'https://api.vocalstack.com', apiKey: process.env.VOCALSTACK_API_KEY });\n\nconst tx = await vs.transcriptions.update('TRANSCRIPTION_ID', { name: 'Board meeting' });\nconsole.log(tx.name);\n"},{"label":"Python SDK","lang":"python","source":"import asyncio\nimport os\n\nfrom vocalstack import VocalStack\n\n\nasync def main() -> None:\n    async with VocalStack(\"https://api.vocalstack.com\", api_key=os.environ[\"VOCALSTACK_API_KEY\"]) as vs:\n        tx = await vs.transcriptions.update(\"TRANSCRIPTION_ID\", name=\"Board meeting\")\n        print(tx.name)\n\n\nasyncio.run(main())\n"},{"label":"curl","lang":"shell","source":"curl -X PATCH \"https://api.vocalstack.com/v1/transcriptions/{id}\" \\\n  -H \"Authorization: Bearer $VOCALSTACK_API_KEY\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"name\":\"<name>\"}'"}],"x-vocalstack-auth":"userOrKey","x-vocalstack-scopes":["write"]}},"/v1/transcriptions/{id}/archive":{"post":{"description":"Archive the transcription: hidden, its links dead, its data kept until it is restored or purged.","operationId":"transcriptions.archive","parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Transcription"}}},"description":"OK"}},"security":[{"bearerAuth":[]}],"summary":"Archive a transcription","tags":["transcriptions"],"x-codeSamples":[{"label":"TypeScript SDK","lang":"typescript","source":"import { VocalStack } from '@vocalstack/sdk';\n\nconst vs = new VocalStack({ baseUrl: 'https://api.vocalstack.com', apiKey: process.env.VOCALSTACK_API_KEY });\n\nawait vs.transcriptions.archive('TRANSCRIPTION_ID');\n"},{"label":"Python SDK","lang":"python","source":"import asyncio\nimport os\n\nfrom vocalstack import VocalStack\n\n\nasync def main() -> None:\n    async with VocalStack(\"https://api.vocalstack.com\", api_key=os.environ[\"VOCALSTACK_API_KEY\"]) as vs:\n        await vs.transcriptions.archive(\"TRANSCRIPTION_ID\")\n\n\nasyncio.run(main())\n"},{"label":"curl","lang":"shell","source":"curl -X POST \"https://api.vocalstack.com/v1/transcriptions/{id}/archive\" \\\n  -H \"Authorization: Bearer $VOCALSTACK_API_KEY\""}],"x-vocalstack-auth":"userOrKey","x-vocalstack-scopes":["write"]}},"/v1/transcriptions/{id}/broadcasts/{broadcast}":{"delete":{"description":"Stop feeding the broadcast from the live transcription `id`. Returns the transcription.","operationId":"transcriptions.detachBroadcast","parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string"}},{"in":"path","name":"broadcast","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Transcription"}}},"description":"OK"}},"security":[{"bearerAuth":[]}],"summary":"Detach a broadcast","tags":["transcriptions"],"x-codeSamples":[{"label":"TypeScript SDK","lang":"typescript","source":"import { VocalStack } from '@vocalstack/sdk';\n\nconst vs = new VocalStack({ baseUrl: 'https://api.vocalstack.com', apiKey: process.env.VOCALSTACK_API_KEY });\n\nawait vs.transcriptions.detachBroadcast('TRANSCRIPTION_ID', 'BROADCAST_ID');\n"},{"label":"Python SDK","lang":"python","source":"import asyncio\nimport os\n\nfrom vocalstack import VocalStack\n\n\nasync def main() -> None:\n    async with VocalStack(\"https://api.vocalstack.com\", api_key=os.environ[\"VOCALSTACK_API_KEY\"]) as vs:\n        await vs.transcriptions.detach_broadcast(\"TRANSCRIPTION_ID\", \"BROADCAST_ID\")\n\n\nasyncio.run(main())\n"},{"label":"curl","lang":"shell","source":"curl -X DELETE \"https://api.vocalstack.com/v1/transcriptions/{id}/broadcasts/{broadcast}\" \\\n  -H \"Authorization: Bearer $VOCALSTACK_API_KEY\""}],"x-errors":["invalid_options","not_found"],"x-vocalstack-auth":"userOrKey","x-vocalstack-scopes":["write"]},"put":{"description":"Feed a broadcast from the live transcription `id`: listeners on the broadcast's link read the transcription while it records, and its recordings are listed with the broadcast's own. One transcription can feed several broadcasts, and a broadcast can be fed by several. Returns the transcription.","operationId":"transcriptions.attachBroadcast","parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string"}},{"in":"path","name":"broadcast","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Transcription"}}},"description":"OK"}},"security":[{"bearerAuth":[]}],"summary":"Attach a broadcast","tags":["transcriptions"],"x-codeSamples":[{"label":"TypeScript SDK","lang":"typescript","source":"import { VocalStack } from '@vocalstack/sdk';\n\nconst vs = new VocalStack({ baseUrl: 'https://api.vocalstack.com', apiKey: process.env.VOCALSTACK_API_KEY });\n\n// Listeners of the broadcast follow this live transcription.\nawait vs.transcriptions.attachBroadcast('TRANSCRIPTION_ID', 'BROADCAST_ID');\n"},{"label":"Python SDK","lang":"python","source":"import asyncio\nimport os\n\nfrom vocalstack import VocalStack\n\n\nasync def main() -> None:\n    async with VocalStack(\"https://api.vocalstack.com\", api_key=os.environ[\"VOCALSTACK_API_KEY\"]) as vs:\n        # Listeners of the broadcast follow this live transcription.\n        await vs.transcriptions.attach_broadcast(\"TRANSCRIPTION_ID\", \"BROADCAST_ID\")\n\n\nasyncio.run(main())\n"},{"label":"curl","lang":"shell","source":"curl -X PUT \"https://api.vocalstack.com/v1/transcriptions/{id}/broadcasts/{broadcast}\" \\\n  -H \"Authorization: Bearer $VOCALSTACK_API_KEY\""}],"x-errors":["invalid_options","invalid_state","not_found"],"x-vocalstack-auth":"userOrKey","x-vocalstack-scopes":["write"]}},"/v1/transcriptions/{id}/purge":{"post":{"description":"Delete an archived transcription and everything in it, for good.","operationId":"transcriptions.purge","parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK"}},"security":[{"bearerAuth":[]}],"summary":"Purge a transcription","tags":["transcriptions"],"x-codeSamples":[{"label":"TypeScript SDK","lang":"typescript","source":"import { VocalStack } from '@vocalstack/sdk';\n\nconst vs = new VocalStack({ baseUrl: 'https://api.vocalstack.com', apiKey: process.env.VOCALSTACK_API_KEY });\n\nawait vs.transcriptions.purge('TRANSCRIPTION_ID'); // only an archived transcription\n"},{"label":"Python SDK","lang":"python","source":"import asyncio\nimport os\n\nfrom vocalstack import VocalStack\n\n\nasync def main() -> None:\n    async with VocalStack(\"https://api.vocalstack.com\", api_key=os.environ[\"VOCALSTACK_API_KEY\"]) as vs:\n        await vs.transcriptions.purge(\"TRANSCRIPTION_ID\")  # only an archived transcription\n\n\nasyncio.run(main())\n"},{"label":"curl","lang":"shell","source":"curl -X POST \"https://api.vocalstack.com/v1/transcriptions/{id}/purge\" \\\n  -H \"Authorization: Bearer $VOCALSTACK_API_KEY\""}],"x-vocalstack-auth":"userOrKey","x-vocalstack-scopes":["write"]}},"/v1/transcriptions/{id}/recordings":{"get":{"description":"The transcription's recordings.","operationId":"transcriptions.listRecordings","parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string"}},{"in":"query","name":"lifecycle","schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"properties":{"recordings":{"items":{"$ref":"#/components/schemas/Recording"},"type":"array"}},"type":"object"}}},"description":"OK"}},"security":[{"bearerAuth":[]}],"summary":"List a transcription's recordings","tags":["transcriptions"],"x-codeSamples":[{"label":"TypeScript SDK","lang":"typescript","source":"import { VocalStack } from '@vocalstack/sdk';\n\nconst vs = new VocalStack({ baseUrl: 'https://api.vocalstack.com', apiKey: process.env.VOCALSTACK_API_KEY });\n\nconst recordings = await vs.transcriptions.listRecordings('TRANSCRIPTION_ID');\nconsole.log(recordings);\n"},{"label":"Python SDK","lang":"python","source":"import asyncio\nimport os\n\nfrom vocalstack import VocalStack\n\n\nasync def main() -> None:\n    async with VocalStack(\"https://api.vocalstack.com\", api_key=os.environ[\"VOCALSTACK_API_KEY\"]) as vs:\n        print(await vs.transcriptions.list_recordings(\"TRANSCRIPTION_ID\"))\n\n\nasyncio.run(main())\n"},{"label":"curl","lang":"shell","source":"curl \"https://api.vocalstack.com/v1/transcriptions/{id}/recordings\" \\\n  -H \"Authorization: Bearer $VOCALSTACK_API_KEY\""}],"x-vocalstack-auth":"userOrKey","x-vocalstack-scopes":["read"]},"post":{"description":"Start recording a live transcription and mint a publisher realtime session (`streamUrl`, `sessionId`). 409 conflict with details.takeover when another device is recording. When its source is a stream URL the server pulls the URL and publishes it itself: the grant carries no `streamUrl`, and `recording.ingest` (plus `stream.ingest` frames) says where it stands. `scriptText` gives the recording words to follow like a teleprompter (`Recording.scriptText`).","operationId":"transcriptions.startRecording","parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string"}},{"description":"Makes a retry safe: a request repeated with the same key and the same body within 24 hours returns the first response (with `Idempotent-Replayed: true`) instead of running again. The same key with a different request is 422 `idempotency_key_reused`; a retry while the first is still running is 409 `conflict`. Only a successful (2xx) response is remembered. Keys are per account.","in":"header","name":"Idempotency-Key","required":false,"schema":{"maxLength":255,"minLength":1,"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/StartRecording"}}},"required":false},"responses":{"201":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublishGrant"}}},"description":"Created"}},"security":[{"bearerAuth":[]}],"summary":"Start a recording","tags":["transcriptions"],"x-codeSamples":[{"label":"TypeScript SDK","lang":"typescript","source":"import { VocalStack } from '@vocalstack/sdk';\n\nconst vs = new VocalStack({ baseUrl: 'https://api.vocalstack.com', apiKey: process.env.VOCALSTACK_API_KEY });\n\nconst grant = await vs.transcriptions.startRecording('TRANSCRIPTION_ID');\nconst stream = await vs.recordings.publish(grant);\n// stream.sendAudio(pcm) (f32le, 16 kHz, mono), then stream.eof()\nfor await (const ev of stream) if (ev.type === 'transcript.committed') console.log(ev.segment.text);\n"},{"label":"Python SDK","lang":"python","source":"import asyncio\nimport os\n\nfrom vocalstack import VocalStack\n\n\nasync def main() -> None:\n    async with VocalStack(\"https://api.vocalstack.com\", api_key=os.environ[\"VOCALSTACK_API_KEY\"]) as vs:\n        grant = await vs.transcriptions.start_recording(\"TRANSCRIPTION_ID\")\n        async with await vs.recordings.publish(grant) as stream:\n            # await stream.send_audio(pcm) (f32le, 16 kHz, mono), then await stream.eof()\n            async for event in stream:\n                if event.type == \"transcript.committed\":\n                    print(event.segment.text)\n\n\nasyncio.run(main())\n"},{"label":"curl","lang":"shell","source":"curl -X POST \"https://api.vocalstack.com/v1/transcriptions/{id}/recordings\" \\\n  -H \"Authorization: Bearer $VOCALSTACK_API_KEY\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"audioSource\":\"<audioSource>\"}'"}],"x-errors":["invalid_state","conflict","insufficient_credit","quota_exceeded","rate_limited","idempotency_key_reused"],"x-vocalstack-auth":"userOrKey","x-vocalstack-scopes":["recordings:publish"]}},"/v1/transcriptions/{id}/recordings/upload":{"post":{"description":"A file or link recording in this transcription: no live phase, transcribed at full speed.","operationId":"transcriptions.uploadRecording","parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string"}},{"description":"Makes a retry safe: a request repeated with the same key and the same body within 24 hours returns the first response (with `Idempotent-Replayed: true`) instead of running again. The same key with a different request is 422 `idempotency_key_reused`; a retry while the first is still running is 409 `conflict`. Only a successful (2xx) response is remembered. Keys are per account.","in":"header","name":"Idempotency-Key","required":false,"schema":{"maxLength":255,"minLength":1,"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UploadRecording"}}},"required":false},"responses":{"201":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublishGrant"}}},"description":"Created"}},"security":[{"bearerAuth":[]}],"summary":"Upload a recording","tags":["transcriptions"],"x-codeSamples":[{"label":"TypeScript SDK","lang":"typescript","source":"import { VocalStack } from '@vocalstack/sdk';\n\nconst vs = new VocalStack({ baseUrl: 'https://api.vocalstack.com', apiKey: process.env.VOCALSTACK_API_KEY });\n\nconst grant = await vs.transcriptions.uploadRecording('TRANSCRIPTION_ID', { url: 'https://example.com/part-2.mp3' });\nconst job = await vs.jobs.wait(grant.recording.id);\nconsole.log(job.state);\n"},{"label":"Python SDK","lang":"python","source":"import asyncio\nimport os\n\nfrom vocalstack import VocalStack\n\n\nasync def main() -> None:\n    async with VocalStack(\"https://api.vocalstack.com\", api_key=os.environ[\"VOCALSTACK_API_KEY\"]) as vs:\n        grant = await vs.transcriptions.upload_recording(\"TRANSCRIPTION_ID\", url=\"https://example.com/part-2.mp3\")\n        job = await vs.jobs.wait(grant.recording.id)\n        print(job.state)\n\n\nasyncio.run(main())\n"},{"label":"curl","lang":"shell","source":"curl -X POST \"https://api.vocalstack.com/v1/transcriptions/{id}/recordings/upload\" \\\n  -H \"Authorization: Bearer $VOCALSTACK_API_KEY\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"settings\":{\"confidence\":{\"hideBelow\":{}}}}'"}],"x-errors":["insufficient_credit","quota_exceeded","rate_limited","idempotency_key_reused"],"x-vocalstack-auth":"userOrKey","x-vocalstack-scopes":["recordings:publish"]}},"/v1/transcriptions/{id}/restore":{"post":{"description":"Bring an archived transcription back.","operationId":"transcriptions.restore","parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Transcription"}}},"description":"OK"}},"security":[{"bearerAuth":[]}],"summary":"Restore a transcription","tags":["transcriptions"],"x-codeSamples":[{"label":"TypeScript SDK","lang":"typescript","source":"import { VocalStack } from '@vocalstack/sdk';\n\nconst vs = new VocalStack({ baseUrl: 'https://api.vocalstack.com', apiKey: process.env.VOCALSTACK_API_KEY });\n\nawait vs.transcriptions.restore('TRANSCRIPTION_ID');\n"},{"label":"Python SDK","lang":"python","source":"import asyncio\nimport os\n\nfrom vocalstack import VocalStack\n\n\nasync def main() -> None:\n    async with VocalStack(\"https://api.vocalstack.com\", api_key=os.environ[\"VOCALSTACK_API_KEY\"]) as vs:\n        await vs.transcriptions.restore(\"TRANSCRIPTION_ID\")\n\n\nasyncio.run(main())\n"},{"label":"curl","lang":"shell","source":"curl -X POST \"https://api.vocalstack.com/v1/transcriptions/{id}/restore\" \\\n  -H \"Authorization: Bearer $VOCALSTACK_API_KEY\""}],"x-vocalstack-auth":"userOrKey","x-vocalstack-scopes":["write"]}},"/v1/transcriptions/{id}/share-links":{"get":{"description":"Active share links of the transcription.","operationId":"transcriptions.listShareLinks","parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"properties":{"links":{"items":{"$ref":"#/components/schemas/ShareLink"},"type":"array"}},"type":"object"}}},"description":"OK"}},"security":[{"bearerAuth":[]}],"summary":"List a transcription's share links","tags":["transcriptions"],"x-codeSamples":[{"label":"TypeScript SDK","lang":"typescript","source":"import { VocalStack } from '@vocalstack/sdk';\n\nconst vs = new VocalStack({ baseUrl: 'https://api.vocalstack.com', apiKey: process.env.VOCALSTACK_API_KEY });\n\nconst links = await vs.transcriptions.listShareLinks('TRANSCRIPTION_ID');\nconsole.log(links);\n"},{"label":"Python SDK","lang":"python","source":"import asyncio\nimport os\n\nfrom vocalstack import VocalStack\n\n\nasync def main() -> None:\n    async with VocalStack(\"https://api.vocalstack.com\", api_key=os.environ[\"VOCALSTACK_API_KEY\"]) as vs:\n        print(await vs.transcriptions.list_share_links(\"TRANSCRIPTION_ID\"))\n\n\nasyncio.run(main())\n"},{"label":"curl","lang":"shell","source":"curl \"https://api.vocalstack.com/v1/transcriptions/{id}/share-links\" \\\n  -H \"Authorization: Bearer $VOCALSTACK_API_KEY\""}],"x-vocalstack-auth":"userOrKey","x-vocalstack-scopes":["read"]},"post":{"description":"A named link (\"Who is it for?\") to the read-only public page. `allowDownload`, `allowExport` and `allowTranslate` are all on by default and per link. Send `allowTranslate: false` to close a link to translation; it takes effect from the next request.","operationId":"transcriptions.createShareLink","parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateShareLink"}}},"required":true},"responses":{"201":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ShareLink"}}},"description":"Created"}},"security":[{"bearerAuth":[]}],"summary":"Create a share link","tags":["transcriptions"],"x-codeSamples":[{"label":"TypeScript SDK","lang":"typescript","source":"import { VocalStack } from '@vocalstack/sdk';\n\nconst vs = new VocalStack({ baseUrl: 'https://api.vocalstack.com', apiKey: process.env.VOCALSTACK_API_KEY });\n\nconst link = await vs.transcriptions.createShareLink('TRANSCRIPTION_ID', { label: 'Client review', allowExport: true });\nconsole.log(link);\n"},{"label":"Python SDK","lang":"python","source":"import asyncio\nimport os\n\nfrom vocalstack import VocalStack\n\n\nasync def main() -> None:\n    async with VocalStack(\"https://api.vocalstack.com\", api_key=os.environ[\"VOCALSTACK_API_KEY\"]) as vs:\n        print(await vs.transcriptions.create_share_link(\"TRANSCRIPTION_ID\", label=\"Client review\", allow_export=True))\n\n\nasyncio.run(main())\n"},{"label":"curl","lang":"shell","source":"curl -X POST \"https://api.vocalstack.com/v1/transcriptions/{id}/share-links\" \\\n  -H \"Authorization: Bearer $VOCALSTACK_API_KEY\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"label\":\"<label>\"}'"}],"x-vocalstack-auth":"userOrKey","x-vocalstack-scopes":["write"]}},"/v1/voices":{"get":{"description":"The account's cloned voices, newest first.","operationId":"voices.list","responses":{"200":{"content":{"application/json":{"schema":{"properties":{"voices":{"items":{"$ref":"#/components/schemas/Voice"},"type":"array"}},"type":"object"}}},"description":"OK"}},"security":[{"bearerAuth":[]}],"summary":"List voices","tags":["voices"],"x-codeSamples":[{"label":"TypeScript SDK","lang":"typescript","source":"import { VocalStack } from '@vocalstack/sdk';\n\nconst vs = new VocalStack({ baseUrl: 'https://api.vocalstack.com', apiKey: process.env.VOCALSTACK_API_KEY });\n\nconst voices = await vs.voices.list();\nfor (const v of voices) console.log(v.id, v.name);\n"},{"label":"Python SDK","lang":"python","source":"import asyncio\nimport os\n\nfrom vocalstack import VocalStack\n\n\nasync def main() -> None:\n    async with VocalStack(\"https://api.vocalstack.com\", api_key=os.environ[\"VOCALSTACK_API_KEY\"]) as vs:\n        for v in await vs.voices.list():\n            print(v.id, v.name)\n\n\nasyncio.run(main())\n"},{"label":"curl","lang":"shell","source":"curl \"https://api.vocalstack.com/v1/voices\" \\\n  -H \"Authorization: Bearer $VOCALSTACK_API_KEY\""}],"x-vocalstack-auth":"userOrKey","x-vocalstack-scopes":["read"]},"post":{"description":"Create a voice from a recorded sample and queue its training: multipart/form-data with a `sample` audio part and a `meta` JSON part. The voice comes back `training` with progress 0; poll GET /v1/voices/{id}. Training reports a real state — it never fakes progress, and a voice that cannot be trained ends `failed` with the reason on `error`.","operationId":"voices.create","parameters":[{"description":"Makes a retry safe: a request repeated with the same key and the same body within 24 hours returns the first response (with `Idempotent-Replayed: true`) instead of running again. The same key with a different request is 422 `idempotency_key_reused`; a retry while the first is still running is 409 `conflict`. Only a successful (2xx) response is remembered. Keys are per account.","in":"header","name":"Idempotency-Key","required":false,"schema":{"maxLength":255,"minLength":1,"type":"string"}}],"requestBody":{"content":{"multipart/form-data":{"schema":{"properties":{"meta":{"$ref":"#/components/schemas/CreateVoice"},"sample":{"format":"binary","type":"string"}},"required":["sample","meta"],"type":"object"}}},"required":true},"responses":{"201":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Voice"}}},"description":"Created"}},"security":[{"bearerAuth":[]}],"summary":"Create a voice","tags":["voices"],"x-codeSamples":[{"label":"TypeScript SDK","lang":"typescript","source":"import { VocalStack } from '@vocalstack/sdk';\n\nconst vs = new VocalStack({ baseUrl: 'https://api.vocalstack.com', apiKey: process.env.VOCALSTACK_API_KEY });\n\n// Asserts you have the speaker's permission (or pass draft: true).\nconst voice = await vs.voices.create({ name: 'Ana', sample: './ana-sample.wav' });\nconsole.log(voice.id);\n"},{"label":"Python SDK","lang":"python","source":"import asyncio\nimport os\n\nfrom vocalstack import VocalStack\n\n\nasync def main() -> None:\n    async with VocalStack(\"https://api.vocalstack.com\", api_key=os.environ[\"VOCALSTACK_API_KEY\"]) as vs:\n        # Asserts you have the speaker's permission (or pass draft=True).\n        voice = await vs.voices.create(name=\"Ana\", sample=\"./ana-sample.wav\")\n        print(voice.id)\n\n\nasyncio.run(main())\n"},{"label":"curl","lang":"shell","source":"curl -X POST \"https://api.vocalstack.com/v1/voices\" \\\n  -H \"Authorization: Bearer $VOCALSTACK_API_KEY\" \\\n  -F 'meta={\"name\":\"Ana\",\"consent\":true}' \\\n  -F sample=@sample.wav"}],"x-errors":["invalid_options","not_found","idempotency_key_reused"],"x-vocalstack-auth":"userOrKey","x-vocalstack-scopes":["write"]}},"/v1/voices/available":{"get":{"description":"The voices that can speak a language, best first (`default` marks the one a recording that pins none gets). Open to listeners; a bearer token also returns that account's ready cloned voices. A clip is synthesised once per language and fanned out, so this is the set a host may pin in `settings.voiceByLang` — and all a listener can ever be offered.","operationId":"voices.available","parameters":[{"in":"query","name":"lang","required":true,"schema":{"type":"string"}},{"description":"Also offer the cloned voices this recording uses, to whoever may read it (its owner, or a listener with `listenerToken` or `shareCode`): what a listener of it may pick from.","in":"query","name":"recording","schema":{"format":"uuid","type":"string"}},{"description":"With `recording`: the listener token of a private broadcast.","in":"query","name":"listenerToken","schema":{"type":"string"}},{"description":"With `recording`: a share link's code.","in":"query","name":"shareCode","schema":{"type":"string"}},{"description":"The bearer token, for a client that cannot set a header (an `<audio>` element).","in":"query","name":"access_token","schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/VoiceCatalogue"}}},"description":"OK"}},"summary":"List available voices","tags":["voices"],"x-codeSamples":[{"label":"TypeScript SDK","lang":"typescript","source":"import { VocalStack } from '@vocalstack/sdk';\n\nconst vs = new VocalStack({ baseUrl: 'https://api.vocalstack.com', apiKey: process.env.VOCALSTACK_API_KEY });\n\nconst catalogue = await vs.voices.available('es');\nconsole.log(catalogue);\n"},{"label":"Python SDK","lang":"python","source":"import asyncio\nimport os\n\nfrom vocalstack import VocalStack\n\n\nasync def main() -> None:\n    async with VocalStack(\"https://api.vocalstack.com\", api_key=os.environ[\"VOCALSTACK_API_KEY\"]) as vs:\n        print(await vs.voices.available(\"es\"))\n\n\nasyncio.run(main())\n"},{"label":"curl","lang":"shell","source":"curl \"https://api.vocalstack.com/v1/voices/available?lang=<lang>\" \\\n  -H \"Authorization: Bearer $VOCALSTACK_API_KEY\""}],"x-errors":["invalid_options"],"x-vocalstack-auth":"optional","x-vocalstack-scopes":[]}},"/v1/voices/from-speaker":{"post":{"description":"Create a voice from one speaker of a recording — no sample to upload, because the recording's own audio is the sample. Works while the recording is still running: the server joins the stretches that speaker actually spoke for and clones from those. Naming them here also labels them in the transcript and associates them with the new voice. Needs at least 30 seconds of them; the refusal says how much there is.","operationId":"voices.createFromSpeaker","parameters":[{"description":"Makes a retry safe: a request repeated with the same key and the same body within 24 hours returns the first response (with `Idempotent-Replayed: true`) instead of running again. The same key with a different request is 422 `idempotency_key_reused`; a retry while the first is still running is 409 `conflict`. Only a successful (2xx) response is remembered. Keys are per account.","in":"header","name":"Idempotency-Key","required":false,"schema":{"maxLength":255,"minLength":1,"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateVoiceFromSpeaker"}}},"required":true},"responses":{"201":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Voice"}}},"description":"Created"}},"security":[{"bearerAuth":[]}],"summary":"Create a voice from a speaker","tags":["voices"],"x-codeSamples":[{"label":"TypeScript SDK","lang":"typescript","source":"import { VocalStack } from '@vocalstack/sdk';\n\nconst vs = new VocalStack({ baseUrl: 'https://api.vocalstack.com', apiKey: process.env.VOCALSTACK_API_KEY });\n\nconst voice = await vs.voices.createFromSpeaker({ recordingId: 'RECORDING_ID', speaker: 0, name: 'Ana' });\nconsole.log(voice.id);\n"},{"label":"Python SDK","lang":"python","source":"import asyncio\nimport os\n\nfrom vocalstack import VocalStack\n\n\nasync def main() -> None:\n    async with VocalStack(\"https://api.vocalstack.com\", api_key=os.environ[\"VOCALSTACK_API_KEY\"]) as vs:\n        voice = await vs.voices.create_from_speaker(recording_id=\"RECORDING_ID\", speaker=0, name=\"Ana\")\n        print(voice.id)\n\n\nasyncio.run(main())\n"},{"label":"curl","lang":"shell","source":"curl -X POST \"https://api.vocalstack.com/v1/voices/from-speaker\" \\\n  -H \"Authorization: Bearer $VOCALSTACK_API_KEY\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"name\":\"<name>\",\"recordingId\":\"<recordingId>\",\"speaker\":0}'"}],"x-errors":["invalid_options","not_found","forbidden","idempotency_key_reused"],"x-vocalstack-auth":"userOrKey","x-vocalstack-scopes":["write"]}},"/v1/voices/preview":{"get":{"description":"A few seconds of a voice speaking a language, Ogg/Opus (`format=caf` for Apple's media stack). Only voices the catalogue offers can be previewed.","operationId":"voices.preview","parameters":[{"in":"query","name":"voice","required":true,"schema":{"type":"string"}},{"in":"query","name":"lang","required":true,"schema":{"type":"string"}},{"in":"query","name":"format","schema":{"enum":["ogg","caf"],"type":"string"}}],"responses":{"200":{"description":"audio/ogg or audio/x-caf"},"202":{"description":"Being rendered right now; poll. `engine_unavailable` (503) is the other thing, and means the engine is not there."}},"summary":"Preview a voice","tags":["voices"],"x-codeSamples":[{"label":"TypeScript SDK","lang":"typescript","source":"import { VocalStack } from '@vocalstack/sdk';\n\nconst vs = new VocalStack({ baseUrl: 'https://api.vocalstack.com', apiKey: process.env.VOCALSTACK_API_KEY });\n\nconst res = await vs.voices.preview('VOICE', 'es');\nconsole.log(res.status); // 202 while it is prepared\n"},{"label":"Python SDK","lang":"python","source":"import asyncio\nimport os\n\nfrom vocalstack import VocalStack\n\n\nasync def main() -> None:\n    async with VocalStack(\"https://api.vocalstack.com\", api_key=os.environ[\"VOCALSTACK_API_KEY\"]) as vs:\n        ogg = await vs.voices.preview(\"VOICE\", \"es\")\n        print(len(ogg), \"bytes\")\n\n\nasyncio.run(main())\n"},{"label":"curl","lang":"shell","source":"curl \"https://api.vocalstack.com/v1/voices/preview?voice=<voice>&lang=<lang>\" \\\n  -H \"Authorization: Bearer $VOCALSTACK_API_KEY\""}],"x-errors":["not_found","preview_pending","language_unavailable","engine_unavailable"],"x-vocalstack-auth":"optional","x-vocalstack-scopes":[]}},"/v1/voices/{id}":{"delete":{"description":"Delete the voice, its recorded sample and anything trained from it.","operationId":"voices.delete","parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK"}},"security":[{"bearerAuth":[]}],"summary":"Delete a voice","tags":["voices"],"x-codeSamples":[{"label":"TypeScript SDK","lang":"typescript","source":"import { VocalStack } from '@vocalstack/sdk';\n\nconst vs = new VocalStack({ baseUrl: 'https://api.vocalstack.com', apiKey: process.env.VOCALSTACK_API_KEY });\n\nawait vs.voices.delete('VOICE_ID');\n"},{"label":"Python SDK","lang":"python","source":"import asyncio\nimport os\n\nfrom vocalstack import VocalStack\n\n\nasync def main() -> None:\n    async with VocalStack(\"https://api.vocalstack.com\", api_key=os.environ[\"VOCALSTACK_API_KEY\"]) as vs:\n        await vs.voices.delete(\"VOICE_ID\")\n\n\nasyncio.run(main())\n"},{"label":"curl","lang":"shell","source":"curl -X DELETE \"https://api.vocalstack.com/v1/voices/{id}\" \\\n  -H \"Authorization: Bearer $VOCALSTACK_API_KEY\""}],"x-errors":["not_found","forbidden"],"x-vocalstack-auth":"userOrKey","x-vocalstack-scopes":["write"]},"get":{"description":"Training state: `training` / `ready` / `failed`, with `progress` and, when it failed, `error`.","operationId":"voices.get","parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Voice"}}},"description":"OK"}},"security":[{"bearerAuth":[]}],"summary":"Get a voice","tags":["voices"],"x-codeSamples":[{"label":"TypeScript SDK","lang":"typescript","source":"import { VocalStack } from '@vocalstack/sdk';\n\nconst vs = new VocalStack({ baseUrl: 'https://api.vocalstack.com', apiKey: process.env.VOCALSTACK_API_KEY });\n\nconst voice = await vs.voices.get('VOICE_ID');\nconsole.log(voice);\n"},{"label":"Python SDK","lang":"python","source":"import asyncio\nimport os\n\nfrom vocalstack import VocalStack\n\n\nasync def main() -> None:\n    async with VocalStack(\"https://api.vocalstack.com\", api_key=os.environ[\"VOCALSTACK_API_KEY\"]) as vs:\n        print(await vs.voices.get(\"VOICE_ID\"))\n\n\nasyncio.run(main())\n"},{"label":"curl","lang":"shell","source":"curl \"https://api.vocalstack.com/v1/voices/{id}\" \\\n  -H \"Authorization: Bearer $VOCALSTACK_API_KEY\""}],"x-vocalstack-auth":"userOrKey","x-vocalstack-scopes":["read"]},"patch":{"description":"Rename a voice, recolour it, or switch it off. Absent fields are left alone; `color: \"\"` clears a chosen colour. Not the place to withdraw permission — that deletes the voice.","operationId":"voices.update","parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PatchVoice"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Voice"}}},"description":"OK"}},"security":[{"bearerAuth":[]}],"summary":"Update a voice","tags":["voices"],"x-codeSamples":[{"label":"TypeScript SDK","lang":"typescript","source":"import { VocalStack } from '@vocalstack/sdk';\n\nconst vs = new VocalStack({ baseUrl: 'https://api.vocalstack.com', apiKey: process.env.VOCALSTACK_API_KEY });\n\nconst voice = await vs.voices.update('VOICE_ID', { name: 'Ana (warm)' });\nconsole.log(voice);\n"},{"label":"Python SDK","lang":"python","source":"import asyncio\nimport os\n\nfrom vocalstack import VocalStack\n\n\nasync def main() -> None:\n    async with VocalStack(\"https://api.vocalstack.com\", api_key=os.environ[\"VOCALSTACK_API_KEY\"]) as vs:\n        print(await vs.voices.update(\"VOICE_ID\", name=\"Ana (warm)\"))\n\n\nasyncio.run(main())\n"},{"label":"curl","lang":"shell","source":"curl -X PATCH \"https://api.vocalstack.com/v1/voices/{id}\" \\\n  -H \"Authorization: Bearer $VOCALSTACK_API_KEY\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"color\":\"<color>\"}'"}],"x-errors":["invalid_options","not_found","forbidden"],"x-vocalstack-auth":"userOrKey","x-vocalstack-scopes":["write"]}},"/v1/voices/{id}/preview":{"get":{"description":"The cloned voice speaking, Ogg/Opus — `format=caf` for Apple's media stack, which will not play Ogg. 409 while it is not `ready`. The owner's token may ride in `access_token`, because an `<audio>` element cannot carry a header.","operationId":"voices.clonePreview","parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string"}},{"in":"query","name":"lang","schema":{"type":"string"}},{"in":"query","name":"format","schema":{"enum":["ogg","caf"],"type":"string"}},{"description":"The bearer token, for a client that cannot set a header (an `<audio>` element).","in":"query","name":"access_token","schema":{"type":"string"}},{"description":"The voice as it speaks in this recording, for whoever may read the recording (its owner, or a listener with `listenerToken` or `shareCode`) rather than for the voice's owner alone. 404 when the voice reads nobody in it.","in":"query","name":"recording","schema":{"format":"uuid","type":"string"}},{"description":"With `recording`: the listener token of a private broadcast.","in":"query","name":"listenerToken","schema":{"type":"string"}},{"description":"With `recording`: a share link's code.","in":"query","name":"shareCode","schema":{"type":"string"}}],"responses":{"200":{"description":"audio/ogg or audio/x-caf"},"202":{"description":"The render has been started by this very ask; it is not finished. Poll — the next ask is a cache hit. Body is an error-shaped `preview_pending`."}},"security":[{"bearerAuth":[]}],"summary":"Preview a cloned voice","tags":["voices"],"x-codeSamples":[{"label":"TypeScript SDK","lang":"typescript","source":"import { VocalStack } from '@vocalstack/sdk';\n\nconst vs = new VocalStack({ baseUrl: 'https://api.vocalstack.com', apiKey: process.env.VOCALSTACK_API_KEY });\n\nconst res = await vs.voices.clonePreview('VOICE_ID', { lang: 'fr' });\nconsole.log((await res.arrayBuffer()).byteLength);\n"},{"label":"Python SDK","lang":"python","source":"import asyncio\nimport os\n\nfrom vocalstack import VocalStack\n\n\nasync def main() -> None:\n    async with VocalStack(\"https://api.vocalstack.com\", api_key=os.environ[\"VOCALSTACK_API_KEY\"]) as vs:\n        ogg = await vs.voices.clone_preview(\"VOICE_ID\", lang=\"fr\")\n        print(len(ogg), \"bytes\")\n\n\nasyncio.run(main())\n"},{"label":"curl","lang":"shell","source":"curl \"https://api.vocalstack.com/v1/voices/{id}/preview\" \\\n  -H \"Authorization: Bearer $VOCALSTACK_API_KEY\""}],"x-errors":["invalid_state","not_found","preview_pending","language_unavailable"],"x-vocalstack-auth":"listener","x-vocalstack-scopes":["read"]}},"/v1/voices/{id}/sample":{"get":{"description":"The audio this voice was made from, for its owner alone. Ranged. It is what lets a draft picked up after a reload be the screen that was left — take, waveform and playback — rather than a preview of one.","operationId":"voices.sample","parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string"}},{"description":"The bearer token, for a client that cannot set a header (an `<audio>` element).","in":"query","name":"access_token","schema":{"type":"string"}}],"responses":{"200":{"description":"the recorded sample, as it was uploaded"}},"security":[{"bearerAuth":[]}],"summary":"Get a voice's sample","tags":["voices"],"x-codeSamples":[{"label":"TypeScript SDK","lang":"typescript","source":"import { VocalStack } from '@vocalstack/sdk';\n\nconst vs = new VocalStack({ baseUrl: 'https://api.vocalstack.com', apiKey: process.env.VOCALSTACK_API_KEY });\n\nconst res = await vs.voices.sample('VOICE_ID');\nconsole.log(res.headers.get('content-type'));\n"},{"label":"Python SDK","lang":"python","source":"import asyncio\nimport os\n\nfrom vocalstack import VocalStack\n\n\nasync def main() -> None:\n    async with VocalStack(\"https://api.vocalstack.com\", api_key=os.environ[\"VOCALSTACK_API_KEY\"]) as vs:\n        audio = await vs.voices.sample(\"VOICE_ID\")\n        print(len(audio), \"bytes\")\n\n\nasyncio.run(main())\n"},{"label":"curl","lang":"shell","source":"curl \"https://api.vocalstack.com/v1/voices/{id}/sample\" \\\n  -H \"Authorization: Bearer $VOCALSTACK_API_KEY\""}],"x-errors":["not_found","forbidden"],"x-vocalstack-auth":"userOrKey","x-vocalstack-scopes":["read"]}}},"tags":[{"description":"Stream audio over a WebSocket (a realtime session) and get transcript events back. Nothing is stored.","name":"realtime","x-displayName":"Realtime"},{"description":"Stored transcripts of a file, a link, or audio captured as it is spoken.","name":"transcriptions","x-displayName":"Transcriptions"},{"description":"The links listeners open, fed by live transcriptions or by the broadcast's own audio sources.","name":"broadcasts","x-displayName":"Broadcasts"},{"description":"One take inside a transcription: its transcript, audio, translations, voice and summary.","name":"recordings","x-displayName":"Recordings"},{"description":"The server's work on a file or link recording.","name":"jobs","x-displayName":"Jobs"},{"description":"Word edits and line rewrites to a recording's transcript.","name":"corrections","x-displayName":"Corrections"},{"description":"Named, read-only links to a broadcast's or a transcription's public page.","name":"share-links","x-displayName":"Share links"},{"description":"The voices spoken translations are read in, stock and cloned.","name":"voices","x-displayName":"Voices"},{"description":"Find sentences across transcripts, in any language.","name":"search","x-displayName":"Search"},{"description":"The languages for transcription, translation and voices.","name":"languages","x-displayName":"Languages"},{"description":"The account's RTMP server URL and stream key, for sending audio from an encoder.","name":"rtmp","x-displayName":"RTMP"},{"description":"Who the credential is.","name":"me","x-displayName":"Account"},{"description":"The account's plan, credit balance and limits, the price list, the ledger and the charges.","name":"billing","x-displayName":"Billing"},{"description":"Health and this OpenAPI document.","name":"meta","x-displayName":"Meta"}],"x-websocket":{"accountEvents":{"description":"The owner's dashboard: `events.ready` once the socket is listening, then `lists.changed` whenever a list of transcriptions or broadcasts of any principal the caller may act for is out of date — a recording changed state, a transcription settled, a title was written, or the owner changed something from another device. It says that, not what: re-read `GET /v1/transcriptions` and `GET /v1/broadcasts`. It replaces polling those lists. A user may hold a few of these at once (5 by default): opening one more closes their oldest with code 4001. One is closed with code 4002 after a day. After either, do not reconnect at once — a client that does takes its place straight back; reconnect when the user next uses that screen.","path":"/v1/events","query":"access_token (a browser cannot set a header on a socket)","serverFrames":["events.ready","lists.changed"]},"broadcastEvents":{"description":"What a screen waiting on a broadcast needs: when a recording starts in it (`recording.started`: it is live), when that recording ends (`recording.ended`), a file job's progress (`job.progress`), and a pulled stream's state (`stream.ingest`). Each frame's `transcriptionId` is the transcription the recording is made in: on a broadcast's socket, the live transcription feeding it.","path":"/v1/broadcasts/{id}/events","query":"listenerToken / shareCode / access_token, as on subscribe","serverFrames":["recording.started","recording.ended","job.progress","stream.ingest"]},"realtimeSession":{"binary":"input.kind=live: raw PCM per SessionOptions.audio (pcm_f32le, 16 kHz, mono). input.kind=file: the encoded file's own bytes, decoded server-side","clientFrames":["ClientFrame"],"connectErrors":{"description":"Rejected upgrades carry the ApiErrorBody envelope over HTTP.","statuses":{"401":"invalid_token","409":"invalid_token (same-machine replay)","410":"session_expired"}},"description":"Connect to the `streamUrl` a realtime session returned (treat it as opaque).","path":"/v1/realtime/sessions/{token}/socket","rules":["transcript.draft is a FULL REPLACEMENT of the live tail (replace-not-append)","segment.seq is per realtime session, monotonic, 0-based, gapless; ordering only","single total order per realtime session; the draft tail begins at the last committed segment's end","committed segments always include words; their text/speaker may later be corrected by a transcript.amended frame (same seq) — apply it in place","sentences.enabled: transcript.sentence is a FULL REPLACEMENT upsert keyed by sentenceId (status open -> closed; dropped = discard); seqs lists the committed segments it covers","sentences.enabled + translate: translations attach to transcript.sentence (provisional=true while open, replaced on close) instead of transcript.translated","events.drafts / events.words govern live frames only","clients MUST ignore unknown server frame types; servers ignore unknown client text frames","no resume: a dropped connection ends the realtime session; reconnect = create a new one","backpressure: commits are never dropped; drafts/words coalesce-latest; slow consumers are closed with slow_consumer","saturation may surface OUTSIDE this envelope as a hanging connect or bare 503 from the load balancer; clients synthesize capacity_exceeded from a connect/ready deadline","file input (input.kind=file): binary frames carry the encoded file; transcription starts at eof, so no transcript frames arrive before it","file input emits transcript.progress on a timer (also the keep-alive); maxDurationS applies to the file's DECODED duration — a longer-than-allowed file is refused with invalid_audio, or truncated with session.done/max_duration when the container declared no duration","file input defaults events.drafts to false (a draft previews a live tail that a file does not have); ask explicitly to receive them","session.done carries stats {audioS, processingS, realtimeFactor} measured server-side; processingS excludes a file's upload and decode setup, and the object is the extension point for future metadata about the realtime session"],"serverFrames":["ServerFrame"]},"subscribe":{"binary":"Ogg/Opus pages of the original audio, only after subscribe.update{audio:true}; droppable under backpressure","clientFrames":["SubscriberClientFrame"],"description":"Follow a recording: first frame `subscribe.ready {lastSeq}`, then `GET /v1/recordings/{id}/transcript` and discard buffered frames with `seq <= lastSeq`. `voice` picks which voice speaks the language for this listener; two listeners on the same (lang, voice) share one synthesis, so changing voice means reopening the socket. Send `{\"type\":\"subscribe.update\",\"lang\":\"ja\"}` to switch language, `{\"type\":\"subscribe.update\",\"audio\":true}` to receive live Opus.","path":"/v1/recordings/{id}/subscribe","query":"lang (translation target — through a share link it needs the link's allowTranslate, else the upgrade is refused 403 and a mid-socket subscribe.update{lang} closes the socket with 1008), voice (which voice speaks it — a voice id from GET /v1/voices/available?lang=…; omit for the recording's settings.voiceByLang pin, else the engine's default), listenerToken / shareCode / access_token, role, device","rules":["durable frames carry seq (gapless per recording); ephemeral frames (transcript.words, audio.chunk) carry none; recordings emit no transcript.draft","a frame withheld from a role still consumes its seq: listeners receive {type:'noop', seq} in its place, so a real jump always means a missed frame","listeners receive translations filtered to their lang; suppressed lines arrive with text='' and suppressed=true","transcript.voice is filtered to the listener's (lang, voice): a clip reaches only subscribers whose voice= matches its voiceKey (both empty = the engine's default), and one clip serves every listener on that pair — the voice is chosen per listener, the synthesis is paid once","a long line is spoken in pieces (part, parts): the engine cuts it at its clause boundaries so the first piece can be heard while the rest are still being made. The pieces share the sentence id, arrive in order, and are played back to back — one line on screen, with one translation and one place in the reading. part=0/parts=1 is a line spoken whole","transcript.voiceStatus is filtered to the same (lang, voice) pair as transcript.voice: state 'preparing' means that voice's assets are downloading and the sentences spoken meanwhile are held and will arrive as clips; 'unavailable' means it cannot be spoken here and detail says why. A listener never hears a different voice than the one asked for","admin subscribers (owning-principal members) receive the full frame set plus audience aggregates","recording.state announces LIVE ⇄ PAUSED → ENDED → FINALIZED; listeners are not disconnected at ENDED","transcript.corrected is a human edit; transcript.amended is an engine rewrite — clients clear uncertainty marks only on the former"],"serverFrames":["ServerFrame","SubscriberFrame"]},"transcriptionEvents":{"description":"What a screen waiting on a transcription needs: when a recording starts in it (`recording.started`: it is live), when that recording ends (`recording.ended`), a file job's progress (`job.progress`), and a pulled stream's state (`stream.ingest`). Each frame's `transcriptionId` is the transcription the recording is made in.","path":"/v1/transcriptions/{id}/events","query":"listenerToken / shareCode / access_token, as on subscribe","serverFrames":["recording.started","recording.ended","job.progress","stream.ingest"]}}}