Documentation
What Recurro is, how an approval relates to money, every concept in the product, and the API.
What Recurro is
Recurro settles recurring stablecoin payments privately, with the governance a finance team already needs. Every payment, typed by a person or submitted by a software agent, is decided before settlement: allow, hold or refuse, against the payer’s mandate and the organisation’s policy, with the screening status receipted. Every step of every decision is an append-only, hash-chained receipt, signed on the payer’s own device with a key we never hold, and recomputable by anyone holding it.
Humans sit exactly where they should: they sign mandates and policies, they decide held payments, they issue and revoke disclosure keys, and they pull the kill switch. Agents move fast inside the fence; the humans own the fence.
How an approval relates to money
Recurro never holds funds and never holds signing keys. Settlement happens two ways. Privately: deposit, private send and moving to a public wallet are proved in the payer’s own browser against a shielded pool on Base. Publicly, for agents: an approval on the rail instrument returns an unsigned USDC transaction on Base; the agent signs exactly those bytes; the server relays.
The binding works in two gates. Gate one, before anything is prepared: may this be attempted at all? That is the mandate, the policy, the caps and the screening status. Gate two, after the server prepares the transaction and before the key signs it: is this prepared transaction exactly the one that was approved? The agent’s verifier decodes the bytes with its own code, checks recipient, amount, token and chain against what it asked for, and recomputes the signing hash independently. A server that prepared something else is caught, not trusted. The signature stays with the key holder; Recurro authorises, verifies and relays only what was approved.
Concepts
Environments
Test mode is the public showroom: a fictional treasury operation with real mechanics, open to anyone from the landing page with no account. Live is yours: sign in, create your organisation, and everything you see is your own data. The two are separated at the database on every table, and the switch is always in the banner.
Agents, and why several
An agent is software that spends under a named principal, with an operator of record and a wallet whose private key only you hold. One all-powerful agent is one key with all authority. That is the failure this product exists to prevent. Separate agents keep mandates narrow, keep the kill switch’s blast radius small, and make every payment attributable to a function.
Wallets, and where the keys live
There is no Connect Wallet button for agents, and there is not meant to be one. An agent runs unattended, so its signing key belongs where the agent runs. An agent’s wallet address is bound to it once and cannot be rebound through the API. A stolen API credential can spend within a mandate, which is bounded, revocable and visible on the Agents screen, but cannot redirect where the money goes.
The payer’s own keys are different and stronger: the spend key, signing key and recovery split all derive from a passphrase in the browser and never reach a server. That is why a signed receipt means more than our word, and why what we can do to your money is a page that reads the contract rather than a promise.
Mandates, and the protocols they express
A mandate is one agent’s permission to spend: a payee, per-payment and daily caps, a cadence and an expiry, signed over its own terms so that changing any of them needs a new signature. Different agent-payment protocols carry their own authority objects, and this one model is designed to express each of them, keeping the native object beside the normalised form. The demo shows them side by side on the Mandates screen, and adapters that ingest these objects directly come next:
- x402 · Machine payments over HTTP: per-call authority for small, frequent spend.
- acp · A checkout token scoped to one merchant interaction.
- ap2 · A cryptographically signed mandate; the richest authority object of the five.
- mpp · A metered session with a pre-authorised spending limit.
- ledger-native (illustrative) · A design fixture, not an external protocol: a hypothetical ledger-native mandate with need-to-know disclosure, included to test whether the policy model holds for an authority object that lives on a ledger.
- direct · Granted directly in Recurro; protocol adapters normalise the rest to the same model.
What a mandate is for
A mandate can also say, in a sentence, what it is for: cloud compute and API credits for the research team, say. The sentence is part of the signed terms. When the mandate’s owner asks for it, every payment’s description is judged against that purpose, and a purchase that clearly does not fit waits on Approvals for a person, with the reason on its receipt. The caps and the policy decide first; the purpose check can add a hold and can never allow what they refuse.
A description that tries to instruct whoever reads it is held, and so is a gift card, a top-up or a transfer to a person under a mandate that is for something else. When an agent sends two descriptions, the less favourable reading of the pair stands. A payment with no description passes with that fact on its receipt, unless the mandate asks for every purchase to be described. The mandate form previews a purpose against the agent’s recent payments before anything is switched on, and says when a purpose would exclude nothing. Judging sends the purpose and the description to the model provider, and never the payee, the amount or the organisation.
Policies, holds, and the second approver
The policy is the organisation’s rulebook: limits, the approval threshold, screening behaviour. Limits refuse; payments above the threshold are held for a second person, and the second approver cannot be the person who sent it. A held payment stops before anything settles and stays on Approvals as the record, whichever way it is decided.
The kill switch
Suspending an agent stands between signature and relay: the API refuses a suspended agent’s attempts, a template already signed will not be relayed, and the scheduler will not run its standing mandates. Reinstatement restores what was frozen. It withdraws authority, not funds.
Evidence
Every payment carries a hash-chained receipt naming who authorised it, against which signed mandate, why the amount was the amount (rate, quantity, conversion and its source), and which invoice it answers, by fingerprint. The bundle exports as one file that verifies offline with a checker that shares no code with this application. The format, and exactly what verification proves and cannot prove, is published in the standard.
Disclosure
An auditor holds a link scoped to a date range and a set of payees, sees exactly that, and every use, including refused ones, is logged for the organisation that issued it. We store a hash of the link, never the link, so a leaked database is not a set of working keys.
Teams, roles and recovery
Colleagues join by email invitation as operator, approver or auditor, attaching when that email first signs in. The passphrase itself can be split between named colleagues so that any chosen number of them can restore it together and no smaller group learns anything at all. This is rehearsable on the Keys screen before it is ever needed.
The API
One endpoint decides a payment before settlement and returns the decision with its receipt. It runs against the test environment with the published key below (the same key the demo uses) and against your own organisation with a live key issued on the Developer screen.
curl -X POST https://app.recurro.xyz/api/v1/decisions \
-H "Authorization: Bearer sk_test_recurro_public_demo_key" \
-H "Content-Type: application/json" \
-d '{
"amount": "1250.00",
"currency": "USD",
"agent": "Payroll Runner",
"counterparty": "Reinhardt Logistik GmbH",
"idempotency_key": "docs-example-001"
}'The response is the decision and its evidence:
{
"payment_id": "9c0453a7-…",
"state": "allowed",
"verdict": "allow",
"reason": "Within policy.",
"needs_second_approver": false,
"receipt": { "id": "10c3b01f-…", "seq": 1, "hash": "07cbaef3…" },
"replayed": false,
"signing": null
}| Verdict | What happens |
|---|---|
| allow | Within mandate and policy. A live allow on the rail instrument with a bound wallet returns a signing template, and the transaction settles on Base once the agent’s signature verifies against its bound wallet. |
| hold | Above the policy’s approval threshold, or the payee’s screening is not clear. The payment stops and waits on Approvals for a second person. |
| refuse | The payment violates its mandate or policy and is never constructed. The attempt is receipted with the rule that refused it. |
Amounts are fixed-point strings. With the test key, agent is one of the demo’s registered agents: Payroll Runner, compute-buyer (x402), procurement (ACP, MPP), or treasury-rebalancer (AP2). counterparty is a name from its payee directory. Unknown counterparties are refused rather than invented. Repeated requests with the same idempotency key replay the original decision instead of creating a second payment.
Two optional fields say what is being bought, up to 500 characters each: description, in the agent’s words, and seller_description, what the seller’s page or invoice says as the agent read it. Both come from the agent, so both are judged as the agent’s. Under a mandate that states a purpose, the response carries a purpose object: whether the description was judged, the band it fell in (fits, borderline, misfit, cash-like, instructs, disagree), whether it caused the hold, and the same sentence the reviewer sees, so an agent that was held can correct itself.
Paying over x402
Recurro runs an x402 facilitator (protocol version 2, the exact scheme). The agent signs an ordinary USDC authorisation to the facilitator; the payee is named by a Recurro address in extra.recurroAddress, and what reaches them is a private note. The chain sees the agent and the amount, and never who was paid. It works with no key at all; a key raises the hourly ceiling and meters what it settles.
| GET /api/x402/supported | The networks and token it settles on right now, the address to pay on each, the floor and ceiling per payment, and whether private delivery is available this minute. |
| POST /api/x402/verify | Whether a signed payment would settle, without spending anything: the signature, the payer’s balance, replay, and whether the private leg can be delivered. |
| POST /api/x402/settle | Broadcasts the transfer exactly as signed and queues the private delivery. Returns the transaction and a settlement id. |
| GET /api/x402/settlement/{id} | Both legs of one settlement: the public transfer and the private delivery. |
| GET /api/x402/usage | What a key has been counted for, from the same rows an invoice is drawn from. |
Every refusal is a named reason, from /verify before the seller does any work and from /settle before any money moves: invalid_pay_to, invalid_signature, insufficient_funds, amount_below_minimum, amount_above_maximum, authorization_expired, authorization_not_yet_valid, authorization_already_used, missing_recurro_address, unsupported_network, unsupported_scheme, resource_mismatch, rate_limited. The private leg is paid out of a float in the pool, and a payment the float cannot deliver is refused before the public transfer, never taken and owed: that is float_insufficient and delivery_unavailable. USDC only: the scheme is EIP-3009, which Tether’s token does not implement.
The signing flow
Four endpoints carry an agent’s payment from decision to settled receipt. The server never holds the key; the agent never constructs a transaction.
// A live allow for the rail instrument, with a wallet bound to the agent,
// carries the unsigned transaction instead of null:
"signing": {
"status": "ready",
"template": { "chainId": 84532, "to": "0x036C…", "data": "0xa9059cbb…", "nonce": 4, … },
"template_hash": "0x7d1f…",
"expires_at": "2026-08-24T12:02:00.000Z",
"submit": "https://app.recurro.xyz/api/v1/payments/<id>/signature",
"funding": null
}
// The agent verifies the bytes itself, signs the keccak256 of the serialised
// transaction, and posts the 65-byte signature. The server checks it recovers
// to the bound wallet and relays. A deviating payment is unconstructable.- POST /api/v1/decisions: decide; a live allow carries the template.
- POST /api/v1/payments/:id/signature: submit the 65-byte signature; verified against the bound wallet, then relayed. One template, one signature, ever.
- POST /api/v1/payments/:id/bump: re-bid a stuck transaction with the same nonce, same recipient, same calldata; only the fees rise. Three attempts, then a human looks.
- GET /api/v1/payments/:id: the payment, its submission state, and the full receipt chain with every hash recomputed rather than read from a column.
Expired templates are reissued by a fresh decision, a stuck transaction is re-bid at the same nonce, and a chain reorganisation appends an observation to the receipt chain rather than rewriting it. Every retry carries an idempotency key, so a repeated request replays the original decision instead of creating a second payment.