Back to Blog
EnglishTips

AI SaaS Unit Economics: Calculate Cost per Usable Output

Calculate quality-adjusted unit cost with Decimal, controlled ledger fixtures and real usage counts. Rates are explicitly illustrative, with no actual billing claim.

S
Sophie M
September 28, 2026 / 352 views
Share:
AI SaaS Unit Economics: Calculate Cost per Usable Output

Measure AI unit economics per usable result, including failed-quality attempts that consumed tokens. Dividing only by successful HTTP requests can make a workflow look cheaper than it is. Keep arithmetic, output quality and actual billing reconciliation as separate checks.

Crazyrouter is the API used in this ledger example: one completed request supplied real token counts, while a controlled three-attempt fixture showed that two usable results cost more per result than the per-attempt estimate. The rates used below are illustrative currency units, not Crazyrouter or provider prices.

By Sophie M · Tested November 3, 2026

How do I calculate cost per usable AI output?#

Add the modeled cost of all attempts in the evaluation window, then divide by the number of results accepted by the task's quality rule. If no result is usable, report that no finite unit cost was observed rather than dividing by zero or returning a reassuring zero.

For a simplified text-only fixture, modeled cost is input tokens times an input rate plus output tokens times an output rate. Real billing can contain cache categories, reasoning, media, tiers, tool charges and adjustments. This article deliberately tests the simple arithmetic without pretending it is a complete invoice calculator.

A controlled ledger and one real usage receipt#

On November 3, 2026, Python 3.10.2 used Decimal arithmetic with illustrative rates of 1 currency unit per million input tokens and 2 per million output tokens. Those numbers are test parameters, not a tariff.

The local ledger contained three attempts, each with 1,000 input and 100 output tokens. Two outputs were marked usable; one was not. The live request used gpt-4.1-mini through /v1/chat/completions at https://api.crazyrouter.com, with COST-1406 as the required exact answer.

ObservationResult
Controlled attempts / usable results3 / 2
Illustrative modeled total0.0036 units
Illustrative cost per usable result0.0018 units
Live prompt / completion tokens13 / 5
Local arithmetic fixtures4 passed
Actual bill queriedNo

Observed response IDs (successful responses only):

text
chatcmpl-Eg6ApocuzMnUh6RWtRejnEjftN1TK

Three token-consuming attempts produced only two usable fixture results

The synthetic ledger's total was 0.0036 illustrative units. Dividing by three attempts gives 0.0012 per attempt, but dividing by two usable results gives 0.0018 per usable result—a 50% increase in this constructed example. This is arithmetic, not an observed production failure rate.

Run the calculation and usage probe#

Install requests, set CRAZYROUTER_API_KEY in the process environment, save this as unit_cost_probe.py, and run it. The Python Decimal documentation explains decimal arithmetic. The example constructs rates from strings instead of introducing binary floating-point rounding into the rate values.

python
"""Measure quality-adjusted unit cost with explicitly illustrative input rates."""
from decimal import Decimal
import json
import os
import requests

def unit_cost(attempts,input_rate,output_rate):
    # Rates are illustrative currency units per million tokens, not a live tariff.
    total=Decimal('0');usable=0
    for r in attempts:
        if type(r['input_tokens']) is not int or type(r['output_tokens']) is not int or min(r['input_tokens'],r['output_tokens'])<0:raise ValueError('invalid token counts')
        total+=(Decimal(r['input_tokens'])*input_rate+Decimal(r['output_tokens'])*output_rate)/Decimal(1000000)
        usable+=bool(r['usable'])
    return {'attempts':len(attempts),'usable':usable,'estimated_total':str(total),'per_usable':str(total/usable) if usable else None}

def main():
    ir=Decimal('1');orr=Decimal('2')
    fixture=[{'input_tokens':1000,'output_tokens':100,'usable':True},{'input_tokens':1000,'output_tokens':100,'usable':False},{'input_tokens':1000,'output_tokens':100,'usable':True}]
    calculated=unit_cost(fixture,ir,orr)
    assert Decimal(calculated['estimated_total'])==Decimal('0.0036') and Decimal(calculated['per_usable'])==Decimal('0.0018')
    tests=[{'case':'three attempts / two usable','passed':True}]
    assert unit_cost([],ir,orr)['per_usable'] is None;tests.append({'case':'empty input has no unit cost','passed':True})
    assert unit_cost([dict(fixture[0],usable=False)],ir,orr)['per_usable'] is None;tests.append({'case':'zero usable has no unit cost','passed':True})
    try:unit_cost([dict(fixture[0],input_tokens=-1)],ir,orr)
    except ValueError:tests.append({'case':'negative usage rejected','passed':True})
    else:raise AssertionError('negative usage accepted')
    endpoint='https://api.crazyrouter.com/v1/chat/completions';model='gpt-4.1-mini'
    s=requests.Session();s.trust_env=False
    response=s.post(endpoint,headers={'Authorization':'Bearer '+os.environ['CRAZYROUTER_API_KEY']},json={'model':model,'max_tokens':32,'messages':[{'role':'user','content':'Reply only COST-1406'}]},timeout=60)
    response.raise_for_status();data=response.json();c=data['choices'][0];usage=data['usage']
    usable=c['finish_reason']=='stop' and c['message']['content'].strip()=='COST-1406'
    assert usable
    live_estimate=unit_cost([{'input_tokens':usage['prompt_tokens'],'output_tokens':usage['completion_tokens'],'usable':usable}],ir,orr)
    print(json.dumps({'endpoint':endpoint,'model':model,'response_id':data['id'],'output':c['message']['content'],'usage':usage,'local_tests':tests,'illustrative_rates':{'input_per_million':str(ir),'output_per_million':str(orr)},'fixture':calculated,'live_tokens_at_illustrative_rates':live_estimate,'billing_claim':False}))

if __name__=='__main__':main()

The live response reported 13 prompt tokens and 5 completion tokens. At the deliberately illustrative rates, the function calculates 0.000023 units. This is not the amount charged for that request. No billing transaction was queried or reconciled.

Keep denominators and unknown costs visible#

RecordInclude in token-based modeled cost?Count as usable?
Validated successful answerYesYes
Completed answer that fails qualityYes, when usage is availableNo
Retry that consumed tokensInclude each attempt separatelyOnly accepted final work counts
Timeout without known usageMark cost unknown pending reconciliationNo completed result established
Crazyrouter live fixtureReal usage with illustrative ratesYes, exact marker validated

Illustrative cost was 0.0012 units per attempt and 0.0018 per usable output

Do not enter zero token cost for a timeout merely because no usage response arrived. The server may have processed work before the response was lost. Use your billing records and per-attempt telemetry to reconcile uncertain attempts.

The small function tests a provided list; it is not a durable, multi-tenant billing ledger. It does not deduplicate records or choose which business result counts once. A real implementation needs stable attempt and task IDs, rate versioning, currency policy and explicit rounding rules. Decimal helps arithmetic precision but does not solve accounting completeness.

Reduce work without hiding quality failures#

Start with a representative task set and define acceptance before comparing routes. Use structured-output validation when shape is part of the contract, but remember that valid JSON can still contain wrong facts.

Use admission control to limit simultaneous work and a bounded retry policy to keep attempts visible. If you use fallback models, measure their accepted outputs independently. An application cache may remove a generation, while prompt caching changes input processing; those require different counters.

FAQ#

Are the rates in this article real model prices?#

No. They are explicitly illustrative test inputs of 1 and 2 currency units per million tokens.

Why does a failed-quality answer still count toward cost?#

It may have consumed billed resources even though it did not solve the task. Keep its attempt and known usage in the ledger.

What happens when there are no usable results?#

The function returns null for per-usable cost. It does not divide by zero or claim a zero cost per result.

Can I count an unknown timeout as zero usage?#

No. Record usage or cost as unknown until you can reconcile it.

Does Decimal make this production billing software?#

No. It addresses arithmetic representation, not missing records, rate categories, deduplication or settlement.

Did this prove one model is cheaper than another?#

No. The test validates the calculation and one usage receipt; it does not compare real tariffs or representative quality.

Implementation Guides

Podcast

AI Dev Tools: The Crazyrouter Podcast

Short episodes for developers on AI APIs, gateways, cost control and coding tools.

Latest episodes

  • EP196 · Oct 29, 2026 · 6:53

    AI API Security Posture — Turn Controls Into Continuous Evidence

    A practical guide to AI API security posture: inventory exposure, verify controls continuously, protect keys and data, constrain tools, and turn security assumptions into evidence that operators can act on.

  • EP195 · Oct 28, 2026 · 7:21

    AI API Policy Drift — Detect When Production Rules Stop Matching Intent

    A practical guide to policy drift in AI APIs: version routing and safety rules, detect stale enforcement, compare intended and observed behavior, and restore control before small gaps become incidents.

Episodes with written show notes →

Available in other languages:

Topics

Related Articles