Subscriber statsGET /api/v1/subscribers/stats

Subscription health for an app: active subscribers total and per plan, movements inside the window (new subscriptions, cancellations, reactivations, plan changes with from/to pairs), and the churn rate.

Request

curl "https://www.vevee.org/api/v1/subscribers/stats?window=30" \
  -H "Authorization: Bearer sk_live_YOUR_KEY"

Parameters

NameTypeDescription
windowoptionalnumberWindow size in days, 1 to 365. Defaults to 30.

Response

data matches the SubscriberStats shape used internally. byPlan only counts subscriptions active as of now (not ended); the window block counts transitions that happened inside the requested window, read from the append-only subscription_events log.

{
  "ok": true,
  "data": {
    "windowDays": 30,
    "activeSubscribers": 842,
    "byPlan": [
      { "planId": "plan_free", "planName": "Free", "count": 601 },
      { "planId": "plan_pro", "planName": "Pro", "count": 219 },
      { "planId": "plan_team", "planName": "Team", "count": 22 }
    ],
    "window": {
      "newSubscriptions": 74,
      "canceled": 31,
      "reactivated": 6,
      "planChanges": 18,
      "changes": [
        {
          "fromPlanId": "plan_free",
          "fromPlanName": "Free",
          "toPlanId": "plan_pro",
          "toPlanName": "Pro",
          "count": 13
        },
        {
          "fromPlanId": "plan_pro",
          "fromPlanName": "Pro",
          "toPlanId": "plan_free",
          "toPlanName": "Free",
          "count": 5
        }
      ]
    },
    "churnRate": 0.0355
  }
}

How churn is computed

churnRate is canceled / (activeSubscribers + canceled), using the window's canceled count and the current-as-of-now activeSubscribers total - not a canceled-at-window-start denominator. It is 0 when the denominator is 0.

“Canceled” here means a canceled row in subscription_events, which only exists for apps using the hard-cancel pattern. Vevee supports two cancellation patterns per app, and only one shows up in this number:

  • Apps with a free plan call upsertSubscription with planId: 'free' on cancel. This logs a plan_changed row, not a canceled row - the end user keeps using the app under free limits, and this movement counts toward planChanges, not canceled.
  • Apps without a free plan call cancelSubscription, which sets ends_at and logs a canceled row. Once ends_at passes, the end user is blocked. This is the pattern churnRate is actually measuring.

If an app mixes both patterns for different users, churnRate only reflects the hard-cancel half of the picture - the free-plan downgrades are still visible in window.changes.

Errors

  • invalid_key (401) - bad or revoked API key.
  • requires_secret_key (403) - a pk_* key was used.
  • invalid_request (400) - window outside 1 to 365.
i
Related: the MCP server exposes the same data as get_subscriber_stats - see MCP server.