app/api is the whole server surface, Server Components read data at render time, and Server Actions handle the writes. Nothing runs on the client that doesn’t have to.
Install
corsair; add one @corsair-dev/* package per service you connect. The full list is in Plugins.
Create the instance
Build the instance once. The handler and your read/write code both import it.corsair.ts
.db / .api call on this page import this corsair. multiTenancy: true is what makes corsair.withTenant(id) available, and a tenant.github.* call resolves to the github() entry here. Add a service by installing its @corsair-dev/* package and dropping it into plugins. Database choices and the Hub keys are in Quick Start.
Project layout
Mount the handler
The management API, OAuth callbacks, and Hub’s connect delivery all arrive on the same path, so a single catch-all route handler serves them. Mount it once and you never edit it again.app/api/corsair/[[...path]]/route.ts
@/server/corsair is your createCorsair({ ... }) instance. The optional-catch-all segment [[...path]] matches /api/corsair and everything under it.
Resolve the tenant
withTenant takes any stable string that identifies the current user or org. You produce it from the auth you already run; Corsair never supplies one. Read it at the top of the component and scope from there.
Read and write
A Server Component reads from.db with no network, so the page renders as fast as any database query. Because it’s already on the server, you call the tenant directly instead of round-tripping through the route.
app/issues/page.tsx
.api:
app/issues/actions.ts
Refresh after a write
The.api call above upserts the response into your database, so .db is already current. revalidatePath('/issues') just re-runs the Server Component, and the closed issue is gone from the list, with no cache to invalidate by hand.
Connect a tenant
The read and write paths above assume a tenant has already connected GitHub. That flow is client-side, so create the React hook client once in a"use client" module and call the hooks from any component.
1
Create the client
app/corsair-client.ts
useTenants() knows it returns tenants. client is the escape hatch to the vanilla client for imperative calls. The full hook list is on the React Hooks reference.2
Read what's connected
connections.tsx
useConnectionStatus returns a map of plugin id to connected | missing_credentials | not_connected, so your UI knows what’s live and what still needs a connect flow.3
Mint a connect link
connect-github.tsx
"github" for any plugin you configured.Go green
/api/corsair registers your delivery URL with Hub and turns the App sync dot in the dashboard header green.
Deploy
Deploy to Vercel as you would any App Router app. The mounted/api/corsair route becomes the delivery URL Hub calls in production, with no separate service to stand up.
Next
Build a dashboard
The full
.db read / .api write pattern, with refresh and webhooks.React hooks
Every hook the client factory returns, with types and examples.
Connect / OAuth
The full connect flow, error codes, and retry.
Multi-tenancy
One flag and every user gets their own data and credentials.