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

<Tabs>
  <Tab title="TypeScript (EVM)">
    ```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: "..." });
    });
    ```
  </Tab>
  <Tab title="TypeScript (SVM)">
    ```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: "..." });
    });
    ```
  </Tab>
  <Tab title="Go (EVM)">
    ```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": "..."})
    })
    ```
  </Tab>
  <Tab title="Go (SVM)">
    ```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": "..."})
    })
    ```
  </Tab>
  <Tab title="Python">
    ```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": "..."}
    ```
  </Tab>
</Tabs>

### Client Setup

Register the `upto` scheme alongside `exact` if your client may call both fixed-price and usage-based resources.

<Tabs>
  <Tab title="TypeScript (EVM)">
    ```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));
    ```
  </Tab>
  <Tab title="TypeScript (SVM)">
    ```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
    ```
  </Tab>
  <Tab title="Go (EVM)">
    ```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))
    ```
  </Tab>
  <Tab title="Go (SVM)">
    ```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
    ```
  </Tab>
  <Tab title="Python">
    ```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))
    ```
  </Tab>
</Tabs>

### 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:

<Tabs>
  <Tab title="TypeScript">
    ```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 });
    ```
  </Tab>
  <Tab title="Go">
    ```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()
    ```
  </Tab>
</Tabs>

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