SDK and CLI

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.

Shell
npm install @catena/sdk

Create a client

First, you'll need a client:

TypeScript
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). 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:

TypeScript
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:

TypeScript
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.

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:

TypeScript
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.

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.

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.

Updated October 30, 2026 Applies to @catena/sdk 0.11.0 View as Markdown