# SDK reference

`@catena/sdk` is the typed Node.js client for the agent API, and it is the one
you'll want when you write your agent's code yourself. The SDK signs each
request with the agent's key, and the actions you submit go through the agent's
policy and approvals, the same as with the CLI.

This page covers `@catena/sdk` 0.11.0. The SDK requires Node.js 20 or later,
ships as ESM only, and has no framework dependencies.

```sh
npm install @catena/sdk
```

## Create a client

First, you'll need a client:

```ts
import { createCatenaClient } from "@catena/sdk"

// Pass the key explicitly, or omit it to read CATENA_SECRET_KEY.
const client = createCatenaClient({ privateKeyHex })
```

The key comes from the agent's **Secret keys** tab in the console (see
[Link with secret keys](https://docs.catena.com/connect/secret-keys.md)). The SDK validates the key when
you create the client. Note that the SDK does not write credentials to disk or
read CLI profiles.

## Read data

Once you have a client, you can read the agent, its policy, the accounts it can
see, and each account's balance and transactions:

```ts
const agent = await client.whoami()
const policy = await client.getPolicy()
const { accounts } = await client.listAccounts()

const balance = await client.getAccountBalance(accounts[0].id)
console.log(balance.balances?.available)

const { transactions } = await client.listAccountTransactions(accounts[0].id, {
  limit: 25,
})
```

## Send money

To send money, find one of a counterparty's rails and submit a `send` intent.
The example below pays the first bank rail it finds by ACH:

```ts
const { counterparties } = await client.listCounterparties()
const bankRail = counterparties
  .flatMap((counterparty) => counterparty.rails)
  .find((rail) => rail.type === "bank")
if (!bankRail) throw new Error("Add a counterparty with a bank rail first.")

const intent = await client.submitIntent({
  action: {
    type: "send",
    accountId: accounts[0].id,
    counterpartyRailId: bankRail.id,
    amount: "12.50",
    method: "ach",
  },
})
```

Amounts are decimal USD strings, so `"12.50"` is $12.50. `counterpartyRailId`
takes the ID of one of the counterparty's rails (`cpr_...`), and you can omit
`accountId` when the policy allows exactly one source account.

Note that `submitIntent` returns the intent's current state without waiting for
the payment to finish. See [Intents and retries](https://docs.catena.com/intents.md).

## Payments that need approval

If a send or transfer is at or above the approval threshold, it will wait for a
person. In the meantime, you can poll `getIntent` until `status` is final:

```ts
const submitted = await client.submitIntent({ action })

let current = await client.getIntent(submitted.id)
while (current.status === "pending" || current.status === "processing") {
  await new Promise((resolve) => setTimeout(resolve, 30_000))
  current = await client.getIntent(current.id)
}
// "completed": paid. "blocked" or "failed": not paid; read current.reasons.
```

After approval, an admin signs a wallet payment in the console. While that
happens, the intent will stay `processing`, and `movement.nextStep` will read
`wait`. See [Approval and execution](https://docs.catena.com/approvals.md#approval-and-execution).

In a short-lived function (e.g., a serverless handler), we recommend storing the
intent ID and calling `getIntent` on a later run instead of waiting in a loop.

> [!NOTE] **Payment checks**
>
> Before the SDK signs a payment, it checks the payment against the action you
> submitted, and it will refuse to sign a payment that would not pay the amount
> you asked for.

## Methods

The client exposes the following methods:

| Method                                        | Purpose                                                                                                |
| --------------------------------------------- | ------------------------------------------------------------------------------------------------------ |
| `whoami()`                                    | Returns the linked agent.                                                                              |
| `getSetupState()`                             | Returns outstanding setup steps and the organization's assets.                                         |
| `getPolicy()`                                 | Returns the agent's effective policy.                                                                  |
| `listAccounts()`                              | Lists the accounts the agent can see.                                                                  |
| `getAccountBalance(accountId)`                | Returns total and available balances, overall and per network.                                         |
| `listAccountTransactions(accountId, params?)` | Lists transactions, with optional `start`, `end`, `limit`, and `offset`.                               |
| `getAccountDepositAddress(accountId, params)` | Returns a deposit address for a `network` and `asset`.                                                 |
| `listCounterparties(params?)`                 | Lists counterparties, optionally filtered by wallet address and network.                               |
| `submitIntent({ action, idempotencyKey? })`   | Submits a policy-checked action.                                                                       |
| `listIntents(params?)`                        | Lists this agent's intents, filtered by `status`, `type`, `start`, `accountId`, `limit`, and `offset`. |
| `getIntent(id)`                               | Returns an intent's current state.                                                                     |
| `cancelIntent(id)`                            | Cancels an intent that is waiting for approval, or approved but not yet executing.                     |
| `unlinkAgent()`                               | Permanently revokes the current link and its active keys.                                              |
| `submitFeedback(body)`                        | Sends feedback to the Catena team.                                                                     |
| `reportSettlement(params)`                    | Reports an on-chain settlement. The payment wrappers call it for you.                                  |

`limit` defaults to 50, with a maximum of 200, and `offset` defaults to 0.
`start` and `end` are inclusive ISO 8601 timestamps.

## Intent actions

`submitIntent` accepts the following action types:

| `action.type`                  | Purpose                                            |
| ------------------------------ | -------------------------------------------------- |
| `send`                         | Sends by ACH, wires, or on-chain rail.             |
| `transfer`                     | Transfers between your own accounts.               |
| `create_counterparty`          | Saves a counterparty with contact or rail details. |
| `request_counterparty_details` | Asks a counterparty for bank or wallet details.    |
| `x402`                         | Submits a low-level x402 payment requirement.      |
| `mpp`                          | Submits a low-level MPP charge.                    |
| `policy_override`              | Asks for a temporary increase to a limit.          |

To pay x402 and MPP endpoints, we recommend the helpers in `@catena/sdk/x402`
and `@catena/sdk/mpp` rather than the low-level actions. See
[Use x402 and MPP](https://docs.catena.com/x402-and-mpp.md#pay-with-the-sdk).

## Errors

We recommend branching on the error class and its `code`. Your code should not
parse the message text. Below are the error classes and what to do with each.

| Error                     | What to do                                                                                                                                            |
| ------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- |
| `InvalidKeyError`         | Supply a valid P-256 private key.                                                                                                                     |
| `ApiError`                | Branch on `code`, and inspect `status`, `fields`, and `details`.                                                                                      |
| `NetworkClosedError`      | Do not retry on that network. Use one of `alternatives` instead, or tell the user.                                                                    |
| `InvalidApiResponseError` | The server may have acted, so reconcile before retrying.                                                                                              |
| `TimeoutError`            | The outcome is unknown until you reconcile.                                                                                                           |
| `IntentSubmitError`       | Branch on `outcome`. `not-submitted` means no money moved, while `unknown` means you should poll `getIntent(error.intentId)` before you submit again. |

To retry a submission, choose an `idempotencyKey` and save it before the first
call. Then retry the unchanged action with the same key.

## Client options

You can pass the following options to `createCatenaClient`:

| Option           | Purpose                                                                      |
| ---------------- | ---------------------------------------------------------------------------- |
| `privateKeyHex`  | The agent's 64-character P-256 private key. Defaults to `CATENA_SECRET_KEY`. |
| `baseUrl`        | The API origin. Defaults to the hosted Catena API.                           |
| `appInfo`        | `{ name, version?, url? }`, added to the User-Agent.                         |
| `defaultHeaders` | Headers to add to each request. The SDK drops auth headers from them.        |
| `fetch`          | A custom fetch, for proxies, instrumentation, or tests.                      |
| `timeout`        | The deadline per HTTP exchange, in milliseconds. Defaults to 80,000.         |
| `identityUrl`    | A stable signer identity URL. Defaults to one derived from the key.          |

## Entry points

The SDK ships the following entry points:

| Import                  | Contents                                                                                            |
| ----------------------- | --------------------------------------------------------------------------------------------------- |
| `@catena/sdk`           | The client, intent and account types, errors, and constants.                                        |
| `@catena/sdk/x402`      | Managed and low-level x402 payments.                                                                |
| `@catena/sdk/mpp`       | Managed MPP payments and Catena-backed mppx methods. Needs the optional `mppx` and `viem` packages. |
| `@catena/sdk/viem`      | A viem account that signs x402 payments through a policy-checked intent.                            |
| `@catena/sdk/keypair`   | P-256 key utilities, for hosts that create their own keys.                                          |
| `@catena/sdk/movements` | Checks a prepared payment locally before signing it.                                                |

## Stability

The SDK is in initial development. Patch releases within a `0.x` minor version
are meant to stay compatible, but a new minor version may change the public API,
so we recommend testing before you upgrade to one. You'll find the full README,
with every option, on [npm](https://www.npmjs.com/package/@catena/sdk).

## Related

- [Agent API](https://docs.catena.com/api.md): The base URL, request signing, and conventions of the agent API, the signed HTTP API that the CLI and the SDK call.
- [Link with secret keys](https://docs.catena.com/connect/secret-keys.md): A secret key lets an agent act in Catena from hosts where catena link cannot store a credential, such as CI jobs, cloud sandboxes, and deployed services.
- [Intents and retries](https://docs.catena.com/intents.md): Learn how an intent moves through its statuses and resolutions, which fields explain its result, and how to retry safely with idempotency keys.
- [Use x402 and MPP](https://docs.catena.com/x402-and-mpp.md): Let your agent pay HTTP 402 endpoints in USDC with x402 or the Machine Payments Protocol, under the same policy as any other payment.
