Payment Orchestrator
Subscription
Products

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).

MethodPathDoes
POST/api/v1/subscriptions/productsCreate a product
GET/api/v1/subscriptions/productsList (filter includeArchived, paginate pageSize / pageToken)
GET/api/v1/subscriptions/products/:idFetch one product
PATCH/api/v1/subscriptions/products/:idUpdate fields
DELETE/api/v1/subscriptions/products/:idArchive (soft delete)
GET/api/v1/subscriptions/products/:id/translationsList translations
PATCH/api/v1/subscriptions/products/:id/translations/:localeCreate 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

FieldTypeNotes
codestring, requiredYour stable identifier; used in integrations, unique per merchant.
namestring, requiredCustomer-facing name (base locale).
descriptionstringCustomer-facing description.
defaultPriceUpdatePolicyLOCKED | FOLLOW_PLANDefault 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).
defaultPriceChangeBehaviorIMMEDIATE | NEXT_PERIODWhen a followed price change takes effect: right away, or at the next billing period.
metadataobjectFree-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

StatusMeaningWhat to do
400Validation failed (missing code/name, bad enum value, malformed locale)Fix the flagged fields.
401Missing or invalid API keySend Authorization: Bearer at_live_….
404Product or translation locale not foundCheck the :id; list with includeArchived=true if it may be archived.
409Duplicate codeProduct codes are unique per merchant — pick another or reuse the existing product.

Webhooks

EventFired when
product.createdA product was created.
product.updatedFields or translations changed.
product.archivedThe product was archived via DELETE.

Next steps