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 fit is exact
Section titled “The fit is exact”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 phase | costQL price | the guarantee |
|---|---|---|
authorize — amount = max the client will allow | basis: "predicted" — a quote, before the query runs | the safe ceiling; never under-prices |
settle — amount = actual consumed (≤ max) | basis: "measured" — an exact receipt, after it ran | the 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.10Keeping 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.
Authorize: price the query, answer 402
Section titled “Authorize: price the query, answer 402”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 ceilingfrom 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 ondef 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 ceilingfunction 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
uptoceiling 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.
What ships today, and what you wire
Section titled “What ships today, and what you wire”- The ceiling (authorize) is one call, today. T1 prices any GraphQL endpoint
black-box and returns a safe ceiling — enough to authorize an
uptopayment right now, in Python or JS, with no server changes. - The exact settle needs the trace. A
basis: "measured"receipt readsextensions.cost_trace, which means instrumenting for T2/T3. Until you do, you have two honest options: settle at the ceiling (the plainexactscheme — safe, just not generous), or settle on the T1 wall-clock proxy. Both stay at-or-above nothing the client didn’t authorize. uptois experimental in x402. If your facilitator doesn’t support it yet, the same mapping works with the stableexactscheme 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.