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.

1 · REQUEST

Client calls a protected endpoint without payment evidence.

2 · QUOTE

Server returns HTTP 402 plus the accepted USDC payment requirements.

3 · SETTLE

Client signs the Algorand transfer and submits it through the facilitator flow.

4 · RETRY

Client repeats the same request with PAYMENT-SIGNATURE attached.

5 · EXECUTE

Server verifies settlement, runs the tool, and returns a typed result.

Raw HTTP lifecyclehttp
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 OK

03 · 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.

One pricing helper for every routets
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.

A result designed for policy, not blind trustjson
{
  "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.