Skip to main content
GET
Public plan catalog
Base URL: https://api.nozle.app/engine/api/v1. The plan catalog is the one part of the API a publishable pk_ key can call. Use it to build pricing pages and plan selectors. Auth: pk_ (publishable key) or sk_ (secret key). A pk_ key receives a trimmed plan; an sk_ key receives the whole plan.
A publishable key ships in browsers, so everything it receives is readable by anyone who has the key: prices, usage charges and their rates, taxes, minimum commitments, entitlements, credit grants and metadata. Keep negotiated or internal pricing out of the organization whose publishable key you ship. See Keep sensitive pricing out of the catalog.

What a publishable key can call

See Authentication for the full credential model. The Engine catalog answers CORS from any origin, so a browser on your own site can call it with the publishable key. GET /plans, GET /plans/{code} and their preflight return Access-Control-Allow-Origin: * and Access-Control-Allow-Methods: GET, OPTIONS, never Access-Control-Allow-Credentials, so do not send cookies. Every other Engine route keeps its origin allow-list. Engine forwards the request, with its query string, to Core and returns Core’s status code and body unchanged, including Core’s errors. If Engine cannot reach Core within 10 seconds, it returns 502 with {"code": "billing_unavailable", "error": "failed to reach billing service"}.

Which plans are returned

All top-level plans of the organization, newest first. There is no listed, public or draft flag, so trial plans, pay-in-arrears plans and free plans are all included. Per-customer override plans (plans with a parent_id) and deleted plans are not.
  • A pk_ key does not receive plans that are pending deletion, and families[].plan_codes leaves their codes out. GET /plans/{code} returns 404 with "code": "plan_not_found" for one.
  • An sk_ key receives them, with pending_deletion: true.
  • A pk_ key, or an sk_ key with include_hidden=false, does not receive plans whose plan family is hidden from the pricing table in the list. Hiding a family is a listing setting only: GET /plans/{code} still returns a plan of a hidden family, or of a deleted family, to a pk_ key, so the SDK can describe a subscriber’s own plan.
  • include_hidden (secret keys only): send include_hidden=false to get the same pricing table view as a pk_ key. Any other value, or none, returns every plan.
  • Every plan, listed or read by code, carries all its entitlements and charges, including those of features hidden from the pricing table. Only the pricing table rows (families[].features) leave hidden features out.
A plan created through POST /plans, the dashboard onboarding or the Agent Kit starts in a new plan family that is shown on the pricing table, so it is listed at once. Send show_on_pricing: false in plan (GraphQL showOnPricing: false) to create it hidden. A plan that joins an existing family with plan_family_code keeps that family’s visibility.

Plan families

A plan family groups the billing intervals of one offer, for example “Pro” monthly and “Pro” yearly. Every plan has plan_family_code, and the list returns a families array with the display settings (show_on_pricing, pricing_position, pricing_highlighted, badge_text, cta_text, plan_codes and the feature rows) for the families of the returned plans.
  • POST /plans and PUT /plans/{code} accept plan_family_code. A code that does not exist yet creates the family; without a code the plan gets its own family.
  • A family holds at most one plan per interval, all in one currency. Otherwise the call returns 422 with interval: plan_family_interval_already_exist or amount_currency: plan_family_currency_mismatch.
  • A plan that joins a family with other plans takes the family’s features and privileges.
  • POST, PATCH and DELETE /plans/{code}/entitlements (and .../privileges/{code}) change the features and privileges of every plan in the family. usage_limit and hard_limit apply only to the plan in the path, because usage is counted per billing period.

Pagination

Core accepts page and per_page (default and page size 100) and returns meta. Engine forwards page and per_page to Core, so both hosts page the same way.

Example

What a pk_ key receives. The example shows one entry of each array. Optional fields that this plan does not use are left out, and the charge shows only its main fields; every field is listed under Fields.

Fields

Identifiers are named nozle_id. Amounts named *_cents are integers in cents of the plan currency. Amounts inside a charge’s properties are decimal strings in currency units (for example "14.99" is 14.99 USD), not cents.

Plan

Only a secret key receives: invoice_display_name, parent_id, pending_deletion, created_at and applicable_usage_thresholds.

Charge (charges[])

Fixed charge (fixed_charges[])

Only in GET /plans/{code}. Fields: nozle_id, add_on_id, add_on_code, code, invoice_display_name, created_at, charge_model (standard, graduated or volume), pay_in_advance, prorated, properties, units, parent_id and taxes.

Minimum commitment (minimum_commitment)

Fields: nozle_id, plan_code, commitment_type (always minimum_commitment), invoice_display_name, amount_cents (per interval), interval, created_at, updated_at and taxes.

Usage threshold (usage_thresholds[])

Fields: nozle_id, threshold_display_name, amount_cents (usage amount in cents that triggers a progressive billing invoice), recurring (true repeats the invoice each time usage grows by amount_cents), created_at and updated_at.

Tax (taxes[])

Fields: nozle_id, name, code, description, rate (percentage, for example 20), applied_to_organization (deprecated), created_at. The count fields add_ons_count, customers_count, plans_count, charges_count and commitments_count are always 0.

Entitlement (entitlements[])

Only a secret key receives: feature_id, hard_limit (for metered features, true blocks usage over the limit), min_margin_percent, margin_enforcement_mode and enforce_plan_margin.

Credit grant rule (credit_grant_rules[])

Only a secret key receives: plan_id, credit_system_id, scope (customer or entity), entity_join_policy, entity_leave_policy, upgrade_policy, downgrade_policy, created_at and updated_at.

Credit pool policy (credit_pool_policies[])

Fields: nozle_id, plan_id, plan_code, credit_system_id, credit_system_code, policy (entity_only, entity_then_customer or customer_only; see pool policies), status (active), created_at and updated_at.

React, Node, Python and Go

The SDKs declare only five fields for each plan (code, name, amount_cents, amount_currency, interval), but the API sends the fields above. In TypeScript, read an undeclared field by widening the type yourself. See usePlans.

Filter the catalog on the client

Every plan is returned, so decide in your code which ones to show:
  • Decide what to do with plans where trial_period is not null: show them as trial plans or hide them.
  • Decide what to do with pay-in-arrears plans (pay_in_advance: false). Their base price is billed at the end of the period.
  • Skip the plan codes you never sell on a pricing page, such as internal or legacy plans. Keep that list in your application; the catalog has no flag for it.

Keep sensitive pricing out of the catalog

Hiding a plan in your UI does not hide it from the API. Anyone can copy the publishable key from your page and call the endpoint directly.
  • Do not put negotiated, customer-specific or internal pricing in plans of the organization whose pk_ key you ship. This covers charges, taxes, minimum commitments, credit grants and metadata.
  • Do not store secrets, internal notes or account details in a plan description or metadata.
  • If you need internal plans, keep them in a separate organization, so that its publishable key is never shipped.
  • If you want a pricing page that shows a smaller set of fields, call the catalog from your backend with an sk_ key, keep only the fields you want, and pass that list to PricingTable through its plans prop. The browser then never calls the catalog. The publishable key still works for anyone who has it.