Skip to content

Paying per query with x402

x402 lets an HTTP server charge for a request in stablecoin: the server answers with 402 Payment Required, the client signs a payment, a facilitator settles it. Its experimental upto scheme splits the charge in two — the client authorizes a maximum up front, and the server settles the actual amount consumed, which must be less than or equal to that maximum.

That is the same two numbers costQL already returns for every query. This guide is the seam between them. It adds nothing to the pricing engine; it just points the two numbers costQL gives you at the two fields x402 asks for.

The upto scheme reuses one field, amount, in two phases: at verification it is the ceiling the client authorizes; at settlement it is the actual amount to charge ( the ceiling). costQL’s output contract returns a price under two bases that line up one-to-one:

x402 upto phasecostQL pricethe guarantee
authorizeamount = max the client will allowbasis: "predicted" — a quote, before the query runsthe safe ceiling; never under-prices
settleamount = actual consumed ( max)basis: "measured" — an exact receipt, after it ranthe real work the run caused; confidence: "exact"

The predicted price is what makes an upto authorization safe: because it is guaranteed never to fall below the real cost, you never authorize too little and get a legitimate query rejected at settlement. The measured price is what makes settlement fair: the payer is charged for the work their query actually caused, not a hand-guessed sticker price. The gap between them — the headroom costQL leaves above true cost — is simply never settled, and the client keeps it.

The one thing you own: the cost-unit → stablecoin rate

Section titled “The one thing you own: the cost-unit → stablecoin rate”

costQL prices in cost-units, never dollars (work_ms, or wall_time_ms at T1). x402 settles a stablecoin amount in atomic units. So you own exactly one number: the rate that turns a cost-unit into atomic units of your asset. Pick it once, apply it identically to the ceiling and the settle amount. Because the same rate multiplies the same cost-units, ceiling ≥ settle holds by construction — costQL’s guarantee carries straight through the conversion.

RATE = 100 # atomic units of USDC (6 decimals) per work_ms
# -> 1000 work_ms = 100_000 atomic = $0.10

Keeping the rate app-side is deliberate: costQL never needs to know your margin, your currency, or your business model, and the same pack prices the query whether you bill in USDC, credits, or nothing at all.

When a request arrives, quote it with your pack and hand the predicted price to x402 as the authorized maximum. This is the shipped, one-call path, and it works black-box at T1 on any GraphQL API — no server instrumentation needed to start.

# server: return 402 with a costQL-derived ceiling
from costql import PricingPack
pack = PricingPack.load("packs/your_pack.json")
RATE = 100 # atomic units per work_ms
def payment_required(query, variables=None):
quote = pack.quote(query, variables) # basis: "predicted"
max_amount = round(quote["price"] * RATE) # safe ceiling -> atomic units
return {
"scheme": "upto",
"network": "eip155:8453", # e.g. Base
"amount": str(max_amount), # the MAX the client authorizes
"asset": USDC_ADDRESS,
"payTo": YOUR_ADDRESS,
"maxTimeoutSeconds": 60,
}

The same call in TypeScript, using the npm costql package (the quote side ports one-to-one):

import { PricingPack } from "costql";
const pack = PricingPack.fromObject(packJson);
const RATE = 100n; // atomic units per work_ms
function paymentRequired(query: string, variables?: Record<string, unknown>) {
const quote = pack.quote(query, variables); // basis: "predicted"
const maxAmount = BigInt(Math.ceil(quote.price)) * RATE; // safe ceiling
return {
scheme: "upto",
network: "eip155:8453",
amount: maxAmount.toString(), // the MAX authorized
asset: USDC_ADDRESS,
payTo: YOUR_ADDRESS,
maxTimeoutSeconds: 60,
};
}

Round the ceiling up (ceil), never down: rounding is the one place you could accidentally authorize below cost, and up keeps the ceiling a ceiling.

Settle: measure the run, charge the actual

Section titled “Settle: measure the run, charge the actual”

After the query runs, settle at what it actually cost. The measured number is the total real work from the response’s extensions.cost_trace — the same seam that powers T2/T3. Its work_ms total is the price of the basis: "measured" receipt (see the measured examples in the contract). Convert it with the same RATE and pass it back to the facilitator’s settle step as the new amount.

# server: after executing the query with COSTQL_TIER on
def settlement_amount(response):
trace = response["extensions"]["cost_trace"] # emitted by your instrumented server
measured_ms = trace["work_ms"] # the exact work this run caused
return round(measured_ms * RATE) # actual -> atomic units, <= the ceiling
function settlementAmount(response: GraphQLResponse): bigint {
const measuredMs = response.extensions.cost_trace.work_ms; // exact work
return BigInt(Math.round(measuredMs * RATE)); // actual, <= ceiling
}

You then set this as the amount in the PaymentRequirements you pass to the facilitator’s /settle endpoint — per the upto scheme, that field carries the actual amount at settlement time. It is the ceiling you authorized, so the facilitator accepts it and the client is charged only for real work. Round the settle amount to nearest; the ceiling already covers any rounding slack.

Why a measured basis beats charging per token or byte

Section titled “Why a measured basis beats charging per token or byte”

x402’s own upto examples charge per token generated or per byte transferred — proxies you multiply by a hand-set unit price. costQL settles on the query’s measured cost instead, which is the honest basis for a GraphQL API and the two places hand-authored proxies break:

  • Batching. A field behind a DataLoader resolves a list of 100 in one round-trip. A per-item price over-charges it 100×; costQL prices the shared work once (on our share-heaviest test that cut error from 315% to 12% — see the Northwind case study). The upto ceiling stays honest and the settle stays fair.
  • Recursion. A cyclic query is the classic way to make a cheap-looking request expensive. costQL detects the cycle up front, flags it confidence: "low", and authorizes at a structural ceiling rather than a too-low guess (Rick & Morty case study) — so the authorization covers the blow-up before a token is spent.
  • The ceiling (authorize) is one call, today. T1 prices any GraphQL endpoint black-box and returns a safe ceiling — enough to authorize an upto payment right now, in Python or JS, with no server changes.
  • The exact settle needs the trace. A basis: "measured" receipt reads extensions.cost_trace, which means instrumenting for T2/T3. Until you do, you have two honest options: settle at the ceiling (the plain exact scheme — safe, just not generous), or settle on the T1 wall-clock proxy. Both stay at-or-above nothing the client didn’t authorize.
  • upto is experimental in x402. If your facilitator doesn’t support it yet, the same mapping works with the stable exact scheme by authorizing and settling on the predicted ceiling — you lose the measured refund, not the safety.

costQL prices; it does not move money. x402 moves money; it does not know what a query costs. The pricing engine is the product, and it stands on its own — this guide is just the adaptor that lets one feed the other.

costQL gives you a measured estimate, not a guarantee. Whether the prices fit your business is yours to verify. Provided as-is under Apache-2.0, with no warranty.