vocalstack / sdk
VocalStack speech API SDK for PHP: one client grouped by resource, file and link transcription, realtime WebSocket streaming.
Requires
- php: >=8.1
- ext-json: *
Requires (Dev)
- phpunit/phpunit: ^10.5
- phrity/websocket: ^3
Suggests
- ext-curl: The default HTTP client (VocalStack\Http\CurlHttpClient); without it pass your own VocalStack\Http\HttpClient
- phrity/websocket: Realtime sessions, publishing and event sockets (^3)
Provides
None
Conflicts
None
Replaces
None
This package is auto-updated.
Last update: 2026-10-22 17:45:19 UTC
README
The VocalStack speech API for PHP (8.1 or newer, synchronous): one client,
VocalStack\VocalStack, with one group per resource. Requests go through a
pluggable HTTP client (cURL by default); answers are associative arrays
decoded from the API's JSON, with the wire's camelCase keys, as the API's one
OpenAPI document (/v1/openapi.json) describes them.
<?php require 'vendor/autoload.php'; use VocalStack\VocalStack; $vs = new VocalStack(apiKey: getenv('VOCALSTACK_API_KEY')); // baseUrl defaults to https://api.vocalstack.com $tx = $vs->transcriptions->create(['url' => 'https://example.com/talk.mp3']); // or create(file: 'talk.mp3') $job = $vs->jobs->wait($tx['job']['recordingId']); // done | failed | cropping $snap = $vs->recordings->transcript($job['recordingId']); echo implode(' ', array_column($snap['sentences'], 'text')), "\n"; file_put_contents('talk.srt', $vs->recordings->export($job['recordingId'], 'srt'));
Install
composer require vocalstack/sdk
Guides
- Quickstart: An API key, the SDK, and your first transcript in a dozen lines.
- Transcribe a file: Upload a file, wait for its job, read the transcript and export it. Asynchronous; nothing to keep open.
- Transcribe a link: The server fetches a public URL or a YouTube video, then transcribes it like a file. Asynchronous, with a fetch phase first.
- Transcribe a batch of files: Send many files or links as one batch, start it once, and read every transcript when it is done. Cheaper than one transcription per file.
- Stream a microphone: Live audio in, transcript events out, over one WebSocket. Drafts replace the live tail; commits append.
- Stream a file at full speed: Send a file's bytes through a realtime session and read the transcript as fast as the server makes it. Nothing is stored.
- Go live: Publish live audio to a stored transcription, and put it on a broadcast link listeners open. Control is HTTP; audio is one WebSocket.
- Listen: Follow a recording as it happens, in any language: subscribe, catch up from a snapshot, then apply live frames in order.
- Live translation and spoken translations: Translate 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.
- Connect an app with one click: Let the people who use your app connect their VocalStack account with one Authenticate click — OAuth 2.0 authorization code with PKCE, where the access token is an API key — instead of pasting a key.
- Errors, retries and limits: One error envelope everywhere; what to retry (429, 503 unavailable) and how to make a retry safe (Idempotency-Key); credit, plan limits and the 402s.
What is what
| Name | What it is | Group |
|---|---|---|
| realtime session | a signed token plus one socket; nothing stored | $vs->realtime |
| broadcast | the link listeners open | $vs->broadcasts |
| transcription | a file, link, microphone or stream, transcribed and stored | $vs->transcriptions |
| recording | one take inside a broadcast or a transcription | $vs->recordings |
| job | a file or link recording's transcription work | $vs->jobs |
| batch | many files transcribed together, for less than one transcription each | $vs->batches |
The other groups: $vs->corrections, $vs->shareLinks, $vs->voices,
$vs->search, $vs->me, $vs->connect, $vs->billing, $vs->languages,
$vs->rtmp, $vs->events, $vs->meta. Method names are the document's
operationIds in camelCase (broadcasts->startRecording is
POST /v1/broadcasts/{id}/recordings). Ids are positional; a request body is
one array with the wire's camelCase keys (['settings' => ['spokenLanguage' => 'en']]);
a few fixed options are named arguments (idempotencyKey:, lang:,
listenerToken:). Lists return lists (every page of a paged list; the
...Page methods read one); binary answers (recordings->export,
recordings->clip, voices->sample, ...) return the bytes as a string.
A file to upload is a path string, a stream resource, or
VocalStack\Upload::bytes($data, 'name.mp3'). Paths and streams are streamed,
never read whole into memory.
new VocalStack( apiKey: null, // an API key (vs_key_...), for a server baseUrl: null, // default https://api.vocalstack.com token: null, // or: an access token / browser token, or a callable returning one actingPrincipal: null, // act for an organization (X-Acting-Principal) deviceId: null, maxRetries: 2, // 429s, and 503 unavailable on safe calls, retried after Retry-After timeout: 300.0, // seconds, for the default cURL client readyDeadline: 30.0, // how long a socket may take to report ready httpClient: null, // your own VocalStack\Http\HttpClient (WordPress, a framework, a test) );
Your own HTTP client
ext-curl is used only by the default VocalStack\Http\CurlHttpClient.
Implement VocalStack\Http\HttpClient to send requests your own way:
interface HttpClient { public function send(string $method, string $url, array $headers, string|MultipartBody|null $body): HttpResponse; }
A MultipartBody streams through chunks(), or exposes parts(): each
MultipartPart is a field (value) or a file (upload->path or
upload->stream, with filename and contentType). Never follow redirects;
answer 4xx/5xx as responses; throw VocalStack\Http\ConnectionException when
no answer came.
Realtime sessions and live recordings
Realtime needs phrity/websocket (composer require phrity/websocket:^3);
nothing else does.
use function VocalStack\paceRealtime; // Nothing stored: a file through one socket. $result = $vs->realtime->transcribeFile('meeting.mp3', ['spokenLanguage' => 'en']); echo $result->text(), "\n"; // A stored live transcription: start a recording, stream raw PCM // (f32le, 16 kHz, mono) into the publisher socket. $tx = $vs->transcriptions->createLive(['name' => 'Standup']); $grant = $vs->transcriptions->startRecording($tx['id']); $stream = $vs->recordings->publish($grant); foreach (paceRealtime($stream, $pcm) as $event) { // sends at realtime speed, then eof() if ($event['type'] === 'transcript.committed') { echo $event['segment']['speaker'], ' ', $event['segment']['text'], "\n"; } } $stream->close();
PHP has no event loop: foreach ($session as $event) (or $session->events())
blocks until the next transcript event, and $session->poll($seconds) returns
what arrives within that time, so a sender can read between sends
(paceRealtime does).
transcript.draftframes fully replace the live tail; never append them.transcript.committedsegments are immutable, gapless bysegment.seq, and carry word timings.- A dropped connection ends the socket (no resume): start a new one.
One-click Connect
use VocalStack\Connect\Pkce; $verifier = Pkce::verifier(); // keep it on the server, with the state $challenge = Pkce::challenge($verifier); // the link's code_challenge (S256) // ... the dashboard redirects back with ?code=... $token = $vs->connect->token($code, $verifier, $redirectUri); // $token['access_token'] is the key
Errors
VocalStack\VocalStackException (a RuntimeException) carries errorCode
(the contract's closed enum; also errorCode()), getMessage(), status,
retryable, details (quota numbers, code suggestions), headers,
requestId and retryAfter().
Verified against a language-agnostic conformance suite shared by every
VocalStack SDK and against a running API. For agents: SKILL.md.