> ## Documentation Index
> Fetch the complete documentation index at: https://flow9.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# SDK quickstart

> Make your first authenticated Flow9 call in a few minutes with the typed @flow9/sdk — generated from the OpenAPI spec,

Make your first authenticated Flow9 call in a few minutes with the typed
[`@flow9/sdk`](/api-reference/introduction) — generated from the [OpenAPI spec](/api-reference),
so every endpoint, parameter and response is typed, with retries, pagination helpers
and webhook verification included. Node 18+, Deno and Bun; server-side only — never
ship a key to a browser you don't control.

## 1. Install

```bash theme={null}
npm install @flow9/sdk
```

## 2. Get a key

Create a key in **Settings → Developers → API keys** (or headlessly via
`POST /v1/api-keys` after [signing up](/api-reference/authentication)). Use a **Test** key (`f9_test_`)
while developing — [nothing real is sent](/api-reference/test-mode).

```bash theme={null}
export F9_KEY="f9_test_0123456789abcdef0123456789abcdef"
```

## 3. Your first calls

```ts theme={null}
import { Flow9 } from "@flow9/sdk";

const flow9 = new Flow9({
  security: { apiKeyAuth: process.env.F9_KEY! },
  // serverURL defaults to production; point it at another environment if needed.
});

// Reachability — no auth needed.
const health = await flow9.health.getHealth();
console.log(health.result.data?.status); // "ok"

// Create a lead. The Idempotency-Key makes a retry safe: the same key + same body
// replays the original response instead of creating a second lead.
const created = await flow9.leads.createLead({
  idempotencyKey: crypto.randomUUID(),
  body: { firstName: "Ada", lastName: "Lovelace", email: "ada@example.com" },
});
const lead = created.result.data!;
console.log("created lead", lead.id);

// Add a note to it, then list its activities.
await flow9.records.createRecordNote({
  object: "leads",
  id: lead.id,
  body: { content: "Imported from the quickstart." },
});
const activities = await flow9.records.listRecordActivities({ object: "leads", id: lead.id, limit: 5 });
console.log(activities.result.data?.activities);
```

Every response is the standard envelope: `result.success`, `result.data`, and
`result.meta.request_id` (quote that in support requests). Field names are camelCase
in the SDK (`firstName`) and snake\_case on the wire (`first_name`) — the SDK maps them.

## 4. Page through a list

Every list endpoint takes `limit` + `cursor` and returns `nextCursor` + `hasMore`
([conventions](/api-reference/conventions#pagination)). `@flow9/sdk/extras` turns that into a
plain `for await`:

```ts theme={null}
import { paginate } from "@flow9/sdk/extras";

for await (const record of paginate(async (cursor) => {
  const page = (await flow9.records.listRecords({ object: "leads", limit: 100, cursor })).result.data!;
  return { items: page.records, nextCursor: page.nextCursor };
})) {
  console.log(record.id, record.name);
}
```

## 5. Verify webhooks

The one thing a generator cannot produce: byte-for-byte parity with the server's
signer lives in `@flow9/sdk/extras` too.

```ts theme={null}
import { verifyWebhookSignature } from "@flow9/sdk/extras";

app.post("/webhooks/flow9", express.raw({ type: "application/json" }), async (req, res) => {
  const ok = await verifyWebhookSignature(req.body.toString("utf8"), req.headers["flow9-signature"], process.env.WEBHOOK_SECRET!);
  if (!ok) return res.status(400).end();
  // …handle JSON.parse(req.body)
  res.status(200).end();
});
```

## 6. Handle errors and limits

* **Errors** throw `Flow9Error` subclasses carrying the envelope's stable `error.code` —
  branch on the code, never the message. Full catalogue: [errors.md](/api-reference/errors).
* **Rate limits** return `429 RATE_LIMIT_EXCEEDED` with `Retry-After`. The SDK's built-in
  retry config honours it; `@flow9/sdk/extras` exposes `retryAfterMs` / `retryDelayMs`
  if you roll your own loop. Details: [rate-limits.md](/api-reference/rate-limits).
* **Retries on writes** must carry an `Idempotency-Key`; `idempotencyKey()` from extras
  mints one. Details: [idempotency.md](/api-reference/idempotency).

## Next steps

* Recipes — batch import, webhook mirror, full export.
* [Records reference](/api-reference/records) — filtering, sorting, custom objects.
* [Webhooks](/api-reference/webhooks) — react to events instead of polling.
* [Connect an AI assistant](/api-reference/mcp) — drive the same data from Claude.
* Prefer plain `fetch`? The [API conventions](/api-reference/conventions) doc shows the envelope
  and pagination contract the SDK wraps; `scripts/docs-curl-examples.sh` has runnable curl.
