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

# Corsair Cloud

> A hosted runtime for your integrations. You have an API key, and every tool call is one HTTP request.

Corsair Cloud runs the integration layer for you. Instead of building and
hosting a server that talks to Notion, Slack, GitHub, and the rest, you get a
project with its own hosted runtime and call any plugin operation over HTTP.

Nothing to deploy, no OAuth server to write, no tokens to store. Connect your
users' accounts once, then make calls.

## Set up

You need one thing: your project's **API key**, from the dashboard Overview
page. It looks like `ck_cloud_…`. The client derives its own URL from the key,
so that's the whole configuration.

<Warning>
  The API key is a server secret, sent as a bearer token. Keep it on your
  server; never put it in browser code or a mobile app you ship. For the browser,
  see [Server vs. browser](#server-vs-browser).
</Warning>

## Your first call

Install the client for your language and make one call. Every language takes the
key and nothing else.

<CodeGroup>
  ```ts TypeScript theme={null}
  import { corsairCloud } from "corsair";

  const corsair = corsairCloud({ apiKey: process.env.CORSAIR_CLOUD_KEY! });

  const pages = await corsair.withTenant("acme").notion.api.pages.searchPage({});
  ```

  ```python Python theme={null}
  from corsair_cloud import CorsairCloud
  import os

  corsair = CorsairCloud(api_key=os.environ["CORSAIR_CLOUD_KEY"])

  pages = corsair.with_tenant("acme").call("notion", "pages.searchPage", {})
  ```

  ```go Go theme={null}
  corsair := corsaircloud.New(os.Getenv("CORSAIR_CLOUD_KEY"))

  raw, err := corsair.Tenant("acme").Call(ctx, "notion", "pages.searchPage", map[string]any{})
  ```

  ```swift Swift theme={null}
  let corsair = CorsairCloud(apiKey: ProcessInfo.processInfo.environment["CORSAIR_CLOUD_KEY"]!)

  let pages = try await corsair.tenant("acme").call("notion", "pages.searchPage", args: [:])
  ```

  ```bash curl theme={null}
  # The clients derive the URL from the key; for raw curl, build it from your key's
  # slug (the segment between ck_cloud_ and the first '.'): https://api.corsair.cloud/<slug>/api/corsair
  curl -X POST "https://api.corsair.cloud/<slug>/api/corsair/acme/notion/call/pages.searchPage" \
    -H "authorization: Bearer $CORSAIR_CLOUD_KEY" \
    -H "content-type: application/json" \
    -d '{"args":{}}'
  ```
</CodeGroup>

That runs Notion's `pages.searchPage` as your user "acme" and returns the
result. It's always one request, `POST /{tenant}/{plugin}/call/{op}` with a
`{ "args": {...} }` body, so the language clients are thin wrappers over the
same shape.

## What is a tenant?

A tenant is one of your users. `withTenant("acme")` (or `with_tenant`, `Tenant`,
`tenant`) picks which user a call acts as, so it uses that user's connected
account. One runtime serves all your tenants at once. Building for a single
user? Pick one tenant id and forget about it.

The runtime rejects an empty tenant id rather than guessing, so a call is never
silently scoped to the wrong user.

## Map your tenants

The runtime holds the tenants and which accounts each has connected. List them
to line up against your own users:

<CodeGroup>
  ```ts TypeScript theme={null}
  const tenants = await corsair.manage.tenants.list();
  // [{ id: "acme", connectedPlugins: ["notion"] }, …]
  ```

  ```python Python theme={null}
  tenants = corsair.manage.tenants()
  ```
</CodeGroup>

<Note>
  `tenants` gives you each tenant's id and connected plugins, `connectionStatus`
  gives the per-plugin auth state, and `manage.permissions.get({ id })` reads a
  permission grant (its scopes and approval status). All redact secrets: you see
  *that* a tenant connected Notion, never their token. Reading a tenant's stored
  *data* rows directly, or pointing the runtime at your own database, comes with
  bring-your-own-DB (coming soon).
</Note>

## Connect a user's account

Before a tenant can call Notion, they have to connect their Notion account.
Corsair Cloud runs the OAuth. Create a connect link and send your user to it:

<CodeGroup>
  ```ts TypeScript theme={null}
  const link = await corsair.manage.connect.createLink({
    plugin: "notion",
    tenantId: "acme",
  });
  // send the user to link.connectUrl
  ```

  ```python Python theme={null}
  link = corsair.manage.create_connect_link("notion", "acme")
  # send the user to link["connectUrl"]
  ```
</CodeGroup>

When they finish authorizing, the runtime stores their token, encrypted at rest
with a key Corsair holds. Raw provider tokens never sit in your store. Check who
is connected any time:

<CodeGroup>
  ```ts TypeScript theme={null}
  const status = await corsair.manage.connectionStatus.get({ tenantId: "acme" });
  // { notion: "connected" }
  ```

  ```python Python theme={null}
  status = corsair.manage.connection_status("acme")
  # {"notion": "connected"}
  ```
</CodeGroup>

## Server vs. browser

The runtime has one contract and two ways to reach it:

* **From a server or agent.** Use the client directly. It holds the key and
  calls the runtime, the path in every example above.
* **From a browser (React).** Browser code must never see the key. Stand up a
  route in *your own* app that injects the bearer server-side. `corsair/connect`
  builds that route from just the key:

```ts app/api/corsair/[...path]/route.ts theme={null}
import { corsairConnect } from 'corsair/connect';
import { getSession } from '@/lib/auth';

const proxy = corsairConnect({
    apiKey: process.env.CORSAIR_CLOUD_KEY!,
    // Runs before every forwarded request. The caller's own credentials
    // never reach this handler, so this is where you check who's asking.
    authorize: async (req) => Boolean(await getSession(req)),
});

export const GET = proxy;
export const POST = proxy;
```

Pair it with `<CorsairProvider baseURL="/api/corsair">` in the browser. The
provider only ever calls this same-origin route and never sees the key.

`authorize` is required: without it, `corsairConnect` throws at startup, since
anyone who can reach the route could otherwise invoke any tenant/plugin/operation
the project key allows. To intentionally run an open proxy, pass
`allowUnauthenticated: true`.

Then wrap your app in `CorsairProvider` and use `useCorsair()` to drive the
connect flow and read connection state. The key stays on your server the whole
time.

## Type hints for your calls (TypeScript)

`corsairCloud` takes no plugin list, since the plugins live on the runtime, so
by default `corsair.withTenant(t).notion.api…` is typed as `any`. For
autocomplete, run the CLI once:

```bash theme={null}
pnpm corsair cloud pull
```

It reads the exact plugins and operations your runtime has, down to the scopes
you allowed, and writes a local `corsair-env.d.ts` that TypeScript picks up, with
no plugin import to maintain. Coding agents can skip it and list operations with
`pnpm corsair cloud list`.

## Where calls go

Your call goes straight to the runtime, not through the dashboard. The dashboard
creates and configures the runtime, then steps out of the request path, so a
call is one hop out (your code to the runtime) and one hop on (the runtime to the
provider).

## Next steps

* [Python client](/clients/python)
* [Go client](/clients/go)
* [Swift client](/clients/swift)
