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

# Provisioning

> Initialize integrations, accounts, and tenants with setupCorsair at runtime or the corsair CLI in ops.

Provisioning creates the database rows, DEKs, and credential slots Corsair needs before a tenant can connect. The same logic runs two ways: `setupCorsair` from your backend (on signup, in production) and `pnpm corsair setup` from the CLI (local and ops). Both are idempotent — they skip rows that already exist.

## Data model

```
corsair_integrations   ← one row per plugin in createCorsair({ plugins })
corsair_accounts       ← one row per (tenant, plugin with an authType)
```

| Layer       | Table                  | Scope          | Holds                                                         |
| ----------- | ---------------------- | -------------- | ------------------------------------------------------------- |
| Integration | `corsair_integrations` | Shared         | OAuth app creds: `client_id`, `client_secret`, `redirect_url` |
| Account     | `corsair_accounts`     | Per tenant     | API keys, OAuth tokens, refresh tokens                        |
| Tenant      | *(no table)*           | Your ID string | Materializes once account rows exist                          |

Account rows are created only for plugins with an `authType` (`api_key`, `oauth_2`, `bot_token`). A tenant is any stable ID you choose — user id, org id, workspace slug.

## setupCorsair

Call it from your backend, most often on signup — no deploy per tenant.

```ts onboarding.ts theme={null}
import { setupCorsair } from 'corsair';
import { corsair } from '@/server/corsair';

export async function onUserCreated(userId: string) {
    await setupCorsair(corsair, { tenantId: userId });
}
```

The same call covers single-tenant, credentials, and backfill:

```ts theme={null}
// Single-tenant → provisions "default"
await setupCorsair(corsair);

// Multi-tenant → provision a tenant, seed a credential, backfill data
await setupCorsair(corsair, {
    tenantId: 'workspace_123',
    credentials: { linear: { api_key: process.env.LINEAR_KEY! } },
    backfill: true,
});
```

It returns a log string and skips existing rows.

### What it creates

| Creates                                 | Does not touch                           |
| --------------------------------------- | ---------------------------------------- |
| `corsair_accounts` per auth-type plugin | New plugins (needs a code change)        |
| Account DEKs                            | OAuth tokens (arrive on connect)         |
| `corsair_integrations` rows if missing  | Integration creds (set those explicitly) |

## CLI

Same provisioning from the terminal — for local setup and ops:

```bash theme={null}
# Single-tenant
pnpm corsair setup
pnpm corsair setup --slack api_key=xoxb-... --linear api_key=lin_api_...

# Multi-tenant — account rows + account credentials
pnpm corsair setup --tenant=workspace_123 --linear api_key=lin_api_...

# Integration-level OAuth app creds — no --tenant, even on multi-tenant
pnpm corsair setup --gmail client_id=... client_secret=...
```

| Flag                     | Effect                                                                   |
| ------------------------ | ------------------------------------------------------------------------ |
| `--tenant <id>`          | Account rows + account credentials                                       |
| `--<plugin> field=value` | Inline credentials                                                       |
| `--backfill`             | Seed data (`setup/backfill.config.ts`); needs `--tenant` on multi-tenant |

## Credentials

**Integration-level** — shared OAuth app creds, one per plugin:

```ts theme={null}
await corsair.keys.gmail.set_client_id('...');
await corsair.keys.gmail.set_client_secret('...');
```

**Account-level** — per tenant, for `api_key` / `bot_token` fields and OAuth tokens:

```ts theme={null}
// multi-tenant
await corsair.withTenant('user_abc').linear.keys.set_api_key('lin_api_...');

// single-tenant
await corsair.linear.keys.set_api_key('lin_api_...');
```

`keys.set_*()` needs an account row first — run `setupCorsair({ tenantId })` before setting API keys.

<Warning>
  Multi-tenant: passing integration fields together with a `tenantId` in `setupCorsair({ credentials })` throws. Set integration creds without a `tenantId`.
</Warning>

## Multi-tenant

```ts corsair.ts theme={null}
export const corsair = createCorsair({
    multiTenancy: true,
    plugins: [github(), linear()],
    database: db,
    kek: process.env.CORSAIR_KEK!,
});
```

| Task                                  | `--tenant` / `tenantId` |
| ------------------------------------- | ----------------------- |
| Integration rows + OAuth app creds    | Omit                    |
| Account rows, account creds, backfill | Required                |

Every runtime call goes through `corsair.withTenant(id)`. See [Multi-tenancy](/concepts/multi-tenancy).

## OAuth

Provisioning does not run OAuth — it prepares the rows. Tokens arrive when a user connects. After integration creds are set:

```bash theme={null}
pnpm corsair auth --plugin=gmail --tenant=workspace_123
```

Or in app code, [`processOAuthCallback`](/concepts/oauth-process) stores the tokens and creates the account row lazily if missing. This is the OAuth-only path — you can skip `setupCorsair({ tenantId })` on signup and let the first connect provision the row. API keys still need `setupCorsair` first.

## Adding a plugin later

Plugins come from `createCorsair({ plugins: [...] })`, so adding one is a code change and deploy. After deploying:

```bash theme={null}
pnpm corsair setup                                    # integration row (+ default account, single-tenant)
pnpm corsair setup --gmail client_id=... client_secret=...
```

Multi-tenant — provision the new plugin for existing tenants (each still authorizes it separately; rows are not copied):

```ts theme={null}
for (const tenantId of await listActiveTenantIds()) {
    await setupCorsair(corsair, { tenantId });
}
```

<Warning>
  Removing a plugin from code does not delete its database rows.
</Warning>

<Note>
  `corsair.manage.tenants.create()` records a tenant but does **not** create account rows — use `setupCorsair` or the CLI to provision.
</Note>
