Cross-cloud cost math: when serverless beats fixed tiers
Every API consumer asks the same question sooner or later: which gateway
tier should this run on? Most teams answer from habit. The right answer is
arithmetic.
I added a cost command to
api-onboarding-kit
because onboarding should include the answer. It reads the consumer spec,
does the multiplication across every gateway tier, and prints the options
cheapest first. The point is not that the numbers are perfect. They are
from public list pricing and they move. The point is that the method is
boring and it beats arguing from gut feeling.
The shape of the math
Every tier is one of two shapes:
- Serverless:
cost = requests x per-million price, no base. - Fixed:
cost = flat monthly base, no per-request term.
Requests per month come straight from the spec:
monthly_requests = expected_rps x 86,400 x 30 So a consumer at 10 rps average does about 25.9 million requests a month.
Run that through the pricing table in the repo and you get:
- Azure APIM Consumption: 25.92 x $4.00 = $103.68
- AWS API Gateway HTTP API: 25.92 x $1.00 = $25.92
- GCP API Gateway: 25.92 x $3.00 = $77.76
- Azure APIM Standard: $700.00 flat
- Apigee X pay-as-you-go: 25.92 x $40.00 = $1,036.80
At this traffic level, serverless wins everywhere and the fixed tier looks
absurd. That reverses fast.
The crossover point
Fixed beats serverless when:
monthly_millions > base_usd / per_million_usd Take Azure APIM Standard ($700 flat) against Consumption ($4 per million):
# 700 / 4 = 175 million requests per month crossover_millions = 700.0 / 4.00 crossover_rps = crossover_millions * 1e6 / (86_400 * 30) # => ~67 rps sustained, all month Below ~67 rps sustained, the consumption tier is cheaper. Above it, the
fixed tier wins and every request past the line is effectively free.
That is the whole decision. One division.
What the math misses
Cheapest is not always right. Two things I learned the hard way:
1. Serverless tiers drop features. In APIM, the consumption tier does
not give you VNet support or the production SLA. If your security review
requires private networking, the "cheaper" tier is not an option and the
comparison is over before it starts. The estimator flags these notes on
every row for a reason.
2. Averages lie. expectedRps drives the cost, but burstRps
decides whether the tier survives. A consumer that averages 5 rps and
bursts to 500 rps can blow through a per-request tier's limits during the
spike while costing nothing on paper. Burst is a capacity question, not a
pricing question. Model it separately.
So the working rule I give teams: run the arithmetic to pick the tier,
then check the feature list and the burst behavior to confirm the tier
is even allowed. Cost picks the winner among legal options.
The command
onboard cost --all --spec payments-team.yaml It reads expectedRps from the spec and prints every gateway, every
tier, cheapest first. Adding a tier is one dictionary entry:
PRICING["azure-apim"]["tiers"]["premium"] = { "base_usd": 2600.0, "per_million_usd": 0.0, "note": "Multi-region, higher throughput", } Then the crossover math just works, because it is arithmetic, not logic.
The takeaway
Gateway cost is not a negotiation or a vendor pitch. It is one division
problem per tier. Do it in CI, on every new consumer, before the traffic
exists. The numbers move and the features change, but the method does not.