Skip to main content
Call setupCorsair({ tenantId }) on signup — no deploy per tenant. Idempotent.

corsair.ts

corsair.ts
import { createCorsair } from 'corsair';
import { github, slack, linear } from 'corsair';

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

On signup

onboarding.ts
import { setupCorsair } from 'corsair';
import { corsair } from '@/server/corsair';

export async function onUserCreated(userId: string) {
    await setupCorsair(corsair, { tenantId: userId });
}
Use any stable ID — user ID, org ID, workspace slug.

Connect credentials

API key:
const tenant = corsair.withTenant(userId);
await tenant.linear.keys.set_api_key(apiKey);
// account row must exist — setupCorsair creates it
OAuth:
import { processOAuthCallback } from 'corsair/oauth';

await processOAuthCallback(corsair, { code, state, redirectUri });
// creates account row for that plugin if missing
Full routes: OAuth process. Runtime:
const tenant = corsair.withTenant(userId);
await tenant.github.api.repositories.list({ type: 'owner' });
Webhooks: ?tenantId=user_abc123 on the URL.

What setupCorsair creates

await setupCorsair(corsair, { tenantId: 'user_abc123' });
corsair_accounts per auth-type pluginNew plugins (code change)
Account DEKsOAuth tokens
Integration rows if missingIntegration creds (set on corsair.keys)

New plugin → redeploy

Plugins come from createCorsair({ plugins: [...] }). Adding one requires a code change and deploy. After deploy:
# integration row (+ default account on single-tenant)
pnpm corsair setup

# set OAuth app creds if needed
pnpm corsair setup --gmail client_id=... client_secret=...
Multi-tenant — provision existing tenants:
for (const tenantId of await listActiveTenantIds()) {
    await setupCorsair(corsair, { tenantId });
}
Each tenant still authorizes the new plugin separately — rows are not copied.
Removing a plugin from code does not delete DB rows.

Lazy provisioning

OAuth-only path — skip setupCorsair({ tenantId }) on signup:
# once: integration rows + OAuth app creds
pnpm corsair setup --gmail client_id=... client_secret=...
// per user, per plugin — creates account row
await processOAuthCallback(corsair, { code, state, redirectUri });
// or: pnpm corsair auth --plugin=gmail --tenant=userId
API keys require setupCorsair({ tenantId }) first — keys.set_*() throws without an account row.

CLI vs code

setupCorsair in backendProduction
pnpm corsair setup --tenant=<id>Local / ops
pnpm corsair uiDev exploration
Basics: Setup.