Revenue ledger
The metric endpoints give you the aggregates; the ledger gives you the rows behind them. Three paginated object: "list" endpoints: every subscription entity, every transaction in the payout ledger, and a per-acquisition-cohort LTV rollup. Each subscription and transaction row carries a stable id so you can MERGE / dedup downstream and reconcile state over time.
Like every Partner-backed endpoint, these read the app's Shopify Partner integration. If it is not connected, rejected, or still initializing, expect a 409 — see partner errors and the Errors reference. When the integration is connected but the app simply has no rows yet, you get an empty list, never an error.
Subscriptions
The app's Partner-API subscription entities — the row-level detail behind the active_subscriptions count metric. Each row has a stable id for MERGE / dedup and its own ISO currency (the Partner payout currency can differ per subscription, so there is no envelope-level currency). Test subscriptions are excluded by default — pass include_test=true to include them.
Path parameter
- Name
app- Type
- string
- Description
The team app's ULID.
Query parameters
- Name
status- Type
- string
- Description
Filter by status:
active,cancelled,declined,expired,frozen.
- Name
include_test- Type
- boolean
- Description
Include Shopify test subscriptions. Default
false.
- Name
sort- Type
- string
- Description
Sort column:
activated_at(default),cancelled_at,created_at,amount.
- Name
order- Type
- string
- Description
ascordesc(defaultdesc).
- Name
page- Type
- integer
- Description
Page to return. Default
1.
- Name
per_page- Type
- integer
- Description
Rows per page. Default
50, maximum100.
Row fields: id, shop, plan, status, amount, currency, billing_interval, activated_at, cancelled_at, created_at. metadata carries summary.total_subscriptions (the filtered total) and the echoed filters.
Request
curl -G https://ranksyapp.com/api/v1/apps/01JN4HKQX0000000000000000/subscriptions \
-H "Authorization: Bearer rk_live_..." \
-d status=active \
-d per_page=50
Response
{
"object": "list",
"url": "/api/v1/apps/01JN4HKQX0000000000000000/subscriptions",
"page": 1,
"per_page": 50,
"total_count": 640,
"has_more": true,
"metadata": {
"summary": { "total_subscriptions": 640 },
"filters": {
"status": "active",
"include_test": false,
"sort": "activated_at",
"order": "desc"
}
},
"data": [
{
"id": "48217",
"shop": "acme-store.myshopify.com",
"plan": "Pro",
"status": "active",
"amount": 19.0,
"currency": "USD",
"billing_interval": "EVERY_30_DAYS",
"activated_at": "2026-01-14T09:32:11+00:00",
"cancelled_at": null,
"created_at": "2026-01-14T09:35:00+00:00"
}
]
}
Transactions
The app's Partner-API transaction ledger — the payout rows behind the revenue metric. Each row has a stable id, its own ISO currency, and a full processed_at timestamp (not just a day), plus the gross / net / shopify_fee split per row. Optional type and date-range filters; date filters apply to processed_at. Test transactions are excluded by default.
Path parameter
- Name
app- Type
- string
- Description
The team app's ULID.
Query parameters
- Name
type- Type
- string
- Description
Filter by Partner transaction type:
APP_SUBSCRIPTION_SALE,APP_USAGE_SALE,APP_ONE_TIME_SALE,APP_SALE_ADJUSTMENT,APP_SALE_CREDIT,REFERRAL_TRANSACTION,APP_REFUND.
- Name
include_test- Type
- boolean
- Description
Include Shopify test transactions. Default
false.
- Name
start_date- Type
- string
- Description
YYYY-MM-DD. Filters onprocessed_at(inclusive).
- Name
end_date- Type
- string
- Description
YYYY-MM-DD. Must be greater than or equal tostart_date.
- Name
sort- Type
- string
- Description
Sort column:
processed_at(default),created_at,gross_amount,net_amount.
- Name
order- Type
- string
- Description
ascordesc(defaultdesc).
- Name
page- Type
- integer
- Description
Page to return. Default
1.
- Name
per_page- Type
- integer
- Description
Rows per page. Default
50, maximum100.
Row fields: id, type, type_label, shop, amount (alias of gross_amount), gross_amount, net_amount, shopify_fee, currency, processed_at, created_at. metadata carries summary.total_transactions and the echoed filters.
Request
curl -G https://ranksyapp.com/api/v1/apps/01JN4HKQX0000000000000000/transactions \
-H "Authorization: Bearer rk_live_..." \
-d type=APP_SUBSCRIPTION_SALE \
-d start_date=2026-05-01 \
-d end_date=2026-05-31
Response
{
"object": "list",
"url": "/api/v1/apps/01JN4HKQX0000000000000000/transactions",
"page": 1,
"per_page": 50,
"total_count": 318,
"has_more": true,
"metadata": {
"summary": { "total_transactions": 318 },
"filters": {
"type": "APP_SUBSCRIPTION_SALE",
"include_test": false,
"start_date": "2026-05-01",
"end_date": "2026-05-31",
"sort": "processed_at",
"order": "desc"
}
},
"data": [
{
"id": "9912034",
"type": "APP_SUBSCRIPTION_SALE",
"type_label": "Subscription sale",
"shop": "acme-store.myshopify.com",
"amount": 19.0,
"gross_amount": 19.0,
"net_amount": 15.2,
"shopify_fee": 3.8,
"currency": "USD",
"processed_at": "2026-05-01T00:00:00+00:00",
"created_at": "2026-05-01T04:12:00+00:00"
}
]
}
LTV cohorts
Per-acquisition-cohort lifetime value — one row per install month, with the cohort's customer count and its average / median / total lifetime revenue. This is the cohort detail behind the aggregate ltv metric: LTV per shop is SUM(gross_amount) over its real paid transactions, and the cohort is the month a shop first installed. Rows are ordered oldest cohort first.
Monetary values share one base payout currency, declared once on the envelope's currency key (never per row). Only pagination parameters are accepted — the breakdown is a full computed set.
Path parameter
- Name
app- Type
- string
- Description
The team app's ULID.
Query parameters
- Name
page- Type
- integer
- Description
Page to return. Default
1.
- Name
per_page- Type
- integer
- Description
Cohorts per page. Default
100, maximum100.
Row fields: cohort (YYYY-MM), customers, average_ltv, median_ltv, total_revenue. metadata carries summary.total_cohorts and a value_note describing the calculation.
Request
curl https://ranksyapp.com/api/v1/apps/01JN4HKQX0000000000000000/ltv/cohorts \
-H "Authorization: Bearer rk_live_..."
Response
{
"object": "list",
"url": "/api/v1/apps/01JN4HKQX0000000000000000/ltv/cohorts",
"currency": "USD",
"page": 1,
"per_page": 100,
"total_count": 18,
"has_more": false,
"metadata": {
"summary": { "total_cohorts": 18 },
"value_note": "LTV is per-shop SUM(gross_amount) over real paid transactions; cohort = the month a shop first installed. Amounts are in the app's base payout currency."
},
"data": [
{
"cohort": "2026-01",
"customers": 42,
"average_ltv": 184.5,
"median_ltv": 96.0,
"total_revenue": 7749.0
},
{
"cohort": "2026-02",
"customers": 51,
"average_ltv": 132.4,
"median_ltv": 72.0,
"total_revenue": 6752.4
}
]
}
Response shape
All three endpoints share the standard object: "list" envelope. They are not date-windowed in the series sense, so there is no coverage block. The currency key is present only on LTV cohorts (subscriptions and transactions carry currency per row instead).
- Name
object- Type
- string
- Description
Always
"list".
- Name
url- Type
- string
- Description
The endpoint path.
- Name
currency- Type
- string
- Description
Base-currency ISO code. Present only on LTV cohorts; applies to every row.
- Name
page- Type
- integer
- Description
Current page number.
- Name
per_page- Type
- integer
- Description
Rows per page.
- Name
total_count- Type
- integer
- Description
Total rows across all pages (the filtered total for subscriptions / transactions).
- Name
has_more- Type
- boolean
- Description
Whether further pages exist.
- Name
metadata- Type
- object
- Description
A
summary(filtered total) plus the echoedfilters(subscriptions / transactions) or avalue_note(LTV cohorts).
- Name
data- Type
- array
- Description
The rows.
Pagination
Walk the result set with page and per_page (default 50 for subscriptions / transactions, 100 for LTV cohorts; maximum 100), and check has_more / total_count to know when to stop. The metadata (and currency for LTV cohorts) on the envelope applies to every page.
Errors
Beyond the universal 401 / 403 / 422 / 429 (see Errors), these endpoints return:
409— the app's Shopify Partner integration isn't usable:partner_integration_not_connected,partner_integration_unauthorized(reconnect in Ranksy), orpartner_integration_pending(first sync still running — retry shortly).
A bad query parameter (unknown status / type, malformed date, out-of-range per_page) surfaces as a 422.
Things to keep in mind
- Use
idto MERGE. Subscription and transaction rows carry a stableid— dedup and reconcile on it rather than on natural keys. - Currency placement differs. Subscriptions and transactions put
currencyon each row (per-subscription payout currency); LTV cohorts declare it once on the envelope. - Test data is opt-in. Subscriptions and transactions exclude Shopify test rows by default; pass
include_test=trueto include them. (LTV cohorts already exclude test shops.) - These are the rows behind the aggregates. Subscriptions back
active_subscriptions, transactions backrevenue, and LTV cohorts decomposeltv.