Skip to main content
The subscription analytics endpoints power the Admin analytics dashboard. You can fetch KPI summary cards, retrieve grouped time-series data for charts, export a flattened dataset as JSON or CSV, and trigger a manual rebuild of daily analytics snapshots for a historical range. All routes require an authenticated Medusa Admin user. Read routes share a common filter contract; the rebuild route uses a separate request body.
All routes require an authenticated Medusa Admin user. Unauthenticated requests return 401.

Metric keys

KeyUnitDescription
mrrcurrencyMonthly recurring revenue
churn_ratepercentageSubscriber churn rate
ltvcurrencyEstimated lifetime value
active_subscriptions_countcountNumber of active subscriptions
MRR and LTV resolve to null when the selected dataset lacks a single valid currency context.

Shared filter parameters

All three read routes (/kpis, /trends, /export) accept the same filter parameters.
date_from
string
ISO datetime lower bound. Must be on or before date_to. Maximum analytics window is 731 days.
date_to
string
ISO datetime upper bound.
status
string | string[]
Filter by subscription status. Supported values: active, paused, cancelled, past_due.
product_id
string | string[]
Filter by product ID. Accepts a single value or an array.
frequency
string | string[]
Filter by cadence using serialized tokens such as week:1, month:1, or year:1. Accepts a single value or an array.
group_by
string
Time bucket granularity. One of day, week, or month. Defaults to day.
timezone
string
Timezone for bucket boundaries. Only UTC is supported in MVP.

GET /admin/subscription-analytics/kpis

Returns the KPI summary payload used by the Admin analytics overview cards.

Response

kpis
object[]
required
Array of KPI summaries, one per supported metric key.
filters
object
required
Resolved filter values used for the query.
metrics_version
string
required
Analytics schema version identifier.
generated_at
string
required
Timestamp when the response was generated.
Response example
{
  "filters": {
    "date_from": "2026-04-01T00:00:00.000Z",
    "date_to": "2026-04-30T23:59:59.999Z",
    "status": ["active", "past_due"],
    "product_id": ["prod_123"],
    "frequency": [
      {
        "interval": "month",
        "value": 1
      }
    ],
    "group_by": "day"
  },
  "metrics_version": "analytics-v1",
  "generated_at": "2026-05-01T10:00:00.000Z",
  "kpis": [
    {
      "key": "mrr",
      "label": "MRR",
      "value": 2480,
      "unit": "currency",
      "currency_code": "usd",
      "precision": 2,
      "previous_value": 2310,
      "delta_value": 170,
      "delta_percentage": 7.36
    },
    {
      "key": "churn_rate",
      "label": "Churn Rate",
      "value": 3.2,
      "unit": "percentage",
      "currency_code": null,
      "precision": 2,
      "previous_value": 4.1,
      "delta_value": -0.9,
      "delta_percentage": -21.95
    },
    {
      "key": "ltv",
      "label": "LTV",
      "value": 412,
      "unit": "currency",
      "currency_code": "usd",
      "precision": 2,
      "previous_value": 398,
      "delta_value": 14,
      "delta_percentage": 3.52
    },
    {
      "key": "active_subscriptions_count",
      "label": "Active Subscriptions",
      "value": 182,
      "unit": "count",
      "currency_code": null,
      "precision": 0,
      "previous_value": 176,
      "delta_value": 6,
      "delta_percentage": 3.41
    }
  ]
}

Errors

CodeErrorMeaning
400invalid_dataInvalid filter shape, unsupported grouping value, or invalid frequency token

GET /admin/subscription-analytics/trends

Returns grouped time-series data for Admin analytics charts. Each series covers one metric key and contains one point per time bucket within the requested range.

Response

series
object[]
required
Array of trend series, one per metric key.
filters
object
required
Resolved filter values.
metrics_version
string
required
Analytics schema version identifier.
generated_at
string
required
Timestamp when the response was generated.
Response example
{
  "filters": {
    "date_from": "2026-04-01T00:00:00.000Z",
    "date_to": "2026-04-30T23:59:59.999Z",
    "status": ["active"],
    "product_id": [],
    "frequency": [],
    "group_by": "week"
  },
  "metrics_version": "analytics-v1",
  "generated_at": "2026-05-01T10:00:00.000Z",
  "series": [
    {
      "metric": "mrr",
      "label": "MRR",
      "unit": "currency",
      "currency_code": "usd",
      "precision": 2,
      "points": [
        {
          "bucket_start": "2026-03-30T00:00:00.000Z",
          "bucket_end": "2026-04-05T23:59:59.999Z",
          "value": 2280
        },
        {
          "bucket_start": "2026-04-06T00:00:00.000Z",
          "bucket_end": "2026-04-12T23:59:59.999Z",
          "value": 2330
        }
      ]
    },
    {
      "metric": "active_subscriptions_count",
      "label": "Active Subscriptions",
      "unit": "count",
      "currency_code": null,
      "precision": 0,
      "points": [
        {
          "bucket_start": "2026-03-30T00:00:00.000Z",
          "bucket_end": "2026-04-05T23:59:59.999Z",
          "value": 174
        },
        {
          "bucket_start": "2026-04-06T00:00:00.000Z",
          "bucket_end": "2026-04-12T23:59:59.999Z",
          "value": 178
        }
      ]
    }
  ]
}

Errors

CodeErrorMeaning
400invalid_dataInvalid filter shape, unsupported grouping value, or invalid frequency token

GET /admin/subscription-analytics/export

Returns an export payload aligned with the active analytics filters. The export is synchronous and supports json and csv formats. Rows represent one entry per time bucket.

Additional query parameter

format
string
Export format. One of json or csv. Defaults to json.

Response

format
string
required
Resolved export format.
filters
object
required
Resolved filter values.
metrics_version
string
required
Analytics schema version identifier.
generated_at
string
required
Timestamp when the export was generated.
file_name
string
required
Suggested file name for the export.
content_type
string
required
MIME type for the export format.
columns
string[]
required
Ordered column names for the export dataset.
rows
object[]
required
Flattened export rows. Each row contains one value per column. Metric cells may be null.
Response example
{
  "format": "json",
  "filters": {
    "date_from": "2026-04-01T00:00:00.000Z",
    "date_to": "2026-04-30T23:59:59.999Z",
    "status": ["active"],
    "product_id": [],
    "frequency": [],
    "group_by": "month"
  },
  "metrics_version": "analytics-v1",
  "generated_at": "2026-05-01T10:00:00.000Z",
  "file_name": "subscription-analytics-2026-05-01.json",
  "content_type": "application/json",
  "columns": [
    "bucket_start",
    "bucket_end",
    "mrr",
    "churn_rate",
    "ltv",
    "active_subscriptions_count"
  ],
  "rows": [
    {
      "bucket_start": "2026-04-01T00:00:00.000Z",
      "bucket_end": "2026-04-30T23:59:59.999Z",
      "mrr": 2480,
      "churn_rate": 3.2,
      "ltv": 412,
      "active_subscriptions_count": 182
    }
  ]
}

Errors

CodeErrorMeaning
400invalid_dataInvalid filter shape, unsupported grouping value, unsupported export format, or invalid frequency token

POST /admin/subscription-analytics/rebuild

Triggers a manual rebuild of daily analytics snapshots for a historical date range. Reuses the same shared workflow used by scheduled and incremental analytics jobs. Rebuild is day-level idempotent — rerunning the same range is safe.

Body parameters

date_from
string
required
ISO datetime start of the rebuild range.
date_to
string
required
ISO datetime end of the rebuild range. Maximum manual rebuild window is 365 days.
reason
string
Optional reason for the rebuild, recorded for audit purposes.
Request example
{
  "date_from": "2026-04-01T00:00:00.000Z",
  "date_to": "2026-04-30T23:59:59.999Z",
  "reason": "historical backfill after metrics review"
}

Response

date_from
string
required
Rebuild range start.
date_to
string
required
Rebuild range end.
processed_days
number
required
Number of calendar days processed.
processed_subscriptions
number
required
Total subscription records evaluated.
upserted_rows
number
required
Snapshot rows created or updated.
skipped_rows
number
required
Rows skipped during the rebuild.
blocked_days
string[]
required
Days that could not be processed.
failed_days
string[]
required
Days where processing failed. Partial failure does not change the HTTP status to 500.
Response example
{
  "date_from": "2026-04-01T00:00:00.000Z",
  "date_to": "2026-04-30T23:59:59.999Z",
  "processed_days": 30,
  "processed_subscriptions": 1240,
  "upserted_rows": 1240,
  "skipped_rows": 0,
  "blocked_days": [],
  "failed_days": []
}

Errors

CodeErrorMeaning
400invalid_dataInvalid request body or rebuild window exceeds 365 days