4.0 KiB
client
REST + WebSocket transport for the ElevenLabs platform. ElevenLabsClient
exposes typed fetchJson / fetchBinary / fetchStream /
fetchMultipartJson / fetchMultipartBinary / openSocket methods, plus
internal retry-with-exponential-backoff that honours the Retry-After
header. Every adapter in the package (tts, stt, voices, …) composes
this single client so authentication, timeouts, retries, and event
emissions live in exactly one place.
(no submodules)
Purpose
ElevenLabs offers a sprawling REST + WebSocket API surface. Without a
shared transport, every adapter would duplicate the same error mapping,
header injection, retry policy, and timeout handling. ElevenLabsClient
solves that: it's the only piece of code in the package that calls
fetch, and the only piece that knows how to map HTTP status codes onto
typed errors. Adapters become thin domain remappers.
Features
- Five typed request shapes — JSON in/out, JSON in / binary out, JSON in / streaming out, multipart in / JSON out, multipart in / binary out.
- WebSocket support via
openSocket()for ElevenLabs's streaming-input TTS endpoints. - Built-in retry: exponential backoff (
initialDelay * 2^attempt) withRetry-Afterhonouring on 429 / 503 / 504 / network errors. - Per-request
AbortSignalplus a per-calltimeoutMsrace. - Optional
LoggerLikeandEventBusLikefor observability — emitsrequest-start,request-end,retry-attempt,rate-limit-hit. - Caller-supplied
fetchImplandwebSocketImplfor tests / edge runtimes.
Usage: Basic Example
import { ElevenLabsClient } from "@suaveplan/third-party-elevenlabs";
const client = new ElevenLabsClient({ apiKey: process.env.ELEVENLABS_KEY! });
const res = await client.fetchJson<{ voice_id: string }>({
method: "GET",
path: "/v1/voices/abc",
feature: "voices",
operation: "get",
});
console.log(res.body.voice_id, res.metadata.requestId);
Usage: Advanced Example
import { ElevenLabsClient } from "@suaveplan/third-party-elevenlabs";
const client = new ElevenLabsClient({
apiKey: process.env.ELEVENLABS_KEY!,
baseUrl: "https://eu.api.elevenlabs.io",
timeoutMs: 60_000,
maxRetries: 5,
initialRetryDelayMs: 250,
fetchImpl: customFetch,
events: myBus,
logger: myLogger,
});
const ctrl = new AbortController();
const stream = await client.fetchStream({
method: "POST",
path: "/v1/text-to-speech/voice-id/stream",
feature: "tts",
operation: "synthesize_stream",
body: { text: "hello" },
accept: "audio/mpeg",
signal: ctrl.signal,
});
for await (const chunk of stream.body) {
/* … */
}
API Reference
new ElevenLabsClient(options: ElevenLabsClientOptions)— constructor.fetchJson<TBody>(req: JsonRequest): Promise<JsonResponse<TBody>>fetchBinary(req: BinaryRequest): Promise<BinaryResponse>fetchStream(req: StreamRequest): Promise<StreamResponse>fetchMultipartJson<TBody>(req: MultipartRequest): Promise<JsonResponse<TBody>>fetchMultipartBinary(req: MultipartRequest): Promise<BinaryResponse>openSocket(req: SocketRequest): ElevenLabsSocket— wss connection withxi-api-keyinjected as a query parameter.
Throws typed ElevenLabsApiError subclasses on non-2xx responses.
Implementation Notes
xi-api-keyis sent as a request header for HTTP, but as a query parameter for WebSocket (browsers can't set headers on the WS upgrade).- Multipart values may be
Uint8Array | Blob | ReadableStream | URL; streams are buffered and URLs are fetched before upload — ElevenLabs multipart endpoints require complete bodies. - Retries fire only for
RATE_LIMIT.EXCEEDED,SERVER.TRANSIENT,NETWORK.TIMEOUT, andNETWORK.UNREACHABLEcodes. Auth and validation failures are not retried. - The class delegates error mapping, multipart materialisation, retry
decision, and backoff math to
internals.tsso the class itself stays focused on transport orchestration.