Spend Policy and Payout Address Management
Guard outbound x402 payments with an org-level kill-switch, per-payment and daily caps, and a payee allowlist, and inspect the payout addresses your org has registered.
Every outbound x402 payment your org makes with pay() is checked against a spend policy: a paused kill-switch, per-payment and daily caps in token base units, and an allowlist of payee orgs. Reach for this page when you need to cap exposure before handing out payment credentials to an agent, or when you need to see which wallet addresses your org has already registered as payout destinations.
For the payment flow itself (registering a payout address as payee, creating a challenge, paying it), see Charging and Registering Payout Addresses and Paying a Challenge. This page covers the two read/update operations that sit alongside that flow: getSpendPolicy / setSpendPolicy and listPayoutAddresses.
What the spend policy controls#
The spend policy is the org-level guardrail on outbound x402 payments, with four fields: a paused kill-switch, a max_per_payment cap, a max_per_day cap (both in token base units), and an allowlist of payee org ids.
The spend policy guards outbound payments only, payments your org makes with pay(). It has no effect on payments your org receives via charge().
| Field | Type | Meaning |
|---|---|---|
paused | boolean | Kill-switch. When true, every outbound payment is refused. |
max_per_payment | string | null | Per-payment cap, in token base units. null means no cap. |
max_per_day | string | null | Daily cap, in token base units. null means no cap. |
allowlist | string[] | null | Allowed payee org ids. null means any on-net payee is allowed; [] denies every payee. |
USDC has 6 decimals, so a cap of "5000000" is 5.00 USDC.
Read the current policy#
Call getSpendPolicy() on the x402 client to read all four fields as the server currently holds them.
import { createX402Client } from "@primitivedotdev/sdk/x402";
const x402 = createX402Client({ apiKey: process.env.PRIMITIVE_API_KEY! });
const policy = await x402.getSpendPolicy();
console.log(policy);
// {
// paused: false,
// max_per_payment: "5000000",
// max_per_day: null,
// allowlist: null,
// }
Update the policy#
Call setSpendPolicy() with only the fields you want to change; omitted fields keep their current server-side value.
- 1
Decide which fields to change#
setSpendPolicymerges: only the fields you pass in the update change, and any field you omit keeps its current server-side value. There is no full-replace mode, so you never need to re-send the whole policy just to change one field. - 2
Call setSpendPolicy with only those fields#
// Cap every single payment at 5.00 USDC (5,000,000 base units). // paused and allowlist are left untouched. await x402.setSpendPolicy({ paused: false, max_per_payment: "5000000" }); - 3
Pass null to clear a cap#
nullis a real value here, distinct from omitting the field. Passingnullexplicitly removes a previously-set cap:// Remove the daily cap; max_per_payment (and paused, allowlist) are unchanged. await x402.setSpendPolicy({ max_per_day: null }); - 4
Verify with a fresh read#
const updated = await x402.getSpendPolicy(); console.log(updated.max_per_payment); // "5000000"
Kill-switch: pause all outbound payments#
Setting paused: true refuses every outbound payment regardless of caps or allowlist. Use it as an emergency stop when you suspect an agent is misbehaving:
await x402.setSpendPolicy({ paused: true });
Allowlist a fixed set of payee orgs#
allowlist takes payee org ids. null (the default) allows any on-net payee; an empty array denies every payee, which is useful for a policy you want fully closed until you explicitly open it:
// Only allow payments to two specific payee orgs.
await x402.setSpendPolicy({
allowlist: ["org_9f2a1b", "org_44de70"],
});
// Deny every payee (fully closed).
await x402.setSpendPolicy({ allowlist: [] });
// Allow any on-net payee again.
await x402.setSpendPolicy({ allowlist: null });
Setting allowlist: [] denies every payee, so every subsequent pay() call your org makes is refused. Confirm this is the intended blast radius before applying it in production.
Combine max_per_payment with max_per_day for defense in depth: max_per_payment bounds the damage from a single runaway call, max_per_day bounds it across a burst of calls.
List registered payout addresses#
listPayoutAddresses() returns every payout address your org has registered as a payee, each with its network, label, default flag, and verification timestamp. The one-time x402 payout address registration step is covered in Charging and Registering Payout Addresses.
const addresses = await x402.listPayoutAddresses();
console.log(addresses);
// [
// {
// id: "...",
// address: "0x1111111111111111111111111111111111111111",
// network: "base-sepolia",
// label: "treasury",
// is_default: true,
// verified_at: "2026-01-01T00:00:00.000Z",
// },
// ]
Registration is per network, so an org can hold a default payout address on base-sepolia and another on base. charge() resolves its pay_to from this directory, which is why you register before requesting payments.
Errors#
Every method on the x402 client, including getSpendPolicy, setSpendPolicy, and listPayoutAddresses, throws X402Error on a client-side, transport, or non-2xx server error. X402Error carries status (0 for a request that never reached the server), body, and retryAfter. See Node.js SDK Errors for the full X402Error shape (status, body, retryAfter) and how to distinguish a request that never reached the server from a server-side rejection.
Next steps#
Register a payout address as payee and create payment challenges with charge().
Paying a ChallengeSign and submit payment for an x402 challenge as the payer.
x402 Payments OverviewUnderstand the full non-custodial payment model shared across every SDK.
Node.js SDK ErrorsLook up X402Error and every other error type the SDK can raise.
Was this page helpful?