Skip to content

docs(agents): add buy page for paying x402 APIs from an agent - #2425

Open
szapelao wants to merge 4 commits into
celo-org:mainfrom
szapelao:patch-1
Open

szapelao wants to merge 4 commits into
celo-org:mainfrom
szapelao:patch-1

Conversation

@szapelao

@szapelao szapelao commented Nov 3, 2026 •

Copy link
Copy Markdown
Contributor

Adds a page for buy (@celo/buy), Celo's x402 payment client, which had no page in the docs and was missing from llms.txt. buy was only mentioned on x402-get-discovered, as a catalog for sellers.

What this does:

  • New guide page build/agents/buy.mdx: sandbox, mainnet with --max-amount, MCP server, buy skills, troubleshooting
  • Adds it to the Agents navigation in docs.json, which also puts it in llms.txt (generated by Mintlify)
  • Lists it on ai-resources and overview, per AGENTS.md §8.9
  • Also: punctuation-only edits on overview.mdx (hyphen in the "Real-World" heading, serial commas in four sentences). The heading anchor is unchanged. Happy to revert them if you'd rather keep this PR to the buy page only.

What this does NOT do:

  • No mainnet payment was run. Mainnet commands come from the @celo/buy 0.8.5 README.
  • Commands are pinned to @celo/buy@0.8.5 (npm latest today) and need updating on new releases.
  • Gateway catalog contents are not listed; the page links to usebuy.ai.

Verification:

  • Sandbox example run on Celo Sepolia with @celo/buy@0.8.5 (Node v24.14.1). The paid response came back, and buy printed:
    buy created sandbox account 0x5d5674DF7F6A9229cC74417335Be6A6613f7d828 on celo-sepolia (test funds only)
    buy sandbox: funded 0x5d5674DF7F6A9229cC74417335Be6A6613f7d828 with 1 USDC (tx 0x3ceeeef2decd0caa9e33e9c2fdcc47b9166f9ff23c5f7ab60dc022164164a6bf)
  • mint broken-links: not run locally; CI runs it on this PR.

Judgement calls:

@artugrande could you review for accuracy against the current buy release?

@szapelao
szapelao requested a review from a team as a code owner November 3, 2026 16:19
@mintlify

mintlify Bot commented Nov 3, 2026 •

Copy link
Copy Markdown
Contributor

✅ Preview approved

@artugrande approved a Mintlify preview for commit 6b5c33e.

@artugrande artugrande left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verdict: REQUEST-CHANGES

Reviewed at head 6b5c33ea (re-checked against the API right before drafting; the branch is 0 commits behind main). Checked against @celo/buy@0.8.5, which is still npm latest (published 2026-10-28; no newer version). Locally, all five steps of the Documentation Validation job pass at 6b5c33e with the CI-pinned mintlify@4.2.920: validate, broken-links --check-redirects, check-orphans.sh, redirect unit tests (25/25), and check_redirects.py. I rendered the page with mint dev at 1440px and 375px, and neither width scrolls horizontally. I ran --help for every documented command, plus quote against the real gateway (sandbox and mainnet). I also ran CLI and MCP paid calls against a local buy gate --mock paywall, using a throwaway unfunded file-backend wallet in an isolated BUY_HOME. I did not run any paid call, including the sandbox one.

Tiered as new surface: a page that tells agent developers how to spend money. This round should be targeted fixes; the page does not need a rethink.

Findings

1. High · build/agents/buy.mdx:67: the MCP spending cap is optional, but the page says it is always applied.
The page says: "Every paid call over MCP carries a maxAmount and is refused before signing when the quote is above it." In 0.8.5, maxAmount is an optional input of buy_curl/buy_fetch (@celo/buy-mcp/dist/tools/pay-curl.js:24-27). It is applied only when the agent passes one (:107, ...(maxAmount ? { maxAmount: BigInt(maxAmount) } : {})), and it is in atomic units (6 decimals), not the decimal amount the CLI flag takes. If the account has no daily cap (none by default: buy-keystore/dist/spend-ledger.js:146, if (!account.dailySpendCap) return undefined), nothing bounds the call.
Runtime, against a local 0.05 USDC paywall:

  • buy_curl {maxAmount:"20000"} → Challenge amount 0.05 USDC exceeds cap 0.02 USDC (error code: amount_exceeds_max)
  • buy_curl {} → gets past the cap check and stops only at insufficient_balance because the test wallet is empty. A funded wallet would have paid.

Failure scenario: an agent developer installs the MCP server and trusts the docs that a cap is always enforced. Their agent then calls buy_curl without maxAmount and pays whatever the 402 asks.
Fix (one sentence): "Pass maxAmount on every paid call: it is optional, in atomic units (20000 = 0.02 USDC), and without it the call is capped only by the account's daily cap, if you set one with buy account cap <name> <amount>."

2. High · build/agents/overview.mdx:61 and :63: the overview repeats the cap claim, and the next paragraph no longer reads correctly.
Line 61 says buy pays "with a spending cap on every call". That has the same problem as finding 1: the CLI applies the cap only when --max-amount is passed (buy/dist/commands/curl.js:37-38,147), and the MCP tool applies it only when maxAmount is passed. The next line, 63, still starts "Both settle on-chain in seconds…", but it now follows three bullets, and buy is a client, not something that settles.
Fix: reword line 61 to "…with a per-call spending cap you set (--max-amount)", and change "Both" to "x402 and MPP" (or move the buy bullet below that paragraph).

3. Medium · build/agents/buy.mdx:67: the steps for enabling buy_send are incomplete.
The page says buy_send "stays off unless you enable it with a budget or an allowlist". In 0.8.5 the tool is registered only when all of the following are set (buy-mcp/dist/server.js:12-21,32-38,66-74): BUY_MCP_ENABLE_SEND=1, BUY_MCP_SEND_ACCOUNT, and BUY_MCP_SEND_BUDGET or BUY_MCP_SEND_ALLOWLIST. Runtime: with ENABLE_SEND=1 and SEND_BUDGET=1 but no account, the server prints buy: NOT registering buy_send — no source account — set BUY_MCP_SEND_ACCOUNT… and the tool list has no buy_send.
Failure scenario: a reader sets a budget as the page says, and buy_send never shows up. Nothing on the page explains why.
Fix: name the three environment variables in that sentence.

4. Low · build/agents/buy.mdx:61-65: mcp install --client all registers buy only with Claude Code and Codex, not with every MCP client.
--client accepts claude | codex | all, and all means ['claude', 'codex'] (buy/dist/commands/mcp.js:17). The page says "Install the buy MCP server in your MCP clients".
Fix: "Register the buy MCP server with Claude Code and Codex. For other clients, configure npx --yes @celo/buy@0.8.5 mcp serve as a stdio server."

5. Low · build/agents/buy.mdx:54: the mainnet example does not run as written (AGENTS.md §5).
<paid-endpoint-url> is a placeholder. The page already uses a real paid route. buy quote shows that route offering 0.005 USDC/USD₮/USA₮ on celo, so this works and stays under the cap:

npx --yes @celo/buy@0.8.5 curl --max-amount 0.01 https://gateway.usebuy.ai/v1/social/x/posts/search \
  -X POST -H 'content-type: application/json' -d '{"query":"celo"}'

6. Low · build/agents/buy.mdx:77-81: the Troubleshooting table leaves out the two errors the page's own advice leads to (AGENTS.md §8.4).
A reader who follows "always set --max-amount" or "fund the printed address" will see these errors, which are observed verbatim above:

  • amount_exceeds_max (challenge amount 0.05 USDC exceeds cap 0.02 USDC). Nothing was signed. Raise --max-amount only if the price is expected.
  • insufficient_balance (Not enough to pay: this needs … but 0x… holds 0 USDC). Nothing was signed. Fund the account.

Not a finding, but must happen before merge

  • CI has not run on this PR. Documentation Validation for 6b5c33e is action_required because a maintainer must approve workflows from a fork. So the PR body's statement "mint broken-links: not run locally; CI runs it on this PR" has not come true yet. My local run above passes. The workflow still needs approving so the check runs on GitHub.
  • The Mintlify fork preview check is NEUTRAL and is waiting for approval from someone with write access.

Verified good

  • npx --yes @celo/buy@0.8.5: 0.8.5 is still latest. Node.js 22+ matches the README.
  • --sandbox is a global flag placed before curl. That is correct, because everything after curl is forwarded to curl. Sandbox quote on the X search route shows a celo-sepolia offer of 0.005 USDC, which is inside the 1 USDC/day faucet grant (README). Chain IDs 11142220 and 42220 are right.
  • setup --name <name>: the default network is mainnet.
  • curl --max-amount refuses before signing when the price is above the cap (runtime: challenge amount 0.05 USDC exceeds cap 0.02 USDC, exit 1). --token accepts USDC|USDT|USAT and defaults to USDC.
  • quote takes curl arguments, needs no wallet, and prints token, price, network and recipient, then Nothing was signed or paid. A route that does not return 402 gives unexpected_response (observed with --json).
  • receipts --resolve and receipts --account <name> both exist. The meanings of 500 provision_failed, 500 settle_uncertain, 4xx and 503 match the README.
  • MCP tools buy_pay_quote, buy_curl, buy_balance and buy_whoami all exist. buy_send is off by default (runtime tool list).
  • npx skills add celo-org/buy-skill matches that repo's README, which ships four skills.
  • The Beta warning matches the npm README.
  • Wiring: build/agents/buy sits in the Agents group right after mpp, and the sidebar shows "buy (agent payments)". It is listed on ai-resources under "Celo tooling for agents" and on overview, as AGENTS.md §8.9 requires. There is no llms.txt in the repo: Mintlify generates it from navigation plus description. The live file has mpp in that format, and the new page has both inputs. mint dev does not serve llms.txt, so I did not see the new line rendered.
  • Page anatomy follows AGENTS.md §3 for a guide: frontmatter, section order, a Resources table, and a Related list.
  • Writing USD₮ in prose follows the maintainer instruction on #2415 and 10 other pages. The stale rule is in AGENTS.md §7; it is tracked separately in #2427 so it does not block this PR.
  • The overview.mdx punctuation edits are fine to keep. The "Real-World" heading slug stays agentic-activity-with-real-world-utility, and no page links to that anchor.

What the PR got right

It fills a real gap, and every command is pinned to an exact version. The sandbox-first structure means readers can try buy without risking money. The page describes the "do not retry on settle_uncertain" rule correctly, and the PR body says plainly what was and was not run.

Merge-order hazard

#2420 touches the same three files (docs.json Agents group, overview.mdx x402/MPP bullets, ai-resources.mdx). Its overview.mdx hunk changes line 59, right next to this PR's edits on lines 60-61, so whichever PR merges second will probably need a small rebase.

Follow-up issue (filed with this review)

  • #2427: AGENTS.md §7 still says to write USDT, not USD₮, in prose, which contradicts #2415 and the current pages.

🤖 Generated with Claude Code

@viral-sangani viral-sangani left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1 to Artu's points, esp the cap ones: maxAmount is optional and in atomic units, and the overview's 'spending cap on every call' isn't true. same for the buy_send env vars and --client all only meaning Claude Code + Codex

build/agents/buy.mdx:81: 'A 4xx or 503 response: refused before settlement, so nothing was charged' is too broad. our gateway settles before proxying, and a 503 relayed from the upstream is charged. third-party sellers settle however they want too. would say: check whether a payment receipt came back, or run buy receipts --resolve, before assuming nothing was charged, and don't auto-retry

@artugrande

Copy link
Copy Markdown
Contributor

@benibauer3, FYI: the review above is REQUEST-CHANGES at 6b5c33e, with 6 findings. The two high-severity ones are about the spending cap: the page and the overview say it always applies, but over MCP it is optional. Documentation Validation now runs and passes on this commit. The Mintlify preview is approved but has not built, which is tracked in #2430. In the meantime, I reviewed the page with a local mint dev build at desktop width and 375px.

🤖 Generated with Claude Code

@benibauer3

Copy link
Copy Markdown
Contributor

@szapelao, FYI: Artu's review above is REQUEST-CHANGES at 6b5c33e, with 6 findings. The two high-severity ones: the page and the overview say the spending cap always applies, but over MCP maxAmount is optional (and in atomic units). Could you address them? The Mintlify preview is not building (tracked in #2430), so check the page locally with mint dev for now. A maintainer also needs to approve the Documentation Validation workflow, since this PR comes from a fork.

@artugrande

Copy link
Copy Markdown
Contributor

Correction to my review: @viral-sangani is right about build/agents/buy.mdx:81. Under "Verified good" I wrote that the 4xx/503 row matches the README, but I had checked it against the README, not against the code. The README claim is too broad as well.

In @celo/buy-gateway@0.8.5 (dist/server.js), the gateway settles first (settleX402Payment, line 935) and only then proxies. Whatever status the upstream returns is relayed as-is (res.writeHead(upstreamRes.status, …), line 1256), with an X-PAYMENT-RESPONSE receipt attached (line 1212). So an upstream 4xx or 503 that arrives after settlement has been paid for. Only the gateway's own refusals before settlement say so explicitly in the body: "paymentSettled": false (lines 704, 712, 857, 881, 992).

Finding 7 · Medium · build/agents/buy.mdx:81: "A 4xx or 503 response: the service refused the request before settlement, so nothing was charged" is wrong for errors relayed from the upstream.
Failure scenario: an agent gets a 503 relayed from the upstream, reads it as free, and retries. It pays twice.
Fix: "A 4xx or 503 with paymentSettled: false in the body, or with no X-PAYMENT-RESPONSE header, was refused before settlement. Anything else may have been charged: check for a receipt, or run buy receipts --resolve, before retrying, and do not retry automatically."

The verdict stays REQUEST-CHANGES. There are now 7 findings at 6b5c33e.

🤖 Generated with Claude Code

@benibauer3

Copy link
Copy Markdown
Contributor

@szapelao, here is a patch that applies the 7 findings from Artu's and Viral's reviews at 6b5c33e, as a starting point. I took the fixes from their review text. I have not run mintlify validate or broken-links on it, so please check it after applying. Save the block below as fixes.patch and run git apply fixes.patch, or copy the edits by hand.

What it changes:

  • buy.mdx: maxAmount is optional, in atomic units, and without it only the daily cap applies; the three env vars that enable buy_send; --client all covers Claude Code and Codex only, with a stdio line for other clients; a runnable mainnet example; amount_exceeds_max and insufficient_balance rows; the 4xx/503 row no longer says "nothing was charged".
  • overview.mdx: the cap is "a per-call spending cap you set (--max-amount)", and "Both settle" becomes "x402 and MPP settle".
diff --git a/build/agents/buy.mdx b/build/agents/buy.mdx
index f567b44..0736c1e 100644
--- a/build/agents/buy.mdx
+++ b/build/agents/buy.mdx
@@ -50,21 +50,24 @@ Fund the printed address with a small amount of USDC, USD₮, or USA₮ on Celo
 Every paid call should carry `--max-amount`. buy refuses to sign when the quote is above it:
 
 ```bash
-# Celo mainnet (42220), pays at most 0.02 USDC
-npx --yes @celo/buy@0.8.5 curl --max-amount 0.02 <paid-endpoint-url>
+# Celo mainnet (42220), pays at most 0.01 USDC
+npx --yes @celo/buy@0.8.5 curl --max-amount 0.01 https://gateway.usebuy.ai/v1/social/x/posts/search \
+  -X POST -H 'content-type: application/json' -d '{"query":"celo"}'
 ```
 
 USDC is the default token. Add `--token USDT` or `--token USAT` to pay in one of those instead; the service must offer that token in its `402` response.
 
 ## Give an agent payment tools
 
-Install the buy MCP server in your MCP clients:
+Register the buy MCP server with Claude Code and Codex:
 
 ```bash
 npx --yes @celo/buy@0.8.5 mcp install --client all
 ```
 
-The agent gets tools such as `buy_pay_quote`, `buy_curl`, `buy_balance` and `buy_whoami`. Every paid call over MCP carries a `maxAmount` and is refused before signing when the quote is above it. `buy_send`, which moves money directly, stays off unless you enable it with a budget or an allowlist.
+For any other MCP client, configure `npx --yes @celo/buy@0.8.5 mcp serve` as a stdio server.
+
+The agent gets tools such as `buy_pay_quote`, `buy_curl`, `buy_balance` and `buy_whoami`. Pass `maxAmount` on every paid call: it is optional, in atomic units (`20000` is 0.02 USDC), and without it the call is capped only by the account's daily cap, if you set one with `buy account cap <name> <amount>`. `buy_send`, which moves money directly, is off by default. To turn it on, set `BUY_MCP_ENABLE_SEND=1`, `BUY_MCP_SEND_ACCOUNT`, and either `BUY_MCP_SEND_BUDGET` or `BUY_MCP_SEND_ALLOWLIST`.
 
 The MCP server gives an agent the ability to pay. The [buy skills](https://github.com/celo-org/buy-skill) teach it how to use that safely: read the free catalog, quote the exact request, get your approval for the price, pay once, and report the transaction hash.
 
@@ -78,7 +81,9 @@ npx skills add celo-org/buy-skill
 |-------|------------|
 | `500 provision_failed` or `500 settle_uncertain` | Do not retry automatically: the payment may already have settled. Run `buy receipts --resolve` to ask the token contract whether it did. |
 | `unexpected_response` from `buy quote` | The route answered with something other than `402`, so it is not a paid endpoint. Check the URL. |
-| A `4xx` or `503` response | The service refused the request before settlement, so nothing was charged. |
+| `amount_exceeds_max` | The quote is above your `--max-amount` (or `maxAmount`). Nothing was signed. Raise the cap only if the price is expected. |
+| `insufficient_balance` | The wallet holds less than the price. Nothing was signed. Fund the account. |
+| A `4xx` or `503` response | With `paymentSettled: false` in the body, or no `X-PAYMENT-RESPONSE` header, the request was refused before settlement. Otherwise it may have been charged: check for a receipt, or run `buy receipts --resolve`, before retrying, and do not retry automatically. |
 
 `buy receipts --account <name>` lists your local payment history, including each settlement transaction.
 
diff --git a/build/agents/overview.mdx b/build/agents/overview.mdx
index b848eca..a308994 100644
--- a/build/agents/overview.mdx
+++ b/build/agents/overview.mdx
@@ -58,9 +58,9 @@ Agents on Celo can charge and pay for services over plain HTTP, settling in stab
 
 - **[x402](/build/agents/x402)** gives meaning to the HTTP `402 Payment Required` status code, letting an agent settle a stablecoin micropayment inline with the request.
 - **[MPP (Machine Payments Protocol)](/build/agents/mpp)** is a separate protocol that uses the same `402` flow, so any service can charge USDC per request. On Celo, it settles through the same hosted facilitator as x402 — the buyer pays no gas.
-- **[buy](/build/agents/buy)** is Celo's x402 payment client: it lets an agent or a script pay for paid HTTP APIs in stablecoins, with a spending cap on every call.
+- **[buy](/build/agents/buy)** is Celo's x402 payment client: it lets an agent or a script pay for paid HTTP APIs in stablecoins, with a per-call spending cap you set (`--max-amount`).
 
-Both settle on-chain in seconds, making them well-suited to paid APIs, pay-per-use tools, and agent-to-agent commerce. A paid API, MCP server, or bot can also carry its own ERC-8004 identity, so agents can discover it and rate it — see [Register a service](/build/agents/8004#register-a-service).
+x402 and MPP settle on-chain in seconds, making them well-suited to paid APIs, pay-per-use tools, and agent-to-agent commerce. A paid API, MCP server, or bot can also carry its own ERC-8004 identity, so agents can discover it and rate it — see [Register a service](/build/agents/8004#register-a-service).
 
 ## Giving an agent a wallet
 

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants