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.
npm install @catena/sdkCreate a client
First, you'll need a client:
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:
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:
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:
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.
NotePayment 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.