Recipes

Pay a vendor invoice

An agent can pay a vendor invoice by ACH from one of your accounts. Invoices under your approval threshold will go through without an admin, and invoices at or above it will wait for one. In addition, an idempotency key keeps a retry from paying the same invoice twice.

Example use case

Cora's Coffee has received invoice INV-4471 for $420.00 from Acme Supplies, one of its vendors. Cora's Coffee asks its agent to pay the invoice by ACH from the operating account, using the vendor and the invoice number as the idempotency key (two vendors can use the same invoice number). If the payment is under the approval threshold in the agent's policy, it will go through on its own. If it's at or above it, the agent sends an admin the approval link and waits.

The steps below follow that agent from start to finish:

Text
Find the bank rail → Choose the account → Send → Read the result → Confirm

Requirements

Before you start, you'll need:

  • A linked agent: The agent runs each step below with the CLI.
  • A verified organization: ACH payments are only available to verified organizations.
  • A policy that allows the send: The agent's policy must allow sends from the paying account to the vendor.

Prompt

You can hand the task to your agent with a prompt like this one:

Prompt for your agent
Pay Acme Supplies invoice INV-4471 for $420.00 by ACH from our operating account. Use idempotency key acme-inv-4471. If it needs approval, send me the approval link and wait.

Steps

1. Find the vendor's bank rail

First, the agent looks for the vendor among your counterparties:

Shell
catena counterparties list

The agent finds Acme Supplies, picks a rail with type: "bank", and takes its cpr_... ID. If Acme isn't a counterparty yet, the agent can create it from details you give it, or ask Acme for them:

Shell
catena counterparties create --name "Acme Supplies" --email billing@acme.example
catena counterparties request-details cp_... --bank

We email Acme a secure form (the request expires after 7 days), and the agent waits until a bank rail appears. The agent must not make up bank details.

2. Choose the source account

Next, the agent chooses the account to pay from and check its balance:

Shell
catena accounts list
catena accounts balance acct_...

The agent checks that .balances.available covers the $420.00 invoice.

3. Send the payment

Now the agent can send the payment, passing the invoice number as both the description and the idempotency key:

Shell
catena send --account acct_... --rail cpr_... \
  --amount 420.00 --method ach \
  --description "INV-4471" --idempotency-key acme-inv-4471

If the agent runs the same command again with the same key, we will return the same intent (with replayed: true) and will not pay a second time. See Retry safely for more on how retries work.

4. Read the result

Below are the statuses the send may return, and what happens next for each:

.status What happens next
completed The invoice is paid.
pending The payment is at or above the approval threshold. The agent sends you metadata.dataUrl (the approval link), and you approve or deny the payment in the console.
processing Catena has accepted the payment, and it's in progress. The agent polls catena intents get int_....
blocked The policy refused the payment, or you denied it. The agent reports .reasons and stops.
failed The payment didn't go through (for example, the bank returned it). The agent reports it and waits for you. If you approve another attempt, it uses a new idempotency key.

5. Confirm the payment

Finally, the agent confirms the payment:

Shell
catena intents get int_...

The vendor is paid once the intent's status is completed. .data holds the linked transaction when one is available, and can be null until then. Note that an approved payment still has to be submitted and settle. See Approval and execution for details.

Variations

You can adapt this recipe in a few ways:

Variation Change
Pay by wire Use a bank rail and --method wire. Outgoing wires include a $25.00 fee.
Pay a wallet Use a wallet rail and --method on-chain, then follow .movement.nextStep on the intent until it reads done.
Set a default memo An admin can set a default ACH memo in Settings, which replaces the agent's --memo on every ACH payment.
Updated October 30, 2026 Applies to @catena/cli 0.19.0 View as Markdown