01 · Premise
Payment is the authorization layer
Most commercial APIs begin with identity: create an account, issue an API key, attach a billing method, then authorize requests against that account. AgentHub starts from the request instead. A client asks for a capability, receives a machine-readable price, settles it, and retries the same request with payment evidence.
That distinction matters for autonomous software. An agent can discover a tool, decide whether the quoted price is acceptable, pay for exactly one call, and move on. There is no subscription lifecycle and no shared bearer secret to provision or rotate.
The resulting system is deliberately small: one Express service, one x402 resource server, one facilitator, one receiving address, and a set of routes priced in decimal USDC. Seven endpoints derive deterministic answers from Algorand data; four wrap LLM-backed work. The protocol is shared, but the reliability contract of every tool remains explicit.
02 · Protocol
The 402 handshake, end to end
A protected call is a two-request protocol. The first response describes what the server will accept. The client constructs and signs the Algorand USDC transfer, then repeats the identical application request with the payment signature attached. The resource server asks the facilitator to verify and settle before the handler returns the paid result.
The quote includes the network, asset, pay-to address, amount, and scheme. Keeping that response structured is what lets a generic client - or an MCP-connected agent - complete the exchange without bespoke checkout UI.
Client calls a protected endpoint without payment evidence.
Server returns HTTP 402 plus the accepted USDC payment requirements.
Client signs the Algorand transfer and submits it through the facilitator flow.
Client repeats the same request with PAYMENT-SIGNATURE attached.
Server verifies settlement, runs the tool, and returns a typed result.
GET /api/wallet-risk/<ADDRESS>
HTTP/1.1 402 Payment Required
PAYMENT-REQUIRED: <base64 quote>
GET /api/wallet-risk/<ADDRESS>
PAYMENT-SIGNATURE: <signed payment payload>
HTTP/1.1 200 OK03 · Correctness
Price units are a protocol boundary
AgentHub prices routes with a helper because the x402 AVM middleware expects a decimal amount in whole USDC - not micro-USDC. The library performs the six-decimal conversion internally. Passing 0.03 bills three cents; passing 30000 does not mean thirty thousand micro-units. It means 30,000 USDC.
This is the kind of integration detail that looks cosmetic until it moves real money. I kept the conversion in one function and route declarations use readable decimal strings, so reviewers can compare the code directly with the public price table.
function usdcPrice(priceUsdc: string) {
return {
scheme: "exact" as const,
network: NETWORK,
payTo: PAY_TO,
price: priceUsdc,
extra: { asset: USDC_ASA_ID, tag: CHALLENGE_TAG },
};
}
// Three cents, not 30,000 micro-USDC supplied manually.
accepts: usdcPrice("0.03")04 · API contract
Reject what you can before asking for payment
The server validates request shape before the payment middleware asks the caller to settle. A malformed Algorand address, a missing field, or a non-numeric asset ID returns 400 with no charge. Charging first and discovering a syntactic error second would technically satisfy the payment protocol while producing a hostile API.
Some errors are unknowable until the paid work begins. An indexer may reveal that a transaction does not exist; GitHub may reject a repository lookup; an upstream model or indexer may fail after settlement. Algorand settlement is final, and x402 does not provide a server-side refund primitive. AgentHub therefore documents those states instead of pretending every paid call is guaranteed to return a useful answer.
- Malformed input → 400, not charged
- Valid request without payment → 402 quote, not charged
- Settled request with successful work → 200, charged
- Resource missing after lookup → 404, charged
- Upstream failure after settlement → 502, charged
05 · Reliability
Once payment settles, retries need a ceiling
Every Algorand indexer request goes through a shared wrapper with an eight-second timeout and three bounded attempts. Public indexers can become slow on accounts with large histories; an unbounded retry would keep a paying caller waiting indefinitely, while no retry would surface avoidable transient failures.
The same principle appears in result precision. Algorand amounts are uint64 values and can exceed JavaScript's safe integer range. AgentHub returns exact base-unit values as decimal strings - amountRaw and totalSupplyRaw - alongside convenient display numbers. Agents doing arithmetic are told to parse the exact fields with BigInt.
Several endpoints also report whether their result is complete. windowComplete distinguishes a fully scanned relationship history from a bounded window. concentrationExact tells the caller whether holder concentration came from the complete holder set. truncated marks capped model output or a capped portfolio response. Uncertainty is data, not an implementation detail to hide.
- 8-second timeout per indexer attempt
- Three bounded attempts with backoff
- Exact uint64 values preserved as decimal strings
- Completeness flags travel with approximated or bounded results
06 · Product decision
Return the factors, not only the score
Wallet and asset risk are deterministic scoring models, not claims of objective truth. A wallet score considers account age, transaction count, ALGO balance, USDC opt-in, counterparty diversity, and rekey history. Asset risk looks at clawback, freeze, mutable management, default-frozen state, holder concentration, and creator age.
The response includes every signal that contributed. That gives the consuming agent enough context to apply its own policy - for example, rejecting rekeyed accounts regardless of the aggregate score - rather than trusting an unexplained number.
Two Algorand-specific correctness details shaped the implementation. Disabled administrative roles use the all-zero address rather than a missing field. And meaningful concentration uses circulating supply, excluding unissued reserve holdings, instead of blindly dividing by the declared maximum supply.
{
"riskScore": 35,
"riskLevel": "medium",
"signals": {
"accountAgeDays": 1,
"txCount": 100,
"balanceAlgo": 10.162856,
"usdcOptedIn": true,
"distinctCounterparties": 7,
"rekeyed": false
}
}07 · Reflection
What I would change before calling it infrastructure
AgentHub proves that the payment and discovery loop works on Algorand mainnet, but a production merchant needs more than a successful demo. The most important missing layer is a compensation policy for paid upstream failures - whether that is service credit, an application-level receipt that can be redeemed later, or a facilitator extension with stronger delivery semantics.
I would also move public-indexer dependence behind provider health checks, persist payment and execution receipts for support, add idempotency keys for retried POST bodies, and benchmark the cold path of widely-held assets. Finally, risk models should be versioned so agents can pin policy to a known set of weights instead of silently inheriting a future scoring change.
The useful lesson is not that HTTP 402 makes API commerce simple. It makes the commercial boundary composable. Everything behind that boundary still has to earn the caller's trust.