/** * Computes the monthly token volume at which a fixed-cost self-hosted box * (Hetzner GEX44 GPU dedicated server) breaks even against Claude API * pay-per-token pricing. Artifact for * /answers/claude-api-cost-vs-self-hosted-llm-breakeven. * * Pure arithmetic on public list prices, no network call: every number below * is a published rate, not a measurement of anyone's actual traffic. What * this script establishes is the *shape* of the breakeven curve and how hard * the input:output token ratio moves it, which no single "self-hosting is * cheaper past X tokens" headline number can carry on its own. * * Inputs (sourced, see content page for full citations): * - Claude API pricing per MTok, input/output: platform.claude.com/docs/en/about-claude/pricing * (fetched 2026-08-24, current since Sonnet 5 introductory pricing became standard) * - Hetzner GEX44 dedicated GPU server: EUR184/mo, RTX 4000 SFF Ada, 20 GB * GDDR6, runs open models up to ~32B params at 4-bit quantisation. * hetzner.com/dedicated-rootserver/matrix-gpu/ * - EUR/USD reference rate: 1.1699, ECB, 2026-08-21 * ecb.europa.eu/stats/policy_and_exchange_rates/euro_reference_exchange_rates * * Model: a request mixes input and output tokens in some ratio R = input:output. * Blended price per million tokens = (R * inputPrice + 1 * outputPrice) / (R + 1). * Breakeven volume (millions of tokens/month) = fixedMonthlyCostUSD / blendedPrice. * Below that volume, the API is cheaper; above it, the fixed box is cheaper, * on token cost alone (see the page's "What these numbers do not say" for * everything this ignores: ops time, latency, quality gap, egress). * * Run: * node claude-vs-hetzner-breakeven.mjs * * Measured output (deterministic, no seed needed): * * Hetzner GEX44: EUR184/mo -> $215.26/mo (EUR/USD 1.1699, ECB 2026-08-21) * * model | input$/MTok | output$/MTok | R=1:1 | R=3:1 | R=10:1 * -------------|-------------|--------------|--------|--------|-------- * Haiku 4.5 | 1.00 | 5.00 | 71.8M | 107.6M | 157.9M * Sonnet 5 | 2.00 | 10.00 | 35.9M | 53.8M | 78.9M * Opus 5 | 5.00 | 25.00 | 14.4M | 21.5M | 31.6M * * (M = million tokens/month, input+output combined, to break even with one * Hetzner GEX44 running a comparable open model at 4-bit quantisation) * * What it establishes: * * 1. The breakeven point moves 2.2x just from the I/O ratio, holding the * model fixed. Sonnet 5 breaks even at 36M tokens/month on a 1:1 chat * workload but not until 79M on a 10:1 RAG/agent workload, because * output is 5x more expensive than input and a context-heavy workload * is mostly input. A page (or a vendor) that quotes one breakeven number * without stating its I/O ratio is quoting a made-up number. * * 2. The three model tiers do not scale together. Opus 5 breaks even 2.5x * sooner than Sonnet 5 at every ratio (both input and output are exactly * 2.5x Sonnet's price), so "self-hosting pays off past N million tokens" * is only meaningful once the model tier is named. * * 3. This is a token-cost breakeven, not a total-cost-of-ownership one. It * ignores the ops time to run and patch the box, the latency and quality * gap between a 32B open model and Sonnet/Opus, and that GEX44 is a * fixed capacity ceiling (~32B params at 4-bit) the API tiers are not * bound by. */ const EUR_USD = 1.1699 // ECB reference rate, 2026-08-21 const HETZNER_GEX44_EUR_MONTHLY = 184 const HETZNER_GEX44_USD_MONTHLY = HETZNER_GEX44_EUR_MONTHLY * EUR_USD const MODELS = [ { name: 'Haiku 4.5', inputPerMTok: 1.0, outputPerMTok: 5.0 }, { name: 'Sonnet 5', inputPerMTok: 2.0, outputPerMTok: 10.0 }, { name: 'Opus 5', inputPerMTok: 5.0, outputPerMTok: 25.0 }, ] const IO_RATIOS = [1, 3, 10] // input:output, e.g. 3 means 3 input tokens per 1 output token function blendedPricePerMTok(inputPrice, outputPrice, ioRatio) { return (ioRatio * inputPrice + 1 * outputPrice) / (ioRatio + 1) } function breakevenMillionTokens(fixedMonthlyUSD, blendedPrice) { return fixedMonthlyUSD / blendedPrice } function fmtM(n) { return `${n.toFixed(1)}M` } console.log() console.log( `Hetzner GEX44: EUR${HETZNER_GEX44_EUR_MONTHLY}/mo -> $${HETZNER_GEX44_USD_MONTHLY.toFixed(2)}/mo ` + `(EUR/USD ${EUR_USD}, ECB 2026-08-21)`, ) console.log() const header = ['model', 'input$/MTok', 'output$/MTok', ...IO_RATIOS.map((r) => `R=${r}:1`)] console.log(header.map((h) => h.padEnd(12)).join('| ')) console.log(header.map((h) => '-'.repeat(12)).join('|-')) for (const m of MODELS) { const cells = [ m.name.padEnd(12), m.inputPerMTok.toFixed(2).padEnd(12), m.outputPerMTok.toFixed(2).padEnd(12), ...IO_RATIOS.map((r) => { const blended = blendedPricePerMTok(m.inputPerMTok, m.outputPerMTok, r) const breakeven = breakevenMillionTokens(HETZNER_GEX44_USD_MONTHLY, blended) return fmtM(breakeven).padEnd(12) }), ] console.log(cells.join('| ')) } console.log() console.log('M = million tokens/month (input+output) to break even with one Hetzner') console.log('GEX44 running a comparable open model at 4-bit quantisation.')