Cloudflare paidTool pricing when a Sume run sits behind it

If a Cloudflare paidTool wraps a Sume Agent Completion, set the tool price above the run's generation_spend_cap_usd so one sale cannot lose money.

4 min readSume
All posts

Make the paidTool price higher than the generation_spend_cap_usd you pass to Sume. The cap is the most the run may spend on generation, so a price above it keeps each paid call from costing you more than you charged. This is arithmetic about margin, not a Cloudflare feature.

Cloudflare's x402 page shows this.server.paidTool(name, description, price, inputSchema, annotations, handler), with the price in USD. A caller that has not paid gets a 402 with payment requirements, pays, and retries with proof. Your handler runs only after that.

Pieces and who owns them

x402 seller side and the Sume side (read 2026-10-04)
PieceDetailOwner
paidTool priceUSD, set per toolCloudflare Agents SDK
X402Confignetwork base or base-sepolia, recipient wallet, facilitator https://x402.org/facilitatorCloudflare Agents SDK
Test setupbase-sepolia with Circle faucet USDCCloudflare docs
generation_spend_cap_usdRequired; no default; missing means 400Sume Agent Completions
Idempotency-KeySame key returns the original receipt with idempotency_hit: trueSume Agent Completions

Handler sketch

The function below is what a paid handler would call after payment is verified. It creates the run with a cap derived from the price and returns the receipt id for polling. Pass the payment id as the idempotency key so a retried request does not start a second run.

export async function startRun(task: string, priceUsd: number, paymentId: string) {
  const res = await fetch("https://api.sume.com/v1/agent/completions", {
    method: "POST",
    headers: {
      Authorization: `Bearer ${process.env.SUME_API_KEY}`,
      "Content-Type": "application/json",
      "Idempotency-Key": paymentId,
    },
    body: JSON.stringify({
      instruction: task,
      generation_spend_cap_usd: Math.max(0.5, priceUsd / 2),
    }),
  });
  if (res.status !== 202) throw new Error(`Sume ${res.status}`);
  const { data } = await res.json();
  return data.id as string;
}

Things to watch

  • Pick the cap from your own cost data, not from this example's priceUsd / 2.
  • The run is async: return the agrun_ id and let the buyer poll, or use a run webhook.
  • Test the 402 path on base-sepolia before real USDC.
  • Service-account keys cannot create Agent Completions; use a user API key with the write scope.

Sources

Related posts

More in Developers

All Developers posts

Written by Sume