Skip to content

Build a catalog

Build the catalog once, in code or in the dashboard. From then on, your application references plans by code, not by ID, and the same code works in sandbox and live.

1. Decide the structure

For a typical SaaS:

Product: "Pro"
└── Plan: "pro-monthly-usd"
├── Component: "base" (flat $49)
├── Component: "seats" (per-unit $10/seat above 5 included)
└── Component: "calls" (usage-based, $0.001 per call)

You’ll usually have one product per tier (Starter / Pro / Business) and one plan per (tier × interval × currency):

  • pro-monthly-usd
  • pro-monthly-eur
  • pro-yearly-usd

2. Create the product

POST /v1/products
Idempotency-Key: <uuid>
{
"code": "pro",
"name": "Pro",
"description": "For growing teams.",
"tax_code": "txcd_10000000"
}

The code is your handle. Use slug-case; it’s what your code will reference forever.

3. Create the plan

POST /v1/plans
Idempotency-Key: <uuid>
{
"product_code": "pro",
"code": "pro-monthly-usd",
"currency": "USD",
"interval": "month",
"interval_count": 1,
"trial_days": 14,
"components": [
{ "code": "base", "pricing": { "model": "flat", "amount": "49.00" } },
{ "code": "seats", "pricing": { "model": "per_unit", "unit_amount": "10.00", "included_units": 5, "meter": "active_seats" } },
{ "code": "calls", "pricing": { "model": "usage", "meter": "api_calls", "aggregation": "sum", "tiers": [{ "up_to": null, "unit_amount": "0.001" }] } }
]
}

4. Add a yearly plan

POST /v1/plans
Idempotency-Key: <uuid>
{
"product_code": "pro",
"code": "pro-yearly-usd",
"currency": "USD",
"interval": "year",
"components": [
{ "code": "base", "pricing": { "model": "flat", "amount": "490.00" } },
{ "code": "seats", "pricing": { "model": "per_unit", "unit_amount": "100.00", "included_units": 5, "meter": "active_seats" } }
]
}

Note the absence of the usage component — annual plans typically meter usage on a separate, ungated component or fold it into the base price.

5. Verify

GET /v1/plans/pro-monthly-usd

Inspect the response. The components array, currency, interval, and trial_days should match exactly.

6. Promote to live

The catalog you build in sandbox doesn’t auto-sync to live. Two paths:

Manual recreation: rerun the same POST calls against the live base URL with a live API key.

Catalog export / import:

Terminal window
# Sandbox export
curl https://api.sandbox.paylera.io/v1/admin/catalog/export \
-H "Authorization: Bearer $SANDBOX_KEY" > catalog.json
# Live import
curl -X POST https://api.paylera.io/v1/admin/catalog/import \
-H "Authorization: Bearer $LIVE_KEY" \
-H "Content-Type: application/json" \
--data @catalog.json

Import is upsert by code: missing entries are created, existing entries are updated, nothing is deleted.

7. Lifecycle: editing, archiving

  • Edit an existing plan: prices and components can change. Existing subscriptions get the new prices on their next billing period (set effective_at to control timing).
  • Archive a plan: existing subscriptions keep running, but no new subscriptions can be created against it. Use this for grandfathered pricing.
  • Delete a plan: only allowed if no subscription has ever referenced it.
PATCH /v1/plans/pro-monthly-usd
{ "status": "archived" }

What you cannot change

  • currency of a plan
  • interval of a plan
  • code of a plan or product

If you need a different currency or interval, create a new plan and migrate.

Where to next