Skip to main content
The conversation flow — proposals drafted from a prompt, accepted via instructions — is optional. Every artifact it creates has a plain API surface, and when your application already knows exactly what to create, you can drive it directly. Same objects, same guarantees, no LLM in the loop. Two invariants never relax, whichever path you take:
  • Dual control: a mandate activates only when a recognized signer other than the bound one signs it (Mandate signing).
  • Fire-time coverage: the mandate gate matches every payment against active mandates at the moment it fires — nothing is bound in advance.

Mandates

Create

POST /mandates drafts a pending mandate bound to a signer — see choosing targets for the three target kinds. Exactly one binding form names that signer:
  • payment_agent_id — the payment agent convenience: binds the agent’s signer and anchors the mandate to the agent’s customer.
  • signer_id + customer_id — binds any of your signers. Mandates were always signer-level at the gate; this form just names the signer directly. customer_id is required alongside because the customer anchors two things the agent used to supply: the recipient-target scope (names resolve against this customer’s payees) and the approver set (approval takes a recognized signer of this customer, other than the bound one). The signer must be recognized for that customer — a member of a signer group attached to one of its wallets.
The hosted-agent form:
The same mandate bound to a signer named directly:
Either way it stays pending — and covers nothing — until the customer approves it with a signed payload (POST /mandates/{mandate_id}/approve, walked through in Mandate signing). The returned mandate carries bound_signer_id and customer_id, whichever form created it. The rule’s asset is validated at create: it must be a sendable asset — deployed on the pinned network_id (or on at least one network when the rule leaves the network open). A symbol that can never match a payment (a fiat output like USD, a typo) is a 400 here, not a mandate that silently denies every fire.

List, inspect, cancel

Cancelling is a mandate mutation, so it is §8-signed like approval — same canonical payload, "action": "cancel", signed by a recognized signer other than the bound one. A pending mandate becomes rejected, an active one revoked:

Scheduled payments

Create

POST /scheduled-payments schedules one or more payments for a signer, without the proposal flow. Name the payee either with destination_id (an existing crypto destination of the wallet’s customer) or with a raw address + network_id — exactly one of the two. The schedule is explicit dates (one payment per timestamp), or count × interval_seconds from start_at (0 = now). One payment to an existing destination:
Four weekly payments to a raw address, starting now:
The response is one row per date, each scheduled. What is validated at create: the signer must be permitted to spend on the funding wallet, the wallet must belong to the calling client, the destination must belong to the wallet’s customer, and dates must be plausible (not past, not absurdly far). What is not validated at create: mandate coverage — that is decided at fire time by the gate, so a payment with no covering active mandate fails at fire (emitting scheduled_payment.failed) rather than being rejected here. Schedule first and sign the mandate later, or the other way round — order doesn’t matter, coverage at the fire moment does.

List and cancel

List filters compose: customer_id, signer_id, wallet_id, mandate_id (which mandate covered an executed payment), and comma-separated status values (scheduled, executed, cancelled, failed). Executed rows carry mandate_id and wallet_transaction_id for audit.

Mixing the two paths

Direct creates and agent-drafted instructions produce the same rows — a mandate signed after an instruction covers direct schedules too, and vice versa. Use the agent where natural language earns its keep, and this surface where your application already speaks the schema.