> ## Documentation Index
> Fetch the complete documentation index at: https://docs.borga.is/llms.txt
> Use this file to discover all available pages before exploring further.

# Usage-based billing with meters and events

> Set up usage-based pricing with Borga meters, ingest usage events, and bill customers at the end of each period based on their actual consumption.

Usage-based billing charges customers for what they actually consume rather than a fixed recurring amount. Borga implements this through two primitives: **meters**, which define what to measure and how to aggregate it, and **events**, which are the raw usage records you send from your application. At the end of each billing period, Borga totals the events for each customer and generates an invoice based on the metered price you configured.

<Steps>
  <Step title="Create a meter">
    A meter defines the unit of measurement — what event name to listen for and how to aggregate the raw events into a billable quantity.

    <CodeGroup>
      ```bash curl theme={null}
      curl --request POST \
        --url https://api.borga.is/v1/meters \
        --header "Authorization: Bearer sk_test_YOUR_SECRET_KEY" \
        --header "X-Merchant-Id: mer_YOUR_MERCHANT_ID" \
        --header "Content-Type: application/json" \
        --data '{
          "name": "API Calls",
          "event_name": "api_call",
          "aggregate_type": "count"
        }'
      ```

      ```javascript Node.js theme={null}
      const meter = await fetch('https://api.borga.is/v1/meters', {
        method: 'POST',
        headers: {
          'Authorization': 'Bearer sk_test_YOUR_SECRET_KEY',
          'X-Merchant-Id': 'mer_YOUR_MERCHANT_ID',
          'Content-Type': 'application/json',
        },
        body: JSON.stringify({
          name: 'API Calls',
          event_name: 'api_call',
          aggregate_type: 'count',
        }),
      }).then(r => r.json());

      console.log(meter.id); // mtr_01HXYZ…
      ```
    </CodeGroup>

    The `event_name` links the meter to events you send later — any event with `event_name: "api_call"` is captured by this meter. The `aggregate_type` controls how Borga combines individual events into a single quantity for the billing period:

    | Aggregate type | Behaviour                                          |
    | -------------- | -------------------------------------------------- |
    | `count`        | Number of events received.                         |
    | `sum`          | Sum of the `value` field across all events.        |
    | `max`          | Highest `value` seen across all events.            |
    | `min`          | Lowest `value` seen across all events.             |
    | `avg`          | Average `value` across all events.                 |
    | `unique`       | Count of distinct `value` entries (deduplication). |

    For API call billing, `count` is the right choice. For data transfer billing, use `sum` and pass bytes in the `value` field.
  </Step>

  <Step title="Create a metered price">
    Create a price that references the meter. Set `recurring.usage_type` to `"metered"` so Borga knows to aggregate events rather than charge a fixed amount.

    <CodeGroup>
      ```bash curl theme={null}
      curl --request POST \
        --url https://api.borga.is/v1/prices \
        --header "Authorization: Bearer sk_test_YOUR_SECRET_KEY" \
        --header "X-Merchant-Id: mer_YOUR_MERCHANT_ID" \
        --header "Content-Type: application/json" \
        --data '{
          "product": "pro_01HXYZ",
          "currency": "ISK",
          "type": "recurring",
          "unit_amount": 1,
          "meter": "mtr_01HXYZ",
          "recurring": {
            "interval": "month",
            "usage_type": "metered"
          }
        }'
      ```

      ```javascript Node.js theme={null}
      const price = await fetch('https://api.borga.is/v1/prices', {
        method: 'POST',
        headers: {
          'Authorization': 'Bearer sk_test_YOUR_SECRET_KEY',
          'X-Merchant-Id': 'mer_YOUR_MERCHANT_ID',
          'Content-Type': 'application/json',
        },
        body: JSON.stringify({
          product: 'pro_01HXYZ',
          currency: 'ISK',
          type: 'recurring',
          unit_amount: 1,
          meter: 'mtr_01HXYZ',
          recurring: {
            interval: 'month',
            usage_type: 'metered',
          },
        }),
      }).then(r => r.json());

      console.log(price.id); // pri_01HXYZ…
      ```
    </CodeGroup>

    The `unit_amount` is the price per unit in whole ISK. In this example, each API call costs 1 kr. You can combine a metered price with a fixed base price by including both as items on the same subscription.
  </Step>

  <Step title="Create a subscription with the metered price">
    Create a subscription that includes the metered price as one of its items. The customer is not charged for usage until Borga invoices at the end of the billing period.

    ```bash curl theme={null}
    curl --request POST \
      --url https://api.borga.is/v1/subscriptions \
      --header "Authorization: Bearer sk_test_YOUR_SECRET_KEY" \
      --header "X-Merchant-Id: mer_YOUR_MERCHANT_ID" \
      --header "Content-Type: application/json" \
      --data '{
        "customer": "cus_01HXYZ",
        "items": [
          { "price": "pri_01HXYZ", "quantity": 1 }
        ],
        "collection_method": "charge_automatically"
      }'
    ```

    You can add both a fixed base price and the metered price to the same subscription to implement a "base fee plus usage" model:

    ```json theme={null}
    {
      "customer": "cus_01HXYZ",
      "items": [
        { "price": "pri_BASE_PLAN" },
        { "price": "pri_METERED_CALLS" }
      ],
      "collection_method": "charge_automatically"
    }
    ```
  </Step>

  <Step title="Ingest usage events">
    Send usage events from your application as they occur. Each event must include the `event_name` that matches your meter and the `customer` being charged.

    <CodeGroup>
      ```bash Single event theme={null}
      curl --request POST \
        --url https://api.borga.is/v1/events \
        --header "Authorization: Bearer sk_test_YOUR_SECRET_KEY" \
        --header "X-Merchant-Id: mer_YOUR_MERCHANT_ID" \
        --header "Content-Type: application/json" \
        --data '{
          "event_name": "api_call",
          "customer": "cus_01HXYZ",
          "idempotency_key": "req_7a3f91b2c4d5e6f0",
          "timestamp": "2026-04-29T10:30:00Z",
          "metadata": {
            "endpoint": "/v1/payments",
            "method": "POST"
          }
        }'
      ```

      ```bash Batch events theme={null}
      curl --request POST \
        --url https://api.borga.is/v1/events/batch \
        --header "Authorization: Bearer sk_test_YOUR_SECRET_KEY" \
        --header "X-Merchant-Id: mer_YOUR_MERCHANT_ID" \
        --header "Content-Type: application/json" \
        --data '{
          "events": [
            {
              "event_name": "api_call",
              "customer": "cus_01HXYZ",
              "idempotency_key": "req_7a3f91b2c4d5e6f0",
              "timestamp": "2026-04-29T10:30:00Z"
            },
            {
              "event_name": "api_call",
              "customer": "cus_01HXYZ",
              "idempotency_key": "req_8b4c02d3e5f6a1b2",
              "timestamp": "2026-04-29T10:31:00Z"
            }
          ]
        }'
      ```

      ```javascript Node.js theme={null}
      // Single event
      await fetch('https://api.borga.is/v1/events', {
        method: 'POST',
        headers: {
          'Authorization': 'Bearer sk_test_YOUR_SECRET_KEY',
          'X-Merchant-Id': 'mer_YOUR_MERCHANT_ID',
          'Content-Type': 'application/json',
        },
        body: JSON.stringify({
          event_name: 'api_call',
          customer: 'cus_01HXYZ',
          idempotency_key: crypto.randomUUID(),
          timestamp: new Date().toISOString(),
        }),
      });
      ```
    </CodeGroup>

    <Tip>
      Always include a unique `idempotency_key` on every event. If your application retries an event after a network failure, Borga uses the key to detect and discard the duplicate — preventing the customer from being billed twice for the same usage. A UUID or a hash of your internal request ID works well.
    </Tip>

    Use the batch endpoint (`POST /v1/events/batch`) when you need to ingest high volumes of events. Batching reduces API call overhead and is the recommended approach for ingesting events asynchronously from a queue.
  </Step>

  <Step title="Check quantities">
    At any point during the billing period, query the aggregated quantity for a customer to see how much usage they have accumulated.

    <CodeGroup>
      ```bash curl theme={null}
      curl --request GET \
        --url "https://api.borga.is/v1/meters/mtr_01HXYZ/quantities?customer=cus_01HXYZ&period_start=2026-04-01T00:00:00Z&period_end=2026-04-30T23:59:59Z" \
        --header "Authorization: Bearer sk_test_YOUR_SECRET_KEY" \
        --header "X-Merchant-Id: mer_YOUR_MERCHANT_ID"
      ```

      ```javascript Node.js theme={null}
      const params = new URLSearchParams({
        customer: 'cus_01HXYZ',
        period_start: '2026-04-01T00:00:00Z',
        period_end: '2026-04-30T23:59:59Z',
      });

      const result = await fetch(
        `https://api.borga.is/v1/meters/mtr_01HXYZ/quantities?${params}`,
        {
          headers: {
            'Authorization': 'Bearer sk_test_YOUR_SECRET_KEY',
            'X-Merchant-Id': 'mer_YOUR_MERCHANT_ID',
          },
        }
      ).then(r => r.json());

      console.log(result.quantity); // e.g. 4821
      ```
    </CodeGroup>

    The response includes the aggregated `quantity` for the given customer and time range. Use this to build usage dashboards or to preview what a customer will be charged at the end of the period.
  </Step>
</Steps>

## Displaying usage to customers

You can expose usage data directly to your customers by querying `GET /v1/meters/{id}/quantities` from your server and surfacing the result in your application UI. This helps customers understand their consumption before the invoice arrives and reduces billing-related support requests.

## Next steps

* [Subscriptions](/guides/subscriptions) — set up fixed recurring plans alongside or instead of metered pricing
* [Webhooks](/guides/webhooks) — receive `invoice.paid` events when usage invoices are collected
* [Accounting link](/guides/accounting-link) — sync invoices generated from metered usage to your accounting software
