Data API
Paginated REST endpoints to read members, orders, customers, subscriptions, cancellation reasons and store credits. Authenticated via the Subscribfy API key over the Shopify App Proxy.
The Data API exposes paginated GET endpoints to read your Subscribfy data: members, orders, customers, subscription contracts, cancellation reasons and the full store credit ledger. Use these to sync with a CRM or warehouse, build custom dashboards, or reconcile credit balances.
The Data API is the paginated successor to the Collection API. New integrations should prefer these endpoints because they paginate and return a stable { data, meta } envelope.
Authentication
Requests go through the Shopify App Proxy and require your Subscribfy API key. Generate it in Settings → Integrations → Subscribfy API (see API Key).
Prop
Type
Endpoints
| Endpoint | Method | Returns |
|---|---|---|
/apps/subscribfy-api/v1/data/members | GET | Members with their store credit balances |
/apps/subscribfy-api/v1/data/credits-history | GET | Store credit transactions, newest first |
/apps/subscribfy-api/v1/data/orders | GET | Order-level export rows with revenue, checkout type and dispute data |
/apps/subscribfy-api/v1/data/customers | GET | Customer-level aggregates: membership status and store credit totals |
/apps/subscribfy-api/v1/data/subscriptions | GET | Subscription contracts with billing dates and churn offer state |
/apps/subscribfy-api/v1/data/cancellation-reasons | GET | Cancellation events with the reason the customer gave |
/apps/subscribfy-api/v1/data/store-credits | GET | The full store credit ledger |
Replace the host with your Shopify store domain, for example:
https://your-store.myshopify.com/apps/subscribfy-api/v1/data/members?key=YOUR_API_KEYResponse Envelope
Every endpoint returns a { data, meta } wrapper, but there are two pagination styles. Check which one your endpoint uses before writing the loop.
Used by /data/members and /data/credits-history. You pass an incrementing page number.
{
"data": [ /* records */ ],
"meta": {
"current_page": 1,
"per_page": 10000
}
}The list ends when data returns fewer items than per_page, or is empty.
Used by /data/orders, /data/customers, /data/subscriptions, /data/cancellation-reasons and /data/store-credits. You pass the previous page's last_id back as since_id.
{
"data": [ /* records */ ],
"meta": {
"per_page": 10000,
"last_id": 48213,
"has_more": true,
"timezone": "America/New_York"
}
}Keep requesting while has_more is true. When has_more is false you have reached the end.
| Endpoint | Pagination | Records per page |
|---|---|---|
/data/members | page | 10000 |
/data/credits-history | page | 1000 |
/data/orders | since_id | 10000 |
/data/customers | since_id | 10000 |
/data/subscriptions | since_id | 10000 |
/data/cancellation-reasons | since_id | 10000 |
/data/store-credits | since_id | 10000 |
Cursor endpoints are not filtered by page number
Passing page to a cursor endpoint has no effect. Rows are ordered by ascending internal id, so you must carry meta.last_id forward as since_id or you will re-read the first page forever.
GET /data/members
Returns all customers known to Subscribfy for the shop, with their current store credit balance. If a third-party reward points integration is active (for example Klaviyo or Yotpo loyalty), an extra balance field is added per member.
Query parameters
Prop
Type
Example
curl -G "https://your-store.myshopify.com/apps/subscribfy-api/v1/data/members" \
--data-urlencode "key=YOUR_API_KEY" \
--data-urlencode "page=1"Response:
{
"data": [
{
"shopify_customer_gid": "7834521098",
"email": "john@example.com",
"balance_from_subscribfy": "150.00"
},
{
"shopify_customer_gid": "7834521099",
"email": "jane@example.com",
"balance_from_subscribfy": "0.00"
}
],
"meta": {
"current_page": 1,
"per_page": 10000
}
}Member fields
Prop
Type
When a reward points integration is active and exposes its balance to the storefront, an additional integration-specific field appears on each member (for example klaviyo_points, yotpo_points). The exact key is provided by the integration. If the integration is connected but its balance is hidden from the storefront, the value returns as " - ".
GET /data/credits-history
Returns store credit transactions for the shop, newest first. Cancelled and aborted records are excluded. You can optionally restrict results to a single customer.
Query parameters
Prop
Type
Example
curl -G "https://your-store.myshopify.com/apps/subscribfy-api/v1/data/credits-history" \
--data-urlencode "key=YOUR_API_KEY" \
--data-urlencode "page=1" \
--data-urlencode "shopify_customer_gid=7834521098"Response:
{
"data": [
{
"shopify_customer_gid": "7834521098",
"body": "Discount Redemption",
"value": "-15.00",
"unit": "money",
"status": 1,
"created_at": "2026-01-20 14:22",
"order_name": "#1234"
},
{
"shopify_customer_gid": "7834521098",
"body": "You've earned 29.00 Store Credits!",
"value": "29.00",
"unit": "money",
"status": 0,
"created_at": "2026-01-15 10:30"
}
],
"meta": {
"current_page": 1,
"per_page": 1000
}
}Credit history fields
Prop
Type
Timestamps are formatted in your shop's configured timezone (the Shopify "Standards and formats" timezone). Convert to UTC client-side if you need timezone-independent storage.
Cursor Endpoints
The five endpoints below share the same contract. They walk rows in ascending internal id order and return a meta.last_id you feed back as since_id on the next call.
Shared query parameters
Prop
Type
Shared meta fields
Prop
Type
Date values in cursor endpoints are plain strings formatted in the shop timezone reported by meta.timezone, not ISO 8601 with an offset. Fields that have no value return an empty string rather than null.
GET /data/orders
Returns one row per order, enriched with membership context, store credit usage, dispute state and auto-charge failures.
Query parameters
Prop
Type
since_date filters on the purchase date, since_updated filters on the last time the order row changed in Subscribfy. Use since_updated to pick up refunds, dispute changes and store credit corrections on orders that were purchased before your window.
Example
curl -G "https://your-store.myshopify.com/apps/subscribfy-api/v1/data/orders" \
--data-urlencode "key=YOUR_API_KEY" \
--data-urlencode "since_id=0" \
--data-urlencode "since_date=2026-01-01"Response:
{
"data": [
{
"shopify_customer_gid": "7834521098",
"customer_email": "john@example.com",
"customer_name": "John Doe",
"order_id": "gid://shopify/Order/5512345678",
"order_name": "#1234",
"order_date": "2026-01-20 14:22",
"financial_status": "paid",
"payment_gateway": "shopify_payments",
"checkout_type": "VIP",
"revenue": "89.00",
"tax": "7.12",
"currency": "USD",
"order_tag": "recharge",
"store_credit_used": "15.00",
"country_code": "US",
"province": "NY"
}
],
"meta": {
"per_page": 10000,
"last_id": 48213,
"has_more": true,
"timezone": "America/New_York"
}
}Order fields
| Field | Description |
|---|---|
shopify_customer_gid | Shopify customer ID |
customer_email | Customer email address |
customer_name | Customer name on the order |
order_id | Shopify order GID |
order_name | Shopify order name, for example #1234 |
order_date | Purchase date and time, formatted Y-m-d H:i |
financial_status | Shopify financial status, for example paid or refunded |
payment_gateway | Gateway that processed the payment |
refund_date | Refund date, or empty when not refunded |
customer_first_order_date | Date of the customer's first order |
customer_first_vip_order_date | Date of the customer's first VIP membership order |
customer_first_recharge_order_date | Date of the customer's first VIP recharge |
checkout_type | VIP, Product Subscription or PAYG |
checkout_type_first_order | Checkout type of the customer's first order |
revenue | Order total, including auto-charge orders |
tax | Tax applied to the order |
currency | Order currency code |
order_tag | New-vip, New-SubsProd, recharge, SubsProd-Recharge or trial fee. Empty for plain orders |
coupon_code | Discount code applied to the order |
coupon_code_amount | Value the discount code took off |
store_credit_used | Subscribfy store credit spent on this order |
autocharge_status | On recharge orders: Dispute, or the refunded or voided financial status |
dispute_status | Dispute status, empty when the order was never disputed |
dispute_initiated_date | Date the dispute was opened |
dispute_last_updated_date | Date the dispute last changed |
dispute_reason | Reason given for the dispute |
brand | Brand recorded on the order |
store_credit_deducted | Store credit deducted against this order |
deduction_date | Date of that deduction |
sales_channel | Sales channel name resolved from the Shopify app id |
auto_charge_failed_count | Number of auto-charge failures logged for this order |
auto_charge_failure_reasons | Failure reasons, newline separated |
customer_first_ps_order_date | Date of the customer's first product subscription order |
customer_first_ps_recharge_order_date | Date of the customer's first product subscription recharge |
country_code | Shipping country code |
province | Shipping province or state |
GET /data/customers
Returns one row per customer with their membership state and lifetime store credit totals. This is the aggregate view, where /data/members is the lightweight balance-only view.
Query parameters
Prop
Type
No since_date on this endpoint
/data/customers accepts only since_id and shopify_customer_gid. A since_date parameter is ignored, because the row is a rolling aggregate rather than a dated event.
Example
curl -G "https://your-store.myshopify.com/apps/subscribfy-api/v1/data/customers" \
--data-urlencode "key=YOUR_API_KEY" \
--data-urlencode "since_id=0"Response:
{
"data": [
{
"shopify_customer_gid": "7834521098",
"customer_email": "john@example.com",
"membership_type": "VIP",
"current_status": "ACTIVE",
"membership_start_date": "2025-03-14",
"available_current_balance": "150.00",
"total_store_credits_charged": "480.00",
"total_store_credit_used": "330.00",
"country_code": "US",
"province": "NY"
}
],
"meta": {
"per_page": 10000,
"last_id": 90114,
"has_more": true,
"timezone": "America/New_York"
}
}Customer fields
| Field | Description |
|---|---|
shopify_customer_gid | Shopify customer ID |
customer_email | Customer email address |
membership_type | Membership the customer is on, for example VIP or PAYG |
current_status | Current membership status |
date_paused | Date the membership was paused, when applicable |
date_of_last_charge | Date of the most recent store credit charge |
membership_start_date | Oldest membership start date |
membership_end_date | Most recent membership end date |
available_current_balance | Store credit currently available in Subscribfy |
total_store_credits_charged | Lifetime store credit issued to the customer |
total_store_credit_used | Lifetime store credit spent |
total_store_credit_disputed | Store credit tied to disputed orders |
total_store_credit_refunded | Store credit refunded |
total_store_credit_forfeit | Store credit forfeited |
last_store_forfeit_date | Date of the most recent forfeit |
store_forfeit_history | Forfeit history entries |
total_store_credit_reconciled | Store credit reconciled by manual adjustment |
last_store_reconciled_date | Date of the most recent reconciliation |
store_reconciled_history | Reconciliation history entries |
country_code | Country code from the customer's order |
province | Province or state from the customer's order |
auto_charge_failed | Whether the customer has a failed auto-charge |
failure_reasons | VIP auto-charge failure reasons |
cancel_reason | Reason recorded when the VIP membership was cancelled |
ps_failure_reasons | Product subscription auto-charge failure reasons |
ps_cancel_reason | Reason recorded when a product subscription was cancelled |
ps_total_contracts | Total product subscription contracts |
ps_active_contracts | Currently active product subscription contracts |
vip_status_history | History of VIP status changes |
GET /data/subscriptions
Returns one row per subscription contract, including billing cadence, last and next charge, and the churn offer currently attached to the contract.
Query parameters
Prop
Type
since_date filters on the contract creation date, since_updated filters on the last time the contract changed. Use since_updated to catch cancellations, pauses, price changes and churn offers on contracts that started long before your window.
Example
curl -G "https://your-store.myshopify.com/apps/subscribfy-api/v1/data/subscriptions" \
--data-urlencode "key=YOUR_API_KEY" \
--data-urlencode "since_id=0" \
--data-urlencode "since_updated=2026-02-01"Response:
{
"data": [
{
"shopify_customer_gid": "7834521098",
"customer_email": "john@example.com",
"customer_name": "John Doe",
"shopify_contract_id": "1234567890",
"status": "ACTIVE",
"type": "Monthly VIP",
"price": "29.00",
"currency": "USD",
"start_date": "2025-03-14",
"last_charge_date": "2026-01-14",
"last_charge_status": "Successful",
"next_charge_date": "2026-02-14",
"interval_name": "MONTH",
"interval_count": 1,
"churn_offer_status": "",
"cancelled_date": ""
}
],
"meta": {
"per_page": 10000,
"last_id": 7741,
"has_more": false,
"timezone": "America/New_York"
}
}Subscription fields
| Field | Description |
|---|---|
shopify_customer_gid | Shopify customer ID |
customer_email | Customer email address |
customer_name | Customer name |
shopify_contract_id | Numeric Shopify subscription contract ID, with the GID prefix stripped |
status | Contract status, for example ACTIVE or CANCELLED |
type | Contract title, which is the plan name |
price | Contract price |
currency | Contract currency code |
start_date | Date the contract was created |
cancelled_date | Cancellation date, empty while the contract is live |
last_charge_date | Date of the last billing attempt |
last_charge_status | Successful or Failed. Empty when never billed |
next_charge_date | Next billing date. Empty once cancelled |
interval_name | Billing interval unit, for example MONTH |
interval_count | Number of intervals between charges |
churn_offer_status | Status of the churn offer on this contract, empty when none |
offer_type | Type of the attached churn offer |
price_with_offer | Contract price once the offer is applied |
offer_accepted_date | Date the customer accepted the offer |
offer_cancel_date | Date the offer was cancelled |
GET /data/cancellation-reasons
Returns cancellation events with the reason the customer selected or typed. This is the feed to use for churn reporting.
Query parameters
Prop
Type
Example
curl -G "https://your-store.myshopify.com/apps/subscribfy-api/v1/data/cancellation-reasons" \
--data-urlencode "key=YOUR_API_KEY" \
--data-urlencode "since_date=2026-01-01"Response:
{
"data": [
{
"shopify_customer_gid": "7834521098",
"customer_email": "john@example.com",
"subscription_plan_name": "Monthly VIP",
"cancel_date": "2026-01-22 09:41",
"cancel_reason": "Too expensive"
}
],
"meta": {
"per_page": 10000,
"last_id": 30982,
"has_more": false,
"timezone": "America/New_York"
}
}Cancellation fields
| Field | Description |
|---|---|
shopify_customer_gid | Shopify customer ID |
customer_email | Customer email address |
subscription_plan_name | Plan group name the customer cancelled |
cancel_date | Cancellation date and time, formatted Y-m-d H:i |
cancel_reason | Reason recorded on the cancellation |
GET /data/store-credits
Returns the raw store credit ledger, one row per transaction. Aborted records are excluded, as are cancelled redemptions.
This is the cursor-paginated sibling of /data/credits-history. Prefer /data/store-credits for full exports and warehouse loads, and /data/credits-history when you want the newest transactions first or a single customer's ledger.
Query parameters
Prop
Type
Example
curl -G "https://your-store.myshopify.com/apps/subscribfy-api/v1/data/store-credits" \
--data-urlencode "key=YOUR_API_KEY" \
--data-urlencode "since_id=0"Response:
{
"data": [
{
"shopify_customer_gid": "7834521098",
"type": 1,
"message": "You've earned 29.00 Store Credits!",
"value": "29.00",
"redeemed_from_subscribfy": "",
"order_gid": "gid://shopify/Order/5512345678",
"created_at": "2026-01-15",
"used_in": ""
}
],
"meta": {
"per_page": 10000,
"last_id": 118422,
"has_more": true,
"timezone": "America/New_York"
}
}Store credit fields
| Field | Description |
|---|---|
shopify_customer_gid | Shopify customer ID |
type | Store credit transaction type code |
message | Human-readable description of the transaction |
value | Transaction amount |
redeemed_from_subscribfy | Subscribfy credit spent on the order. Populated on redemption rows only |
order_gid | Shopify order GID the transaction relates to |
created_at | Transaction date, formatted Y-m-d |
used_in | Where the credit was consumed, when recorded |
Pagination Pattern
Page-based endpoints
/data/members and /data/credits-history use an incrementing page. Loop until you receive fewer items than per_page:
$apiKey = 'your_api_key';
$store = 'your-store.myshopify.com';
$page = 1;
$members = [];
do {
$url = "https://{$store}/apps/subscribfy-api/v1/data/members?" . http_build_query([
'key' => $apiKey,
'page' => $page,
]);
$response = json_decode(file_get_contents($url), true);
$members = array_merge($members, $response['data'] ?? []);
$page++;
} while (count($response['data'] ?? []) === ($response['meta']['per_page'] ?? 0));
echo count($members) . " members loaded\n";const axios = require('axios');
const apiKey = 'your_api_key';
const store = 'your-store.myshopify.com';
async function getAllMembers() {
const all = [];
let page = 1;
let perPage = 0;
do {
const { data } = await axios.get(
`https://${store}/apps/subscribfy-api/v1/data/members`,
{ params: { key: apiKey, page } }
);
all.push(...data.data);
perPage = data.meta.per_page;
page++;
if (data.data.length < perPage) break;
} while (true);
return all;
}
getAllMembers().then(m => console.log(`${m.length} members`));import requests
api_key = 'your_api_key'
store = 'your-store.myshopify.com'
page = 1
members = []
while True:
res = requests.get(
f'https://{store}/apps/subscribfy-api/v1/data/members',
params={'key': api_key, 'page': page},
).json()
members.extend(res['data'])
if len(res['data']) < res['meta']['per_page']:
break
page += 1
print(f'{len(members)} members loaded')Cursor endpoints
/data/orders, /data/customers, /data/subscriptions, /data/cancellation-reasons and /data/store-credits use a since_id cursor. Loop while meta.has_more is true, carrying meta.last_id forward:
$apiKey = 'your_api_key';
$store = 'your-store.myshopify.com';
$sinceId = 0;
$orders = [];
do {
$url = "https://{$store}/apps/subscribfy-api/v1/data/orders?" . http_build_query([
'key' => $apiKey,
'since_id' => $sinceId,
]);
$response = json_decode(file_get_contents($url), true);
$orders = array_merge($orders, $response['data'] ?? []);
$sinceId = $response['meta']['last_id'] ?? null;
} while (!empty($response['meta']['has_more']) && $sinceId !== null);
echo count($orders) . " orders loaded\n";const axios = require('axios');
const apiKey = 'your_api_key';
const store = 'your-store.myshopify.com';
async function getAllOrders() {
const all = [];
let sinceId = 0;
let hasMore = true;
while (hasMore) {
const { data } = await axios.get(
`https://${store}/apps/subscribfy-api/v1/data/orders`,
{ params: { key: apiKey, since_id: sinceId } }
);
all.push(...data.data);
hasMore = data.meta.has_more;
sinceId = data.meta.last_id;
if (sinceId === null) break;
}
return all;
}
getAllOrders().then(o => console.log(`${o.length} orders`));import requests
api_key = 'your_api_key'
store = 'your-store.myshopify.com'
since_id = 0
orders = []
while True:
res = requests.get(
f'https://{store}/apps/subscribfy-api/v1/data/orders',
params={'key': api_key, 'since_id': since_id},
).json()
orders.extend(res['data'])
since_id = res['meta']['last_id']
if not res['meta']['has_more'] or since_id is None:
break
print(f'{len(orders)} orders loaded')Always advance the cursor
If you send the same since_id twice you get the same page twice. Persist meta.last_id between runs so an incremental sync resumes where it stopped instead of re-reading the whole table.
Incremental sync with since_updated
/data/orders and /data/subscriptions accept since_updated, a Y-m-d date that keeps only rows last changed on or after midnight of that date in the shop timezone. This is the parameter to use for a recurring sync that has to stay accurate, because a since_id cursor only moves forward and never returns a row that changed after you read it.
| Parameter | Filters on | Returns |
|---|---|---|
since_id | Internal row id | New rows only |
since_date | Order purchase date, or contract creation date | Rows created in the window |
since_updated | Last change to the row, falling back to its creation time when it has never changed | Rows created or changed in the window |
A nightly run with since_updated picks up a refund posted against a two year old order, a contract cancelled this morning, or a price change on a plan that started last spring. A run with since_date or since_id alone misses all three.
import requests
from datetime import date, timedelta
api_key = 'your_api_key'
store = 'your-store.myshopify.com'
# Overlap by a day: since_updated has day granularity
since_updated = (date.today() - timedelta(days=1)).isoformat()
since_id = 0
rows = []
while True:
res = requests.get(
f'https://{store}/apps/subscribfy-api/v1/data/orders',
params={
'key': api_key,
'since_id': since_id,
'since_updated': since_updated,
},
).json()
rows.extend(res['data'])
since_id = res['meta']['last_id']
if not res['meta']['has_more'] or since_id is None:
break
print(f'{len(rows)} orders created or changed since {since_updated}')Start every since_updated run at since_id 0
A row that just changed can have any internal id, including a very low one. Carry the previous run's meta.last_id forward and you will skip exactly the old rows you are trying to catch. Reset since_id to 0 on each run and only advance it inside that run, as the loop above does.
since_updated resolves to 00:00 in the shop timezone, so a run always re-reads rows that changed earlier on the same date. Upsert by order_id or shopify_contract_id instead of appending, and the overlap costs nothing.
Combining filters
Filters are combined with AND. since_date with since_updated returns rows created on or after the first date and changed on or after the second. On /data/orders, shopify_customer_gid narrows either of them to one customer. Rows that carry no creation or change timestamp at all are left out once since_updated is set.
Errors
| Status | Cause |
|---|---|
| 401 | Missing key, invalid API key, or the Shopify App Proxy signature could not be verified |
| 422 | page is not a positive integer, since_id is not an integer of 0 or more, or since_date / since_updated is not in Y-m-d format |
When to Use Which Endpoint
Use the Webhook Events API for real-time pushes (a payment, a credit change, a tier upgrade), and the Data API for periodic reconciliation, exports, or rebuilding state after downtime. They are complementary, not redundant.
| Need | Use |
|---|---|
| React to events as they happen | Webhook Events API |
| Initial backfill or full re-sync | Data API cursor endpoints |
| Incremental nightly sync | Cursor endpoints with a stored since_id, or since_date |
| Catch changes to orders or contracts created earlier | /data/orders or /data/subscriptions with since_updated |
| One customer's credit ledger | /data/credits-history with shopify_customer_gid |
| Aggregated balances and emails | /data/members |
| Revenue and checkout-type reporting | /data/orders |
| Membership status and credit totals per customer | /data/customers |
| Billing cadence and churn offer state | /data/subscriptions |
| Churn reporting | /data/cancellation-reasons |
| Full store credit ledger export | /data/store-credits |
Related
- Subscribfy API Key - generate and manage your key
- Collection API - the older, non-paginated equivalent
- Webhook Events API - real-time push notifications
Was this page helpful?
Storefront Customer Portal API
Storefront API for customer self-service: pause, cancel, skip, swap products, update billing, and manage subscriptions.
Collection API (Deprecated)
Deprecated. Use the Data API for new integrations. Query customer data, store credit history, activity logs, and subscription contracts via the legacy Collection API endpoint.