[{"data":1,"prerenderedAt":2551},["ShallowReactive",2],{"page-\u002Fbuilding-monetizing-api-driven-micro-saas\u002Fdesigning-api-pricing-tiers\u002Fcalculating-cost-per-api-request\u002F":3,"faq-schema-\u002Fbuilding-monetizing-api-driven-micro-saas\u002Fdesigning-api-pricing-tiers\u002Fcalculating-cost-per-api-request\u002F":2530},{"id":4,"title":5,"body":6,"description":2520,"extension":2521,"meta":2522,"navigation":331,"path":2526,"seo":2527,"stem":2528,"__hash__":2529},"content\u002Fbuilding-monetizing-api-driven-micro-saas\u002Fdesigning-api-pricing-tiers\u002Fcalculating-cost-per-api-request\u002Findex.md","Calculating Cost per API Request",{"type":7,"value":8,"toc":2509},"minimark",[9,13,23,26,31,34,46,52,63,69,75,150,265,273,275,279,282,1169,1180,1182,1186,1189,1192,1331,1334,1702,1710,1712,1716,1719,1738,1821,1829,1831,1835,1838,1846,1942,1945,1947,1951,1954,1972,2392,2404,2406,2410,2418,2420,2424,2430,2436,2442,2448,2454,2458,2463,2486,2491,2505],[10,11,5],"h1",{"id":12},"calculating-cost-per-api-request",[14,15,16,17,22],"p",{},"You cannot defend a price you cannot cost. Most indie API builders pick a number that feels right — $29 a month, 10,000 calls included — then discover six months later that the heaviest 5% of traffic eats the entire gross margin. This page resolves that: it builds a real unit-economics model for a Python API, component by component, and turns it into a price floor you can point at. Part of the ",[18,19,21],"a",{"href":20},"\u002Fbuilding-monetizing-api-driven-micro-saas\u002Fdesigning-api-pricing-tiers\u002F","Designing API Pricing Tiers"," guide, it assumes you have already picked a billing axis and now need the arithmetic underneath it. The working example throughout is a document-enrichment endpoint: it validates input, hits Postgres, calls a hosted language model, and returns JSON. Every number below is a real one you can substitute with your own rate card.",[24,25],"hr",{},[27,28,30],"h2",{"id":29},"the-five-things-a-request-actually-costs-you","The five things a request actually costs you",[14,32,33],{},"Variable cost per request splits into five buckets, and they differ in size by three orders of magnitude. Getting the ranking right matters more than getting any single number precise.",[14,35,36,40,41,45],{},[37,38,39],"strong",{},"Compute"," is the cheapest and the one builders overestimate. A 1 vCPU \u002F 2 GB container on a managed host runs about $25 a month. Serve two million requests through it and compute costs $0.0000125 per request — 12.5 micro-dollars. Compute only becomes interesting when your workers sit idle; the fix is worker sizing, not pricing, and it is covered in ",[18,42,44],{"href":43},"\u002Fgetting-started-with-python-apis-for-builders\u002Fsetting-up-fastapi\u002Fuvicorn-vs-gunicorn-worker-configuration\u002F","Uvicorn vs Gunicorn worker configuration",".",[14,47,48,51],{},[37,49,50],{},"Egress"," is almost always noise for a JSON API. An 8 KB response at $0.09\u002FGB costs 0.7 micro-dollars. It stops being noise the moment you return files, images, or streamed payloads — at 2 MB per response the same rate costs 180 micro-dollars, and egress jumps from fifth place to second.",[14,53,54,57,58,62],{},[37,55,56],{},"Database"," covers the managed Postgres instance plus the write amplification of your own metering. A $50\u002Fmonth instance across two million requests is 25 micro-dollars per call. If you write a usage row on every request — and you should, per ",[18,59,61],{"href":60},"\u002Fbuilding-monetizing-api-driven-micro-saas\u002Ftracking-api-usage-and-analytics\u002Flogging-api-usage-events-to-postgres\u002F","logging API usage events to Postgres"," — that row is part of the cost of the request, not overhead.",[14,64,65,68],{},[37,66,67],{},"Third-party and model spend"," dominates everything else the instant an external vendor is in the path. Our example call sends 900 input tokens and returns 300 at $0.25 and $1.25 per million respectively: 600 micro-dollars. That is 48 times the compute cost.",[14,70,71,74],{},[37,72,73],{},"Observability"," — structured logs, traces, metric cardinality — runs about 3 micro-dollars per request at 2 KB of log volume. Small, but log vendors bill on ingest, so a debug-level deploy can multiply it tenfold overnight.",[76,77,78,94],"table",{},[79,80,81],"thead",{},[82,83,84,88,91],"tr",{},[85,86,87],"th",{},"Component",[85,89,90],{},"Per request (µ$)",[85,92,93],{},"Share",[95,96,97,109,119,129,140],"tbody",{},[82,98,99,103,106],{},[100,101,102],"td",{},"Model \u002F vendor calls",[100,104,105],{},"600.0",[100,107,108],{},"93.6%",[82,110,111,113,116],{},[100,112,56],{},[100,114,115],{},"25.0",[100,117,118],{},"3.9%",[82,120,121,123,126],{},[100,122,39],{},[100,124,125],{},"12.5",[100,127,128],{},"1.9%",[82,130,131,134,137],{},[100,132,133],{},"Logs + metrics",[100,135,136],{},"3.0",[100,138,139],{},"0.5%",[82,141,142,144,147],{},[100,143,50],{},[100,145,146],{},"0.7",[100,148,149],{},"0.1%",[151,152,160,161,160,165,160,169,160,176,160,184,160,190,160,195,160,203,160,207,160,210,160,215,160,219,160,222,160,227,160,231,160,234,160,240,160,244,160,247,160,250,160,254,160,261],"svg",{"viewBox":153,"role":154,"ariaLabelledBy":155,"xmlns":158,"style":159},"0 0 720 300","img",[156,157],"cpr-stack-t","cpr-stack-d","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg","width:100%;height:auto;margin:1.5rem 0;font-family:var(--font-sans);","\n  ",[162,163,164],"title",{"id":156},"Cost components of a single API request",[166,167,168],"desc",{"id":157},"Horizontal bars showing model spend at 600 micro-dollars dwarfing database at 25, compute at 12.5, logs at 3 and egress at 0.7 micro-dollars per request.",[170,171],"rect",{"x":172,"y":172,"width":173,"height":174,"fill":175},"0","720","300","var(--c-surface)",[177,178,183],"text",{"x":179,"y":180,"fill":181,"style":182},"20","32","var(--c-text)","font-size:14;font-family:var(--font-sans);","Variable cost of one enrichment request",[177,185,189],{"x":179,"y":186,"fill":187,"style":188},"52","var(--c-text-muted)","font-size:11;font-family:var(--font-sans);","bar length = micro-dollars, linear scale",[177,191,194],{"x":179,"y":192,"fill":181,"style":193},"88","font-size:12;font-family:var(--font-sans);","Model tokens",[170,196],{"x":197,"y":198,"width":199,"height":200,"rx":201,"fill":202},"180","76","380","18","3","var(--c-coral)",[177,204,105],{"x":205,"y":206,"fill":181,"style":193},"570","89",[177,208,56],{"x":179,"y":209,"fill":181,"style":193},"128",[170,211],{"x":197,"y":212,"width":213,"height":200,"rx":201,"fill":214},"116","16","var(--c-blue)",[177,216,115],{"x":217,"y":218,"fill":181,"style":193},"206","129",[177,220,39],{"x":179,"y":221,"fill":181,"style":193},"168",[170,223],{"x":197,"y":224,"width":225,"height":200,"rx":201,"fill":226},"156","8","var(--c-teal)",[177,228,125],{"x":229,"y":230,"fill":181,"style":193},"198","169",[177,232,133],{"x":179,"y":233,"fill":181,"style":193},"208",[170,235],{"x":197,"y":236,"width":237,"height":200,"rx":238,"fill":239},"196","4","2","var(--c-yellow)",[177,241,136],{"x":242,"y":243,"fill":181,"style":193},"194","209",[177,245,50],{"x":179,"y":246,"fill":181,"style":193},"248",[170,248],{"x":197,"y":249,"width":201,"height":200,"rx":238,"fill":187},"236",[177,251,146],{"x":252,"y":253,"fill":181,"style":193},"193","249",[255,256],"line",{"x1":179,"y1":257,"x2":258,"y2":257,"stroke":259,"style":260},"266","700","var(--c-border)","stroke-width:1;",[177,262,264],{"x":179,"y":263,"fill":181,"style":193},"286","Mean total: 641.2 micro-dollars = $0.00064 per request",[14,266,267,268,272],{},"The lesson is blunt: if a vendor sits in your request path, your unit economics are their unit economics plus a rounding error. Optimizing container size while a language model burns 94% of the bill is theatre. Cut token spend first, using the tactics in ",[18,269,271],{"href":270},"\u002Fautomating-side-hustle-operations-with-apis\u002Fautomating-ai-workflows-with-python-apis\u002Fcontrolling-llm-api-costs-in-production\u002F","controlling LLM API costs in production",", then worry about the infrastructure line.",[24,274],{},[27,276,278],{"id":277},"building-the-model-in-code","Building the model in code",[14,280,281],{},"Keep the rate card out of your Python. Rates change when you switch regions, renegotiate, or swap models, and you want that change to be a config edit reviewed in a pull request, not a code change. Load it from TOML, describe a request's resource shape as a frozen dataclass, and let one function price each component.",[283,284,289],"pre",{"className":285,"code":286,"language":287,"meta":288,"style":288},"language-python shiki shiki-themes github-light github-dark","import os\nimport tomllib\nfrom dataclasses import dataclass, asdict\n\nRATE_CARD_PATH = os.getenv(\"RATE_CARD_PATH\", \"rates.toml\")\n\n\n@dataclass(frozen=True)\nclass RequestShape:\n    \"\"\"Resources consumed by a single request.\"\"\"\n    cpu_ms: float\n    egress_kb: float\n    db_seconds: float\n    input_tokens: int\n    output_tokens: int\n    log_kb: float\n\n\ndef load_rates(path: str | None = None) -> dict[str, float]:\n    with open(path or RATE_CARD_PATH, \"rb\") as fh:\n        return tomllib.load(fh)[\"rates\"]\n\n\ndef component_cost(kind: str, qty: float, rates: dict[str, float]) -> float:\n    \"\"\"Cost in USD for one metered component of a request.\"\"\"\n    match kind:\n        case \"cpu_ms\":\n            return qty \u002F 3_600_000 * rates[\"compute_usd_per_vcpu_hour\"]\n        case \"egress_kb\" | \"log_kb\":\n            key = \"egress_usd_per_gb\" if kind == \"egress_kb\" else \"log_usd_per_gb\"\n            return qty \u002F 1_048_576 * rates[key]\n        case \"db_seconds\":\n            return qty \u002F 3600 * rates[\"db_usd_per_hour\"]\n        case \"input_tokens\":\n            return qty \u002F 1_000_000 * rates[\"model_usd_per_m_input\"]\n        case \"output_tokens\":\n            return qty \u002F 1_000_000 * rates[\"model_usd_per_m_output\"]\n        case _:\n            raise ValueError(f\"unknown cost component: {kind}\")\n\n\ndef cost_of(shape: RequestShape, rates: dict[str, float]) -> dict[str, float]:\n    breakdown = {k: component_cost(k, v, rates) for k, v in asdict(shape).items()}\n    breakdown[\"total\"] = sum(breakdown.values())\n    return breakdown\n\n\nif __name__ == \"__main__\":\n    mean_request = RequestShape(\n        cpu_ms=float(os.getenv(\"MEAN_CPU_MS\", \"45\")),\n        egress_kb=float(os.getenv(\"MEAN_EGRESS_KB\", \"8\")),\n        db_seconds=float(os.getenv(\"MEAN_DB_SECONDS\", \"1.8\")),\n        input_tokens=int(os.getenv(\"MEAN_INPUT_TOKENS\", \"900\")),\n        output_tokens=int(os.getenv(\"MEAN_OUTPUT_TOKENS\", \"300\")),\n        log_kb=float(os.getenv(\"MEAN_LOG_KB\", \"2\")),\n    )\n    for name, usd in cost_of(mean_request, load_rates()).items():\n        print(f\"{name:>14}: {usd * 1_000_000:8.1f} micro-dollars\")\n","python","",[290,291,292,304,312,326,333,359,364,369,391,403,409,418,426,434,443,451,459,464,469,507,539,554,559,564,598,604,613,624,650,665,693,710,720,741,751,772,782,802,810,841,846,851,879,902,922,931,936,941,958,969,993,1015,1037,1060,1082,1104,1110,1124],"code",{"__ignoreMap":288},[293,294,296,300],"span",{"class":255,"line":295},1,[293,297,299],{"class":298},"szBVR","import",[293,301,303],{"class":302},"sVt8B"," os\n",[293,305,307,309],{"class":255,"line":306},2,[293,308,299],{"class":298},[293,310,311],{"class":302}," tomllib\n",[293,313,315,318,321,323],{"class":255,"line":314},3,[293,316,317],{"class":298},"from",[293,319,320],{"class":302}," dataclasses ",[293,322,299],{"class":298},[293,324,325],{"class":302}," dataclass, asdict\n",[293,327,329],{"class":255,"line":328},4,[293,330,332],{"emptyLinePlaceholder":331},true,"\n",[293,334,336,340,343,346,350,353,356],{"class":255,"line":335},5,[293,337,339],{"class":338},"sj4cs","RATE_CARD_PATH",[293,341,342],{"class":298}," =",[293,344,345],{"class":302}," os.getenv(",[293,347,349],{"class":348},"sZZnC","\"RATE_CARD_PATH\"",[293,351,352],{"class":302},", ",[293,354,355],{"class":348},"\"rates.toml\"",[293,357,358],{"class":302},")\n",[293,360,362],{"class":255,"line":361},6,[293,363,332],{"emptyLinePlaceholder":331},[293,365,367],{"class":255,"line":366},7,[293,368,332],{"emptyLinePlaceholder":331},[293,370,372,376,379,383,386,389],{"class":255,"line":371},8,[293,373,375],{"class":374},"sScJk","@dataclass",[293,377,378],{"class":302},"(",[293,380,382],{"class":381},"s4XuR","frozen",[293,384,385],{"class":298},"=",[293,387,388],{"class":338},"True",[293,390,358],{"class":302},[293,392,394,397,400],{"class":255,"line":393},9,[293,395,396],{"class":298},"class",[293,398,399],{"class":374}," RequestShape",[293,401,402],{"class":302},":\n",[293,404,406],{"class":255,"line":405},10,[293,407,408],{"class":348},"    \"\"\"Resources consumed by a single request.\"\"\"\n",[293,410,412,415],{"class":255,"line":411},11,[293,413,414],{"class":302},"    cpu_ms: ",[293,416,417],{"class":338},"float\n",[293,419,421,424],{"class":255,"line":420},12,[293,422,423],{"class":302},"    egress_kb: ",[293,425,417],{"class":338},[293,427,429,432],{"class":255,"line":428},13,[293,430,431],{"class":302},"    db_seconds: ",[293,433,417],{"class":338},[293,435,437,440],{"class":255,"line":436},14,[293,438,439],{"class":302},"    input_tokens: ",[293,441,442],{"class":338},"int\n",[293,444,446,449],{"class":255,"line":445},15,[293,447,448],{"class":302},"    output_tokens: ",[293,450,442],{"class":338},[293,452,454,457],{"class":255,"line":453},16,[293,455,456],{"class":302},"    log_kb: ",[293,458,417],{"class":338},[293,460,462],{"class":255,"line":461},17,[293,463,332],{"emptyLinePlaceholder":331},[293,465,467],{"class":255,"line":466},18,[293,468,332],{"emptyLinePlaceholder":331},[293,470,472,475,478,481,484,487,490,492,494,497,499,501,504],{"class":255,"line":471},19,[293,473,474],{"class":298},"def",[293,476,477],{"class":374}," load_rates",[293,479,480],{"class":302},"(path: ",[293,482,483],{"class":338},"str",[293,485,486],{"class":298}," |",[293,488,489],{"class":338}," None",[293,491,342],{"class":298},[293,493,489],{"class":338},[293,495,496],{"class":302},") -> dict[",[293,498,483],{"class":338},[293,500,352],{"class":302},[293,502,503],{"class":338},"float",[293,505,506],{"class":302},"]:\n",[293,508,510,513,516,519,522,525,527,530,533,536],{"class":255,"line":509},20,[293,511,512],{"class":298},"    with",[293,514,515],{"class":338}," open",[293,517,518],{"class":302},"(path ",[293,520,521],{"class":298},"or",[293,523,524],{"class":338}," RATE_CARD_PATH",[293,526,352],{"class":302},[293,528,529],{"class":348},"\"rb\"",[293,531,532],{"class":302},") ",[293,534,535],{"class":298},"as",[293,537,538],{"class":302}," fh:\n",[293,540,542,545,548,551],{"class":255,"line":541},21,[293,543,544],{"class":298},"        return",[293,546,547],{"class":302}," tomllib.load(fh)[",[293,549,550],{"class":348},"\"rates\"",[293,552,553],{"class":302},"]\n",[293,555,557],{"class":255,"line":556},22,[293,558,332],{"emptyLinePlaceholder":331},[293,560,562],{"class":255,"line":561},23,[293,563,332],{"emptyLinePlaceholder":331},[293,565,567,569,572,575,577,580,582,585,587,589,591,594,596],{"class":255,"line":566},24,[293,568,474],{"class":298},[293,570,571],{"class":374}," component_cost",[293,573,574],{"class":302},"(kind: ",[293,576,483],{"class":338},[293,578,579],{"class":302},", qty: ",[293,581,503],{"class":338},[293,583,584],{"class":302},", rates: dict[",[293,586,483],{"class":338},[293,588,352],{"class":302},[293,590,503],{"class":338},[293,592,593],{"class":302},"]) -> ",[293,595,503],{"class":338},[293,597,402],{"class":302},[293,599,601],{"class":255,"line":600},25,[293,602,603],{"class":348},"    \"\"\"Cost in USD for one metered component of a request.\"\"\"\n",[293,605,607,610],{"class":255,"line":606},26,[293,608,609],{"class":298},"    match",[293,611,612],{"class":302}," kind:\n",[293,614,616,619,622],{"class":255,"line":615},27,[293,617,618],{"class":298},"        case",[293,620,621],{"class":348}," \"cpu_ms\"",[293,623,402],{"class":302},[293,625,627,630,633,636,639,642,645,648],{"class":255,"line":626},28,[293,628,629],{"class":298},"            return",[293,631,632],{"class":302}," qty ",[293,634,635],{"class":298},"\u002F",[293,637,638],{"class":338}," 3_600_000",[293,640,641],{"class":298}," *",[293,643,644],{"class":302}," rates[",[293,646,647],{"class":348},"\"compute_usd_per_vcpu_hour\"",[293,649,553],{"class":302},[293,651,653,655,658,660,663],{"class":255,"line":652},29,[293,654,618],{"class":298},[293,656,657],{"class":348}," \"egress_kb\"",[293,659,486],{"class":298},[293,661,662],{"class":348}," \"log_kb\"",[293,664,402],{"class":302},[293,666,668,671,673,676,679,682,685,687,690],{"class":255,"line":667},30,[293,669,670],{"class":302},"            key ",[293,672,385],{"class":298},[293,674,675],{"class":348}," \"egress_usd_per_gb\"",[293,677,678],{"class":298}," if",[293,680,681],{"class":302}," kind ",[293,683,684],{"class":298},"==",[293,686,657],{"class":348},[293,688,689],{"class":298}," else",[293,691,692],{"class":348}," \"log_usd_per_gb\"\n",[293,694,696,698,700,702,705,707],{"class":255,"line":695},31,[293,697,629],{"class":298},[293,699,632],{"class":302},[293,701,635],{"class":298},[293,703,704],{"class":338}," 1_048_576",[293,706,641],{"class":298},[293,708,709],{"class":302}," rates[key]\n",[293,711,713,715,718],{"class":255,"line":712},32,[293,714,618],{"class":298},[293,716,717],{"class":348}," \"db_seconds\"",[293,719,402],{"class":302},[293,721,723,725,727,729,732,734,736,739],{"class":255,"line":722},33,[293,724,629],{"class":298},[293,726,632],{"class":302},[293,728,635],{"class":298},[293,730,731],{"class":338}," 3600",[293,733,641],{"class":298},[293,735,644],{"class":302},[293,737,738],{"class":348},"\"db_usd_per_hour\"",[293,740,553],{"class":302},[293,742,744,746,749],{"class":255,"line":743},34,[293,745,618],{"class":298},[293,747,748],{"class":348}," \"input_tokens\"",[293,750,402],{"class":302},[293,752,754,756,758,760,763,765,767,770],{"class":255,"line":753},35,[293,755,629],{"class":298},[293,757,632],{"class":302},[293,759,635],{"class":298},[293,761,762],{"class":338}," 1_000_000",[293,764,641],{"class":298},[293,766,644],{"class":302},[293,768,769],{"class":348},"\"model_usd_per_m_input\"",[293,771,553],{"class":302},[293,773,775,777,780],{"class":255,"line":774},36,[293,776,618],{"class":298},[293,778,779],{"class":348}," \"output_tokens\"",[293,781,402],{"class":302},[293,783,785,787,789,791,793,795,797,800],{"class":255,"line":784},37,[293,786,629],{"class":298},[293,788,632],{"class":302},[293,790,635],{"class":298},[293,792,762],{"class":338},[293,794,641],{"class":298},[293,796,644],{"class":302},[293,798,799],{"class":348},"\"model_usd_per_m_output\"",[293,801,553],{"class":302},[293,803,805,807],{"class":255,"line":804},38,[293,806,618],{"class":298},[293,808,809],{"class":302}," _:\n",[293,811,813,816,819,821,824,827,830,833,836,839],{"class":255,"line":812},39,[293,814,815],{"class":298},"            raise",[293,817,818],{"class":338}," ValueError",[293,820,378],{"class":302},[293,822,823],{"class":298},"f",[293,825,826],{"class":348},"\"unknown cost component: ",[293,828,829],{"class":338},"{",[293,831,832],{"class":302},"kind",[293,834,835],{"class":338},"}",[293,837,838],{"class":348},"\"",[293,840,358],{"class":302},[293,842,844],{"class":255,"line":843},40,[293,845,332],{"emptyLinePlaceholder":331},[293,847,849],{"class":255,"line":848},41,[293,850,332],{"emptyLinePlaceholder":331},[293,852,854,856,859,862,864,866,868,871,873,875,877],{"class":255,"line":853},42,[293,855,474],{"class":298},[293,857,858],{"class":374}," cost_of",[293,860,861],{"class":302},"(shape: RequestShape, rates: dict[",[293,863,483],{"class":338},[293,865,352],{"class":302},[293,867,503],{"class":338},[293,869,870],{"class":302},"]) -> dict[",[293,872,483],{"class":338},[293,874,352],{"class":302},[293,876,503],{"class":338},[293,878,506],{"class":302},[293,880,882,885,887,890,893,896,899],{"class":255,"line":881},43,[293,883,884],{"class":302},"    breakdown ",[293,886,385],{"class":298},[293,888,889],{"class":302}," {k: component_cost(k, v, rates) ",[293,891,892],{"class":298},"for",[293,894,895],{"class":302}," k, v ",[293,897,898],{"class":298},"in",[293,900,901],{"class":302}," asdict(shape).items()}\n",[293,903,905,908,911,914,916,919],{"class":255,"line":904},44,[293,906,907],{"class":302},"    breakdown[",[293,909,910],{"class":348},"\"total\"",[293,912,913],{"class":302},"] ",[293,915,385],{"class":298},[293,917,918],{"class":338}," sum",[293,920,921],{"class":302},"(breakdown.values())\n",[293,923,925,928],{"class":255,"line":924},45,[293,926,927],{"class":298},"    return",[293,929,930],{"class":302}," breakdown\n",[293,932,934],{"class":255,"line":933},46,[293,935,332],{"emptyLinePlaceholder":331},[293,937,939],{"class":255,"line":938},47,[293,940,332],{"emptyLinePlaceholder":331},[293,942,944,947,950,953,956],{"class":255,"line":943},48,[293,945,946],{"class":298},"if",[293,948,949],{"class":338}," __name__",[293,951,952],{"class":298}," ==",[293,954,955],{"class":348}," \"__main__\"",[293,957,402],{"class":302},[293,959,961,964,966],{"class":255,"line":960},49,[293,962,963],{"class":302},"    mean_request ",[293,965,385],{"class":298},[293,967,968],{"class":302}," RequestShape(\n",[293,970,972,975,977,979,982,985,987,990],{"class":255,"line":971},50,[293,973,974],{"class":381},"        cpu_ms",[293,976,385],{"class":298},[293,978,503],{"class":338},[293,980,981],{"class":302},"(os.getenv(",[293,983,984],{"class":348},"\"MEAN_CPU_MS\"",[293,986,352],{"class":302},[293,988,989],{"class":348},"\"45\"",[293,991,992],{"class":302},")),\n",[293,994,996,999,1001,1003,1005,1008,1010,1013],{"class":255,"line":995},51,[293,997,998],{"class":381},"        egress_kb",[293,1000,385],{"class":298},[293,1002,503],{"class":338},[293,1004,981],{"class":302},[293,1006,1007],{"class":348},"\"MEAN_EGRESS_KB\"",[293,1009,352],{"class":302},[293,1011,1012],{"class":348},"\"8\"",[293,1014,992],{"class":302},[293,1016,1018,1021,1023,1025,1027,1030,1032,1035],{"class":255,"line":1017},52,[293,1019,1020],{"class":381},"        db_seconds",[293,1022,385],{"class":298},[293,1024,503],{"class":338},[293,1026,981],{"class":302},[293,1028,1029],{"class":348},"\"MEAN_DB_SECONDS\"",[293,1031,352],{"class":302},[293,1033,1034],{"class":348},"\"1.8\"",[293,1036,992],{"class":302},[293,1038,1040,1043,1045,1048,1050,1053,1055,1058],{"class":255,"line":1039},53,[293,1041,1042],{"class":381},"        input_tokens",[293,1044,385],{"class":298},[293,1046,1047],{"class":338},"int",[293,1049,981],{"class":302},[293,1051,1052],{"class":348},"\"MEAN_INPUT_TOKENS\"",[293,1054,352],{"class":302},[293,1056,1057],{"class":348},"\"900\"",[293,1059,992],{"class":302},[293,1061,1063,1066,1068,1070,1072,1075,1077,1080],{"class":255,"line":1062},54,[293,1064,1065],{"class":381},"        output_tokens",[293,1067,385],{"class":298},[293,1069,1047],{"class":338},[293,1071,981],{"class":302},[293,1073,1074],{"class":348},"\"MEAN_OUTPUT_TOKENS\"",[293,1076,352],{"class":302},[293,1078,1079],{"class":348},"\"300\"",[293,1081,992],{"class":302},[293,1083,1085,1088,1090,1092,1094,1097,1099,1102],{"class":255,"line":1084},55,[293,1086,1087],{"class":381},"        log_kb",[293,1089,385],{"class":298},[293,1091,503],{"class":338},[293,1093,981],{"class":302},[293,1095,1096],{"class":348},"\"MEAN_LOG_KB\"",[293,1098,352],{"class":302},[293,1100,1101],{"class":348},"\"2\"",[293,1103,992],{"class":302},[293,1105,1107],{"class":255,"line":1106},56,[293,1108,1109],{"class":302},"    )\n",[293,1111,1113,1116,1119,1121],{"class":255,"line":1112},57,[293,1114,1115],{"class":298},"    for",[293,1117,1118],{"class":302}," name, usd ",[293,1120,898],{"class":298},[293,1122,1123],{"class":302}," cost_of(mean_request, load_rates()).items():\n",[293,1125,1127,1130,1132,1134,1136,1138,1141,1144,1146,1149,1151,1154,1157,1159,1162,1164,1167],{"class":255,"line":1126},58,[293,1128,1129],{"class":338},"        print",[293,1131,378],{"class":302},[293,1133,823],{"class":298},[293,1135,838],{"class":348},[293,1137,829],{"class":338},[293,1139,1140],{"class":302},"name",[293,1142,1143],{"class":298},":>14",[293,1145,835],{"class":338},[293,1147,1148],{"class":348},": ",[293,1150,829],{"class":338},[293,1152,1153],{"class":302},"usd ",[293,1155,1156],{"class":298},"*",[293,1158,762],{"class":338},[293,1160,1161],{"class":298},":8.1f",[293,1163,835],{"class":338},[293,1165,1166],{"class":348}," micro-dollars\"",[293,1168,358],{"class":302},[14,1170,1171,1172,1175,1176,45],{},"The ",[290,1173,1174],{},"db_seconds"," field deserves a note. You are renting a whole instance, so the honest allocation is the fraction of that instance one request occupies — query time, not query count. Measure it once with your pool's checkout timer and treat it as fixed until your schema changes. If your pool is saturated the number lies badly, which is one more reason to keep an eye on ",[18,1177,1179],{"href":1178},"\u002Fscaling-and-operating-production-python-apis\u002Fasync-database-access-with-sqlalchemy\u002Ffixing-connection-pool-exhaustion\u002F","connection pool exhaustion",[24,1181],{},[27,1183,1185],{"id":1184},"the-mean-is-a-marketing-number-the-p95-is-the-bill","The mean is a marketing number, the p95 is the bill",[14,1187,1188],{},"Averages hide the customers that hurt you. In the enrichment example, the mean request costs 641 micro-dollars but the 95th-percentile request costs 2,058 — a 3.2x spread — because long documents burn more tokens, miss the cache, and hold a database connection longer. Three multipliers stack on the same request, and they are correlated rather than independent.",[14,1190,1191],{},"That spread would be harmless if every customer drew randomly from the distribution. They do not. Customer usage is lumpy and self-similar: one integration sends tidy 400-token snippets all day, another pipes in scanned PDFs and lives permanently in the tail. Price against the mean and the tail customer is served at a loss, quietly, on the same tier as everybody else.",[151,1193,160,1198,160,1201,160,1204,160,1207,160,1211,160,1216,160,1219,160,1225,160,1229,160,1233,160,1237,160,1243,160,1247,160,1251,160,1255,160,1260,160,1264,160,1269,160,1274,160,1280,160,1284,160,1288,160,1292,160,1296,160,1300,160,1304,160,1308,160,1314,160,1318,160,1322,160,1325,160,1328],{"viewBox":1194,"role":154,"ariaLabelledBy":1195,"xmlns":158,"style":159},"0 0 720 320",[1196,1197],"cpr-dist-t","cpr-dist-d",[162,1199,1200],{"id":1196},"Distribution of per-request cost with mean and p95 marked",[166,1202,1203],{"id":1197},"Histogram of request cost in micro-dollars: 30 percent of requests fall between 400 and 600, the mean sits at 641 and the 95th percentile at 2060.",[170,1205],{"x":172,"y":172,"width":173,"height":1206,"fill":175},"320",[177,1208,1210],{"x":179,"y":1209,"fill":181,"style":182},"26","Per-request cost distribution (share of traffic)",[255,1212],{"x1":1213,"y1":1214,"x2":1215,"y2":1214,"stroke":259,"style":260},"66","240","668",[255,1217],{"x1":1213,"y1":1218,"x2":1213,"y2":1214,"stroke":259,"style":260},"80",[177,1220,1224],{"x":1221,"y":1222,"fill":187,"style":1223},"58","244","text-anchor:end;font-size:10;font-family:var(--font-sans);","0%",[177,1226,1228],{"x":1221,"y":1227,"fill":187,"style":1223},"191","10%",[177,1230,1232],{"x":1221,"y":1231,"fill":187,"style":1223},"138","20%",[177,1234,1236],{"x":1221,"y":1235,"fill":187,"style":1223},"85","30%",[170,1238],{"x":1239,"y":1240,"width":1241,"height":1242,"fill":214},"70","197","68","43",[170,1244],{"x":1245,"y":1246,"width":1241,"height":209,"fill":214},"144","112",[170,1248],{"x":1249,"y":1218,"width":1241,"height":1250,"fill":214},"218","160",[170,1252],{"x":1253,"y":1245,"width":1241,"height":1254,"fill":214},"292","96",[170,1256],{"x":1257,"y":1258,"width":1241,"height":1259,"fill":226},"366","181","59",[170,1261],{"x":1262,"y":1263,"width":1241,"height":213,"fill":226},"440","224",[170,1265],{"x":1266,"y":1267,"width":1241,"height":1268,"fill":202},"514","235","5",[170,1270],{"x":1271,"y":1272,"width":1241,"height":1273,"fill":202},"588","213","27",[177,1275,1279],{"x":1276,"y":1277,"fill":187,"style":1278},"104","256","text-anchor:middle;font-size:10;font-family:var(--font-sans);","0-200",[177,1281,1283],{"x":1282,"y":1277,"fill":187,"style":1278},"178","200-400",[177,1285,1287],{"x":1286,"y":1277,"fill":187,"style":1278},"252","400-600",[177,1289,1291],{"x":1290,"y":1277,"fill":187,"style":1278},"326","600-800",[177,1293,1295],{"x":1294,"y":1277,"fill":187,"style":1278},"400","800-1200",[177,1297,1299],{"x":1298,"y":1277,"fill":187,"style":1278},"474","1200-1600",[177,1301,1303],{"x":1302,"y":1277,"fill":187,"style":1278},"548","1600-2000",[177,1305,1307],{"x":1306,"y":1277,"fill":187,"style":1278},"622","2000+",[177,1309,1313],{"x":1310,"y":1311,"fill":187,"style":1312},"360","278","text-anchor:middle;font-size:11;font-family:var(--font-sans);","cost bucket, micro-dollars per request",[255,1315],{"x1":1316,"y1":1239,"x2":1316,"y2":1214,"stroke":181,"style":1317},"306","stroke-width:2;stroke-dasharray:4 3;",[177,1319,1321],{"x":1316,"y":1320,"fill":181,"style":1312},"62","mean 641",[255,1323],{"x1":1324,"y1":1239,"x2":1324,"y2":1214,"stroke":202,"style":1317},"602",[177,1326,1327],{"x":1324,"y":1320,"fill":181,"style":1312},"p95 2,060",[177,1329,1330],{"x":179,"y":1316,"fill":181,"style":193},"Tail requests cost 3.2x the mean and concentrate in a few accounts",[14,1332,1333],{},"So compute both, per customer, and act on the ratio. The snippet below reads priced usage rows and reports the mean, p95, and the tail multiplier for every account. Anything above 2x deserves a look; anything above 4x is a pricing conversation or a rate limit.",[283,1335,1337],{"className":285,"code":1336,"language":287,"meta":288,"style":288},"import os\nimport statistics\nfrom collections import defaultdict\nfrom collections.abc import Iterable\n\nTAIL_ALERT_RATIO = float(os.getenv(\"TAIL_ALERT_RATIO\", \"2.0\"))\n\n\ndef cost_profile(rows: Iterable[tuple[str, float]]) -> dict[str, dict[str, float]]:\n    \"\"\"rows: (customer_id, request_cost_usd). Returns mean, p95 and tail ratio.\"\"\"\n    by_customer: dict[str, list[float]] = defaultdict(list)\n    for customer_id, usd in rows:\n        by_customer[customer_id].append(usd)\n\n    profile = {}\n    for customer_id, costs in by_customer.items():\n        costs.sort()\n        mean = statistics.fmean(costs)\n        p95 = (\n            statistics.quantiles(costs, n=20)[-1] if len(costs) >= 20 else costs[-1]\n        )\n        profile[customer_id] = {\n            \"requests\": len(costs),\n            \"mean_usd\": mean,\n            \"p95_usd\": p95,\n            \"tail_ratio\": p95 \u002F mean if mean else 0.0,\n            \"flagged\": (p95 \u002F mean if mean else 0.0) > TAIL_ALERT_RATIO,\n        }\n    return profile\n",[290,1338,1339,1345,1352,1364,1376,1380,1403,1407,1411,1444,1449,1474,1486,1491,1495,1505,1517,1522,1532,1542,1590,1595,1605,1618,1626,1634,1660,1690,1695],{"__ignoreMap":288},[293,1340,1341,1343],{"class":255,"line":295},[293,1342,299],{"class":298},[293,1344,303],{"class":302},[293,1346,1347,1349],{"class":255,"line":306},[293,1348,299],{"class":298},[293,1350,1351],{"class":302}," statistics\n",[293,1353,1354,1356,1359,1361],{"class":255,"line":314},[293,1355,317],{"class":298},[293,1357,1358],{"class":302}," collections ",[293,1360,299],{"class":298},[293,1362,1363],{"class":302}," defaultdict\n",[293,1365,1366,1368,1371,1373],{"class":255,"line":328},[293,1367,317],{"class":298},[293,1369,1370],{"class":302}," collections.abc ",[293,1372,299],{"class":298},[293,1374,1375],{"class":302}," Iterable\n",[293,1377,1378],{"class":255,"line":335},[293,1379,332],{"emptyLinePlaceholder":331},[293,1381,1382,1385,1387,1390,1392,1395,1397,1400],{"class":255,"line":361},[293,1383,1384],{"class":338},"TAIL_ALERT_RATIO",[293,1386,342],{"class":298},[293,1388,1389],{"class":338}," float",[293,1391,981],{"class":302},[293,1393,1394],{"class":348},"\"TAIL_ALERT_RATIO\"",[293,1396,352],{"class":302},[293,1398,1399],{"class":348},"\"2.0\"",[293,1401,1402],{"class":302},"))\n",[293,1404,1405],{"class":255,"line":366},[293,1406,332],{"emptyLinePlaceholder":331},[293,1408,1409],{"class":255,"line":371},[293,1410,332],{"emptyLinePlaceholder":331},[293,1412,1413,1415,1418,1421,1423,1425,1427,1430,1432,1435,1437,1439,1441],{"class":255,"line":393},[293,1414,474],{"class":298},[293,1416,1417],{"class":374}," cost_profile",[293,1419,1420],{"class":302},"(rows: Iterable[tuple[",[293,1422,483],{"class":338},[293,1424,352],{"class":302},[293,1426,503],{"class":338},[293,1428,1429],{"class":302},"]]) -> dict[",[293,1431,483],{"class":338},[293,1433,1434],{"class":302},", dict[",[293,1436,483],{"class":338},[293,1438,352],{"class":302},[293,1440,503],{"class":338},[293,1442,1443],{"class":302},"]]:\n",[293,1445,1446],{"class":255,"line":405},[293,1447,1448],{"class":348},"    \"\"\"rows: (customer_id, request_cost_usd). Returns mean, p95 and tail ratio.\"\"\"\n",[293,1450,1451,1454,1456,1459,1461,1464,1466,1469,1472],{"class":255,"line":411},[293,1452,1453],{"class":302},"    by_customer: dict[",[293,1455,483],{"class":338},[293,1457,1458],{"class":302},", list[",[293,1460,503],{"class":338},[293,1462,1463],{"class":302},"]] ",[293,1465,385],{"class":298},[293,1467,1468],{"class":302}," defaultdict(",[293,1470,1471],{"class":338},"list",[293,1473,358],{"class":302},[293,1475,1476,1478,1481,1483],{"class":255,"line":420},[293,1477,1115],{"class":298},[293,1479,1480],{"class":302}," customer_id, usd ",[293,1482,898],{"class":298},[293,1484,1485],{"class":302}," rows:\n",[293,1487,1488],{"class":255,"line":428},[293,1489,1490],{"class":302},"        by_customer[customer_id].append(usd)\n",[293,1492,1493],{"class":255,"line":436},[293,1494,332],{"emptyLinePlaceholder":331},[293,1496,1497,1500,1502],{"class":255,"line":445},[293,1498,1499],{"class":302},"    profile ",[293,1501,385],{"class":298},[293,1503,1504],{"class":302}," {}\n",[293,1506,1507,1509,1512,1514],{"class":255,"line":453},[293,1508,1115],{"class":298},[293,1510,1511],{"class":302}," customer_id, costs ",[293,1513,898],{"class":298},[293,1515,1516],{"class":302}," by_customer.items():\n",[293,1518,1519],{"class":255,"line":461},[293,1520,1521],{"class":302},"        costs.sort()\n",[293,1523,1524,1527,1529],{"class":255,"line":466},[293,1525,1526],{"class":302},"        mean ",[293,1528,385],{"class":298},[293,1530,1531],{"class":302}," statistics.fmean(costs)\n",[293,1533,1534,1537,1539],{"class":255,"line":471},[293,1535,1536],{"class":302},"        p95 ",[293,1538,385],{"class":298},[293,1540,1541],{"class":302}," (\n",[293,1543,1544,1547,1550,1552,1554,1557,1560,1563,1565,1567,1570,1573,1576,1579,1581,1584,1586,1588],{"class":255,"line":509},[293,1545,1546],{"class":302},"            statistics.quantiles(costs, ",[293,1548,1549],{"class":381},"n",[293,1551,385],{"class":298},[293,1553,179],{"class":338},[293,1555,1556],{"class":302},")[",[293,1558,1559],{"class":298},"-",[293,1561,1562],{"class":338},"1",[293,1564,913],{"class":302},[293,1566,946],{"class":298},[293,1568,1569],{"class":338}," len",[293,1571,1572],{"class":302},"(costs) ",[293,1574,1575],{"class":298},">=",[293,1577,1578],{"class":338}," 20",[293,1580,689],{"class":298},[293,1582,1583],{"class":302}," costs[",[293,1585,1559],{"class":298},[293,1587,1562],{"class":338},[293,1589,553],{"class":302},[293,1591,1592],{"class":255,"line":541},[293,1593,1594],{"class":302},"        )\n",[293,1596,1597,1600,1602],{"class":255,"line":556},[293,1598,1599],{"class":302},"        profile[customer_id] ",[293,1601,385],{"class":298},[293,1603,1604],{"class":302}," {\n",[293,1606,1607,1610,1612,1615],{"class":255,"line":561},[293,1608,1609],{"class":348},"            \"requests\"",[293,1611,1148],{"class":302},[293,1613,1614],{"class":338},"len",[293,1616,1617],{"class":302},"(costs),\n",[293,1619,1620,1623],{"class":255,"line":566},[293,1621,1622],{"class":348},"            \"mean_usd\"",[293,1624,1625],{"class":302},": mean,\n",[293,1627,1628,1631],{"class":255,"line":600},[293,1629,1630],{"class":348},"            \"p95_usd\"",[293,1632,1633],{"class":302},": p95,\n",[293,1635,1636,1639,1642,1644,1647,1649,1651,1654,1657],{"class":255,"line":606},[293,1637,1638],{"class":348},"            \"tail_ratio\"",[293,1640,1641],{"class":302},": p95 ",[293,1643,635],{"class":298},[293,1645,1646],{"class":302}," mean ",[293,1648,946],{"class":298},[293,1650,1646],{"class":302},[293,1652,1653],{"class":298},"else",[293,1655,1656],{"class":338}," 0.0",[293,1658,1659],{"class":302},",\n",[293,1661,1662,1665,1668,1670,1672,1674,1676,1678,1680,1682,1685,1688],{"class":255,"line":615},[293,1663,1664],{"class":348},"            \"flagged\"",[293,1666,1667],{"class":302},": (p95 ",[293,1669,635],{"class":298},[293,1671,1646],{"class":302},[293,1673,946],{"class":298},[293,1675,1646],{"class":302},[293,1677,1653],{"class":298},[293,1679,1656],{"class":338},[293,1681,532],{"class":302},[293,1683,1684],{"class":298},">",[293,1686,1687],{"class":338}," TAIL_ALERT_RATIO",[293,1689,1659],{"class":302},[293,1691,1692],{"class":255,"line":626},[293,1693,1694],{"class":302},"        }\n",[293,1696,1697,1699],{"class":255,"line":652},[293,1698,927],{"class":298},[293,1700,1701],{"class":302}," profile\n",[14,1703,1704,1705,1709],{},"Run this weekly against your usage table and surface it in ",[18,1706,1708],{"href":1707},"\u002Fbuilding-monetizing-api-driven-micro-saas\u002Ftracking-api-usage-and-analytics\u002Fbuilding-a-customer-usage-dashboard\u002F","a customer usage dashboard"," so support sees the same number you do.",[24,1711],{},[27,1713,1715],{"id":1714},"the-gross-margin-floor","The gross-margin floor",[14,1717,1718],{},"Now turn cost into a minimum price. Four adjustments separate raw cost from a list price, and skipping any one of them is how builders end up at 40% gross margin wondering where the money went.",[14,1720,1721,1722,1725,1726,1729,1730,1733,1734,1737],{},"Start with the mean billable cost of 641 micro-dollars. ",[37,1723,1724],{},"First",", divide by the billable share of traffic. If 8% of requests come from free-tier accounts that never convert, paying customers carry that load: 641 ÷ 0.92 = 697. ",[37,1727,1728],{},"Second",", divide by your target cost share. An 80% gross margin means cost is 20% of price, so 697 ÷ 0.20 = 3,483 micro-dollars. ",[37,1731,1732],{},"Third",", add payment processing: 2.9% of the charge plus a fixed 30 cents per invoice, which across a 40,000-request average invoice adds 7.5 micro-dollars per request. Solving for the gross price gives (3,483 + 7.5) ÷ 0.971 = 3,594. ",[37,1735,1736],{},"Fourth",", round up and give yourself headroom for the tail: $0.004 per request, or $4 per thousand.",[151,1739,160,1743,160,1746,160,1749,160,1751,160,1755,160,1759,160,1765,160,1770,160,1774,160,1778,160,1783,160,1786,160,1790,160,1794,160,1797,160,1801,160,1805,160,1808,160,1811,160,1815,160,1818],{"viewBox":153,"role":154,"ariaLabelledBy":1740,"xmlns":158,"style":159},[1741,1742],"cpr-floor-t","cpr-floor-d",[162,1744,1745],{"id":1741},"From mean cost to list price in four adjustments",[166,1747,1748],{"id":1742},"Stepped chart rising from 641 micro-dollars of mean cost through free-tier drag, an eighty percent margin gross-up and card fees to a 4000 micro-dollar list price.",[170,1750],{"x":172,"y":172,"width":173,"height":174,"fill":175},[177,1752,1754],{"x":179,"y":1753,"fill":181,"style":182},"28","Cost to price, in micro-dollars per request",[255,1756],{"x1":1757,"y1":1758,"x2":258,"y2":1758,"stroke":259,"style":260},"30","250",[170,1760],{"x":1761,"y":1762,"width":1763,"height":1764,"rx":201,"fill":226},"40","221","100","29",[177,1766,1769],{"x":1767,"y":1272,"fill":181,"style":1768},"90","text-anchor:middle;font-size:12;font-family:var(--font-sans);","641",[177,1771,1773],{"x":1767,"y":1772,"fill":181,"style":1312},"270","Mean cost",[170,1775],{"x":197,"y":1776,"width":1763,"height":1777,"rx":201,"fill":226},"219","31",[177,1779,1782],{"x":1780,"y":1781,"fill":181,"style":1768},"230","211","697",[177,1784,1785],{"x":1780,"y":1772,"fill":181,"style":1312},"Free-tier drag",[170,1787],{"x":1206,"y":1788,"width":1763,"height":1789,"rx":201,"fill":214},"93","157",[177,1791,1793],{"x":1792,"y":1235,"fill":181,"style":1768},"370","3,483",[177,1795,1796],{"x":1792,"y":1772,"fill":181,"style":1312},"Margin gross-up",[170,1798],{"x":1799,"y":192,"width":1763,"height":1800,"rx":201,"fill":214},"460","162",[177,1802,1804],{"x":1803,"y":1218,"fill":181,"style":1768},"510","3,594",[177,1806,1807],{"x":1803,"y":1772,"fill":181,"style":1312},"Card fees",[170,1809],{"x":1810,"y":1239,"width":1763,"height":197,"rx":201,"fill":202},"600",[177,1812,1814],{"x":1813,"y":1320,"fill":181,"style":1768},"650","4,000",[177,1816,1817],{"x":1813,"y":1772,"fill":181,"style":1312},"List price",[177,1819,1820],{"x":179,"y":1253,"fill":187,"style":188},"$0.004 per request = $4 per thousand, an 84% gross margin at the mean",[14,1822,1823,1824,1828],{},"Translate that into a package and the tiers write themselves: a $29 tier with 8,000 requests included prices each call at 3,625 micro-dollars — above the floor — while a $99 tier with 30,000 included sits at 3,300 and needs either a volume-discount justification or a smaller allowance. That is the whole point of the floor. It converts \"does this tier feel cheap?\" into a yes-or-no test. Wire the resulting per-unit price into ",[18,1825,1827],{"href":1826},"\u002Fbuilding-monetizing-api-driven-micro-saas\u002Fdesigning-api-pricing-tiers\u002Fstripe-metered-billing-configuration\u002F","Stripe metered billing configuration"," and the model stops being a spreadsheet and starts being an invoice.",[24,1830],{},[27,1832,1834],{"id":1833},"when-to-model-per-request-and-when-not-to","When to model per request — and when not to",[14,1836,1837],{},"Per-request modelling earns its keep when a variable, per-call vendor cost dominates: model tokens, geocoding lookups, SMS sends, enrichment providers. There the marginal request has a real price tag and margin genuinely moves with volume.",[14,1839,1840,1841,1845],{},"It is the wrong lens when your bill is almost entirely fixed. A CRUD API on a $25 container and a $7 database has a marginal cost near zero and a fixed cost that only steps when you add an instance. Model that per tenant-month instead, and spend your effort on the step function — the traffic level at which you add a machine — rather than on six decimal places per call. Hosting choice shifts that step function considerably, which is what ",[18,1842,1844],{"href":1843},"\u002Fbuilding-monetizing-api-driven-micro-saas\u002Fdeploying-apis-to-render-or-vercel\u002Frender-vs-railway-vs-fly-io\u002F","Render vs Railway vs Fly.io"," compares.",[151,1847,160,1852,160,1855,160,1858,160,1861,160,1866,160,1871,160,1874,160,1878,160,1881,160,1883,160,1885,160,1888,160,1892,160,1896,160,1899,160,1902,160,1905,160,1908,160,1911,160,1914,160,1918,160,1920,160,1922,160,1925,160,1929,160,1931,160,1934,160,1936,160,1939],{"viewBox":1848,"role":154,"ariaLabelledBy":1849,"xmlns":158,"style":159},"0 0 720 290",[1850,1851],"cpr-tree-t","cpr-tree-d",[162,1853,1854],{"id":1850},"Choosing the right cost unit for your API",[166,1856,1857],{"id":1851},"Decision tree branching from what dominates the bill into per-request, per-tenant-month and per-stored-gigabyte cost models.",[170,1859],{"x":172,"y":172,"width":173,"height":1860,"fill":175},"290",[170,1862],{"x":1214,"y":1863,"width":1214,"height":1864,"rx":225,"fill":175,"stroke":187,"style":1865},"24","46","stroke-width:2;",[177,1867,1870],{"x":1310,"y":1868,"fill":181,"style":1869},"53","text-anchor:middle;font-size:13;font-family:var(--font-sans);","What dominates the bill?",[255,1872],{"x1":1310,"y1":1239,"x2":1310,"y2":1873,"stroke":259,"style":1865},"98",[255,1875],{"x1":1876,"y1":1873,"x2":1877,"y2":1873,"stroke":259,"style":1865},"130","590",[255,1879],{"x1":1876,"y1":1873,"x2":1876,"y2":1880,"stroke":259,"style":1865},"126",[255,1882],{"x1":1310,"y1":1873,"x2":1310,"y2":1880,"stroke":259,"style":1865},[255,1884],{"x1":1877,"y1":1873,"x2":1877,"y2":1880,"stroke":259,"style":1865},[170,1886],{"x":1757,"y":1880,"width":1887,"height":1864,"rx":225,"fill":175,"stroke":202,"style":1865},"200",[177,1889,1891],{"x":1876,"y":1890,"fill":181,"style":1768},"146","Per-call vendor",[177,1893,1895],{"x":1876,"y":1894,"fill":181,"style":1768},"163","or model spend",[170,1897],{"x":1898,"y":1880,"width":1887,"height":1864,"rx":225,"fill":175,"stroke":214,"style":1865},"260",[177,1900,1901],{"x":1310,"y":1890,"fill":181,"style":1768},"Fixed instances",[177,1903,1904],{"x":1310,"y":1894,"fill":181,"style":1768},"and idle capacity",[170,1906],{"x":1907,"y":1880,"width":1887,"height":1864,"rx":225,"fill":175,"stroke":226,"style":1865},"490",[177,1909,1910],{"x":1877,"y":1890,"fill":181,"style":1768},"Stored data and",[177,1912,1913],{"x":1877,"y":1894,"fill":181,"style":1768},"large payloads",[255,1915],{"x1":1876,"y1":1916,"x2":1876,"y2":1917,"stroke":259,"style":1865},"172","204",[255,1919],{"x1":1310,"y1":1916,"x2":1310,"y2":1917,"stroke":259,"style":1865},[255,1921],{"x1":1877,"y1":1916,"x2":1877,"y2":1917,"stroke":259,"style":1865},[170,1923],{"x":1757,"y":1917,"width":1887,"height":1761,"rx":225,"fill":202,"fillOpacity":1924,"stroke":202,"style":1865},0.22,[177,1926,1928],{"x":1876,"y":1927,"fill":181,"style":1869},"229","Cost per request",[170,1930],{"x":1898,"y":1917,"width":1887,"height":1761,"rx":225,"fill":214,"fillOpacity":1924,"stroke":214,"style":1865},[177,1932,1933],{"x":1310,"y":1927,"fill":181,"style":1869},"Cost per tenant",[170,1935],{"x":1907,"y":1917,"width":1887,"height":1761,"rx":225,"fill":226,"fillOpacity":1924,"stroke":226,"style":1865},[177,1937,1938],{"x":1877,"y":1927,"fill":181,"style":1869},"Cost per stored GB",[177,1940,1941],{"x":1757,"y":1772,"fill":187,"style":188},"Pick the unit your bill actually scales with, then price that unit.",[14,1943,1944],{},"Skip the exercise entirely below roughly 50,000 requests a month. At that volume the entire infrastructure bill is smaller than one hour of your time, and the honest answer is to ship features. Start modelling when a vendor invoice grows faster than revenue, when you are about to publish a public price, or when one account's traffic exceeds 20% of the total.",[24,1946],{},[27,1948,1950],{"id":1949},"the-transition-path-instrumenting-cost-attribution","The transition path: instrumenting cost attribution",[14,1952,1953],{},"Moving from guesswork to a live model takes four steps and about an afternoon.",[14,1955,1956,1959,1960,1963,1964,1967,1968,1971],{},[37,1957,1958],{},"Step one:"," attach a mutable cost accumulator to every request and let handlers add to it. ",[37,1961,1962],{},"Step two:"," price it in middleware and emit the total on the access log line. ",[37,1965,1966],{},"Step three:"," persist that number alongside the usage row you already write. ",[37,1969,1970],{},"Step four:"," replace the estimated rate card with the real numbers from last month's invoices, divided by last month's request count. Reconciliation is the step people skip, and it is the one that catches the storage line you forgot.",[283,1973,1975],{"className":285,"code":1974,"language":287,"meta":288,"style":288},"import os\nimport time\nfrom contextvars import ContextVar\nfrom fastapi import FastAPI, Request\n\nfrom cost_model import RequestShape, cost_of, load_rates\n\nRATES = load_rates()\nusage: ContextVar[dict[str, float]] = ContextVar(\"usage\")\napp = FastAPI()\n\n\n@app.middleware(\"http\")\nasync def attribute_cost(request: Request, call_next):\n    token = usage.set({\"input_tokens\": 0.0, \"output_tokens\": 0.0, \"db_seconds\": 0.0})\n    started = time.perf_counter()\n    try:\n        response = await call_next(request)\n    finally:\n        elapsed_ms = (time.perf_counter() - started) * 1000\n        counters = usage.get()\n        shape = RequestShape(\n            cpu_ms=elapsed_ms,\n            egress_kb=float(response.headers.get(\"content-length\", 0)) \u002F 1024,\n            db_seconds=counters[\"db_seconds\"],\n            input_tokens=int(counters[\"input_tokens\"]),\n            output_tokens=int(counters[\"output_tokens\"]),\n            log_kb=float(os.getenv(\"LOG_KB_PER_REQUEST\", \"2\")),\n        )\n        response.headers[\"x-request-cost-micro\"] = f\"{cost_of(shape, RATES)['total'] * 1e6:.1f}\"\n        usage.reset(token)\n    return response\n",[290,1976,1977,1983,1990,2002,2014,2018,2030,2034,2044,2067,2077,2081,2085,2097,2111,2150,2160,2167,2180,2187,2207,2217,2226,2236,2265,2280,2297,2312,2332,2336,2380,2385],{"__ignoreMap":288},[293,1978,1979,1981],{"class":255,"line":295},[293,1980,299],{"class":298},[293,1982,303],{"class":302},[293,1984,1985,1987],{"class":255,"line":306},[293,1986,299],{"class":298},[293,1988,1989],{"class":302}," time\n",[293,1991,1992,1994,1997,1999],{"class":255,"line":314},[293,1993,317],{"class":298},[293,1995,1996],{"class":302}," contextvars ",[293,1998,299],{"class":298},[293,2000,2001],{"class":302}," ContextVar\n",[293,2003,2004,2006,2009,2011],{"class":255,"line":328},[293,2005,317],{"class":298},[293,2007,2008],{"class":302}," fastapi ",[293,2010,299],{"class":298},[293,2012,2013],{"class":302}," FastAPI, Request\n",[293,2015,2016],{"class":255,"line":335},[293,2017,332],{"emptyLinePlaceholder":331},[293,2019,2020,2022,2025,2027],{"class":255,"line":361},[293,2021,317],{"class":298},[293,2023,2024],{"class":302}," cost_model ",[293,2026,299],{"class":298},[293,2028,2029],{"class":302}," RequestShape, cost_of, load_rates\n",[293,2031,2032],{"class":255,"line":366},[293,2033,332],{"emptyLinePlaceholder":331},[293,2035,2036,2039,2041],{"class":255,"line":371},[293,2037,2038],{"class":338},"RATES",[293,2040,342],{"class":298},[293,2042,2043],{"class":302}," load_rates()\n",[293,2045,2046,2049,2051,2053,2055,2057,2059,2062,2065],{"class":255,"line":393},[293,2047,2048],{"class":302},"usage: ContextVar[dict[",[293,2050,483],{"class":338},[293,2052,352],{"class":302},[293,2054,503],{"class":338},[293,2056,1463],{"class":302},[293,2058,385],{"class":298},[293,2060,2061],{"class":302}," ContextVar(",[293,2063,2064],{"class":348},"\"usage\"",[293,2066,358],{"class":302},[293,2068,2069,2072,2074],{"class":255,"line":405},[293,2070,2071],{"class":302},"app ",[293,2073,385],{"class":298},[293,2075,2076],{"class":302}," FastAPI()\n",[293,2078,2079],{"class":255,"line":411},[293,2080,332],{"emptyLinePlaceholder":331},[293,2082,2083],{"class":255,"line":420},[293,2084,332],{"emptyLinePlaceholder":331},[293,2086,2087,2090,2092,2095],{"class":255,"line":428},[293,2088,2089],{"class":374},"@app.middleware",[293,2091,378],{"class":302},[293,2093,2094],{"class":348},"\"http\"",[293,2096,358],{"class":302},[293,2098,2099,2102,2105,2108],{"class":255,"line":436},[293,2100,2101],{"class":298},"async",[293,2103,2104],{"class":298}," def",[293,2106,2107],{"class":374}," attribute_cost",[293,2109,2110],{"class":302},"(request: Request, call_next):\n",[293,2112,2113,2116,2118,2121,2124,2126,2129,2131,2134,2136,2138,2140,2143,2145,2147],{"class":255,"line":445},[293,2114,2115],{"class":302},"    token ",[293,2117,385],{"class":298},[293,2119,2120],{"class":302}," usage.set({",[293,2122,2123],{"class":348},"\"input_tokens\"",[293,2125,1148],{"class":302},[293,2127,2128],{"class":338},"0.0",[293,2130,352],{"class":302},[293,2132,2133],{"class":348},"\"output_tokens\"",[293,2135,1148],{"class":302},[293,2137,2128],{"class":338},[293,2139,352],{"class":302},[293,2141,2142],{"class":348},"\"db_seconds\"",[293,2144,1148],{"class":302},[293,2146,2128],{"class":338},[293,2148,2149],{"class":302},"})\n",[293,2151,2152,2155,2157],{"class":255,"line":453},[293,2153,2154],{"class":302},"    started ",[293,2156,385],{"class":298},[293,2158,2159],{"class":302}," time.perf_counter()\n",[293,2161,2162,2165],{"class":255,"line":461},[293,2163,2164],{"class":298},"    try",[293,2166,402],{"class":302},[293,2168,2169,2172,2174,2177],{"class":255,"line":466},[293,2170,2171],{"class":302},"        response ",[293,2173,385],{"class":298},[293,2175,2176],{"class":298}," await",[293,2178,2179],{"class":302}," call_next(request)\n",[293,2181,2182,2185],{"class":255,"line":471},[293,2183,2184],{"class":298},"    finally",[293,2186,402],{"class":302},[293,2188,2189,2192,2194,2197,2199,2202,2204],{"class":255,"line":509},[293,2190,2191],{"class":302},"        elapsed_ms ",[293,2193,385],{"class":298},[293,2195,2196],{"class":302}," (time.perf_counter() ",[293,2198,1559],{"class":298},[293,2200,2201],{"class":302}," started) ",[293,2203,1156],{"class":298},[293,2205,2206],{"class":338}," 1000\n",[293,2208,2209,2212,2214],{"class":255,"line":541},[293,2210,2211],{"class":302},"        counters ",[293,2213,385],{"class":298},[293,2215,2216],{"class":302}," usage.get()\n",[293,2218,2219,2222,2224],{"class":255,"line":556},[293,2220,2221],{"class":302},"        shape ",[293,2223,385],{"class":298},[293,2225,968],{"class":302},[293,2227,2228,2231,2233],{"class":255,"line":561},[293,2229,2230],{"class":381},"            cpu_ms",[293,2232,385],{"class":298},[293,2234,2235],{"class":302},"elapsed_ms,\n",[293,2237,2238,2241,2243,2245,2248,2251,2253,2255,2258,2260,2263],{"class":255,"line":566},[293,2239,2240],{"class":381},"            egress_kb",[293,2242,385],{"class":298},[293,2244,503],{"class":338},[293,2246,2247],{"class":302},"(response.headers.get(",[293,2249,2250],{"class":348},"\"content-length\"",[293,2252,352],{"class":302},[293,2254,172],{"class":338},[293,2256,2257],{"class":302},")) ",[293,2259,635],{"class":298},[293,2261,2262],{"class":338}," 1024",[293,2264,1659],{"class":302},[293,2266,2267,2270,2272,2275,2277],{"class":255,"line":600},[293,2268,2269],{"class":381},"            db_seconds",[293,2271,385],{"class":298},[293,2273,2274],{"class":302},"counters[",[293,2276,2142],{"class":348},[293,2278,2279],{"class":302},"],\n",[293,2281,2282,2285,2287,2289,2292,2294],{"class":255,"line":606},[293,2283,2284],{"class":381},"            input_tokens",[293,2286,385],{"class":298},[293,2288,1047],{"class":338},[293,2290,2291],{"class":302},"(counters[",[293,2293,2123],{"class":348},[293,2295,2296],{"class":302},"]),\n",[293,2298,2299,2302,2304,2306,2308,2310],{"class":255,"line":615},[293,2300,2301],{"class":381},"            output_tokens",[293,2303,385],{"class":298},[293,2305,1047],{"class":338},[293,2307,2291],{"class":302},[293,2309,2133],{"class":348},[293,2311,2296],{"class":302},[293,2313,2314,2317,2319,2321,2323,2326,2328,2330],{"class":255,"line":626},[293,2315,2316],{"class":381},"            log_kb",[293,2318,385],{"class":298},[293,2320,503],{"class":338},[293,2322,981],{"class":302},[293,2324,2325],{"class":348},"\"LOG_KB_PER_REQUEST\"",[293,2327,352],{"class":302},[293,2329,1101],{"class":348},[293,2331,992],{"class":302},[293,2333,2334],{"class":255,"line":652},[293,2335,1594],{"class":302},[293,2337,2338,2341,2344,2346,2348,2351,2353,2355,2358,2360,2362,2365,2367,2369,2372,2375,2377],{"class":255,"line":667},[293,2339,2340],{"class":302},"        response.headers[",[293,2342,2343],{"class":348},"\"x-request-cost-micro\"",[293,2345,913],{"class":302},[293,2347,385],{"class":298},[293,2349,2350],{"class":298}," f",[293,2352,838],{"class":348},[293,2354,829],{"class":338},[293,2356,2357],{"class":302},"cost_of(shape, ",[293,2359,2038],{"class":338},[293,2361,1556],{"class":302},[293,2363,2364],{"class":348},"'total'",[293,2366,913],{"class":302},[293,2368,1156],{"class":298},[293,2370,2371],{"class":338}," 1e6",[293,2373,2374],{"class":298},":.1f",[293,2376,835],{"class":338},[293,2378,2379],{"class":348},"\"\n",[293,2381,2382],{"class":255,"line":695},[293,2383,2384],{"class":302},"        usage.reset(token)\n",[293,2386,2387,2389],{"class":255,"line":712},[293,2388,927],{"class":298},[293,2390,2391],{"class":302}," response\n",[14,2393,2394,2395,2398,2399,2403],{},"Handlers add to the counters with ",[290,2396,2397],{},"usage.get()[\"input_tokens\"] += n"," after each model call. Emit the total through ",[18,2400,2402],{"href":2401},"\u002Fscaling-and-operating-production-python-apis\u002Fmonitoring-and-logging-python-apis\u002Fstructured-logging-with-structlog\u002F","structured logging with structlog"," rather than a response header in production — the header above is for local inspection.",[24,2405],{},[27,2407,2409],{"id":2408},"builder-verdict","Builder verdict",[14,2411,2412,2413,2417],{},"Build the model, but build the cheap version. A frozen dataclass, a TOML rate card, and a middleware that stamps a cost on every request will get you within 10% of truth in an afternoon, and 10% is plenty when you are deciding between $3 and $4 per thousand calls. The winner here is the per-request model priced off the mean with a p95 alert on top — not a per-tenant allocation, and not a full activity-based costing exercise. Set your floor at mean cost divided by your target cost share, adjusted for free-tier drag and card fees, then round up. Where cost really bites is the tail, and the answer to the tail is operational, not arithmetic: cache aggressively with ",[18,2414,2416],{"href":2415},"\u002Fscaling-and-operating-production-python-apis\u002Fcaching-python-api-responses-with-redis\u002F","Redis response caching",", cap request size, and rate-limit the accounts your tail-ratio report flags. Price on the mean, defend the p95, and revisit the rate card every quarter.",[24,2419],{},[27,2421,2423],{"id":2422},"faq","FAQ",[14,2425,2426,2429],{},[37,2427,2428],{},"What gross margin should an API side project target?","\nAim for 80% at the mean request. Below 70% you have no room for support, refunds, or a bad month of vendor pricing, and below 50% you are reselling somebody else's infrastructure at a markup. If a vendor call makes 80% impossible, the fix is caching, smaller models, or a higher price — not accepting the margin.",[14,2431,2432,2435],{},[37,2433,2434],{},"Should I price on the mean cost or the p95 cost?","\nPrice on the mean, monitor the p95. Pricing on the p95 makes you uncompetitive for the 95% of traffic that is cheap. Instead, set the floor from the mean and control the tail with request-size caps, per-account rate limits, and a tail-ratio alert that flags accounts costing more than twice their mean.",[14,2437,2438,2441],{},[37,2439,2440],{},"How do free-tier users change the calculation?","\nThey raise the effective cost of every paid request. Divide your mean cost by the share of traffic that is billable — at 8% free traffic that is a 9% cost increase carried by paying customers. If free traffic climbs past 20% of volume, tighten the tier using the tactics in the free-tier abuse guide rather than raising prices.",[14,2443,2444,2447],{},[37,2445,2446],{},"What is the migration risk when I raise prices after modelling costs?","\nGrandfather existing customers for at least one full billing cycle and announce the new price 30 days ahead. Most churn at a price change comes from surprise, not from the number. Publishing the included allowance and the overage rate up front also protects you when a vendor raises rates and you need to follow.",[14,2449,2450,2453],{},[37,2451,2452],{},"How often should I refresh the rate card?","\nQuarterly, and immediately after any vendor pricing change or region move. Reconcile the modelled cost against the actual invoice each month: divide the total bill by the request count and compare. A drift above 15% means a component is missing from the model, usually storage, backups, or log ingest.",[27,2455,2457],{"id":2456},"related","Related",[14,2459,2460],{},[37,2461,2462],{},"Same track:",[2464,2465,2466,2472,2479],"ul",{},[2467,2468,2469,2471],"li",{},[18,2470,21],{"href":20}," — the parent guide that turns this cost floor into published tiers.",[2467,2473,2474,2478],{},[18,2475,2477],{"href":2476},"\u002Fbuilding-monetizing-api-driven-micro-saas\u002Fdesigning-api-pricing-tiers\u002Fusage-based-vs-seat-based-pricing\u002F","Usage-Based vs Seat-Based Pricing"," — pick the billing axis before you compute a per-unit floor.",[2467,2480,2481,2485],{},[18,2482,2484],{"href":2483},"\u002Fbuilding-monetizing-api-driven-micro-saas\u002Fdesigning-api-pricing-tiers\u002Fpreventing-free-tier-abuse\u002F","Preventing Free-Tier Abuse"," — keeps the unbillable share of traffic small enough to absorb.",[14,2487,2488],{},[37,2489,2490],{},"Other tracks:",[2464,2492,2493,2499],{},[2467,2494,2495,2498],{},[18,2496,2497],{"href":60},"Logging API Usage Events to Postgres"," — the usage table this model reads its per-request costs from.",[2467,2500,2501,2504],{},[18,2502,2503],{"href":270},"Controlling LLM API Costs in Production"," — cut the component that dominates 94% of the bill.",[2506,2507,2508],"style",{},"html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":288,"searchDepth":306,"depth":306,"links":2510},[2511,2512,2513,2514,2515,2516,2517,2518,2519],{"id":29,"depth":306,"text":30},{"id":277,"depth":306,"text":278},{"id":1184,"depth":306,"text":1185},{"id":1714,"depth":306,"text":1715},{"id":1833,"depth":306,"text":1834},{"id":1949,"depth":306,"text":1950},{"id":2408,"depth":306,"text":2409},{"id":2422,"depth":306,"text":2423},{"id":2456,"depth":306,"text":2457},"Model the real cost per API request — compute, egress, database, vendor and LLM spend — then set the p95-aware price floor that clears your gross-margin target.","md",{"pageTitle":2523,"type":2524,"datePublished":2525,"dateModified":2525},"Calculating Cost per API Request: A Unit-Economics Model","article","2026-07-23","\u002Fbuilding-monetizing-api-driven-micro-saas\u002Fdesigning-api-pricing-tiers\u002Fcalculating-cost-per-api-request",{"title":5,"description":2520},"building-monetizing-api-driven-micro-saas\u002Fdesigning-api-pricing-tiers\u002Fcalculating-cost-per-api-request\u002Findex","bq1gqu2poEhWIk3-uURdaPhQ8wpjDfQZImjDaI3JBaw",{"@context":2531,"@type":2532,"mainEntity":2533},"https:\u002F\u002Fschema.org","FAQPage",[2534,2539,2542,2545,2548],{"@type":2535,"name":2428,"acceptedAnswer":2536},"Question",{"@type":2537,"text":2538},"Answer","Aim for 80% at the mean request. Below 70% you have no room for support, refunds, or a bad month of vendor pricing, and below 50% you are reselling somebody else's infrastructure at a markup. If a vendor call makes 80% impossible, the fix is caching, smaller models, or a higher price — not accepting the margin.",{"@type":2535,"name":2434,"acceptedAnswer":2540},{"@type":2537,"text":2541},"Price on the mean, monitor the p95. Pricing on the p95 makes you uncompetitive for the 95% of traffic that is cheap. Instead, set the floor from the mean and control the tail with request-size caps, per-account rate limits, and a tail-ratio alert that flags accounts costing more than twice their mean.",{"@type":2535,"name":2440,"acceptedAnswer":2543},{"@type":2537,"text":2544},"They raise the effective cost of every paid request. Divide your mean cost by the share of traffic that is billable — at 8% free traffic that is a 9% cost increase carried by paying customers. If free traffic climbs past 20% of volume, tighten the tier using the tactics in the free-tier abuse guide rather than raising prices.",{"@type":2535,"name":2446,"acceptedAnswer":2546},{"@type":2537,"text":2547},"Grandfather existing customers for at least one full billing cycle and announce the new price 30 days ahead. Most churn at a price change comes from surprise, not from the number. Publishing the included allowance and the overage rate up front also protects you when a vendor raises rates and you need to follow.",{"@type":2535,"name":2452,"acceptedAnswer":2549},{"@type":2537,"text":2550},"Quarterly, and immediately after any vendor pricing change or region move. Reconcile the modelled cost against the actual invoice each month: divide the total bill by the request count and compare. A drift above 15% means a component is missing from the model, usually storage, backups, or log ingest.",1784887028459]