# Usage Reporting (/licensing/usage-reporting)



An **online** server (one licensed with a key) sends a small usage report every
**15 minutes** with ±10% jitter, plus one at boot and one when it drains. The report is
what usage-based plans are billed on, and what lets KubeMQ tell an active installation
from a dead one. An **offline** server (licensed with a file) sends nothing — ever.

There is no switch to turn reporting off on an online license. If your network cannot
allow it, the answer is a [pre-activated file](/licensing/license-key#pre-activated-file-for-egress-restricted-networks).

## The report [#the-report]

`POST https://license.kubemq.io/v1/usage/report`, authenticated with the current
lease as a bearer token. The body:

```json
{
  "license_id":  "6f1c2a3e-9b4d-4c5e-8a7f-1d2e3f4a5b6c",
  "fingerprint": "3f2a9c1e-7b6d-4e5f-9a8b-0c1d2e3f4a5b",
  "boot_id":     "0b3d5f7a-9c1e-4a2b-8d6f-1e3c5a7b9d0f",
  "source": {
    "type": "kubernetes",
    "host_id": "node-a",
    "cluster_id": "3f2a9c1e-7b6d-4e5f-9a8b-0c1d2e3f4a5b",
    "namespace": "kubemq",
    "pod": "kubemq-next-0",
    "version": "next-1.0.0",
    "image_digest": "sha256:9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08"
  },
  "cursor": { "from": "2026-09-19T12:00:00Z", "to": "2026-09-19T12:15:00Z" },
  "totals": { "messages": 123456789, "volume_bytes": 987654321 },
  "activity": null
}
```

The `host_id` and `image_digest` values above are illustrative. On an operator-managed
Kubernetes cluster `host_id` is the node name (operator 1.1.1 and later inject `NODE_NAME`
on every pod) and `image_digest` is filled only when the cluster's image reference is
pinned by digest (`…@sha256:…`); with a tag-only reference it stays empty.

Field by field:

| Field                            | What it is                                                                                                                                                                                                                                                                                                                                                                                                           | Why it is sent                                                                               |
| -------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------- |
| `license_id`                     | Your license's public id (a uuid)                                                                                                                                                                                                                                                                                                                                                                                    | Attributes the report to a license. It must match the lease used as bearer                   |
| `fingerprint`                    | This installation's identity — the `kube-system` namespace UID on Kubernetes, a persisted random id elsewhere                                                                                                                                                                                                                                                                                                        | Counts installations against your cap, and must match the lease                              |
| `boot_id`                        | A random uuid generated when the process started; constant until it exits                                                                                                                                                                                                                                                                                                                                            | Lets the service tell a restart from a continuing run, so counters can be cumulative (below) |
| `source.type`                    | `standalone`, `docker` or `kubernetes`                                                                                                                                                                                                                                                                                                                                                                               | Support context; how the server detected its environment                                     |
| `source.host_id`                 | OS or container hostname; on Kubernetes the node name from `NODE_NAME`, which operator 1.1.1 and later inject on every pod                                                                                                                                                                                                                                                                                           | Support context — which machine                                                              |
| `source.cluster_id`              | The `kube-system` UID (Kubernetes only; empty if unreadable)                                                                                                                                                                                                                                                                                                                                                         | Groups pods of one cluster                                                                   |
| `source.namespace`, `source.pod` | Pod namespace and name (Kubernetes only): `POD_NAMESPACE` (else the service-account namespace file, else `default`) and `POD_NAME` (else the hostname)                                                                                                                                                                                                                                                               | Support context — which pod                                                                  |
| `source.version`                 | The server version                                                                                                                                                                                                                                                                                                                                                                                                   | Which release is running                                                                     |
| `source.image_digest`            | The digest of the running image — sent only when the deployment injects it as `KUBEMQ_IMAGE_DIGEST` (a well-formed `sha256:` digest). The server cannot read its own digest; without the variable the field is empty (omitted on a standalone binary). On Kubernetes, operator 1.1.1 and later inject it when the cluster's image reference is pinned by digest (`…@sha256:…`); a tag-only reference leaves it empty | Confirms the server is the signed official image, once injected                              |
| `cursor.from`, `cursor.to`       | The window this report covers (UTC)                                                                                                                                                                                                                                                                                                                                                                                  | Ordering and de-duplication; windows never overlap and always advance                        |
| `totals.messages`                | Messages handled **since this `boot_id` started**                                                                                                                                                                                                                                                                                                                                                                    | The billable count                                                                           |
| `totals.volume_bytes`            | Bytes handled since this `boot_id` started                                                                                                                                                                                                                                                                                                                                                                           | The billable volume                                                                          |
| `activity`                       | `"start"` on the boot report, `"stop"` on the drain report, `null` otherwise                                                                                                                                                                                                                                                                                                                                         | Marks the edges of a run                                                                     |

### Cumulative counters [#cumulative-counters]

`totals` are **cumulative since boot**, never deltas. The service subtracts the previous
accepted report for the same `(license, fingerprint, boot_id)` to get the 15-minute
increment; a new `boot_id` starts from zero. This is why nothing is lost if a report is
missed — the next one carries the full count — and why the server cannot shrink a bill
by choosing windows. A counter that goes backwards without a new `boot_id` is clamped
to zero and flagged, never silently accepted.

### Delivery [#delivery]

* **At-least-once.** The server keeps a cursor in `metrics.db` under the store path and
  advances it only after the service acknowledges (`200`). A report sent twice — for
  example after a crash between the acknowledgement and the cursor write — is answered
  `duplicate` and not counted again. A `401` (the lease the report carried is no longer
  accepted) keeps the row for the next attempt with the next lease; a `413` or `422` is
  terminal — the row is dropped and the cursor advances, so one bad report cannot block
  the ones after it.
* **Rate limited.** The service allows a burst of 5 and one report per minute per
  license; the server's 15-minute cadence sits far inside that. A `Retry-After` on a
  `429` (or any other unexpected answer) puts the reporter on hold: ticks that fall
  inside the hold are skipped.
* **Never blocking.** Reporting runs in the background and never affects message
  traffic. There is no retry backoff: a failed report is simply attempted again at the
  next 15-minute tick, with the full cumulative counters.

## What is never sent [#what-is-never-sent]

The report contains **no payload data and no identifying traffic data**:

* No message content, headers, metadata or tags.
* No channel names, queue names, client ids or subscription details.
* No client IP addresses, and no server IP addresses — the service sees only the
  connection it receives.
* No configuration values, credentials, or the license key.
* No per-channel or per-client breakdown — only two totals.

Everything it does contain is listed in the table above. The whole body is limited to
16 KiB and `source` to 2 KiB.

## What if reports cannot get through? [#what-if-reports-cannot-get-through]

Nothing stops. An online license whose refreshes succeed but whose usage reports do not
arrive for **72 hours** is marked *silent*: the server logs a warning every minute
([#silent-usage](/licensing/troubleshooting#silent-usage)), leases are shortened to 24
hours, and KubeMQ sends you an email. After **30 days** of continuous silence the
service stops issuing new leases, and the server runs out its lease and grace. That is
the only automatic consequence, and it is suspended fleet-wide whenever the silence
looks like an outage on KubeMQ's side rather than yours.

## Retention [#retention]

| Data                                      | Kept for                                  |
| ----------------------------------------- | ----------------------------------------- |
| Raw 15-minute reports                     | **90 days**, then pruned                  |
| Daily totals per license and installation | Indefinitely — this is the billing record |

Raw rows are pruned only after every day they cover has been rolled up into the daily
totals.

## Verify what your server is sending [#verify-what-your-server-is-sending]

`GET /api/v1/license` reports the state of the reporter:

```bash title="Terminal"
curl -s http://localhost:8080/api/v1/license | jq .usage
```

```json
{
  "enabled": true,
  "last_report_at": "2026-09-19T12:10:00Z",
  "last_outcome": "accepted",
  "last_status": 200,
  "cursor_to": "2026-09-19T12:10:00Z",
  "boot_id": "0b3d5f7a-9c1e-4a2b-8d6f-1e3c5a7b9d0f"
}
```

| Field            | Healthy value                                                                                                                                                                                                |
| ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `enabled`        | `true` for an online license; `false` for a file                                                                                                                                                             |
| `last_report_at` | Within the last 15–17 minutes                                                                                                                                                                                |
| `last_outcome`   | `accepted` (or `duplicate` right after a restart). `rejected` means the service acknowledged but did not count the row; `error` means it never got through; `never` means no report has been sent since boot |
| `last_status`    | `200`                                                                                                                                                                                                        |
| `cursor_to`      | Equal to the end of the last accepted window                                                                                                                                                                 |

The `silent` flag at the top level of the same response tells you whether the service
agrees that it is receiving your reports. To see the traffic itself, watch outbound
HTTPS to `license.kubemq.io` from the container; the server sends one request per
report and nothing else on that connection.
