API credit pricing: how to decide what one credit is worth
Most API pricing posts hand you a taxonomy of models and stop before the arithmetic. This is the seller's side: cost per route, the credit unit, whole-number weights, and the one margin number that actually protects you.

Sorower
Co-founder

In this article
- API credit pricing is a unit of account, not a price
- Step 1: Find the marginal cost of each route
- Step 2: Pick what one credit is worth
- Step 3: Weight your routes in whole numbers
- Step 4: Check the floor, not the blend
- Step 5: Publish the table and the rules around it
- Wiring the rate card into ReqKey
- Key takeaways
Someone emails asking what your API costs. You have a hosting bill, a creeping suspicion that your upstream provider is eating the margin, and no answer. So you pick $0.01 per request, because $0.01 looks like a price.
API credit pricing is the usual escape hatch, and it is a good one. But almost everything written about it stops one step before the part you actually need. Zuplo's rundown of eight pricing models is the best of the genre and it still contains no arithmetic for turning your costs into a rate card. Most of the rest of page one is cost calculators built for the buyer, so someone can estimate what your API will cost them.
This is the seller's side. Measure per route, pick a unit, weight in whole numbers, and check the one margin number that actually protects you. That last step is where most credit systems quietly go wrong, and it takes about six lines of arithmetic to catch.
A pricing model is not a price. Choosing "credit-based" tells you as much about what to charge as choosing "metric" tells you how tall to build.
API credit pricing is a unit of account, not a price
A credit is an internal unit you invent. It does two jobs, and only two.
First, it lets one number cover operations that cost you wildly different amounts to serve. A cache-warm lookup and a twelve-second document synthesis both come out of the same balance, at different weights, and your customer only has to understand one currency.
Second, it decouples your published prices from your cost structure. When your upstream model provider doubles its price, you change the weight on the two routes that use it. You do not renegotiate every contract or republish every plan.
Which also tells you when credits are the wrong answer. If every route on your API costs roughly the same to serve, credits are a layer of indirection that makes your pricing page harder to read for no benefit. Charge per request and go do something more useful. Credits start earning their keep the moment your most expensive endpoint costs something like ten times your cheapest.
Do credits have to map to dollars? No, and it is worth knowing that before you start. The YouTube Data API runs a quota-unit system with no money in it at all: per Google's published quota table, a videos.list read costs 1 unit while a videos.delete costs 50, all drawn against a daily allowance. Same weighting mechanics, zero billing. If you are rationing capacity rather than charging for it, you can follow this post to step 3 and stop.
Step 1: Find the marginal cost of each route
Not your average cost per request. Your cost per request on that route. An average is exactly the number that hides the problem you are trying to find.
What belongs in it:
- Upstream fees. Third-party API calls, model tokens, data licences. Anything you pay per call because the call happened.
- Compute. The CPU and memory time the route actually burns. A route that holds a browser open for eight seconds is not the same as one that reads an index.
- Data movement. Egress, storage you write, caches you populate.
- The metering layer. The thing counting the usage costs money too, and it is the line people forget.
That last one deserves a number rather than a shrug. On ReqKey's pricing, one request means one key validation or one logged API call, so a route where you both validate the key and ship the traffic log consumes two. It is small, it is real, and it belongs in the cost column rather than in a surprise at the end of the month.
What stays out: salaries, base infrastructure, support, the office plant. Those are fixed costs. They determine the volume you need to survive, not the price of a single call. Mixing them into a per-call figure produces a number that changes every time you hire someone, which is not a useful basis for a rate card.
Where do these numbers come from? Your traffic logs, not your intuition. You want p50 and p95 cost per route, because the tail is what a heavy customer will find. If you cannot break cost down by route today, that is the actual first task, and it is a smaller job than the pricing exercise you were about to attempt without it.
Step 2: Pick what one credit is worth
There is no industry standard here, and the spread is larger than you would guess. Two credit-priced APIs I read while writing this differ by more than a factor of ten:
- Tavily publishes $0.008 per credit on pay-as-you-go, falling to between $0.0075 and $0.005 per credit on monthly plans.
- Firecrawl's Scale plan is $599 per month, billed yearly, for 1,000,000 credits. Divide one by the other and a credit is worth about $0.0006.
Neither is wrong, because a credit is not a thing with a market rate. It is a unit you define, and its only job is to make the ratios between your operations legible.
So the practical rule is boring and it works: set one credit equal to your cheapest chargeable operation. Every other route becomes a whole-number multiple of that, and the dollar value of a credit is simply the price you want on your cheapest call. You are not deriving a currency. You are picking a denominator.
One warning about the pay-as-you-go rate. Whatever number you publish for a single credit becomes the anchor customers use to judge everything else on the page, including your plans. Tavily's plan rates land below its pay-as-you-go rate, which is the shape you want: buying ahead should be visibly cheaper than not. Get that backwards and your pricing page argues against your own plans.
Step 3: Weight your routes in whole numbers
Now the ratio table, which is the part customers will actually read. Two real ones, both published openly, both worth copying the shape of:
| Vendor | Operation | Credit cost |
|---|---|---|
| Firecrawl | Scrape | 1 per page |
| Firecrawl | Crawl | 1 per page |
| Firecrawl | Map | 1 per page |
| Firecrawl | Search | 2 per 10 results |
| Firecrawl | Interact | 2 per browser minute |
| Tavily | Search (basic) | 1 |
| Tavily | Search (advanced) | 2 |
| Tavily | Extract (basic) | 1 per 5 successful extractions |
| Tavily | Research (mini) | 4 to 110 |
| Tavily | Research (pro) | 15 to 250 |
Two patterns in there. Most rows are a fixed weight per unit of work, which is what you want: a customer can multiply and budget. Tavily's Research rows are a range, because the underlying work genuinely varies by two orders of magnitude. That is honest, and it is also expensive in a way that does not show up on the invoice. Nobody can plan against "somewhere between 15 and 250." If you have a route like that, consider charging for the sub-operations it performs rather than the route itself, so the number becomes derivable instead of a surprise.
Then there is the trap that eats fractional weights. In ReqKey, POST /key/validate takes credits as a number and rounds a non-integer value down, so 2.5 charges 2 and 0.5 charges nothing at all. Most metering layers do something like this, and the direction is rarely in your favour. If your ratios need halves, multiply the whole table by ten and let your cheapest call cost 10 credits. Integers all the way down.
Charging a weighted call looks like this:
curl -X POST "https://api.reqkey.com/key/validate" \
-H "Authorization: Bearer reqkey_xxx..." \
-H "Content-Type: application/json" \
-d '{
"key": "prod_A1B2C3D4E5F6G7H8I9J0K1L2",
"credits": 20,
"resource": "/v1/report"
}'
Expected response:
{
"valid": true,
"requestId": "abc123xyz",
"creditsRemaining": 9980,
"creditsLimit": 10000,
"resource": "/v1/report"
}
The resource field is worth setting properly. It is what lets you answer "which route is this customer's spend going to" later, and you will want that answer during step 4.
Step 4: Check the floor, not the blend
Here is the part nobody writes down, and it is the reason credit systems that looked fine in a spreadsheet start losing money in month four.
Take a three-route API. The cost column here is invented for the example; yours comes from step 1. One credit is worth $0.004.
| Route | Cost per call | Credits | Revenue per call | Gross margin |
|---|---|---|---|---|
GET /v1/lookup | $0.0004 | 1 | $0.004 | 90% |
POST /v1/enrich | $0.0110 | 4 | $0.016 | 31.3% |
POST /v1/report | $0.0620 | 20 | $0.080 | 22.5% |
Now run two customers through it. Both send exactly 10,000 calls a month.
| Customer A (90/8/2 mix) | Customer B (20/30/50 mix) | |
|---|---|---|
| Revenue | $64.80 | $456.00 |
| Cost | $24.80 | $343.80 |
| Gross margin | 61.7% | 24.6% |
Same rate card, same call volume, and a margin gap of 37 points. Nothing was mispriced. The mix moved.
Now the useful bit. Blended margin is 1 - (total cost / total revenue), and total cost / total revenue is a revenue-weighted average of each route's individual cost ratio. A weighted average cannot escape the range of its own inputs. So:
Your blended margin is mathematically trapped between your worst route's margin and your best route's margin. Traffic mix only decides where inside that band you land.
Which means the average-mix scenario in your spreadsheet is close to worthless as a safety check. It tells you where you probably are, not how bad it can get. The number that protects you is the gross margin on your single most expensive route, because that is the floor. In the table above the floor is 22.5%. If your business needs 40%, the report route is underpriced and no amount of favourable mix will rescue it. Change the weight to 32 credits and the floor moves to 51.6%, and every blend improves with it.
While you are here, notice what weighting bought you. Had you priced this API flat at $0.0065 per request, roughly the blended rate across a normal-looking mix, Customer B would pay $65 and cost you $343.80. That is not a thin month. That is a customer you are paying to keep. Weighted credits do not equalise margin across customers, and they were never going to. What they do is stop a single heavy user from turning your unit economics inside out.
Your worst customer is not the one sending the most traffic. It is the one whose traffic sits entirely on the route you priced closest to cost.
Step 5: Publish the table and the rules around it
A rate card with no policy attached is half a rate card. Five questions your docs need to answer before a developer will integrate, in roughly the order they will ask them:
- What is a credit worth in money? On every plan, not just the cheapest one.
- What does each operation cost? A table, on a page, not buried in a changelog.
- What happens when a call fails? Tavily states plainly that you are never charged if a URL extraction fails or a map request fails. That is a published policy. If you do not publish yours, customers will infer one from their invoice, and they will infer the least generous version available. We wrote about the ways this goes wrong in your API credit system bills before it knows the request worked.
- Do credits expire? Rollover, carryover caps, and whether an unused balance survives a renewal. Say it out loud, because the answer "they vanish" discovered mid-integration is a support ticket with a refund attached.
- What happens at zero? The status code, the body, and how to fix it. ReqKey returns
402 Payment Requiredwith{"valid": false, "message": "Credit limit exceeded"}and the remaining and limit values, which is enough for a client to distinguish "out of money" from "bad key" without guessing.
Question 5 is the one that decides whether your support inbox is survivable. A customer hitting a wall wants to know which wall it was.
Wiring the rate card into ReqKey
Two pieces. A plan holds the credit allowance and records the commercial terms; /key/validate charges the weight per call.
curl -X POST "https://api.reqkey.com/plan/create" \
-H "Authorization: Bearer reqkey_xxx..." \
-H "Content-Type: application/json" \
-d '{
"planName": "Growth",
"credits": {
"limit": 50000,
"refill": {
"interval": "monthly",
"refillCredit": 50000,
"refillPolicy": "reset"
}
},
"pricing": {
"currency": "USD",
"perCreditCharge": 0.004,
"overageCharge": 0.006
},
"status": "active"
}'
Worth being straight about scope: that pricing block is informational. It is where the rate card lives next to the credit limits so the two cannot drift apart, and it is what /plan/details hands back to your billing job. It does not invoice anybody. ReqKey meters; your billing system charges.
On the request path, keep the weights in one map and pass them through. Note the catch: if the metering layer is unreachable you have to decide, deliberately, whether the call proceeds. Serving a request you cannot meter is a choice, and it is a better one than a 500, but it should be a choice you made in advance rather than one your stack trace makes for you.
import { ReqKey, ReqKeyError } from 'reqkey'
const client = ReqKey.fromEnv()
const CREDIT_COST: Record<string, number> = {
'/v1/lookup': 1,
'/v1/enrich': 4,
'/v1/report': 20,
}
export async function charge(apiKey: string, route: string) {
try {
const result = await client.verify(apiKey, {
credits: CREDIT_COST[route] ?? 1,
resource: route,
})
if (!result.valid) {
return { ok: false, reason: result.reason, retryAfter: result.retryAfter }
}
return { ok: true, remaining: result.creditsRemaining }
} catch (err) {
if (err instanceof ReqKeyError) {
// Metering unavailable. Serve the call, reconcile the usage later.
console.error('reqkey verify failed, failing open:', err.message)
return { ok: true, remaining: null, metered: false }
}
throw err
}
}
The ?? 1 fallback is deliberate. A route you forgot to add to the map should cost something rather than nothing, because free-by-accident is the failure mode you will not notice for a quarter. If you would rather find out immediately, throw on the missing key instead. Both are defensible; silently charging zero is not.
Where the limit itself should live is a separate design question, and one we went through properly in multi-tenant API quotas: where the limit actually belongs. Short version: in ReqKey the credit pool sits on the consumer, so every key that consumer holds draws from the same balance. That is usually the seam you want, because a customer rotating a key should not reset their bill.
Key takeaways
- Price routes, not requests. An average cost per call is the number that hides your worst endpoint. Break cost down per route first; everything else in this post depends on having that table.
- One credit should equal your cheapest chargeable operation. That makes every other weight a whole number and makes the dollar value of a credit a decision you already made. Fractional weights get rounded down and quietly leak revenue.
- Check the margin on your most expensive route, not the blend. Blended margin cannot fall below your worst route's margin or rise above your best. That floor is the only number that survives a change in traffic mix.
- Publish the failure policy alongside the rate card. What a failed call costs, whether credits expire, and what status code arrives at zero. Customers who have to infer these from an invoice infer the worst version.
- Weighted credits protect unit economics, not fairness. They stop one heavy user from inverting your margins. They will not make every customer equally profitable, and no rate card will.
If you want to try the weighting without building the meter first, the credits field on /key/validate is just a number you pass per route, and ReqKey's free tier covers 100,000 requests a month, which is more than enough traffic to run a real rate card against real customers before you commit to it. The plans and credits page has the mechanics, and if you are still deciding what to build this on, our comparison of six key management platforms prices the same workload across all of them.


