Plans
A plan turns a product into something a customer can subscribe to: a price, a currency, an optional trial, and a billing rule that tells the engine when to generate each invoice. The plan also owns the failure story — how many times a failed charge is retried (maxDunningAttempts), on which days (dunningScheduleDays), and how long the customer keeps access while payment is being chased (gracePeriodDays).
price is a minor-unit integer (kuruş): "price": 49900 = ₺499.00.
Endpoints
All Bearer-authenticated (at_live_…); lists use cursor pagination (pageSize, pageToken, filter by productId / includeArchived).
| Method | Path | Does |
|---|---|---|
| POST | /api/v1/subscriptions/plans | Create a plan |
| GET | /api/v1/subscriptions/plans | List (filter productId, includeArchived) |
| GET | /api/v1/subscriptions/plans/:id | Fetch one plan |
| PATCH | /api/v1/subscriptions/plans/:id | Update |
| DELETE | /api/v1/subscriptions/plans/:id | Archive (soft delete) |
| GET | /api/v1/subscriptions/plans/:id/translations | List translations |
| PATCH | /api/v1/subscriptions/plans/:id/translations/:locale | Create or update a translation |
Create a plan
curl -X POST https://api-gateway.lunixi.com/api/v1/subscriptions/plans \
-H "Authorization: Bearer at_live_5f3e2b1a..." \
-H "Content-Type: application/json" \
-d '{
"productId": "prd_4b2e88",
"code": "pro-monthly",
"name": "Pro Monthly",
"price": 49900,
"currency": "TRY",
"trialPeriodDays": 7,
"billing": {
"frequency": "MONTHLY",
"interval": 1,
"byMonthDay": [15],
"timezone": "Europe/Istanbul",
"lastDayOfMonthFix": true
},
"maxDunningAttempts": 3,
"dunningScheduleDays": [1, 3, 7],
"gracePeriodDays": 7
}'Fields
| Field | Type | Notes |
|---|---|---|
productId | string, required | The product this plan prices. |
code | string, required | Your stable identifier (e.g. pro-monthly), unique per merchant. |
name | string, required | Customer-facing name (base locale). |
description | string | Customer-facing description. |
price | integer, required | Minor units. 49900 = ₺499.00. |
currency | string, required | ISO code, e.g. TRY. |
trialPeriodDays | integer ≥ 0 | Free trial length; the subscription starts in trial and converts automatically. 0/omitted = no trial. |
billing | object, required | The billing rule — see below. |
maxDunningAttempts | integer 0–10 | How many retries after a failed charge before the invoice is given up on. |
dunningScheduleDays | integer[] | Days after the failure on which retries run — [1, 3, 7] retries on day 1, 3 and 7. |
gracePeriodDays | integer ≥ 0 | How long the subscription retains access while dunning is in progress. |
metadata | object | Free-form key/value store. |
The billing rule
The billing object is a recurrence rule — the same building blocks as an RRULE:
| Field | Type | Meaning |
|---|---|---|
frequency | DAILY | WEEKLY | MONTHLY | YEARLY, required | Base cadence. |
interval | integer 1–365, required | Every N frequency units — frequency: MONTHLY, interval: 3 bills quarterly. |
byMonthDay | integer[] | Pin billing to specific days of the month, e.g. [15]. |
byWeekDay | string[] | For weekly cadences: ["MO", "WE", "FR"]. |
timezone | string, required | IANA zone the rule is evaluated in, e.g. Europe/Istanbul. Billing "on the 15th" means the 15th in this zone. |
lastDayOfMonthFix | boolean, required | With true, a cycle anchored on the 31st bills on the last day of shorter months instead of skipping them. |
count | integer ≥ 1 | Total number of occurrences; omit (null) for an open-ended subscription. |
Recipes:
{ "frequency": "MONTHLY", "interval": 1, "timezone": "Europe/Istanbul", "lastDayOfMonthFix": true } // classic monthly
{ "frequency": "MONTHLY", "interval": 3, "byMonthDay": [1], "timezone": "Europe/Istanbul", "lastDayOfMonthFix": true } // quarterly, on the 1st
{ "frequency": "WEEKLY", "interval": 1, "byWeekDay": ["MO"], "timezone": "Europe/Istanbul", "lastDayOfMonthFix": false } // every Monday
{ "frequency": "YEARLY", "interval": 1, "timezone": "Europe/Istanbul", "lastDayOfMonthFix": true, "count": 3 } // 3 annual charges, then doneUpdate & archive
PATCH /api/v1/subscriptions/plans/:id updates name, description, price, trialPeriodDays, billing, the dunning fields and metadata (plus an audit reason). Whether and when a price update reaches existing subscribers is governed by the product/subscription price policy (LOCKED / FOLLOW_PLAN, IMMEDIATE / NEXT_PERIOD) — see Products.
DELETE /api/v1/subscriptions/plans/:id archives the plan: it can no longer be subscribed to and leaves the public catalog, but existing subscriptions on it keep billing normally.
curl -X DELETE https://api-gateway.lunixi.com/api/v1/subscriptions/plans/pln_9f3a1c \
-H "Authorization: Bearer at_live_5f3e2b1a..." \
-H "Content-Type: application/json" \
-d '{ "reason": "superseded by pro-monthly-v2" }'Translations & display text
Plan name / description localize exactly like products — PATCH …/plans/:id/translations/:locale with { "name": …, "description": … }, served automatically wherever a locale is passed (public catalog, portal).
The marketing bullet lines under a plan on your pricing page — "500 transactions / month", "Priority support" — are the plan's display features: ordered, per-locale display texts managed from the merchant panel and returned with the plan in the Public Catalog API. They are presentation only; enforceable quotas live in Features & entitlements.
Response
{
"id": "pln_9f3a1c",
"productId": "prd_4b2e88",
"code": "pro-monthly",
"name": "Pro Monthly",
"price": 49900,
"currency": "TRY",
"trialPeriodDays": 7,
"billing": {
"frequency": "MONTHLY",
"interval": 1,
"byMonthDay": [15],
"timezone": "Europe/Istanbul",
"lastDayOfMonthFix": true
},
"maxDunningAttempts": 3,
"dunningScheduleDays": [1, 3, 7],
"gracePeriodDays": 7,
"isActive": true,
"createdAt": "2026-07-06T09:31:00Z"
}Switching plans
Moving an existing subscriber to another plan is an operation on the subscription, not the plan: POST /api/v1/subscriptions/subscriptions/:id/change-plan with behavior: "IMMEDIATE" (switch and settle now) or "NEXT_PERIOD" (schedule for the period boundary). Upgrades, downgrades and the events they emit are covered in Subscriptions.
Errors
| Status | Meaning | What to do |
|---|---|---|
400 | Validation failed (negative price, interval out of 1–365, maxDunningAttempts > 10, empty dunningScheduleDays) | Fix the flagged fields. |
401 | Missing or invalid API key | Send Authorization: Bearer at_live_…. |
404 | productId or plan not found | Create the product first; check the id. |
409 | Duplicate plan code | Codes are unique per merchant. |
Webhooks
| Event | Fired when |
|---|---|
plan.created | A plan was created. |
plan.updated | Fields, price or translations changed. |
plan.archived | The plan was archived via DELETE. |
Next steps
- Subscriptions — subscribe customers, cancel, pause, change plan
- Products — price-change policy and catalog translations
- Pricing & Portal SDK — render these plans on your own pricing page
- Invoices — what the billing rule produces each period