# `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 ```ts 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 ```ts 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(req: JsonRequest): Promise>` - `fetchBinary(req: BinaryRequest): Promise` - `fetchStream(req: StreamRequest): Promise` - `fetchMultipartJson(req: MultipartRequest): Promise>` - `fetchMultipartBinary(req: MultipartRequest): Promise` - `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.