Skip to main content
createCorsairClient({ baseURL }) returns a typed client that mirrors every route on the handler. Use it from a Node script, a CLI, a worker, or a non-React frontend.
client.ts
import { createCorsairClient } from "corsair";

const client = createCorsairClient({ baseURL: "/api/corsair" });
For React, prefer createCorsairReactClient — it wraps this one and adds hooks.

Reading

reads.ts
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:
const status = await client.connectionStatus.get({ tenantId: "acme" });
// { github: 'connected', slack: 'not_connected', notion: 'missing_credentials' }

Writing

writes.ts
await client.tenants.create({ id: "acme" });      // POST /tenants
Connect/OAuth methods are documented on the Connect page.

Options

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 below. Need auth headers, custom retry, or interceptors? Pass a wrapped fetch:
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:
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 ErrorCorsairClientError 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.
// 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.