Two agents that have never met want to trade. Neither one wants to deliver first, and neither wants to hand the other its private key. An escrow exists for exactly that, and for two agents running in two separate environments it is the only way to settle a job with proof attached to it.
Below is the whole lifecycle as API calls, in the order you run them: the seller agent creates the request, the buyer agent funds it, the seller submits proof, the buyer releases. Every route and field here is copied out of the public reference at https://openclawcash.com/docs.
The shape of it
- Seller side: create a pay request, submit proof, refund if the terms were not met.
- Buyer side: accept, fund, release or dispute.
- One escrow wallet holds the money in between. Neither agent ever holds the other agent's key.
Every authenticated call uses one header:
X-Agent-Key: occ_your_api_key
Writes also send an Idempotency-Key header with a value you generate once per logical action, so a retried request does not create a second escrow.
1. Set the account-level User Tag once
Escrow names each side by a global User Tag. Setting it is a single call:
curl -X PUT https://openclawcash.com/api/agent/user-tag \
-H "Content-Type: application/json" \
-H "X-Agent-Key: occ_your_api_key" \
-d '{ "userTag": "studio" }'
The tag is 3 to 8 lowercase characters from a-z, 0-9, ., _ and -, and it cannot be changed once it is set. Read it back with GET /api/agent/user-tag, which answers {"userTag": "studio"}, or {"userTag": null} before it is set.
2. Seller agent creates the pay request
curl -X POST https://openclawcash.com/api/agent/checkout/payreq \
-H "Content-Type: application/json" \
-H "X-Agent-Key: occ_your_api_key" \
-H "Idempotency-Key: checkout-create-001" \
-d '{
"walletId": "Q7X2K9P",
"amount": "1000000"
}'
The response is the contract both sides will act on: id, escrowId, escrowAddress, payreqToken, the chain, network and assetSymbol, and state: "pending_funding".
Three term fields are optional and worth setting on purpose:
-
expiresInSeconds: the funding deadline. -
autoReleaseSeconds: when a funded escrow can auto-release if nobody disputes. -
disputeWindowSeconds: how long the buyer can open a dispute after the auto-release point.
Each has a floor of 3600 seconds, and the dispute window must be less than or equal to the auto-release point.
3. The buyer reads the same request
A buyer agent that runs in the same workspace can call the agent routes with its own key: GET /api/agent/checkout/payreq/:id returns the request, its state and its escrow address. A buyer that runs elsewhere comes in through the hosted checkout link built on payreqToken, which is what makes this work across two separate systems.
4. Buyer accepts, then funds
Accepting is optional and claims the escrow as buyer before any money moves:
curl -X POST https://openclawcash.com/api/agent/checkout/escrows/es_d4e5f6/accept \
-H "Content-Type: application/json" \
-H "X-Agent-Key: occ_your_api_key" \
-H "Idempotency-Key: escrow-accept-001" \
-d '{}'
Funding has two routes. Direct, when the buyer wallet already holds the settlement token:
curl -X POST https://openclawcash.com/api/agent/checkout/escrows/es_d4e5f6/quick-pay \
-H "Content-Type: application/json" \
-H "X-Agent-Key: occ_your_api_key" \
-H "Idempotency-Key: quick-pay-001" \
-d '{ "walletId": "Q7X2K9P" }'
Or through a swap, when it does not: POST /api/agent/checkout/escrows/:id/swap-and-pay quotes first with "confirm": false, then executes with "confirm": true. If you would rather move the funds yourself, fund the escrow address on chain and then call POST /api/agent/checkout/escrows/:id/funding-confirm with the txHash and a minConfirmations value.
The escrow state goes to funded and the response carries the txHash and confirmations.
5. Seller submits proof
Before the auto-release point, the seller posts the deliverable hash and a URL the buyer can open:
curl -X POST https://openclawcash.com/api/agent/checkout/escrows/es_d4e5f6/proof \
-H "Content-Type: application/json" \
-H "X-Agent-Key: occ_your_api_key" \
-H "Idempotency-Key: proof-submit-001" \
-d '{ "proofHash": "sha256:...", "proofUrl": "https://example.com/proof/123" }'
State becomes proof_submitted.
6. Settle: release, refund or dispute
- Release is the buyer-side action, and it pays the seller payout wallet:
POST /api/agent/checkout/escrows/:id/release. The answer carriestxHash,gasDeductedandnetAmount. - Refund is the seller-side action:
POST /api/agent/checkout/escrows/:id/refund. Gas is deducted from the escrow amount. - A dispute is opened by the assigned buyer with a
reasonCode, before the dispute deadline:POST /api/agent/checkout/escrows/:id/dispute.
None of the three is reversible, which is why each one takes its own confirmation on the operator side.
7. Read the state whenever you need it
GET /api/agent/checkout/escrows/:id returns the current state plus grossAmount, gasDeducted and netAmount. The lifecycle is short: pending_funding, then funded, then proof_submitted, then released, refunded or disputed.
What to watch
- Fund from the network the request was created on. A buyer wallet funded on another chain is the most common way this stalls.
- Wallet webhooks and escrow webhooks are separate surfaces on purpose. If you want a callback when an escrow settles, subscribe to the
escrow.*events by name; wallet events are never delivered to a wildcard subscription. - An external buyer without an account can fund and release through the public checkout routes after a wallet-sign challenge, but opening a dispute still requires a claimed buyer account.
Top comments (0)