Escrows
Create a conditional deal, attest delivery, and read its state. Release is the contract's job, not an endpoint.
Escrow over the API is the mechanism escrow describes: funds locked in a programmable contract on Base or Solana, settlement in USDC, release decided by the conditions the parties wrote — never by us.
Create an escrow
Callable with an agent key (the agent is the buyer) or an organization key naming the buyer. The request runs the full policy evaluation first: an escrowed deal that breaks a mandate is denied before anything is locked, and the response then carries the denial instead of an escrow.
| Field | Type | Required | Meaning |
|---|---|---|---|
agent | string | org key | The buyer |
seller | string | yes | Counterparty: another agent, or a settlement address |
amount | number | yes | Amount to lock |
currency | string | yes | USDC |
chain | string | yes | base or solana |
condition.type | string | yes | attestation — a named party confirms delivery; deadline — time decides |
condition.attestor | string | if attestation | Who may attest: a principal or an agent |
condition.deadline | string | yes | When an unmet condition refunds the buyer |
arbiter | string | no | A third party both sides chose, written in before funding |
metadata | object | no | Up to 20 string keys |
{
"escrow": "esc_2f8d",
"state": "created",
"decision": "dec_77b1",
"agent": "agt_procurement_01",
"seller": "agt_datamart_02",
"amount": 1250.00,
"currency": "USDC",
"chain": "base",
"contract": "base:0x5f3a...9c1e",
"condition": { "type": "attestation", "attestor": "role:procurement_lead", "deadline": "2027-09-15T00:00:00Z" },
"arbiter": null,
"disputed": false,
"created_at": "2026-09-01T10:20:00Z"
}contract is the on-chain address holding the funds — public, and checkable on the chain itself without asking us. Public in both directions: whoever has the address reads the amount, both sides and the full state history, which is what the chain shows.
Lifecycle
| State | Meaning | What moves it on |
|---|---|---|
created | Contract deployed, waiting for funding | Buyer's funds arrive at contract |
funded | Funds locked | Attestation, or the deadline |
delivered | Condition attested | Release runs on the contract |
released | Funds with the seller; deal closed | — |
refunded | Funds back with the buyer; deal closed | — |
Each transition fires the matching webhook: escrow.funded, escrow.delivered, escrow.released, escrow.refunded.
Attest
Records that the condition was met. Only the named attestor may attest — the call is authorized against that identity, not against whoever holds an organization key. The state moves to delivered and release runs.
Dispute
Either side may dispute. It sets disputed: true and fires escrow.disputed — and changes nothing else by itself. Without an arbiter, a contested deal follows the same rule as any other: condition unmet by the deadline, contract refunds the buyer. An arbiter named at creation can release or refund within the terms the parties agreed.
Why there is no release endpoint
Because we cannot build one. The funds sit in a contract we do not control, on conditions we did not write — nobody at Saifuro can release an escrow early or freeze one, and an API that pretended otherwise would be describing a different product. Attest, dispute, and read are the whole surface.
Read
Filters: agent, seller, state, disputed. Paginated.
Fees
0.25% of the escrowed amount, minimum $1 per deal, charged to the customer that created the escrow when it releases, on top of the settlement fee on the released amount. A refunded or expired deal carries no fee, and nothing accrues while funds sit. Same numbers as pricing.

