Payment Orchestrator
Subscription
Subscriptions

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

Subscription lifecycleState machine: a subscription starts in trial or directly active, can be paused and resumed, and ends on cancellation — either immediately or at period end.TRIAL ENDSskipTrial: truePAUSERESUMECANCELTrialActivePausedCancelledLEGENDBILLSDOES NOT BILLEND
Invoices are produced only while Active. Cancelling with 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):

statusMeaning
PENDINGScheduled start — startsAt has not arrived yet
TRIALINGTrial period running
ACTIVELive — period invoices are generated and collected
PAST_DUEA charge failed — dunning is running
UNPAIDDunning retries exhausted — grace period running
PAUSEDPaused by the merchant
UPGRADEDEnded by an upgrade plan change — terminal
DOWNGRADEDEnded by a downgrade plan change — terminal
CANCELEDCanceled — terminal
EXPIREDThe 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

collectionMethodBehavior
AUTO_CHARGEEach period invoice is charged automatically to the stored card (paymentCardId / the customer's default card). Failures enter dunning.
SEND_INVOICEAn invoice is issued and the customer pays it themselves; no automatic card charge.
MANUALNothing 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.

EventWhen
subscription.createdSubscription created (trialing or not)
subscription.trial_will_endShortly before the trial expires
subscription.activatedFirst paid period begins

Cancel

POST /api/v1/subscriptions/subscriptions/:id/cancel

ModeRequestBehavior
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:

behaviorEffectEvents
IMMEDIATESwitches now; billing follows the new plan from this momentsubscription.upgraded or subscription.downgraded
NEXT_PERIODSchedules the switch for the next renewal; current period is untouchedsubscription.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

MethodPathWhat it does
GET/api/v1/subscriptions/subscriptionsList subscriptions (cursor pagination: pageSize, pageToken; filter by customerId, status)
GET/api/v1/subscriptions/subscriptions/:idOne subscription with current plan and period
GET/api/v1/subscriptions/subscriptions/:id/audit-logsChronological 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

StatusMeaningWhat to do
400Validation failed (missing idempotencyKey, unknown collectionMethod / behavior)Fix the body; idempotencyKey is required on create
401Missing/invalid Bearer keyCheck the Authorization header
404Subscription, customer, plan or card not foundVerify ids belong to your account
409Invalid state transition (e.g. resuming a non-paused subscription, canceling an already-canceled one) or idempotency conflictRead the subscription (GET …/subscriptions/:id) first and act on the current status

Webhooks

EventWhen
subscription.createdSubscription created
subscription.activatedFirst paid period starts (post-trial or immediately)
subscription.trial_will_endTrial is about to expire
subscription.paused / subscription.resumedPause / resume
subscription.plan_change_scheduledA NEXT_PERIOD plan change was scheduled
subscription.pending_change_appliedA scheduled change took effect
subscription.upgraded / subscription.downgradedPlan changed up / down
subscription.canceledCanceled (immediately or at period end)
subscription.payment_card_swappedThe card backing the subscription changed
subscription.payment_method_lostStored card became unusable — collection falls back to MANUAL

See Webhooks for delivery, retries and signature verification.

Next steps