Versioning
Five things in Kontier carry versions: plans, products, tax rules, rate tables and price formulas. They share one model: you edit a working draft, then publish it as a new version, now or at a set time.
Versioning exists so that editing something you already sell is safe. Change a price, publish, and only the subscription lines that follow the catalog move, at the moment their version-change rule says. Lines that are pinned keep what they were sold.
You mostly do not have to think about it. Edit, publish, done. This page is for when you do.
What is not versioned
Prices, meters and costs have no versions of their own, and neither do customers, subscriptions or invoices.
A price belongs to a product: editing one lands in the product's working draft and reaches billing when the product is next published. An invoice is immutable once finalised. A subscription records its own history as revisions (see Subscriptions), and each of its lines names the catalog version it was drawn from.
Promotions have a lifecycle of their own (draft, live,
ended) and go live with POST /v1/promotions/{id}/publish, not through the
model on this page.
The working draft
Edits to what an entity bills stage on its working draft and change nothing that is live:
| Entity | Edit the draft with | Read / discard it |
|---|---|---|
| Plan | POST /v1/plans/{id}/products, PATCH / DELETE /v1/plans/{id}/products/{productId}, the plan defaults on PATCH /v1/plans/{id} | GET / DELETE /v1/plans/{id}/draft |
| Product | its prices, costs and meter bindings; or PUT /v1/products/{id}/draft/content | GET / DELETE /v1/products/{id}/draft |
| Rate table | PUT /v1/rate-tables/{id}/draft/content | GET / DELETE /v1/rate-tables/{id}/draft |
| Tax rule | PUT /v1/tax-rules/{id} (a billing-field edit to a live rule) or PUT /v1/tax-rules/{id}/draft/content | GET / DELETE /v1/tax-rules/{id}/draft |
| Price formula | PUT /v1/price-formulas/{id}/draft/content | GET / DELETE /v1/price-formulas/{id}/draft |
Display-only fields (names, descriptions, metadata) land live immediately.
POST …/draft (for example POST /v1/plans/{id}/draft) opens the draft explicitly, on the live head or on
one of the entity's scheduled versions (base_version_id).
None of the update routes publishes. On plans and products, sending
"status": "active" is refused with PUBLISH_IS_A_CHANGE; there is no
effective_at, save_as_draft or publish_now field anywhere.
Publishing is a change
Taking a draft live is POST /v1/changes with the operation
catalog.publish_version. subject_kind names the entity: plan, product,
rate_table, tax_rule or price_formula.
Publish a product's draft at 09:00 Berlin time
when | What happens |
|---|---|
now | Published in the request. The change answers status: "applied" |
at, with at + zone | Scheduled. at is a wall clock read in the IANA zone: send it with a Z suffix and name the zone separately. An offset in at is refused |
payload is optional naming: release_name (up to 120 characters) and
release_note (up to 2,000). You can set or change both later with
PATCH …/versions/{version} on the entity (for example
PATCH /v1/products/{id}/versions/{version}), which never creates a version.
Add "gate": "approval" to hold the publish until a second person releases it.
A person decides approvals in the dashboard. The API can read them
(GET /v1/approvals, GET /v1/approvals/{id}) but not decide them.
Scheduled versions
A scheduled publish freezes the draft into a pending version, and the working
draft is free again for the next edit. The entity lists pending releases under
scheduled_versions, in the order they will publish, each with:
| Field | Meaning |
|---|---|
change_id | The pending change. Reschedule or cancel it there |
version | The number it will publish as. Provisional: recomputed whenever the timeline moves |
scheduled_for | When it is due |
version_id, base_version_id | The frozen version, and the one it publishes on top of |
The entity's own state never says "scheduled": lifecycle is draft,
published or archived, and an entity with a pending release is still what it
was, and still editable.
| To | Call |
|---|---|
| See what is pending | GET /v1/changes?operation=catalog.publish_version&status=planned |
| Move it | PATCH /v1/changes/{id} with a new when (and at + zone) |
| Publish it now instead | PATCH /v1/changes/{id} with "when": "now" |
| Cancel it | DELETE /v1/changes/{id} |
Cancelling cancels the release, not the edit: the content goes back to being
staged and can be published later, or discarded with
the entity's DELETE …/draft. Once a change has run or been retired, both
PATCH and DELETE answer 409 CHANGE_NOT_EDITABLE. See
Scheduling for the full change lifecycle.
Version rows
GET …/versions and GET …/timeline on each entity (for example
GET /v1/rate-tables/{id}/timeline) return version rows. The timeline lists them in the order they fired and will fire; pass
include=all for the working draft and archived rows too.
status | Meaning |
|---|---|
draft | The working draft. Not visible to customers |
scheduled | A frozen release waiting for its change to fire (scheduled_for, scheduled_change_id) |
published | On the chain: live now or superseded by a later version |
archived | Left the timeline. archived_reason says why: canceled, discarded or superseded |
Each row also says who made it in origin: manual (an operator or API key published it),
scheduled_release (a scheduled publish fired) or cascade (republished
automatically because something it depends on published, such as a plan that
tracks a product's latest version).
The parent row and the version row use different words
A plan's own status says active where its version row says
published. Same idea, two vocabularies, because the parent and the version
are different rows. Read the one you are actually querying.
History
GET /v1/plans/{id}/history and GET /v1/products/{id}/history list the
published and scheduled versions newest first, each with its publisher, release
name, effective time and a readable diff against the version it replaced. On
every kind, GET …/versions/{version}/diff compares versions
directly.
Before you publish
| Call | Answers |
|---|---|
GET …/versions/{version}/publish-impact | How many subscriptions would move, and how many hold still |
GET …/versions/{version}/followers | What else would publish because this one does |
GET …/versions/{version}/referrers | What else depends on this version |
GET …/versions/{version}/usage | How much this version is actually being used |
POST /v1/changes/preview | The POST /v1/changes body, answered with who moves, who does not and why, and the money delta, without writing anything |
{version} is a version number; followers and diff also accept the word
draft, to ask about the open working draft. Plans and products also have
an entity-level GET /v1/plans/{id}/publish-impact and
GET /v1/products/{id}/publish-impact; plans add
GET /v1/plans/{id}/publish-preflight, which runs the real publish and rolls it
back to tell you whether it would succeed.
Who follows a new version
Nothing is migrated in bulk, and there is no migrate call. Each consumer either follows the newest version or holds still:
- A subscription line has a
track:followorpinned. A following line moves when its plan or product publishes, at the time itsversion_changesays:immediate_prorate,next_periodorat_phase_change. A pinned line keeps its version. A line that states neither inherits them from the plan version and the subscription's defaults; see Plans. - A plan membership chooses per product with
product_track_mode:latest(publishing the product republishes the plan) orpinned. - A price tier that uses a formula records the formula version
in force when the product publishes (
formula_version_id). A pinned line bills that exact formula version; a following line uses the formula's current head.
What each entity can do
| Plans | Products | Tax rules | Rate tables | Formulas | |
|---|---|---|---|---|---|
Working draft, publish / schedule via /changes | ✅ | ✅ | ✅ | ✅ | ✅ |
| Versions, timeline, diff, followers, referrers, usage, version publish-impact | ✅ | ✅ | ✅ | ✅ | ✅ |
PUT …/draft/content | — | ✅ | ✅ | ✅ | ✅ |
| History | ✅ | ✅ | — | — | — |
| Entity-level publish-impact | ✅ | ✅ | — | — | — |
| Publish preflight | ✅ | — | — | — | — |
| Copy the entity | ✅ | ✅ | ✅ | — | — |
| Archive | PATCH status | PATCH status | POST …/archive | POST …/archive | — |
Where versioning shows up
| Page | What it adds |
|---|---|
| Plans | The version you meet first, and what publishing does to subscriptions |
| Products | Product versions and what a plan membership tracks |
| Taxes | Dated rules, where valid_from and the version interact |
| Rate tables / Formulas | Versioned pricing inputs |
| Scheduling | The change list every scheduled publish lives in |