> Documentation index: https://docs.beetl.io/llms.txt

# SDK reference

> The public API of @beetlio/connect 0.6: exports, the integration definition, the sync context, auth helpers and CLI commands.

Source: https://docs.beetl.io/connect/sdk-reference/
Last updated: 2026-11-10

`@beetlio/connect` is the TypeScript SDK you write Connect packages with. The SDK is open source
under the Apache-2.0 license: the full source and type definitions are on
[GitHub](https://github.com/beetlio/connect), and its user guide is at
[beetlio.github.io/connect](https://beetlio.github.io/connect/). This page covers version 0.6,
which needs Node.js 24.2 or newer.

```sh
npm install --save-dev @beetlio/connect
```

## Entry points

| Import                     | Use                                                                                                  |
| -------------------------- | ---------------------------------------------------------------------------------------------------- |
| `@beetlio/connect`         | Everything you need to write a package: `defineIntegration`, `z`, `auth`, the helpers and the types. |
| `@beetlio/connect/builder` | `packIntegration`, `buildIntegration` and `createIntegrationManifest`, for your own build tooling.   |
| `@beetlio/connect/host`    | For programs that execute integrations. You do not need it to write a package for Beetl.             |

## Exports

| Export                          | Kind            | Purpose                                                                         |
| ------------------------------- | --------------- | ------------------------------------------------------------------------------- |
| `defineIntegration(definition)` | Function        | Defines the integration. Make it the default export of `integration.ts`.        |
| `z`                             | Namespace       | Zod 4, re-exported. Use it for every schema so versions match.                  |
| `auth`                          | Object          | Authentication helpers. See [Auth helpers](#auth-helpers).                      |
| `secret(schema)`                | Function        | Marks a string credential as secret. Its value is masked in prompts and forms.  |
| `batchRecords(source)`          | Async generator | Groups a record-only stream into batches. Use with `yield*`.                    |
| `storageRecord(schema)`         | Function        | Turns a provider object schema into a record schema with fixed columns.         |
| `HttpError`                     | Class           | Thrown by `ctx.json` for HTTP failures. Has `status`, `detail` and `requestId`. |

Types such as `SyncContext`, `Batch` and `RetryPolicy` are exported too.

The SDK can also define `destinations`, which write records to an API. Beetl runs a package's
syncs, and a package installed in Beetl must declare at least one sync.

## The integration definition

```ts title="integration.ts"
import { auth, defineIntegration, secret, z } from "@beetlio/connect";

const Order = z.object({ id: z.string(), total: z.number(), status: z.string() });
const Page = z.object({ orders: z.array(Order), next: z.string().nullable() });

export default defineIntegration({
  key: "acme",
  displayName: "Acme",
  connection: {
    origin: { type: "input", input: "baseUrl" },
    inputs: z.strictObject({ baseUrl: z.url().meta({ title: "API URL" }) }),
    auth: auth.custom({
      credentials: z.strictObject({ apiKey: secret(z.string().min(1)) }),
      headers: { "x-api-key": { credential: "apiKey" } },
    }),
    async verify(ctx) {
      await ctx.json("/me", z.object({ id: z.string() }));
    },
  },
  syncs: (sync) => ({
    orders: sync({
      records: Order,
      inputs: z.strictObject({ status: z.enum(["open", "closed"]).default("open") }),
      async *run(ctx) {
        for await (const page of ctx.paginate({
          request: {
            path: "/orders",
            query: { status: ctx.config.sync.status },
          },
          schema: Page,
          next: ({ data, request }) =>
            data.next === null
              ? undefined
              : { ...request, query: { ...request.query, cursor: data.next } },
        })) {
          yield { records: page.data.orders };
        }
      },
    }),
  }),
});
```

This example reads every order with the chosen status on each run. Its pagination cursor is used
only within that run, so it suits the **Replace** write mode, which the person who adds the sync
chooses. For incremental reads with a checkpoint that carries across runs, see
[Authoring a sync](https://docs.beetl.io/connect/authoring-a-source#checkpoints).

### `defineIntegration`

| Field         | Type                                       | Notes                                                                                          |
| ------------- | ------------------------------------------ | ---------------------------------------------------------------------------------------------- |
| `key`         | `string`                                   | Identifies the integration.                                                                    |
| `displayName` | `string`                                   | Shown in the app.                                                                              |
| `description` | `string`, optional                         | Shown in the app.                                                                              |
| `icon`        | `"icon.png"` or `"icon.webp"`, optional    | A file in the package. List it in `package.json` `files`.                                      |
| `connection`  | `ConnectionDefinition`                     | See below.                                                                                     |
| `syncs`       | `(sync) => Record<string, SyncDefinition>` | Build each sync with the `sync` function passed in, so types are inferred. Keys are sync keys. |

### `connection`

| Field         | Type                                                  | Notes                                                                                                                                                                                                                                                   |
| ------------- | ----------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `origin`      | `ProviderOriginDefinition`                            | A fixed URL, `{ type: "input", input }` (a connection setting), `{ type: "environment", input, values }` (a setting mapped to fixed URLs) or `{ type: "oauth", oauthTokenField }` (an OAuth token-response field). Authenticated origins require HTTPS. |
| `auth`        | `AuthDefinition`, optional                            | Built with an `auth` helper. Omit it for public APIs.                                                                                                                                                                                                   |
| `inputs`      | `z.ZodObject`, optional                               | Connection settings, shown as a form. Use `z.strictObject`.                                                                                                                                                                                             |
| `retry`       | `RetryPolicy` or `false`, optional                    | `maxAttempts`, `statuses`, `methods`, `initialDelayMs`, `maxDelayMs`. The default retries status 408, 429, 500, 502, 503 and 504 on `GET`, `HEAD` and `OPTIONS`, up to 3 attempts. `false` disables retries.                                            |
| `verify(ctx)` | `(ctx: ConnectionContext) => Promise<void>`, optional | Make a small authenticated request and throw if access is invalid. `ctx.config` holds the connection settings.                                                                                                                                          |

Input schemas become forms, so they cannot use transforms or custom refinements. Set labels and
help text with `.meta({ title, description })`.

### `sync(definition)`

| Field         | Type                    | Notes                                                                                                                                                      |
| ------------- | ----------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `records`     | `z.ZodObject`           | The schema of each output row. Its output must be representable as JSON Schema.                                                                            |
| `inputs`      | `z.ZodObject`, optional | Sync settings, shown when a user adds the sync.                                                                                                            |
| `checkpoint`  | Zod schema, optional    | The shape of the position you resume from. Required before you yield a checkpoint. A sync that declares one resumes from it when its write mode is Append. |
| `displayName` | `string`, optional      | Shown in the app.                                                                                                                                          |
| `run(ctx)`    | Async generator         | Yields batches. Returning ends the run successfully.                                                                                                       |

### Batches

Each value `run` yields is one batch. Beetl accepts a batch's records and checkpoint together
before the generator continues; the dataset changes only when the run succeeds.

A sync does not choose a write mode, declare a primary key or report deletions. The person who adds
the sync chooses Append or Replace. See [Write modes](https://docs.beetl.io/connect/write-modes).

| Field        | Type                      | Notes                                                                                           |
| ------------ | ------------------------- | ----------------------------------------------------------------------------------------------- |
| `records`    | Array                     | Rows, each parsed with the `records` schema.                                                    |
| `checkpoint` | Checkpoint type, optional | The position to resume after this batch. Omitting it keeps the previous one; `null` is a value. |

A batch holds at most 10,000 records. For the per-run limits, see
[Write modes](https://docs.beetl.io/connect/write-modes#per-run-limits).

## Sync context

`run(ctx)` receives a `SyncContext`.

| Member                          | Type                                | Notes                                                                                                                                                                     |
| ------------------------------- | ----------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `ctx.config.connection`         | Object                              | Parsed connection settings.                                                                                                                                               |
| `ctx.config.sync`               | Object                              | Parsed sync settings, with defaults applied.                                                                                                                              |
| `ctx.checkpoint`                | Checkpoint type or `undefined`      | The last committed checkpoint. `undefined` on the first run, after the sync's settings change, and in every Replace run.                                                  |
| `ctx.signal`                    | `AbortSignal`                       | Pass it to async work that does not go through `ctx`.                                                                                                                     |
| `ctx.log`                       | `IntegrationLogger`                 | `debug`, `info`, `warn` and `error`, each `(message, fields?) => Promise<void>`. Lines appear on the connection's **Logs** tab. Never log credentials.                    |
| `ctx.fetch(path, init?)`        | `Promise<Response>`                 | Sends an authenticated request to a path relative to the origin, or a same-origin URL. `init` takes `method`, `headers`, `body` and `signal`. Redirects are not followed. |
| `ctx.json(path, schema, init?)` | `Promise<output>`                   | Like `fetch`, then parses the JSON body with `schema`. Throws `HttpError` for an unsuccessful status.                                                                     |
| `ctx.paginate(options)`         | Async generator of `PaginationPage` | See below.                                                                                                                                                                |

`ctx.paginate` takes a `request` (`path`, optional `query` and `headers`), a `schema` for each page,
and a `next` function. `next` receives the page and returns the next request, or `undefined` to
stop. Each page has `data`, `response` (`status` and `headers`), `request` and `index`. A
pagination stops with an error after 10,000 pages or on a repeated request.

Await every `ctx` call. The SDK adds credentials to each request, so sync code never handles
secrets.

## Auth helpers

| Helper                                                                                     | Credential fields the user enters                         | Use                                                                                                                                  |
| ------------------------------------------------------------------------------------------ | --------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ |
| `auth.bearer()`                                                                            | `token`                                                   | Bearer token in the `Authorization` header.                                                                                          |
| `auth.basic()`                                                                             | `username`, `password`                                    | HTTP basic authentication.                                                                                                           |
| `auth.apiKey({ in, name })`                                                                | `apiKey`                                                  | An API key in a header or query parameter called `name`.                                                                             |
| `auth.oauth2({ issuer, authorizationUrl, tokenUrl, scopes, clientSecret?, tokenFields? })` | `clientId`, plus `clientSecret` when `clientSecret: true` | OAuth 2.0 authorization code with PKCE and token refresh. `tokenFields` maps token-response fields for use as an origin.             |
| `auth.tokenExchange({ credentials, request, response, session? })`                         | Your `credentials` schema                                 | Exchanges credentials for a session token at `request.path`, reads it from `response.tokenPath`, and renews it by `response.expiry`. |
| `auth.custom({ credentials, headers?, query? })`                                           | Your `credentials` schema                                 | Places credentials in headers or query parameters with `{ credential: "field" }`.                                                    |

Custom and token-exchange credentials are strict objects of strings, and they cannot have defaults.
Wrap sensitive fields in `secret()`. See [Authentication](https://docs.beetl.io/connect/oauth-and-rest).

## Helpers

`batchRecords(source)` takes an iterable or async iterable of records and yields record-only batches
of a suitable size. Use it as `yield* batchRecords(rows)` when a sync has no checkpoint; yield
batches yourself when each one carries a checkpoint.

`storageRecord(schema)` wraps a provider object schema so every top-level field gets a fixed column
type. Strings, numbers, booleans, arrays and strict nested objects keep their type. A union of
scalar types is stored as text, and other dynamic values, such as records with arbitrary keys, are
stored as JSON text. The schema must be a plain object without a catchall or root refinements.

```ts
import { auth, batchRecords, defineIntegration, storageRecord, z } from "@beetlio/connect";

const Customer = z.object({
  id: z.number(),
  name: z.string(),
  attributes: z.record(z.string(), z.unknown()),
});

export default defineIntegration({
  key: "acme-customers",
  displayName: "Acme customers",
  connection: { origin: "https://api.example.com", auth: auth.bearer() },
  syncs: (sync) => ({
    customers: sync({
      records: storageRecord(Customer),
      async *run(ctx) {
        yield* batchRecords(await ctx.json("/customers", z.array(Customer)));
      },
    }),
  }),
});
```

## CLI

The SDK installs the `beetl-connect` command. Run it with `npx beetl-connect`, and add `--help` to
any command for every option.

| Command                  | Options                                                                     | What it does                                                                                                                                                                                                    |
| ------------------------ | --------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `configure <dir> [sync]` | `--profile`, `--connection`, `--origin`, `--inputs <json>`, `--reauthorize` | Prompts for settings and credentials, authorizes OAuth, runs `verify`, and saves a local profile.                                                                                                               |
| `sync <dir> [sync]`      | `--profile`, `--output <path>`, `--state <path>`, `--mode append\|replace`  | Runs a sync and writes its records as NDJSON. Checkpoints are saved under `.beetl/state/` unless you pass `--state`. `--mode replace` starts from the beginning and writes the file only when the run succeeds. |
| `pack <dir>`             | `--output <path>`                                                           | Creates the `.tgz` you upload to Beetl and prints the files it contains.                                                                                                                                        |
| `compatibility`          | None                                                                        | Checks the installed SDK against its compatibility fixtures.                                                                                                                                                    |

The sync argument is optional when the integration has one sync. Local credentials are saved
unencrypted in files only your user can read, in the operating system's user configuration
directory. See [Local development](https://docs.beetl.io/connect/local-development) and
[Pack and upload](https://docs.beetl.io/connect/distribution).
