Search by

VocalStack speech API SDK for PHP: one client grouped by resource, file and link transcription, realtime WebSocket streaming.

v1.0.0 2026-10-19 19:29 UTC

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.draft frames fully replace the live tail; never append them.
  • transcript.committed segments are immutable, gapless by segment.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.