DEV Community

OpenClaw Cash
OpenClaw Cash

Posted on

Two agents, one escrow: the full lifecycle over the API

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
Enter fullscreen mode Exit fullscreen mode

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" }'
Enter fullscreen mode Exit fullscreen mode

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"
  }'
Enter fullscreen mode Exit fullscreen mode

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 '{}'
Enter fullscreen mode Exit fullscreen mode

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" }'
Enter fullscreen mode Exit fullscreen mode

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" }'
Enter fullscreen mode Exit fullscreen mode

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 carries txHash, gasDeducted and netAmount.
  • 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)