---
url: https://docs.sunlicense.com/api/analytics.md
---
# Product analytics API

Your products' installs report device metrics every 30 minutes through the [official SDKs](/guide/sdks); you read
them back as counts of installs. Reports never affect license checks, and the SDKs never wait for them.

## POST /api/v2/analytics/report

What the SDKs send. You only need this if you write your own client.

```http
POST /api/v2/analytics/report
Authorization: Bearer slk_...          # licensed installs: the API key you validate with
Content-Type: application/json

{
  "schema": 1,
  "licenseKey": "K7QX-M2PD-9HWT-R4CN", "fingerprint": "machine-a",
  "sdk": { "name": "java", "version": "1.4.0" },
  "metrics": {
    "productVersion": "2.3.1", "os": "Linux", "osVersion": "5.15.0", "arch": "aarch64", "cores": 4, "locale": "en_US",
    "runtime": "java", "runtimeVersion": "21.0.4", "platform": "paper", "platformVersion": "1.21.1"
  }
}
```

A **free (unlicensed) product** sends no `Authorization` header, its public **product analytics id** (on the product's
Analytics tab) and a random `installId` it creates once and keeps:

```json
{ "productAnalyticsId": "pa_7Hk2mQ9xZr4LbN8vT1wYcD", "installId": "0b8e5c9a-3f1d-4e7a-9c55-2d8f1e6a7b40", "metrics": { "os": "Linux" } }
```

Send raw values: SunLicense groups them (`5.15.0-1051-oracle` → `Linux 5.15`, `1.8.0_392` → `java 8`, `amd64` →
`x86_64`). Every metric is optional. Reports are at most 16 KB; strings longer than 64 characters are cut.

| Answer | When | What the client does |
|---|---|---|
| `204` | Accepted | Reports again in 30 minutes |
| `400 invalid_report` | Not a report (wrong types, no identity) | Reports again in 30 minutes |
| `401` | Unknown API key | Same |
| `403 analytics_disabled` | You switched analytics off for the product | Waits `Retry-After` (a day) |
| `404 unknown_license` / `unknown_product` | No such license key in your organization / product analytics id | Waits `Retry-After` (30 minutes for a key, 6 hours for an id) |
| `413 report_too_large` | Over 16 KB | Reports again in 30 minutes |
| `429 rate_limited` | More than one report per install per 5 minutes, or too many from one address | Waits `Retry-After` |
| `503 busy` | We're catching up | Waits `Retry-After` |

Each install's first report after it starts comes after a random 1 to 30 minutes, then every 30 minutes. End users
switch reporting off with the product's `analytics: false` setting or `SUNLICENSE_ANALYTICS=off`.

## Reading analytics

With a dashboard session or an access token with `analytics.read`. Ranges are UTC days; asking for more history than
your plan keeps returns what there is, with `"clipped": true`.

| Route | Returns |
|---|---|
| `GET /api/v1/analytics/overview` | Active installs (reported in the last hour) per product, and your plan's analytics limits |
| `GET /api/v1/analytics/products/{id}/summary` | Active, licensed and unlicensed installs; installs today and in 30 days; first and last report |
| `GET /api/v1/analytics/products/{id}/series?metric=os&days=30` | Installs per day (or `granularity=hour`, up to 7 days) for the top values (`top`, default 6) and `other` |
| `GET /api/v1/analytics/products/{id}/breakdown?metric=os` | Installs per value in the last full hour, or per day on average with `days=N`; `parent=Linux` with `metric=osVersion` drills down |
| `GET /api/v1/analytics/catalog` | The metrics: `installs`, `licensed`, `productVersion`, `os`, `osVersion`, `arch`, `cores`, `locale`, `runtime`, `runtimeVersion`, `platform`, `platformVersion`, `country`, `sdk` |
| `GET /api/v1/analytics/licenses/{id}/installs` | A license's installs with their device details (needs `licenses.read`) |
| `GET /api/v1/analytics/customers/{id}/installs` | The same for all of a customer's licenses |
| `GET /api/v1/portal/licenses/{id}/installs` | The customer portal's version, with a portal session: the signed-in customer's own license only, device fields and a shortened fingerprint |
| `GET /api/v1/analytics/products/{id}/settings` | `enabled` and the product analytics id (`PATCH` with `{"enabled": false}`, `POST …/rotate-id` with `products.write`) |
| `GET /api/v1/analytics/usage` | Reports this month, installs tracked in 30 days and your plan's installs limit |

```json
// GET /api/v1/analytics/products/{id}/breakdown?metric=os&days=7
{ "metric": "os", "mode": "average", "totalInstalls": 1200, "clipped": false,
  "values": [ { "value": "Linux", "installs": 812, "share": 0.677, "total": 812 } ], "otherInstalls": 0 }
```

Plans: Free keeps 30 days of history and tracks 1,000 installs, Lite 90 days and 10,000 installs, Standard a year and
unlimited installs. Past the installs limit, new installs aren't counted; nothing stops working. See [Plans](/guide/plans).
