---
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)