# Bill for seats (/docs/tutorials/bill-for-seats)

In this tutorial you bill a team for its seats. The plan includes 5 seats. Each seat above that costs 8 USD a month, and the bill uses the **most** seats the team had at any hour of the month. A team that grows to 12 seats for a week and then drops back to 6 pays for 12.

This needs a `gauge` metric. A gauge does not add events up: each hour keeps its highest reading, and the bill uses the highest hour of the period.

## Before you start [#before-you-start]

You need the same things as in [Bill for API calls](/docs/tutorials/bill-for-api-calls#before-you-start): usage billing turned on, a test secret token, a test monthly USD subscription and a usage key. If you did that tutorial, reuse its agreement `agr_team_42` and its key.

<Steps>

<Step>

## Create a gauge metric [#create-a-gauge-metric]

```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_seats",
    "name": "Seats",
    "event_key": "seats",
    "unit_label": "seat",
    "aggregation": "gauge"
  }'
```

Choose the aggregation with care: it can not change once the usage meter has the metric. For a different one, create a new metric.

</Step>

<Step>

## Price the seats [#price-the-seats]

A `flat_per_unit` plan charges `overage_price` for each seat above `included_allowance`.

```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": "seats_standard",
    "version": 1,
    "metric_id": "met_seats",
    "currency": "USD",
    "pricing_model": "flat_per_unit",
    "included_allowance": 5,
    "overage_price": 8
  }'
```

In the dashboard this is **Usage Billing**, **Prices**, with **How it is charged** set to **Per unit**.

</Step>

<Step>

## Add seats to the agreement [#add-seats-to-the-agreement]

```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_seats",
    "billing_agreement_id": "agr_team_42",
    "metric_id": "met_seats",
    "rate_plan_id": "seats_standard",
    "rate_plan_version": 1
  }'
```

An agreement can hold one entitlement per metric, so the same team can be billed for API calls and seats on one invoice.

</Step>

<Step>

## Send the seat count every hour [#send-the-seat-count-every-hour]

Run a job once an hour that sends each team's current number of seats. Make the `operation_id` from the team and the hour. A rerun in the same hour is then a `duplicate`, never a second reading.

```js title="report-seats.js"
const USAGE_URL = 'https://usage.comecero.com/v1/events';

export async function reportSeats(teams) {
  const hour = new Date();
  hour.setUTCMinutes(0, 0, 0);
  const stamp = hour.toISOString().replace('.000Z', 'Z');

  const events = teams.map((team) => ({
    operation_id: `seats_${team.id}_${stamp}`,
    external_customer_id: team.id,
    metric_key: 'seats',
    quantity: String(team.seatCount),
    timestamp: stamp,
  }));

  for (let i = 0; i < events.length; i += 1000) {
    const response = await fetch(USAGE_URL, {
      method: 'POST',
      headers: {
        Authorization: `Bearer ${process.env.COMECERO_USAGE_KEY}`,
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({ events: events.slice(i, i + 1000) }),
    });
    if (response.status !== 200) throw new Error(`usage meter answered ${response.status}: try this hour again`);
  }
}
```

Run it again on any error: every event of the hour is safe to resend. To catch a short spike, also send a reading when seats are added, with its own id such as `seats_team_42_2026-11-10T09:41:07Z_added`. The hour keeps the highest reading.

</Step>

</Steps>

## What the team pays [#what-the-team-pays]

The bill reads the highest hour of the month:

| Week | Seats |
| --- | --- |
| 1 | 6 |
| 2 | 12 |
| 3 and 4 | 6 |

The highest hour is 12 seats. 12 minus the 5 included is 7 billable seats, at 8 USD: **56.00 USD** on the renewal invoice, as "Seats (seat): 12 used".

If you would rather bill the average over the month, count seat-hours instead: a `sum` metric that gets the seat count every hour, priced per seat-hour.

## Next steps [#next-steps]

- [Metrics and aggregations](/docs/usage-billing/metrics-and-aggregations) explains `sum` and `gauge` in full.
- [Show usage in your app](/docs/tutorials/show-usage-in-your-app) so admins see their seats against the plan.
