Files
2026-05-27 23:47:53 +01:00

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) with Retry-After honouring on 429 / 503 / 504 / network errors.
  • Per-request AbortSignal plus a per-call timeoutMs race.
  • Optional LoggerLike and EventBusLike for observability — emits request-start, request-end, retry-attempt, rate-limit-hit.
  • Caller-supplied fetchImpl and webSocketImpl for 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 with xi-api-key injected as a query parameter.

Throws typed ElevenLabsApiError subclasses on non-2xx responses.

Implementation Notes

  • xi-api-key is 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, and NETWORK.UNREACHABLE codes. Auth and validation failures are not retried.
  • The class delegates error mapping, multipart materialisation, retry decision, and backoff math to internals.ts so the class itself stays focused on transport orchestration.