Churned customers
A churned customer is a shop that left — it had your app and now it doesn't. GET /api/v1/apps/:app/churned-customers returns a paginated object: "list" of the shops that have churned, with when they left, the plan they were last on, and their lifetime revenue and duration. Useful for win-back campaigns and warehouse ingestion. Requires the metrics:read scope.
Churned customers come from your Shopify Partner API integration. If the integration was never configured for the app, the list is simply empty (a 200, not an error). Fields that haven't been captured for a shop (last_plan_amount, lifetime_days) come back as null rather than being dropped.
Churned customers
Returns the app's churned shops as a paginated object: "list", most recently churned first. Each row is a shop that has left, with its churn timestamp, the plan it was last on, its lifetime revenue, and how long it stayed. total_count is the count after the lookback window and has_more tells you when to fetch the next page.
Use the months window to scope how far back to look — for example, only the shops that churned in the last quarter. To export the full list into a warehouse, prefer cursor pagination.
Path parameter
- Name
app- Type
- string
- Description
The team app's ULID, e.g.
01JN4HKQX0000000000000000.
Query parameters
- Name
months- Type
- integer
- Description
Look back this many months from today;
1–12. Omit to use the default lookback window.
- Name
cursor- Type
- string
- Description
Opt-in cursor pagination; opaque, max 64 chars. See Pagination.
- Name
page- Type
- integer
- Description
Page number, min
1.
- Name
per_page- Type
- integer
- Description
Rows per page,
1–100.
Request
curl -G https://ranksyapp.com/api/v1/apps/01JN4HKQX0000000000000000/churned-customers \
-H "Authorization: Bearer rk_live_..." \
-d months=3 \
-d per_page=25
Response
{
"object": "list",
"url": "/api/v1/apps/01JN4HKQX0000000000000000/churned-customers",
"currency": "USD",
"page": 1,
"per_page": 25,
"total_count": 47,
"has_more": true,
"metadata": {},
"data": [
{
"shop_domain": "skin-authority.myshopify.com",
"shop_id": "5271523",
"churned_at": "2026-07-08T00:00:00+00:00",
"last_plan_name": "Growth",
"last_plan_amount": 49.00,
"lifetime_revenue": 98.00,
"lifetime_days": 30
}
]
}
Response fields
The list is the shared catalog object: "list" envelope; each item in data is a churned-customer row.
List envelope (object: "list")
- Name
object- Type
- string
- Description
Always
"list".
- Name
url- Type
- string
- Description
The endpoint path for this resource.
- Name
currency- Type
- string
- Description
The currency the monetary fields are reported in, e.g.
USD.
- Name
page- Type
- integer
- Description
The current page number (1-based).
nullin cursor mode.
- Name
per_page- Type
- integer
- Description
Rows per page.
- Name
total_count- Type
- integer
- Description
Total churned shops in the lookback window.
nullin cursor mode.
- Name
has_more- Type
- boolean
- Description
Whether further pages exist.
- Name
metadata- Type
- object
- Description
Extra context about the query.
{}when there is none.
- Name
next_cursor- Type
- string
- Description
Present only in cursor mode: the cursor to pass to fetch the next page, or
nullwhen there are no more rows.
- Name
data- Type
- array
- Description
The churned-customer rows for this page.
Churned-customer row
- Name
shop_domain- Type
- string
- Description
The
*.myshopify.comdomain of the churned shop, ornullwhen unknown.
- Name
shop_id- Type
- string
- Description
The Shopify shop id, or
nullwhen unknown.
- Name
churned_at- Type
- string
- Description
ISO-8601 timestamp of when the shop churned.
- Name
last_plan_name- Type
- string
- Description
The plan the shop was on when it churned, or
nullwhen unknown.
- Name
last_plan_amount- Type
- number
- Description
That plan's amount in the plan currency, or
nullwhen unknown.
- Name
lifetime_revenue- Type
- number
- Description
Total revenue earned from the shop before it churned.
- Name
lifetime_days- Type
- integer
- Description
How many days the shop was a customer, or
nullwhen unknown.
Pagination
By default the list uses offset pagination. Use page and per_page (1–100) to walk the result set, and check has_more / total_count to know when to stop.
For exports, prefer cursor pagination — it opts in the moment you send a cursor parameter. Start with an empty cursor, read next_cursor off the response, and pass it back on the next request; repeat until next_cursor is null (equivalently, has_more is false). Cursor mode is stable under concurrent inserts and is not subject to the page-mode row cap, so it is the recommended way to export the full list into a warehouse (e.g. BigQuery). In cursor mode, page and total_count come back null.
Errors
Beyond the universal 401 / 403 / 422 / 429 (see Errors):
- The list never
404s — an app with no configured Partner API integration, or no churned shops, returns an emptydatalist (a200). - An out-of-range
months(outside1–12) surfaces as422 validation_failed.