Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/phpaisdk/elevenlabs/llms.txt

Use this file to discover all available pages before exploring further.

The PHP AI SDK Live API provides a server-side WebSocket integration for ElevenLabs Scribe v2 Realtime. You stream raw audio bytes to the session and iterate over typed events as the transcript materializes in real time — receiving revisable partial updates as audio arrives, and committed final transcripts when each utterance is complete.

Installation

Install the ElevenLabs provider and the optional aisdk/transport package, which supplies a ready-made WebSocket transport:
composer require aisdk/elevenlabs
composer require aisdk/transport
aisdk/transport is optional. Any class implementing AiSdk\Live\Contracts\TransportInterface works in its place — the event normalization and protocol encoding always come from this package.

Basic Server-Side Usage

1

Open a live session

Call Live::transcribe(), chain configuration, and connect using Transport::auto(). The session is synchronous and stays open until you stop iterating or call $session->close().
use AiSdk\ElevenLabs;
use AiSdk\Live;
use AiSdk\Live\TranscriptCompleted;
use AiSdk\Live\TranscriptUpdate;
use AiSdk\Transport;

$session = Live::transcribe()
    ->model(ElevenLabs::model('scribe_v2_realtime'))
    ->language('en')
    ->audioFormat('pcm_16000')
    ->providerOptions('elevenlabs', [
        'commit_strategy' => 'manual',
        'include_timestamps' => true,
    ])
    ->connect(Transport::auto());
2

Send and commit audio

Call sendAudio() with raw PCM bytes as they become available. When you have sent a complete utterance, call commitAudio() to signal ElevenLabs to finalize the transcript for that segment.
$session->sendAudio($pcmBytes);  // Pass raw audio bytes
$session->commitAudio();         // Signal end of utterance
3

Iterate over transcript events

events() is a generator that yields typed LiveEvent objects. Handle TranscriptUpdate for partial in-progress transcripts and TranscriptCompleted for committed final transcripts.
foreach ($session->events() as $event) {
    if ($event instanceof TranscriptUpdate) {
        // Partial transcript — may be revised by subsequent updates
        echo "\r{$event->text}";
    }

    if ($event instanceof TranscriptCompleted) {
        // Committed final transcript for this utterance
        echo "\n{$event->text}\n";
    }
}

Transcript Event Types

TranscriptUpdate

A partial, revisable transcript emitted as audio is received. The text may change with subsequent TranscriptUpdate events before the utterance is committed. Corresponds to ElevenLabs partial_transcript messages.

TranscriptCompleted

A committed, final transcript for a completed utterance. No further updates will modify this text. Corresponds to ElevenLabs committed_transcript messages.

ProviderEvent

A raw ElevenLabs message that has no portable SDK equivalent. Inspect $event->providerType and $event->payload to access ElevenLabs-specific data such as committed_transcript_with_timestamps or session_started.

LiveError

A normalized error event. Inspect $event->code for the ElevenLabs error type and $event->message for a human-readable description.

Timestamps and ProviderEvent

When include_timestamps is true, ElevenLabs sends two messages for each committed utterance: the standard committed_transcript and then a committed_transcript_with_timestamps message containing per-word timing metadata. The adapter normalizes the first into a TranscriptCompleted event. The second is delivered as a ProviderEvent to preserve the word metadata without duplicating the portable completion event.
use AiSdk\Live\ProviderEvent;
use AiSdk\Live\TranscriptCompleted;

foreach ($session->events() as $event) {
    if ($event instanceof TranscriptCompleted) {
        echo $event->text . "\n";
    }

    if ($event instanceof ProviderEvent
        && $event->providerType === 'committed_transcript_with_timestamps'
    ) {
        // Access ElevenLabs word-level timing
        foreach ($event->payload['words'] ?? [] as $word) {
            printf("  %s: %.2f–%.2f\n", $word['text'], $word['start'], $word['end']);
        }
    }
}

Provider Options

Pass ElevenLabs-specific session configuration through providerOptions('elevenlabs', [...]). All recognized fields are sent as WebSocket URL query parameters.
commit_strategy
string
Controls when audio segments are committed. Use 'vad' to let ElevenLabs detect speech boundaries automatically, or 'manual' to commit utterances yourself with $session->commitAudio().
include_timestamps
boolean
When true, each committed utterance is followed by a committed_transcript_with_timestamps ProviderEvent containing per-word start and end times.
include_language_detection
boolean
When true, ElevenLabs includes detected language information in its response messages.
vad_silence_threshold_secs
float
Duration of silence (in seconds) after which the VAD considers the current utterance complete. Only applies when commit_strategy is 'vad'.
vad_threshold
float
Voice activity detection sensitivity threshold. Higher values require louder or clearer speech to trigger VAD. Only applies when commit_strategy is 'vad'.
min_speech_duration_ms
int
Minimum duration of speech (in milliseconds) required before the VAD treats audio as an utterance. Only applies when commit_strategy is 'vad'.
min_silence_duration_ms
int
Minimum silence gap (in milliseconds) between detected utterances. Only applies when commit_strategy is 'vad'.
filter_background_audio
boolean
When true, Scribe applies background noise suppression before transcribing.
keyterms
array
An array of domain-specific terms to boost recognition accuracy. Each term is sent as a separate keyterms query parameter.
'keyterms' => ['PHP AI SDK', 'ElevenLabs', 'Scribe'],
no_verbatim
boolean
When true, Scribe omits filler words and false starts from the transcript.
enable_logging
boolean
When false, ElevenLabs does not store the session in your history.

Audio Format Mapping

Use the portable .audioFormat() method on the builder. The adapter maps common portable identifiers to their ElevenLabs equivalents before connecting.
Portable valueElevenLabs formatDescription
'pcm' or 'pcm16' or 'audio/pcm'pcm_1600016-bit PCM at 16 kHz
'pcmu' or 'mulaw' or 'g711_ulaw'ulaw_8000µ-law at 8 kHz (telephony)
Any other stringPassed through as-isUse ElevenLabs format strings directly
// Portable alias
->audioFormat('pcm')          // → pcm_16000

// ElevenLabs format string directly
->audioFormat('pcm_16000')    // → pcm_16000

Error Handling

ElevenLabs Scribe Realtime can return typed error messages over the WebSocket. The adapter normalizes them all to LiveError events. Check $event->code to branch on specific conditions.
use AiSdk\Live\LiveClosed;
use AiSdk\Live\LiveError;

foreach ($session->events() as $event) {
    if ($event instanceof LiveError) {
        echo "Error [{$event->code}]: {$event->message}\n";

        // Handle specific error codes
        match ($event->code) {
            'quota_exceeded'            => handleQuotaExceeded(),
            'rate_limited'              => handleRateLimit(),
            'session_time_limit_exceeded' => handleSessionExpired(),
            default                     => logError($event),
        };
    }

    if ($event instanceof LiveClosed) {
        break; // Connection closed cleanly
    }
}
The full set of error codes the adapter normalizes to LiveError:
CodeDescription
auth_errorInvalid or missing API key
quota_exceededAccount quota exhausted
transcriber_errorInternal Scribe processing error
input_errorMalformed audio input
errorGeneric error
commit_throttledCommit rate exceeded
unaccepted_termsTerms of service not accepted
rate_limitedRequest rate too high
queue_overflowSession queue is full
resource_exhaustedServer resource limit reached
session_time_limit_exceededMaximum session duration reached
chunk_size_exceededAudio chunk too large
insufficient_audio_activityNot enough audio to produce a transcript

Client-Secret Flow for Browser Connections

For browser-based applications, you should never expose your workspace API key to client-side JavaScript. Instead, your server generates a short-lived single-use token and returns only its value to authenticated clients. The browser then connects directly to ElevenLabs using that token.
Never expose your ElevenLabs workspace API key (xi-...) to client-side code. Always use the client-secret flow for browser connections.

Server: Generate a Token

Call .clientSecret() on the builder instead of .connect(). It returns a ClientSecret object with a value string and an expiresAt Unix timestamp (15 minutes from creation).
use AiSdk\ElevenLabs;
use AiSdk\Live;

$secret = Live::transcribe()
    ->model(ElevenLabs::model('scribe_v2_realtime'))
    ->clientSecret();

// Return only the token value and expiry to the authenticated client.
// Never return your workspace API key.
return [
    'token'      => $secret->value,
    'expires_at' => $secret->expiresAt,
];

Browser: Connect with the Token

The browser receives the token and constructs a WebSocket URL directly to ElevenLabs. The xi-api-key header is never used in client-side code.
const { token } = await fetch('/api/elevenlabs/scribe-token')
    .then(response => response.json());

const query = new URLSearchParams({
    model_id:        'scribe_v2_realtime',
    audio_format:    'pcm_16000',
    commit_strategy: 'vad',
    token,
});

const socket = new WebSocket(
    `wss://api.elevenlabs.io/v1/speech-to-text/realtime?${query}`
);

socket.addEventListener('message', ({ data }) => {
    const event = JSON.parse(data);

    if (event.message_type === 'partial_transcript') {
        console.log('Partial:', event.text);
    }

    if (event.message_type === 'committed_transcript') {
        console.log('Final:', event.text);
    }
});

Complete Server-Side Example

use AiSdk\ElevenLabs;
use AiSdk\Live;
use AiSdk\Live\LiveClosed;
use AiSdk\Live\LiveError;
use AiSdk\Live\ProviderEvent;
use AiSdk\Live\TranscriptCompleted;
use AiSdk\Live\TranscriptUpdate;
use AiSdk\Transport;

$session = Live::transcribe()
    ->model(ElevenLabs::model('scribe_v2_realtime'))
    ->language('en')
    ->audioFormat('pcm_16000')
    ->providerOptions('elevenlabs', [
        'commit_strategy'          => 'manual',
        'include_timestamps'       => true,
        'include_language_detection' => true,
        'filter_background_audio'  => true,
        'keyterms'                 => ['PHP', 'AI SDK'],
        'enable_logging'           => false,
    ])
    ->connect(Transport::auto());

// Stream audio in chunks
foreach (audioChunks() as $chunk) {
    $session->sendAudio($chunk);
}

// Signal end of utterance
$session->commitAudio();

// Process events
foreach ($session->events() as $event) {
    if ($event instanceof TranscriptUpdate) {
        echo "\r{$event->text}";
    }

    if ($event instanceof TranscriptCompleted) {
        echo "\n{$event->text}\n";
    }

    if ($event instanceof ProviderEvent
        && $event->providerType === 'committed_transcript_with_timestamps'
    ) {
        foreach ($event->payload['words'] ?? [] as $word) {
            printf("  %s: %.2f–%.2f\n", $word['text'], $word['start'], $word['end']);
        }
    }

    if ($event instanceof LiveError) {
        error_log("Scribe error [{$event->code}]: {$event->message}");
        break;
    }

    if ($event instanceof LiveClosed) {
        break;
    }
}

$session->close();

Build docs developers (and LLMs) love