Bill for API calls
Charge customers for the API calls they make, with calls included each month and cheaper tiers at volume. Count calls and send them in batches.
In this tutorial you bill customers of your own API for the calls they make. Each customer gets 10,000 calls a month in their plan. After that, the next 100,000 calls cost 0.002 USD each and every call above that costs 0.001 USD. Your server counts calls as they happen and sends them to the Comecero usage meter in batches. Comecero adds the calls to the customer's monthly invoice.
You build it in test mode. Nothing is charged.
Before you start
- Usage billing turned on for your account. Comecero turns it on: ask support. Until then, the usage routes answer
403with the codeusage_billing_off. - A test secret token in
COMECERO_SECRET_TOKEN. See Authentication. - A test customer with a monthly USD subscription, for example from the Subscriptions guide. Put its id in
SUBSCRIPTION_ID. The usage is billed on this subscription's renewal invoices. - Node.js 18 or later and an Express app (or any server: the code is plain
fetch).
Create the metric
A metric is what you count. event_key is the name your server sends with each event, and sum adds the events up over the month.
curl https://api.comecero.com/api/v1/usage/metrics \
-H "Authorization: Bearer $COMECERO_SECRET_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"metric_id": "met_api_calls",
"name": "API calls",
"event_key": "api_calls",
"unit_label": "call",
"aggregation": "sum",
"allowed_properties": ["endpoint"]
}'Always send event_key. It defaults to name, and a name with capitals or spaces is not a valid key. allowed_properties lists the extra values an event may carry. Here each call can say which endpoint it hit.
Set the price
A tiered_graduated plan prices each slice of usage at its own tier's price. Tiers count the calls above the included allowance, so up_to: 100000 means the first 100,000 calls after the 10,000 included ones.
curl https://api.comecero.com/api/v1/usage/rate_plans \
-H "Authorization: Bearer $COMECERO_SECRET_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"rate_plan_id": "api_calls_standard",
"version": 1,
"metric_id": "met_api_calls",
"currency": "USD",
"pricing_model": "tiered_graduated",
"included_allowance": 10000,
"tiers": [
{ "up_to": 100000, "unit_price": 0.002 },
{ "up_to": null, "unit_price": 0.001 }
]
}'A saved version never changes. To change the price later, save version: 2 with the same rate_plan_id and move customers to it. Use a plan in the subscription's currency: a plan in another currency is accepted here, but its bill fails with currency_mismatch.
Link the customer
A billing agreement links your id for the customer to their Comecero subscription. Your server sends that id with every event as external_customer_id. Use your own user or team id, never an email.
curl https://api.comecero.com/api/v1/usage/agreements \
-H "Authorization: Bearer $COMECERO_SECRET_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"agreement_id": "agr_team_42",
"customer_id": "team_42",
"subscription_id": "'"$SUBSCRIPTION_ID"'",
"currency": "USD",
"effective_from": "2026-11-10T00:00:00Z"
}'Then put the metric and its price on the agreement with a metered entitlement:
curl https://api.comecero.com/api/v1/usage/entitlements \
-H "Authorization: Bearer $COMECERO_SECRET_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"entitlement_id": "ent_team_42_api_calls",
"billing_agreement_id": "agr_team_42",
"metric_id": "met_api_calls",
"rate_plan_id": "api_calls_standard",
"rate_plan_version": 1
}'You can do the same in the dashboard: Usage Billing, then Customers and Bill a customer. Tiered prices are made with the API only.
Create a usage key
Your server sends events with a usage key, not with your API token.
curl https://api.comecero.com/api/v1/usage/keys \
-H "Authorization: Bearer $COMECERO_SECRET_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "name": "API server" }'The answer has key, which starts with apk_test_. It is shown only this once: put it in your server's secrets as COMECERO_USAGE_KEY. A new key starts working within about 5 minutes.
Count calls in your server
Add a small queue that collects one event per call and sends them every 10 seconds, or as soon as 500 are waiting. Each event gets its own operation_id, so a resend is never counted twice.
import { randomUUID } from 'node:crypto';
const USAGE_URL = 'https://usage.comecero.com/v1/events';
const queue = [];
export function countCall(customerId, endpoint) {
queue.push({
operation_id: `call_${randomUUID()}`,
external_customer_id: customerId,
metric_key: 'api_calls',
quantity: '1',
timestamp: new Date().toISOString(),
properties: { endpoint },
});
if (queue.length >= 500) flush();
}
export async function flush() {
const batch = queue.splice(0, 1000);
if (batch.length === 0) return;
for (let attempt = 1; attempt <= 5; attempt++) {
try {
const response = await fetch(USAGE_URL, {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.COMECERO_USAGE_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({ events: batch }),
});
if (response.status === 200) {
const answer = await response.json();
for (const result of answer.results) {
if (result.status === 'rejected' || result.status === 'conflict') console.error('usage event not counted', result);
}
return;
}
if (response.status !== 429 && response.status < 500) {
console.error('usage meter refused the batch', response.status, await response.text());
return; // a 400, 401, 403 or 413 needs a fix, not a resend
}
} catch {
// network error or timeout: resending the same batch is safe
}
await new Promise((resolve) => setTimeout(resolve, 2 ** attempt * 1000));
}
queue.unshift(...batch); // keep them for the next flush: events up to 30 days old are still billed
}
setInterval(flush, 10_000).unref();
process.on('SIGTERM', () => flush().finally(() => process.exit(0)));Call it from a middleware that runs after your auth, where you know the customer:
import express from 'express';
import { countCall } from './usage.js';
const app = express();
app.use('/v1', (req, res, next) => {
res.on('finish', () => {
if (res.statusCode < 400 && req.teamId) countCall(req.teamId, req.path);
});
next();
});This counts only calls that worked. Count what your pricing promises: if failed calls are billed too, drop the status check.
A busy server can send fewer events by adding calls up first: one event with quantity: "250" per customer and minute, with an operation_id such as team_42_2026-11-10T09:41. Never reuse an operation_id for new usage: the same id with another quantity is a conflict, and the first one stays.
Check that calls arrive
Make a few calls, wait a minute, then read the usage so far:
curl https://api.comecero.com/api/v1/usage/agreements/agr_team_42/current \
-H "Authorization: Bearer $COMECERO_SECRET_TOKEN"{
"agreement_id": "agr_team_42",
"customer_id": "team_42",
"currency": "USD",
"entitlements": [
{ "metric_name": "API calls", "unit_label": "call", "used": 37, "included": 10000, "percent": 0, "amount": 0 }
]
}It is kept for 5 minutes, so it can be a few minutes behind. In the dashboard, open Usage Billing, Customers, the customer, then Events to see each event.
The meter counts an event for any external_customer_id. Usage is billed only when the id is the customer_id of an agreement, so a typo is counted but never billed. If used stays at 0, check the ids first.
What the customer pays
When the subscription renews, Comecero bills the month that just ended. For 130,000 calls:
| Step | Calls | Amount |
|---|---|---|
| Included | 10,000 | 0.00 |
| First tier, at 0.002 USD | 100,000 | 200.00 |
| Second tier, at 0.001 USD | 20,000 | 20.00 |
| Usage charge | 130,000 | 220.00 USD |
The 220.00 USD goes on the renewal invoice as one line, "API calls (call): 130000 used", next to the plan's price, and is charged with the customer's saved payment method. See How usage is billed.
Next steps
- Show usage in your app and warn customers at 80%.
- Sell prepaid credit that is used before anything is charged.
- Send usage has every field, answer and limit of the usage meter.
- When it works in test mode, do the same with a live token. Live metrics, keys and usage are kept apart from test ones.
Hello World
Build a test checkout in one HTML page. Get a limited token from the browser, create a cart, pay it with a test card and show the result.
Bill for seats
Charge for the most seats a team used in a month with a gauge metric. Send a seat count every hour, include five seats and price the rest per seat.