API monetization for the agent era
Agents are ready to pay. Most API stacks are not.
NetDollar is an x402 facilitator for HTTP 402 API monetization. Remove human checkout bottlenecks and let autonomous buyers pay in-band so high-intent usage converts immediately.
Payment flow
End-to-end sequence
- Step 1Buyer Agent
Request premium endpoint
Agent calls the protected route without payment on first request.
GET /v1/premium/quote - Step 2Seller API / Gateway
Return HTTP 402
Seller enforces payment and returns payment requirements for the route.
status=402paymentRequirements - Step 3Buyer Agent
Run buyer guardrails
Client validates max spend, network, asset, and payTo policy before signing.
maxAmountallowedNetworksallowedAssetsallowedPayTo - Step 4Buyer Agent
Sign payment + retry
Client creates signed payload with payment identifier and retries request.
PAYMENT-SIGNATUREpayment identifier - Step 5Seller API / Gateway
Validate and verify
Seller validates payload and calls facilitator verify endpoint.
POST /v2/x402/verifyx402VersionpaymentPayload - Step 6NetDollar Facilitator
Verify checks
Facilitator verifies signature, amount, network, asset, and payTo.
isValidpayerinvalidReason - Step 7Seller API / Gateway
Idempotency gate
Seller checks payment identifier to prevent duplicate settlement.
paymentId lookupcached outcome - Step 8Seller API / Gateway
Settle payment
If not already settled, seller calls facilitator settle endpoint.
POST /v2/x402/settletransaction hash - Step 9Seller API / Gateway
Return protected response
On settlement success, seller returns premium response payload.
status=200premium data - Step 10Async Jobs
Post-settlement pipeline
Emit analytics, persist receipts, and queue webhook/confirmation jobs.
event logsreceiptswebhooks
Payment header present?
Seller API / Gateway
Pass: Proceed to verify pipeline.
Fail: Return HTTP 402 with payment requirements.
Verify result valid?
NetDollar Facilitator
Pass: Proceed to idempotency + settle.
Fail: Return payment failure with invalidReason.
Already settled for payment identifier?
Seller API / Gateway
Pass: Return cached response and skip double-settlement.
Fail: Call settle endpoint.
Settle success?
NetDollar Facilitator
Pass: Return premium response.
Fail: Return settlement failure and do not serve paid payload.
402 Payment Required response
Seller API / Gateway → Buyer Agent
Payment requirements
Seller API / Gateway → Buyer Agent
Paid retry request
Buyer Agent → Seller API / Gateway
Verify payload
Seller API / Gateway → NetDollar Facilitator
Verify response
NetDollar Facilitator → Seller API / Gateway
Settle response
NetDollar Facilitator → Seller API / Gateway
- 1. `GET /v2/x402/supported` to confirm network and scheme support.
- 2. `POST /v2/x402/verify` before service delivery.
- 3. `POST /v2/x402/settle` to finalize payment execution.
Integration
Staging: https://api.staging.netdollar.dev
Production: https://api.netdollar.dev
GET /v2/x402/supported
POST /v2/x402/verify
POST /v2/x402/settle
GET /v1/discovery/resources
- • Define payment requirements for each paid route (scheme, network, asset, amount, payTo).
- • Return HTTP 402 when `PAYMENT-SIGNATURE` is missing.
- • On retry, call `POST /v2/x402/verify` with your seller API key.
- • If valid, call `POST /v2/x402/settle` and then return the protected response.
- • Optionally publish capabilities through `GET /v1/discovery/resources`.
1// Example: paid FX quote endpoint with real pricing tiers (Cloudflare Worker style)
2type Tier = "basic" | "pro";
3
4interface Env {
5 FACILITATOR_BASE_URL: "https://api.staging.netdollar.dev" | "https://api.netdollar.dev";
6 SELLER_API_KEY: string;
7 SELLER_PAY_TO: string;
8}
9
10interface PaymentRequirements {
11 scheme: "exact";
12 network: "base";
13 asset: "USDC";
14 maxAmountRequired: string; // USDC base units (6 decimals)
15 payTo: string;
16 resource: string;
17 description: string;
18 mimeType: "application/json";
19 maxTimeoutSeconds: number;
20 extra: {
21 tier: Tier;
22 paymentIdentifier: true;
23 };
24}
25
26interface VerifyResponse {
27 isValid: boolean;
28 payer?: string;
29 invalidReason?: string;
30}
31
32interface SettleResponse {
33 success: boolean;
34 payer?: string;
35 network?: string;
36 transaction?: string;
37 errorReason?: string;
38}
39
40const PRICE_USDC_6: Record<Tier, string> = {
41 basic: "25000", // $0.025 for quote only
42 pro: "150000", // $0.15 for quote + indicators
43};
44
45function buildPaymentRequirements(url: URL, payTo: string): PaymentRequirements {
46 const wantsIndicators = url.searchParams.get("detail") === "indicators";
47 const tier: Tier = wantsIndicators ? "pro" : "basic";
48
49 return {
50 scheme: "exact",
51 network: "base",
52 asset: "USDC",
53 maxAmountRequired: PRICE_USDC_6[tier],
54 payTo,
55 resource: url.pathname,
56 description: wantsIndicators ? "FX quote + indicators" : "FX quote",
57 mimeType: "application/json",
58 maxTimeoutSeconds: 60,
59 extra: {
60 tier,
61 paymentIdentifier: true,
62 },
63 };
64}
65
66async function facilitatorPost<TResponse>(
67 env: Env,
68 path: "/v2/x402/verify" | "/v2/x402/settle",
69 body: unknown,
70): Promise<TResponse> {
71 const response = await fetch(env.FACILITATOR_BASE_URL + path, {
72 method: "POST",
73 headers: {
74 authorization: "Bearer " + env.SELLER_API_KEY,
75 "content-type": "application/json",
76 },
77 body: JSON.stringify(body),
78 });
79
80 if (!response.ok) {
81 throw new Error("Facilitator call failed: " + path + " status=" + response.status);
82 }
83
84 return (await response.json()) as TResponse;
85}
86
87export default {
88 async fetch(request: Request, env: Env): Promise<Response> {
89 const url = new URL(request.url);
90 if (url.pathname !== "/v1/fx/quote") return new Response("Not found", { status: 404 });
91
92 const paymentRequirements = buildPaymentRequirements(url, env.SELLER_PAY_TO);
93 const signatureHeader = request.headers.get("PAYMENT-SIGNATURE");
94
95 if (!signatureHeader) {
96 return Response.json(
97 { error: "payment_required", x402Version: 2, paymentRequirements },
98 { status: 402 },
99 );
100 }
101
102 const paymentPayload: unknown = JSON.parse(signatureHeader);
103 const paymentIdentifier = request.headers.get("X-PAYMENT-ID") ?? crypto.randomUUID();
104 const idempotencyUrl = new URL("/__payments/" + paymentIdentifier, request.url).toString();
105 const idempotencyRequest = new Request(idempotencyUrl);
106
107 // Idempotency: if this payment identifier already settled, return cached response.
108 const cached = await caches.default.match(idempotencyRequest);
109 if (cached) return cached;
110
111 const verify = await facilitatorPost<VerifyResponse>(env, "/v2/x402/verify", {
112 x402Version: 2,
113 paymentPayload,
114 paymentRequirements,
115 });
116
117 if (!verify.isValid) {
118 return Response.json(
119 { error: "invalid_payment", invalidReason: verify.invalidReason ?? "unknown" },
120 { status: 402 },
121 );
122 }
123
124 const settle = await facilitatorPost<SettleResponse>(env, "/v2/x402/settle", {
125 x402Version: 2,
126 paymentPayload,
127 paymentRequirements,
128 });
129
130 if (!settle.success) {
131 return Response.json(
132 { error: "settlement_failed", errorReason: settle.errorReason ?? "unknown" },
133 { status: 402 },
134 );
135 }
136
137 const symbol = url.searchParams.get("symbol") ?? "EURUSD";
138 const response = Response.json({
139 symbol,
140 tier: paymentRequirements.extra.tier,
141 data: {
142 bid: 1.0832,
143 ask: 1.0834,
144 spreadBps: 1.8,
145 timestamp: new Date().toISOString(),
146 },
147 payment: {
148 payer: settle.payer,
149 transaction: settle.transaction,
150 network: settle.network,
151 amount: paymentRequirements.maxAmountRequired,
152 asset: paymentRequirements.asset,
153 },
154 });
155
156 response.headers.set("X-PAYMENT-ID", paymentIdentifier);
157 await caches.default.put(idempotencyRequest, response.clone());
158 return response;
159 },
160};FAQ
Copy-ready prompts
Paste these directly into Cursor or your preferred coding agent to implement NetDollar integration. Each prompt is shown in a read-only textarea and can be copied with one click.