A plan is a reusable bundle of products and prices that a subscription line can take its values from.
Versions. Edits land in the plan's working draft; they never change what subscribers are billed. A draft goes live as a new version through POST /changes with operation: catalog.publish_version — now or at a scheduled time. scheduled_versions lists versions waiting to publish, GET /plans/{id}/history lists the published ones.
Subscribers. Each subscription line either follows its plan (moves to new versions, by its version_change strategy) or is pinned to the version it was sold on.
List plans
Returns a page of plans with optional status and custom field filters.
Required scope: plan.read (Read plans).
query Parameters
created_atFilter on created_at (date-time). Operators: eq, gt, gte, lt, lte — dot grammar, e.g. created_at.gt=value; a bare created_at=value means eq.
external_idFilter on external_id (string). Operators: eq, in — dot grammar, e.g. external_id.in=value; a bare external_id=value means eq. A bare comma-separated value is in-sugar: external_id=a,b means external_id.in=a,b.
nameFilter on name (string). Operators: eq, in, contains — dot grammar, e.g. name.in=value; a bare name=value means eq. A bare comma-separated value is in-sugar: name=a,b means name.in=a,b.
published_atFilter on published_at (date-time). Operators: eq, gt, gte, lt, lte — dot grammar, e.g. published_at.gt=value; a bare published_at=value means eq.
updated_atFilter on updated_at (date-time). Operators: eq, gt, gte, lt, lte — dot grammar, e.g. updated_at.gt=value; a bare updated_at=value means eq.
versionFilter on version (number). Operators: eq, gt, gte, lt, lte — dot grammar, e.g. version.gt=value; a bare version=value means eq.
includeExtra data to add to the response: tags, scheduled_versions, products, products.product, unpublished_removals. Send a comma-separated list or repeat the parameter. An unknown name is refused with 400. tags is sent without being asked until 2026-11-30; after that date it is sent only when named here. scheduled_versions is sent without being asked until 2026-11-30; after that date it is sent only when named here.
limitPage size. Values above the server-side cap are clamped (standard default 50, cap 200; a few document-heavy lists use larger windows). Invalid values fall back to the default.
cursorOpaque continuation token from the previous response's pagination.cursor. Omit for the first page. Cursors are stateless and do not expire, but are only valid for the list and filters that produced them.
searchCase-insensitive substring match over name and external_id; relevance-ranked (exact > prefix > substring) unless an explicit sort is given
statusFilter by status. Filterable fields (status, name, external_id, version, published_at, created_at, updated_at) accept apifilter operator suffixes; custom_fields.
tagFilter by tag name (repeatable, containment semantics)
countsComma-separated countable fields (status) to include per-value counts for
has_scheduled_versionFilter by whether the entity has a version scheduled to publish (true or false). A scheduled version is a FROZEN version row a pending catalog.publish_version Change is bound to; the plan itself stays draft, active or archived and its working draft stays free for immediate changes.
sortOrdering: created_at, name, status or description, each with an optional :asc/:desc suffix (default created_at:desc). Plans with no description sort last in both directions. version is deliberately not sortable - the list renders the joined catalog_version_headers.version, and a joined column cannot be keyset-ordered. An unknown value is rejected. Cursors are bound to the ordering that issued them, so changing sort mid-walk needs a fresh first page. Sorting suppresses relevance ranking when search is also given.
Headers
X-Workspace-IdSelects the workspace this request operates in, by workspace UUID or slug (e.g. a sandbox workspace for test integrations). Omitted: the organization's default live workspace. Unknown workspace: 404; workspace outside your organization: 403. Discover workspaces via GET /workspaces.
Workspace UUID or slug.
List plans › Responses
OK
Create a plan
Creates a plan in draft status. It answers the plan as GET /plans answers it.
Required scope: plan.write (Write plans).
query Parameters
includeExtra data to add to the response: tags, scheduled_versions, products, products.product, unpublished_removals. Send a comma-separated list or repeat the parameter. An unknown name is refused with 400. tags is sent without being asked until 2026-11-30; after that date it is sent only when named here. scheduled_versions is sent without being asked until 2026-11-30; after that date it is sent only when named here.
Headers
Idempotency-KeyUnique key that makes this POST safe to retry: repeats with the same key replay the first response instead of re-executing. Replays are scoped to the retrying principal (same API key / user) and kept for 24 hours. Required on every POST.
Client-generated idempotency key (e.g. a UUID).
X-Workspace-IdSelects the workspace this request operates in, by workspace UUID or slug (e.g. a sandbox workspace for test integrations). Omitted: the organization's default live workspace. Unknown workspace: 404; workspace outside your organization: 403. Discover workspaces via GET /workspaces.
Workspace UUID or slug.
Create a plan › Request Body
nameInternal operator-facing plan name.
Where the billing periods of a subscription on this plan fall, overriding the organization's default: start, day_of_month with its day (1-28), or last_day. Omit to state none; the organization's default then applies.
Org-defined custom field values for this plan.
default_early_stopWhat a stop inside a billing period paid in advance does with the unused part for a deal on this plan: no_credit or credit_unused. Omit to state none; no_credit then applies.
default_timingWhen a line of this plan is invoiced if the deal states nothing for it: in_advance or in_arrears. Omit to state none; such a line is then invoiced in arrears.
default_version_changeWhen a following subscription line of this plan moves to a newer version if the deal states nothing for it: immediate_prorate, next_period or at_phase_change. Omit to state none; the subscription's default, else next_period, then applies.
descriptionInternal operator-facing description; not shown to buyers.
external_idCaller-supplied external identifier for reconciliation with an upstream system.
Free-form operator key/value metadata.
price_cadence_unitThe billing period this plan's prices are authored for (week, month or year); defaults to month. Subscriptions, quotes and checkouts against this plan must bill on exactly this cadence.
public_descriptionBuyer-facing description shown in checkout and the customer portal.
Locale-keyed overrides for name, description and public_description.
Create a plan › Responses
Created
Get a plan
Returns one plan under data.plan, the same Plan every other plan operation answers. From 2026-11-30 data is the Plan itself, with no plan wrapper: the products move to include=products (data.plan.products now) and the pending removals to include=unpublished_removals.
Required scope: plan.read (Read plans).
path Parameters
idPlan UUID
query Parameters
includeExtra data to add to the response: tags, scheduled_versions, products, products.product, unpublished_removals. Send a comma-separated list or repeat the parameter. An unknown name is refused with 400. tags is sent without being asked until 2026-11-30; after that date it is sent only when named here. scheduled_versions is sent without being asked until 2026-11-30; after that date it is sent only when named here.
Headers
X-Workspace-IdSelects the workspace this request operates in, by workspace UUID or slug (e.g. a sandbox workspace for test integrations). Omitted: the organization's default live workspace. Unknown workspace: 404; workspace outside your organization: 403. Discover workspaces via GET /workspaces.
Workspace UUID or slug.
Get a plan › Responses
OK
Delete a plan
Deletes a plan: from then on every read of it answers 404. To retire a plan and keep it readable, archive it with PATCH /plans/{id} {"status": "archived"}.
Required scope: plan.write (Write plans).
path Parameters
idPlan UUID
Headers
X-Workspace-IdSelects the workspace this request operates in, by workspace UUID or slug (e.g. a sandbox workspace for test integrations). Omitted: the organization's default live workspace. Unknown workspace: 404; workspace outside your organization: 403. Discover workspaces via GET /workspaces.
Workspace UUID or slug.
Delete a plan › Responses
No Content
Update a plan
Merge-patch update: only supplied fields change, and none of them publish. Display fields (name, description, price cadence, metadata, …) land live on the plan immediately; the composition — attached products, their terms and prices — is edited through the plan's own sub-routes and stages on the plan's draft head, which you can read with GET /v1/plans/{id}/draft and discard with DELETE. Taking the draft live, now or on a date, is POST /v1/changes with operation catalog.publish_version and subject_kind=plan, whose pins hold named products at a chosen version. status="active" here is refused, and there is no effective_at, save_as_draft, publish_now or product_version_pins. status can still archive the plan, which is terminal. expected_version is the optimistic-concurrency token: when supplied and stale, the update returns 409.
Required scope: plan.write (Write plans).
path Parameters
idPlan UUID
query Parameters
includeExtra data to add to the response: tags, scheduled_versions, products, products.product, unpublished_removals. Send a comma-separated list or repeat the parameter. An unknown name is refused with 400. tags is sent without being asked until 2026-11-30; after that date it is sent only when named here. scheduled_versions is sent without being asked until 2026-11-30; after that date it is sent only when named here.
Headers
X-Workspace-IdSelects the workspace this request operates in, by workspace UUID or slug (e.g. a sandbox workspace for test integrations). Omitted: the organization's default live workspace. Unknown workspace: 404; workspace outside your organization: 403. Discover workspaces via GET /workspaces.
Workspace UUID or slug.
Update a plan › Request Body
billing_alignmentThe alignment override of the plan's next version: start, day_of_month with its day (1-28), or last_day. Absent leaves it unchanged; null clears it, so the organization's default applies. It goes live with the next publish; a subscription already published keeps the alignment it copied.
Org-defined custom field values for this plan.
default_early_stopThe early-stop term of the plan's next version: no_credit or credit_unused. Absent leaves it unchanged; null clears it, so no_credit applies. It goes live with the next publish.
default_timingThe default timing of the plan's next version: in_advance or in_arrears. Absent leaves it unchanged; null clears it, so such a line is invoiced in arrears. It goes live with the next publish.
default_version_changeThe default version change of the plan's next version: immediate_prorate, next_period or at_phase_change. Absent leaves it unchanged; null clears it, so the subscription's default, else next_period, applies. It goes live with the next publish.
descriptionNew internal description; null leaves it unchanged.
expected_versionOptimistic-concurrency token: send the version you last read and a stale value answers 409 CONFLICT. Omitted: the update applies unconditionally (last write wins).
external_idNew external identifier; null leaves it unchanged.
Free-form operator key/value metadata.
nameNew internal plan name; null leaves it unchanged.
price_cadence_unitNew price cadence (week, month or year); null leaves it unchanged. Changing it re-scopes which billing intervals existing and future subscriptions may use — it does NOT restate any price amount.
public_descriptionNew buyer-facing description; null leaves it unchanged.
statusLifecycle move: archived retires the plan (terminal); null leaves it unchanged. Going live, now or at a date, is not a status write: it is POST /v1/changes with operation catalog.publish_version.
Locale-keyed overrides for name, description and public_description.
Update a plan › Responses
OK
Copy a plan
Creates a new draft plan copying the source plan's name (with " (Copy)" suffix unless overridden), description, metadata, every plan_products row, and the price overrides it offers now (its working draft's, else the published version shown), as overrides of its own in the copy's draft. Versions are not copied: the new plan starts in status draft with no published version. external_id is not copied. It answers the plan as GET /plans answers it.
Required scope: plan.write (Write plans).
path Parameters
idSource plan UUID
query Parameters
includeExtra data to add to the response: tags, scheduled_versions, products, products.product, unpublished_removals. Send a comma-separated list or repeat the parameter. An unknown name is refused with 400. tags is sent without being asked until 2026-11-30; after that date it is sent only when named here. scheduled_versions is sent without being asked until 2026-11-30; after that date it is sent only when named here.
Headers
Idempotency-KeyUnique key that makes this POST safe to retry: repeats with the same key replay the first response instead of re-executing. Replays are scoped to the retrying principal (same API key / user) and kept for 24 hours. Required on every POST.
Client-generated idempotency key (e.g. a UUID).
X-Workspace-IdSelects the workspace this request operates in, by workspace UUID or slug (e.g. a sandbox workspace for test integrations). Omitted: the organization's default live workspace. Unknown workspace: 404; workspace outside your organization: 403. Discover workspaces via GET /workspaces.
Workspace UUID or slug.
Copy a plan › Responses
Created
Get the open working draft
The entity's ONE working draft: its row id, the version it publishes as, when it was opened, the version it is authored on top of, and a counted summary of what is staged. data is null when nothing is staged — a state, not an error.
Required scope: plan.read (Read plans).
path Parameters
idEntity UUID
Headers
X-Workspace-IdSelects the workspace this request operates in, by workspace UUID or slug (e.g. a sandbox workspace for test integrations). Omitted: the organization's default live workspace. Unknown workspace: 404; workspace outside your organization: 403. Discover workspaces via GET /workspaces.
Workspace UUID or slug.
Get the open working draft › Responses
OK
Open the working draft on a chosen base
Opens the working draft if none is open, and records which version it is authored on top of: the live head (omit base_version_id) or one of the entity's scheduled versions. Re-basing an open draft keeps its edits — the delta is the author's intent and is never rewritten — and re-materialises them over the new base. A publish of a draft based on a scheduled version cannot be scheduled to fire before that version does.
Required scope: plan.write (Write plans).
path Parameters
idEntity UUID
Headers
Idempotency-KeyUnique key that makes this POST safe to retry: repeats with the same key replay the first response instead of re-executing. Replays are scoped to the retrying principal (same API key / user) and kept for 24 hours. Required on every POST.
Client-generated idempotency key (e.g. a UUID).
X-Workspace-IdSelects the workspace this request operates in, by workspace UUID or slug (e.g. a sandbox workspace for test integrations). Omitted: the organization's default live workspace. Unknown workspace: 404; workspace outside your organization: 403. Discover workspaces via GET /workspaces.
Workspace UUID or slug.
Open the working draft on a chosen base › Request Body
base_version_idThe version the working draft is authored on top of — the live head, or one of the entity's scheduled versions. Omit for the head.
Open the working draft on a chosen base › Responses
OK
Discard the open working draft
Archives the working draft with reason 'discarded'. The live entity is untouched: nothing pointed at the draft and it was never on the billing timeline. 404 when nothing is staged, which is a different answer from a successful discard and is reported as one.
Required scope: plan.write (Write plans).
path Parameters
idEntity UUID
Headers
X-Workspace-IdSelects the workspace this request operates in, by workspace UUID or slug (e.g. a sandbox workspace for test integrations). Omitted: the organization's default live workspace. Unknown workspace: 404; workspace outside your organization: 403. Discover workspaces via GET /workspaces.
Workspace UUID or slug.
Discard the open working draft › Responses
No Content
List a plan's version history
Deprecated: use GET /plans/{id}/versions?status=published,scheduled&sort=version:desc&include=diff instead. Removed on 2026-11-30.
The plan's published and scheduled versions, newest first (default 20, max 100 per page), each with its publisher, release label, when it took or takes effect, when it was recorded, and its readable diff against the version it replaced. The immediate acts (create, copy, archive, label, discard) are not versions and are not listed here; they are audit rows: GET /audit-events?plan_id=. Replaced by GET /plans/{id}/versions?status=published,scheduled&sort=version:desc&include=diff, which answers the same versions with the diff on each.
Required scope: plan.read (Read plans).
path Parameters
idEntity UUID
query Parameters
limitPage size (default 20, max 100)
cursorOpaque continuation token from the previous response's pagination.cursor. Omit for the first page. Cursors are stateless and do not expire, but are only valid for the list and filters that produced them.
Headers
X-Workspace-IdSelects the workspace this request operates in, by workspace UUID or slug (e.g. a sandbox workspace for test integrations). Omitted: the organization's default live workspace. Unknown workspace: 404; workspace outside your organization: 403. Discover workspaces via GET /workspaces.
Workspace UUID or slug.
List a plan's version history › Responses
OK
Add a product to a plan
Adds a product to the plan's working membership together with its allowance configuration (included quantity, rollover, refresh cadence, REFRESHING/POOL kind, limits). No product version is pinned at attach time: the membership's product_track_mode (defaulting to the org setting, else 'latest') decides at each plan publish whether the snapshot follows the product head or holds a pinned version. The membership reaches subscribers on the plan's next publish. It answers the membership as include=products lists it; include=product adds the product.
Required scope: plan.write (Write plans).
path Parameters
idPlan UUID
query Parameters
includeExtra data to add to the response: product. Send a comma-separated list or repeat the parameter. An unknown name is refused with 400.
Headers
Idempotency-KeyUnique key that makes this POST safe to retry: repeats with the same key replay the first response instead of re-executing. Replays are scoped to the retrying principal (same API key / user) and kept for 24 hours. Required on every POST.
Client-generated idempotency key (e.g. a UUID).
X-Workspace-IdSelects the workspace this request operates in, by workspace UUID or slug (e.g. a sandbox workspace for test integrations). Omitted: the organization's default live workspace. Unknown workspace: 404; workspace outside your organization: 403. Discover workspaces via GET /workspaces.
Workspace UUID or slug.
Add a product to a plan › Request Body
product_idProduct to attach to the plan.
cadenceHow often a subscription sold from the plan bills this line: every_period (the default when omitted) or once, on the first paid invoice.
default_quantityQuantity a new subscription seeds for this product when the caller passes no explicit item; omit to seed 1.
included_quantity^-?\d+(\.\d+)?$Quantity bundled into the base price before per-unit billing applies (the included allowance/seat floor); null means none.
max_quantityHighest quantity a buyer may pick for this product; omit for no plan-level ceiling.
min_quantityLowest quantity a buyer may pick for this product; omit for 1.
product_track_modeHow this membership tracks the product version: latest (auto-follow head) or pinned; omitted seeds from the org default.
refresh_countPlan-level default allowance-refresh cadence count; null (with refresh_unit) means one whole-period pool.
refresh_unitUnit for the refresh cadence: day, week, month, year or billing_cycle.
rollover_enabledWhen true, unused included allowance carries forward into later periods.
rollover_expiry_periodsNumber of periods after which rolled-over allowance expires; null means it never expires.
rollover_max^-?\d+(\.\d+)?$Cap on accumulated rolled-over allowance; null means uncapped.
unit_limit^-?\d+(\.\d+)?$Ceiling on a POOL's unit balance; POOL only.
Add a product to a plan › Responses
Created
Remove a product from a plan
Deletes the plan-product membership row. Already-published plan versions keep their snapshot — the removal takes effect on the plan's next publish; until then a plan read with include=unpublished_removals lists it. Responds 404 when the product is not on the plan.
Required scope: plan.write (Write plans).
path Parameters
idPlan UUID
productIdProduct UUID
Headers
X-Workspace-IdSelects the workspace this request operates in, by workspace UUID or slug (e.g. a sandbox workspace for test integrations). Omitted: the organization's default live workspace. Unknown workspace: 404; workspace outside your organization: 403. Discover workspaces via GET /workspaces.
Workspace UUID or slug.
Remove a product from a plan › Responses
No Content
Update a plan-product membership
Updates a product's membership on a plan: the version track mode, the purchasable quantity band, and what the price includes. Band and allowance are each opt-in (quantity_band_set / allowance_set) and rewritten whole. Terms edit the membership's DRAFT revision and reach customers at the next publish; the track mode applies immediately. It answers the membership as include=products lists it; include=product adds the product.
Required scope: plan.write (Write plans).
path Parameters
idPlan UUID
productIdProduct UUID
query Parameters
includeExtra data to add to the response: product. Send a comma-separated list or repeat the parameter. An unknown name is refused with 400.
Headers
X-Workspace-IdSelects the workspace this request operates in, by workspace UUID or slug (e.g. a sandbox workspace for test integrations). Omitted: the organization's default live workspace. Unknown workspace: 404; workspace outside your organization: 403. Discover workspaces via GET /workspaces.
Workspace UUID or slug.
Update a plan-product membership › Request Body
allowance_setSet true to rewrite the included allowance from the seven fields below; false leaves the stored allowance untouched.
cadenceNew charge for the line: every_period or once, on the first paid invoice. Omit to leave it unchanged.
default_quantityQuantity a new subscription seeds for this product; null clears it (seeds 1). Only applied when quantity_band_set is true.
included_quantity^-?\d+(\.\d+)?$Quantity bundled into the base price before per-unit billing applies; null clears it. Only applied when allowance_set is true.
max_quantityHighest quantity a buyer may pick; null clears it (no plan-level ceiling). Only applied when quantity_band_set is true.
min_quantityLowest quantity a buyer may pick; null clears it (1). Only applied when quantity_band_set is true.
product_track_modeNew version-tracking mode for the membership: latest (auto-follow the product head) or pinned.
quantity_band_setSet true to rewrite the quantity band from the three fields below; false leaves the stored band untouched.
refresh_countHow often the included quantity comes back, paired with refresh_unit; null (with refresh_unit) means one whole-period pool. Only applied when allowance_set is true.
refresh_unitUnit for the refresh cadence: day, week, month, year, billing_cycle, or never (granted once). Only applied when allowance_set is true.
rollover_enabledWhen true, unused included allowance carries forward. Only applied when allowance_set is true.
rollover_expiry_periodsPeriods after which carried-forward allowance expires; null means it never expires. Only applied when allowance_set is true.
rollover_max^-?\d+(\.\d+)?$Cap on the accumulated carried-forward bank; null means uncapped, 0 means nothing carries. Only applied when allowance_set is true.
unit_limit^-?\d+(\.\d+)?$Lifetime ceiling on units drawn; only valid beside a never-refreshing included quantity. Only applied when allowance_set is true.
Update a plan-product membership › Responses
OK
Preview the impact of publishing a plan
Read-only preview of a publish now: how many active subscriptions on this plan follow the latest version and would move, how many hold a version pin and would not, and the split of the movers by version change strategy (when the new prices bill). blockers holds the refusal a publish now would hit, from the publish itself run and rolled back, with a message that names the remedy; it is empty when a publish would succeed.
Required scope: plan.read (Read plans).
path Parameters
idPlan UUID
Headers
X-Workspace-IdSelects the workspace this request operates in, by workspace UUID or slug (e.g. a sandbox workspace for test integrations). Omitted: the organization's default live workspace. Unknown workspace: 404; workspace outside your organization: 403. Discover workspaces via GET /workspaces.
Workspace UUID or slug.
Preview the impact of publishing a plan › Responses
OK
Check whether publishing a plan would succeed
Deprecated: use GET /plans/{id}/publish-impact instead. Removed on 2026-11-30.
Read-only check of the publish preconditions: runs the real publish inside a transaction and rolls it back. Returns ok=true, or the refusal code and a message that names the remedy. Replaced by blockers of GET /plans/{id}/publish-impact (the same answer).
Required scope: plan.read (Read plans).
path Parameters
idPlan UUID
Headers
X-Workspace-IdSelects the workspace this request operates in, by workspace UUID or slug (e.g. a sandbox workspace for test integrations). Omitted: the organization's default live workspace. Unknown workspace: 404; workspace outside your organization: 403. Discover workspaces via GET /workspaces.
Workspace UUID or slug.
Check whether publishing a plan would succeed › Responses
OK
Add tags to a plan
Adds the given tags to the plan and keeps the tags it already has. Returns all tags on the plan after the change. The plan itself lists them in tag_ids, and in tags with include=tags.
Required scope: tag.write (Manage tags).
path Parameters
idResource UUID
query Parameters
includeExtra data to add to the response: usage_count. Send a comma-separated list or repeat the parameter. An unknown name is refused with 400.
Headers
Idempotency-KeyUnique key that makes this POST safe to retry: repeats with the same key replay the first response instead of re-executing. Replays are scoped to the retrying principal (same API key / user) and kept for 24 hours. Required on every POST.
Client-generated idempotency key (e.g. a UUID).
X-Workspace-IdSelects the workspace this request operates in, by workspace UUID or slug (e.g. a sandbox workspace for test integrations). Omitted: the organization's default live workspace. Unknown workspace: 404; workspace outside your organization: 403. Discover workspaces via GET /workspaces.
Workspace UUID or slug.
Add tags to a plan › Request Body
tag_idsThe tags to add. Each must be a tag for this kind of record.
Add tags to a plan › Responses
OK
Remove tags from a plan
Removes the tags given in the tag_ids query parameter from the plan in one call. Its other tags stay; an id it does not carry is ignored.
Required scope: tag.write (Manage tags).
path Parameters
idResource UUID
query Parameters
tag_idsTag UUIDs to remove
Headers
X-Workspace-IdSelects the workspace this request operates in, by workspace UUID or slug (e.g. a sandbox workspace for test integrations). Omitted: the organization's default live workspace. Unknown workspace: 404; workspace outside your organization: 403. Discover workspaces via GET /workspaces.
Workspace UUID or slug.
Remove tags from a plan › Responses
No Content
List a plan's version timeline
The entity's versions in the order they fired and will fire: history, head, scheduled releases, working draft. Published and scheduled rows are returned by default; pass include=all for the draft and the archived rows. Same row shape as /versions.
Required scope: plan.read (Read plans).
path Parameters
idEntity UUID
query Parameters
includeSet to 'all' to include the working draft and archived rows.
limitPage size (default 100, max 200)
cursorOpaque continuation token from the previous response's pagination.cursor. Omit for the first page. Cursors are stateless and do not expire, but are only valid for the list and filters that produced them.
Headers
X-Workspace-IdSelects the workspace this request operates in, by workspace UUID or slug (e.g. a sandbox workspace for test integrations). Omitted: the organization's default live workspace. Unknown workspace: 404; workspace outside your organization: 403. Discover workspaces via GET /workspaces.
Workspace UUID or slug.
List a plan's version timeline › Responses
OK
List plan versions
Returns version rows for the entity, keyset-paginated (default 100, max 200 per page). The status filter accepts draft, scheduled, published, archived; the default excludes draft. include=usage_count adds how many records use each version; sort=version:desc lists the newest first. include=diff adds each version's readable diff against the version it replaced, and when its publish was recorded.
Required scope: plan.read (Read plans).
path Parameters
idEntity UUID
query Parameters
includeExtra data to add to the response: usage_count, diff. Send a comma-separated list or repeat the parameter. An unknown name is refused with 400.
statusLifecycle status filter (repeatable / comma-separated): draft, scheduled, published, archived. Default excludes draft.
archived_reasonNarrow archived rows by reason (repeatable / comma-separated): canceled, discarded, errored, superseded
sortOrder: version:asc (the default) lists the versions in the order they fired and will fire; version:desc lists the highest number first, versions without a number (the working draft, archived rows) last.
limitPage size (default 100, max 200)
cursorOpaque continuation token from the previous response's pagination.cursor. Omit for the first page. Cursors are stateless and do not expire, but are only valid for the list and filters that produced them.
Headers
X-Workspace-IdSelects the workspace this request operates in, by workspace UUID or slug (e.g. a sandbox workspace for test integrations). Omitted: the organization's default live workspace. Unknown workspace: 404; workspace outside your organization: 403. Discover workspaces via GET /workspaces.
Workspace UUID or slug.
List plan versions › Responses
OK
Get a plan version snapshot
The version's own fields plus the common version header. {version} is the number, or the word 'draft' for the open working draft.
Required scope: plan.read (Read plans).
path Parameters
idEntity UUID
versionThe version number, or the word 'draft' for the open working draft.
Headers
X-Workspace-IdSelects the workspace this request operates in, by workspace UUID or slug (e.g. a sandbox workspace for test integrations). Omitted: the organization's default live workspace. Unknown workspace: 404; workspace outside your organization: 403. Discover workspaces via GET /workspaces.
Workspace UUID or slug.
Get a plan version snapshot › Responses
OK
Name or annotate a plan version
Sets the version's release_name and/or release_note. These are display metadata, not content: editing them never creates a new version and is allowed on published versions. An absent field is left unchanged; null clears it; an empty string is refused. It answers the version row without the data envelope; from 2026-11-30 it answers {"data": …} like every other operation.
Required scope: plan.write (Write plans).
path Parameters
idEntity UUID
versionThe version number, or the word 'draft'.
Headers
X-Workspace-IdSelects the workspace this request operates in, by workspace UUID or slug (e.g. a sandbox workspace for test integrations). Omitted: the organization's default live workspace. Unknown workspace: 404; workspace outside your organization: 403. Discover workspaces via GET /workspaces.
Workspace UUID or slug.
Name or annotate a plan version › Request Body
release_nameNew name; null clears it. Absent leaves it unchanged.
release_noteNew note; null clears it. Absent leaves it unchanged.
Name or annotate a plan version › Responses
OK
created_atWhen the row was opened — for a draft when editing started, for a release when it was armed.
effective_fromeffective_toidThe catalog_versions row id. A client selecting a version writes this, never the number.
originWho made this version: manual (an operator published it), scheduled_release (a scheduled publish fired), cascade (republished automatically because something it depends on published).
published_atstatusThe row's own state: draft, scheduled, published or archived.
versionThe version's display number, or 0 for a row that is not on the chain (a working draft, an archived row).
archived_reasonWhy an archived row left the timeline: canceled, discarded or superseded.
For origin=cascade: what caused this version.
What the version does differently from the version it replaced; from is absent for the entity's first version. Sent only with include=diff (plans and products).
Who published the version — the same actor the audit trail names: an operator, an API key, or the system for a cascade. Absent on a version not yet published and on one published before publishers were recorded.
rank1-based position in the entity's queue of scheduled versions — the order they will fire. Present only on scheduled rows.
recorded_atWhen the publish was captured, on the wall clock (its audit row). published_at is the operation's instant — a sandbox's test clock — and effective_from when it governs. Sent with include=diff on published versions that have an audit row.
release_nameOperator-given name of this version, shown beside its number.
release_noteFree-text note about this version.
scheduled_change_idThe pending catalog.publish_version Change that will publish this version — reschedule or cancel it there.
scheduled_forWhen this scheduled version is due to publish. Present only on a scheduled row.
usage_countPresent only with ?include=usage_count.
Compare two plan versions
What the version does differently from the one it is compared against, as typed changes: prices per identity (key, currency, country, dimensions) added, removed or changed with their ladders, products and their terms, overrides, billing terms, costs, meters, bindings and the versions it pins, each named. Only values that differ are reported. ?against= takes 'head' (the default), another version number, or 'draft'.
Required scope: plan.read (Read plans).
path Parameters
idEntity UUID
versionThe version number, or the word 'draft'.
query Parameters
againstWhat to compare against: 'head' (the default) for the live version, or another version number, or 'draft'.
Headers
X-Workspace-IdSelects the workspace this request operates in, by workspace UUID or slug (e.g. a sandbox workspace for test integrations). Omitted: the organization's default live workspace. Unknown workspace: 404; workspace outside your organization: 403. Discover workspaces via GET /workspaces.
Workspace UUID or slug.
Compare two plan versions › Responses
OK
Preview what else publishing this version would publish
The entities that will publish BECAUSE this one does — a plan that tracks this product's latest, say — each with the cells its next head would carry that its current one does not. Derived on every read and never frozen: something that starts following tomorrow appears tomorrow.
Required scope: plan.read (Read plans).
path Parameters
idEntity UUID
versionThe version number, or the word 'draft' for the open working draft.
Headers
X-Workspace-IdSelects the workspace this request operates in, by workspace UUID or slug (e.g. a sandbox workspace for test integrations). Omitted: the organization's default live workspace. Unknown workspace: 404; workspace outside your organization: 403. Discover workspaces via GET /workspaces.
Workspace UUID or slug.
Preview what else publishing this version would publish › Responses
OK
Forecast the impact of superseding this plan version
Who is using the version in force today, and what a publish over it would move. pinnable reports whether holding a version is possible for this entity at all — it is false for tax rules, which are dated rather than pinned, so a caller must not render a pinned count for them. republishes_consumers is the cross-entity fan-out (publishing a price formula republishes every ACTIVE product whose head tiers reference it), and live_readers counts consumers billing off the live head, who move as soon as the publish lands. Read-only, and uncached: this is read at the moment money changes.
Required scope: plan.read (Read plans).
path Parameters
idEntity UUID
versionVersion number (positive integer)
Headers
X-Workspace-IdSelects the workspace this request operates in, by workspace UUID or slug (e.g. a sandbox workspace for test integrations). Omitted: the organization's default live workspace. Unknown workspace: 404; workspace outside your organization: 403. Discover workspaces via GET /workspaces.
Workspace UUID or slug.
Forecast the impact of superseding this plan version › Responses
OK
List plan version referrers
Offset-paginated drill-down of the referrers of one type for one version of the entity.
Required scope: plan.read (Read plans).
path Parameters
idEntity UUID
versionVersion number (positive integer)
query Parameters
typeReferrer type to list (required)
limitPage size (default 50, max 200)
cursorOpaque continuation token from the previous response's pagination.cursor. Omit for the first page. Cursors are stateless and do not expire, but are only valid for the list and filters that produced them.
Headers
X-Workspace-IdSelects the workspace this request operates in, by workspace UUID or slug (e.g. a sandbox workspace for test integrations). Omitted: the organization's default live workspace. Unknown workspace: 404; workspace outside your organization: 403. Discover workspaces via GET /workspaces.
Workspace UUID or slug.
List plan version referrers › Responses
OK
Get plan version usage summary
Referrer counts for one version of the entity, grouped by referrer type. Powers the version-switcher hover card.
Required scope: plan.read (Read plans).
path Parameters
idEntity UUID
versionVersion number (positive integer)
Headers
X-Workspace-IdSelects the workspace this request operates in, by workspace UUID or slug (e.g. a sandbox workspace for test integrations). Omitted: the organization's default live workspace. Unknown workspace: 404; workspace outside your organization: 403. Discover workspaces via GET /workspaces.
Workspace UUID or slug.
Get plan version usage summary › Responses
OK
Get a plan-level dunning policy
Returns the dunning policy override for a specific plan; data is null when the plan has none and follows the organization's.
Required scope: dunning.read (Read dunning state).
path Parameters
planIdPlan UUID
query Parameters
includeExtra data to add to the response: provenance. Send a comma-separated list or repeat the parameter. An unknown name is refused with 400.
Headers
X-Workspace-IdSelects the workspace this request operates in, by workspace UUID or slug (e.g. a sandbox workspace for test integrations). Omitted: the organization's default live workspace. Unknown workspace: 404; workspace outside your organization: 403. Discover workspaces via GET /workspaces.
Workspace UUID or slug.
Get a plan-level dunning policy › Responses
OK
Create or update a plan-level dunning policy
Creates (201) or updates (200) a dunning policy override scoped to a specific plan.
Required scope: dunning.write (Manage dunning policies).
path Parameters
planIdPlan UUID
query Parameters
includeExtra data to add to the response: provenance. Send a comma-separated list or repeat the parameter. An unknown name is refused with 400.
Headers
X-Workspace-IdSelects the workspace this request operates in, by workspace UUID or slug (e.g. a sandbox workspace for test integrations). Omitted: the organization's default live workspace. Unknown workspace: 404; workspace outside your organization: 403. Discover workspaces via GET /workspaces.
Workspace UUID or slug.
Create or update a plan-level dunning policy › Request Body
nameCreate or update a plan-level dunning policy › Responses
OK
Delete a plan-level dunning policy
Removes the dunning policy override for a specific plan.
Required scope: dunning.write (Manage dunning policies).
path Parameters
planIdPlan UUID
Headers
X-Workspace-IdSelects the workspace this request operates in, by workspace UUID or slug (e.g. a sandbox workspace for test integrations). Omitted: the organization's default live workspace. Unknown workspace: 404; workspace outside your organization: 403. Discover workspaces via GET /workspaces.
Workspace UUID or slug.
Delete a plan-level dunning policy › Responses
No Content