Price what the work was worth
Authorize a ceiling, then settle the amount a judge puts on the work — a model, a rubric, or your own rules.
upTo() exists because the work is not known when the money is authorized. The gate holds a ceiling, the handler calls payment.fulfill({ amount }), and only that amount settles.
That leaves a question the payment protocol cannot answer: what was the work worth? Counting tokens, rows, or milliseconds answers it for some services and not for others — two answers of the same length can be a fact restated and a week of reading.
This guide puts that decision behind an interface, so it can be a rule today and something better later.
The shape
export type Work = { question: string; answer: string; sources: number; charactersWritten: number };
export type Verdict = {
answered: boolean; // false settles nothing
amount: string; // what to settle, at or below the ceiling
confidence: number; // 0–1
reason: string; // shown to the buyer
judgedBy: string; // "rules", or a model version
};
export type Judge = (work: Work) => Promise<Verdict>;app.post('/research', tollstile(toll.price(upTo('$0.05'))), async (c) => {
const work = await research(await c.req.json());
const verdict = await judge(work);
const payment = c.get('payment');
// Nothing was answered, so nothing is charged: a 4xx releases the hold.
if (!verdict.answered) return c.json({ answered: false, pricing: explain(verdict) }, 422);
await payment.fulfill({ amount: verdict.amount });
return c.json({ answer: work.answer, pricing: explain(verdict) });
});The route does not know or care whether the judge is twenty lines of thresholds or a call to a model. Start with the thresholds; they are deterministic, free, and often enough.
Price the distribution, not the label
A judge that returns calibrated probabilities is not a classifier with extra fields. The distribution is the product, and the price that uses it is the expected value:
const expected = LEVELS.reduce((total, level, i) => total + level.micros * p[i], 0);30% a lookup, 55% a synthesis, 15% an investigation — at $0.01, $0.025 and $0.05 — is $0.0235. Not a tier. Money in Tollstile is micro-denominated, so fulfill({ amount: "$0.0235" }) settles exactly that.
Two reasons this is the right shape, not a flourish:
- It is unbiased. If the probabilities are calibrated, the average of what you charge converges on the average of what the work was worth. Rounding to the nearest bucket does not; it is a systematic transfer to whichever side the rounding favours.
- Uncertainty prices itself. A judge torn between two levels charges between them, with no threshold to tune. The confidence figure stops being a magic number in your code and becomes an explanation you can show the buyer.
A second question can discount the first. Asking is this grounded in the sources and multiplying by that probability means an invented investigation is priced as the lookup it really is — and the buyer can see the arithmetic in the receipt. If you ask whether an answer is grounded, send the sources in the state: a verification question with nothing to verify against is a guess, and it comes back low for everything.
Volume is the point
A System One model answers in hundreds of milliseconds and bills input tokens only, which is what makes per-request judging affordable at all. Ten questions judged in parallel:
judged 10 in 718 ms (each 278–718 ms) · charged $0.129884 of $0.50 authorizedThat is one HTTP call, one charge, ten independent prices — measured on demo.tollstile.com/jev, 20 September 2026, through the Vercel AI Gateway. An agent that fans out does not want ten payments; it wants one ceiling and an itemized settlement, which is exactly what upTo() and one fulfill() give you.
The seller's judge decides whether the seller gets paid
Say it out loud and the design follows. Three rules, and they belong in code rather than in a policy document:
Doubt costs the seller. With an expected value there is nothing to round, and uncertainty already pulls the price toward the cheaper levels. Where you do threshold — whether to charge at all — put the benefit of the doubt on the buyer's side.
An unanswered request is free. Return a 4xx; the adapter releases the hold and the caller keeps their authorization. The body still explains what happened.
The buyer is told. Put the price and its reasoning in the response — what was charged, what was authorized, which tier, what confidence, and which judge and version decided it.
"pricing": {
"charged": "$0.02", "authorized": "$0.05", "tier": "synthesis",
"judgedBy": "jev-1.13.0", "confidence": 0.81,
"reason": "judged 1.20 on the effort scale with confidence 0.81"
}A price nobody can question is a price nobody trusts. It is also what a refund argument is about later; on rails that support refunds, toll can issue one.
When a model is worth the call
A model earns its place when the judgement is about content a rule cannot read — was this answered, how deep did it go, is this résumé screened in, did the support conversation resolve. It does not earn its place deciding whether a caller is over sixty requests a minute.
Two practical notes:
- Where the latency lands. Judging after the handler hides the call behind work the caller is already waiting for. Judging before the handler adds it to every request, paid or not.
- What leaves your process. A judging call sends your customer's text to a third party. Send the smallest set of fields that supports the judgement, list them by hand where they are assembled, and write down what the vendor retains. If that is not acceptable, the same interface takes a judge that never leaves the building.
Keep the pricing log narrower still: identifiers, counts, tier, amount, confidence, judge. A pricing log is read by people who have no business reading the request body.
Keep the judge on your own machine
POST /v1/systemone — state in, typed questions in, answers with probabilities out — is a small enough contract that several servers implement it. LocalJev, from GitHub Next, speaks it in front of a local model; us/jev-local does the same over open weights. Point the judge at one and nothing about the request leaves the building:
export JEV_URL=http://127.0.0.1:8080/v1/systemone # no key is sentTwo things change with a local judge, and both bear on money. Its probabilities are typically reported by the model rather than read from its logits, so the confidence your price depends on is softer than a hosted System One model's. And tier boundaries calibrated against one judge's scale do not transfer to another's. Run your own examples through both before letting either decide what to charge.
Fall back, always
A judge is a dependency in the path of money. Give every failure — no key, a timeout, an error, an answer in a shape you do not recognise — the same destination: the rule-based judge, with the reason recorded.
async function fallback(work: Work, why: string): Promise<Verdict> {
const verdict = await ruleJudge(work);
return { ...verdict, reason: `${verdict.reason} (${why}, so the rules priced it)` };
}A trial key that expires should change your prices, not stop your server.
A worked example
examples/jev-pricing is this guide as a running service: one route, a ceiling of $0.05, three tiers, and two judges — thresholds, and Jev (TypeSafe AI's System One model) asked two typed questions, was this answered and how much work was it. Set JEV_API_KEY and the model prices the work, JEV_URL and a local server does, and with neither the rules do.
A key beginning vck_ is a Vercel AI Gateway key rather than a TypeSafe one, and goes to the gateway's compatibility endpoint under the model id typesafe-ai/jev. Sent to the wrong address it answers 401 — which a fallback will swallow into rule pricing, quietly. Let the key's prefix pick the address.
"What do anglers say about fishing the bay?" authorized $0.05 → charged $0.01 (lookup)
"When is the next ferry refund due?" authorized $0.05 → charged $0.02 (synthesis)
"Should I book the ferry, given tides and refunds?" authorized $0.05 → charged $0.04 (investigation)
"What is the capital of Mars?" authorized $0.05 → charged $0.00 (nothing found)Its tests cover the tiers, the unanswered case, the buyer's explanation, the absence of request text in the log, and each path back to the rules.
The same thing is running at demo.tollstile.com/jev, as a conversation: ask it anything and watch the ceiling stay at $0.05 while the settlement moves.
See also
- Charge for usage —
upTo()andfulfill()in detail - Dynamic pricing — a price computed before the work, from the request
- Charge per detection with SAM 3 — the same idea where the results are detections, and the detector scores itself
- Authorizations and charges — what a settled amount is, and what a refund does to it
Charge for usage
Authorize a maximum per call and settle only what the handler actually used — for LLM, image, and video generation.
Charge per detection with SAM 3
Outcome pricing for a segmentation API — SAM 3 proposes every instance, a second opinion checks each crop, and only the detections that survive are charged for.