Subscriptions
A subscription is recurring billing tied to a customer: the buyer agreed to a plan at a price, on a cadence, and Storlaunch (or Plugipay, when the module is on) charges them on schedule. State, dunning, cancellation, trial-end, and webhook fan-out are all on this resource.
All endpoints require an sk_* key.
Endpoints
| Method | Path | Purpose |
|---|---|---|
POST |
/v1/payment/subscriptions |
Create a subscription |
GET |
/v1/payment/subscriptions |
List subscriptions |
GET |
/v1/payment/subscriptions/:id |
Retrieve a subscription |
PATCH |
/v1/payment/subscriptions/:id |
Update (change plan, pause, resume) |
DELETE |
/v1/payment/subscriptions/:id |
Cancel |
Create
POST /v1/payment/subscriptions
Idempotency-Key required. Tier-limited — some plans cap subscription count.
Request body
| Field | Type | Required | Description |
|---|---|---|---|
customerId |
string | yes | Buyer (cust_ or cus_ prefix). |
planId |
string | yes | The plan being subscribed to. |
trialDays |
integer | no | Free trial length. During trial, the subscription is trialing and no charges fire. |
metadata |
object | no | Free-form. |
Response — 201 Created. Returns the subscription object.
Events emitted — subscription.created.
curl -X POST https://storlaunch.forjio.com/api/v1/payment/subscriptions \
-H "Authorization: Bearer sk_live_xxx" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{"customerId":"cust_01HX...","planId":"plan_01HX...","trialDays":7}'
Retrieve
GET /v1/payment/subscriptions/:id
List
GET /v1/payment/subscriptions?limit=50&cursor=…&status=…&customerId=…
Filter by status (active, trialing, past_due, paused, canceled, unpaid) and by customerId.
Update
PATCH /v1/payment/subscriptions/:id
Change the plan (which re-prorates the current cycle), pause, or resume. Idempotency-Key supported.
Request body
| Field | Type | Description |
|---|---|---|
planId |
string | Switch to a different plan. Pro-rated against the current cycle. |
status |
paused | active |
Pause to skip the next renewal, resume to charge again. Cannot set to canceled here; use DELETE. |
metadata |
object | Replace metadata map. |
Cancel
DELETE /v1/payment/subscriptions/:id
Cancels at period-end by default. Pass ?immediate=true to terminate now (no refund — refund separately via checkout sessions).
Events emitted — subscription.canceled.
Subscription lifecycle
[trialing] --trial_end--> [active] <----------> [paused]
| |
v v
[past_due] --grace--> [unpaid] --> [canceled]
|
+--retry_success--> [active]
The dunning flow:
- Renewal charge attempted at
currentPeriodEnd. On success:subscription.renewed+subscription.payment_succeeded. - On failure:
subscription.past_duefires; status moves topast_due. Retry schedule kicks in (default: T+1d, T+3d, T+7d). - Each retry attempt that fails emits
subscription.payment_failedwith the attempt count. - After the final retry, status moves to
unpaidand ultimatelycanceledif the merchant's dunning config calls it.
A successful retry returns the subscription to active and emits subscription.payment_succeeded.
The subscription object
| Field | Type | Nullable | Description |
|---|---|---|---|
id |
string | no | sub_ + 26-char ULID. |
accountId |
string | no | Workspace. |
customerId |
string | no | The buyer. |
planId |
string | no | The plan. |
status |
enum | no | active / trialing / past_due / paused / canceled / unpaid. |
currentPeriodStart / currentPeriodEnd |
string (ISO 8601) | no | Current billing window. |
trialEnd |
string (ISO 8601) | yes | When the trial ends. |
cancelAtPeriodEnd |
boolean | no | Set by DELETE (deferred cancel). |
canceledAt |
string (ISO 8601) | yes | When the cancel actually executed. |
pausedAt |
string (ISO 8601) | yes | When pause was set. |
failedPaymentCount |
integer | no | Consecutive failed-retry count. Reset to 0 on a successful charge. |
metadata |
object | no | Free-form. |
createdAt / updatedAt |
string | no | Timestamps. |
Events
| Event type | Fires on |
|---|---|
subscription.created |
POST /v1/payment/subscriptions succeeds. |
subscription.renewed |
Successful renewal charge. |
subscription.payment_succeeded |
Any successful charge (initial, renewal, retry). |
subscription.payment_failed |
Charge attempt failed. Includes attempt counter. |
subscription.past_due |
Status flipped to past_due. |
subscription.canceled |
Cancel executed (immediate or end-of-period). |
Trial-end notifications (subscription.trial_will_end) are scheduled in Plugipay's cron when the module is on; in legacy mode this isn't currently emitted.
subscription.trial_will_endis reserved but not emitted in legacy mode. Free-tier workspaces (no Plugipay module) don't fire a trial-ending warning. Subscribe defensively if you handle this in your app.