Products
A product is the sellable unit at the root of your catalog — "Pro", "Enterprise", "Storage Pack". It has no price of its own; plans attach prices and billing cadences to it. What the product does own: your stable code, the customer-facing name and description, per-locale translations, and the default price-change policy inherited by subscriptions on its plans.
Endpoints
All endpoints are Bearer-authenticated (at_live_…) and list responses use cursor pagination (pageSize, pageToken).
| Method | Path | Does |
|---|---|---|
| POST | /api/v1/subscriptions/products | Create a product |
| GET | /api/v1/subscriptions/products | List (filter includeArchived, paginate pageSize / pageToken) |
| GET | /api/v1/subscriptions/products/:id | Fetch one product |
| PATCH | /api/v1/subscriptions/products/:id | Update fields |
| DELETE | /api/v1/subscriptions/products/:id | Archive (soft delete) |
| GET | /api/v1/subscriptions/products/:id/translations | List translations |
| PATCH | /api/v1/subscriptions/products/:id/translations/:locale | Create or update a translation |
Create a product
curl -X POST https://api-gateway.lunixi.com/api/v1/subscriptions/products \
-H "Authorization: Bearer at_live_5f3e2b1a..." \
-H "Content-Type: application/json" \
-d '{
"code": "pro",
"name": "Pro",
"description": "For growing teams",
"defaultPriceUpdatePolicy": "LOCKED",
"defaultPriceChangeBehavior": "NEXT_PERIOD",
"metadata": { "tier": 2 }
}'Fields
| Field | Type | Notes |
|---|---|---|
code | string, required | Your stable identifier; used in integrations, unique per merchant. |
name | string, required | Customer-facing name (base locale). |
description | string | Customer-facing description. |
defaultPriceUpdatePolicy | LOCKED | FOLLOW_PLAN | Default for subscriptions created on this product's plans: LOCKED pins the price the customer signed up at; FOLLOW_PLAN tracks later plan price changes. Overridable per subscription (priceUpdatePolicy). |
defaultPriceChangeBehavior | IMMEDIATE | NEXT_PERIOD | When a followed price change takes effect: right away, or at the next billing period. |
metadata | object | Free-form key/value store echoed back on reads. |
Think of the two policy fields as your grandfathering switch. LOCKED + NEXT_PERIOD is the conservative default posture: existing subscribers keep their price, and any change that does apply waits for the period boundary.
Update
PATCH /api/v1/subscriptions/products/:id accepts any subset of name, description, defaultPriceUpdatePolicy, defaultPriceChangeBehavior, isActive, metadata, plus an optional reason recorded in the audit trail.
curl -X PATCH https://api-gateway.lunixi.com/api/v1/subscriptions/products/prd_4b2e88 \
-H "Authorization: Bearer at_live_5f3e2b1a..." \
-H "Content-Type: application/json" \
-d '{ "name": "Pro Plus", "reason": "rebrand Q3" }'Archive — DELETE is soft
DELETE never destroys data. It archives the product: it disappears from the public catalog and from default listings, while existing plans and subscriptions keep working and history stays intact.
curl -X DELETE https://api-gateway.lunixi.com/api/v1/subscriptions/products/prd_4b2e88 \
-H "Authorization: Bearer at_live_5f3e2b1a..." \
-H "Content-Type: application/json" \
-d '{ "reason": "discontinued" }'Archived products stay reachable: GET /api/v1/subscriptions/products?includeArchived=true.
Translations — a multi-language catalog
Every product carries per-locale overrides for name and description. The Public Catalog API and the portal serve them automatically when the caller passes a locale — one catalog, every language your pricing page speaks.
curl -X PATCH https://api-gateway.lunixi.com/api/v1/subscriptions/products/prd_4b2e88/translations/tr \
-H "Authorization: Bearer at_live_5f3e2b1a..." \
-H "Content-Type: application/json" \
-d '{
"name": "Pro",
"description": "Büyüyen ekipler için"
}'The :locale segment accepts BCP-47-style tags (tr, en, en-US, de-DE). PATCH is an upsert — the same call creates a missing locale or updates an existing one; GET …/translations lists everything defined.
Response
{
"id": "prd_4b2e88",
"code": "pro",
"name": "Pro",
"description": "For growing teams",
"defaultPriceUpdatePolicy": "LOCKED",
"defaultPriceChangeBehavior": "NEXT_PERIOD",
"isActive": true,
"metadata": { "tier": 2 },
"createdAt": "2026-07-06T09:30:00Z"
}List responses return items plus a nextPageToken — pass it back as pageToken to fetch the next page (max pageSize is 500).
Errors
| Status | Meaning | What to do |
|---|---|---|
400 | Validation failed (missing code/name, bad enum value, malformed locale) | Fix the flagged fields. |
401 | Missing or invalid API key | Send Authorization: Bearer at_live_…. |
404 | Product or translation locale not found | Check the :id; list with includeArchived=true if it may be archived. |
409 | Duplicate code | Product codes are unique per merchant — pick another or reuse the existing product. |
Webhooks
| Event | Fired when |
|---|---|
product.created | A product was created. |
product.updated | Fields or translations changed. |
product.archived | The product was archived via DELETE. |
Next steps
- Plans — attach prices and billing cadences to this product
- Pricing & Portal SDK — serve the (translated) catalog to your own site
- Getting Started — the full product → subscription chain