Subscriptions
A subscription binds a customer to a plan and drives the billing engine: trials, period invoices, dunning, plan changes and cancellation. This page covers the whole lifecycle through the merchant API.
All endpoints use your secret key as a Bearer token under https://api-gateway.lunixi.com/api/v1/subscriptions/subscriptions — the segment appears twice on purpose: the first subscriptions is the product namespace, the second is this resource group. List endpoints use cursor pagination (pageSize, pageToken).
Lifecycle at a glance
cancelAtPeriodEnd keeps the subscription active until the period ends — that period's invoice is still issued.Every transition emits a webhook — subscription.created, subscription.activated, subscription.paused, subscription.resumed, subscription.canceled — so your system can mirror state without polling. The full event table is at the bottom.
Status values
The status field on a subscription takes exactly these values (you can also filter the list endpoint by status):
status | Meaning |
|---|---|
PENDING | Scheduled start — startsAt has not arrived yet |
TRIALING | Trial period running |
ACTIVE | Live — period invoices are generated and collected |
PAST_DUE | A charge failed — dunning is running |
UNPAID | Dunning retries exhausted — grace period running |
PAUSED | Paused by the merchant |
UPGRADED | Ended by an upgrade plan change — terminal |
DOWNGRADED | Ended by a downgrade plan change — terminal |
CANCELED | Canceled — terminal |
EXPIRED | The billing rule's occurrence count completed — terminal |
Entitlements are honored while the status is ACTIVE or PAST_DUE. Terminal statuses (UPGRADED, DOWNGRADED, CANCELED, EXPIRED) never transition again.
Create a subscription
Create (or upsert) the customer
Customers are upserted by your own externalId via PUT /api/v1/subscriptions/customers, so you can safely retry.
Store a card (for automatic collection)
If you will charge automatically, save a card first (POST /api/v1/subscriptions/cards with a paymentMethodToken) and pass its id as paymentCardId. Card storage requires 3DS — see Getting Started.
Create the subscription
idempotencyKey is required — creating a subscription can move money, and the key guarantees a network retry never creates a duplicate.
curl -X POST https://api-gateway.lunixi.com/api/v1/subscriptions/subscriptions \
-H "Authorization: Bearer at_live_5f3e2b1a..." \
-H "Content-Type: application/json" \
-d '{
"customerId": "cust_01JX2F9K4T",
"planId": "plan_01JX2G3M8V",
"idempotencyKey": "order-84172-sub",
"paymentCardId": "card_01JX2H5N1Q",
"collectionMethod": "AUTO_CHARGE",
"skipTrial": false,
"startsAt": "2026-08-01T00:00:00Z"
}'{
"success": true,
"data": {
"id": "sub_01JX5B8R3M",
"customerId": "cust_01JX2F9K4T",
"planId": "plan_01JX2G3M8V",
"status": "TRIALING",
"collectionMethod": "AUTO_CHARGE",
"currentPeriodEnd": "2026-09-01T00:00:00Z"
}
}idempotencyKey is mandatory on create. Use a key derived from your business object (e.g. your order id) — retrying with the same key returns the original subscription instead of creating a second one.
Collection methods
collectionMethod | Behavior |
|---|---|
AUTO_CHARGE | Each period invoice is charged automatically to the stored card (paymentCardId / the customer's default card). Failures enter dunning. |
SEND_INVOICE | An invoice is issued and the customer pays it themselves; no automatic card charge. |
MANUAL | Nothing is collected automatically — you settle invoices out-of-band and mark them via retry-charge or your own process. |
If a subscription's stored card becomes unusable (deleted, expired without replacement), the platform emits subscription.payment_method_lost and the subscription's collection drops to MANUAL until a new card is attached. Details on the Invoices page.
Trials
If the plan has trialPeriodDays > 0, the subscription starts in trial: entitlements are live, but no charge happens until the trial ends. Pass skipTrial: true on create to bypass it and bill immediately.
Before the trial ends the platform emits subscription.trial_will_end — use it to prompt the customer to add or confirm a payment method. When the trial ends, the first period invoice is generated and collected per collectionMethod; the subscription emits subscription.activated.
| Event | When |
|---|---|
subscription.created | Subscription created (trialing or not) |
subscription.trial_will_end | Shortly before the trial expires |
subscription.activated | First paid period begins |
Cancel
POST /api/v1/subscriptions/subscriptions/:id/cancel
| Mode | Request | Behavior |
|---|---|---|
| Immediate | { "cancelAtPeriodEnd": false } (or omit) | Subscription ends now; entitlements stop |
| At period end | { "cancelAtPeriodEnd": true } | Subscription stays active until currentPeriodEnd, then ends — no further invoices |
curl -X POST https://api-gateway.lunixi.com/api/v1/subscriptions/subscriptions/sub_01JX5B8R3M/cancel \
-H "Authorization: Bearer at_live_5f3e2b1a..." \
-H "Content-Type: application/json" \
-d '{ "cancelAtPeriodEnd": true }'Emits subscription.canceled. Customers can also cancel themselves through the customer portal — that path additionally requires an email confirmation token as fraud protection.
Pause & resume
POST /api/v1/subscriptions/subscriptions/:id/pause suspends billing; pass an optional resumeAt to schedule automatic reactivation. POST /api/v1/subscriptions/subscriptions/:id/resume reactivates immediately.
curl -X POST https://api-gateway.lunixi.com/api/v1/subscriptions/subscriptions/sub_01JX5B8R3M/pause \
-H "Authorization: Bearer at_live_5f3e2b1a..." \
-H "Content-Type: application/json" \
-d '{ "resumeAt": "2026-10-01T00:00:00Z" }'
# later, or instead of resumeAt:
curl -X POST https://api-gateway.lunixi.com/api/v1/subscriptions/subscriptions/sub_01JX5B8R3M/resume \
-H "Authorization: Bearer at_live_5f3e2b1a..."While paused, no period invoices are generated. Emits subscription.paused and subscription.resumed.
Change plan (upgrade / downgrade)
POST /api/v1/subscriptions/subscriptions/:id/change-plan with the target newPlanId and a behavior:
behavior | Effect | Events |
|---|---|---|
IMMEDIATE | Switches now; billing follows the new plan from this moment | subscription.upgraded or subscription.downgraded |
NEXT_PERIOD | Schedules the switch for the next renewal; current period is untouched | subscription.plan_change_scheduled, then subscription.pending_change_applied (plus upgraded/downgraded) when it takes effect |
curl -X POST https://api-gateway.lunixi.com/api/v1/subscriptions/subscriptions/sub_01JX5B8R3M/change-plan \
-H "Authorization: Bearer at_live_5f3e2b1a..." \
-H "Content-Type: application/json" \
-d '{
"newPlanId": "plan_01JX2G9P7W",
"behavior": "NEXT_PERIOD"
}'A common pattern: upgrades IMMEDIATE (the customer gets the bigger plan right away), downgrades NEXT_PERIOD (they keep what they paid for until renewal).
Retrieve, list & audit
| Method | Path | What it does |
|---|---|---|
GET | /api/v1/subscriptions/subscriptions | List subscriptions (cursor pagination: pageSize, pageToken; filter by customerId, status) |
GET | /api/v1/subscriptions/subscriptions/:id | One subscription with current plan and period |
GET | /api/v1/subscriptions/subscriptions/:id/audit-logs | Chronological record of every state change — who/what triggered creates, cancels, plan changes, pauses |
The audit log is the tool for support questions like "why did this customer's plan change?" — every mutation above appears there with its timestamp and origin.
Errors
| Status | Meaning | What to do |
|---|---|---|
400 | Validation failed (missing idempotencyKey, unknown collectionMethod / behavior) | Fix the body; idempotencyKey is required on create |
401 | Missing/invalid Bearer key | Check the Authorization header |
404 | Subscription, customer, plan or card not found | Verify ids belong to your account |
409 | Invalid state transition (e.g. resuming a non-paused subscription, canceling an already-canceled one) or idempotency conflict | Read the subscription (GET …/subscriptions/:id) first and act on the current status |
Webhooks
| Event | When |
|---|---|
subscription.created | Subscription created |
subscription.activated | First paid period starts (post-trial or immediately) |
subscription.trial_will_end | Trial is about to expire |
subscription.paused / subscription.resumed | Pause / resume |
subscription.plan_change_scheduled | A NEXT_PERIOD plan change was scheduled |
subscription.pending_change_applied | A scheduled change took effect |
subscription.upgraded / subscription.downgraded | Plan changed up / down |
subscription.canceled | Canceled (immediately or at period end) |
subscription.payment_card_swapped | The card backing the subscription changed |
subscription.payment_method_lost | Stored card became unusable — collection falls back to MANUAL |
See Webhooks for delivery, retries and signature verification.
Next steps
- Invoices — period invoices, dunning and retry-charge
- Customer Portal — let customers manage their own subscription
- Features & Add-ons — per-subscription add-on charges
- Usage & Entitlements — metered billing and quota checks