SAM 3 found the cars. Jev decided what they were worth.
Most usage-based APIs I have paid for charge by length. Tokens, calls, rows, seconds of video. It is a proxy for value, and it quietly pays a model to pad: the same question answered in four sentences earns more than the one answered in one. I wanted to see what happens if the price is decided after the work instead, by something that reads the result. The payment layer already allowed the shape of this: authorize a ceiling before the work starts, settle less after it. What it could not do - what nothing can do - is say how much less. That number has to come from somewhere, fast enough to sit inside a request, and it cannot come from the thing that produced the work. Here is what I built, in the order I got it wrong. 1. I rounded the distribution, which is the whole product The judge is Jev, a System One model: you send state and typed questions, it answers with probabilities and a confidence, no prose. I asked it one question - how much work does this answer represent, on a three-level scale - and mapped the answer to a tier. That was the mistake. score: 0.99 on a scale whose levels are lookup / synthesis / investigation means "essentially a synthesis". I floored it to 0 and charged for a lookup. The distribution is the product. The moment you round it into a bucket you have thrown away the only thing that distinguishes a calibrated model from a general one asked for JSON. The fix is to charge the mean: const expected = LEVELS.reduce((total, level, i) => total + level.micros * p[i], 0); 12% a synthesis and 88% an investigation, at $0.025 and $0.05, is $0.047 - not a tier. And it is unbiased in a specific, limited sense: if the probabilities are calibrated, what you charge converges on the expected value of the tier prices - the value function I chose, not some true worth of the work. Rounding to a tier throws that information away and makes the price jump at an arbitrary line; whether the jump favours you or the buyer depends on where the line sits. $0.0235 is not a display value, by the way. The ledger holds micros, so that is the amount that settles - which is what lets a price be a mean in the first place. Uncertainty also stops needing a hand-tuned rule. A judge torn between two levels charges between them, automatically. 2. I asked for verification without sending the evidence The second question I ask is whether the answer is grounded - supported by what the service actually read, rather than asserted. Every call came back between 0.07 and 0.54, and I spent a while believing the model was bad at it. It was not. I was sending the question and the answer, and asking "is this supported by the sources?" - without sending the sources. With them in the state, grounded answers score 0.98 and ungrounded ones stay low. What I do with that number is a pricing decision, not a mathematical consequence: I multiply the expected price by the grounding probability, which treats a wholly ungrounded answer as worth nothing and a half-grounded one as worth half. Someone else could reasonably floor it, or refuse to bill at all below 0.5. Whatever you choose, put it in the receipt. Obvious in hindsight. A verification question with nothing to verify against is a vibe check, and it will happily return numbers that look like data. 3. A silent fallback hid a vendor being down Every path through the judge falls back to a rule-based one: no key, timeout, error, unrecognised shape. A trial key that expires should change your prices, not stop your server. Then I set a real key and everything kept working - priced by rules. The key was a Vercel AI Gateway key (vck_… ), I was sending it to TypeSafe's own endpoint, the answer was 401 , and my fallback swallowed it into a reason string nobody was reading. A fallback in the path of money must be loud. It is now in the receipt: every response says which judge decided and why, so "the rules priced it because the judge answered 401" is visible to the buyer, not just to the logs. 4. Then I pointed it at pictures, and the seller's model scored its own work Text is the easy case. The sharper one is detection: "find every solar panel in this photo" has no price until it runs - there might be forty, there might be none - and the model that answers also scores its own answers. I assumed a segmentation model would sidestep that, because the SAM I remembered was class-agnostic: you clicked, it gave you a mask, it had no idea what the mask was. SAM 3 is not that model. It takes a noun phrase, returns every instance of it, and scores each one itself. The self-marking problem does not disappear with segmentation; it arrives with it. SAM 3 found the three cars in that photo in 2.0 seconds, scoring them 0.93-0.97. It is equally confident about a crop that is not a car, which is the point. So a second opinion decides the money: a vision model says what each crop shows, in one sentence, and Jev says whether that sentence is the thing the buyer asked for. Neither of them chose the crop. The price is $0.01 per accepted detection, so three cars surviving at p ≥ 0.9 is $0.03 of a $0.20 ceiling. Ask for a bicycle and SAM returns nothing, so the charge is $0.00. The empty photo is the case a flat fee gets wrong, and the one a caller remembers. That zero is not an error path I wrote, either. The handler answers 422 and the gate releases the hold - the authorization goes back to the caller, unspent, to use elsewhere. Charging nothing has to be as ordinary as charging something, or the seller quietly rounds up. Two stages, because neither model can do it alone I would rather have asked the vision model directly and read the probability from its logprobs. Two things stopped me, and I diagnosed the first one wrong: - logprobs: true with an image returned400 . I wrote "the gateway refuses logprobs alongside images" in a comment and moved on. The actual error wasmax_output_tokens below the minimum of 16 - mymax_tokens: 1 . Read the error body. - With that fixed, the request succeeds and the logprobs come back empty, on that route, for text as much as for images. So the only graded number available comes from the model that cannot see. Hence: describe, then decide. Is the second opinion worth the extra call? That is measurable, so I measured it. Seven crops of the same photograph - the three cars, plus a front wheel, a traffic light, a palm tree and bare road - judged three ways. All three score 6 / 7, and all three are wrong about the same crop: a front wheel, described as "a car wheel and part of the vehicle's body". Equal on accuracy. Not equal on what you can do next. Only the graded answer was unsure - 0.85, against a confident yes from the other two - and the real cars sit at 0.98 and above. On this tiny sample, 0.9 happens to separate the wheel from the cars, so moving the threshold there makes that column 7 / 7 with nothing else lost. That is a threshold picked by looking at the same seven crops it is scored on, which is not evidence of anything except that the knob exists. A yes/no has no knob at all. That is the whole argument for the extra call, and it rests on seven crops of one photograph. It is an anecdote, not a benchmark. But the shape holds: if your price is graded, your judge has to be. What actually costs you Not the judging. Ten questions judged in parallel came back in about 600 ms for a fraction of a cent, because a System One model bills input tokens and emits no prose. What costs you is the rate limit. Three image crops took 38 seconds, because the gateway team I was on allows five vision calls a minute. Plan the queue, not the tokens. And the failure that would actually keep me up is neither. It is the provider going quiet between charge and charged, leaving a charge that is neither - which is the part I did not have to solve. The ledger records the reservation and the settlement separately, and reconciliation asks the provider afterwards what really happened. Once, never twice. What I still cannot tell you The judge never sees the image. It sees a sentence a vision model wrote about the image, so its calibration applies to "is this sentence a car" and not to "is this region a car". If the description is wrong, the judge is confidently wrong. The visual-to-language hop is unmeasured, and it is the weakest link in the chain. Two ways to close it, neither done: take two descriptions per crop and let disagreement cost the seller, or find a route where the vision model itself returns a distribution. The part that is actually new Outcome pricing is not new - support tools have billed per resolution for years. What I have not seen is the buyer being handed the arithmetic: the distribution the price was averaged over, the grounding score that discounted it, the confidence, the model version that decided, and the threshold it was measured against. "pricing": { "charged": "$0.01739", "authorized": "$0.05", "judgedBy": "typesafe-ai/jev", "confidence": 0.82, "reason": "depth 0.00 / 0.12 / 0.88 → $0.047 expected, × 0.37 grounded" } A price nobody can check is a price nobody trusts. And it is what a refund argument is about later, so it had better be written down at the time. The whole route is twelve lines, and three of them are the payment layer: app.post('/detect', tollstile(toll.price(upTo('$0.20'))), async (c) => { const found = await sam3(image, concept); // proposes, and scores itself const kept = await secondOpinion(image, concept, found); // someone else's opinion const payment = c.get('payment'); if (kept.length === 0) return c.json({ charged: '$0.00' }, 422); // the hold is released await payment.fulfill({ amount: price(kept) }); // settle what survived return c.json({ detections: kept, pricing: explain(kept) }); }); The pricing does not care which compatible rail moves the money: x402's upto scheme settles the computed amount here, and any other variable-amount rail plugs into the same handler. Fixed-amount rails - MPP's card charges among them - are excluded from a route priced this way, and
Comments
No comments yet. Start the discussion.