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

# TypeScript SDK

> The typed @checkfu/sdk client: generated from the same OpenAPI document as these docs, with a hand-written transport core.

`@checkfu/sdk` is the TypeScript client for the Checkfu API. Its typed surface is **generated from the same OpenAPI document that publishes these docs**. The generated files are checked in, and the drift gate `pnpm test:sdk-drift` compares a stamp of (spec, generator) and a content hash of each generated file, failing when they no longer match. That gate runs at commit time whenever a change stages the spec, the generator, or a generated file; on the repository's `check:heavy` cadence; before every release; and again at publish — so a released client cannot lag the API it was generated from. It does not run on every landing, so `main` between cadence passes is the one place a freshly landed API change can briefly lead the checked-in client. Only the transport core (auth, retry, idempotency, pagination, SSE) is hand-written. Plain HTTP remains the baseline integration; the SDK is a convenience, never a second contract.

<Note>
  **Private alpha:** the package is not yet published to npm. Today it lives inside the platform repository while the public release is prepared. The import surface below is the stable one.
</Note>

<Warning>
  The generated `checkfu.beta.agents` Agent and Version surface is current. The
  Session and `tools.sync` examples below are migration-fenced until they admit
  CMA Agents, Environments, and Resources directly; do not combine those legacy
  examples with a newly created CMA Agent yet.
</Warning>

## Quick start

```ts theme={"theme":{"light":"github-light","dark":"github-dark"}}
import { Checkfu } from "@checkfu/sdk"

const checkfu = new Checkfu({
  apiKey: process.env.CHECKFU_API_KEY,
})

// Create a session and drive a turn.
const session = await checkfu.sessions.create({ agent: "agent_…", principal: "prin_…" })
await checkfu.sessions.events.send(session.id, {
  type: "user.message",
  payload: { content: "hello", authored_by: "prin_…", caused_by: { kind: "api" } },
})

// Tail the event log to settlement. The stream yields typed, discriminated
// events (same as events.list) and resumes losslessly across drops.
for await (const event of checkfu.sessions.events.stream(session.id)) {
  if (event.type === "session.status_idle") break
}
```

`baseUrl` defaults to `https://api.checkfu.com`. Omitting an option falls back to `CHECKFU_API_KEY` or `CHECKFU_BASE_URL`. The API key is bound to one Workspace, matching CMA; the client accepts no per-request Workspace selector.

The client sends your API key to whatever `baseUrl` names, so the endpoint is validated once at construction, before any request: HTTPS is required except for exact loopback HTTP (`localhost`, `127.0.0.1`, `[::1]`), and credentials, a query string, or a fragment in the URL are refused. A proxy path is preserved as given and trailing slashes are removed. A refused value throws `CheckfuError` without echoing the value, which may itself be a secret.

To render best-effort live output before settlement, opt in explicitly:

```ts theme={"theme":{"light":"github-light","dark":"github-dark"}}
for await (const event of checkfu.sessions.events.stream(session.id, {
  eventDeltas: ["agent.message", "agent.thinking"],
})) {
  if (event.type === "event_start" || event.type === "event_delta") {
    renderPreview(event)
    continue
  }
  renderCommitted(event)
}
```

The opt-in widens the generator type with `SessionEventPreview`. Preview frames are not durable and do not move the SDK's reconnect cursor. If the corresponding observation commits, its final persisted event carries the same event ID and remains authoritative. A transparent reconnect yields a `preview_reset` item before later frames so the consumer can discard every unmatched preview.

## What the client handles for you

* **Headers**: `Authorization` and `Checkfu-Version` per the [authentication contract](/reference/authentication), on every versioned call.
* **Idempotency**: mutating calls accept an idempotency key and retries replay rather than duplicate, per the [HTTP overview](/reference/overview).
* **Pagination**: list surfaces follow the opaque `next_page` cursor for you.
* **SSE resume**: `events.stream` reconnects with `Last-Event-ID`, advances its cursor only for strictly increasing durable frames, and rejects duplicate or reordered sequences. A `connectTimeout` (default: the client's request timeout) bounds header establishment; retryable handshakes (`429`/`>=500`), connect timeouts, mid-stream drops, and clean closes share one `maxReconnects` budget and honor parsed `Retry-After`. Auth, policy, and protocol failures never reconnect. Optional live previews are cursor-neutral and reset explicitly after reconnect. An unreadable retention/ZDR range raises `EventStreamCursorGapError` instead of being skipped. It implements the discipline from [Resume a stream](/guides/resume-a-stream).
* **Custom tools**: `defineTool` declares and validates one customer-executed tool, `tools.sync` updates only the AgentDefinition draft and its enforced approval ActionPolicies, and `tools.serve` implements the [ToolServing contract](/guides/serve-tools-from-your-app) over the public event log for explicitly named Sessions.
* **Session projections**: `createSessionProjections()` and the individual projectors (`MessageProjector`, `ToolCallProjector`, `InteractionProjector`, `MultiagentProjector`, `ContextLedgerProjector`) fold the one event stream into the named read-side views — messages, tool calls, ActionApprovals and Questions, the multi-agent view, and the context ledger. They are pure, replay-idempotent, process-lifetime folds with no transport or durable snapshot format of their own. The observer's `eventDelta` is retryable input, not a complete view; an external provider owns cumulative state and its cursor in one transaction. See [Project a session](/guides/project-a-session).

The bounded hand-written core also contains surfaces that OpenAPI cannot generate. The two SSE helpers are `sessions.events.stream(sessionId)` and CMA-shaped `sessions.threads.events.stream(threadId, { session_id })`; both resume with `Last-Event-ID`, and the thread helper tails that thread's assigned projection: condensed Session history for the primary or complete local history for a child. Add `event_deltas: ["agent.message"]` to the thread params to opt into previews. `sessions.exportStream` hands back bytes instead of a parsed document. `capabilities.status(id)` is a convenience over `GET /v1/support/capabilities`. `connections.waitForActive(id, { timeoutMs })` polls the public read until a `pending` Connection becomes `active` and `healthy` — there is no connection-lifecycle webhook, so this is the honest completion signal for the interactive connect flow (see [Connect a provider account](/guides/connect-a-providers-account)). Its `timeoutMs` is one overall budget covering every poll, response body, transport retry/backoff, and sleep; a caller `signal` abort rejects with a `ConnectionError` carrying the caller's reason. The schema adapter and ToolServing projection behind `defineTool` and `tools` stay dependency-free and use only generated public operations.

## Driving a session: the six sendable events

`events.send` takes one discriminated event. The six drive events are [`user.message`, `user.interrupt`, `user.custom_tool_result`, `user.tool_confirmation`, `user.question_answer`, and `user.define_outcome`](/reference/events#client-drive-events), each with a `payload` that carries required attribution (`authored_by`, `caused_by`).

The hand-written interaction helpers `submitCustomToolResult`,
`respondToToolConfirmation`, `allowTool`, and `denyTool` send the matching
event through the primary Session. Pass `sessionThreadId` for a child action;
the helper echoes it with `toolUseId` and required actor attribution. The
separate `checkfu.actionApprovals.decide(id, …)` resource method remains available
for ActionApproval queues and optimistic-concurrency workflows.

## Downloading an export as bytes

`checkfu.sessions.export(id)` gives you the parsed export document. For a log too large to hold in memory, `checkfu.sessions.exportStream(id)` resolves to a `ReadableStream<Uint8Array>` of the same `checkfu.session-export` v1 response, without buffering the complete JSON:

```ts theme={"theme":{"light":"github-light","dark":"github-dark"}}
const body = await checkfu.sessions.exportStream(sessionId)
```

You own the returned stream: read it to EOF or cancel it. The request's timeout and abort ownership stay live until you do, and cancelling propagates to the export request itself. [Export and own your logs](/guides/export-your-log) pipes it to a file.

## Buffered response limits

The SDK incrementally collects ordinary JSON and binary success responses up to 64 MiB and error responses up to 64 KiB. Overflow raises a non-retryable `ResponseBodyTooLargeError` carrying the HTTP status and `maximumBytes`; response bytes are never attached to that error.

There are exactly two compatibility exceptions for dated complete-export contracts. `checkfu.memory.export(id)` and the structured `checkfu.sessions.export(id)` method may buffer their complete aggregate without a client byte ceiling. Use `checkfu.memory.exportPage(id, query)` for bounded Memory traversal and `checkfu.sessions.exportStream(id)` for a large Session log. `/export-pages`, binary calls, different methods, similarly named paths, and every ordinary resource remain bounded.

## Naming conventions

Resources follow `create` / `retrieve` / `list` / `update` / `delete`, and **every API family has a generated group**: `sessions` (+ `.events`, `.checkpoints`, `.threads`, `.ledger`, `.mounts`, `.watches`), `agents`, `principals`, `connections`, `skills`, `memory` (+ `.documents`, `.revisions`, `.proposals`, `.changes`, `.curationRuns`, `.redactions`, `.workingRefs`), `automations` (+ `.firings`, `.deliveries`, `.reports`, `.sources`), `actionApprovals`, `workspaces`, `projects`, `files`, `budgets`, `usage`, `webhooks`, `audit`, `dreams`, `runs`, `skillProposals`, `instructionProposals`, and the rest of the catalog. If it's in the [API reference](/reference/overview), it's on the client.

Two shape rules that trip up newcomers from other agent platforms:

* **`sessions.create` requires a `principal`**: the identity the session acts for, created with `checkfu.principals.create(...)` ([Get access](/reference/access)). The harness, model, and sandbox come from the Agent: the create shape technically lists those fields, but sending any value is [rejected, never ignored](/concepts/installations#installed-session-admission-derives-everything). They are derived, not overridable.
* **Attribution is required on every drive event**: `authored_by` names the Principal speaking, and each author is [authorization-checked per event](/concepts/conversation-bindings#shared-threads-authorize-each-author).

## Next steps

<CardGroup cols={2}>
  <Card title="Quickstart" icon="rocket" href="/getting-started/quickstart">
    The same first-session flow over plain HTTP.
  </Card>

  <Card title="Events reference" icon="list-timeline" href="/reference/events">
    Every event the stream can yield, with payload shapes.
  </Card>
</CardGroup>
