1. Basics
| Item | Value |
|---|---|
| Base URL | https://app.sublyra.com/api/v1 |
| Authentication | Authorization: Bearer cad_pk_… — an API key from Settings → Integrations. See Authentication. |
| Methods | GET only. Everything else returns 405. |
| Response format | JSON, always — including errors. Success: { "data": [ … ], "page": { … } }. |
| Money | Integer cents in the shop's currency (totalCents: 4200 = $42.00). |
| Dates | ISO 8601 UTC strings; unset dates are null. |
The API serves Sublyra's local mirror of your subscription data — Shopify remains the source of truth, and inbound Shopify webhooks keep the mirror in step. Reads are fast, consistent and never rate-limited by Shopify's own API.
2. Pagination
All three endpoints share the same cursor pagination:
| Parameter | Default | Notes |
|---|---|---|
limit | 50 | Maximum 250. Non-numeric or non-positive values fall back to the default. |
cursor | — | The nextCursor value from the previous page, passed verbatim. Opaque — don't build one yourself. |
Every response carries a page object:
{
"data": [ … ],
"page": {
"limit": 50,
"nextCursor": "2026-08-01T12:00:00.000Z~clx9k2m4p0001…"
}
}
When nextCursor is null, you're on the last page. When it's a string, pass it back as ?cursor= to get the next page:
curl "https://app.sublyra.com/api/v1/subscriptions?limit=250&cursor=2026-08-01T12%3A00%3A00.000Z~clx9k2m4p0001" \
-H "Authorization: Bearer cad_pk_your_key_here"
Pages are ordered newest-first by each resource's date field (startedAt for subscriptions, createdAt for customers, billedAt for orders). The cursor is compound — <ISO date>~<id> — so rows that share a timestamp are never skipped or duplicated between pages.
400 bad_cursor. URL-encode it when building query strings (the ~ and : characters need it in some clients).
3. GET /api/v1/subscriptions
Lists your subscription contracts, newest startedAt first.
curl "https://app.sublyra.com/api/v1/subscriptions?limit=50" \
-H "Authorization: Bearer cad_pk_your_key_here"
Response
{
"data": [
{
"id": "clx9k2m4p0001abcd",
"shopifyId": "gid://shopify/SubscriptionContract/1001",
"customerId": "clx9k2m4p0002efgh",
"status": "ACTIVE",
"interval": "MONTH",
"intervalCount": 1,
"sellingPlanId": "gid://shopify/SellingPlan/2001",
"lines": [
{
"variantId": "gid://shopify/ProductVariant/3001",
"productId": "gid://shopify/Product/4001",
"title": "House Blend — 1kg",
"quantity": 2,
"unitPriceCents": 2100
}
],
"nextBillingDate": "2026-09-01T00:00:00.000Z",
"cyclesCompleted": 3,
"startedAt": "2026-06-01T09:30:00.000Z",
"cancelledAt": null
}
],
"page": { "limit": 50, "nextCursor": null }
}
Fields
| Field | Type | Notes |
|---|---|---|
id | string | Sublyra's internal contract ID. Stable — use it to join with contractId on orders. |
shopifyId | string | The Shopify subscription contract gid. |
customerId | string | Internal customer ID — matches id from /api/v1/customers. |
status | string | ACTIVE · PAUSED · CANCELLED · PAYMENT_FAILED · EXPIRED |
interval | string | DAY · WEEK · MONTH |
intervalCount | number | How many intervals between billings — interval: MONTH, intervalCount: 2 bills every two months. |
sellingPlanId | string | null | The Shopify selling plan gid the contract belongs to. |
lines | array | Line-item snapshot: variantId, productId, title, quantity, unitPriceCents. |
nextBillingDate | string | null | ISO date of the next scheduled billing; null for cancelled/expired contracts. |
cyclesCompleted | number | Renewals billed so far. |
startedAt | string | ISO date the contract started. The sort field for this endpoint. |
cancelledAt | string | null | ISO date of cancellation, if any. |
4. GET /api/v1/customers
Lists customers known to Sublyra — your subscribers and their loyalty state — newest createdAt first.
curl "https://app.sublyra.com/api/v1/customers?limit=50" \
-H "Authorization: Bearer cad_pk_your_key_here"
Response
{
"data": [
{
"id": "clx9k2m4p0002efgh",
"shopifyId": "gid://shopify/Customer/5001",
"email": "jane@example.com",
"firstName": "Jane",
"lastName": "Doe",
"marketingConsent": true,
"creditsCents": 750,
"loyaltyTier": 1,
"subscriptionCount": 2,
"createdAt": "2026-06-01T09:28:00.000Z"
}
],
"page": { "limit": 50, "nextCursor": null }
}
Fields
| Field | Type | Notes |
|---|---|---|
id | string | Internal customer ID — the value customerId refers to on subscriptions and orders. |
shopifyId | string | The Shopify customer gid. |
email, firstName, lastName | string | null | From the Shopify customer record. |
marketingConsent | boolean | Whether the customer opted in to marketing. Transactional mail ignores this; marketing features respect it. |
creditsCents | number | Current loyalty store-credit balance, in cents. |
loyaltyTier | number | Tier index — 0 is the base tier. |
subscriptionCount | number | How many contracts (any status) this customer has. |
createdAt | string | ISO date the customer record was first mirrored. The sort field for this endpoint. |
5. GET /api/v1/orders
Lists subscription orders — both checkout (first) orders and recurring renewals — newest billedAt first.
curl "https://app.sublyra.com/api/v1/orders?limit=50" \
-H "Authorization: Bearer cad_pk_your_key_here"
Response
{
"data": [
{
"id": "clx9k2m4p0003ijkl",
"shopifyId": "gid://shopify/Order/6001",
"contractId": "clx9k2m4p0001abcd",
"customerId": "clx9k2m4p0002efgh",
"source": "recurring",
"cycleNumber": 3,
"status": "PAID",
"lines": [
{
"variantId": "gid://shopify/ProductVariant/3001",
"productId": "gid://shopify/Product/4001",
"title": "House Blend — 1kg",
"quantity": 2,
"unitPriceCents": 2100
}
],
"subtotalCents": 4200,
"discountCents": 420,
"creditsCents": 0,
"totalCents": 3780,
"billedAt": "2026-08-01T00:01:00.000Z"
}
],
"page": { "limit": 50, "nextCursor": null }
}
Fields
| Field | Type | Notes |
|---|---|---|
id | string | Internal order ID. |
shopifyId | string | null | The Shopify order gid. null until the charge succeeds — a failed attempt has no Shopify order yet. |
contractId | string | Internal contract ID — matches id from /api/v1/subscriptions. |
customerId | string | Internal customer ID. |
source | string | checkout (the subscriber's first order) · recurring (a renewal). |
cycleNumber | number | Which renewal this is for the contract — 1 is the first. |
status | string | PENDING · PAID · RECOVERED · FAILED · UNCOLLECTIBLE · SKIPPED |
lines | array | Line-item snapshot, same shape as contract lines. |
subtotalCents | number | Line total before discounts and credits. |
discountCents | number | Discounts applied to this order. |
creditsCents | number | Loyalty store credit applied to this order. |
totalCents | number | What was (or will be) charged: subtotal − discount − credits. |
billedAt | string | ISO date of the billing attempt. The sort field for this endpoint. |
6. Errors
Every error — including unexpected server failures — is JSON with a stable, machine-readable code:
{
"error": {
"code": "bad_cursor",
"message": "Malformed cursor. Pass the nextCursor value from the previous page verbatim."
}
}
| Status | Code | Meaning → fix |
|---|---|---|
400 | bad_cursor | The cursor parameter isn't a cursor this API issued → pass the previous page's nextCursor verbatim, URL-encoded. |
401 | unauthorized | Missing/malformed header, or an unknown key → see Authentication errors. Responses include WWW-Authenticate: Bearer. |
404 | not_found | Unknown resource path → the available resources are subscriptions, customers, orders. |
405 | method_not_allowed | Anything but GET → the public API is read-only. Responses include Allow: GET. |
500 | internal | Unexpected server error → safe to retry; if it persists, contact support. |
7. Read-only by design
The public API has no write surface at all — there is no POST to create a contract and no DELETE to cancel one. That's deliberate:
- Shopify is the source of truth. Subscriptions are created in checkout and changed through the customer portal or the Sublyra admin, so every change flows through Shopify's own billing and notification machinery.
- A leaked key can't hurt your store. The worst case of a compromised read-only key is data exposure, not fraudulent subscriptions or mass cancellations.
To react to changes instead of polling, subscribe to outbound webhooks; to automate inside Shopify, use the Flow triggers.