> ## 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

> Meeting intelligence with tl;dv - list and read recorded meetings, pull transcripts and AI notes, import a recording from a URL, and search folders.

Use **tl;dv** through Corsair: one client, typed API calls, and incoming webhooks.

Create an API key at [https://tldv.io/app/integrations/api](https://tldv.io/app/integrations/api) (you must be signed in, on a Pro or Business plan), then store it as the account's `api_key`. Every request sends it in an `x-api-key` header.

Things to know before you call:

* **A `User-Agent` is mandatory.** tl;dv blocks default library agents before the key is read: a request sent as `Python-urllib/3.11` gets a Cloudflare `403` with `error code: 1010` and no JSON body. The plugin always sends `Corsair-tldv-integration/1.0`.
* **The host is `pasta.tldv.io`,** not `api.tldv.io`; the latter answers 404 for every `/v1alpha1` path.
* **API access depends on the organizer's plan.** Seeing a meeting in the web app does not grant API access: a meeting organized on a Free plan cannot be exported, and sharing does not change that. Organization-wide automation needs Enterprise.
* **Two error shapes, split by status.** A `400` carries `{ name: 'BadRequestError', message, errors: [{ property, constraints }] }`; `401`, `403`, `404` and `500` carry `{ name, message }`. The plugin throws a `TldvAPIError`. Its `status` is the HTTP status, its `code` is tl;dv's `name` (for example `NotFoundError`) and its `detail` is the first constraint. `error.name` is always `TldvAPIError`, so compare `error.code`.
* **Timestamps are ISO 8601 strings** while `duration` and transcript offsets are numbers in seconds. `duration` can be fractional. The list endpoint itself answers `happenedAt` as a JavaScript date string (`Tue Nov 10 2026 19:06:44 GMT+0000 (Coordinated Universal Time)`); `meetings.list` converts it to ISO 8601 so it matches `meetings.get`.
* **No recording download.** `GET /meetings/{id}/download` answers a 302 to a signed URL, and the shared HTTP client follows redirects, so it would pull the whole file into memory. The meeting's `url` field is the tl;dv web page for the meeting, not the media file. To get the recording, call `GET https://pasta.tldv.io/v1alpha1/meetings/{id}/download` yourself with the same `x-api-key` and `User-Agent` headers and redirects turned off, then read the signed URL from the `Location` header. It expires after 6 hours.
* **Webhooks are not signed, so they carry a shared secret.** tl;dv publishes no signature scheme for either trigger. The plugin accepts a delivery only when it carries your shared secret in an `x-corsair-webhook-secret` header, and it rejects every delivery when no secret is configured. tl;dv can send that header itself: in Integrations > Webhooks > Configure new webhook, set Authentication type to "API Key", the header name to `x-corsair-webhook-secret` and the value to your secret. Set the same secret with `tldv({ webhookSecret })` or `keys.set_webhook_signature(secret)`. Webhooks need a Pro or Business plan, and the endpoint must be HTTPS and must not redirect.
* **What the secret proves.** The secret is the only thing that ties a delivery to tl;dv. Use a long random value, keep it out of logs and client code, and replace it in both places if it leaks.
* **Answer within 10 seconds.** tl;dv retries a delivery that gets no 2xx in that time, and a `MeetingReady` delivery with the transcript included can be several hundred KB. When the plugin rejects a delivery (wrong secret, a payload that does not parse), `processWebhook` returns `response.success === false`. Answer that with a non-2xx status.
* **Tenant routing.** Add `?tenantId=<id>` to the endpoint URL you give tl;dv, and pass `Object.fromEntries(new URL(request.url).searchParams)` to `processWebhook` as its fourth argument. Without it a delivery runs under `default`. If your handler resolves the tenant from the payload instead, a `MeetingReady` delivery is matched by the organizer's email, which you store with `keys.set_tenant_external_id(email)`. A `TranscriptReady` delivery names no organizer, so only `tenantId` can route it.

**What you get:**

* 6 typed API operations
* 2 incoming webhook event types

## Setup

**1. Install**

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

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

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

```bash bun theme={null}
bun add corsair @corsair-dev/tldv 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 { tldv } from '@corsair-dev/tldv';

export const corsair = createCorsair({
	plugins: [
		tldv(),
	],
	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}
> tldv()
> ```
>
> 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: 'tldv',
	tenantId: 'acme',
});
// redirect the user's browser to connectUrl
```

## Example API calls

**List recent meetings**

```ts theme={null}
const tenant = corsair.withTenant('acme');
await tenant.tldv.api.meetings.list({ limit: 10 });
```

**Import a recording from a URL**

```ts theme={null}
const tenant = corsair.withTenant('acme');
await tenant.tldv.api.meetings.import({ url: 'https://example.org/call-recording.mp4', name: 'Discovery call' });
```

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

## Webhooks

React as soon as a meeting is processed, with its notes and, when included, its transcript. Pass `webhookSecret` next to `webhookHooks`, or store it with `keys.set_webhook_signature(secret)`. With no secret the plugin rejects every delivery and this hook never runs.

```ts theme={null}
tldv({
    webhookHooks: {
        meetings: {
            meetingReady: {
                after: async (ctx, result) => {
                    console.log('Meeting ready:', result.data?.data.name, 'notes:', result.data?.data.notes?.structuredNotes?.length);
                }
            },
        },
    },
})
```

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/tldv/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/tldv/api.md)
>
> Every `tldv.api.*` operation with input and output types.

> [card] [Webhooks](https://docs.corsair.dev/plugins/tldv/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.