Charge agents per call
Agents don't sign up for plans. They discover a tool, decide it's worth a fraction of a cent, and pay per call — if your API can take the money. Today that takes one decorator.
The monetization story for agent-facing APIs is currently a stack of workarounds: a billing proxy in front of the MCP server, a spreadsheet mapping tool names to prices, a cron job reconciling gateway logs against invoices. The price of an operation lives in three places, none of them the code that implements it.
AgentBack's answer follows the framework's one rule: the declaration lives on the boundary, and everything derives from it.
@price('$0.001')
@tool('get_forecast', {input: ForecastIn, output: ForecastOut})
async forecast(input: z.infer<typeof ForecastIn>) { … }
@price('$0.01')
@get('/premium', {response: Premium})
async premium() { … }
Two consumers, one declaration
Metering reads it first. With
MeteringComponent installed, every call's usage event now
carries cost and units alongside the
principal, operation, latency, and status it already had. The usage
log is billing-ready: StripeMeterSink streams those
events to Stripe metered billing as they happen, and
StripeUsageReporter replays a durable log in batch,
idempotently, with each event's id as the Stripe identifier. No
gateway, no reconciliation job — the same events that power your audit
trail price your invoices.
The payment gate reads it second.
installPriceGate(app, {rail}) refuses unpaid calls to
priced operations. Unpriced routes and tools pass through untouched,
so the gate is safe to install on a mixed surface.
app.component(MeteringComponent); // order matters: metering wraps
installPriceGate(app, {rail}); // the gate, so refusals are logged
// as payment_required — never billed
A refused call gets the framework's standard machine-actionable envelope, the same shape on a REST 402 and an MCP tool error:
{
"error": {
"statusCode": 402,
"code": "payment_required",
"message": "Payment required: 0.01 USD per call.",
"retryable": true,
"hint": "Pay per the attached 'challenge' (x402: retry with the X-PAYMENT header; MPP: open or top up a session), then retry the identical request.",
"challenge": {
"rail": "x402",
"accepts": [{"maxAmountRequired": "10000", "asset": "0xUSDC", "…": "…"}]
}
}
}
The agent reads the challenge, pays through its wallet or session, and
retries. A settled payment returns the receipt in the
x-payment-response header. Note where the asked amount
came from: the rail's requirements callback receives the
@price spec in its payment context, so "0.01 USD" becomes
"10000 USDC base units" in one place — there is no parallel price
table to drift.
Rails are pluggable, and that's the point
Which payment rail wins for agents is genuinely unsettled. x402 has
the protocol momentum and the crypto-native story; Stripe's metered
billing and MPP sessions are where most real invoices get paid today.
We don't think a framework should bet your revenue on that race.
PaymentRail is one interface with one question — is this
call paid for? — and x402, MPP sessions, and Stripe usage-log billing
are adapters behind it. Pricing stays on the decorator either way.
Swap the rail; the controllers don't know.
The honest caveat: framework-native pricing only gates and meters. Custody, settlement, refunds, tax — those stay with the processor and facilitator, where they belong. What the framework removes is the part that kept drifting: the mapping from "this operation" to "this price" to "this invoice line."
The runnable version is examples/hello-x402 — a paid route and a paid MCP tool behind a stand-in x402 facilitator, with the usage log printed on shutdown.