For the complete documentation index, see llms.txt. This page is also available as Markdown.

Pay an Invoice Safely

Propose a payment, route to approval if needed, execute, get a receipt.

Goal: pay an invoice with a single SDK call. If it's within the tenant's policy, it goes through. If not, it routes to a human approver. Either way, you get a receipt you can show a customer.

The Simplest Case

const action = await brain.pay("acme", { invoiceId: "inv_8231" });

console.log(action.status);
// "auto"           → already executed
// "needs_approval" → waiting for a human
// "rejected"       → policy said no

action.status uses the SDK aliases auto | needs_approval | rejected. The HTTP PaymentIntent lifecycle uses approved | pending_approval | rejected | executed | … for the same states (autoapproved/executed). See the mapping table.

Handling All Three Outcomes

const action = await brain.pay("acme", { invoiceId: "inv_8231" });

switch (action.status) {
  case "auto":
    // Already done. Brain executed and recorded the receipt.
    console.log("paid:", action.receipt.txHash ?? action.receipt.railReceipt);
    break;

  case "needs_approval":
    // Surface to your approval UI. Approvers receive the action.
    console.log("waiting on:", action.approvers);
    break;

  case "rejected":
    console.log("blocked:", action.reason);
    break;
}

Approving from Your App

approve records the typed signature. Once all required approvers have signed, the intent becomes approved and Brain's internal settlement path runs the §6 gate and dispatches it; you do not call a separate execute step, and the approver's signature is not itself a settlement call. The action's status moves from needs_approval to auto.

For multi-approver policies, every required approver calls brain.approve. Brain holds the action in needs_approval until the last one lands.

Rejecting from Your App

Rejection is final. The action moves to rejected and emits a webhook your app can react to.

Getting the Receipt

Every executed action has a verifiable receipt.

If you ever need to prove to a customer that a payment happened, this is the thing to send them. They can verify it without a Brain account.

Paying Without an Invoice

Sometimes you're paying something that isn't a structured invoice yet (a vendor name and an amount, say). Pass the destination directly.

Brain still runs every check it would run for an invoice payment.

Idempotency

Always pass an idempotency key. Retries with the same key return the existing action instead of creating a duplicate.

If your service crashes mid-call and your retry handler fires, you'll get the same action back. No duplicate payments.

Webhooks for Long-Running Flows

Most ACH and wire payments don't settle instantly. Subscribe to the action's lifecycle.

Outbound webhooks use the payment_intent.* event names (the same event_type values the Webhooks API lists), not an action.* namespace:

event_type

When

payment_intent.created

Just after brain.pay returns (intent proposed)

payment_intent.approved

All required approvers have signed (or Policy returned allow)

payment_intent.rejected

Policy or an approver rejected

payment_intent.execute.after

The §6 gate ran and the intent was dispatched to its rail

Settlement is asynchronous and there is no dedicated payment_intent.settled / payment_intent.failed outbound event today. payment_intent.execute.after fires when the intent is dispatched; final rail settlement (or failure) is observed via the rail-specific provider webhook and confirmed through GET /v1/proof/{action_id} / replay-investigation.

What if My Action Fails?

Brain returns a structured failure code and never silently retries.

You can re-propose with a different source account, a different amount, or wait until the balance covers it.

What's Next

🛡 Spending Limits

Define what counts as "needs approval" in plain English.

📜 Audit Trail

Pull the full record of what happened and why.

Last updated