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

# Vanilla Client

> createCorsairClient is a typed fetch wrapper for the management API. No React, no framework — types only.

`createCorsairClient({ baseURL })` returns a typed client that mirrors every route on the [handler](/management/handler). Use it from a Node script, a CLI, a worker, or a non-React frontend.

```ts client.ts theme={null}
import { createCorsairClient } from "corsair";

const client = createCorsairClient({ baseURL: "/api/corsair" });
```

For React, prefer [`createCorsairReactClient`](/adapters/react) — it wraps this one and adds hooks.

## Reading

```ts reads.ts theme={null}
await client.tenants.list();                      // GET /tenants
await client.tenants.get("acme");                 // GET /tenants/acme

await client.plugins.list();                      // GET /plugins
await client.plugins.get("github");               // GET /plugins/github

await client.connectionStatus.get({               // GET /connection-status
  tenantId: "acme",
});

await client.permissions.get({ id: "perm_123" });   // GET /permissions/perm_123
await client.permissions.get({ token: "tok_abc" }); // POST /permissions/lookup-by-token

await client.ok();                                // GET /ok
```

Every method is typed against the route's response. Hovering `client.tenants.list()` in your editor shows `Promise<Tenant[]>`, and so on.

`connectionStatus.get` returns a `Record<string, 'connected' | 'missing_credentials' | 'not_connected'>` keyed by plugin id:

```ts theme={null}
const status = await client.connectionStatus.get({ tenantId: "acme" });
// { github: 'connected', slack: 'not_connected', notion: 'missing_credentials' }
```

## Writing

```ts writes.ts theme={null}
await client.tenants.create({ id: "acme" });      // POST /tenants
```

Connect/OAuth methods are documented on the [Connect page](/management/connect).

## Options

```ts theme={null}
createCorsairClient({
  baseURL: "/api/corsair",     // required
  fetch: customFetch,          // optional — override globalThis.fetch
});
```

`baseURL` is the origin + base path the handler is mounted at, e.g. `https://app.example.com/api/corsair`. A trailing slash is tolerated.

`fetch` is optional — see [Custom fetch](#custom-fetch) below.

Need auth headers, custom retry, or interceptors? Pass a wrapped `fetch`:

```ts theme={null}
const client = createCorsairClient({
  baseURL: "/api/corsair",
  fetch: (input, init) =>
    globalThis.fetch(input, {
      ...init,
      headers: { ...init?.headers, Authorization: `Bearer ${token()}` },
    }),
});
```

## Error handling

Failed requests throw `CorsairClientError`:

```ts theme={null}
import { CorsairClientError } from "corsair";

try {
  await client.tenants.get("does-not-exist");
} catch (err) {
  if (err instanceof CorsairClientError) {
    err.status;   // number — HTTP status
    err.code;     // string — e.g. "not_found"
    err.message;  // string — human message from the server
    err.extra;    // Record<string, unknown> — any additional fields the server returned
  }
}
```

Network failures (DNS, abort, no response) throw a plain `Error` — `CorsairClientError` is only used when the server responded with a non-2xx body.

## Custom fetch

When you call `createCorsairClient`, the client picks its `fetch` function once — either the one you pass in, or `globalThis.fetch` at that moment. Every later call (`client.tenants.list()`, etc.) uses that same function; it does not re-read `globalThis.fetch` on each request.

In a normal browser or Node 18+ app, this makes no practical difference. `fetch` is already available when you create the client, and it stays the same.

It only matters in two cases:

* **Tests** — your test runner or jsdom may install or replace `fetch` after your client module is imported. Pass an explicit `fetch` so the client uses the right one.
* **Custom behavior** — auth headers, retries, or routing requests to an in-process handler instead of over HTTP.

```ts theme={null}
// Test: wire the client directly to the handler, no TCP socket
const handler = managementHandler(corsair);
const client = createCorsairClient({
  baseURL: "http://test.local/api/corsair",
  fetch: (input, init) => handler(new Request(String(input), init)),
});
```

If you create the client at module scope and later replace `globalThis.fetch`, the client will not pick up the change. Create the client after your environment is ready, or pass `fetch` explicitly.
