# Dynamic pricing with x402 Source: https://docs.payai.network/guides/dynamic-pricing-x402 x402 dynamic pricing: per-request and tier-based pricing for paid APIs. Charge by tier, headers, or request data. TypeScript (Express, Hono, Next.js), Python (Flask, FastAPI), Go (Gin). # How to Charge Different Prices for the Same Endpoint with x402 A common question from developers building paid APIs with [x402](/x402/introduction) is: **how do I charge different prices for the same endpoint based on some criteria?** With **x402 SDK v2**, you can do exactly that by using a **custom function** for the price (and optionally for the payment recipient) instead of a fixed value. The middleware calls your function on each request and uses the returned price when returning the `402 Payment Required` response and when verifying and settling the payment. This article shows how to implement **dynamic pricing** in every supported stack: TypeScript (Next.js, Express, Hono, and by hand), Python (Flask, FastAPI, and by hand), and Go (Gin and by hand). *** ## The core concept: the pricing function What makes dynamic pricing possible in x402 is that **price** (and optionally **payTo**) can be a **function of the request** instead of a static value. The server middleware invokes your function on every request and uses the returned price when it sends the `402 Payment Required` response and when it verifies and settles the payment. The same idea applies across TypeScript and Python; Go uses a manual approach. ### Function signature (TypeScript and Python) In both TypeScript and Python, the contract is the same: * **Input:** A **request context** object that gives you access to the incoming HTTP request (headers, query params, path, method, and optionally body). * **Output:** A **price** for that request—typically a string like `"$0.01"` or a scheme-specific price object (e.g. `AssetAmount` in Python for custom tokens). Optionally, **payTo** (the recipient address) can also be a function of the same context, so you can route payments differently per request (e.g. by tenant or marketplace split). | Language | Price function signature | Context type / adapter | | -------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- | | **TypeScript** | `(context: HTTPRequestContext) => Price` or `Promise` | `context.adapter`: `getHeader(name)`, `getQueryParam(name)`, `getMethod()`, `getPath()`, `getUrl()`, optional `getBody()`. | | **Python** | Callable that takes `HTTPRequestContext` and returns a price (e.g. `str` or `AssetAmount`) | `context.adapter`: `get_header(name)`, `get_query_param(name)`, plus method/path/URL. Sync for Flask; sync or async for FastAPI. | | **Go** | No built-in pricing function. You compute the price in your handler (e.g. from query or header), then build the route config or payment requirements for that request yourself before calling the x402 middleware or facilitator. | N/A — you read the request (e.g. `c.Query("tier")`, `c.GetHeader("X-Tier")`) and pass the result into your config. | Where you plug this in depends on the framework: in **TypeScript** you pass the function as `accepts[].price` (and optionally `accepts[].payTo`) in your route config for Express, Hono, or Next.js. In **Python** you pass it as `PaymentOption(..., price=get_tier_price, ...)` in your `RouteConfig`. The middleware then calls your function when it needs to build the payment requirements for that request. Once you see that, the rest of this article is just framework-specific wiring. *** ## Use case: tier-based pricing We'll use one scenario for all examples so you can compare approaches: * **Endpoint:** `GET /api/insight` (or the framework's equivalent). * **Behavior:** The client sends a tier via query parameter or header: `basic`, `plus`, or `pro`. * **Prices:** * `basic` → \$0.01 * `plus` → \$0.05 * `pro` → \$0.10 If no tier is provided, we default to `basic`. The same pattern works for other criteria (e.g. authenticated user, plan, or custom header). *** ## TypeScript ### Next.js Use `withX402` for API routes so payment is settled only after a successful response. The route config can use a **function** for `price` (and `payTo` if needed). The function receives a request context with `adapter` (headers, query, body, path, method). Example: `app/api/insight/route.ts` (or under your `api` directory): ```typescript theme={null} import { NextRequest, NextResponse } from "next/server"; import { withX402 } from "@x402/next"; import { server, paywall, evmAddress, svmAddress } from "../../../proxy"; const TIER_PRICES: Record = { basic: "$0.01", plus: "$0.05", pro: "$0.10", }; function getTierPrice(context: { adapter: { getQueryParam?: (name: string) => string | string[] | undefined; getHeader?: (name: string) => string | undefined } }) { const raw = context.adapter.getQueryParam?.("tier") ?? context.adapter.getHeader?.("x-tier"); const tier = Array.isArray(raw) ? raw[0] : raw; return TIER_PRICES[tier ?? "basic"] ?? TIER_PRICES.basic; } const handler = async (_: NextRequest) => { return NextResponse.json({ insight: "Your tier-based insight here." }); }; export const GET = withX402( handler, { accepts: [ { scheme: "exact", price: getTierPrice, network: "eip155:84532", payTo: evmAddress, }, { scheme: "exact", price: getTierPrice, network: "solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1", payTo: svmAddress, }, ], description: "Tier-based insight (basic / plus / pro)", mimeType: "application/json", }, server, undefined, paywall ); ``` Your `proxy.ts` (or shared server setup) should export `server`, `paywall`, and `evmAddress` / `svmAddress` as in the [Next.js x402 example](https://github.com/x402-foundation/x402/tree/main/examples/typescript/fullstack/next). *** ### Express Use `paymentMiddleware` with a route config where `accepts[].price` is a function. The middleware passes a context whose `adapter` exposes the request (e.g. `getQueryParam`, `getHeader`). ```typescript theme={null} import { config } from "dotenv"; import express from "express"; import { paymentMiddleware, x402ResourceServer } from "@x402/express"; import { ExactEvmScheme } from "@x402/evm/exact/server"; import { ExactSvmScheme } from "@x402/svm/exact/server"; import { HTTPFacilitatorClient } from "@x402/core/server"; import { facilitator } from "@payai/facilitator"; config(); const evmAddress = process.env.EVM_ADDRESS as `0x${string}`; const svmAddress = process.env.SVM_ADDRESS; if (!evmAddress || !svmAddress) { console.error("Missing required environment variables"); process.exit(1); } const facilitatorClient = new HTTPFacilitatorClient(facilitator); const resourceServer = new x402ResourceServer(facilitatorClient) .register("eip155:84532", new ExactEvmScheme()) .register("solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1", new ExactSvmScheme()); const TIER_PRICES: Record = { basic: "$0.01", plus: "$0.05", pro: "$0.10", }; function getTierPrice(context: { adapter: { getQueryParam?: (name: string) => string | string[] | undefined; getHeader?: (name: string) => string | undefined } }) { const raw = context.adapter.getQueryParam?.("tier") ?? context.adapter.getHeader?.("tier"); const tier = Array.isArray(raw) ? raw[0] : raw; return TIER_PRICES[tier ?? "basic"] ?? TIER_PRICES.basic; } const app = express(); app.use( paymentMiddleware( { "GET /api/insight": { accepts: [ { scheme: "exact", price: getTierPrice, network: "eip155:84532", payTo: evmAddress, }, { scheme: "exact", price: getTierPrice, network: "solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1", payTo: svmAddress, }, ], description: "Tier-based insight (basic / plus / pro)", mimeType: "application/json", }, }, resourceServer ) ); app.get("/api/insight", (req, res) => { res.json({ insight: "Your tier-based insight here." }); }); app.listen(4021, () => { console.log("Server listening at http://localhost:4021"); }); ``` *** ### Hono Use `paymentMiddleware` from `@x402/hono` with a route config where `accepts[].price` is a function. The middleware passes a context whose `adapter` exposes the request (e.g. `getQueryParam`, `getHeader`). Attach the middleware with `app.use()`, then define your route; run the server with `serve({ fetch: app.fetch, port })`. ```typescript theme={null} import { config } from "dotenv"; import { Hono } from "hono"; import { serve } from "@hono/node-server"; import { paymentMiddleware, x402ResourceServer } from "@x402/hono"; import { ExactEvmScheme } from "@x402/evm/exact/server"; import { ExactSvmScheme } from "@x402/svm/exact/server"; import { HTTPFacilitatorClient } from "@x402/core/server"; import { facilitator } from "@payai/facilitator"; config(); const evmAddress = process.env.EVM_ADDRESS as `0x${string}`; const svmAddress = process.env.SVM_ADDRESS; if (!evmAddress || !svmAddress) { console.error("Missing required environment variables"); process.exit(1); } const facilitatorClient = new HTTPFacilitatorClient(facilitator); const resourceServer = new x402ResourceServer(facilitatorClient) .register("eip155:84532", new ExactEvmScheme()) .register("solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1", new ExactSvmScheme()); const TIER_PRICES: Record = { basic: "$0.01", plus: "$0.05", pro: "$0.10", }; function getTierPrice(context: { adapter: { getQueryParam?: (name: string) => string | string[] | undefined; getHeader?: (name: string) => string | undefined } }) { const raw = context.adapter.getQueryParam?.("tier") ?? context.adapter.getHeader?.("tier"); const tier = Array.isArray(raw) ? raw[0] : raw; return TIER_PRICES[tier ?? "basic"] ?? TIER_PRICES.basic; } const app = new Hono(); app.use( paymentMiddleware( { "GET /api/insight": { accepts: [ { scheme: "exact", price: getTierPrice, network: "eip155:84532", payTo: evmAddress, }, { scheme: "exact", price: getTierPrice, network: "solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1", payTo: svmAddress, }, ], description: "Tier-based insight (basic / plus / pro)", mimeType: "application/json", }, }, resourceServer ) ); app.get("/api/insight", (c) => c.json({ insight: "Your tier-based insight here." })); serve({ fetch: app.fetch, port: 4021 }); console.log("Server listening at http://localhost:4021"); ``` *** ### Custom (by hand) The important part is that `RouteConfig.accepts[].price` (and optionally `payTo`) can be a **function** `(context: HTTPRequestContext) => Price | Promise`. The HTTP resource server calls it when building the payment requirements for that request. "By hand" you wire the same `x402HTTPResourceServer` and dynamic route config into your own HTTP stack instead of using Express, Hono, or Next.js. Without one of those frameworks, you can still use the same **dynamic price** support from the SDK: 1. Create an `x402ResourceServer` and register schemes (EVM, SVM, etc.). 2. Create an `x402HTTPResourceServer` from `@x402/core/server` with a **single route** whose `accepts[].price` is your `getTierPrice` function (and optionally `payTo` as a function). 3. Use `paymentMiddlewareFromHTTPServer(httpServer)` from `@x402/express` (or the Hono equivalent) to plug that HTTP server into any stack that can run the middleware, **or** drive it yourself: on each request, build an `HTTPRequestContext` with an adapter that implements `getHeader`, `getMethod`, `getPath`, `getUrl`, and optionally `getQueryParam` / `getBody`, then call `httpServer.processHTTPRequest(context)` and handle the result (402, payment-verified, or no-payment-required). 4. If the result is payment-verified, run your handler, then call `httpServer.processSettlement(...)` with the captured payload and requirements. *** ## Python ### Flask Use `payment_middleware` with a `RouteConfig` whose `PaymentOption.price` is a **callable** that receives `HTTPRequestContext`. The context has `adapter` (e.g. `get_query_param`, `get_header`). Use the sync server and sync callables for Flask. ```python theme={null} import os from dotenv import load_dotenv from flask import Flask, jsonify from x402.http import FacilitatorConfig, HTTPFacilitatorClientSync, PaymentOption from x402.http.middleware.flask import payment_middleware from x402.http.types import HTTPRequestContext, RouteConfig from x402.mechanisms.evm.exact import ExactEvmServerScheme from x402.mechanisms.svm.exact import ExactSvmServerScheme from x402.schemas import Network from x402.server import x402ResourceServerSync load_dotenv() EVM_ADDRESS = os.getenv("EVM_ADDRESS") SVM_ADDRESS = os.getenv("SVM_ADDRESS") EVM_NETWORK: Network = "eip155:84532" SVM_NETWORK: Network = "solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1" FACILITATOR_URL = os.getenv("FACILITATOR_URL", "https://facilitator.payai.network") if not EVM_ADDRESS or not SVM_ADDRESS: raise ValueError("Missing required environment variables") app = Flask(__name__) facilitator = HTTPFacilitatorClientSync(FacilitatorConfig(url=FACILITATOR_URL)) server = x402ResourceServerSync(facilitator) server.register(EVM_NETWORK, ExactEvmServerScheme()) server.register(SVM_NETWORK, ExactSvmServerScheme()) TIER_PRICES = {"basic": "$0.01", "plus": "$0.05", "pro": "$0.10"} def get_tier_price(context: HTTPRequestContext) -> str: tier = context.adapter.get_query_param("tier") or context.adapter.get_header("x-tier") or "basic" if isinstance(tier, list): tier = tier[0] if tier else "basic" return TIER_PRICES.get(tier, TIER_PRICES["basic"]) routes = { "GET /api/insight": RouteConfig( accepts=[ PaymentOption( scheme="exact", pay_to=EVM_ADDRESS, price=get_tier_price, network=EVM_NETWORK, ), PaymentOption( scheme="exact", pay_to=SVM_ADDRESS, price=get_tier_price, network=SVM_NETWORK, ), ], mime_type="application/json", description="Tier-based insight (basic / plus / pro)", ), } payment_middleware(app, routes=routes, server=server) @app.route("/api/insight") def get_insight(): return jsonify({"insight": "Your tier-based insight here."}) if __name__ == "__main__": app.run(host="0.0.0.0", port=4021, debug=False) ``` *** ### FastAPI Use `PaymentMiddlewareASGI` from `x402.http.middleware.fastapi` with a `RouteConfig` whose `PaymentOption.price` is a **callable** that receives `HTTPRequestContext`. The context has `adapter` (e.g. `get_query_param`, `get_header`). Use the async HTTP resource server; your price callable can be sync or async—the server will await it when building payment requirements. ```python theme={null} import os from dotenv import load_dotenv from fastapi import FastAPI from x402.http import FacilitatorConfig, HTTPFacilitatorClient, PaymentOption from x402.http.middleware.fastapi import PaymentMiddlewareASGI from x402.http.types import HTTPRequestContext, RouteConfig from x402.mechanisms.evm.exact import ExactEvmServerScheme from x402.mechanisms.svm.exact import ExactSvmServerScheme from x402.schemas import Network from x402.server import x402ResourceServer load_dotenv() EVM_ADDRESS = os.getenv("EVM_ADDRESS") SVM_ADDRESS = os.getenv("SVM_ADDRESS") EVM_NETWORK: Network = "eip155:84532" SVM_NETWORK: Network = "solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1" FACILITATOR_URL = os.getenv("FACILITATOR_URL", "https://facilitator.payai.network") if not EVM_ADDRESS or not SVM_ADDRESS: raise ValueError("Missing required environment variables") app = FastAPI() facilitator = HTTPFacilitatorClient(FacilitatorConfig(url=FACILITATOR_URL)) server = x402ResourceServer(facilitator) server.register(EVM_NETWORK, ExactEvmServerScheme()) server.register(SVM_NETWORK, ExactSvmServerScheme()) TIER_PRICES = {"basic": "$0.01", "plus": "$0.05", "pro": "$0.10"} def get_tier_price(context: HTTPRequestContext) -> str: tier = context.adapter.get_query_param("tier") or context.adapter.get_header("x-tier") or "basic" if isinstance(tier, list): tier = tier[0] if tier else "basic" return TIER_PRICES.get(tier, TIER_PRICES["basic"]) routes = { "GET /api/insight": RouteConfig( accepts=[ PaymentOption( scheme="exact", pay_to=EVM_ADDRESS, price=get_tier_price, network=EVM_NETWORK, ), PaymentOption( scheme="exact", pay_to=SVM_ADDRESS, price=get_tier_price, network=SVM_NETWORK, ), ], mime_type="application/json", description="Tier-based insight (basic / plus / pro)", ), } app.add_middleware(PaymentMiddlewareASGI, routes=routes, server=server) @app.get("/api/insight") async def get_insight(): return {"insight": "Your tier-based insight here."} if __name__ == "__main__": import uvicorn uvicorn.run(app, host="0.0.0.0", port=4021) ``` *** ### Custom (by hand) The important part is that `PaymentOption.price` (and optionally `pay_to`) can be a **callable** that takes `HTTPRequestContext` and returns a price (e.g. a string like `"$0.01"` or an `AssetAmount`). The context's `adapter` exposes the request (e.g. `get_query_param`, `get_header`). The HTTP resource server calls your callable when building the payment requirements for that request. "By hand" you wire the same `x402HTTPResourceServer` (or sync variant) and dynamic route config into your own stack instead of using Flask or FastAPI middleware. Without that middleware, you can still use the same **dynamic price** support: 1. Build an `x402ResourceServer` (sync or async) and register schemes. 2. Build an `x402HTTPResourceServer` or `x402HTTPResourceServerSync` with a single route whose `PaymentOption.price` is your `get_tier_price(context)` callable. 3. For each request, wrap the request in the framework's adapter, build an `HTTPRequestContext`, and call `http_server.process_http_request(context)` (or the async variant). 4. If the result is payment-verified, run your handler, then call `process_settlement` with the returned payload and requirements. *** ## Go ### Gin The Go x402 HTTP layer currently expects **static** route configs (no function type for price in the public API). You can still implement tier-based pricing in two ways: **Option A – Multiple route patterns (static configs)**\ Register separate route patterns per tier, e.g. `GET /api/insight/basic`, `GET /api/insight/plus`, `GET /api/insight/pro`, each with its own fixed price in `RoutesConfig`. The client then calls the path that matches their tier. This is "different prices" by using different routes, not one endpoint with a dynamic function. **Option B – One route, dynamic price by hand**\ Use a single route that does **not** go through the middleware's static route table for payment. In the handler, read `tier` from the request, compute the price, build payment requirements (and optionally call the facilitator's verify/settle), and return 402 or proceed. That means you implement the 402 flow yourself for that route while still using the same facilitator and schemes elsewhere. Example sketch for **Option B** with Gin: in a handler, get the tier from query or header, map it to a price string, build a `RouteConfig`-like struct for that request, then use the Go HTTP server's low-level APIs (if exposed) to run verify/settle with that config. The [Gin example](https://github.com/x402-foundation/x402/tree/main/examples/go/servers/gin) uses static `RoutesConfig`; for true per-request price you'd call the resource server's verify/settle with requirements you build from the current request's tier. *** ### Custom (by hand) Fully by hand in Go you: 1. Parse the request (path, method, query, headers) and decide the price (e.g. from `tier`). 2. Build the payment requirements (scheme, network, payTo, **price**, etc.) for that request. 3. If there is no valid `PAYMENT-SIGNATURE` (or equivalent), respond with **402** and the `PAYMENT-REQUIRED` body/header. 4. If the client sends a payment, call the facilitator's **verify** endpoint with the payload and your requirements; if valid, run your business logic, then call **settle**, and return the resource with the settlement response. So in Go, "custom" dynamic pricing is: compute price per request, build requirements, then drive verify/settle yourself. The [x402 reference](/x402/reference) and [facilitator docs](/x402/facilitators/introduction) describe the message shapes and endpoints. *** ## Summary | Stack | How to get different prices for the same endpoint | | -------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **TS (Next/Express/Hono)** | Use a **function** for `accepts[].price` (and optionally `payTo`) in the route config. The middleware passes a request context; your function returns the price for that request. | | **Python (Flask/FastAPI)** | Use a **callable** for `PaymentOption.price` (and optionally `pay_to`) that takes `HTTPRequestContext`. Sync for Flask (sync server), sync or async for FastAPI (async server). | | **Go (Gin)** | Use multiple static routes (different paths per tier) or implement one route "by hand": compute price from the request, build requirements, then verify/settle via the facilitator. | The TypeScript and Python SDKs support **dynamic price (and payTo) functions** out of the box; in Go you achieve the same behavior by building requirements per request and using the low-level verify/settle flow. For more on x402, see the [x402 introduction](/x402/introduction), [x402 reference](/x402/reference), and server quickstarts: [Express](/x402/servers/typescript/express), [Hono](/x402/servers/typescript/hono), [Next.js](/x402/servers/typescript/nextjs), [Flask](/x402/servers/python/flask), [FastAPI](/x402/servers/python/fastapi), [Gin](/x402/servers/go/gin). ### Still looking for more? Check out the [x402 repository](https://github.com/x402-foundation/x402) for the official protocol spec, SDK source, and additional examples. # Introduction Source: https://docs.payai.network/introduction ## Welcome to PayAI PayAI is building tools and products for the future of agentic commerce, where AI Agents coordinate and transact with one another seamlessly. Our open-source technologies empower developers to create and monetize AI agents and services. This documentation provides everything you need to get started with our products. ## Tools & Products The backbone for managing and routing pay-per-use API transactions between agents. A live demo merchant for instantly testing the X402 protocol and micro-transactions. ## Need help? Have questions or want to connect with other developers? Join our Discord server. # Contributing Source: https://docs.payai.network/project-info/contributing How to contribute to the PayAI ecosystem and documentation. ## Contributing to PayAI Contributions are vital for the growth and innovation of the PayAI marketplace. We welcome contributions of any size and form, and we're excited to support community members with great ideas. ### How to Contribute 1. **Identify an Area of Interest**: Whether it's developing new tools, improving existing features, or enhancing documentation, there are numerous ways to contribute. 2. **Submit Your Contribution**: Submit your idea or contribution via [GitHub](https://github.com/PayAINetwork) issues and pull requests, or on the X community. 3. **Engage with the Community**: Engage in our X community to gather feedback, collaborate with other contributors, and refine your ideas. # Official Links Source: https://docs.payai.network/project-info/official-links Official resources, links, and contract addresses for PayAI and $PAYAI. ## Official Links * **Website**: [https://payai.network](https://payai.network) * **Terms of Service**: [https://payai.network/terms-of-service](https://payai.network/terms-of-service) * **Twitter**: [https://x.com/PayAINetwork](https://x.com/PayAINetwork) * **Telegram**: [https://t.me/PayAINetwork](https://t.me/PayAINetwork) * **Docs**: [https://docs.payai.network](https://docs.payai.network) * **Github**: [https://github.com/payainetwork](https://github.com/payainetwork) * **Partnerships Form**: [https://forms.gle/qi1eeb8X5Uu56erx6](https://forms.gle/qi1eeb8X5Uu56erx6) * **Pumpfun**: [https://pump.fun/coin/E7NgL19JbN8BhUDgWjkH8MtnbhJoaGaWJqosxZZepump](https://pump.fun/coin/E7NgL19JbN8BhUDgWjkH8MtnbhJoaGaWJqosxZZepump) * **\$PAYAI Token Address**: [PAYmo6moDF3Ro3X6bU2jwe2UdBnBhv8YjLgL1j4DxGu](https://solscan.io/account/PAYmo6moDF3Ro3X6bU2jwe2UdBnBhv8YjLgL1j4DxGu) * **\$PAYAI on Jupiter**: [https://jup.ag/swap/SOL-PAYmo6moDF3Ro3X6bU2jwe2UdBnBhv8YjLgL1j4DxGu](https://jup.ag/swap/SOL-PAYmo6moDF3Ro3X6bU2jwe2UdBnBhv8YjLgL1j4DxGu) ## PayAI Contract Addresses | **Contract** | **Solana** | | --------------------- | -------------------------------------------- | | PayAI Token | PAYmo6moDF3Ro3X6bU2jwe2UdBnBhv8YjLgL1j4DxGu | | PayAI Payment Handler | 5FhmaXvWm1FZ3bpsE5rxkey5pNWDLkvaGAzoGkTUZfZ3 | # Token Use & Legal Disclaimer Source: https://docs.payai.network/project-info/token-use-and-legal-disclaimer Intended use, risks, and legal disclaimer for the $PAYAI utility token. ## 📜 PAYAI Token Disclaimer **DISCLAIMER:**\ \$PAYAI is a utility token intended solely for use within the PayAI ecosystem. It is **not an investment vehicle**. Nothing in our documentation, website, or social media channels constitutes a solicitation or offer to buy or sell securities or investment products. The \$PAYAI token is designed to provide **platform-based functionality**, such as reducing transaction fees, boosting visibility of services or offers, and participating in future governance or arbitration mechanisms. PAYAI does **not represent ownership**, equity, dividends, or any rights to profit-sharing or claims against PayAI or its contributors. Holding \$PAYAI does **not entitle users to financial returns** or any form of compensation outside the intended utility. Users and buyers should use \$PAYAI only for its designated purposes within the PayAI platform. The team does not make guarantees or representations about the token’s market price, trading volume, or future value. PayAI is an open-source project in ongoing development. Features, functionality, and token utility are subject to change. Users assume all risk when interacting with the platform or the token. ## ⚠️ Token Use & Risk Disclosure ### 🎯 Intended Use of \$PAYAI: * Reduce platform fees when executing service contracts between AI Agents. * Boost visibility for Buyer or Seller Agent listings. * Participate in future platform governance (e.g. voting on feature proposals, agent ratings). * Pay arbitration fees in dispute resolution (future feature). \$PAYAI is **not required** to use the PayAI platform, but provides additional benefits for active participants. *** ### 🔍 Key Risks: 1. **Market Risk:**\ \$PAYAI may fluctuate in price based on external trading activity. We do not control or guarantee any secondary market outcomes. 2. **Regulatory Risk:**\ Regulations surrounding digital tokens are evolving. While PayAI is designed as a utility platform, regulators may take a different view in certain jurisdictions. 3. **Utility Risk:**\ \$PAYAI's full utility depends on platform development and adoption. If the platform fails to grow or certain features are delayed, utility may be limited. 4. **Speculation Risk:**\ PAYAI may be traded on public markets. Users are cautioned not to treat \$PAYAI as an investment or expect financial gain. 5. **Loss or Misuse:**\ As with any blockchain-based asset, loss of private keys or misuse of wallets can result in permanent loss of funds. Use secure practices at all times. *** ### ✅ Safe Use Guidelines: * \$PAYAI tokens do not ascribe any legal, financial, or other benefits except as expressly provided in our [Terms of Service](https://payai.network/terms-of-service). * Stay informed about feature updates and roadmap progress on our official channels. # Tokenomics Source: https://docs.payai.network/project-info/tokenomics Overview of $PAYAI token supply, treasury, liquidity, and unlocks. ℹ️ For legal and risk disclosures regarding \$PAYAI, [click here](./token-use-and-legal-disclaimer). ## Token Details 1,000,000,000 \$PAYAI tokens. This is the maximum token supply and cannot be changed. For the token contract address and other official links, see [Official Links](./official-links). *** 100% of tokens were liquid on launch. Treasury's intended use, subject to change: operations, marketing, and future token emissions, e.g. community rewards, partnerships, etc. ## Locks * **Token Lock (6 month rolling)**: [https://app.streamflow.finance/contract/solana/mainnet/8ga5ESLDb481fzroQrm1sfTQxALrWBreL8fLNQ9Rcids](https://app.streamflow.finance/contract/solana/mainnet/8ga5ESLDb481fzroQrm1sfTQxALrWBreL8fLNQ9Rcids) * **Token Lock (2 month rolling)**: [https://lock.jup.ag/escrow/BWyb5cPsFodrJV9ka4CnvgUvH2EZHAbtof8VE648VdgJ](https://lock.jup.ag/escrow/BWyb5cPsFodrJV9ka4CnvgUvH2EZHAbtof8VE648VdgJ) * **Raydium LP Lock**: [https://app.streamflow.finance/token-dashboard/solana/mainnet/2CSKrToHnkFRWoGv1fnGE7uoAL7zqjEW3BgiCZbxXMT8?type=lock](https://app.streamflow.finance/token-dashboard/solana/mainnet/2CSKrToHnkFRWoGv1fnGE7uoAL7zqjEW3BgiCZbxXMT8?type=lock) # Getting started Source: https://docs.payai.network/x402-echo/getting-started # Getting Started with X402 Echo Merchant Use the Echo Merchant to validate your x402 client in minutes—no server to deploy. Visit the Echo Merchant Website to test your client against a live x402-powered merchant. ## What you need * An x402 client implementation (see examples below) * A private key suitable for the network you’re testing ## Step 1 — Pick a client example Start from one of the reference clients and run it locally: * TypeScript: Axios, Fetch * Python: httpx, requests These examples already implement the x402 flow (handling `402`, constructing the Payment Payload, retrying with `PAYMENT-SIGNATURE`, and decoding `PAYMENT-RESPONSE`). ## Step 2 — Point your client at the Echo Merchant Visit the [Echo Merchant Website](https://x402.payai.network) to see the supported networks and endpoints for the Echo Merchant. For example: ``` https://x402.payai.network/api/solana-devnet/paid-content https://x402.payai.network/api/solana-mainnet/paid-content https://x402.payai.network/api/base/paid-content https://x402.payai.network/api/base-sepolia/paid-content https://x402.payai.network/api/skale-base/paid-content https://x402.payai.network/api/skale-base-spolia/paid-content ``` ## Step 3 — Run and observe Run the client. You should see: 1. Initial `402 Payment Required` response with `PAYMENT-REQUIRED` header 2. A retry with `PAYMENT-SIGNATURE` header containing your payment 3. A `200 OK` response with `PAYMENT-RESPONSE` header containing settlement details The tokens you sent are refunded automatically and PayAI covers the fees. # Introduction Source: https://docs.payai.network/x402-echo/introduction # Introduction to X402 Echo Merchant The x402 Echo Merchant is a free, live x402-powered merchant you can use to test any x402 client end‑to‑end. It behaves like a real merchant: it advertises payment requirements, verifies your payment, settles it, and fulfills the request. The only difference is economic: every token you send is refunded immediately and PayAI covers the network fees. This gives you production‑like behavior with zero cost. ## At a glance * **Live URL**: [https://x402.payai.network](https://x402.payai.network) * **Who it’s for**: Developers building or validating x402 clients * **What happens**: You send a payment → the request is fulfilled → your tokens are refunded → fees are paid by PayAI ## Why use it * **Fast iteration**: Validate your x402 client integration without deploying your own merchant. * **Zero cost**: All tokens are refunded and PayAI pays on‑chain fees. * **Realistic flow**: Exercise discovery, verification, retry with `PAYMENT-SIGNATURE`, and settlement just like a production merchant. * **Multi‑network coverage**: Test on the networks supported by x402 without juggling faucets or balances. ## How it fits into the x402 flow 1. Your client requests a protected resource at the Echo Merchant. 2. The Echo Merchant responds with `402 Payment Required` and payment requirements. 3. Your client constructs a Payment Payload and retries with the `PAYMENT-SIGNATURE` header. 4. The Echo Merchant verifies and settles the payment, then fulfills the request. 5. The response includes `PAYMENT-RESPONSE` (base64 JSON) you can decode for details. ## Related examples * TypeScript clients: [Axios](https://github.com/payai-network/x402/tree/main/clients/typescript/axios) and [Fetch](https://github.com/payai-network/x402/tree/main/clients/typescript/fetch) * Python clients: [httpx](https://github.com/payai-network/x402/tree/main/clients/python/httpx) and [requests](https://github.com/payai-network/x402/tree/main/clients/python/requests) Use any of these examples and simply point the base URL to the Echo Merchant to validate your implementation. # Http Source: https://docs.payai.network/x402/clients/go/http ## Getting started with Go (net/http) Make x402 payments with Go's standard `net/http` client in 2 minutes. You can find the full code for this example [here](https://github.com/x402-foundation/x402/tree/main/examples/go/clients/http). ### Step 1: Create a Go module and install dependencies ```bash theme={null} go mod init myclient go get github.com/x402-foundation/x402/go github.com/joho/godotenv ``` ### Step 2: Set your environment variables Your `.env` file should look like this: * `EVM_PRIVATE_KEY`: Hex EVM private key of the paying account * `SVM_PRIVATE_KEY`: Base58 Solana private key (optional if only using EVM) * `RESOURCE_SERVER_URL`: Base URL of the server (e.g. [http://localhost:4021](http://localhost:4021)) * `ENDPOINT_PATH`: Path to a paid endpoint (e.g. /weather) ```env theme={null} EVM_PRIVATE_KEY= SVM_PRIVATE_KEY= RESOURCE_SERVER_URL=http://localhost:4021 ENDPOINT_PATH=/weather ``` The upstream example uses `SERVER_URL` (full URL, e.g. `http://localhost:4021/weather`). You can use that instead by setting a single `SERVER_URL` and building the request URL from it. ### Step 3: Preview the client code This example loads your env, creates an x402 client with EVM and SVM schemes (using the mechanism-helper registration pattern), wraps `http.DefaultClient` with payment handling, makes a GET request, and logs the response body and payment settlement from the `PAYMENT-RESPONSE` header. ```go theme={null} package main import ( "context" "encoding/json" "fmt" "net/http" "os" "time" x402 "github.com/x402-foundation/x402/go" x402http "github.com/x402-foundation/x402/go/http" evm "github.com/x402-foundation/x402/go/mechanisms/evm/exact/client" svm "github.com/x402-foundation/x402/go/mechanisms/svm/exact/client" evmsigners "github.com/x402-foundation/x402/go/signers/evm" svmsigners "github.com/x402-foundation/x402/go/signers/svm" "github.com/joho/godotenv" ) func main() { godotenv.Load() evmPrivateKey := os.Getenv("EVM_PRIVATE_KEY") if evmPrivateKey == "" { fmt.Println("❌ EVM_PRIVATE_KEY environment variable is required") os.Exit(1) } svmPrivateKey := os.Getenv("SVM_PRIVATE_KEY") baseURL := os.Getenv("RESOURCE_SERVER_URL") if baseURL == "" { baseURL = "http://localhost:4021" } endpointPath := os.Getenv("ENDPOINT_PATH") if endpointPath == "" { endpointPath = "/weather" } url := baseURL + endpointPath // Create signers and client evmSigner, err := evmsigners.NewClientSignerFromPrivateKey(evmPrivateKey) if err != nil { fmt.Printf("❌ EVM signer: %v\n", err) os.Exit(1) } client := x402.Newx402Client() // nil = no JSON-RPC config; pass one to enable onchain reads for // gas-sponsoring extensions. client.Register("eip155:*", evm.NewExactEvmScheme(evmSigner, nil)) if svmPrivateKey != "" { svmSigner, err := svmsigners.NewClientSignerFromPrivateKey(svmPrivateKey) if err != nil { fmt.Printf("❌ SVM signer: %v\n", err) os.Exit(1) } client.Register("solana:*", svm.NewExactSvmScheme(svmSigner)) } // Wrap HTTP client with x402 payment handling httpClient := x402http.Newx402HTTPClient(client) wrappedClient := x402http.WrapHTTPClientWithPayment(http.DefaultClient, httpClient) fmt.Printf("Making request to: %s\n\n", url) ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) defer cancel() req, err := http.NewRequestWithContext(ctx, "GET", url, nil) if err != nil { fmt.Printf("❌ %v\n", err) os.Exit(1) } resp, err := wrappedClient.Do(req) if err != nil { fmt.Printf("❌ %v\n", err) os.Exit(1) } defer resp.Body.Close() var body interface{} if err := json.NewDecoder(resp.Body).Decode(&body); err != nil { fmt.Printf("❌ decode: %v\n", err) os.Exit(1) } out, _ := json.MarshalIndent(body, "", " ") fmt.Println("Response body:", string(out)) if resp.StatusCode < 400 { paymentHeader := resp.Header.Get("PAYMENT-RESPONSE") if paymentHeader == "" { paymentHeader = resp.Header.Get("X-PAYMENT-RESPONSE") } if paymentHeader != "" { fmt.Println("\nPayment response header present (decode base64 JSON for details)") } } } ``` For a full runnable example with payment-response decoding and both builder-pattern and mechanism-helper registration, see the [upstream Go HTTP client](https://github.com/x402-foundation/x402/tree/main/examples/go/clients/http) (`main.go`, `utils.go`, `mechanism_helper_registration.go`, `builder_pattern.go`). ### Step 4: Run the client ```bash theme={null} go run . ``` Your client is now making x402 payments! ### Step 5: Test the client You can test your client against a local server by running the [Gin example](/x402/servers/go/gin), or the [Express](/x402/servers/typescript/express), [Hono](/x402/servers/typescript/hono), or [Next.js](/x402/servers/typescript/nextjs) examples. You can also test your client against a live merchant for free. You will receive a full refund of any tokens that you send, and PayAI will pay for the network fees. ## x402 reference For a deeper dive into message shapes, headers, verification and settlement responses, see the [x402 Reference](/x402/reference). ## Need help? Have questions or want to connect with other developers? Join our Discord server. # Client Introduction Source: https://docs.payai.network/x402/clients/introduction Learn how to support x402 in your client ## Why use x402 with your client x402 standardizes how clients discover payment requirements, construct payment payloads, and complete on-chain payments for HTTP resources. Benefits include: ✅ Customers don't pay network fees.\ ✅ Payment settles in \< 1 second.\ ✅ Universal compatibility -- if it speaks HTTP, it speaks x402. ## Architecture at a glance x402 sequence diagram * **Client (buyer)**: Calls protected resources and constructs payment payloads. * **Server**: Advertises payment requirements, verifies/settles payments, fulfills requests. * **Facilitator**: Verifies and/or settles payments for the resource server. * **Blockchains**: Execute and confirm payments. ## It's that easy Make x402 payments with just a few lines: ```typescript theme={null} import { wrapFetchWithPayment } from "x402-fetch"; const fetchWithPayment = wrapFetchWithPayment(fetch, account); const response = await fetchWithPayment("https://api.example.com/premium"); ``` ```typescript theme={null} import { withPaymentInterceptor } from "x402-axios"; const api = withPaymentInterceptor(axios.create({ baseURL }), account); const response = await api.get("/premium"); ``` ```python theme={null} from x402.clients.httpx import x402HttpxClient async with x402HttpxClient(account=account, base_url=base_url) as client: response = await client.get("/premium") ``` ## Getting started Select one of the quickstart examples, or read the [reference](/x402/reference) for more details. Quickstart for building an x402 client with Axios. Quickstart for building an x402 client with Fetch. Quickstart for building an x402 client with httpx. Quickstart for building an x402 client with requests. ## x402 reference For a deeper dive into message shapes, headers, verification and settlement responses, see the x402 Reference. ## Need help? Have questions or want to connect with other developers? Join our Discord server. # Httpx Source: https://docs.payai.network/x402/clients/python/httpx ## Getting started with httpx Make x402 payments with an httpx client in 2 minutes. You can find the full code for this example [here](https://github.com/x402-foundation/x402/tree/main/examples/python/clients/httpx). ### Step 1: Install dependencies ```bash theme={null} pip install 'x402[evm,svm]' 'solana<0.40' httpx eth-account python-dotenv ``` `solana<0.40` is a temporary pin. `x402[svm]` requires `solana>=0.36.0` with no upper bound, but `solana` 0.40 removed `solana.rpc.api` and `rpc.types.TxOpts`, which `x402`'s SVM signer still imports — so an unpinned install resolves to a version the SDK cannot use. A fix is proposed upstream in [x402-foundation/x402#3071](https://github.com/x402-foundation/x402/pull/3071) — drop the pin once it ships in an `x402` release. The `[evm,svm]` extras pull in the EVM and SVM payment mechanisms used below. Without them, the `x402.mechanisms.evm` / `x402.mechanisms.svm` imports fail. Install only the extra you need (e.g. `'x402[svm]'`) if you're paying on a single network. The quotes keep shells like zsh from expanding the brackets. ### Step 2: Set your environment variables Your `.env` file should look like this: * `EVM_PRIVATE_KEY`: Hex EVM private key of the paying account (optional if SVM provided) * `SVM_PRIVATE_KEY`: Base58 Solana private key of the paying account (optional if EVM provided) * `RESOURCE_SERVER_URL`: Base URL of the server to call (e.g. [http://localhost:4021](http://localhost:4021)) * `ENDPOINT_PATH`: Path to a paid endpoint (e.g. /weather) ```env theme={null} EVM_PRIVATE_KEY= SVM_PRIVATE_KEY= RESOURCE_SERVER_URL=http://localhost:4021 ENDPOINT_PATH=/weather ``` At least one of `EVM_PRIVATE_KEY` or `SVM_PRIVATE_KEY` is required. ### Step 3: Create the client script This example loads your env, creates an x402 client, registers the EVM and/or SVM payment schemes, uses `x402HttpxClient` as an async context manager to make a GET request, and logs the response body and payment settlement from the response headers. ```python theme={null} """x402 httpx client example - async HTTP with automatic payment handling.""" import asyncio import os import sys from dotenv import load_dotenv from eth_account import Account from x402 import x402Client from x402.http import x402HTTPClient from x402.http.clients import x402HttpxClient from x402.mechanisms.evm import EthAccountSigner from x402.mechanisms.evm.exact.register import register_exact_evm_client from x402.mechanisms.svm import KeypairSigner from x402.mechanisms.svm.exact.register import register_exact_svm_client # Load environment variables load_dotenv() def validate_environment() -> tuple[str | None, str | None, str, str]: """Validate required environment variables. Returns: Tuple of (evm_private_key, svm_private_key, base_url, endpoint_path). Raises: SystemExit: If required environment variables are missing. """ evm_private_key = os.getenv("EVM_PRIVATE_KEY") svm_private_key = os.getenv("SVM_PRIVATE_KEY") base_url = os.getenv("RESOURCE_SERVER_URL") endpoint_path = os.getenv("ENDPOINT_PATH") missing = [] if not evm_private_key and not svm_private_key: missing.append("EVM_PRIVATE_KEY or SVM_PRIVATE_KEY") if not base_url: missing.append("RESOURCE_SERVER_URL") if not endpoint_path: missing.append("ENDPOINT_PATH") if missing: print(f"Error: Missing required environment variables: {', '.join(missing)}") print("Please copy .env-local to .env and fill in the values.") sys.exit(1) return evm_private_key, svm_private_key, base_url, endpoint_path async def main() -> None: """Main entry point demonstrating httpx with x402 payments.""" # Validate environment evm_private_key, svm_private_key, base_url, endpoint_path = validate_environment() # Create x402 client client = x402Client() # Register EVM payment scheme if private key provided if evm_private_key: account = Account.from_key(evm_private_key) register_exact_evm_client(client, EthAccountSigner(account)) print(f"Initialized EVM account: {account.address}") # Register SVM payment scheme if private key provided if svm_private_key: svm_signer = KeypairSigner.from_base58(svm_private_key) register_exact_svm_client(client, svm_signer) print(f"Initialized SVM account: {svm_signer.address}") # Create HTTP client helper for payment response extraction http_client = x402HTTPClient(client) # Build full URL url = f"{base_url}{endpoint_path}" print(f"Making request to: {url}\n") # Make request using async context manager async with x402HttpxClient(client) as http: response = await http.get(url) await response.aread() print(f"Response status: {response.status_code}") print(f"Response body: {response.text}") # Extract and print payment response if present if response.is_success: try: settle_response = http_client.get_payment_settle_response( lambda name: response.headers.get(name) ) print( f"\nPayment response: {settle_response.model_dump_json(indent=2)}" ) except ValueError: print("\nNo payment response header found") else: print(f"\nRequest failed (status: {response.status_code})") if __name__ == "__main__": asyncio.run(main()) ``` ### Step 4: Run the script ```bash theme={null} python main.py ``` Your client is now making x402 payments! ### Step 5: Test the client You can test payments against a local server by running the [fastapi example](https://github.com/x402-foundation/x402/tree/main/examples/python/servers/fastapi) or the [flask example](https://github.com/x402-foundation/x402/tree/main/examples/python/servers/flask) from the x402 repository. Just set your environment variables to match your local server, install the dependencies, and run the examples. You can also test your client against PayAI's [live Echo Merchant](https://x402.payai.network) for free. You will receive a full refund of any tokens that you send, and PayAI will pay for the network fees. ## x402 reference For a deeper dive into message shapes, headers, verification and settlement responses, see the [x402 Reference](/x402/reference). ## Need help? Have questions or want to connect with other developers? Join our Discord server. # Manual Flow Source: https://docs.payai.network/x402/clients/python/manual-flow Step-by-step guide to implementing the x402 payment protocol manually in Python without SDK wrappers. ## Manual x402 client flow (Python) This page shows how to perform the **entire x402 flow by hand** in Python: no starter kit, no `x402.http.clients` wrappers. You send raw HTTP requests, decode the 402 response, build the payment payload, and resend with the payment signature. For production you'll usually use the [httpx](/x402/clients/python/httpx) or [requests](/x402/clients/python/requests) quickstarts; this tutorial is for agents or environments that need a minimal, dependency-light implementation or want to understand the protocol step by step. *** ## 1. Request the resource Send a normal GET (or other method) to the protected URL. No special headers yet. ```python theme={null} import requests url = "http://localhost:4021/weather" response = requests.get(url) ``` *** ## 2. Handle 402 and decode PAYMENT-REQUIRED If the server requires payment, it returns **402 Payment Required** and puts the payment options in the **PAYMENT-REQUIRED** header (base64-encoded JSON). ```python theme={null} import base64 import json if response.status_code != 402: # 200: already paid or free; 4xx/5xx: handle as needed print(response.json()) payment_required_b64 = response.headers.get("PAYMENT-REQUIRED") if not payment_required_b64: raise ValueError("402 without PAYMENT-REQUIRED header") payment_required = json.loads( base64.b64decode(payment_required_b64).decode("utf-8") ) # payment_required has: x402Version, error, resource, accepts, extensions # accepts is a list of payment options (scheme, network, amount, asset, payTo, etc.) ``` Exact field names and shapes are in the [x402 Reference](/x402/reference). You must use **x402Version: 2** and the **accepts** array. *** ## 3. Choose an accepted option Pick one entry from `payment_required["accepts"]` by network or scheme. For **EVM** (steps below), take an option whose `network` starts with `eip155:`. For **Solana**, take one whose `network` starts with `solana:` (see [Solana (exact scheme)](#solana-exact-scheme) in step 4). ```python theme={null} accepts = payment_required["accepts"] # EVM path: accepted = next((a for a in accepts if a["network"].startswith("eip155:")), None) if not accepted: raise ValueError("No EVM accept option") ``` *** ## 4. Build the payment payload For the **exact** scheme on **EVM**, the client must produce an EIP-3009-style authorization and sign it with EIP-712 (see [x402 Reference](/x402/reference)). The payload is sent in the **PAYMENT-SIGNATURE** header as base64-encoded JSON. You need a signer (e.g. `eth_account` or `web3`) to produce the signature and authorization fields. Example shape (simplified; real code must use correct domain, types, and signing): ```python theme={null} import os import time # You need: payer private key, accepted requirement, token contract (asset), # valid_after, valid_before, nonce, and EIP-712 signing. payload = { "x402Version": 2, "scheme": "exact", "network": accepted["network"], "accepted": { "scheme": accepted["scheme"], "network": accepted["network"], "amount": accepted["amount"], "asset": accepted["asset"], "payTo": accepted["payTo"], "maxTimeoutSeconds": accepted["maxTimeoutSeconds"], **({"extra": accepted["extra"]} if accepted.get("extra") else {}), }, "payload": { "signature": "0x...", # EIP-712 signature "authorization": { "from": payer_address, # Replace with your wallet address "to": accepted["payTo"], "value": accepted["amount"], "validAfter": "0", "validBefore": str(int(time.time()) + 300), "nonce": "0x" + os.urandom(32).hex(), }, }, "extensions": {}, } payment_signature_b64 = base64.b64encode(json.dumps(payload).encode()).decode() ``` #### Solana (exact scheme) On **Solana**, you choose a Solana accept option (e.g. `network` starting with `solana:`), then build a **partially-signed transaction** and send it in the payload as base64. The transaction must contain these instructions **in this order**: 1. **SetComputeUnitLimit** — max **40,000** compute units 2. **SetComputeUnitPrice** — max **5** microlamports per compute unit 3. **TransferChecked** — token transfer (amount, mint, decimals, source, destination) 4. *Optional:* Lighthouse (wallets like Phantom/Solflare may add up to two; merchants do not add these) You need a Solana SDK (e.g. `solders` or `solana-py`) to build and sign the transaction. The payment payload is JSON with the transaction in `payload.transaction` as base64: ```python theme={null} # 3. Choose Solana option instead of EVM accepted = next((a for a in accepts if a["network"].startswith("solana:")), None) if not accepted: raise ValueError("No Solana accept option") # 4. Build Solana transaction (pseudocode — use solders/solana-py in practice) # - Add SetComputeUnitLimit(40_000) # - Add SetComputeUnitPrice(5) # microlamports # - Add TransferChecked: amount=accepted["amount"], mint=accepted["asset"], # decimals from token metadata, source=payer_token_account, destination=payee_token_account # - Sign with payer keypair (partial sign; facilitator may add Lighthouse instructions) # - Serialize transaction to bytes, then base64 # transaction_bytes = serialized_signed_tx # from your Solana library transaction_b64 = base64.b64encode(transaction_bytes).decode() payload = { "x402Version": 2, "scheme": "exact", "network": accepted["network"], "accepted": { "scheme": accepted["scheme"], "network": accepted["network"], "amount": accepted["amount"], "asset": accepted["asset"], "payTo": accepted["payTo"], "maxTimeoutSeconds": accepted["maxTimeoutSeconds"], **({"extra": accepted["extra"]} if accepted.get("extra") else {}), }, "payload": { "transaction": transaction_b64, }, "extensions": {}, } payment_signature_b64 = base64.b64encode(json.dumps(payload).encode()).decode() ``` Exact instruction formats, token-account derivation, and limits are in the [x402 Reference (§6.2 Solana)](/x402/reference). *** ## 5. Resend the request with PAYMENT-SIGNATURE Send the **same** request again (same URL and method), this time adding the **PAYMENT-SIGNATURE** header. ```python theme={null} retry_response = requests.get( url, headers={"PAYMENT-SIGNATURE": payment_signature_b64}, ) ``` *** ## 6. Parse the response and PAYMENT-RESPONSE On success the server returns **200** and may include **PAYMENT-RESPONSE** (base64-encoded settlement details). Decode the header and parse the JSON to get settlement info. ```python theme={null} retry_response.raise_for_status() body = retry_response.json() payment_response_b64 = retry_response.headers.get("PAYMENT-RESPONSE") if payment_response_b64: payment_response = json.loads( base64.b64decode(payment_response_b64).decode("utf-8") ) # success, transaction, network, payer print("Settlement:", payment_response) print(body) ``` **Decoded PAYMENT-RESPONSE examples** On success (EVM): ```json theme={null} { "success": true, "transaction": "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef", "network": "eip155:84532", "payer": "0x857b06519E91e3A54538791bDbb0E22373e36b66" } ``` On success (Solana / SVM): ```json theme={null} { "success": true, "transaction": "AXGcLa7sqSjt7pXV4mpVRP5a77tVjokZbgx8gkQ16X8Wgg3vDkWKMh9BrPTr1f2KrDuf9nSX7FrZEAQTkJ3y5UN", "network": "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdpKuc147dw2N9d", "payer": "6KPYDyuRnpuKcm1TerUmwLd2BcaihvhF4Ccrr8beruu2" } ``` On failure (e.g. insufficient funds), the server may return a non-2xx status and/or a PAYMENT-RESPONSE header with an error. Example decoded payload: ```json theme={null} { "x402Version": 2, "error": "Payment failed: insufficient funds", "accepts": [...] } ``` *** ## Summary | Step | Action | | ---- | -------------------------------------------------------------------------------------------- | | 1 | GET (or other method) the resource URL | | 2 | If status is 402, decode **PAYMENT-REQUIRED** (base64 JSON) | | 3 | Choose one entry from **accepts** | | 4 | Build payment payload (EIP-712 sign for EVM exact scheme; or Solana tx) and base64-encode it | | 5 | Resend the same request with **PAYMENT-SIGNATURE** header | | 6 | On 200, use response body and optionally decode **PAYMENT-RESPONSE** | For exact field names, types, and facilitator usage, see the [x402 Reference](/x402/reference). For a ready-made client, use the [httpx](/x402/clients/python/httpx) or [requests](/x402/clients/python/requests) quickstarts. ## Need help? Have questions or want to connect with other developers? Join our Discord server. # Requests Source: https://docs.payai.network/x402/clients/python/requests ## Getting started with requests Make x402 payments with a requests client in 2 minutes. You can find the full code for this example [here](https://github.com/x402-foundation/x402/tree/main/examples/python/clients/requests). ### Step 1: Install dependencies ```bash theme={null} pip install 'x402[evm,svm]' 'solana<0.40' requests eth-account python-dotenv ``` `solana<0.40` is a temporary pin. `x402[svm]` requires `solana>=0.36.0` with no upper bound, but `solana` 0.40 removed `solana.rpc.api` and `rpc.types.TxOpts`, which `x402`'s SVM signer still imports — so an unpinned install resolves to a version the SDK cannot use. A fix is proposed upstream in [x402-foundation/x402#3071](https://github.com/x402-foundation/x402/pull/3071) — drop the pin once it ships in an `x402` release. The `[evm,svm]` extras pull in the EVM and SVM payment mechanisms used below. Without them, the `x402.mechanisms.evm` / `x402.mechanisms.svm` imports fail. Install only the extra you need (e.g. `'x402[svm]'`) if you're paying on a single network. The quotes keep shells like zsh from expanding the brackets. ### Step 2: Set your environment variables Your `.env` file should look like this: * `EVM_PRIVATE_KEY`: Hex EVM private key of the paying account (optional if SVM provided) * `SVM_PRIVATE_KEY`: Base58 Solana private key of the paying account (optional if EVM provided) * `RESOURCE_SERVER_URL`: Base URL of the server to call (e.g. [http://localhost:4021](http://localhost:4021)) * `ENDPOINT_PATH`: Path to a paid endpoint (e.g. /weather) ```env theme={null} EVM_PRIVATE_KEY= SVM_PRIVATE_KEY= RESOURCE_SERVER_URL=http://localhost:4021 ENDPOINT_PATH=/weather ``` At least one of `EVM_PRIVATE_KEY` or `SVM_PRIVATE_KEY` is required. ### Step 3: Create the client script This example loads your env, creates a sync x402 client (`x402ClientSync`), registers the EVM and/or SVM payment schemes, uses `x402_requests(client)` as a context manager to get a session, makes a GET request, and logs the response body and payment settlement from the response headers. ```python theme={null} """x402 requests client example - sync HTTP with automatic payment handling.""" import os import sys from dotenv import load_dotenv from eth_account import Account from x402 import x402ClientSync from x402.http import x402HTTPClientSync from x402.http.clients import x402_requests from x402.mechanisms.evm import EthAccountSigner from x402.mechanisms.evm.exact.register import register_exact_evm_client from x402.mechanisms.svm import KeypairSigner from x402.mechanisms.svm.exact.register import register_exact_svm_client # Load environment variables load_dotenv() def validate_environment() -> tuple[str | None, str | None, str, str]: """Validate required environment variables. Returns: Tuple of (evm_private_key, svm_private_key, base_url, endpoint_path). Raises: SystemExit: If required environment variables are missing. """ evm_private_key = os.getenv("EVM_PRIVATE_KEY") svm_private_key = os.getenv("SVM_PRIVATE_KEY") base_url = os.getenv("RESOURCE_SERVER_URL") endpoint_path = os.getenv("ENDPOINT_PATH") missing = [] if not evm_private_key and not svm_private_key: missing.append("EVM_PRIVATE_KEY or SVM_PRIVATE_KEY") if not base_url: missing.append("RESOURCE_SERVER_URL") if not endpoint_path: missing.append("ENDPOINT_PATH") if missing: print(f"Error: Missing required environment variables: {', '.join(missing)}") print("Please copy .env-local to .env and fill in the values.") sys.exit(1) return evm_private_key, svm_private_key, base_url, endpoint_path def main() -> None: """Main entry point demonstrating requests with x402 payments.""" # Validate environment evm_private_key, svm_private_key, base_url, endpoint_path = validate_environment() # Create x402 client (sync variant for requests) client = x402ClientSync() # Register EVM payment scheme if private key provided if evm_private_key: account = Account.from_key(evm_private_key) register_exact_evm_client(client, EthAccountSigner(account)) print(f"Initialized EVM account: {account.address}") # Register SVM payment scheme if private key provided if svm_private_key: svm_signer = KeypairSigner.from_base58(svm_private_key) register_exact_svm_client(client, svm_signer) print(f"Initialized SVM account: {svm_signer.address}") # Create HTTP client helper for payment response extraction (sync) http_client = x402HTTPClientSync(client) # Build full URL url = f"{base_url}{endpoint_path}" print(f"Making request to: {url}\n") # Make request using context manager for proper cleanup with x402_requests(client) as session: response = session.get(url) print(f"Response status: {response.status_code}") print(f"Response body: {response.text}") # Extract and print payment response if present if response.ok: try: settle_response = http_client.get_payment_settle_response( lambda name: response.headers.get(name) ) print( f"\nPayment response: {settle_response.model_dump_json(indent=2)}" ) except ValueError: print("\nNo payment response header found") else: print(f"\nRequest failed (status: {response.status_code})") if __name__ == "__main__": main() ``` ### Step 4: Run the script ```bash theme={null} python main.py ``` Your client is now making x402 payments! ### Step 5: Test the client You can test payments against a local server by running the [fastapi example](https://github.com/x402-foundation/x402/tree/main/examples/python/servers/fastapi) or the [flask example](https://github.com/x402-foundation/x402/tree/main/examples/python/servers/flask) from the x402 repository. Just set your environment variables to match your local server, install the dependencies, and run the examples. You can also test your client against PayAI's [live Echo Merchant](https://x402.payai.network) for free. You will receive a full refund of any tokens that you send, and PayAI will pay for the network fees. ## x402 reference For a deeper dive into message shapes, headers, verification and settlement responses, see the [x402 Reference](/x402/reference). ## Need help? Have questions or want to connect with other developers? Join our Discord server. # Axios Source: https://docs.payai.network/x402/clients/typescript/axios ## Getting started with Axios Make x402 payments with an Axios client in 2 minutes. You can find the full code for this example [here](https://github.com/x402-foundation/x402/tree/main/examples/typescript/clients/axios). ### Step 1: Create a project and install dependencies Use your favorite package manager: ##### npm ```bash theme={null} mkdir my-first-client && cd my-first-client npm init -y && npm pkg set type=module npm install axios dotenv viem @solana/kit @scure/base @x402/axios @x402/evm @x402/svm npm install -D typescript tsx ``` ##### pnpm ```bash theme={null} mkdir my-first-client && cd my-first-client pnpm init && pnpm pkg set type=module pnpm add axios dotenv viem @solana/kit @scure/base @x402/axios @x402/evm @x402/svm pnpm add -D typescript tsx ``` ##### bun ```bash theme={null} mkdir my-first-client && cd my-first-client bun init -y bun add axios dotenv viem @solana/kit @scure/base @x402/axios @x402/evm @x402/svm bun add -d typescript ``` This is the same dependency set as the [upstream Axios example](https://github.com/x402-foundation/x402/tree/main/examples/typescript/clients/axios). ### Step 2: Set your environment variables Create a `.env` file in the project root and set the following: * `RESOURCE_SERVER_URL`: Base URL of the server to call (e.g. [http://localhost:4021](http://localhost:4021)) * `ENDPOINT_PATH`: Path to a paid endpoint (e.g. /weather) * `EVM_PRIVATE_KEY`: Hex EVM private key of the paying account * `SVM_PRIVATE_KEY`: Base58 Solana private key of the paying account ```env theme={null} EVM_PRIVATE_KEY= SVM_PRIVATE_KEY= RESOURCE_SERVER_URL=http://localhost:4021 ENDPOINT_PATH=/weather ``` Optionally set `EVM_RPC_URL` to a JSON-RPC endpoint. When present, the client can perform onchain reads, which enables gas-sponsoring extensions: ```env theme={null} EVM_RPC_URL= ``` ### Step 3: Preview the client code Create `index.ts`: It loads your env, builds an `x402Client` and registers the exact and `upto` EVM schemes plus exact SVM, wraps Axios with `wrapAxiosWithPayment`, calls your endpoint, and prints the parsed payment result via `x402HTTPClient.parsePaymentResult`. ```ts theme={null} import { config } from "dotenv"; import { x402Client, wrapAxiosWithPayment, x402HTTPClient } from "@x402/axios"; import { ExactEvmScheme } from "@x402/evm/exact/client"; import { UptoEvmScheme } from "@x402/evm/upto/client"; import { ExactSvmScheme } from "@x402/svm/exact/client"; import { privateKeyToAccount } from "viem/accounts"; import { createKeyPairSignerFromBytes } from "@solana/kit"; import { base58 } from "@scure/base"; import axios from "axios"; config(); const evmPrivateKey = process.env.EVM_PRIVATE_KEY as `0x${string}`; const svmPrivateKey = process.env.SVM_PRIVATE_KEY as string; const evmRpcUrl = process.env.EVM_RPC_URL; const baseURL = process.env.RESOURCE_SERVER_URL || "http://localhost:4021"; const endpointPath = process.env.ENDPOINT_PATH || "/weather"; const url = `${baseURL}${endpointPath}`; /** * Example demonstrating how to use @x402/axios to make requests to x402-protected endpoints. * * Uses the builder pattern to register payment schemes directly. * * Required environment variables: * - EVM_PRIVATE_KEY: The private key of the EVM signer * - SVM_PRIVATE_KEY: The private key of the SVM signer * * Optional environment variables: * - EVM_RPC_URL: JSON-RPC endpoint for on-chain reads (enables gas sponsoring extensions) */ async function main(): Promise { const evmSigner = privateKeyToAccount(evmPrivateKey); const svmSigner = await createKeyPairSignerFromBytes(base58.decode(svmPrivateKey)); const rpcOptions = evmRpcUrl ? { rpcUrl: evmRpcUrl } : undefined; const client = new x402Client(); client.register("eip155:*", new ExactEvmScheme(evmSigner, rpcOptions)); client.register("eip155:*", new UptoEvmScheme(evmSigner, rpcOptions)); client.register("solana:*", new ExactSvmScheme(svmSigner)); const api = wrapAxiosWithPayment(axios.create(), client); const httpClient = new x402HTTPClient(client); console.log(`Making request to: ${url}\n`); const response = await api.get(url); const result = httpClient.parsePaymentResult({ status: response.status, getHeader: name => response.headers[name.toLowerCase()], body: response.data, }); console.dir(result, { depth: null }); } main().catch(error => { console.error(error?.response?.data?.error ?? error); process.exit(1); }); ``` ### Step 4: Run the client ```bash theme={null} npx tsx index.ts ``` Your client is now making x402 payments! ### Step 5: Test the client You can test your client against a local server by running the [Express example](/x402/servers/typescript/express), [Hono example](/x402/servers/typescript/hono), or [Next.js example](/x402/servers/typescript/nextjs). You can also test your client against a live merchant for free. You will receive a full refund of any tokens that you send, and PayAI will pay for the network fees. ## x402 reference For a deeper dive into message shapes, headers, verification and settlement responses, see the [x402 Reference](/x402/reference). ## Need help? Have questions or want to connect with other developers? Join our Discord server. # Fetch Source: https://docs.payai.network/x402/clients/typescript/fetch ## Getting started with Fetch Make x402 payments with a Fetch client in 2 minutes. You can find the full code for this example [here](https://github.com/x402-foundation/x402/tree/main/examples/typescript/clients/fetch). ### Step 1: Create a project and install dependencies Use your favorite package manager: ##### npm ```bash theme={null} mkdir my-first-client && cd my-first-client npm init -y && npm pkg set type=module npm install dotenv viem @solana/kit @scure/base @x402/evm @x402/fetch @x402/svm npm install -D typescript tsx ``` ##### pnpm ```bash theme={null} mkdir my-first-client && cd my-first-client pnpm init && pnpm pkg set type=module pnpm add dotenv viem @solana/kit @scure/base @x402/evm @x402/fetch @x402/svm pnpm add -D typescript tsx ``` ##### bun ```bash theme={null} mkdir my-first-client && cd my-first-client bun init -y bun add dotenv viem @solana/kit @scure/base @x402/evm @x402/fetch @x402/svm bun add -d typescript ``` This is the same dependency set as the [upstream Fetch example](https://github.com/x402-foundation/x402/tree/main/examples/typescript/clients/fetch). ### Step 2: Set your environment variables Create a `.env` file in the project root and set the following: * `EVM_PRIVATE_KEY`: Hex EVM private key of the paying account * `SVM_PRIVATE_KEY`: Base58 Solana private key of the paying account * `RESOURCE_SERVER_URL`: Base URL of the server to call (e.g. [http://localhost:4021](http://localhost:4021)) * `ENDPOINT_PATH`: Path to a paid endpoint (e.g. /weather) ```env theme={null} EVM_PRIVATE_KEY= SVM_PRIVATE_KEY= RESOURCE_SERVER_URL=http://localhost:4021 ENDPOINT_PATH=/weather ``` Optionally set `EVM_RPC_URL` to a JSON-RPC endpoint. When present, the client can perform onchain reads, which enables gas-sponsoring extensions: ```env theme={null} EVM_RPC_URL= ``` ### Step 3: Preview the client code Create `index.ts`: It loads your env, builds an `x402Client` and registers the exact and `upto` EVM schemes plus exact SVM, wraps `fetch` with `wrapFetchWithPayment`, calls your endpoint, and prints the parsed payment result via `x402HTTPClient.processResponse`. ```ts theme={null} import { config } from "dotenv"; import { x402Client, wrapFetchWithPayment, x402HTTPClient } from "@x402/fetch"; import { ExactEvmScheme } from "@x402/evm/exact/client"; import { UptoEvmScheme } from "@x402/evm/upto/client"; import { ExactSvmScheme } from "@x402/svm/exact/client"; import { privateKeyToAccount } from "viem/accounts"; import { createKeyPairSignerFromBytes } from "@solana/kit"; import { base58 } from "@scure/base"; config(); const evmPrivateKey = process.env.EVM_PRIVATE_KEY as `0x${string}`; const svmPrivateKey = process.env.SVM_PRIVATE_KEY as string; const evmRpcUrl = process.env.EVM_RPC_URL; const baseURL = process.env.RESOURCE_SERVER_URL || "http://localhost:4021"; const endpointPath = process.env.ENDPOINT_PATH || "/weather"; const url = `${baseURL}${endpointPath}`; /** * Example demonstrating how to use @x402/fetch to make requests to x402-protected endpoints. * * Uses the builder pattern to register payment schemes directly. * * Required environment variables: * - EVM_PRIVATE_KEY: The private key of the EVM signer * - SVM_PRIVATE_KEY: The private key of the SVM signer * * Optional environment variables: * - EVM_RPC_URL: JSON-RPC endpoint for onchain reads (enables gas sponsoring extensions) */ async function main(): Promise { const evmSigner = privateKeyToAccount(evmPrivateKey); const svmSigner = await createKeyPairSignerFromBytes(base58.decode(svmPrivateKey)); const rpcOptions = evmRpcUrl ? { rpcUrl: evmRpcUrl } : undefined; const client = new x402Client(); client.register("eip155:*", new ExactEvmScheme(evmSigner, rpcOptions)); client.register("eip155:*", new UptoEvmScheme(evmSigner, rpcOptions)); client.register("solana:*", new ExactSvmScheme(svmSigner)); const fetchWithPayment = wrapFetchWithPayment(fetch, client); const httpClient = new x402HTTPClient(client); console.log(`Making request to: ${url}\n`); const response = await fetchWithPayment(url, { method: "GET" }); const result = await httpClient.processResponse(response); console.dir(result, { depth: null }); } main().catch(error => { console.error(error?.response?.data?.error ?? error); process.exit(1); }); ``` ### Step 4: Run the client ```bash theme={null} npx tsx index.ts ``` Your client is now making x402 payments! ### Step 5: Test the client You can test your client against a local server by running the [Express example](/x402/servers/typescript/express), [Hono example](/x402/servers/typescript/hono), or [Next.js example](/x402/servers/typescript/nextjs). You can also test your client against a live merchant for free. You will receive a full refund of any tokens that you send, and PayAI will pay for the network fees. ## x402 reference For a deeper dive into message shapes, headers, verification and settlement responses, see the [x402 Reference](/x402/reference). ## Need help? Have questions or want to connect with other developers? Join our Discord server. # Manual Flow Source: https://docs.payai.network/x402/clients/typescript/manual-flow Step-by-step guide to implementing the x402 payment protocol manually in TypeScript without SDK wrappers. ## Manual x402 client flow (TypeScript) This page shows how to perform the **entire x402 flow by hand** in TypeScript: no starter kit, no `@x402/fetch` or `@x402/axios` wrappers. You send raw HTTP requests, decode the 402 response, build the payment payload, and resend with the payment signature. For production you'll usually use the [Fetch](/x402/clients/typescript/fetch) or [Axios](/x402/clients/typescript/axios) quickstarts; this tutorial is for agents or environments that need a minimal, dependency-light implementation or want to understand the protocol step by step. *** ## 1. Request the resource Send a normal GET (or other method) to the protected URL. No special headers yet. ```ts theme={null} const url = "http://localhost:4021/weather"; const response = await fetch(url, { method: "GET" }); ``` *** ## 2. Handle 402 and decode PAYMENT-REQUIRED If the server requires payment, it returns **402 Payment Required** and puts the payment options in the **PAYMENT-REQUIRED** header (base64-encoded JSON). ```ts theme={null} if (response.status !== 402) { // 200: already paid or free; 4xx/5xx: handle as needed console.log(await response.json()); } const paymentRequiredB64 = response.headers.get("PAYMENT-REQUIRED"); if (!paymentRequiredB64) throw new Error("402 without PAYMENT-REQUIRED header"); const paymentRequiredJson = Buffer.from(paymentRequiredB64, "base64").toString("utf-8"); const paymentRequired = JSON.parse(paymentRequiredJson) as { x402Version: number; error: string; resource: { url: string; description: string; mimeType?: string }; accepts: Array<{ scheme: string; network: string; amount: string; asset: string; payTo: string; maxTimeoutSeconds: number; extra?: Record; }>; extensions?: Record; }; ``` Exact field names and shapes are in the [x402 Reference](/x402/reference). You must use **x402Version: 2** and the **accepts** array. *** ## 3. Choose an accepted option Pick one entry from `paymentRequired.accepts` by network or scheme. For **EVM** (steps below), take an option whose `network` starts with `eip155:`. For **Solana**, take one whose `network` starts with `solana:` (see [Solana (exact scheme)](#solana-exact-scheme) in step 4). ```ts theme={null} // EVM path: const accepted = paymentRequired.accepts.find((a) => a.network.startsWith("eip155:")); if (!accepted) throw new Error("No EVM accept option"); ``` *** ## 4. Build the payment payload For the **exact** scheme on **EVM**, the client must produce an EIP-3009-style authorization and sign it with EIP-712 (see [x402 Reference](/x402/reference)). The payload is sent in the **PAYMENT-SIGNATURE** header as base64-encoded JSON. Example shape (simplified; real code must use correct domain, types, and signing): ```ts theme={null} import { privateKeyToAccount } from "viem/accounts"; import { signTypedData } from "viem/accounts"; // You need: payer private key, accepted requirement, token contract (asset), validAfter/validBefore, nonce const payload = { x402Version: 2, scheme: "exact", network: accepted.network, accepted: { scheme: accepted.scheme, network: accepted.network, amount: accepted.amount, asset: accepted.asset, payTo: accepted.payTo, maxTimeoutSeconds: accepted.maxTimeoutSeconds, ...(accepted.extra && { extra: accepted.extra }), }, payload: { signature: "0x...", // EIP-712 signature from signTypedData authorization: { from: payerAddress, // Replace with your wallet address to: accepted.payTo, value: accepted.amount, validAfter: "0", validBefore: String(Math.floor(Date.now() / 1000) + 300), nonce: "0x..." + Buffer.from(crypto.getRandomValues(new Uint8Array(32))).toString("hex"), }, }, extensions: {}, }; const paymentSignatureB64 = Buffer.from(JSON.stringify(payload)).toString("base64"); ``` #### Solana (exact scheme) On **Solana**, you choose a Solana accept option (e.g. `network` starting with `solana:`), then build a **partially-signed transaction** and send it in the payload as base64. The transaction must contain these instructions **in this order**: 1. **SetComputeUnitLimit** — max **40,000** compute units 2. **SetComputeUnitPrice** — max **5** microlamports per compute unit 3. **TransferChecked** — token transfer (amount, mint, decimals, source, destination) 4. *Optional:* Lighthouse (wallets like Phantom/Solflare may add up to two; merchants do not add these) You need a Solana SDK (e.g. `@solana/web3.js` or `@solana/spl-token`) to build and sign the transaction. Source and destination are **token accounts** (e.g. associated token accounts for the mint); `accepted.payTo` is the recipient **wallet**. Decimals may be in `accepted.extra`. The payment payload is JSON with the transaction in `payload.transaction` as base64: ```ts theme={null} // 3. Choose Solana option instead of EVM const accepted = paymentRequired.accepts.find((a) => a.network.startsWith("solana:")); if (!accepted) throw new Error("No Solana accept option"); // 4. Build Solana transaction (pseudocode — use @solana/web3.js / @solana/spl-token in practice) // - Add setComputeUnitLimit(40_000) // - Add setComputeUnitPrice(5) // microlamports // - Add createTransferCheckedInstruction: amount=accepted.amount, mint=accepted.asset, // decimals from token metadata or accepted.extra, source=payerTokenAccount, destination=payeeTokenAccount // - Sign with payer keypair (partial sign; facilitator may add Lighthouse instructions) // - Serialize transaction to buffer, then base64 // const transactionBuffer = serializedSignedTx; // from your Solana library const transactionB64 = Buffer.from(transactionBuffer).toString("base64"); const payload = { x402Version: 2, scheme: "exact", network: accepted.network, accepted: { scheme: accepted.scheme, network: accepted.network, amount: accepted.amount, asset: accepted.asset, payTo: accepted.payTo, maxTimeoutSeconds: accepted.maxTimeoutSeconds, ...(accepted.extra && { extra: accepted.extra }), }, payload: { transaction: transactionB64, }, extensions: {}, }; const paymentSignatureB64 = Buffer.from(JSON.stringify(payload)).toString("base64"); ``` Exact instruction formats, token-account derivation, and limits are in the [x402 Reference (§6.2 Solana)](/x402/reference). *** ## 5. Resend the request with PAYMENT-SIGNATURE Send the **same** request again (same URL and method), this time adding the **PAYMENT-SIGNATURE** header. ```ts theme={null} const retryResponse = await fetch(url, { method: "GET", headers: { "PAYMENT-SIGNATURE": paymentSignatureB64, }, }); ``` *** ## 6. Parse the response and PAYMENT-RESPONSE On success the server returns **200** and may include **PAYMENT-RESPONSE** (base64-encoded settlement details). Decode the header and parse the JSON to get settlement info. ```ts theme={null} if (!retryResponse.ok) { throw new Error(`Request failed: ${retryResponse.status}`); } const body = await retryResponse.json(); const paymentResponseB64 = retryResponse.headers.get("PAYMENT-RESPONSE"); if (paymentResponseB64) { const paymentResponse = JSON.parse( Buffer.from(paymentResponseB64, "base64").toString("utf-8") ) as { success: boolean; transaction?: string; network?: string; payer?: string }; console.log("Settlement:", paymentResponse); } console.log(body); ``` **Decoded PAYMENT-RESPONSE examples** On success (EVM): ```json theme={null} { "success": true, "transaction": "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef", "network": "eip155:84532", "payer": "0x857b06519E91e3A54538791bDbb0E22373e36b66" } ``` On success (Solana / SVM): ```json theme={null} { "success": true, "transaction": "AXGcLa7sqSjt7pXV4mpVRP5a77tVjokZbgx8gkQ16X8Wgg3vDkWKMh9BrPTr1f2KrDuf9nSX7FrZEAQTkJ3y5UN", "network": "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdpKuc147dw2N9d", "payer": "6KPYDyuRnpuKcm1TerUmwLd2BcaihvhF4Ccrr8beruu2" } ``` On failure (e.g. insufficient funds), the server may return a non-2xx status and/or a PAYMENT-RESPONSE header with an error. Example decoded payload: ```json theme={null} { "x402Version": 2, "error": "Payment failed: insufficient funds", "accepts": [...] } ``` *** ## Summary | Step | Action | | ---- | -------------------------------------------------------------------------------------------- | | 1 | GET (or other method) the resource URL | | 2 | If status is 402, decode **PAYMENT-REQUIRED** (base64 JSON) | | 3 | Choose one entry from **accepts** | | 4 | Build payment payload (EIP-712 sign for EVM exact scheme; or Solana tx) and base64-encode it | | 5 | Resend the same request with **PAYMENT-SIGNATURE** header | | 6 | On 200, use response body and optionally decode **PAYMENT-RESPONSE** | For exact field names, types, and facilitator usage, see the [x402 Reference](/x402/reference). For a ready-made client, use the [Fetch](/x402/clients/typescript/fetch) or [Axios](/x402/clients/typescript/axios) quickstarts. ## Need help? Have questions or want to connect with other developers? Join our Discord server. # Facilitator Authentication Source: https://docs.payai.network/x402/facilitators/authentication How to authenticate with the PayAI facilitator using your merchant API key — protocol reference and standalone examples in TypeScript, Python, Go, and Rust. ## Overview The [PayAI facilitator](https://facilitator.payai.network) authenticates merchants using short-lived **JWTs signed with Ed25519** (the EdDSA algorithm). If you use the `@payai/facilitator` TypeScript package, this is handled automatically. This guide explains the underlying protocol so you can implement authentication in **any language** without depending on PayAI packages. Authentication is only required beyond the [free tier](/x402/facilitators/pricing) (10,000 settlements/month). Create a merchant account at [merchant.payai.network](https://merchant.payai.network) to get your API keys. If you're following one of the TypeScript server guides ([Express](/x402/servers/typescript/express), [Hono](/x402/servers/typescript/hono), [Next.js](/x402/servers/typescript/nextjs)), facilitator authentication is already built in. Just set the `PAYAI_API_KEY_ID` and `PAYAI_API_KEY_SECRET` environment variables and the middleware handles the rest. Refer to those guides for setup instructions. ## API key structure Your API key has two parts, available from the [merchant dashboard](https://merchant.payai.network): | Part | Environment Variable | Description | | ------------------ | ---------------------- | ----------------------------------------------------------- | | **API Key ID** | `PAYAI_API_KEY_ID` | A string identifier for your key | | **API Key Secret** | `PAYAI_API_KEY_SECRET` | An Ed25519 private key in PKCS#8/DER format, base64-encoded | The secret may be prefixed with `payai_sk_` as shown in the dashboard. Strip this prefix before use — the remaining string is a standard base64-encoded PKCS#8 DER key. *** ## Protocol steps ### 1. Normalize the API key secret If the secret starts with `payai_sk_`, remove that prefix. The result is a base64-encoded Ed25519 private key in PKCS#8/DER format. ### 2. Build the JWT header ```json theme={null} { "alg": "EdDSA", "typ": "JWT", "kid": "" } ``` ### 3. Build the JWT payload ```json theme={null} { "sub": "", "iss": "payai-merchant", "iat": 1709700000, "exp": 1709700120, "jti": "550e8400-e29b-41d4-a716-446655440000" } ``` | Claim | Description | | ----- | ------------------------------------------------ | | `sub` | Your API Key ID | | `iss` | Always `"payai-merchant"` | | `iat` | Current time as a Unix timestamp (seconds) | | `exp` | Expiration — `iat + 120` (2 minutes recommended) | | `jti` | A random UUID v4 for replay protection | ### 4. Encode and sign 1. Base64url-encode the header JSON -> `headerB64` 2. Base64url-encode the payload JSON -> `payloadB64` 3. Form the signing input: `headerB64 + "." + payloadB64` 4. Sign the UTF-8 bytes of the signing input with your Ed25519 private key 5. Base64url-encode the 64-byte signature -> `signatureB64` 6. The JWT is: `headerB64.payloadB64.signatureB64` **Base64url encoding** uses the standard Base64 alphabet with `+` replaced by `-`, `/` replaced by `_`, and no `=` padding. ### 5. Send the request Include the JWT as a Bearer token on all facilitator requests: ``` Authorization: Bearer ``` Send this header on `POST /verify` and `POST /settle`. `GET /supported` is currently readable without a token — it returns the same catalogue either way — so authenticating it is harmless but not required. *** ## Token caching JWTs are valid for the full `exp - iat` window (default: 120 seconds). To avoid signing on every request, cache the token and refresh it \~30 seconds before expiry. *** ## Code examples The following examples implement the full authentication flow with **no PayAI-specific dependencies**. Uses the Web Crypto API — works in Node.js 18+, Deno, Bun, and browsers with zero dependencies. ```typescript theme={null} // payai-auth.ts — zero dependencies, Web Crypto API only function base64UrlEncode(data: Uint8Array): string { let binary = ""; for (let i = 0; i < data.byteLength; i++) { binary += String.fromCharCode(data[i]); } return btoa(binary) .replace(/\+/g, "-") .replace(/\//g, "_") .replace(/=+$/, ""); } function base64UrlEncodeString(str: string): string { return base64UrlEncode(new TextEncoder().encode(str)); } function base64ToUint8Array(base64: string): Uint8Array { const standardized = base64.replace(/-/g, "+").replace(/_/g, "/"); const binary = atob(standardized); const bytes = new Uint8Array(binary.length); for (let i = 0; i < binary.length; i++) { bytes[i] = binary.charCodeAt(i); } return bytes; } function normalizeSecret(secret: string): string { const trimmed = secret.trim(); return trimmed.startsWith("payai_sk_") ? trimmed.slice("payai_sk_".length) : trimmed; } export async function generatePayAIJwt( apiKeyId: string, apiKeySecret: string, ): Promise { const now = Math.floor(Date.now() / 1000); const header = JSON.stringify({ alg: "EdDSA", typ: "JWT", kid: apiKeyId, }); const payload = JSON.stringify({ sub: apiKeyId, iss: "payai-merchant", iat: now, exp: now + 120, jti: crypto.randomUUID(), }); const headerB64 = base64UrlEncodeString(header); const payloadB64 = base64UrlEncodeString(payload); const message = `${headerB64}.${payloadB64}`; const keyBytes = base64ToUint8Array(normalizeSecret(apiKeySecret)); const privateKey = await crypto.subtle.importKey( "pkcs8", keyBytes.buffer, { name: "Ed25519" }, false, ["sign"], ); const signature = await crypto.subtle.sign( "Ed25519", privateKey, new TextEncoder().encode(message), ); return `${message}.${base64UrlEncode(new Uint8Array(signature))}`; } // --- Usage --- const jwt = await generatePayAIJwt( "your-api-key-id", "payai_sk_your-api-key-secret", ); const response = await fetch( "https://facilitator.payai.network/verify", { method: "POST", headers: { "Content-Type": "application/json", Authorization: `Bearer ${jwt}`, }, body: JSON.stringify({ x402Version: 2, paymentPayload: { /* ... */ }, paymentRequirements: { /* ... */ }, }), }, ); ``` Requires the [`cryptography`](https://pypi.org/project/cryptography/) package: ```bash theme={null} pip install cryptography ``` ```python theme={null} # payai_auth.py import base64 import json import time import uuid from cryptography.hazmat.primitives.serialization import load_der_private_key def base64url_encode(data: bytes) -> str: return base64.urlsafe_b64encode(data).rstrip(b"=").decode("ascii") def normalize_secret(secret: str) -> str: s = secret.strip() return s.removeprefix("payai_sk_") def generate_payai_jwt(api_key_id: str, api_key_secret: str) -> str: now = int(time.time()) header = json.dumps( {"alg": "EdDSA", "typ": "JWT", "kid": api_key_id}, separators=(",", ":"), ) payload = json.dumps( { "sub": api_key_id, "iss": "payai-merchant", "iat": now, "exp": now + 120, "jti": str(uuid.uuid4()), }, separators=(",", ":"), ) header_b64 = base64url_encode(header.encode()) payload_b64 = base64url_encode(payload.encode()) message = f"{header_b64}.{payload_b64}" key_bytes = base64.b64decode(normalize_secret(api_key_secret)) private_key = load_der_private_key(key_bytes, password=None) signature = private_key.sign(message.encode()) return f"{message}.{base64url_encode(signature)}" # --- Usage --- if __name__ == "__main__": import requests jwt = generate_payai_jwt( "your-api-key-id", "payai_sk_your-api-key-secret", ) response = requests.post( "https://facilitator.payai.network/verify", headers={ "Content-Type": "application/json", "Authorization": f"Bearer {jwt}", }, json={ "x402Version": 2, "paymentPayload": {}, # ... "paymentRequirements": {}, # ... }, ) print(response.json()) ``` Uses the Go standard library plus `github.com/google/uuid` for UUID generation. ```go theme={null} package payaiauth import ( "crypto/ed25519" "crypto/x509" "encoding/base64" "encoding/json" "fmt" "strings" "time" "github.com/google/uuid" ) func base64URLEncode(data []byte) string { return strings.TrimRight( base64.URLEncoding.EncodeToString(data), "=", ) } func normalizeSecret(secret string) string { s := strings.TrimSpace(secret) return strings.TrimPrefix(s, "payai_sk_") } func GeneratePayAIJwt(apiKeyID, apiKeySecret string) (string, error) { now := time.Now().Unix() header, _ := json.Marshal(map[string]string{ "alg": "EdDSA", "typ": "JWT", "kid": apiKeyID, }) payload, _ := json.Marshal(map[string]interface{}{ "sub": apiKeyID, "iss": "payai-merchant", "iat": now, "exp": now + 120, "jti": uuid.New().String(), }) headerB64 := base64URLEncode(header) payloadB64 := base64URLEncode(payload) message := headerB64 + "." + payloadB64 keyBytes, err := base64.StdEncoding.DecodeString( normalizeSecret(apiKeySecret), ) if err != nil { keyBytes, err = base64.URLEncoding.DecodeString( normalizeSecret(apiKeySecret), ) if err != nil { return "", fmt.Errorf("decode API key secret: %w", err) } } parsed, err := x509.ParsePKCS8PrivateKey(keyBytes) if err != nil { return "", fmt.Errorf("parse PKCS#8 key: %w", err) } privateKey, ok := parsed.(ed25519.PrivateKey) if !ok { return "", fmt.Errorf("key is not Ed25519") } signature := ed25519.Sign(privateKey, []byte(message)) return message + "." + base64URLEncode(signature), nil } ``` Usage: ```go theme={null} package main import ( "bytes" "encoding/json" "fmt" "net/http" payaiauth "your-module/payaiauth" ) func main() { jwt, err := payaiauth.GeneratePayAIJwt( "your-api-key-id", "payai_sk_your-api-key-secret", ) if err != nil { panic(err) } body, _ := json.Marshal(map[string]interface{}{ "x402Version": 2, "paymentPayload": map[string]interface{}{}, "paymentRequirements": map[string]interface{}{}, }) req, _ := http.NewRequest( "POST", "https://facilitator.payai.network/verify", bytes.NewReader(body), ) req.Header.Set("Content-Type", "application/json") req.Header.Set("Authorization", "Bearer "+jwt) resp, err := http.DefaultClient.Do(req) if err != nil { panic(err) } defer resp.Body.Close() fmt.Println("Status:", resp.Status) } ``` You can replace `github.com/google/uuid` with `crypto/rand` to generate a UUID v4 manually and avoid the external dependency entirely. Add to `Cargo.toml`: ```toml theme={null} [dependencies] ed25519-dalek = { version = "2", features = ["pkcs8"] } base64 = "0.22" serde_json = "1" uuid = { version = "1", features = ["v4"] } ``` ```rust theme={null} use base64::engine::general_purpose::{STANDARD, URL_SAFE_NO_PAD}; use base64::Engine; use ed25519_dalek::pkcs8::DecodePrivateKey; use ed25519_dalek::{Signer, SigningKey}; use serde_json::json; use std::time::{SystemTime, UNIX_EPOCH}; use uuid::Uuid; fn normalize_secret(secret: &str) -> &str { let s = secret.trim(); s.strip_prefix("payai_sk_").unwrap_or(s) } pub fn generate_payai_jwt( api_key_id: &str, api_key_secret: &str, ) -> Result> { let now = SystemTime::now() .duration_since(UNIX_EPOCH)? .as_secs(); let header = json!({ "alg": "EdDSA", "typ": "JWT", "kid": api_key_id, }); let payload = json!({ "sub": api_key_id, "iss": "payai-merchant", "iat": now, "exp": now + 120, "jti": Uuid::new_v4().to_string(), }); let header_b64 = URL_SAFE_NO_PAD.encode( header.to_string().as_bytes(), ); let payload_b64 = URL_SAFE_NO_PAD.encode( payload.to_string().as_bytes(), ); let message = format!("{header_b64}.{payload_b64}"); let key_bytes = STANDARD.decode( normalize_secret(api_key_secret), )?; let signing_key = SigningKey::from_pkcs8_der(&key_bytes)?; let signature = signing_key.sign(message.as_bytes()); Ok(format!( "{message}.{}", URL_SAFE_NO_PAD.encode(signature.to_bytes()), )) } // Usage: // let jwt = generate_payai_jwt("your-api-key-id", "payai_sk_...")?; // Set header: Authorization: Bearer {jwt} ``` *** ## Facilitator endpoints All endpoints are at `https://facilitator.payai.network`. Note that `/verify` validates the request body before the token, so a malformed request returns `400` rather than `401` even when the token is missing or bad. | Endpoint | Method | Auth | Description | | ------------ | ------ | ----------------------------- | ----------------------------------------------------- | | `/verify` | POST | required beyond the free tier | Verify a payment payload against payment requirements | | `/settle` | POST | required beyond the free tier | Settle a verified payment on-chain | | `/supported` | GET | not required | List supported networks, tokens, and schemes | ### Request body (`/verify` and `/settle`) ```json theme={null} { "x402Version": 2, "paymentPayload": { "..." }, "paymentRequirements": { "..." } } ``` For full payload schemas, see the [x402 Reference](/x402/reference). *** ## Troubleshooting | Error | Cause | Fix | | ----------------------------- | ----------------------------- | ------------------------------------------------------------------------------------------------ | | `401 Unauthorized` | Missing or expired JWT | Generate a fresh token (check `exp` claim) | | Failed to parse key | Secret not valid PKCS#8/DER | Ensure you stripped the `payai_sk_` prefix and the remaining string is valid base64 | | Signature verification failed | Wrong key or corrupted secret | Verify your API Key ID and Secret match the [merchant dashboard](https://merchant.payai.network) | ## Need help? Have questions or want to connect with other developers? Join our Discord server. # Bazaar Discovery Source: https://docs.payai.network/x402/facilitators/bazaar How resources get listed, refreshed, and found in the PayAI Bazaar The Bazaar is the PayAI facilitator's public catalog of x402 resources. AI agents and clients browse it via [`/discovery/resources`](#get-discoveryresources) to find paid APIs and MCP tools they can use. Listing is automatic: there is no registration form, account, or manual submission. The facilitator indexes your resource from the payments it processes — if they carry your discovery declaration. ## How listing works Three parties cooperate to get a resource listed: 1. **Your server declares.** Your 402 response includes a bazaar discovery declaration describing the endpoint (method, input/output shape, service metadata). 2. **The buyer's client echoes.** The client copies your declaration from the 402 response into the payment payload it sends to the facilitator. This is required client behavior in x402 v2 — the client must include at least the extension info it received. 3. **The facilitator catalogs.** On `/verify` and `/settle`, the facilitator extracts the declaration from the payment payload and upserts your catalog entry asynchronously. Step 2 is where most "my resource never appears" reports come from. If the buyer's client drops the `extensions` object when building its payment payload, the facilitator never sees your declaration — no matter how many payments settle successfully. Use the [`EXTENSION-RESPONSES` header](#reading-extension-responses) to tell the cases apart. Since 2026-07-29, cataloging runs on `/verify` as well as `/settle`. Verification moves no funds, so you can list or refresh a resource without a settled payment. ## Declaring your resource **x402 v2** servers declare via the `bazaar` extension on the 402 response. The `@x402/extensions` package builds a valid declaration for you (`declareDiscoveryExtension`), including the JSON schema the facilitator validates against. The declaration lives in the 402 body's top-level `extensions` object: ```json theme={null} { "x402Version": 2, "resource": { "url": "https://api.example.com/convert", "description": "Convert PDFs to markdown", "mimeType": "application/json", "serviceName": "Example Converter", "tags": ["pdf", "markdown"], "iconUrl": "https://api.example.com/icon.png" }, "accepts": [ ... ], "extensions": { "bazaar": { "info": { "input": { "type": "http", "method": "POST", "bodyType": "json", "body": { ... } }, "output": { "type": "json", "example": { ... } } }, "schema": { ... } } } } ``` The optional `serviceName`, `tags`, and `iconUrl` fields on the `resource` object are service-level metadata the Bazaar uses to present your listing; they are persisted along with `description` and `mimeType`. **x402 v1** servers declare through `outputSchema.input` on the payment requirements themselves (with `type` and `method` required). Because v1 discovery info rides inside the payment requirements — which your server controls end to end — v1 listing does not depend on the buyer's client echoing anything. **MCP servers** declare per tool. The catalog key is `(resource, toolName)`, so a server exposing several tools at one URL gets one entry per tool. ## Reading EXTENSION-RESPONSES Every `/verify` and `/settle` response from the facilitator reports what happened to your declaration via the `EXTENSION-RESPONSES` header — a base64-encoded JSON object keyed by extension: ``` EXTENSION-RESPONSES: eyJiYXphYXIiOnsic3RhdHVzIjoicHJvY2Vzc2luZyJ9fQ== → {"bazaar":{"status":"processing"}} ``` | What you get | Meaning | | --------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- | | `{"bazaar":{"status":"processing"}}` | Declaration accepted and queued for cataloging. Your entry appears or refreshes within seconds. | | `{"bazaar":{"status":"rejected","rejectedReason":"..."}}` | Declaration received but failed validation. The reason says why (truncated to 256 characters). Payment is unaffected. | | No header at all | The payment payload carried no bazaar extension — the buyer's client dropped your declaration. | A rejected or missing declaration never affects the payment itself: verification and settlement succeed or fail on their own terms. ## What gets catalogued Each entry in `/discovery/resources` carries: | Field | Description | | ----------------------------------------------------------- | ----------------------------------------------------------------- | | `resource` | The endpoint URL | | `toolName` | MCP tool name; `null` for HTTP resources | | `type` | `http` or `mcp` | | `x402Version` | Protocol version of the indexing payment | | `accepts` | Payment requirements from the indexing payment | | `description`, `mimeType`, `serviceName`, `tags`, `iconUrl` | Service metadata from your declaration (`null` when not declared) | | `method` | HTTP method (`null` for MCP) | | `inputSchema`, `outputSchema` | Input/output shapes from your declaration | | `lastUpdated` | ISO timestamp of the last refresh | ## Refresh semantics * Entries are **upserted on every payment that carries the extension** — `accepts`, schemas, metadata, and `lastUpdated` all refresh. * Refresh is **forward-only**. Correcting your declaration does not rewrite the catalog until the next extension-carrying payment arrives. * There is **no TTL and no eviction**: an entry persists at its last-written state indefinitely. * There is **no re-index endpoint**. To force a refresh, make one verify-shaped payment against your own endpoint through a client that echoes extensions — `/verify` catalogs and moves no funds. Confirm with the `processing` header status. ## Endpoints ### GET /discovery/resources | Parameter | Type | Required | Description | Default | | --------- | -------- | -------- | ------------------------- | ------- | | `limit` | `number` | Optional | Results per page (1–1000) | 100 | | `offset` | `number` | Optional | Results to skip | 0 | Returns `{ items, pagination: { limit, offset, total }, x402Version }`, newest first. Responses are cached for about a minute. The legacy `/list` path redirects here permanently. ### GET /discovery/stats Aggregate catalog and settlement statistics. Note the three catalog counts: ```json theme={null} "merchants": { "hosts": 1505, // distinct services — the "how many merchants" number "resources": 25086, // distinct paid endpoint URLs "catalogEntries": 25086, // rows: one per (resource, toolName) "total": 25086 // deprecated alias of catalogEntries } ``` An MCP server with N tools contributes N `catalogEntries` but one `resource` and at most one new `host`. ## Opting out and delisting * **v1**: declare `discoverable: false` inside `outputSchema.input` and the facilitator will not index the resource. * **v2**: simplest is to omit the `bazaar` extension from your 402 — with nothing to echo, nothing is indexed. * **Delisting an existing entry is a known gap.** Removing or omitting your declaration stops refreshes but does not evict what is already catalogued, and the x402 specification currently defines no delisting semantics at all. Until that is standardized upstream, removal is a manual request — reach out on Discord or open an issue with proof of resource ownership. ## Troubleshooting | Symptom | Likely cause | What to do | | ------------------------------------------------------------ | -------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- | | Payments settle but the resource never appears | Buyer's client is not echoing `extensions` into the payment payload | Check a payment response: no `EXTENSION-RESPONSES` header confirms it. Pay once through an echoing client (any `@x402/*` 2.x, `x402-solana` ≥ 2.0.5) | | Entry exists but is stale after you changed your declaration | No extension-carrying payment since the change | Trigger a refresh via `/verify` with an echoing client; look for `processing` | | Header says `rejected` | Declaration failed schema validation | Fix the declaration per `rejectedReason`; validate locally with `@x402/extensions` | | Entry shows `metadata: {}` and `null` service fields | Entry predates metadata persistence (2026-08-01) or declaration omits the fields | Any extension-carrying payment after declaring `serviceName`/`tags`/`iconUrl` populates them | ## Need help? Have questions or want to connect with other developers? Join our Discord server. # Facilitator Introduction Source: https://docs.payai.network/x402/facilitators/introduction Learn about the role of an x402 facilitator ## Why use an x402 facilitator Facilitators provide shared infrastructure for verifying and settling on-chain payments for HTTP resources using the x402 protocol. They abstract multi-chain complexity, offer a consistent API for resource servers, and enable better reliability, observability, and reuse across multiple merchants and apps. * Consistent verification and settlement across schemes and networks * Offload blockchain operations and complexity from resource servers * Serve multiple merchants/apps via a stable `/verify` and `/settle` API * Help clients discover merchants via the [Bazaar](/x402/facilitators/bazaar) `/discovery/resources` endpoint ## Architecture at a glance x402 sequence diagram * Client: Calls protected resources and constructs payment payloads. * Resource Server: Advertises payment requirements, verifies/settles payments, fulfills requests. * Facilitator Server: Verifies payment payloads and executes settlements via standard endpoints. * Blockchain Networks: Execute and confirm payments. ### Payment flow (facilitator perspective) From the facilitator’s point of view, the key steps of the protocol are: 1. Resource server sends a POST to the facilitator `/verify` endpoint with the Payment Payload and the Payment Requirements selected by the client. 2. Facilitator validates the payload according to the scheme and network (e.g., signatures, amounts, nonces) and returns a Verification Response. 3. When the server is ready to execute the payment, it sends a POST to the facilitator `/settle` endpoint with the Payment Payload and Payment Requirements. 4. Facilitator submits the transaction to the blockchain for the specified scheme/network, waits for confirmation, and returns a Payment Execution Response. ## Facilitator responsibilities * Expose secure `/verify` and `/settle` endpoints that follow x402 request/response shapes * Validate Payment Payloads per scheme and network; prevent replay and ensure idempotency * Submit on-chain transactions for settlement and monitor confirmations * Provide clear error responses for verification or settlement failures * Maintain supported network coverage and RPC/wallet infrastructure * Implement observability (logs/metrics/traces), rate limiting, retries, and alerting ## Getting started The easiest way to connect to the PayAI facilitator is with the `@payai/facilitator` package: ```bash theme={null} npm install @payai/facilitator ``` ```typescript theme={null} import { facilitator } from "@payai/facilitator"; import { HTTPFacilitatorClient } from "@x402/core/server"; const facilitatorClient = new HTTPFacilitatorClient(facilitator); ``` This works immediately on the **free tier** — no API keys required. Ready to start? Visit the PayAI Facilitator to get started. ## API key authentication When you're ready to scale beyond the free tier (10,000 settlements/month), create a merchant account at [merchant.payai.network](https://merchant.payai.network) to top up credits and get API keys. See [Pricing](/x402/facilitators/pricing) for details. Set these environment variables and `@payai/facilitator` will automatically authenticate your requests: ```env theme={null} PAYAI_API_KEY_ID=your-key-id PAYAI_API_KEY_SECRET=your-key-secret ``` For advanced use cases, you can also pass credentials explicitly: ```typescript theme={null} import { createFacilitatorConfig } from "@payai/facilitator"; const facilitatorClient = new HTTPFacilitatorClient( createFacilitatorConfig("your-key-id", "your-key-secret"), ); ``` If you need to authenticate from a language without a PayAI package, or want to understand how auth works under the hood, see the [Facilitator Authentication](/x402/facilitators/authentication) guide for the full protocol and standalone examples in TypeScript, Python, Go, and Rust. ## x402 reference For a deeper dive into message shapes, headers, verification and settlement responses, see the x402 Reference. ## Need help? Have questions or want to connect with other developers? Join our Discord server. # Facilitator Pricing Source: https://docs.payai.network/x402/facilitators/pricing PayAI Facilitator pricing — free tier, per-transaction pricing, and credits ## Free Tier The free tier remains free — no strings attached. **\$0/month** * Up to **10,000 settlements per month** * No API key required Get started immediately with zero setup. Jump to the [quickstart](/x402/quickstart) to start building. ## Beyond the Free Tier Beyond the free tier, pricing is simple and predictable: **\$0.001 per transaction** **Minimum transaction amount**: Transactions must be at least \$0.001. This requirement will be removed after the credit system is turned on for merchants. This fee is designed to cover gas and RPC costs while enabling the network to scale reliably. ### Builder friendly At this pricing, the facilitator remains extremely builder friendly. It costs the same — even slightly less — than what merchants would otherwise pay in raw network fees and RPC costs, while preserving all the benefits of x402 for their users. ### Pricing examples | Transactions | Cost | | ------------ | ------- | | 1 | \$0.001 | | 1,000 | \$1 | | 100,000 | \$100 | ## Credit System To maintain access beyond 10,000 settlements/month, merchants log into a dashboard and top up credits. * **1 credit = 1 settled transaction** * **Credits never expire** Log in to the merchant dashboard to manage your credits. ## Comparison | | Free | Beyond Free | | ------------------------ | ------------------ | -------------------------------------------------------- | | **Monthly cost** | \$0 | \$0.001 per transaction | | **Settlements included** | Up to 10,000/month | Unlimited (credit-based) | | **API key required** | No | Yes | | **Credits** | Not required | 1 credit = 1 settlement | | **Credit expiry** | — | Never | | **Dashboard access** | Not required | [merchant.payai.network](https://merchant.payai.network) | ## How to get started 1. Follow the [quickstart](/x402/quickstart) to start building — the free tier requires no API key 2. When you're ready to scale beyond 10,000 settlements/month, create a merchant account at [merchant.payai.network](https://merchant.payai.network) 3. Top up credits from the dashboard 4. Get your API key ID and secret 5. Set the environment variables in your server: ```env theme={null} PAYAI_API_KEY_ID=your-key-id PAYAI_API_KEY_SECRET=your-key-secret ``` If you're using `@payai/facilitator` (used throughout the TypeScript server guides), your server will automatically authenticate with the facilitator — no code changes needed. ```typescript theme={null} import { facilitator } from "@payai/facilitator"; import { HTTPFacilitatorClient } from "@x402/core/server"; // Automatically uses API keys from environment when available const facilitatorClient = new HTTPFacilitatorClient(facilitator); ``` Pricing is subject to change as PayAI continues to grow and adoption increases. For now, the focus is on sustainability at scale and building long-term value. Visit [facilitator.payai.network](https://facilitator.payai.network) for the latest pricing information. ## Need help? Have questions or want to connect with other developers? Join our Discord server. # x402 Introduction Source: https://docs.payai.network/x402/introduction Learn about the x402 protocol x402 ## What is x402? x402 is an open payment protocol that brings stablecoin payments to plain HTTP. It revives the `HTTP 402 Payment Required` status so that servers can charge for APIs and digital content seamlessly. Clients (human users or AI agents) can pay programmatically to access resources without accounts, API keys, or complex authentication. Learn more about the x402 project at [x402.org](https://x402.org). ## Why x402? Legacy payment rails weren’t built for the web’s speed or for machine-to-machine use. They are slow, costly, and require sign-ups and keys. x402 embeds payment into the web’s native request–response flow, enabling instant, global, usage-based payments with minimal integration—ideal for humans and autonomous agents alike. ## Benefits * Simple HTTP integration using status code 402 * Pay-per-request and other usage-based pricing * Micropayments with stablecoins (e.g., USDC on Solana) * Agent-native: AI agents can discover and pay automatically * Zero friction: no accounts, API keys, or session management ## How it works (high level) 1. A buyer (client) requests a resource from a seller (server). 2. If payment is required, the seller responds with 402 and payment instructions. 3. The buyer constructs and sends a payment payload. 4. The seller verifies and settles the payment (often via a facilitator) and then returns the resource. x402 sequence diagram ## Getting started Quickly get started buying/selling your first service! ## Need help? Have questions or want to connect with other developers? Join our Discord server. # Quickstart Source: https://docs.payai.network/x402/quickstart This page gets you set up quickly as a merchant (server) or a buyer (client). Choose your language and framework below to get started. [Scroll to the end to see all supported blockchain networks.](#supported-networks) ## Servers (Merchants) Quickstart for building an x402-enabled server with Express. Quickstart for building an x402-enabled server with Hono. NextJS server quickstart is coming soon. Quickstart for building an x402-enabled server with FastAPI. Quickstart for building an x402-enabled server with Flask. ## Clients (Buyers) Quickstart for building an x402 client with Axios. Quickstart for building an x402 client with Fetch. Quickstart for building an x402 client with httpx. Quickstart for building an x402 client with requests. ## Facilitator The TypeScript guides use `@payai/facilitator`, which automatically connects to the PayAI facilitator at `https://facilitator.payai.network`. No manual URL configuration needed. ```typescript theme={null} import { facilitator } from "@payai/facilitator"; import { HTTPFacilitatorClient } from "@x402/core/server"; const facilitatorClient = new HTTPFacilitatorClient(facilitator); ``` The facilitator works immediately on the **free tier**. When you're ready for production, set `PAYAI_API_KEY_ID` and `PAYAI_API_KEY_SECRET` environment variables to authenticate — see [Facilitator Pricing](/x402/facilitators/pricing). The PayAI facilitator supports the following endpoints: * `/supported` - see which networks and schemes are supported * `/verify` - verify that a payment is valid * `/settle` - settle the payment * `/discovery/resources` - view the bazaar, a marketplace of merchants Read the [reference](/x402/reference#7-1-post-%2Fverify) for more information on the facilitator endpoints and responses. ## Supported networks The PayAI facilitator supports x402 on the following networks: | Network Name | V1 Network String | V2 CAIP-2 ID | | ------------------ | -------------------- | ----------------------------------------- | | Arbitrum One | `arbitrum` | `eip155:42161` | | Arbitrum Sepolia | `arbitrum-sepolia` | `eip155:421614` | | Avalanche | `avalanche` | `eip155:43114` | | Avalanche Fuji | `avalanche-fuji` | `eip155:43113` | | Base | `base` | `eip155:8453` | | Base Sepolia | `base-sepolia` | `eip155:84532` | | Polygon | `polygon` | `eip155:137` | | Polygon Amoy | `polygon-amoy` | `eip155:80002` | | Sei | `sei` | `eip155:1329` | | Sei Testnet | `sei-testnet` | `eip155:713715` | | SKALE Base | `skale-base` | `eip155:1187947933` | | SKALE Base Sepolia | `skale-base-sepolia` | `eip155:324705682` | | Solana | `solana` | `solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp` | | Solana Devnet | `solana-devnet` | `solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1` | | X Layer | `xlayer` | `eip155:196` | | X Layer Testnet | `xlayer-testnet` | `eip155:1952` | ## Need help? Have questions or want to connect with other developers? Join our Discord server. # x402 Reference Source: https://docs.payai.network/x402/reference Reference documentation for the x402 protocol Once merged, you will be able to read the x402 protocol specification in the [x402 repository](https://github.com/x402-foundation/x402/specs/x402-specification.md). ## 1. Overview x402 is an open payment standard that enables clients to pay for HTTP resources using blockchain technology. The protocol leverages the existing HTTP 402 "Payment Required" status code to indicate when payment is required for resource access, providing a standardized mechanism for micropayments on the web. This specification is based on the x402 protocol implementation and documentation available in the [x402 repository](https://github.com/x402-foundation/x402). It aims to provide a comprehensive and implementation-agnostic specification for the x402 HTTP-native micropayment protocol. ## 2. Core Payment Flow The x402 protocol follows a standard HTTP request-response cycle with payment integration: 1. **Client Request**: Client makes an HTTP request to a resource server 2. **Payment Required Response (402)**: If no valid payment is attached, the server responds with an HTTP 402 status code and includes payment requirements in the `PAYMENT-REQUIRED` header (base64-encoded JSON) 3. **Payment Authorization Request**: Client selects a payment requirement, constructs a payment payload, and submits it in the `PAYMENT-SIGNATURE` header 4. **Settlement Response**: Server verifies the payment authorization, settles the payment, and includes settlement details in the `PAYMENT-RESPONSE` header ## 3. Protocol Components The x402 protocol involves three primary components: * **Resource Server**: A web service that requires payment for access to protected resources (APIs, content, data, etc.) * **Client**: Any application or agent that requests access to protected resources * **Facilitator**: An endpoint service that handles payment verification and blockchain settlement ## 4. HTTP Status Codes The x402 protocol uses standard HTTP status codes with specific semantics: * **200 OK**: Request successful, payment verified and settled * **402 Payment Required**: Payment required to access the resource * **400 Bad Request**: Invalid payment payload or payment requirements * **500 Internal Server Error**: Server error during payment processing ## 5. Data Types This section defines the core data structures used in the x402 protocol. ### 5.1 Payment Requirements Response #### 5.1.1 JSON Payload When a resource server requires payment, it responds with an HTTP 402 status code and includes payment requirements in the `PAYMENT-REQUIRED` header as base64-encoded JSON. Example: ```json theme={null} { "x402Version": 2, "error": "PAYMENT-SIGNATURE header is required", "resource": { "url": "https://api.example.com/premium-data", "description": "Access to premium market data", "mimeType": "application/json" }, "accepts": [ { "scheme": "exact", "network": "eip155:84532", "amount": "10000", "asset": "0x036CbD53842c5426634e7929541eC2318f3dCF7e", "payTo": "0x209693Bc6afc0C5328bA36FaF03C514EF312287C", "maxTimeoutSeconds": 60, "extra": { "name": "USDC", "version": "2" } }, { "scheme": "exact", "network": "solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1", "amount": "1000000", "asset": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", "payTo": "6oD1Qw1k8Qw1k8Qw1k8Qw1k8Qw1k8Qw1k8Qw1k8Qw1k", "maxTimeoutSeconds": 60, "extra": { "feePayer": "2wKupLR9q6wXYppw8Gr2NvWxKBUqm4PPJKkQfoxHDBg4" } } ], "extensions": {} } ``` #### 5.1.2 Field Descriptions The Payment Requirements Response contains the following fields: **All fields are required.** | Field Name | Type | Description | | ------------- | -------- | ------------------------------------------------------------------------ | | `x402Version` | `number` | Protocol version identifier (must be 2) | | `error` | `string` | Human-readable error message explaining why payment is required | | `resource` | `object` | Resource object containing URL, description, and MIME type | | `accepts` | `array` | Array of payment requirement objects defining acceptable payment methods | | `extensions` | `object` | Extensions object for future protocol enhancements | The `resource` object contains: | Field Name | Type | Required | Description | | ------------- | -------- | -------- | ------------------------------------------ | | `url` | `string` | Required | URL of the protected resource | | `description` | `string` | Required | Human-readable description of the resource | | `mimeType` | `string` | Optional | MIME type of the expected response | Each payment requirement object in the `accepts` array contains: | Field Name | Type | Required | Description | | ------------------- | -------- | -------- | ---------------------------------------------------------------------------------------------------------------- | | `scheme` | `string` | Required | Payment scheme identifier (e.g., "exact") | | `network` | `string` | Required | Blockchain network identifier in CAIP-2 format (e.g., "eip155:84532", "solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1") | | `amount` | `string` | Required | Required payment amount in atomic token units | | `asset` | `string` | Required | Token contract address | | `payTo` | `string` | Required | Recipient wallet address for the payment | | `maxTimeoutSeconds` | `number` | Required | Maximum time allowed for payment completion | | `extra` | `object` | Optional | Scheme-specific additional information | ### 5.2 Payment Proof (PAYMENT-SIGNATURE Header) #### 5.2.1 JSON Structure The client includes payment authorization in the `PAYMENT-SIGNATURE` header as base64-encoded JSON: ```json theme={null} { "x402Version": 2, "scheme": "exact", "network": "eip155:84532", "accepted": { "scheme": "exact", "network": "eip155:84532", "amount": "10000", "asset": "0x036CbD53842c5426634e7929541eC2318f3dCF7e", "payTo": "0x209693Bc6afc0C5328bA36FaF03C514EF312287C", "maxTimeoutSeconds": 60 }, "payload": { "signature": "0x2d6a7588d6acca505cbf0d9a4a227e0c52c6c34008c8e8986a1283259764173608a2ce6496642e377d6da8dbbf5836e9bd15092f9ecab05ded3d6293af148b571c", "authorization": { "from": "0x857b06519E91e3A54538791bDbb0E22373e36b66", "to": "0x209693Bc6afc0C5328bA36FaF03C514EF312287C", "value": "10000", "validAfter": "1740672089", "validBefore": "1740672154", "nonce": "0xf3746613c2d920b5fdabc0856f2aeb2d4f88ee6037b8cc5d04a71a4462f13480" } }, "extensions": {} } ``` And on Solana: ```json theme={null} { "x402Version": 2, "scheme": "exact", "network": "solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1", "accepted": { "scheme": "exact", "network": "solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1", "amount": "1000000", "asset": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", "payTo": "6oD1Qw1k8Qw1k8Qw1k8Qw1k8Qw1k8Qw1k8Qw1k8Qw1k", "maxTimeoutSeconds": 60 }, "payload": { "transaction": "base64-encoded partially-signed transaction" }, "extensions": {} } ``` #### 5.2.2 Field Descriptions The Payment Payload contains the following fields: **All fields are required.** | Field Name | Type | Description | | ------------- | -------- | ---------------------------------------------------------------------------------------------------------------- | | `x402Version` | `number` | Protocol version identifier (must be 2) | | `scheme` | `string` | Payment scheme identifier (e.g., "exact") | | `network` | `string` | Blockchain network identifier in CAIP-2 format (e.g., "eip155:84532", "solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1") | | `accepted` | `object` | The payment requirement that was accepted by the client | | `payload` | `object` | Payment data object | | `extensions` | `object` | Extensions object for future protocol enhancements | The `payload` field contains scheme-specific data on EVM: **All fields are required.** | Field Name | Type | Description | | --------------- | -------- | ----------------------------------- | | `signature` | `string` | EIP-712 signature for authorization | | `authorization` | `object` | EIP-3009 authorization parameters | The authorization object contains the following fields: **All fields are required.** | Field Name | Type | Description | | ------------- | -------- | ----------------------------------------------- | | `from` | `string` | Payer's wallet address | | `to` | `string` | Recipient's wallet address | | `value` | `string` | Payment amount in atomic units | | `validAfter` | `string` | Unix timestamp when authorization becomes valid | | `validBefore` | `string` | Unix timestamp when authorization expires | | `nonce` | `string` | 32-byte random nonce to prevent replay attacks | The `payload` field contains scheme-specific data on SVM: **All fields are required.** | Field Name | Type | Description | | ------------- | -------- | ------------------------------------------- | | `transaction` | `string` | Base64-encoded partially-signed transaction | ### 5.3 Settlement Response #### 5.3.1 JSON Structure After payment settlement, the server includes transaction details in the `PAYMENT-RESPONSE` header as base64-encoded JSON: ```json theme={null} { "success": true, "transaction": "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef", "network": "eip155:84532", "payer": "0x857b06519E91e3A54538791bDbb0E22373e36b66" } ``` #### 5.3.2 Field Descriptions The Settlement Response contains the following fields: | Field Name | Type | Required | Description | | ------------- | --------- | -------- | --------------------------------------------------------------- | | `success` | `boolean` | Required | Indicates whether the payment settlement was successful | | `errorReason` | `string` | Optional | Error reason if settlement failed (omitted if successful) | | `transaction` | `string` | Required | Blockchain transaction hash (empty string if settlement failed) | | `network` | `string` | Required | Blockchain network identifier in CAIP-2 format | | `payer` | `string` | Required | Address of the payer's wallet | ## 6. Payment Schemes This section describes the payment schemes supported by the x402 protocol. Each scheme defines a specific method for authorizing and executing payments. ### 6.1 Exact Scheme The "exact" scheme uses EIP-3009 (Transfer with Authorization) to enable secure, gasless transfers of specific amounts of ERC-20 tokens. #### 6.1.1 EIP-3009 Authorization The authorization follows the EIP-3009 standard for `transferWithAuthorization`: ```javascript theme={null} const authorizationTypes = { TransferWithAuthorization: [ { name: "from", type: "address" }, { name: "to", type: "address" }, { name: "value", type: "uint256" }, { name: "validAfter", type: "uint256" }, { name: "validBefore", type: "uint256" }, { name: "nonce", type: "bytes32" }, ], }; ``` #### 6.1.2 Verification Steps The facilitator performs the following verification steps: 1. **Signature Validation**: Verify the EIP-712 signature is valid and properly signed by the payer 2. **Balance Verification**: Confirm the payer has sufficient token balance for the transfer 3. **Amount Validation**: Ensure the payment amount meets or exceeds the required amount 4. **Time Window Check**: Verify the authorization is within its valid time range 5. **Parameter Matching**: Confirm authorization parameters match the original payment requirements 6. **Transaction Simulation**: Simulate the `transferWithAuthorization` transaction to ensure it would succeed #### 6.1.3 Settlement Settlement is performed by calling the `transferWithAuthorization` function on the ERC-20 contract with the signature and authorization parameters provided in the payment payload. ### 6.2 Exact Scheme on Solana (SVM) On Solana, the "exact" scheme uses a partially-signed transaction (base64-encoded in the payment payload) that includes a **TransferChecked** instruction plus compute-budget instructions. Facilitators expect a specific instruction order and enforce limits on compute units and priority fee. #### 6.2.1 Expected Transaction Structure Instructions must appear in this order: 1. **SetComputeUnitLimit** — set the transaction compute unit limit 2. **SetComputeUnitPrice** — set the priority fee (in microlamports per compute unit) 3. **TransferChecked** — the token transfer (amount, mint, decimals, etc.) 4. *Optional:* **Lighthouse** — may be added by the wallet 5. *Optional:* **Lighthouse** — may be added by the wallet The optional Lighthouse instructions are automatically added by wallets (e.g. Phantom, Solflare). Merchants do not need to add them manually; they only need to include the first three instructions. #### 6.2.2 Limits Facilitators enforce the following limits on the Solana transaction: | Instruction | Limit | Description | | ----------------------- | ---------- | ----------------------------------------------------- | | **SetComputeUnitLimit** | **40,000** | Maximum compute units allowed for the transaction | | **SetComputeUnitPrice** | **5** | Maximum microlamports per compute unit (priority fee) | Transactions that exceed these limits may be rejected by the facilitator. ## 7. Facilitator Interface The facilitator provides REST APIs for payment verification and settlement. This allows resource servers to delegate blockchain operations to trusted third parties or host the endpoints themselves. ### 7.1 POST /verify Verifies a payment authorization without executing the transaction on the blockchain. #### Request (Exact Scheme): ```json theme={null} { "paymentPayload": { "x402Version": 1, "scheme": "exact", "network": "base-sepolia", "payload": { "signature": "0x...", "authorization": { "from": "0x...", "to": "0x...", "value": "10000", "validAfter": "1740672089", "validBefore": "1740672154", "nonce": "0x..." } } }, "paymentRequirements": { "scheme": "exact", "network": "base-sepolia", "maxAmountRequired": "10000", "resource": "https://api.example.com/premium-data", "description": "Access to premium market data", "mimeType": "application/json", "payTo": "0x209693Bc6afc0C5328bA36FaF03C514EF312287C", "maxTimeoutSeconds": 60, "asset": "0x036CbD53842c5426634e7929541eC2318f3dCF7e", "extra": { "name": "USDC", "version": "2" } } } ``` #### Request (Exact Scheme on Solana): ```json theme={null} { "paymentPayload": { "x402Version": 1, "scheme": "exact", "network": "solana-devnet", "payload": { "transaction": "base64-encoded partially-signed transaction", } }, "paymentRequirements": { "scheme": "exact", "network": "solana-devnet", "maxAmountRequired": "1000000", "resource": "https://api.example.com/premium-data", "description": "Access to premium market data", "payTo": "6oD1Qw1k8Qw1k8Qw1k8Qw1k8Qw1k8Qw1k8Qw1k8Qw1k", "maxTimeoutSeconds": 60, "asset": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", "extra": { "feePayer": "2wKupLR9q6wXYppw8Gr2NvWxKBUqm4PPJKkQfoxHDBg4" } } ``` #### Successful Response: ```json theme={null} { "isValid": true, "payer": "0x857b06519E91e3A54538791bDbb0E22373e36b66" } ``` #### Error Response: ```json theme={null} { "isValid": false, "invalidReason": "insufficient_funds", "payer": "0x857b06519E91e3A54538791bDbb0E22373e36b66" } ``` ### 7.2 POST /settle Settles a payment by broadcasting the transaction to the blockchain. #### Request (Exact Scheme): ```json theme={null} { "paymentPayload": { "x402Version": 2, "scheme": "exact", "network": "eip155:84532", "accepted": { "scheme": "exact", "network": "eip155:84532", "amount": "10000", "asset": "0x036CbD53842c5426634e7929541eC2318f3dCF7e", "payTo": "0x209693Bc6afc0C5328bA36FaF03C514EF312287C", "maxTimeoutSeconds": 60 }, "payload": { "signature": "0x...", "authorization": { "from": "0x...", "to": "0x...", "value": "10000", "validAfter": "1740672089", "validBefore": "1740672154", "nonce": "0x..." } }, "extensions": {} }, "paymentRequirements": { "scheme": "exact", "network": "eip155:84532", "amount": "10000", "asset": "0x036CbD53842c5426634e7929541eC2318f3dCF7e", "payTo": "0x209693Bc6afc0C5328bA36FaF03C514EF312287C", "maxTimeoutSeconds": 60, "extra": { "name": "USDC", "version": "2" } } } ``` #### Request (Exact Scheme on Solana): ```json theme={null} { "paymentPayload": { "x402Version": 2, "scheme": "exact", "network": "solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1", "accepted": { "scheme": "exact", "network": "solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1", "amount": "1000000", "asset": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", "payTo": "6oD1Qw1k8Qw1k8Qw1k8Qw1k8Qw1k8Qw1k8Qw1k8Qw1k", "maxTimeoutSeconds": 60 }, "payload": { "transaction": "base64-encoded partially-signed transaction" }, "extensions": {} }, "paymentRequirements": { "scheme": "exact", "network": "solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1", "amount": "1000000", "asset": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", "payTo": "6oD1Qw1k8Qw1k8Qw1k8Qw1k8Qw1k8Qw1k8Qw1k8Qw1k", "maxTimeoutSeconds": 60, "extra": { "feePayer": "2wKupLR9q6wXYppw8Gr2NvWxKBUqm4PPJKkQfoxHDBg4" } } } ``` #### Successful Response: ```json theme={null} { "success": true, "payer": "0x857b06519E91e3A54538791bDbb0E22373e36b66", "transaction": "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef", "network": "base-sepolia" } ``` #### Error Response: ```json theme={null} { "success": false, "errorReason": "insufficient_funds", "payer": "0x857b06519E91e3A54538791bDbb0E22373e36b66", "transaction": "", "network": "base-sepolia" } ``` ### 7.3 GET /supported Returns the list of payment schemes and networks supported by the facilitator. #### Response: ```json theme={null} { "kinds": [ { "x402Version": 2, "scheme": "exact", "network": "eip155:84532" }, { "x402Version": 2, "scheme": "exact", "network": "eip155:8453" }, { "x402Version": 2, "scheme": "exact", "network": "avalanche-fuji" }, { "x402Version": 2, "scheme": "exact", "network": "avalanche" }, { "x402Version": 2, "scheme": "exact", "network": "solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1", "extra": { "feePayer": "address of facilitator" } }, { "x402Version": 2, "scheme": "exact", "network": "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp", "extra": { "feePayer": "address of facilitator" } } ] } ``` ## 8. Discovery API The x402 protocol includes a discovery mechanism that allows clients to find and explore available x402-enabled resources. This enables the creation of marketplaces (known as 'Bazaars') where users can discover and access monetized APIs and digital services. For how listing, refreshing, and the `EXTENSION-RESPONSES` header work on the PayAI facilitator, see Bazaar Discovery. ### 8.1 GET /discovery/resources List discoverable x402 resources from the Bazaar. #### Request Parameters: | Parameter | Type | Required | Description | Default | | --------- | -------- | -------- | -------------------------------------------- | ------- | | `limit` | `number` | Optional | Maximum number of results to return (1-1000) | 100 | | `offset` | `number` | Optional | Number of results to skip for pagination | 0 | #### Response: ```json theme={null} { "x402Version": 2, "items": [ { "resource": "https://api.example.com/premium-data", "type": "http", "x402Version": 2, "accepts": [ { "scheme": "exact", "network": "eip155:84532", "amount": "10000", "asset": "0x036CbD53842c5426634e7929541eC2318f3dCF7e", "payTo": "0x209693Bc6afc0C5328bA36FaF03C514EF312287C", "maxTimeoutSeconds": 60, "extra": { "name": "USDC", "version": "2" } } ], "lastUpdated": 1703123456, "metadata": { "category": "finance", "provider": "Example Corp" } } ], "pagination": { "limit": 10, "offset": 0, "total": 1 } } ``` ### 8.2 Discovered Resource Fields | Field Name | Type | Required | Description | | ------------- | -------- | -------- | --------------------------------------------------------------- | | `resource` | `string` | Required | The resource URL or identifier being monetized | | `type` | `string` | Required | Resource type (currently "http" for HTTP endpoints) | | `x402Version` | `number` | Required | Protocol version supported by the resource | | `accepts` | `array` | Required | Array of payment requirement objects specifying payment methods | | `lastUpdated` | `string` | Required | ISO 8601 timestamp of when the resource was last updated | | `metadata` | `object` | Optional | Additional metadata (category, provider, etc.) | ### 8.3 Bazaar Concept The Bazaar is a marketplace ecosystem where x402-enabled resources can be discovered and accessed. Key features: * **Resource Discovery**: Find APIs and services by category, provider, or payment requirements * **Payment Transparency**: View pricing and payment methods upfront * **Provider Information**: Learn about service providers and their offerings * **Dynamic Updates**: Resources can be added, updated, or removed dynamically ### 8.4 Example Usage ```bash theme={null} # Discover financial data APIs GET /discovery/resources?type=http&limit=10 # Search for a specific provider GET /discovery/resources?metadata[provider]=Coinbase ``` ## 9. Error Handling The x402 protocol defines standard error codes that may be returned by facilitators or resource servers. These error codes help clients understand why a payment failed and take appropriate action. * **`insufficient_funds`**: Client does not have enough tokens to complete the payment * **`invalid_exact_evm_payload_authorization_valid_after`**: Payment authorization is not yet valid (before validAfter timestamp) * **`invalid_exact_evm_payload_authorization_valid_before`**: Payment authorization has expired (after validBefore timestamp) * **`invalid_exact_evm_payload_authorization_value`**: Payment amount is insufficient for the required payment * **`invalid_exact_evm_payload_signature`**: Payment authorization signature is invalid or improperly signed * **`invalid_exact_evm_payload_recipient_mismatch`**: Recipient address does not match payment requirements * **`invalid_exact_svm_payload_transaction_instructions_length`**: Solana transaction must contain the three required instructions in order—`SetComputeUnitLimit`, `SetComputeUnitPrice`, and `TransferChecked`—and may include up to two optional Lighthouse instructions (see [§6.2 Exact scheme on Solana](/x402/reference)) * **`invalid_network`**: Specified blockchain network is not supported * **`invalid_payload`**: Payment payload is malformed or contains invalid data * **`invalid_payment_requirements`**: Payment requirements object is invalid or malformed * **`invalid_scheme`**: Specified payment scheme is not supported * **`unsupported_scheme`**: Payment scheme is not supported by the facilitator * **`invalid_x402_version`**: Protocol version is not supported * **`invalid_transaction_state`**: Blockchain transaction failed or was rejected * **`unexpected_verify_error`**: Unexpected error occurred during payment verification * **`unexpected_settle_error`**: Unexpected error occurred during payment settlement ## 10. Security Considerations ### 10.1 Replay Attack Prevention The x402 protocol implements multiple layers of protection against replay attacks: * **EIP-3009 Nonce**: Each authorization includes a unique 32-byte nonce to prevent replay attacks * **Blockchain Protection**: EIP-3009 contracts inherently prevent nonce reuse at the smart contract level * **Time Constraints**: Authorizations have explicit valid time windows to limit their lifetime * **Signature Verification**: All authorizations are cryptographically signed by the payer ### 10.2 Authentication Integration The protocol supports integration with authentication systems (e.g., Sign-In with Ethereum (SIWE)) to enable authenticated pricing models where verified users receive discounted rates or special access terms. ## 11. Implementation Notes ### 11.1 Supported Networks The following blockchain networks are currently supported by the reference implementation (using CAIP-2 format): * **`eip155:84532`**: Base Sepolia testnet (Chain ID: 84532) * **`eip155:8453`**: Base mainnet (Chain ID: 8453) * **`avalanche-fuji`**: Avalanche Fuji testnet (Chain ID: 43113) * **`avalanche`**: Avalanche mainnet (Chain ID: 43114) * **`solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1`**: Solana devnet * **`solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp`**: Solana mainnet ### 11.2 Supported Assets The protocol currently supports the following token types: * **`USDC`**: USD Coin (EIP-3009 compliant ERC-20 token) * **Additional ERC-20 tokens**: May be supported if they implement EIP-3009 (Transfer with Authorization) Token support depends on: * EIP-3009 compliance for the "exact" scheme * Facilitator service capabilities * Network-specific token availability ## 12. Use Cases and Applications The x402 protocol enables diverse monetization scenarios across the internet. While the core protocol is HTTP-native and chain-agnostic, specific implementations can vary based on use case requirements. ### 12.1 AI Agent Integration AI agents can use x402 to autonomously pay for resources and services. The protocol supports: * **Automatic payment handling** for API calls * **Resource discovery** through facilitator services * **Budget management** and spending controls (implementation-specific) * **Correlation tracking** for operation grouping (implementation-specific) ### 12.2 Human User Applications Traditional web applications can implement x402 for: * **Session-based access** (time-limited subscriptions) * **Pay-per-use content** (articles, videos, downloads) * **API monetization** with per-call pricing * **Authentication-based pricing** (discounted rates for verified users) ### 12.3 Server Frameworks x402 integrates with popular web frameworks: * **Express.js**: `require_payment()` middleware * **FastAPI/Flask**: Framework-specific middleware * **Hono**: Edge runtime support * **Next.js**: Full-stack integration ### 12.4 Client Libraries HTTP clients can be enhanced with x402 payment capabilities: * **Axios/fetch**: Browser-based payments * **httpx/requests**: Python client support * **Custom integrations**: Application-specific payment handling ### 12.5 Advanced Patterns The protocol enables sophisticated monetization strategies: * **Dynamic pricing** based on user authentication or usage patterns * **Session management** for time-based access control * **Batch payments** for multiple resource access * **Subscription models** built on micropayments *Note: Implementation details for specific patterns (such as budget management, correlation tracking, or session handling) are available in application notes and implementation guides.* Looking for V1 documentation? Visit the [V1 (Legacy) documentation](/v1/x402/reference) section. *** ## 13. Version History | Version | Date | Changes | Author | | ------- | --------- | ------------- | -------------------------- | | v0.1 | 2025-8-29 | Initial draft | \[derived from repository] | ## 14. Supported Networks x402 is supported on the following networks: | Network Name | V1 Network String | V2 CAIP-2 ID | | ------------------ | -------------------- | ----------------------------------------- | | Arbitrum One | `arbitrum` | `eip155:42161` | | Arbitrum Sepolia | `arbitrum-sepolia` | `eip155:421614` | | Avalanche | `avalanche` | `eip155:43114` | | Avalanche Fuji | `avalanche-fuji` | `eip155:43113` | | Base | `base` | `eip155:8453` | | Base Sepolia | `base-sepolia` | `eip155:84532` | | Polygon | `polygon` | `eip155:137` | | Polygon Amoy | `polygon-amoy` | `eip155:80002` | | Sei | `sei` | `eip155:1329` | | Sei Testnet | `sei-testnet` | `eip155:713715` | | SKALE Base | `skale-base` | `eip155:1187947933` | | SKALE Base Sepolia | `skale-base-sepolia` | `eip155:324705682` | | Solana | `solana` | `solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp` | | Solana Devnet | `solana-devnet` | `solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1` | | X Layer | `xlayer` | `eip155:196` | | X Layer Testnet | `xlayer-testnet` | `eip155:1952` | ## 15. Facilitators x402 is supported by the following facilitators: | Facilitator | URL | Networks | | ----------------- | ---------------------------------------------------------------------- | ---------------------------------------------------- | | PayAI Facilitator | [https://facilitator.payai.network](https://facilitator.payai.network) | Base, Base Sepolia, Solana, Solana Devnet, Avalanche | ## Need help? Have questions or want to connect with other developers? Join our Discord server. # Gin Source: https://docs.payai.network/x402/servers/go/gin ## Getting started with Gin (Go) Start accepting x402 payments in your Gin server in 2 minutes. You can find the full code for this example [here](https://github.com/x402-foundation/x402/tree/main/examples/go/servers/gin). ### Step 1: Create a Go module and install dependencies ```bash theme={null} go mod init myserver go get github.com/x402-foundation/x402/go github.com/gin-gonic/gin github.com/joho/godotenv go mod tidy ``` ### Step 2: Set your environment variables Your `.env` file should look like this: ``` EVM_PAYEE_ADDRESS=0x... # EVM wallet address to receive payments SVM_PAYEE_ADDRESS=... # Solana wallet address to receive payments FACILITATOR_URL=https://facilitator.payai.network ``` ### Step 3: Preview the server code This is the `main.go` the example uses. It loads your env, applies the x402 payment middleware, and defines protected and health routes. ```go theme={null} package main import ( "fmt" "net/http" "os" "time" x402 "github.com/x402-foundation/x402/go" x402http "github.com/x402-foundation/x402/go/http" ginmw "github.com/x402-foundation/x402/go/http/gin" evm "github.com/x402-foundation/x402/go/mechanisms/evm/exact/server" svm "github.com/x402-foundation/x402/go/mechanisms/svm/exact/server" ginfw "github.com/gin-gonic/gin" "github.com/joho/godotenv" ) const ( DefaultPort = "4021" ) func main() { godotenv.Load() evmAddress := os.Getenv("EVM_PAYEE_ADDRESS") if evmAddress == "" { fmt.Println("❌ EVM_PAYEE_ADDRESS environment variable is required") os.Exit(1) } svmAddress := os.Getenv("SVM_PAYEE_ADDRESS") if svmAddress == "" { fmt.Println("❌ SVM_PAYEE_ADDRESS environment variable is required") os.Exit(1) } facilitatorURL := os.Getenv("FACILITATOR_URL") if facilitatorURL == "" { fmt.Println("❌ FACILITATOR_URL environment variable is required") fmt.Println(" Example: https://x402.org/facilitator") os.Exit(1) } // Network configuration - Base Sepolia testnet evmNetwork := x402.Network("eip155:84532") svmNetwork := x402.Network("solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1") fmt.Printf("🚀 Starting Gin x402 server...\n") fmt.Printf(" EVM Payee address: %s\n", evmAddress) fmt.Printf(" SVM Payee address: %s\n", svmAddress) fmt.Printf(" EVM Network: %s\n", evmNetwork) fmt.Printf(" SVM Network: %s\n", svmNetwork) fmt.Printf(" Facilitator: %s\n", facilitatorURL) // Create Gin router r := ginfw.Default() // Create HTTP facilitator client facilitatorClient := x402http.NewHTTPFacilitatorClient(&x402http.FacilitatorConfig{ URL: facilitatorURL, }) /** * Configure x402 payment middleware * * This middleware protects specific routes with payment requirements. * When a client accesses a protected route without payment, they receive * a 402 Payment Required response with payment details. */ routes := x402http.RoutesConfig{ "GET /weather": { Accepts: x402http.PaymentOptions{ { Scheme: "exact", Price: "$0.001", Network: "eip155:84532", PayTo: evmAddress, }, { Scheme: "exact", Price: "$0.001", Network: "solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1", PayTo: svmAddress, }, }, Description: "Get weather data for a city", MimeType: "application/json", }, } // Apply x402 payment middleware r.Use(ginmw.X402Payment(ginmw.Config{ Routes: routes, Facilitator: facilitatorClient, Schemes: []ginmw.SchemeConfig{ ginmw.SchemeConfig{Network: "eip155:84532", Server: evm.NewExactEvmScheme()}, ginmw.SchemeConfig{Network: "solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1", Server: svm.NewExactSvmScheme()}, }, Timeout: 30 * time.Second, })) /** * Protected endpoint - requires $0.001 USDC payment * * Clients must provide a valid x402 payment to access this endpoint. * The payment is verified and settled before the endpoint handler runs. */ r.GET("/weather", func(c *ginfw.Context) { city := c.DefaultQuery("city", "San Francisco") weatherData := map[string]map[string]interface{}{ "San Francisco": {"weather": "foggy", "temperature": 60}, "New York": {"weather": "cloudy", "temperature": 55}, "London": {"weather": "rainy", "temperature": 50}, "Tokyo": {"weather": "clear", "temperature": 65}, } data, exists := weatherData[city] if !exists { data = map[string]interface{}{"weather": "sunny", "temperature": 70} } c.JSON(http.StatusOK, ginfw.H{ "city": city, "weather": data["weather"], "temperature": data["temperature"], "timestamp": time.Now().Format(time.RFC3339), }) }) /** * Health check endpoint - no payment required * * This endpoint is not protected by x402 middleware. */ r.GET("/health", func(c *ginfw.Context) { c.JSON(http.StatusOK, ginfw.H{ "status": "ok", "version": "2.0.0", }) }) fmt.Printf(" Server listening on http://localhost:%s\n\n", DefaultPort) if err := r.Run(":" + DefaultPort); err != nil { fmt.Printf("Error starting server: %v\n", err) os.Exit(1) } } ``` ### Step 4: Run the server ```bash theme={null} go run . ``` Your server is now accepting 402 payments! ### Step 5: Test the server You can test payments against your server locally by following the [fetch example](/x402/clients/typescript/fetch) or the [axios example](/x402/clients/typescript/axios), or by using the Go client examples in the [x402 repository](https://github.com/x402-foundation/x402/tree/main/examples/go/clients). ## Going to production This setup works on the **free tier** out of the box — no API keys required. Beyond the free tier the facilitator authenticates merchants with short-lived Ed25519 JWTs. Create a merchant account at [merchant.payai.network](https://merchant.payai.network), then add your keys to your `.env`: ```env theme={null} PAYAI_API_KEY_ID=your-key-id PAYAI_API_KEY_SECRET=payai_sk_your-api-key-secret ``` There is no PayAI module for Go — the x402 SDK takes an `AuthProvider`, so signing the token is all you need. Put this in `payaiauth/provider.go`: ```go theme={null} // Package payaiauth signs short-lived EdDSA JWTs for the PayAI facilitator. package payaiauth import ( "context" "crypto/ed25519" "crypto/rand" "crypto/x509" "encoding/base64" "encoding/json" "fmt" "strings" "sync" "time" x402http "github.com/x402-foundation/x402/go/http" ) // Provider implements x402http.AuthProvider. type Provider struct { keyID string key ed25519.PrivateKey ttl time.Duration mu sync.Mutex token string renewAt time.Time } // New builds a Provider from a PayAI API key ID and secret. The secret may // carry the payai_sk_ prefix shown in the merchant dashboard. func New(apiKeyID, apiKeySecret string) (*Provider, error) { secret := strings.TrimPrefix(strings.TrimSpace(apiKeySecret), "payai_sk_") der, err := base64.StdEncoding.DecodeString(secret) if err != nil { if der, err = base64.URLEncoding.DecodeString(secret); err != nil { return nil, fmt.Errorf("decode API key secret: %w", err) } } parsed, err := x509.ParsePKCS8PrivateKey(der) if err != nil { return nil, fmt.Errorf("parse PKCS#8 key: %w", err) } key, ok := parsed.(ed25519.PrivateKey) if !ok { return nil, fmt.Errorf("API key secret is not an Ed25519 key") } return &Provider{keyID: apiKeyID, key: key, ttl: 120 * time.Second}, nil } func b64(raw []byte) string { return base64.RawURLEncoding.EncodeToString(raw) } func uuidV4() (string, error) { var b [16]byte if _, err := rand.Read(b[:]); err != nil { return "", err } b[6] = (b[6] & 0x0f) | 0x40 b[8] = (b[8] & 0x3f) | 0x80 return fmt.Sprintf("%x-%x-%x-%x-%x", b[0:4], b[4:6], b[6:8], b[8:10], b[10:16]), nil } func (p *Provider) jwt() (string, error) { p.mu.Lock() defer p.mu.Unlock() if p.token != "" && time.Now().Before(p.renewAt) { return p.token, nil } now := time.Now() jti, err := uuidV4() if err != nil { return "", err } header, _ := json.Marshal(map[string]string{"alg": "EdDSA", "typ": "JWT", "kid": p.keyID}) payload, _ := json.Marshal(map[string]any{ "sub": p.keyID, "iss": "payai-merchant", "iat": now.Unix(), "exp": now.Add(p.ttl).Unix(), "jti": jti, }) msg := b64(header) + "." + b64(payload) p.token = msg + "." + b64(ed25519.Sign(p.key, []byte(msg))) // refresh early so an in-flight request never carries a token that expires // mid-lifetime p.renewAt = now.Add(p.ttl - 30*time.Second) return p.token, nil } // GetAuthHeaders satisfies x402http.AuthProvider. func (p *Provider) GetAuthHeaders(ctx context.Context) (x402http.AuthHeaders, error) { t, err := p.jwt() if err != nil { return x402http.AuthHeaders{}, err } h := map[string]string{"Authorization": "Bearer " + t} return x402http.AuthHeaders{Verify: h, Settle: h, Supported: h, Bazaar: h}, nil } ``` Then pass it to the facilitator client: ```go theme={null} authProvider, err := payaiauth.New( os.Getenv("PAYAI_API_KEY_ID"), os.Getenv("PAYAI_API_KEY_SECRET"), ) if err != nil { fmt.Printf("❌ PayAI auth: %v\n", err) os.Exit(1) } facilitatorClient := x402http.NewHTTPFacilitatorClient(&x402http.FacilitatorConfig{ URL: facilitatorURL, AuthProvider: authProvider, }) ``` The provider caches each token and re-signs 30 seconds before expiry, so a long-running server signs roughly once every two minutes rather than on every request. See [Facilitator Pricing](/x402/facilitators/pricing) for tier details and [Facilitator Authentication](/x402/facilitators/authentication) for the protocol reference. ## x402 reference For a deeper dive into message shapes, headers, verification and settlement responses, see the [x402 Reference](/x402/reference). ## Need help? Have questions or want to connect with other developers? Join our Discord server. # Merchant Introduction Source: https://docs.payai.network/x402/servers/introduction Learn how to sell services with x402. ## Monetize your API with x402 x402 lets you monetize HTTP APIs and content with on-chain payments, while keeping your existing server stack. Benefits: ✅ Customers don't pay network fees.\ ✅ Payment settles in \< 1 second.\ ✅ Universal compatibility -- if it speaks HTTP, it speaks x402. ## Architecture at a glance x402 sequence diagram * **Client**: Calls your protected resource and submits payments. * **Server (merchant)**: Advertises payment requirements, verifies payments, fulfills requests, and settles payments. * **Facilitator**: Verifies and/or settles payments on your behalf via standard endpoints. * **Blockchain**: Where payments are executed and confirmed. ## It's that easy Add x402 payments to your server with just a few lines: ```typescript theme={null} import { paymentMiddleware, x402ResourceServer } from "@x402/express"; import { ExactEvmScheme } from "@x402/evm/exact/server"; import { HTTPFacilitatorClient } from "@x402/core/server"; import { facilitator } from "@payai/facilitator"; const facilitatorClient = new HTTPFacilitatorClient(facilitator); app.use( paymentMiddleware( { "GET /weather": { accepts: [ { scheme: "exact", price: "$0.001", network: "eip155:84532", payTo: evmAddress, }, ], description: "Weather data", mimeType: "application/json", }, }, new x402ResourceServer(facilitatorClient) .register("eip155:84532", new ExactEvmScheme()), ), ); ``` ```typescript theme={null} import { paymentMiddleware, x402ResourceServer } from "@x402/hono"; import { ExactEvmScheme } from "@x402/evm/exact/server"; import { HTTPFacilitatorClient } from "@x402/core/server"; import { facilitator } from "@payai/facilitator"; const facilitatorClient = new HTTPFacilitatorClient(facilitator); app.use( paymentMiddleware( { "GET /weather": { accepts: [ { scheme: "exact", price: "$0.001", network: "eip155:84532", payTo: evmAddress, }, ], description: "Weather data", mimeType: "application/json", }, }, new x402ResourceServer(facilitatorClient) .register("eip155:84532", new ExactEvmScheme()), ), ); ``` ## Getting started Select one of the quickstart examples, or read the [reference](/x402/reference) for more details. Quickstart for building an x402-enabled server with Express. Quickstart for building an x402-enabled server with Hono. Quickstart for building an x402-enabled server with Next.js. Quickstart for building an x402-enabled server with FastAPI. Quickstart for building an x402-enabled server with Flask. ## Facilitator setup The `@payai/facilitator` package provides a pre-configured facilitator that connects to the PayAI facilitator at `https://facilitator.payai.network`: ```bash theme={null} npm install @payai/facilitator ``` ```typescript theme={null} import { facilitator } from "@payai/facilitator"; import { HTTPFacilitatorClient } from "@x402/core/server"; const facilitatorClient = new HTTPFacilitatorClient(facilitator); ``` This works immediately on the free tier. For production, set `PAYAI_API_KEY_ID` and `PAYAI_API_KEY_SECRET` environment variables — see [Going to Production](/x402/facilitators/pricing). For details on how authentication works or to implement it without PayAI packages, see [Facilitator Authentication](/x402/facilitators/authentication). ## x402 reference For a deeper dive into message shapes, headers, verification and settlement responses, see the [x402 Reference](/x402/reference). ## Need help? Have questions or want to connect with other developers? Join our Discord server. # Fastapi Source: https://docs.payai.network/x402/servers/python/fastapi ## Getting started with FastAPI Start accepting x402 payments in your FastAPI server in 2 minutes. You can find the full code for this example [here](https://github.com/x402-foundation/x402/tree/main/examples/python/servers/fastapi). ### Step 1: Install dependencies ```bash theme={null} pip install 'x402[fastapi,httpx,evm,svm]' 'solana<0.40' uvicorn python-dotenv pydantic ``` `solana<0.40` is a temporary pin. `x402[svm]` requires `solana>=0.36.0` with no upper bound, but `solana` 0.40 removed `solana.rpc.api` and `rpc.types.TxOpts`, which `x402`'s SVM signer still imports — so an unpinned install resolves to a version the SDK cannot use. A fix is proposed upstream in [x402-foundation/x402#3071](https://github.com/x402-foundation/x402/pull/3071) — drop the pin once it ships in an `x402` release. ### Step 2: Set your environment variables Your `.env` file should look like this: ``` EVM_ADDRESS=0x... # EVM wallet address to receive payments SVM_ADDRESS=... # Solana wallet address to receive payments FACILITATOR_URL=https://facilitator.payai.network ``` ### Step 3: Create a new FastAPI app ```python theme={null} import os from dotenv import load_dotenv from fastapi import FastAPI from pydantic import BaseModel from x402.http import FacilitatorConfig, HTTPFacilitatorClient, PaymentOption from x402.http.middleware.fastapi import PaymentMiddlewareASGI from x402.http.types import RouteConfig from x402.mechanisms.evm.exact import ExactEvmServerScheme from x402.mechanisms.svm.exact import ExactSvmServerScheme from x402.schemas import AssetAmount, Network from x402.server import x402ResourceServer load_dotenv() # Config EVM_ADDRESS = os.getenv("EVM_ADDRESS") SVM_ADDRESS = os.getenv("SVM_ADDRESS") EVM_NETWORK: Network = "eip155:84532" # Base Sepolia SVM_NETWORK: Network = "solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1" # Solana Devnet FACILITATOR_URL = os.getenv("FACILITATOR_URL", "https://x402.org/facilitator") if not EVM_ADDRESS or not SVM_ADDRESS: raise ValueError("Missing required environment variables") # Response schemas class WeatherReport(BaseModel): weather: str temperature: int class WeatherResponse(BaseModel): report: WeatherReport class PremiumContentResponse(BaseModel): content: str # App app = FastAPI() # x402 Middleware facilitator = HTTPFacilitatorClient(FacilitatorConfig(url=FACILITATOR_URL)) server = x402ResourceServer(facilitator) server.register(EVM_NETWORK, ExactEvmServerScheme()) server.register(SVM_NETWORK, ExactSvmServerScheme()) routes = { "GET /weather": RouteConfig( accepts=[ PaymentOption( scheme="exact", pay_to=EVM_ADDRESS, price="$0.01", network=EVM_NETWORK, ), PaymentOption( scheme="exact", pay_to=SVM_ADDRESS, price="$0.01", network=SVM_NETWORK, ), ], mime_type="application/json", description="Weather report", ), "GET /premium/*": RouteConfig( accepts=[ PaymentOption( scheme="exact", pay_to=EVM_ADDRESS, price=AssetAmount( amount="10000", # $0.01 USDC asset="0x036CbD53842c5426634e7929541eC2318f3dCF7e", extra={"name": "USDC", "version": "2"}, ), network=EVM_NETWORK, ), PaymentOption( scheme="exact", pay_to=SVM_ADDRESS, price="$0.01", network=SVM_NETWORK, ), ], mime_type="application/json", description="Premium content", ), } app.add_middleware(PaymentMiddlewareASGI, routes=routes, server=server) # Routes @app.get("/health") async def health_check() -> dict[str, str]: return {"status": "ok"} @app.get("/weather") async def get_weather() -> WeatherResponse: return WeatherResponse(report=WeatherReport(weather="sunny", temperature=70)) @app.get("/premium/content") async def get_premium_content() -> PremiumContentResponse: return PremiumContentResponse(content="This is premium content") if __name__ == "__main__": import uvicorn uvicorn.run(app, host="0.0.0.0", port=4021) ``` ### Step 4: Run the server ```bash theme={null} uvicorn main:app --reload ``` Your server is now accepting x402 payments! ### Step 5: Test the server You can test payments against your server locally by following the [httpx example](https://github.com/x402-foundation/x402/tree/main/examples/python/clients/httpx) or the [requests example](https://github.com/x402-foundation/x402/tree/main/examples/python/clients/requests) from the x402 repository. Just set your environment variables to match your local server, install the dependencies, and run the examples. ## Going to production This setup works on the **free tier** out of the box — no API keys required. Beyond the free tier the facilitator authenticates merchants with short-lived Ed25519 JWTs. Create a merchant account at [merchant.payai.network](https://merchant.payai.network), then add your keys to your `.env`: ```env theme={null} PAYAI_API_KEY_ID=your-key-id PAYAI_API_KEY_SECRET=payai_sk_your-api-key-secret ``` There is no PayAI package for Python — the x402 SDK takes an `auth_provider`, so signing the token is all you need. Install `cryptography` and drop this file beside your `main.py`: ```bash theme={null} pip install cryptography ``` ```python theme={null} import base64, json, time, uuid from cryptography.hazmat.primitives.serialization import load_der_private_key from x402.http.facilitator_client_base import AuthHeaders class PayAIAuthProvider: """Signs short-lived EdDSA JWTs for the PayAI facilitator. Implements the x402 AuthProvider protocol, so the facilitator client attaches the token to every request without further wiring. """ def __init__(self, api_key_id: str, api_key_secret: str, ttl: int = 120): self._kid = api_key_id secret = api_key_secret.strip() if secret.startswith("payai_sk_"): secret = secret[len("payai_sk_"):] self._key = load_der_private_key(base64.b64decode(secret), password=None) self._ttl = ttl self._token: str | None = None self._renew_at = 0.0 @staticmethod def _b64(raw: bytes) -> str: return base64.urlsafe_b64encode(raw).rstrip(b"=").decode("ascii") def _mint(self) -> str: now = int(time.time()) header = self._b64(json.dumps( {"alg": "EdDSA", "typ": "JWT", "kid": self._kid}, separators=(",", ":")).encode()) payload = self._b64(json.dumps( {"sub": self._kid, "iss": "payai-merchant", "iat": now, "exp": now + self._ttl, "jti": str(uuid.uuid4())}, separators=(",", ":")).encode()) message = f"{header}.{payload}" self._token = f"{message}.{self._b64(self._key.sign(message.encode()))}" # refresh a little early so an in-flight request never carries a token # that expires mid-lifetime self._renew_at = now + self._ttl - 30 return self._token def _jwt(self) -> str: if self._token and time.time() < self._renew_at: return self._token return self._mint() def get_auth_headers(self) -> AuthHeaders: h = {"Authorization": f"Bearer {self._jwt()}"} return AuthHeaders(verify=h, settle=h, supported=h, bazaar=h) ``` Then pass it to the facilitator client: ```python theme={null} from payai_auth import PayAIAuthProvider facilitator = HTTPFacilitatorClient( FacilitatorConfig( url=FACILITATOR_URL, auth_provider=PayAIAuthProvider( os.environ["PAYAI_API_KEY_ID"], os.environ["PAYAI_API_KEY_SECRET"], ), ) ) ``` The provider caches each token and re-signs 30 seconds before expiry, so a long-running server signs roughly once every two minutes rather than on every request. See [Facilitator Pricing](/x402/facilitators/pricing) for tier details and [Facilitator Authentication](/x402/facilitators/authentication) for the protocol reference. ## x402 reference For a deeper dive into message shapes, headers, verification and settlement responses, see the [x402 Reference](/x402/reference). ## Need help? Have questions or want to connect with other developers? Join our Discord server. # Flask Source: https://docs.payai.network/x402/servers/python/flask ## Getting started with Flask Start accepting x402 payments in your Flask server in 2 minutes. You can find the full code for this example [here](https://github.com/x402-foundation/x402/tree/main/examples/python/servers/flask). ### Step 1: Install dependencies ```bash theme={null} pip install 'x402[flask,httpx,evm,svm]' 'solana<0.40' python-dotenv ``` `solana<0.40` is a temporary pin. `x402[svm]` requires `solana>=0.36.0` with no upper bound, but `solana` 0.40 removed `solana.rpc.api` and `rpc.types.TxOpts`, which `x402`'s SVM signer still imports — so an unpinned install resolves to a version the SDK cannot use. A fix is proposed upstream in [x402-foundation/x402#3071](https://github.com/x402-foundation/x402/pull/3071) — drop the pin once it ships in an `x402` release. ### Step 2: Set your environment variables Your `.env` file should look like this: ``` EVM_ADDRESS=0x... # EVM wallet address to receive payments SVM_ADDRESS=... # Solana wallet address to receive payments FACILITATOR_URL=https://facilitator.payai.network ``` ### Step 3: Create a new Flask app ```python theme={null} import os from dotenv import load_dotenv from flask import Flask, jsonify from x402.http import FacilitatorConfig, HTTPFacilitatorClientSync, PaymentOption from x402.http.middleware.flask import payment_middleware from x402.http.types import RouteConfig from x402.mechanisms.evm.exact import ExactEvmServerScheme from x402.mechanisms.svm.exact import ExactSvmServerScheme from x402.schemas import AssetAmount, Network from x402.server import x402ResourceServerSync load_dotenv() # Config EVM_ADDRESS = os.getenv("EVM_ADDRESS") SVM_ADDRESS = os.getenv("SVM_ADDRESS") EVM_NETWORK: Network = "eip155:84532" # Base Sepolia SVM_NETWORK: Network = "solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1" # Solana Devnet FACILITATOR_URL = os.getenv("FACILITATOR_URL", "https://x402.org/facilitator") if not EVM_ADDRESS or not SVM_ADDRESS: raise ValueError("Missing required environment variables") # App app = Flask(__name__) # x402 Middleware facilitator = HTTPFacilitatorClientSync(FacilitatorConfig(url=FACILITATOR_URL)) server = x402ResourceServerSync(facilitator) server.register(EVM_NETWORK, ExactEvmServerScheme()) server.register(SVM_NETWORK, ExactSvmServerScheme()) routes = { "GET /weather": RouteConfig( accepts=[ PaymentOption( scheme="exact", pay_to=EVM_ADDRESS, price="$0.01", network=EVM_NETWORK, ), PaymentOption( scheme="exact", pay_to=SVM_ADDRESS, price="$0.01", network=SVM_NETWORK, ), ], mime_type="application/json", description="Weather report", ), "GET /premium/*": RouteConfig( accepts=[ PaymentOption( scheme="exact", pay_to=EVM_ADDRESS, price=AssetAmount( amount="10000", # $0.01 USDC asset="0x036CbD53842c5426634e7929541eC2318f3dCF7e", extra={"name": "USDC", "version": "2"}, ), network=EVM_NETWORK, ), PaymentOption( scheme="exact", pay_to=SVM_ADDRESS, price="$0.01", network=SVM_NETWORK, ), ], mime_type="application/json", description="Premium content", ), } payment_middleware(app, routes=routes, server=server) # Routes @app.route("/health") def health_check(): return jsonify({"status": "ok"}) @app.route("/weather") def get_weather(): return jsonify({"report": {"weather": "sunny", "temperature": 70}}) @app.route("/premium/content") def get_premium_content(): return jsonify({"content": "This is premium content"}) if __name__ == "__main__": app.run(host="0.0.0.0", port=4021, debug=False) ``` ### Step 4: Run the server ```bash theme={null} flask run ``` Your server is now accepting x402 payments! ### Step 5: Test the server You can test payments against your server locally by following the [httpx example](https://github.com/x402-foundation/x402/tree/main/examples/python/clients/httpx) or the [requests example](https://github.com/x402-foundation/x402/tree/main/examples/python/clients/requests) from the x402 repository. Just set your environment variables to match your local server, install the dependencies, and run the examples. ## Going to production This setup works on the **free tier** out of the box — no API keys required. Beyond the free tier the facilitator authenticates merchants with short-lived Ed25519 JWTs. Create a merchant account at [merchant.payai.network](https://merchant.payai.network), then add your keys to your `.env`: ```env theme={null} PAYAI_API_KEY_ID=your-key-id PAYAI_API_KEY_SECRET=payai_sk_your-api-key-secret ``` There is no PayAI package for Python — the x402 SDK takes an `auth_provider`, so signing the token is all you need. Install `cryptography` and drop this file beside your `main.py`: ```bash theme={null} pip install cryptography ``` ```python theme={null} import base64, json, time, uuid from cryptography.hazmat.primitives.serialization import load_der_private_key from x402.http.facilitator_client_base import AuthHeaders class PayAIAuthProvider: """Signs short-lived EdDSA JWTs for the PayAI facilitator. Implements the x402 AuthProvider protocol, so the facilitator client attaches the token to every request without further wiring. """ def __init__(self, api_key_id: str, api_key_secret: str, ttl: int = 120): self._kid = api_key_id secret = api_key_secret.strip() if secret.startswith("payai_sk_"): secret = secret[len("payai_sk_"):] self._key = load_der_private_key(base64.b64decode(secret), password=None) self._ttl = ttl self._token: str | None = None self._renew_at = 0.0 @staticmethod def _b64(raw: bytes) -> str: return base64.urlsafe_b64encode(raw).rstrip(b"=").decode("ascii") def _mint(self) -> str: now = int(time.time()) header = self._b64(json.dumps( {"alg": "EdDSA", "typ": "JWT", "kid": self._kid}, separators=(",", ":")).encode()) payload = self._b64(json.dumps( {"sub": self._kid, "iss": "payai-merchant", "iat": now, "exp": now + self._ttl, "jti": str(uuid.uuid4())}, separators=(",", ":")).encode()) message = f"{header}.{payload}" self._token = f"{message}.{self._b64(self._key.sign(message.encode()))}" # refresh a little early so an in-flight request never carries a token # that expires mid-lifetime self._renew_at = now + self._ttl - 30 return self._token def _jwt(self) -> str: if self._token and time.time() < self._renew_at: return self._token return self._mint() def get_auth_headers(self) -> AuthHeaders: h = {"Authorization": f"Bearer {self._jwt()}"} return AuthHeaders(verify=h, settle=h, supported=h, bazaar=h) ``` Then pass it to the facilitator client: ```python theme={null} from payai_auth import PayAIAuthProvider facilitator = HTTPFacilitatorClient( FacilitatorConfig( url=FACILITATOR_URL, auth_provider=PayAIAuthProvider( os.environ["PAYAI_API_KEY_ID"], os.environ["PAYAI_API_KEY_SECRET"], ), ) ) ``` The provider caches each token and re-signs 30 seconds before expiry, so a long-running server signs roughly once every two minutes rather than on every request. See [Facilitator Pricing](/x402/facilitators/pricing) for tier details and [Facilitator Authentication](/x402/facilitators/authentication) for the protocol reference. ## x402 reference For a deeper dive into message shapes, headers, verification and settlement responses, see the [x402 Reference](/x402/reference). ## Need help? Have questions or want to connect with other developers? Join our Discord server. # Manual Flow Source: https://docs.payai.network/x402/servers/python/manual-flow Step-by-step guide to building the x402 PAYMENT-REQUIRED response manually in Python without server SDK or middleware. ## Manual x402 server flow (Python) This page shows how to respond with **402 Payment Required** and build the **PAYMENT-REQUIRED** header by hand in Python—no `x402` server middleware. You decide when payment is required, construct the payment-requirements payload, base64-encode it, and send it in the response. For production you’ll usually use the [Flask](/x402/servers/python/flask) or [FastAPI](/x402/servers/python/fastapi) quickstarts; this tutorial is for agents or environments that need a minimal implementation or want to understand the protocol from the server’s perspective. *** ## 1. When to return 402 When a request hits a protected route: * If the request **does not** include a valid **PAYMENT-SIGNATURE** header (or the payment is invalid/expired), respond with **402** and a **PAYMENT-REQUIRED** header so the client knows how to pay. * If the request **does** include a valid payment, you (or your facilitator) verify/settle it and then respond with **200** and the resource. Verification and settlement are typically done via the [PayAI Facilitator](/x402/facilitators/introduction) or your own backend; this page focuses only on building the 402 response. *** ## 2. Build the payment-requirements payload The **PAYMENT-REQUIRED** header must contain **base64-encoded JSON**. The JSON object has this shape (see [x402 Reference §5.1](/x402/reference)): | Field | Type | Description | | ------------- | -------- | -------------------------------------------------------------------------------------------------- | | `x402Version` | `number` | Protocol version; use **2** | | `error` | `string` | Human-readable message (e.g. why payment is required) | | `resource` | `object` | `url`, `description`, and optional `mimeType` for the protected resource | | `accepts` | `array` | List of payment options (scheme, network, amount, asset, payTo, maxTimeoutSeconds, optional extra) | | `extensions` | `object` | Reserved; use `{}` | Each item in **accepts** describes one way the client can pay (e.g. USDC on Base Sepolia, or USDC on Solana). The client will choose one and send it back in PAYMENT-SIGNATURE. *** ## 3. Example: building the payload in Python Define the payload dict, then encode it as base64 and set the header. Use your own recipient addresses (`payTo`), asset addresses, and amounts. ```python theme={null} import base64 import json import os # Your wallet addresses (from env or config) EVM_PAY_TO = os.environ.get("EVM_ADDRESS", "0x209693Bc6afc0C5328bA36FaF03C514EF312287C") SVM_PAY_TO = os.environ.get("SVM_ADDRESS", "6KPYDyuRnpuKcm1TerUmwLd2BcaihvhF4Ccrr8beruu2") # Example: protected resource URL and metadata resource_url = "https://api.example.com/weather" resource_description = "Weather data" resource_mime_type = "application/json" payment_required = { "x402Version": 2, "error": "PAYMENT-SIGNATURE header is required", "resource": { "url": resource_url, "description": resource_description, "mimeType": resource_mime_type, }, "accepts": [ { "scheme": "exact", "network": "eip155:84532", "amount": "10000", "asset": "0x036CbD53842c5426634e7929541eC2318f3dCF7e", "payTo": EVM_PAY_TO, "maxTimeoutSeconds": 60, "extra": {"name": "USDC", "version": "2"}, }, { "scheme": "exact", "network": "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdpKuc147dw2N9d", "amount": "1000000", "asset": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", "payTo": SVM_PAY_TO, "maxTimeoutSeconds": 60, "extra": {"feePayer": "2wKupLR9q6wXYppw8Gr2NvWxKBUqm4PPJKkQfoxHDBg4"}, }, ], "extensions": {}, } payment_required_b64 = base64.b64encode(json.dumps(payment_required).encode()).decode() ``` *** ## 4. Send the 402 response with PAYMENT-REQUIRED Set the **PAYMENT-REQUIRED** header to the base64 string and return status **402**. ```python theme={null} def send_402(start_response, payment_required_b64: str) -> None: status = "402 Payment Required" headers = [ ("Content-Type", "application/json"), ("PAYMENT-REQUIRED", payment_required_b64), ] start_response(status, headers) # WSGI: return body as iterable; Flask/FastAPI use their own response API ``` Example with Flask: ```python theme={null} from flask import Flask, request, jsonify app = Flask(__name__) @app.route("/weather") def weather(): if not request.headers.get("PAYMENT-SIGNATURE"): return ( jsonify({"error": "Payment required"}), 402, {"PAYMENT-REQUIRED": payment_required_b64}, ) # Otherwise: verify/settle payment (e.g. via facilitator), then return 200 + resource return jsonify({"weather": "sunny", "temperature": 70}) ``` Example with FastAPI: ```python theme={null} from fastapi import FastAPI, Request, Response app = FastAPI() @app.get("/weather") def weather(request: Request): if not request.headers.get("payment-signature"): return Response( content='{"error":"Payment required"}', status_code=402, media_type="application/json", headers={"PAYMENT-REQUIRED": payment_required_b64}, ) # Otherwise: verify/settle payment (e.g. via facilitator), then return 200 + resource return {"weather": "sunny", "temperature": 70} ``` *** ## Summary | Step | Action | | ---- | ------------------------------------------------------------------------------------------------- | | 1 | Decide when payment is required (no or invalid PAYMENT-SIGNATURE). | | 2 | Build the payment-requirements dict: `x402Version`, `error`, `resource`, `accepts`, `extensions`. | | 3 | Base64-encode `json.dumps(payment_required)` and set the **PAYMENT-REQUIRED** header. | | 4 | Respond with status **402**. | For exact field types and facilitator behavior, see the [x402 Reference](/x402/reference). For a ready-made server, use the [Flask](/x402/servers/python/flask) or [FastAPI](/x402/servers/python/fastapi) quickstarts. ## Need help? Have questions or want to connect with other developers? Join our Discord server. # Express Source: https://docs.payai.network/x402/servers/typescript/express ## Getting started with Express Start accepting x402 payments in your Express server in 2 minutes. You can find the full code for this example [here](https://github.com/x402-foundation/x402/tree/main/examples/typescript/servers/express). ### Step 1: Create a project and install dependencies Use your favorite package manager: ##### npm ```bash theme={null} mkdir my-server && cd my-server npm init -y && npm pkg set type=module npm install express dotenv @payai/facilitator @x402/core @x402/evm @x402/express @x402/extensions @x402/svm npm install -D typescript tsx @types/express ``` ##### pnpm ```bash theme={null} mkdir my-server && cd my-server pnpm init && pnpm pkg set type=module pnpm add express dotenv @payai/facilitator @x402/core @x402/evm @x402/express @x402/extensions @x402/svm pnpm add -D typescript tsx @types/express ``` ##### bun ```bash theme={null} mkdir my-server && cd my-server bun init -y bun add express dotenv @payai/facilitator @x402/core @x402/evm @x402/express @x402/extensions @x402/svm bun add -d typescript @types/express ``` This is the same dependency set as the [upstream Express example](https://github.com/x402-foundation/x402/tree/main/examples/typescript/servers/express). ### Step 2: Set your environment variables Create a `.env` file in the project root and set: ```env theme={null} EVM_ADDRESS=0x... # EVM wallet address to receive payments SVM_ADDRESS=... # Solana wallet address to receive payments ``` `@payai/facilitator` automatically connects to the PayAI facilitator — no URL configuration needed. ### Step 3: Preview the server code Create `index.ts`: It loads your env, applies the x402 payment middleware, defines example routes, and logs the server URL. ```ts theme={null} import { config } from "dotenv"; import express from "express"; import { paymentMiddleware, x402ResourceServer } from "@x402/express"; import { ExactEvmScheme } from "@x402/evm/exact/server"; import { ExactSvmScheme } from "@x402/svm/exact/server"; import { HTTPFacilitatorClient } from "@x402/core/server"; import { declareDiscoveryExtension } from "@x402/extensions/bazaar"; import { facilitator } from "@payai/facilitator"; config(); const evmAddress = process.env.EVM_ADDRESS as `0x${string}`; const svmAddress = process.env.SVM_ADDRESS; if (!evmAddress || !svmAddress) { console.error("Missing required environment variables"); process.exit(1); } const facilitatorClient = new HTTPFacilitatorClient(facilitator); const app = express(); app.use( paymentMiddleware( { "GET /weather": { accepts: [ { scheme: "exact", price: "$0.001", network: "eip155:84532", payTo: evmAddress, }, { scheme: "exact", price: "$0.001", network: "solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1", payTo: svmAddress, }, ], description: "Weather data", mimeType: "application/json", serviceName: "Weather API", tags: ["weather", "api"], extensions: { ...declareDiscoveryExtension({ output: { example: { report: { weather: "sunny", temperature: 70, }, }, }, }), }, }, }, new x402ResourceServer(facilitatorClient) .register("eip155:84532", new ExactEvmScheme()) .register("solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1", new ExactSvmScheme()), ), ); app.get("/weather", (req, res) => { res.send({ report: { weather: "sunny", temperature: 70, }, }); }); app.listen(4021, () => { console.log(`Server listening at http://localhost:${4021}`); }); ``` ### Step 4: Run the server ```bash theme={null} npx tsx index.ts ``` Your server is now accepting 402 payments! ### Step 5: Test the server You can test payments against your server locally by following the [fetch example](/x402/clients/typescript/fetch) or the [axios example](/x402/clients/typescript/axios). ## Going to production This setup works on the **free tier** out of the box — no API keys required. When you're ready for production, create a merchant account at [merchant.payai.network](https://merchant.payai.network), get your API keys, and add them to your `.env`: ```env theme={null} PAYAI_API_KEY_ID=your-key-id PAYAI_API_KEY_SECRET=your-key-secret ``` The `@payai/facilitator` package automatically detects these environment variables and authenticates your requests to the facilitator. See [Facilitator Pricing](/x402/facilitators/pricing) for tier details and [Facilitator Authentication](/x402/facilitators/authentication) for the full protocol reference. ## x402 reference For a deeper dive into message shapes, headers, verification and settlement responses, see the [x402 Reference](/x402/reference). ## Need help? Have questions or want to connect with other developers? Join our Discord server. # Hono Source: https://docs.payai.network/x402/servers/typescript/hono ## Getting started with Hono Start accepting x402 payments in your Hono server in 2 minutes. You can find the full code for this example [here](https://github.com/x402-foundation/x402/tree/main/examples/typescript/servers/hono). ### Step 1: Create a project and install dependencies Use your favorite package manager: ##### npm ```bash theme={null} mkdir my-server && cd my-server npm init -y && npm pkg set type=module npm install hono @hono/node-server dotenv @payai/facilitator @x402/core @x402/evm @x402/extensions @x402/hono @x402/svm npm install -D typescript tsx ``` ##### pnpm ```bash theme={null} mkdir my-server && cd my-server pnpm init && pnpm pkg set type=module pnpm add hono @hono/node-server dotenv @payai/facilitator @x402/core @x402/evm @x402/extensions @x402/hono @x402/svm pnpm add -D typescript tsx ``` ##### bun ```bash theme={null} mkdir my-server && cd my-server bun init -y bun add hono @hono/node-server dotenv @payai/facilitator @x402/core @x402/evm @x402/extensions @x402/hono @x402/svm bun add -d typescript ``` This is the same dependency set as the [upstream Hono example](https://github.com/x402-foundation/x402/tree/main/examples/typescript/servers/hono). ### Step 2: Set your environment variables Create a `.env` file in the project root and set: ```env theme={null} EVM_ADDRESS=0x... # EVM wallet address to receive payments SVM_ADDRESS=... # Solana wallet address to receive payments ``` `@payai/facilitator` automatically connects to the PayAI facilitator — no URL configuration needed. ### Step 3: Preview the server code Create `index.ts`: It loads your env, applies the x402 payment middleware, defines example routes, and logs the server URL. ```ts theme={null} import { config } from "dotenv"; import { paymentMiddleware, x402ResourceServer } from "@x402/hono"; import { ExactEvmScheme } from "@x402/evm/exact/server"; import { ExactSvmScheme } from "@x402/svm/exact/server"; import { HTTPFacilitatorClient } from "@x402/core/server"; import { declareDiscoveryExtension } from "@x402/extensions/bazaar"; import { facilitator } from "@payai/facilitator"; import { Hono } from "hono"; import { serve } from "@hono/node-server"; config(); const evmAddress = process.env.EVM_ADDRESS as `0x${string}`; const svmAddress = process.env.SVM_ADDRESS; if (!evmAddress || !svmAddress) { console.error("Missing required environment variables"); process.exit(1); } const facilitatorClient = new HTTPFacilitatorClient(facilitator); const app = new Hono(); app.use( paymentMiddleware( { "GET /weather": { accepts: [ { scheme: "exact", price: "$0.001", network: "eip155:84532", payTo: evmAddress, }, { scheme: "exact", price: "$0.001", network: "solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1", payTo: svmAddress, }, ], description: "Weather data", mimeType: "application/json", serviceName: "Weather API", tags: ["weather", "api"], extensions: { ...declareDiscoveryExtension({ output: { example: { report: { weather: "sunny", temperature: 70, }, }, }, }), }, }, }, new x402ResourceServer(facilitatorClient) .register("eip155:84532", new ExactEvmScheme()) .register("solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1", new ExactSvmScheme()), ), ); app.get("/weather", c => { return c.json({ report: { weather: "sunny", temperature: 70, }, }); }); serve({ fetch: app.fetch, port: 4021, }); console.log(`Server listening at http://localhost:4021`); ``` ### Step 4: Run the server ```bash theme={null} npx tsx index.ts ``` Your server is now accepting x402 payments! ### Step 5: Test the server You can test payments against your server locally by: * Following the [fetch example](/x402/clients/typescript/fetch) or the [axios example](/x402/clients/typescript/axios) * Creating a paywall using `@x402/paywall` for EVM chains or `x402-solana-react` for Solana ## Going to production This setup works on the **free tier** out of the box — no API keys required. When you're ready for production, create a merchant account at [merchant.payai.network](https://merchant.payai.network), get your API keys, and add them to your `.env`: ```env theme={null} PAYAI_API_KEY_ID=your-key-id PAYAI_API_KEY_SECRET=your-key-secret ``` The `@payai/facilitator` package automatically detects these environment variables and authenticates your requests to the facilitator. See [Facilitator Pricing](/x402/facilitators/pricing) for tier details and [Facilitator Authentication](/x402/facilitators/authentication) for the full protocol reference. ## x402 reference For a deeper dive into message shapes, headers, verification and settlement responses, see the [x402 Reference](/x402/reference). ## Need help? Have questions or want to connect with other developers? Join our Discord server. # Manual Flow Source: https://docs.payai.network/x402/servers/typescript/manual-flow Step-by-step guide to building the x402 PAYMENT-REQUIRED response manually in TypeScript without server SDK or middleware. ## Manual x402 server flow (TypeScript) This page shows how to respond with **402 Payment Required** and build the **PAYMENT-REQUIRED** header by hand in TypeScript—no `@x402/express` or other server SDK. You decide when payment is required, construct the payment-requirements payload, base64-encode it, and send it in the response. For production you’ll usually use the [Express](/x402/servers/typescript/express), [Hono](/x402/servers/typescript/hono), or [Next.js](/x402/servers/typescript/nextjs) quickstarts; this tutorial is for agents or environments that need a minimal implementation or want to understand the protocol from the server’s perspective. *** ## 1. When to return 402 When a request hits a protected route: * If the request **does not** include a valid **PAYMENT-SIGNATURE** header (or the payment is invalid/expired), respond with **402** and a **PAYMENT-REQUIRED** header so the client knows how to pay. * If the request **does** include a valid payment, you (or your facilitator) verify/settle it and then respond with **200** and the resource. Verification and settlement are typically done via the [PayAI Facilitator](/x402/facilitators/introduction) or your own backend; this page focuses only on building the 402 response. *** ## 2. Build the payment-requirements payload The **PAYMENT-REQUIRED** header must contain **base64-encoded JSON**. The JSON object has this shape (see [x402 Reference §5.1](/x402/reference)): | Field | Type | Description | | ------------- | -------- | -------------------------------------------------------------------------------------------------- | | `x402Version` | `number` | Protocol version; use **2** | | `error` | `string` | Human-readable message (e.g. why payment is required) | | `resource` | `object` | `url`, `description`, and optional `mimeType` for the protected resource | | `accepts` | `array` | List of payment options (scheme, network, amount, asset, payTo, maxTimeoutSeconds, optional extra) | | `extensions` | `object` | Reserved; use `{}` | Each item in **accepts** describes one way the client can pay (e.g. USDC on Base Sepolia, or USDC on Solana). The client will choose one and send it back in PAYMENT-SIGNATURE. *** ## 3. Example: building the payload in TypeScript Define the payload object, then encode it as base64 and set the header. Use your own recipient addresses (`payTo`), asset addresses, and amounts. ```ts theme={null} import type { IncomingMessage, ServerResponse } from "node:http"; function b64Encode(s: string): string { return Buffer.from(s, "utf-8").toString("base64"); } // Your wallet addresses (from env or config) const EVM_PAY_TO = process.env.EVM_ADDRESS ?? "0x209693Bc6afc0C5328bA36FaF03C514EF312287C"; const SVM_PAY_TO = process.env.SVM_ADDRESS ?? "6KPYDyuRnpuKcm1TerUmwLd2BcaihvhF4Ccrr8beruu2"; // Example: protected resource URL and metadata const resourceUrl = "https://api.example.com/weather"; const resourceDescription = "Weather data"; const resourceMimeType = "application/json"; const paymentRequired = { x402Version: 2, error: "PAYMENT-SIGNATURE header is required", resource: { url: resourceUrl, description: resourceDescription, mimeType: resourceMimeType, }, accepts: [ { scheme: "exact", network: "eip155:84532", amount: "10000", asset: "0x036CbD53842c5426634e7929541eC2318f3dCF7e", payTo: EVM_PAY_TO, maxTimeoutSeconds: 60, extra: { name: "USDC", version: "2" }, }, { scheme: "exact", network: "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdpKuc147dw2N9d", amount: "1000000", asset: "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", payTo: SVM_PAY_TO, maxTimeoutSeconds: 60, extra: { feePayer: "2wKupLR9q6wXYppw8Gr2NvWxKBUqm4PPJKkQfoxHDBg4" }, }, ], extensions: {}, }; const paymentRequiredB64 = b64Encode(JSON.stringify(paymentRequired)); ``` *** ## 4. Send the 402 response with PAYMENT-REQUIRED Set the **PAYMENT-REQUIRED** header to the base64 string and return status **402**. ```ts theme={null} function send402(res: ServerResponse, paymentRequiredB64: string): void { res.writeHead(402, { "Content-Type": "application/json", "PAYMENT-REQUIRED": paymentRequiredB64, }); res.end(JSON.stringify({ error: "Payment required" })); } ``` Example in a minimal request handler: ```ts theme={null} function handleGetWeather(req: IncomingMessage, res: ServerResponse): void { const hasPayment = req.headers["payment-signature"]; if (!hasPayment) { send402(res, paymentRequiredB64); return; } // Otherwise: verify/settle payment (e.g. via facilitator), then return 200 + resource res.writeHead(200, { "Content-Type": "application/json" }); res.end(JSON.stringify({ weather: "sunny", temperature: 70 })); } ``` *** ## Summary | Step | Action | | ---- | --------------------------------------------------------------------------------------------------- | | 1 | Decide when payment is required (no or invalid PAYMENT-SIGNATURE). | | 2 | Build the payment-requirements object: `x402Version`, `error`, `resource`, `accepts`, `extensions`. | | 3 | Base64-encode `JSON.stringify(paymentRequired)` and set the **PAYMENT-REQUIRED** header. | | 4 | Respond with status **402**. | For exact field types and facilitator behavior, see the [x402 Reference](/x402/reference). For a ready-made server, use the [Express](/x402/servers/typescript/express), [Hono](/x402/servers/typescript/hono), or [Next.js](/x402/servers/typescript/nextjs) quickstarts. ## Need help? Have questions or want to connect with other developers? Join our Discord server. # Nextjs Source: https://docs.payai.network/x402/servers/typescript/nextjs ## Getting started with Next.js Start accepting x402 payments in your Next.js app in 2 minutes. You can find the full code for this example [here](https://github.com/x402-foundation/x402/tree/main/examples/typescript/fullstack/next). ### Step 1: Create an app and install dependencies Scaffold a Next.js app, then add the x402 packages: ```bash theme={null} npx create-next-app@latest my-app --typescript --app cd my-app npm install @payai/facilitator @x402/core @x402/evm @x402/extensions @x402/next @x402/paywall @x402/svm ``` This is the same dependency set as the [upstream Next.js example](https://github.com/x402-foundation/x402/tree/main/examples/typescript/fullstack/next). ### Step 2: Set your environment variables Create a `.env.local` file in the project root and fill in your values. ```env theme={null} EVM_ADDRESS=0x... # EVM wallet address to receive payments SVM_ADDRESS=... # Solana wallet address to receive payments ``` Optionally set `APP_NAME` and `APP_LOGO` to brand the paywall shown to buyers: ```env theme={null} APP_NAME=Next x402 Demo APP_LOGO=/x402-icon-blue.png ``` `@payai/facilitator` automatically connects to the PayAI facilitator — no URL configuration needed. ### Step 3: Explore the app structure The example uses a **proxy** for page routes and **withX402** for API routes: ```text theme={null} ├── app/ │ ├── api/ # API routes (use withX402 for payment) │ ├── protected/ # Protected page route │ ├── layout.tsx │ └── page.tsx ├── proxy.ts # x402 payment proxy (page routes) ├── next.config.ts └── package.json ``` * **paymentProxy** (in `proxy.ts`) protects page routes and returns a paywall when payment is required. * **withX402** wraps individual API route handlers so payment is settled only after a successful response. Both routes also call `declareDiscoveryExtension`, which lists them in the [PayAI bazaar](/x402/facilitators/bazaar) so buyers and agents can discover them. ### Step 4: Preview the example routes #### Protected Page Route The `/protected` page is protected using `paymentProxy`. The proxy in `proxy.ts` configures the resource server, paywall, and protected routes: ```typescript theme={null} // proxy.ts import { paymentProxy } from "@x402/next"; import { x402ResourceServer, HTTPFacilitatorClient } from "@x402/core/server"; import { ExactEvmScheme } from "@x402/evm/exact/server"; import { ExactSvmScheme } from "@x402/svm/exact/server"; import { createPaywall } from "@x402/paywall"; import { evmPaywall } from "@x402/paywall/evm"; import { svmPaywall } from "@x402/paywall/svm"; import { declareDiscoveryExtension } from "@x402/extensions/bazaar"; import { facilitator } from "@payai/facilitator"; export const evmAddress = process.env.EVM_ADDRESS as `0x${string}`; export const svmAddress = process.env.SVM_ADDRESS; if (!evmAddress || !svmAddress) { console.error("❌ EVM_ADDRESS and SVM_ADDRESS environment variables are required"); process.exit(1); } // Connect to the PayAI facilitator (no URL configuration needed) const facilitatorClient = new HTTPFacilitatorClient(facilitator); // Create x402 resource server export const server = new x402ResourceServer(facilitatorClient); // Register schemes server.register("eip155:*", new ExactEvmScheme()); server.register("solana:*", new ExactSvmScheme()); // Build paywall export const paywall = createPaywall() .withNetwork(evmPaywall) .withNetwork(svmPaywall) .withConfig({ appName: process.env.APP_NAME || "Next x402 Demo", appLogo: process.env.APP_LOGO || "/x402-icon-blue.png", testnet: true, }) .build(); // Build proxy export const proxy = paymentProxy( { "/protected": { accepts: [ { scheme: "exact", price: "$0.001", network: "eip155:84532", // base-sepolia payTo: evmAddress, }, { scheme: "exact", price: "$0.001", network: "solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1", // solana devnet payTo: svmAddress, }, ], description: "Premium music: x402 Remix", mimeType: "text/html", extensions: { ...declareDiscoveryExtension({}), }, }, }, server, undefined, // paywallConfig (using custom paywall instead) paywall, // custom paywall provider ); // Configure which paths the proxy should run on export const config = { matcher: ["/protected/:path*"], }; ``` #### Weather API Route (using withX402) The `/api/weather` route demonstrates the `withX402` wrapper for individual API routes: ```typescript theme={null} // app/api/weather/route.ts import { NextRequest, NextResponse } from "next/server"; import { withX402 } from "@x402/next"; import { declareDiscoveryExtension } from "@x402/extensions/bazaar"; import { server, paywall, evmAddress, svmAddress } from "../../../proxy"; /** * Weather API endpoint handler * * This handler returns weather data after payment verification. * Payment is only settled after a successful response (status < 400). * * @param _ - Incoming Next.js request * @returns JSON response with weather data */ const handler = async (_: NextRequest) => { return NextResponse.json( { report: { weather: "sunny", temperature: 72, }, }, { status: 200 }, ); }; /** * Protected weather API endpoint using withX402 wrapper * * This demonstrates the v2 withX402 wrapper for individual API routes. * Unlike middleware, withX402 guarantees payment settlement only after * the handler returns a successful response (status < 400). */ export const GET = withX402( handler, { accepts: [ { scheme: "exact", price: "$0.001", network: "eip155:84532", // base-sepolia payTo: evmAddress, }, { scheme: "exact", price: "$0.001", network: "solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1", // solana devnet payTo: svmAddress, }, ], description: "Access to weather API", mimeType: "application/json", extensions: { ...declareDiscoveryExtension({ output: { example: { report: { weather: "sunny", temperature: 72, }, }, }, }), }, }, server, undefined, // paywallConfig (using custom paywall from proxy.ts) paywall, ); ``` #### paymentProxy vs withX402 The `paymentProxy` function is used to protect page routes. It can also protect API routes, however this will charge clients for failed API responses. The `withX402` function wraps API route handlers. This is the recommended approach to protect API routes as it guarantees payment settlement only AFTER successful API responses (status \< 400). | Approach | Use Case | | -------------- | -------------------------------------------------------------------------------------- | | `paymentProxy` | Protecting page routes or multiple routes with a single configuration | | `withX402` | Protecting individual API routes where you need precise control over settlement timing | ### Step 5: Run the server ```bash theme={null} npm run dev ``` Your Next.js app is now accepting x402 payments! ### Step 6: Test the server The example includes a built-in paywall at the home page. Navigate to [http://localhost:3000](http://localhost:3000) to test payments directly. You can also test programmatically by following the [fetch example](/x402/clients/typescript/fetch) or [axios example](/x402/clients/typescript/axios). ## Going to production This setup works on the **free tier** out of the box — no API keys required. When you're ready for production, create a merchant account at [merchant.payai.network](https://merchant.payai.network), get your API keys, and add them to your `.env`: ```env theme={null} PAYAI_API_KEY_ID=your-key-id PAYAI_API_KEY_SECRET=your-key-secret ``` The `@payai/facilitator` package automatically detects these environment variables and authenticates your requests to the facilitator. See [Facilitator Pricing](/x402/facilitators/pricing) for tier details and [Facilitator Authentication](/x402/facilitators/authentication) for the full protocol reference. ## x402 reference For a deeper dive into message shapes, headers, verification and settlement responses, see the [x402 Reference](/x402/reference). ## Need help? Have questions or want to connect with other developers? Join our Discord server. # Supported Networks Source: https://docs.payai.network/x402/supported-networks See networks that are supported by the PayAI Facilitator for the x402 protocol | Network Name | V1 Network String | V2 CAIP-2 ID | | ------------------ | -------------------- | ----------------------------------------- | | Arbitrum One | `arbitrum` | `eip155:42161` | | Arbitrum Sepolia | `arbitrum-sepolia` | `eip155:421614` | | Avalanche | `avalanche` | `eip155:43114` | | Avalanche Fuji | `avalanche-fuji` | `eip155:43113` | | Base | `base` | `eip155:8453` | | Base Sepolia | `base-sepolia` | `eip155:84532` | | Polygon | `polygon` | `eip155:137` | | Polygon Amoy | `polygon-amoy` | `eip155:80002` | | Sei | `sei` | `eip155:1329` | | Sei Testnet | `sei-testnet` | `eip155:713715` | | SKALE Base | `skale-base` | `eip155:1187947933` | | SKALE Base Sepolia | `skale-base-sepolia` | `eip155:324705682` | | Solana | `solana` | `solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp` | | Solana Devnet | `solana-devnet` | `solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1` | | X Layer | `xlayer` | `eip155:196` | | X Layer Testnet | `xlayer-testnet` | `eip155:1952` | ## Need help? Have questions or want to connect with other developers? Join our Discord server.