Usage-based billing

Meter events from your system, include free units, and price usage by tier on each subscription invoice.

Usage-based (metered) billing charges customers for what they consume. You define a Meter that turns raw events into a quantity, attach a metered Price to it, and send usage events as things happen. At the end of each billing period Borga aggregates the events, subtracts included units, prices the rest against the tiers and adds a line to the subscription invoice.

  1. Define a meter

    A meter names the event it counts and how to aggregate it.

    Node.jsts
    // Count API requests
    const requests = await borga.meters.create({
      name: "API requests",
      event_name: "api_request",
      aggregate_type: "count",
      unit_label: "requests",
    });
    
    // Sum tokens, priced per million
    const tokens = await borga.meters.create({
      name: "LLM tokens",
      event_name: "llm_completion",
      aggregate_type: "sum",
      aggregate_property: "metadata.total_tokens",
      unit_label: "tokens",
      unit_multiplier: 1_000_000,
    });
    aggregate_typeResult for the period
    countNumber of matching events
    sumSum of aggregate_property across events
    max, min, avgHighest, lowest or mean value of aggregate_property
    uniqueNumber of distinct values of aggregate_property, for example active users

    aggregate_property is a path into the event's metadata such as metadata.total_tokens. filter restricts which events count, for example { "metadata.tier": "pro" }. unit_multiplier divides the aggregate before pricing so you can store raw tokens and price per million.

  2. Create a metered price

    A metered price references the meter and prices its quantity, optionally with free included_units and graduated or volume tiers.

    Node.jsts
    const price = await borga.prices.create({
      product: "prod_4Gh6Ij8Kl0Mn2Op4Qr6St8Uv",
      type: "recurring",
      currency: "ISK",
      recurring: { interval: "month", usage_type: "metered" },
      meter: "mtr_1Zx3Cv5Bn7Mq9We1Rt3Yu5Io",
      included_units: 10_000, // first 10k requests are free
      tiers_mode: "graduated",
      tiers: [
        { up_to: 100_000, unit_amount: 2 }, // 2 kr. per request
        { up_to: null, unit_amount: 1 }, // 1 kr. above 100k
      ],
    });

    graduated tiers price each band separately (the first 100k at one rate, the rest at another). volume tiers price the whole quantity at the rate of the band it lands in. Combine a metered price with a fixed price in one subscription for base-fee-plus-overage plans.

  3. Send usage events

    Report events as they happen, singly or in batches of up to 1000. Identify the customer by Borga id or by your own external_customer_id, which matches Customer.external_id.

    Node.jsts
    // Single event
    await borga.usageEvents.create({
      event_name: "api_request",
      customer: "cus_8Jk2Lm4Np6Qr8St0Uv2Wx4Yz",
      idempotency_key: "req_01J9X2K7M3", // dedupes retries
      metadata: { endpoint: "/v1/chat" },
    });
    
    // Batches of up to 1000, identified by your own customer ids
    await borga.usageEvents.createBatch([
      {
        event_name: "llm_completion",
        external_customer_id: "user_12345",
        timestamp: "2026-09-07T12:00:00Z",
        idempotency_key: "cmpl_a1",
        metadata: { total_tokens: 640 },
      },
      {
        event_name: "llm_completion",
        external_customer_id: "user_12345",
        timestamp: "2026-09-07T12:00:02Z",
        idempotency_key: "cmpl_a2",
        metadata: { total_tokens: 1024 },
      },
    ]);

    Always set idempotency_key to something derived from the event, such as your request id. Repeated keys are dropped and counted in the response's duplicates, so retrying a batch never double-counts. timestamp defaults to now and may not be more than 60 seconds in the future.

  4. Show customers their usage

    Read a customer's live balance for the current period to build usage dashboards or warnings.

    Node.jsts
    const balance = await borga.meters.balance(
      "cus_8Jk2Lm4Np6Qr8St0Uv2Wx4Yz",
      "mtr_1Zx3Cv5Bn7Mq9We1Rt3Yu5Io",
    );
    
    console.log(balance.consumed_units, "of", balance.included_units);
    console.log("overage:", balance.overage_units);

    GET /v1/customer-state/:customerId returns every active subscription and meter balance for a customer in one call, and GET /v1/meters/:id/quantities returns the aggregate for an arbitrary period.

Included units and rollover

included_units on a price (or on a subscription item, which overrides the price) are free each period. Set credit_rollover: true on the item to carry unused units into the next period, for data or message bundles.

When a customer has used 80% or more of their included units, Borga emits usage.high_watermark once per period so you can warn them before overage starts.

At period end

The billing cycle reads the meter for the period, applies included and rolled-over units, prices the remainder and adds a metered line to the invoice with the unit label. Usage in the new period starts from zero (plus any rollover). Events that arrive after the period closed count towards the period they are timestamped in only if that period is still open, so send events promptly.

Reference