# Bill for API calls (/docs/tutorials/bill-for-api-calls)

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 [#before-you-start]

- Usage billing turned on for your account. Comecero turns it on: ask support. Until then, the usage routes answer `403` with the code `usage_billing_off`.
- A **test** secret token in `COMECERO_SECRET_TOKEN`. See [Authentication](/docs/basic-concepts/authentication).
- A test customer with a monthly **USD subscription**, for example from the [Subscriptions guide](/docs/guides/subscriptions). 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`).

<Steps>

<Step>

## Create the metric [#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.

```bash title="Request"
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.

</Step>

<Step>

## Set the price [#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.

```bash title="Request"
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`.

</Step>

<Step>

## Link the customer [#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.

```bash title="Request"
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:

```bash title="Request"
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.

</Step>

<Step>

## Create a usage key [#create-a-usage-key]

Your server sends events with a usage key, not with your API token.

```bash title="Request"
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.

</Step>

<Step>

## Count calls in your server [#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.

```js title="usage.js"
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:

```js title="server.js"
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.

</Step>

<Step>

## Check that calls arrive [#check-that-calls-arrive]

Make a few calls, wait a minute, then read the usage so far:

```bash title="Request"
curl https://api.comecero.com/api/v1/usage/agreements/agr_team_42/current \
  -H "Authorization: Bearer $COMECERO_SECRET_TOKEN"
```

```json title="Response"
{
  "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.

</Step>

</Steps>

## What the customer pays [#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](/docs/usage-billing/how-usage-is-billed).

## Next steps [#next-steps]

- [Show usage in your app](/docs/tutorials/show-usage-in-your-app) and warn customers at 80%.
- [Sell prepaid credit](/docs/tutorials/sell-prepaid-credit) that is used before anything is charged.
- [Send usage](/docs/usage-billing/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.
