A product represents a billable item in your catalog - a feature, service, or resource that customers pay for. Products are the building blocks of plans and appear as line items on invoices.
Products can be recurring (charged every billing period), usage-based (metered and charged based on consumption), or one-time (charged once at purchase).
Key concepts:
- Product type -
recurring,usage,one_time, orseat- determines how charges are calculated - External ID - your internal SKU or product code for integration with other systems
- Cost tracking - named COGS entries via
costs[](per-product or org-scoped) for margin analysis - Dependencies -
requires_product_idsenforces that certain products must be purchased together
List products
Returns a paginated list of products. Supports the unified field.op=value filter grammar (see /docs/pagination) plus custom-field filters via the custom_fields.
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.
invoiceable_standaloneFilter on invoiceable_standalone (boolean). Operators: eq — dot grammar, e.g. invoiceable_standalone.eq=value; a bare invoiceable_standalone=value means eq.
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.
pricing_modelFilter on pricing_model (enum). Operators: eq, in — dot grammar, e.g. pricing_model.in=value; a bare pricing_model=value means eq. A bare comma-separated value is in-sugar: pricing_model=a,b means pricing_model.in=a,b. Legal values: VOLUME, STAIRCASE, PACKAGE.
quantity_sourceFilter on quantity_source (enum). Operators: eq, in — dot grammar, e.g. quantity_source.in=value; a bare quantity_source=value means eq. A bare comma-separated value is in-sugar: quantity_source=a,b means quantity_source.in=a,b. Legal values: DECLARED, METERED.
tax_categoryFilter on tax_category (enum). Operators: eq, in — dot grammar, e.g. tax_category.in=value; a bare tax_category=value means eq. A bare comma-separated value is in-sugar: tax_category=a,b means tax_category.in=a,b. Legal values: DEFAULT, REDUCED, ZERO, EXEMPT.
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.
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)
Case-insensitive substring match over name and external_id; relevance-ranked (exact > prefix > substring)
statusFilter by status (sugar for status.eq). Filterable fields (name, external_id, quantity_source, status, pricing_model, tax_category, invoiceable_standalone, created_at, updated_at) accept apifilter operator suffixes; custom_fields.
Filter by status (sugar for status.eq). Filterable fields (name, external_id, quantity_source, status, pricing_model, tax_category, invoiceable_standalone, created_at, updated_at) accept apifilter operator suffixes; custom_fields.
tagFilter by tag name (repeatable, containment semantics)
Filter by tag name (repeatable, containment semantics)
countsComma-separated countable fields (status, quantity_source) to include per-value counts for
Comma-separated countable fields (status, quantity_source) to include per-value counts for
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 products › Responses
OK
Create a product
Creates a new product in draft status.
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 product › Request Body
Org-defined custom field values for this product.
descriptionInternal operator-facing description; not shown to buyers.
external_idCaller-supplied external identifier for reconciliation with an upstream system.
invoiceable_standaloneWhether the product may appear in the manual-invoice picker; absent defaults to true, explicit false marks it subscription-only.
key_set_excluded_keysKeys subtracted from the linked key set's active entries (subtract-only).
key_set_idOpts the product into keyed pricing by linking it to an org-global key set.
Free-form operator key/value metadata.
nameInternal operator-facing product name.
pricing_modelPricing model applied to this product; defaults to VOLUME when omitted.
public_descriptionBuyer-facing description shown in checkout and the customer portal.
quantity_adjustableWhen true the buyer picks the quantity (seats/licenses); defaults to false (flat charge) and is rejected on METERED products.
quantity_sourceWhere the billed quantity comes from: DECLARED (set on the subscription item) or METERED (from the meter evaluator); defaults to DECLARED.
requires_product_idsProducts that must also be present for this product to be sold (dependency set).
tax_categoryProduct tax category: DEFAULT, REDUCED, ZERO or EXEMPT; defaults to DEFAULT.
Locale-keyed overrides for name, description and public_description.
unit_labelSingular noun one billed unit is called; defaults to unit.
Create a product › Responses
Created
Get a product
Returns a single product enriched with its active-price count; translatable fields are localized to the caller's locale.
path Parameters
idProduct UUID
Product 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 a product › Responses
OK
Delete a product
Soft-deletes a product by UUID.
path Parameters
idProduct UUID
Product 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 product › Responses
No Content
Update a product
Merge-patch update: only supplied fields change. This is ALSO the publish surface (ADR-0006): send status="active" to publish the working draft, optionally with effective_at to schedule the flip (omitted or past = immediate) and save_as_draft=true to stage without publishing. expected_version is the optimistic-concurrency token: when supplied and stale, the update returns 409.
path Parameters
idProduct UUID
Product 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.
Update a product › Request Body
Org-defined custom field values for this product.
descriptionNew internal description; null leaves it unchanged.
effective_atSchedule the versioned change to take effect at this instant instead of publishing immediately; mutually exclusive with save_as_draft.
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.
invoiceable_standaloneFlips the manual-invoice picker eligibility; null leaves it unchanged.
key_set_excluded_keysReplacement excluded-keys list; null leaves it unchanged, non-null replaces the list.
key_set_idLinks or clears the product's key set; changing it once the product has keyed prices or items is rejected.
Free-form operator key/value metadata.
nameNew internal product name; null leaves it unchanged.
pricing_modelNew pricing model (VOLUME, STAIRCASE, PACKAGE); null leaves it unchanged.
public_descriptionNew buyer-facing description; null leaves it unchanged.
quantity_adjustableFlips whether the buyer picks the quantity; null leaves it unchanged, and setting true on a METERED product is rejected.
requires_product_idsReplacement product-dependency set for this product.
save_as_draftPersist the change as a working draft instead of publishing it; mutually exclusive with effective_at.
statusNew catalog lifecycle status (draft, scheduled, active, archived); null leaves it unchanged.
tax_categoryNew tax category (DEFAULT, REDUCED, ZERO, EXEMPT); null leaves it unchanged.
Locale-keyed overrides for name, description and public_description.
unit_labelNew singular unit label; null leaves it unchanged. quantity_source itself is immutable post-create.
Update a product › Responses
OK
Duplicate a product
Creates a new product whose attributes are copied from the source. Prices, costs and meter bindings on the source are NOT copied; only the product definition.
path Parameters
idSource product UUID
Source product 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.
Duplicate a product › Request Body
external_idExternal identifier for the new draft copy.
nameName for the new draft copy; defaults to a derived copy name when omitted.
Duplicate a product › Responses
Created
List plans that use this product
Every plan that includes this product, with the pinned product version, the live head, the membership track mode, and a stale flag (pinned != head).
path Parameters
idProduct UUID
Product 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.
List plans that use this product › Responses
OK
Get the product's open working draft
The open working-draft metadata (draft version id, number, created_at, changed-child counts vs the head). 404 when the product has no open draft.
path Parameters
idProduct UUID
Product 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 product's open working draft › Responses
OK
Discard the product's open working draft
Deletes the open working-draft version row; its revision rows cascade. 404 when no draft is open.
path Parameters
idProduct UUID
Product 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 product's open working draft › Responses
No Content
Preview the impact of publishing a product
Read-only dry-run: the active plans that auto-follow this product ('latest' membership) and would be republished, plus the number of subscriptions that would migrate.
path Parameters
idProduct UUID
Product 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 product › Responses
OK
List product versions (with lifecycle filter)
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 (ADR-0007). Supports ?include=usage_count and ?archived_reason=.
path Parameters
idEntity UUID
Entity UUID
query Parameters
statusLifecycle status filter (repeatable / comma-separated): draft, scheduled, published, archived. Default excludes draft.
Lifecycle 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
Narrow archived rows by reason (repeatable / comma-separated): canceled, discarded, errored, superseded
includeComma-separated includes. Currently supports usage_count.
Comma-separated includes. Currently supports usage_count.
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.
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 product versions (with lifecycle filter) › Responses
OK
Get a product version snapshot
Returns the full snapshot row from product_versions, including the Phase 2.D child version pin maps.
path Parameters
idEntity UUID
Entity UUID
versionVersion number
Version number
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 product version snapshot › Responses
OK
List product version referrers
Offset-paginated drill-down of the referrers of one type for one version of the entity.
path Parameters
idEntity UUID
Entity UUID
versionVersion number (positive integer)
Version number (positive integer)
query Parameters
typeReferrer type to list (required)
Referrer type to list (required)
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.
offsetRow offset (default 0)
Row offset (default 0)
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 product version referrers › Responses
OK
Get product version usage summary
Referrer counts for one version of the entity, grouped by referrer type. Powers the version-switcher hover card.
path Parameters
idEntity UUID
Entity UUID
versionVersion number (positive integer)
Version 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 product version usage summary › Responses
OK