# Chargebacks

## Overview

Chargebacks help you understand how your WarpStream bill maps back to the teams, applications, and datasets that generated usage.

A standard invoice shows charges at the account, workspace, and cluster level. That is usually enough for accounting, but it can be too coarse for internal cost allocation. A single Kafka cluster may contain hundreds or thousands of topics owned by different teams. A Schema Registry cluster may contain subjects for many applications. A Tableflow cluster may contain tables with very different storage and write patterns.

Chargebacks break supported cluster-level invoice line items down by entity:

| Cluster type             | Chargeback entity |
| ------------------------ | ----------------- |
| Kafka clusters           | Topic             |
| Schema Registry clusters | Subject           |
| Tableflow clusters       | Table             |

The result is a showback-style view of billing data. You can use it to answer questions like:

* Which topics contributed the most to this month's Kafka bill?&#x20;
* Which tables are responsible for most Tableflow storage charges
* How should a shared cluster's costs be allocated across teams?
* Which small entities can be ignored when building an internal chargeback report?

Chargebacks are derived from invoice data combined with per-entity usage rollups. The invoice remains the source of truth for billing; chargebacks provide a best-effort allocation of covered cluster-level charges across the entities inside each cluster.

## How Chargebacks Work

Chargeback calculations start with the monthly invoice for the requested billing period. For each cluster on the invoice, WarpStream loads a per-entity usage rollup for the same period and uses that rollup to apportion supported invoice line items across topics, subjects, or tables.

The chargebacks response preserves the same high-level hierarchy as the invoice:

```text wrap
tenant
    workspace
        cluster
            entity
                line item chargebacks
```

Each entity receives one or more line-item-level chargebacks. Each line item chargeback includes the name, unit price, apportioned quantity, and apportioned total.

#### Covered Line Items

Chargebacks currently apportion the following line item types:

| Line item                  | Allocation method                                                                                                                                 |
| -------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- |
| `Cluster minutes`          | Allocated by how long each entity existed during the billing period.                                                                              |
| `Uncompressed GiB written` | Allocated by each entity's share of uncompressed write bytes.                                                                                     |
| `GiB minutes`              | Allocated by storage attribution. Kafka topics use write bytes weighted by retention. Tableflow tables use measured time-integrated stored bytes. |
| `Network ingress`          | (Serverless clusters only) Allocated by each entity's share of write bytes.                                                                       |
| `Network egress`           | (Serverless clusters only) Allocated by each entity's share of write bytes.                                                                       |

The response includes a `covers_products` field so callers can see which invoice line items are included in the chargeback calculation.

#### Storage Attribution

Storage charges need special handling because different cluster types have different methods for measuring storage volume.

For Kafka topics, WarpStream uses:

`write bytes × retention`

This is reported in the response as:

`"storage_attribution_method": "write_bytes_times_retention"`

For Tableflow tables, WarpStream uses measured stored bytes over time. This is reported as:

`"storage_attribution_method": "bytes_seconds_stored"`

The `storage_attribution_method` field only appears on clusters where storage charges were apportioned.

#### Excluded Clusters

Sometimes a cluster appears on the invoice but cannot be broken down into per-entity chargebacks. In that case, the cluster is still returned, but with an `excluded_reason` instead of an `entities` list.

Possible exclusion reasons are:

| Reason              | Meaning                                                                                                                                                                                   |
| ------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `no_rollup`         | We could not find a usable per-entity rollup for the requested period. This can happen for a new cluster, a new billing period, or a cluster whose rollup data has not been produced yet. |
| `cluster_not_found` | The invoice references a cluster that no longer exists in the control plane.                                                                                                              |

When a cluster is excluded, its charges are **not** apportioned across entities in the chargebacks response.

### Using Chargebacks

There are two chargebacks endpoints:

| Endpoint                                       | Use when                                                        |
| ---------------------------------------------- | --------------------------------------------------------------- |
| `POST /api/v1/billing/chargebacks/get_pending` | You want chargebacks for the current in-progress billing month. |
| `POST /api/v1/billing/chargebacks/get_past`    | You want chargebacks for a specific billing month.              |

Use pending chargebacks for ongoing monitoring and internal dashboards. Use past chargebacks when producing a final monthly report after the invoice period has closed. Note that because WarpStream uses a tiered pricing model for writes and storage, pending Chargebacks may show unexpectedly lopsided values early in a monthly billing period.

#### Get Pending Chargebacks

```bash wrap {1,2,3,4,5,6,7,8}
curl https://api.warpstream.com/api/v1/billing/chargebacks/get_pending \
-H 'warpstream-api-key: XXXXXXXXXX' \
-H 'Content-Type: application/json' \
-d '{
    "entity_kind": "topic",
    "max_entities_per_cluster": 500,
    "min_total": 1
    }'
```

`get_pending` does not require a date. It always returns the current billing month.

#### Get Past Chargebacks

```bash wrap {1,2,3,4,5,6,7,8,9}
curl https://api.warpstream.com/api/v1/billing/chargebacks/get_past \
-H 'warpstream-api-key: XXXXXXXXXX' \
-H 'Content-Type: application/json' \
-d '{
    "date_start": "2026-05-01",
    "entity_kind": "topic",
    "max_entities_per_cluster": 500,
    "min_total": 1
    }'
```

For `get_past`, pass any date inside the billing month you want to retrieve. For example, `2026-05-01`, `2026-05-15`, and `2026-05-31` all select the May 2026 billing period.

### Filters

Both endpoints support filters that make the response easier to work with for cases with many clusters, many topics, etc.

| Field                      | Required                                            | Description                                                                                                                                   |
| -------------------------- | --------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------- |
| `date_start`               | Required for `get_past`; not used for `get_pending` | Selects the billing month to retrieve. Pass any date in the desired month, formatted as `YYYY-MM-DD`.                                         |
| `entity_kind`              | No                                                  | Filters the response by entity type. Allowed values are `topic`, `subject`, and `table`. If omitted, all supported entity kinds are returned. |
| `virtual_cluster_id`       | No                                                  | Filters the response to a single virtual cluster. This is useful when drilling into a large cluster or requesting a higher entity limit.      |
| `max_entities_per_cluster` | No                                                  | Limits the number of entities returned per cluster. Defaults to `500`. The maximum allowed value is `5000`.                                   |
| `min_total`                | No                                                  | Omits entities whose total chargeback amount is less than this USD value. Use this to remove very small rows from reports.                    |

:::note
For cases with many clusters or many topics, start with `max_entities_per_cluster` set to the default and use `min_total` to remove low-value rows. If you need a complete breakdown for one cluster, set `virtual_cluster_id` and increase `max_entities_per_cluster`.
:::

### Example Response

```json wrap
{
  "date_from": "2026-05-01",
  "date_to": "2026-06-01",
  "covers_products": [
    "Cluster minutes",
    "Uncompressed GiB written",
    "GiB minutes",
    "Network ingress",
    "Network egress"
  ],
  "tenants": [
    {
      "tenant_id": "ti_xxx",
      "workspaces": [
        {
          "workspace_id": "wi_xxx",
          "workspace_name": "Production",
          "clusters": [
            {
              "virtual_cluster_id": "vci_xxx",
              "cluster_name": "prod-kafka",
              "cluster_regions": [
                "us-east-1"
              ],
              "cluster_type": "byoc",
              "entity_kind": "topic",
              "storage_attribution_method": "write_bytes_times_retention",
              "entities": [
                {
                  "name": "orders",
                  "chargebacks": [
                    {
                      "product": "Cluster minutes",
                      "unit_price": 0.23148,
                      "quantity": 2340,
                      "total": 5.42
                    },
                    {
                      "product": "Uncompressed GiB written",
                      "unit_price": 1,
                      "quantity": 110.25601800903678,
                      "total": 1.1
                    },
                    {
                      "product": "GiB minutes",
                      "unit_price": 0.00000463,
                      "quantity": 1343.727466603741,
                      "total": 0
                    }
                  ],
                  "total": 6.52
                }
              ]
            }
          ]
        }
      ]
    }
  ]
}
```

Each entity has a `total` field equal to the sum of its line item-level chargebacks. To build an internal chargeback report, group entities however you prefer, for example by naming convention, team ownership, or cluster.

### Interpreting Results

Chargebacks are designed for internal allocation and cost analysis. A few details are worth keeping in mind:

* Chargebacks only include products listed in `covers_products`.
* Small rounding differences can occur when summing apportioned charges. Chargebacks are for _analytical estimations_, and total rollups may not match your invoiced charges.
* Network ingress and egress (for Serverless clusters only) are approximated by write-byte share.
* Storage attribution depends on cluster type and is reported per cluster.
* If `truncated` is `true` on a cluster, more entities matched the request than were returned. Increase `max_entities_per_cluster`, filter to a single `virtual_cluster_id`, or raise `min_total`.
* If `excluded_reason` is present, that cluster's invoice charges were not apportioned into entities for the requested period.