server/ and runs on Nitro. Corsair fits that split exactly. The handler is one Nitro route, your pages stay on the client, and the bridge between them is a useFetch against a server route that reads Corsair’s synced tables. Nothing integration-shaped ever ships to the browser.
Install
The adapter ships in core. Each integration is its own package. Add one per service you connect.@corsair-dev/* package. The full catalog with exact ids 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.
Mount the handler
Add a catch-all underserver/routes/. Nitro passes the request straight to the adapter.
server/routes/api/corsair/[...].ts
~/server/corsair is your createCorsair({ ... }) instance. See Getting Started if you haven’t built it yet.
The
[...] catch-all matters: Corsair serves delivery, connect, and tenant paths all under /api/corsair, so the route has to swallow the whole subtree.Resolve the tenant
Every read and write is scoped to a tenant id, whatever stable id identifies the current user or org. In a Nitro route you already have the h3event, so resolve the id from your session or a header (however your auth works) and pass it to withTenant.
Read and write
Reads go through.db, your own database with no rate limits, so keep them in a Nitro server route and pull them into a page with useFetch. Writes go through .api, and Corsair upserts the response, so the next read already reflects it.
server/api/issues.get.ts
app.vue
.post handler that calls .api, e.g. await tenant.github.api.issues.create({ owner: 'acme', repo: 'app', title: '…' }). Entity fields live on .data in camelCase.
Refresh after a write
The.api write already made .db current, so the page just needs to re-read. useFetch gives you a refresh for that. Call it after the mutation resolves, or refreshNuxtData('issues') from anywhere by key.
Connect a tenant
Nuxt renders Vue, and Corsair’s typed hooks client is React-only, so use the framework-agnostic vanilla client for the connect flow. Create it once and call it from a Nitro route or a plain client script; it mints a connect link and reports status without any UI framework.1
Create the client
corsair-client.ts
createCorsairClient is a typed fetch wrapper over the management API, no React required. Use it from your frontend, a worker, or a script. Every route is typed against its response. Full surface on the Vanilla Client reference.2
Read what's connected
connections.ts
connectionStatus.get returns a map of plugin id to connected | missing_credentials | not_connected, so you can show what’s live and what still needs connecting.3
Mint a connect link
connect.ts
"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 the Nuxt app as usual, and the Nitro server route ships with it. In production,/api/corsair at your deployed origin is the delivery URL Hub calls, so nothing extra to wire up.
Next
Build a dashboard
The full read-
.db, write-.api pattern behind the example above.Connect / OAuth
The full connect flow, error codes, and retry.
Use with an agent
Give an agent the Corsair tools and let it call any endpoint.
Multi-tenancy
One flag and every user gets their own data and credentials.