---
title: "Read the figures"
method: GET
path: "/v1/projects/{project}/retention/stats"
tags: ["Revenue Recovery"]
---

# Read the figures

`GET /v1/projects/{project}/retention/stats`

## Request

- Base URL: `https://api.subscriby.net`
- URL: `https://api.subscriby.net/v1/projects/{project}/retention/stats`
- Auth: HTTP bearer

```bash
curl "https://api.subscriby.net/v1/projects/7f3d1c92-8b45-4e6a-9d21-5c8e0a4b6f13/retention/stats?period=30d" \
  -H "Authorization: Bearer sbt_..."
```

What the programme did over the window, as the dashboard card reads it: the failed renewals, the reminders sent, the pauses, the recoveries with what they brought back restated in US dollars, the restorations, the win-back steps sent and the members won back (the churn trigger alone), the cancellation screens with the offers shown and taken, and why members cancelled with each reason's share; then the nurture block: the leads who arrived, entered a lead sequence and bought while it ran, the checkouts started, stalled and recovered with what the recovered ones were worth in US dollars, and the trials without a card a sequence took and the ones whose member paid while it ran. The two "right now" figures, the members in grace and the members paused, ignore the window.

## Path parameters

- `project` string, uuid, required

## Query parameters

- `period` '7d' | '14d' | '30d' | '60d' | '90d' | '1y' | 'mtd' | 'qtd' | 'ytd' | 'all': The preset reporting windows a dashboard, export or analytics call can ask for. Presets are anchored on "now" so the same preset means the same thing on every surface; an explicit from/to pair on `ReportWindow` overrides the preset. Unknown input falls back to thirty days rather than failing, because a period from a query string or an MCP argument is never worth a 422.

## Response `200`

The figures the dashboard card reads, with the reasons members gave.

- object
  - `data` RetentionStatsResource, required: What Revenue Recovery did on a project over the window: the failed renewals and what the recovered ones brought back in US dollars, why members cancelled, the members in grace or paused right now, and the leads, checkouts and trials the sequences nurtured with what the recovered checkouts were worth.
    - `period` string, required: The preset the figures cover.
    - `range` object, required: The window's bounds.
      - `from` string, required: The first day, ISO 8601 date.
      - `to` string, required: The last day, ISO 8601 date.
    - `failed_renewals` integer, required: Renewals the gateway could not collect in the window.
    - `reminders_sent` integer, required: The day-three reminders and final warnings sent.
    - `access_paused` integer, required: Grace windows that closed unpaid, pausing access.
    - `payments_recovered` integer, required: Failed renewals settled later in the window.
    - `recovered_usd` string, required: What those settled renewals were worth, restated in US dollars, a decimal string with two places.
    - `access_restored` integer, required: Paused members let back in.
    - `in_grace_now` integer, required: Members who keep access on an unpaid renewal right now; not windowed.
    - `paused_now` integer, required: Members paused for non-payment right now, waiting for a charge to settle; not windowed.
    - `winback_sent` integer, required: Win-back sequence steps delivered on the churn trigger.
    - `won_back` integer, required: Members a win-back sequence brought back: churn passages that ended because the member paid again.
    - `cancellations` integer, required: Cancellation screens recorded.
    - `offers_shown` integer, required: Cancellation screens that made an offer.
    - `offers_taken` integer, required: Offers the member took.
    - `reasons` CancellationReasonShareResource[], required: Why members cancelled, the most given reason first.
      - `reason` string, required: The reason's value, such as `too_expensive`.
      - `label` string, required: The reason in the creator's language.
      - `count` integer, required: How many members gave it.
      - `share_percent` number, required: Its share of every reason given, in percent with one decimal.
    - `leads_arrived` integer, required: Members who arrived as leads in the window, through any door.
    - `leads_nurtured` integer, required: Leads a lead sequence took.
    - `leads_converted` integer, required: Leads who bought while a lead sequence ran: lead passages that ended because a charge settled.
    - `checkouts_started` integer, required: Checkouts members started on the portal or in a bot.
    - `checkouts_stalled` integer, required: Checkouts the hourly sweep counted as stalled, once the project's window passed with no settled charge.
    - `checkouts_recovered` integer, required: Stalled checkouts a charge finished after all.
    - `checkouts_recovered_usd` string, required: What the charges that finished the stalled checkouts were worth, restated in US dollars, a decimal string with two places.
    - `trials_nurtured` integer, required: Trials without a card a trial-ending sequence took.
    - `trials_saved` integer, required: Trials whose member paid while the sequence ran: trial passages that ended because a charge settled.

## Other responses

- `401`: `AUTHENTICATION_REQUIRED`: the request carries no bearer token, or one that is revoked, malformed, or minted for another environment (an `sbt_test_` token on production).
- `403`: `TOKEN_MISSING_ABILITY`: the token is valid but does not carry the ability this endpoint requires; `error.context.required_ability` names the one to grant. An endpoint that also checks who owns a row or which tier the account is on answers `FORBIDDEN`, `TEAM_TIER_REQUIRED` or `CONNECTOR_TIER_REQUIRED` with the same status, and says so in its own description.
- `404`: `RESOURCE_NOT_FOUND`: an id in the path names nothing the token can see. `TENANT_MISMATCH`: the project sits outside the token's `scope:project:` allow-list, or the token carries no team scope. Both answer 404 rather than 403 so that existence outside the token's scope cannot be inferred.
- `422`: `VALIDATION_FAILED`: the payload broke a rule, and `error.fields` maps each offending key to its messages. A refusal from the domain, such as a plan that cannot go on sale or a member who cannot be removed, uses the same code with `error.message` saying why and no `fields`.
- `429`: `RATE_LIMITED`: the token has spent its 300 requests a minute or 10,000 an hour; `Retry-After` says when the next one is accepted.

---

[API](https://skmtc.dev/memberpass/apis/subscriby-rest-api.md) · [All operations](https://skmtc.dev/memberpass/apis/subscriby-rest-api/llms.txt) · [OpenAPI document](https://skmtc.dev/memberpass/apis/subscriby-rest-api/revisions/59c4df91d764?raw)
