> ## Documentation Index
> Fetch the complete documentation index at: https://docs.corsair.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Overview

> AI audio platform; generate speech, dialogue, sound effects and music, transcribe and align audio, change or isolate voices, dub video, manage voices and history, and run conversational agents.

Use **ElevenLabs** through Corsair: one client, typed API calls, and incoming webhooks.

Audio comes back as base64 in `contentBase64`, with `contentType`, `byteLength` and what ElevenLabs reports in its headers: `requestId`, `traceId`, `characterCost` (credits charged) and `historyItemId` (`songId` on music). Base64 is expensive in an agent's context, so speech operations (`textToSpeech`, `textToDialogue`, `voiceChanger`) take `includeContent: false` to leave it out, and leave it out on their own above `maxBytes` (default 700000 bytes, about 43 s of 128 kbps mp3). `contentOmitted` then says why and `note` says how to get it: `history.getAudio({ historyItemId })`. The history item can take a few seconds to appear after the audio returns.

Sound effects, music, stems and isolation have no history item, so ElevenLabs keeps no copy. Their output always comes back in the result, up to 50 MB.

Calls here wait for the audio. A script on Corsair's cloud has a 25 s wall and can return at most 1,000,000 bytes, and for what we timed, size is the first limit. Single runs on 2026-11-08: a 10 s sound effect took 2.1 s, 530 characters of speech 1.1 s with eleven\_flash\_v2\_5 and 1.7 s with eleven\_multilingual\_v2, and 15, 30 and 60 s of music took 3.9, 4.6 and 6.9 s. Music's default format is about 24 KB/s, so its base64 passes the cap at about 31 s of audio: the 30 s result was 960,972 bytes, the 60 s result 1,920,973. To stay under the cap, ask for a lower-bitrate `outputFormat` (`mp3_22050_32` gave 4.05 KB/s in one 6 s run, so about 3 minutes fits), or pass `contentBase64` to the next operation in the same script and return only the metadata. We did not time long transcription, isolation, voice changer or stems. A run the wall cuts off may still finish and bill. `dubbing.create` is the one long operation with a job: follow it with `dubbing.get`, or let `get_job` do it. Transcripts of long files can use `speechToText.convert({ webhook: true })`.

ElevenLabs has no idempotency keys, so a repeated generation bills again. By default the plugin retries a generation only when ElevenLabs refused it before running it: a 429 with `rate_limit_exceeded`, `concurrent_limit_exceeded`, `system_busy` or the older `too_many_concurrent_requests`, at most 3 times, waiting as `Retry-After` says. If `Retry-After` is over 30 s it doesn't wait and you get the 429. It never retries a generation on a 5xx, a timeout, a network failure or any other 429; those errors say the run may have happened. Reads retry any 429 under the same rules, and a 5xx at most 2 times. A custom handler in `errorHandlers` can change all of this, including retrying a generation after a 5xx, which can bill twice.

Not covered, on purpose: Studio and projects, professional voice training, instant voice cloning from files (consent and verification concerns), websockets, realtime and Speech Engine, streaming variants (an operation returns one value), phone numbers and outbound calls, workspace admin, SMB, and Flows.

**What you get:**

* 50 typed API operations
* 8 incoming webhook event types

## Setup

**1. Install**

```bash npm theme={null}
npm install corsair @corsair-dev/elevenlabs better-sqlite3
npm install --save-dev @types/better-sqlite3
```

```bash yarn theme={null}
yarn add corsair @corsair-dev/elevenlabs better-sqlite3
yarn add --dev @types/better-sqlite3
```

```bash pnpm theme={null}
pnpm install corsair @corsair-dev/elevenlabs better-sqlite3
pnpm install --save-dev @types/better-sqlite3
```

```bash bun theme={null}
bun add corsair @corsair-dev/elevenlabs better-sqlite3
bun add --dev @types/better-sqlite3
```

**2. Add the plugin**

```ts corsair.ts theme={null}
import Database from 'better-sqlite3';
import { createCorsair } from 'corsair';
import { elevenlabs } from '@corsair-dev/elevenlabs';

export const corsair = createCorsair({
	plugins: [
		elevenlabs(),
	],
	database: new Database('corsair.db'),
	kek: process.env.CORSAIR_KEK!,
	multiTenancy: true,
	hub: {
		projectApiKey: process.env.CORSAIR_API_KEY!,
		signingSecret: process.env.CORSAIR_SIGNING_SECRET!,
	},
});
```

With `multiTenancy: true`, scope calls with `corsair.withTenant(id)`. See [Quick start](https://docs.corsair.dev/quick-start.md) for KEK + Hub keys, and [Multi-tenancy](https://docs.corsair.dev/concepts/multi-tenancy.md) for account isolation.

**3. Choose authentication**

> [tab] API Key (Recommended)
>
> No setup required yet. When you make your first request as a tenant, Corsair prompts for the API key.
>
> ```ts theme={null}
> elevenlabs()
> ```
>
> More: [API Key](https://docs.corsair.dev/concepts/api-key)

**4. Connect a tenant**

Mint a connect link and send the tenant to it. Hub hosts the page and delivers the result to your app — see [Connect / OAuth](https://docs.corsair.dev/management/connect).

```ts theme={null}
const { connectUrl } = await corsair.manage.connect.createLink({
	plugin: 'elevenlabs',
	tenantId: 'acme',
});
// redirect the user's browser to connectUrl
```

## Example API calls

**Find a voice**

```ts theme={null}
const tenant = corsair.withTenant('acme');
await tenant.elevenlabs.api.voices.list({ search: 'narrator', pageSize: 5 });
```

**Speak a sentence**

```ts theme={null}
const tenant = corsair.withTenant('acme');
await tenant.elevenlabs.api.textToSpeech.convert({
	voiceId: 'FGY2WhTYpPnrIDTdsKH5',
	text: 'Your order has shipped.',
	modelId: 'eleven_flash_v2_5',
	outputFormat: 'mp3_22050_32',
});
```

See the full list on the [API](https://docs.corsair.dev/plugins/elevenlabs/api.md) page.

## Webhooks

Create a webhook in ElevenLabs under Developers, Webhooks, with HMAC auth, and save the secret it shows you as the account's `webhook_signature` (or pass `webhookSecret`); deliveries are refused until it is set. Tick the events you want, and select the webhook in the agent's settings for post-call events. Hand `processWebhook` the raw body text (`await request.text()`), not parsed JSON, because ElevenLabs signs the exact bytes it sent. Deliveries name no account, so with several tenants put `?tenantId=<id>` on the webhook URL and pass `Object.fromEntries(new URL(request.url).searchParams)` as `processWebhook`'s fourth argument. Passed as a `URLSearchParams`, the query falls back to the `default` tenant. The signature covers the body but not the URL, so `?tenantId=` only picks which tenant's secret checks a delivery. When tenants share a secret, by storing the same one or through the `webhookSecret` option, anyone holding a signed delivery under 30 minutes old can resend it with another `tenantId`, and that tenant's hooks run on it. Give each tenant its own webhook secret, or check in an `after` hook that the event's ids belong to that tenant before acting on it. By then the plugin has logged the event under that tenant. A `before` hook runs ahead of the signature check, so it can receive an unsigned or forged delivery. Keep side effects in `after`. A delivery with a bad signature or a payload that fails validation comes back from `processWebhook` as `response.success: false`, so answer non-2xx for it. The same event can arrive more than once, from ElevenLabs' retries or from someone resending a signed delivery within 30 minutes, so make hooks safe to run twice, for example by recording the `conversation_id` or `request_id` each one acted on.

```ts theme={null}
elevenlabs({
    webhookHooks: {
        speechToText: {
            transcriptionCompleted: {
                after: async (ctx, result) => {
                    if (result.data?.type === 'speech_to_text_transcription') console.log(result.data.data?.request_id, result.data.data?.transcription?.text);
                }
            },
        },
    },
})
```

Mount your framework handler once (see [Frameworks](https://docs.corsair.dev/frameworks/next.md)), then point the provider at that URL. Full event list: [Webhooks](https://docs.corsair.dev/plugins/elevenlabs/webhooks.md). Concepts: [Webhooks](https://docs.corsair.dev/concepts/webhooks.md), [Hooks](https://docs.corsair.dev/concepts/hooks.md).

## What's next

> [card] [API reference](https://docs.corsair.dev/plugins/elevenlabs/api.md)
>
> Every `elevenlabs.api.*` operation with input and output types.

> [card] [Webhooks](https://docs.corsair.dev/plugins/elevenlabs/webhooks.md)
>
> Event paths, payloads, and `webhookHooks` examples.

> [card] [Connect / OAuth](https://docs.corsair.dev/management/connect)
>
> createLink, Hub delivery, and tenant connect flows.

> [card] [Use with agents](https://docs.corsair.dev/mcp-adapters/mcp-adapters)
>
> Expose this plugin's operations as MCP tools.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.