Reference
Endpoints
Every operation of the VocalStack API, each with its TypeScript SDK, Python SDK and curl sample. Base URL: https://api.vocalstack.com.
billing
GET /v1/billingGet the account's billing
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.PUT /v1/billing/auto-top-upSet auto top-up
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.POST /v1/billing/auto-top-up/retryRun auto top-up now
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`).GET /v1/billing/chargesList the charges
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`.POST /v1/billing/checkoutsOpen a checkout
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`.GET /v1/billing/checkouts/{id}Get a checkout
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`).GET /v1/billing/detailsGet the invoice details
The name, email, address and tax id invoices and receipts carry. `email` is the sign-in email until another is saved.PUT /v1/billing/detailsSet the invoice details
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`.GET /v1/billing/ledgerList the ledger
The account's credit movements, newest first: signup credit, top-ups, monthly credit, charges, corrections, refunds. Page with `cursor`.PUT /v1/billing/low-balance-alertSet the low-balance alert
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.GET /v1/billing/payment-methodsList the payment methods
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.POST /v1/billing/payment-methods/manageAdd or change a payment method
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.DELETE /v1/billing/payment-methods/{id}Remove a payment method
Remove a saved card. `409 payment_method_in_use` while it pays for the subscription. Removing the last one turns auto top-up off.GET /v1/billing/paymentsList the payments
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`.GET /v1/billing/payments/{id}/documentGet a payment's invoice or receipt
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`).GET /v1/billing/plansList the plans
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.GET /v1/billing/portalGet the customer portal link
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.POST /v1/billing/promo-codes/checkCheck a promo code
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.POST /v1/billing/subscription/cancelCancel the subscription
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.POST /v1/billing/subscription/intervalSwitch the billing interval
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.DELETE /v1/billing/subscription/intervalKeep the billing interval
Take back a scheduled interval switch.POST /v1/billing/subscription/uncancelKeep the subscription
Take back a cancellation scheduled for the period end.GET /v1/billing/usageGet the usage report
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`).
broadcasts
GET /v1/broadcastsList broadcasts
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.POST /v1/broadcastsCreate a broadcast
Create a broadcast: the link listeners open (`code`), which a host can change later (the old one keeps working).GET /v1/broadcasts/{id}Get a broadcast
One broadcast of the acting principal's, by id.PATCH /v1/broadcasts/{id}Update a broadcast
Rename, change access/password, change the link (`code`; the links it had keep opening it), or update default capture settings.POST /v1/broadcasts/{id}/archiveArchive a broadcast
Archive the broadcast: hidden, its links dead, its data kept until it is restored or purged.POST /v1/broadcasts/{id}/audio-sourcesAdd an audio source
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.DELETE /v1/broadcasts/{id}/audio-sources/{source}Remove an audio source
Take an audio source off a broadcast. What was recorded from it stays. Returns the broadcast.GET /v1/broadcasts/{id}/audio-sources/{source}/checkCheck an audio source
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.POST /v1/broadcasts/{id}/purgePurge a broadcast
Delete an archived broadcast and everything in it, for good.GET /v1/broadcasts/{id}/recordingsList a broadcast's recordings
The broadcast's recordings.POST /v1/broadcasts/{id}/recordingsStart a recording
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`).POST /v1/broadcasts/{id}/restoreRestore a broadcast
Bring an archived broadcast back.GET /v1/broadcasts/{id}/share-linksList a broadcast's share links
Active share links of the broadcast.POST /v1/broadcasts/{id}/share-linksCreate a share link
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.
corrections
POST /v1/corrections/{id}/revertRevert a correction
Undo one correction.GET /v1/recordings/{id}/correctionsList corrections
Every correction made to the recording's transcript.POST /v1/recordings/{id}/correctionsApply corrections
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.POST /v1/recordings/{id}/retranscribeRetranscribe a line
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.
jobs
GET /v1/recordings/{id}/jobGet a job
The recording's transcription job: its state and progress (done/total segments, media seconds, pct).GET /v1/recordings/{id}/job/previewGet a job's preview media
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.POST /v1/recordings/{id}/job/retryRetry a job
Re-queue a failed job from its stored source.POST /v1/recordings/{id}/job/startStart a cropped job
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.
languages
GET /v1/languagesList languages
Transcription, translation and voice lists with picker metadata.
me
GET /v1/meGet the current account
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.
meta
GET /v1/healthCheck health
Liveness: 200 while this server is up.GET /v1/openapi.jsonGet the OpenAPI document
This API's OpenAPI document, with this host in `servers`.
realtime
GET /v1/realtime/capabilitiesGet realtime capabilities
What the realtime socket takes: supported languages, audio formats, and limits.POST /v1/realtime/sessionsCreate a realtime session
Create a realtime session: a signed, stateless token for one WebSocket (`streamUrl`). Nothing is stored server-side.
recordings
GET /v1/recordings/{id}Get a recording
One recording, by id: for its owner, or any listener who can reach its broadcast or transcription.POST /v1/recordings/{id}/archiveArchive a recording
Archive the recording: hidden, its data kept.GET /v1/recordings/{id}/audioDownload a recording's audio
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.GET /v1/recordings/{id}/audio/clipGet an audio clip
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.GET /v1/recordings/{id}/eventsGet a recording's event log
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.GET /v1/recordings/{id}/exportExport a recording
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.GET /v1/recordings/{id}/media-tokenGet a media token
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.POST /v1/recordings/{id}/pausePause a recording
Pause capture. Idempotent.POST /v1/recordings/{id}/publish-tokenCreate a publish token
Re-attach a publisher (reconnect or another device; takeover=true demotes the current one).POST /v1/recordings/{id}/purgePurge a recording
Delete an archived recording for good.POST /v1/recordings/{id}/restoreRestore a recording
Bring an archived recording back.POST /v1/recordings/{id}/resumeResume a recording
Resume a paused recording.PATCH /v1/recordings/{id}/settingsUpdate recording settings
Mid-recording capture settings (mode, language, labels).PUT /v1/recordings/{id}/speakers/{idx}Name a speaker
Name a speaker of this recording.POST /v1/recordings/{id}/stopStop a recording
End capture; finalize runs asynchronously. Only from paused.POST /v1/recordings/{id}/summarizeRequest a summary
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.GET /v1/recordings/{id}/summaryGet a recording's summary
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`.GET /v1/recordings/{id}/transcriptGet a transcript
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.GET /v1/recordings/{id}/translationGet a translation
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.GET /v1/recordings/{id}/voiceGet a recording's voice tracks
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`.POST /v1/recordings/{id}/voiceRender voice tracks
Render spoken tracks for a finished recording, one job per language (1–10 codes, each one /v1/languages says has a voice). Owner only.GET /v1/recordings/{id}/voice/{sentence}Get a spoken sentence
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`.
rtmp
GET /v1/rtmpGet RTMP status
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.
search
GET /v1/searchSearch transcripts
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.
share-links
PATCH /v1/share-links/{id}Update a share link
The share sheet's toggles: any of `label`, `allowDownload`, `allowExport`, `allowTranslate`. Omitted fields are left as they are.DELETE /v1/share-links/{id}Revoke a share link
Revoke: the public page 404s at once.
transcriptions
GET /v1/transcriptionsList transcriptions
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.POST /v1/transcriptionsCreate a transcription
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`.POST /v1/transcriptions/liveCreate a live transcription
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.GET /v1/transcriptions/probeProbe a link
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.GET /v1/transcriptions/{id}Get a transcription
One transcription of the acting principal's, by id.PATCH /v1/transcriptions/{id}Update a transcription
Rename, or update default capture settings.POST /v1/transcriptions/{id}/archiveArchive a transcription
Archive the transcription: hidden, its links dead, its data kept until it is restored or purged.PUT /v1/transcriptions/{id}/broadcasts/{broadcast}Attach a broadcast
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.DELETE /v1/transcriptions/{id}/broadcasts/{broadcast}Detach a broadcast
Stop feeding the broadcast from the live transcription `id`. Returns the transcription.POST /v1/transcriptions/{id}/purgePurge a transcription
Delete an archived transcription and everything in it, for good.GET /v1/transcriptions/{id}/recordingsList a transcription's recordings
The transcription's recordings.POST /v1/transcriptions/{id}/recordingsStart a recording
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`).POST /v1/transcriptions/{id}/recordings/uploadUpload a recording
A file or link recording in this transcription: no live phase, transcribed at full speed.POST /v1/transcriptions/{id}/restoreRestore a transcription
Bring an archived transcription back.GET /v1/transcriptions/{id}/share-linksList a transcription's share links
Active share links of the transcription.POST /v1/transcriptions/{id}/share-linksCreate a share link
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.
voices
GET /v1/voicesList voices
The account's cloned voices, newest first.POST /v1/voicesCreate a voice
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`.GET /v1/voices/availableList available voices
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.POST /v1/voices/from-speakerCreate a voice from a speaker
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.GET /v1/voices/previewPreview a voice
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.GET /v1/voices/{id}Get a voice
Training state: `training` / `ready` / `failed`, with `progress` and, when it failed, `error`.PATCH /v1/voices/{id}Update a voice
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.DELETE /v1/voices/{id}Delete a voice
Delete the voice, its recorded sample and anything trained from it.GET /v1/voices/{id}/previewPreview a cloned voice
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.GET /v1/voices/{id}/sampleGet a voice's sample
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.