---
title: Usage
description: Construct an Outbound instance with an adapter, list and read records, follow a campaign down to its sequence, stats and leads, and handle the one error class.
---

Create one instance per adapter, then call the same methods on it whichever vendor is behind it.

## Construct an instance

```ts title="outbound.ts"
import { createOutbound } from "outbound-sdk";
import { instantly } from "outbound-sdk/instantly";

// Reads INSTANTLY_API_KEY. Or pass it: instantly({ apiKey }).
const outbound = createOutbound({ adapter: instantly() });

console.log(outbound.provider, outbound.maturity);

console.log(outbound.capabilities["replies.category"]);
```

```text title="Output"
instantly beta
vendor
```

Constructing an instance makes no request. `maturity` says how far the adapter has been checked against the live API (see [what is verified](/docs/status)), and `capabilities` says what it can do before you ask.

To try the SDK without a key, use the [in-memory adapter](/docs/adapters/memory). The rest of this page does.

## Read

```ts title="reads.ts"
import { createOutbound } from "outbound-sdk";
import { demoData, memory } from "outbound-sdk/memory";

const outbound = createOutbound({ adapter: memory({ data: demoData() }) });

// One page, with a cursor for the next.
const page = await outbound.campaigns.list({ limit: 1, status: "active" });

console.log(
  page.items.length,
  "campaign on this page; complete:",
  page.complete
);

// A handle is one record and everything under it.
const [first] = page.items;
const campaign = outbound.campaign(first?.ref ?? "");

const sequence = await campaign.sequence();

console.log("steps:", sequence.steps.length);

const stats = await campaign.stats({ from: "2026-10-01", to: "2026-10-31" });

console.log("sent:", stats.rows[0]?.sent);

for await (const lead of campaign.leads.listAll()) {
  console.log(lead.email, lead.status);
}

// Every result says how it was obtained.
console.log(stats.meta.evidence, stats.meta.provider, stats.meta.complete);
```

```text title="Output"
1 campaign on this page; complete: true
steps: 2
sent: 90
person1@example.com replied
person2@example.com active
exact memory true
```

- **Collections** (`campaigns`, `senders`, `leads`, `messages`, `replies`, `lists`, `tags`, `blocklist`, `webhooks`, `workspaces`) have `list` for one page and `listAll` for every page, fetched under the vendor's rate limit as you iterate. See [collections](/docs/api/collections).
- **Handles** (`campaign(id)`, `sender(id)`, `lead(id)`, `list(id)`, `thread(id)`) are one record and what hangs off it. See [handles](/docs/api/handles).
- **Every result carries `meta`:** how it was obtained (`exact`, `sampled` or `derived`), when, how many vendor requests it cost and whether it is complete. See [results](/docs/api/results).

An id can be the vendor's own id or a `ref` from any record. On an instance that [combines](/docs/adapters/combine) several adapters, use the ref.

## Handle errors

There is one error class, `OutboundError`, with a `code`.

```ts title="errors.ts"
import { createOutbound, OutboundError } from "outbound-sdk";
import { demoData, memory } from "outbound-sdk/memory";

const outbound = createOutbound({
  // An adapter that, like HeyReach, cannot read campaign settings.
  adapter: memory({
    capabilities: { "campaigns.settings": false },
    data: demoData(),
  }),
});

try {
  await outbound.campaigns.get("no-such-campaign");
} catch (error) {
  if (OutboundError.is(error)) {
    console.log(error.code, error.permanent);
  }
}

try {
  await outbound.campaign("cmp_founders").settings();
} catch (error) {
  if (OutboundError.is(error)) {
    console.log(error.code, "-", error.message);
  }
}

console.log(await outbound.campaigns.exists("no-such-campaign"));
```

```text title="Output"
NotFound true
Unsupported - Reading a campaign's settings is not available: the memory adapter does not support it (campaigns.settings)
false
```

A missing record is `NotFound`. A read the adapter cannot serve is `Unsupported`, thrown before any request. `exists` never throws for a missing record. The full list of codes is on the [errors](/docs/api/errors) page.

## Next

- [Capabilities](/docs/capabilities): branch on what an adapter can do.
- [Insights](/docs/insights): computed answers on top of these reads.
- [Rate limits](/docs/rate-limits) and [retries](/docs/retries): what the SDK does so you do not have to.
- [Guides](/guides): complete tasks, end to end.
