Installation
Wrap the x402 client you already have. x402-spend observes; your client keeps owning schemes, signers and spend controls.
Install
npm install x402-spend @x402/core @x402/fetch
Node 22.5 or newer. The store is node:sqlite, so there are no native dependencies to build.
Wrap your client
import { createSpend, SqliteSpendStore } from "x402-spend";
const spend = createSpend(client, new SqliteSpendStore()); // ./x402-spend.db
const res = await spend.fetch("https://api.example.com/search?q=x402", {
taskClass: "search_known",
});
spend.fetch is a drop-in fetch. Free calls pass through unrecorded; any call that reaches payment writes a receipt — the wire fields from the protocol, per-leg latency and status from the transport.
Nothing about the request or the response is recorded: not the body, not the response, not headers. See Privacy.
Label what you got
A receipt starts unlabeled. Only the caller knows whether the thing it paid for actually worked, so say so:
await spend.label(spend.last()!, "useful");
await spend.label(id, "not_useful", { reason: "stale" });
await spend.label(id, "not_useful", {
reason: "wrong",
recovery: "went_elsewhere",
note: "wrong city",
});
unlabeled is kept distinct from both outcomes on purpose, so a call nobody judged is never counted as a judgment either way. See Outcomes and reasons.
Publishing reviews
A label is worth something to other buyers, but only if it can be checked. Point a spend at a review server and each label is also published as a review verified against its settlement transaction:
const spend = createSpend(client, new SqliteSpendStore(), {
review: { endpoint: "https://api.x402spend.dev/v1/reviews" },
});
const result = await spend.label(id, "useful", { note: "clean answer" });
// { posted: true, status: "verified" }
label() returns { posted, status, error? }. status is verified once the server has matched the payment against the chain, or pending when it accepted the review but could not reach the chain yet and will retry.
Off by default, opt-in per spend
A review is public, and it names the address that paid. Read Privacy before turning this on — particularly the part about note, which is published verbatim, and taskClass, which is published only when it is shared vocabulary.
Nothing about posting can cost you a label. The local write happens first and always; a review server that is down, slow or unhappy with the submission comes back as { posted: false, error } and is never thrown. A receipt with no settlement — a refused offer, a transport failure — is never published, and unlabeled is not a publishable verdict.