--- title: "Upto" description: "Usage-based x402 payments where the buyer authorizes a maximum and the seller charges actual usage." --- The `upto` scheme lets a seller advertise a maximum price for one request, then settle for the actual amount used. The buyer signs once for the maximum, and the server chooses a final amount that is less than or equal to that maximum. Use `upto` for one-request usage metering, such as LLM token generation, bandwidth, compute time, or dynamic data queries. ### Server Setup Set the route `price` to the maximum authorized amount. In the handler, use settlement overrides to charge the actual amount. ```typescript import { paymentMiddleware, setSettlementOverrides, x402ResourceServer } from "@x402/express"; import { UptoEvmScheme } from "@x402/evm/upto/server"; const resourceServer = new x402ResourceServer(facilitatorClient) .register("eip155:84532", new UptoEvmScheme()); app.use(paymentMiddleware({ "GET /api/generate": { accepts: { scheme: "upto", price: "$0.10", network: "eip155:84532", payTo: "0xYourAddress", }, description: "AI text generation billed by usage", }, }, resourceServer)); app.get("/api/generate", (req, res) => { const actualUsage = computeActualCost(); setSettlementOverrides(res, { amount: String(actualUsage) }); res.json({ result: "..." }); }); ``` ```typescript import { base58 } from "@scure/base"; import { createKeyPairSignerFromBytes } from "@solana/kit"; import { paymentMiddleware, setSettlementOverrides, x402ResourceServer } from "@x402/express"; import { UptoSvmScheme } from "@x402/svm/upto/server"; import { HTTPFacilitatorClient } from "@x402/core/server"; const receiverAuthorizerSigner = await createKeyPairSignerFromBytes( base58.decode(process.env.SVM_RECEIVER_AUTHORIZER_PRIVATE_KEY), ); const resourceServer = new x402ResourceServer(facilitatorClient) .register( "solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1", new UptoSvmScheme({ receiverAuthorizerSigner, rpcUrl: process.env.SVM_RPC_URL, // optional: embeds recentBlockhash in 402 }), ); app.use(paymentMiddleware({ "GET /api/generate": { accepts: { scheme: "upto", price: "$0.10", network: "solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1", payTo: "YourSolanaAddress", }, description: "AI text generation billed by usage", }, }, resourceServer)); app.get("/api/generate", (req, res) => { const actualUsage = computeActualCost(); setSettlementOverrides(res, { amount: String(actualUsage) }); res.json({ result: "..." }); }); ``` ```go routes := x402http.RoutesConfig{ "GET /api/generate": { Accepts: x402http.PaymentOptions{ { Scheme: "upto", Price: "$0.10", Network: "eip155:84532", PayTo: "0xYourAddress", }, }, Description: "AI text generation billed by usage", }, } mux.HandleFunc("GET /api/generate", func(w http.ResponseWriter, r *http.Request) { actualUsage := computeActualCost() nethttpmw.SetSettlementOverrides(w, &x402.SettlementOverrides{ Amount: fmt.Sprintf("%d", actualUsage), }) _ = json.NewEncoder(w).Encode(map[string]string{"result": "..."}) }) ``` ```go import ( x402 "github.com/x402-foundation/x402/go/v2" x402http "github.com/x402-foundation/x402/go/v2/http" ginmw "github.com/x402-foundation/x402/go/v2/http/gin" uptosvm "github.com/x402-foundation/x402/go/v2/mechanisms/svm/upto/server" svmsigners "github.com/x402-foundation/x402/go/v2/signers/svm" ) authorizer, err := svmsigners.NewReceiverAuthorizerSignerFromPrivateKey( os.Getenv("SVM_RECEIVER_AUTHORIZER_PRIVATE_KEY"), ) if err != nil { log.Fatal(err) } r.Use(ginmw.X402Payment(ginmw.Config{ Routes: x402http.RoutesConfig{ "GET /api/generate": { Accepts: x402http.PaymentOptions{ { Scheme: "upto", Price: "$0.10", Network: "solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1", PayTo: "YourSolanaAddress", }, }, Description: "AI text generation billed by usage", }, }, Facilitator: facilitatorClient, Schemes: []ginmw.SchemeConfig{ { Network: x402.Network("solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1"), Server: uptosvm.NewUptoSvmScheme(&uptosvm.Config{ ReceiverAuthorizerSigner: authorizer, RPCURL: os.Getenv("SVM_RPC_URL"), }), }, }, })) r.GET("/api/generate", func(c *gin.Context) { actualUsage := computeActualCost() ginmw.SetSettlementOverrides(c, &x402.SettlementOverrides{ Amount: fmt.Sprintf("%d", actualUsage), }) c.JSON(http.StatusOK, gin.H{"result": "..."}) }) ``` ```python from fastapi import FastAPI, Response from x402.http import FacilitatorConfig, HTTPFacilitatorClient, PaymentOption from x402.http.middleware.fastapi import PaymentMiddlewareASGI, set_settlement_overrides from x402.http.types import RouteConfig from x402.mechanisms.evm.upto import UptoEvmServerScheme from x402.server import x402ResourceServer app = FastAPI() facilitator = HTTPFacilitatorClient(FacilitatorConfig(url="https://x402.org/facilitator")) server = x402ResourceServer(facilitator) server.register("eip155:84532", UptoEvmServerScheme()) routes = { "GET /api/generate": RouteConfig( accepts=[ PaymentOption( scheme="upto", price="$0.10", network="eip155:84532", pay_to="0xYourAddress", ) ], description="AI text generation billed by usage", ) } app.add_middleware(PaymentMiddlewareASGI, routes=routes, server=server) @app.get("/api/generate") async def generate(response: Response) -> dict[str, str]: actual_usage = compute_actual_cost() set_settlement_overrides(response, {"amount": str(actual_usage)}) return {"result": "..."} ``` ### Client Setup Register the `upto` scheme alongside `exact` if your client may call both fixed-price and usage-based resources. ```typescript import { x402Client } from "@x402/core/client"; import { ExactEvmScheme } from "@x402/evm/exact/client"; import { UptoEvmScheme } from "@x402/evm/upto/client"; import { privateKeyToAccount } from "viem/accounts"; const signer = privateKeyToAccount(process.env.EVM_PRIVATE_KEY as `0x${string}`); const client = new x402Client(); client.register("eip155:*", new ExactEvmScheme(signer)); client.register("eip155:*", new UptoEvmScheme(signer)); ``` ```typescript import { x402Client } from "@x402/core/client"; import { ExactSvmScheme } from "@x402/svm/exact/client"; import { UptoSvmScheme } from "@x402/svm/upto/client"; import { createKeyPairSignerFromBytes } from "@solana/kit"; import { base58 } from "@scure/base"; const signer = await createKeyPairSignerFromBytes( base58.decode(process.env.SVM_PRIVATE_KEY), ); const client = new x402Client(); client.register("solana:*", new ExactSvmScheme(signer)); // fixed-price services client.register("solana:*", new UptoSvmScheme(signer)); // usage-based services ``` ```go import ( x402 "github.com/x402-foundation/x402/go/v2" exactevm "github.com/x402-foundation/x402/go/v2/mechanisms/evm/exact/client" uptoevm "github.com/x402-foundation/x402/go/v2/mechanisms/evm/upto/client" evmsigners "github.com/x402-foundation/x402/go/v2/signers/evm" ) evmSigner, err := evmsigners.NewClientSignerFromPrivateKey(os.Getenv("EVM_PRIVATE_KEY")) if err != nil { log.Fatal(err) } x402Client := x402.Newx402Client(). Register("eip155:*", exactevm.NewExactEvmScheme(evmSigner, nil)). Register("eip155:*", uptoevm.NewUptoEvmScheme(evmSigner, nil)) ``` ```go import ( x402 "github.com/x402-foundation/x402/go/v2" exactsvm "github.com/x402-foundation/x402/go/v2/mechanisms/svm/exact/client" uptosvm "github.com/x402-foundation/x402/go/v2/mechanisms/svm/upto/client" svmsigners "github.com/x402-foundation/x402/go/v2/signers/svm" ) svmSigner, err := svmsigners.NewClientSignerFromPrivateKey(os.Getenv("SVM_PRIVATE_KEY")) if err != nil { log.Fatal(err) } x402Client := x402.Newx402Client(). Register("solana:*", exactsvm.NewExactSvmScheme(svmSigner)). // fixed-price services Register("solana:*", uptosvm.NewUptoSvmScheme(svmSigner, nil)) // usage-based services ``` ```python import os from eth_account import Account from x402 import x402Client from x402.mechanisms.evm import EthAccountSigner from x402.mechanisms.evm.exact.register import register_exact_evm_client from x402.mechanisms.evm.upto import UptoEvmScheme account = Account.from_key(os.getenv("EVM_PRIVATE_KEY")) signer = EthAccountSigner(account) client = x402Client() register_exact_evm_client(client, signer) client.register("eip155:*", UptoEvmScheme(signer)) ``` ### Settlement Override Formats `amount` can be expressed as: | Format | Example | Meaning | |--------|---------|---------| | Raw atomic units | `"50000"` | Settle exactly 50,000 token base units | | Percentage | `"50%"` | Settle 50% of the route maximum | | Dollar price | `"$0.05"` | Convert a dollar-denominated route price to base units | Setting the amount to `"0"` means no charge for that request. On SVM, a zero-amount close still lands a transaction to release the escrowed deposit and channel rent. ### EVM Implementation `upto` on EVM uses Permit2 because the settled amount is not known when the buyer signs. The facilitator advertises a `facilitatorAddress` in the payment requirements, and the client binds the authorization to that facilitator. ### SVM Implementation `upto` on Solana uses the [payment-channels program](https://github.com/solana-foundation/payment-channels). The client escrows the ceiling amount in an onchain channel (`open`), and the server settles the actual amount with a signed voucher (`settle_and_seal` + `distribute`). The facilitator sponsors transaction fees and channel rent as a zero-share channel payee, and can always close abandoned channels to recover rent. The server must supply a `receiverAuthorizerSigner` — a hot key that signs settlement vouchers after metering. This key does not need to hold SOL or tokens. For custom facilitator implementations, use `UptoSvmScheme` from the facilitator package. The scheme's rent cleanup manager asynchronously seals and reclaims rent from abandoned channels: ```typescript import { toFacilitatorSvmSigner } from "@x402/svm"; import { UptoSvmScheme } from "@x402/svm/upto/facilitator"; const svmSigner = toFacilitatorSvmSigner(keypair); const svmUptoScheme = new UptoSvmScheme(svmSigner, { rpcUrl: process.env.SVM_RPC_URL, maxChannelLifetimeSecs: 3600, }); facilitator.register("solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1", svmUptoScheme); // Reclaim PDA rent from sealed/distributed channels const rentCleanup = svmUptoScheme.createRentCleanupManager( "solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1", ); rentCleanup.start({ intervalSecs: 60, discoveryIntervalSecs: 86_400 }); ``` ```go import ( uptosvm "github.com/x402-foundation/x402/go/v2/mechanisms/svm/upto/facilitator" ) maxChannelLifetimeSecs := 3600 scheme := uptosvm.NewUptoSvmScheme(svmSigner, &uptosvm.Config{ RPCURL: os.Getenv("SVM_RPC_URL"), MaxChannelLifetimeSecs: &maxChannelLifetimeSecs, }) facilitator.Register([]x402.Network{network}, scheme) // Reclaim PDA rent from abandoned, sealed, and distributed channels cleanup := scheme.NewRentCleanupManager(string(network)) cleanup.Start(ctx, uptosvm.StartConfig{ Interval: 5 * time.Minute, DiscoveryInterval: 24 * time.Hour, }) defer cleanup.Stop() ``` ### Examples * [TypeScript server example (EVM + SVM)](https://github.com/x402-foundation/x402/tree/main/examples/typescript/servers/upto) * [TypeScript facilitator example (EVM + SVM)](https://github.com/x402-foundation/x402/tree/main/examples/typescript/facilitator/upto) * [Go server example (SVM)](https://github.com/x402-foundation/x402/tree/main/examples/go/servers/upto) * [Go facilitator example (SVM)](https://github.com/x402-foundation/x402/tree/main/examples/go/facilitator/upto) ### Specs * [`upto` spec](https://github.com/x402-foundation/x402/blob/main/specs/schemes/upto/scheme_upto.md) * [`upto` EVM spec](https://github.com/x402-foundation/x402/blob/main/specs/schemes/upto/scheme_upto_evm.md) * [`upto` SVM spec](https://github.com/x402-foundation/x402/blob/main/specs/schemes/upto/scheme_upto_svm.md) ### See Also * [Payment schemes overview](/schemes/overview) * [Exact](/schemes/exact) * [Batch settlement](/schemes/batch-settlement)