A price defines the monetary amount charged for a product within a plan. Prices support multiple currencies, billing periods, and pricing models - from simple flat fees to complex tiered and formula-based calculations.
Pricing models:
- Flat - a fixed amount per billing period (e.g., $49/month)
- Per-unit - a fixed amount multiplied by quantity (e.g., $10/seat)
- Tiered - different rates at different volume thresholds (e.g., $0.10/unit for 0–1,000, $0.08/unit for 1,001–10,000)
- Volume - the tier that contains the total quantity determines the rate for all units
- Graduated - each tier's rate applies only to units within that tier's range
- Formula - a mathematical expression evaluated at billing time (see Price Formulas)
Key concepts:
- Effective date - prices can be scheduled to take effect in the future
- Country code - geo-specific pricing for different markets
- Currency - each price is denominated in a single currency; use multiple prices for multi-currency support
List prices
Returns a paginated list of prices. Supports the unified field.op=value filter grammar (see /docs/pagination).
Required scope: price.read (Read prices).
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.
currencyFilter on currency (string). Operators: eq, in — dot grammar, e.g. currency.in=value; a bare currency=value means eq. A bare comma-separated value is in-sugar: currency=a,b means currency.in=a,b.
keyFilter on key (string). Operators: eq, in — dot grammar, e.g. key.in=value; a bare key=value means eq. A bare comma-separated value is in-sugar: key=a,b means key.in=a,b.
kindFilter on kind (enum). Operators: eq, in — dot grammar, e.g. kind.in=value; a bare kind=value means eq. A bare comma-separated value is in-sugar: kind=a,b means kind.in=a,b. Legal values: BASE, PLAN_OVERRIDE.
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.
includeExtra data to add to the response: product, referenced_costs. Send a comma-separated list or repeat the parameter. An unknown name is refused with 400. referenced_costs 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.
sortOrdering: created_at, with an optional :asc/:desc suffix (default created_at:desc). An unknown value is rejected. Cursors are bound to the ordering that issued them, so changing sort mid-walk needs a fresh first page.
product_idFilter by product UUID (sugar for product_id.eq). Filterable fields (product_id, plan_id, status, currency, key, kind, created_at, updated_at) accept apifilter operator suffixes
plan_idFilter by plan UUID (sugar for plan_id.eq); also supports plan_id.in
statusFilter by status (sugar for status.eq); also supports status.ne, status.in
viewValue view: draft (working-draft values) or head (published only)
product_version_idPin values to a product version
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 prices › Responses
OK
Create a price
Creates a new price in the owning product's working draft (or, for a plan override, the plan's). It answers the price with the working draft's values.
Required scope: price.write (Write prices).
query Parameters
includeExtra data to add to the response: product, referenced_costs. Send a comma-separated list or repeat the parameter. An unknown name is refused with 400. referenced_costs 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 price › Request Body
currencyThe price's currency (ISO 4217). Every amount of the body - list_price and each tier's unit_amount - is in it.
list_price^-?\d+(\.\d+)?$ · requiredList price in the price's currency. Nominal (often 0) on a tiered price.
product_idThe product to attach this price to.
country_codeCountry to scope this price to; omit to match any country.
Arbitrary dimension key/values that further scope the price.
display_nameCustomer-facing label for this price / price_key.
effective_fromOptional explicit publish timestamp; defaults to now.
effective_toOptional timestamp at which this price version stops being effective.
is_set_priceMarks this as the set price covering every effective key of the product's key set; requires a keyed product and a null price_key.
kindResolution scope: BASE or PLAN_OVERRIDE. Defaults to the most specific scope implied by the ids present. A price is a CATALOG row; a rate agreed with one customer is a subscription negotiated price, not a price.
Free-form key/value metadata to attach to the price.
plan_idPlan to scope a PLAN_OVERRIDE to; required (with a plan version) for kind=PLAN_OVERRIDE, forbidden otherwise.
plan_versionThe NUMBER of the plan version this override applies from — a convenience resolved against the plan's published versions. 0 on a plan that has never published pins the plan's working draft: the override is staged there and ships with the first publish. Prefer plan_version_id.
plan_version_idThe plan version this override applies from. Either this or plan_version is required for kind=PLAN_OVERRIDE; sending both is allowed only when they name the same version.
price_basisWhether list_price and the tier amounts are net of tax (exclusive, the default when omitted) or already contain it (inclusive). An inclusive price is billed as the stated amount: the invoice derives the net and the tax out of it at the resolved rate.
price_keyKeyed variant this price covers on a keyed product; 1-64 chars matching [a-zA-Z0-9._-].
The tier ladder in ascending up_to order; omit it for a flat price, which then needs a non-zero list_price.
valid_fromCustomer-facing start of the price's applicability window; distinct from effective_from.
valid_toCustomer-facing end of the price's applicability window; distinct from effective_to.
Create a price › Responses
Created
Compute a price
Deprecated: use POST /price-formulas/preview instead. Removed on 2026-11-30.
Evaluates a formula with variable bindings and changes nothing. Send either an inline expression or the id of a stored formula (its declared defaults apply under the bindings you send). Rate-table lookups read the rates in force now. Pass debug=true for a step-by-step trace.
Required scope: price.read (Read prices).
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.
Compute a price › Request Body
debugWhen true, the response includes a step-by-step evaluation trace.
expressionAd-hoc expression to evaluate; either this or formula_id is required.
formula_idA stored formula to evaluate; either this or expression is required.
Variable bindings supplied to the evaluation, keyed by variable name: a number (or a decimal string) for a numeric variable, text for a string one.
Compute a price › Responses
OK
Preview a price
Resolves the most specific price for the given dimensions and quantity, as billing would. Nothing is stored.
Required scope: price.read (Read prices).
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 a price › Request Body
product_idProduct whose price to resolve.
quantity^-?\d+(\.\d+)?$ · requiredQuantity to price; must be positive. Drives the tier walk and formula tier_quantity binding.
at_timePoint in time to resolve at; the cascade honours each price's valid_from/valid_to against this instant. Defaults to now.
country_codeCountry context; country-specific prices outrank generic ones.
currencyCurrency to pin the resolution to; filters the cascade to prices in this currency rather than converting. SEND THIS whenever the product is priced in more than one currency: currency is not a ranking term in the cascade, so an unpinned resolve over a multi-currency slot picks a stable but arbitrary row (and that row becomes the FX source). A pin the catalog cannot satisfy is not an error - it degrades to the catalog currency and converts into the pinned one, reporting conversion_applied and the pre-conversion amounts under original.
customer_idThe customer this resolution is for. It scopes currency preference and the audit record; it does not select a price — the catalog has no customer-scoped prices.
Dimension values used to match a price's custom_dimensions scope.
plan_idPlan scope; when set, plan_version is required and PLAN_OVERRIDE rows for that plan become eligible.
plan_versionThe exact plan version PLAN_OVERRIDE rows must match; required when plan_id is set.
price_keyKeyed variant to resolve on a keyed product; falls back to the set price when no exact key price exists.
Named numeric/string variables supplied to tier formulas and rate expressions during resolution.
subscription_idSubscription context for the resolution, when resolving for an existing sub.
Preview a price › Responses
OK
Resolve a price
Deprecated: use POST /prices/preview instead. Removed on 2026-11-30.
Resolves the most specific price for the given dimensions. Replaced by POST /prices/preview (same body and answer).
Required scope: price.read (Read prices).
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.
Resolve a price › Request Body
product_idProduct whose price to resolve.
quantity^-?\d+(\.\d+)?$ · requiredQuantity to price; must be positive. Drives the tier walk and formula tier_quantity binding.
at_timePoint in time to resolve at; the cascade honours each price's valid_from/valid_to against this instant. Defaults to now.
country_codeCountry context; country-specific prices outrank generic ones.
currencyCurrency to pin the resolution to; filters the cascade to prices in this currency rather than converting. SEND THIS whenever the product is priced in more than one currency: currency is not a ranking term in the cascade, so an unpinned resolve over a multi-currency slot picks a stable but arbitrary row (and that row becomes the FX source). A pin the catalog cannot satisfy is not an error - it degrades to the catalog currency and converts into the pinned one, reporting conversion_applied and the pre-conversion amounts under original.
customer_idThe customer this resolution is for. It scopes currency preference and the audit record; it does not select a price — the catalog has no customer-scoped prices.
Dimension values used to match a price's custom_dimensions scope.
plan_idPlan scope; when set, plan_version is required and PLAN_OVERRIDE rows for that plan become eligible.
plan_versionThe exact plan version PLAN_OVERRIDE rows must match; required when plan_id is set.
price_keyKeyed variant to resolve on a keyed product; falls back to the set price when no exact key price exists.
Named numeric/string variables supplied to tier formulas and rate expressions during resolution.
subscription_idSubscription context for the resolution, when resolving for an existing sub.
Resolve a price › Responses
OK
Get a price
Returns a single price. view and product_version_id read it the way GET /prices does; without them it carries the published values. include=product adds the owning product.
Required scope: price.read (Read prices).
path Parameters
idPrice UUID
query Parameters
includeExtra data to add to the response: product, referenced_costs. Send a comma-separated list or repeat the parameter. An unknown name is refused with 400. referenced_costs is sent without being asked until 2026-11-30; after that date it is sent only when named here.
viewValue view: draft (the working draft's values) or head (only a price the owner has published). Default: the published values.
product_version_idRead the price at one product version. Takes precedence over view.
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 price › Responses
OK
Remove a price
Removes the price from the owning product's working draft: the draft records the removal and the price leaves billing at the release that carries it. Discarding the draft puts the price back, and every already-published version goes on listing it — a version's price list is its own revision set, not the head's. A price that has never been released is hard-deleted instead. Returns 204; an unknown price is 404. From 2026-11-30 DELETE removes only a price no release ever carried and answers 409 for a released one: retire it with PATCH /prices/{id} {"status": "retired"}.
Required scope: price.write (Write prices).
path Parameters
idPrice 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 price › Responses
No Content
Update a price
Merge-patch update: only supplied fields change. Prices carry no publish controls — the edit lands in the owning product's working draft and reaches billing when the product is next published; to schedule a price change, schedule the product version. Currency changes are validated against the workspace's catalog currency allowlist. status, sent alone, removes a released price (retired) or undoes a removal that has not been released yet (active). It answers the price with the working draft's values.
Required scope: price.write (Write prices).
path Parameters
idPrice UUID
query Parameters
includeExtra data to add to the response: product, referenced_costs. Send a comma-separated list or repeat the parameter. An unknown name is refused with 400. referenced_costs 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 price › Request Body
country_codeCountry scope to set on the price; omit to leave unchanged.
currencyThe price's new currency (ISO 4217); omit to keep it. Every amount of the price is in it, including those this body leaves unchanged.
Arbitrary dimension key/values that further scope the price.
display_nameCustomer-facing label for this price / price_key.
list_price^-?\d+(\.\d+)?$List price in the price's currency; omit to leave unchanged.
Free-form key/value metadata to attach to the price.
price_basisWhether the price's amounts are net of tax (exclusive) or already contain it (inclusive); omit to leave unchanged.
statusretired takes a released price out of the catalog: the working draft removes it and it leaves billing at the owner's next release (until then active undoes it). A price no release ever carried is refused (409); DELETE removes it. active undoes a removal that has not been released yet: the price goes back into the working draft with the head's values; refused (409) when the head does not carry it. Send status alone; a price already in the status sent is returned unchanged.
Replacement tier ladder; null carries the previous version's tiers forward, an empty array clears them. Its amounts are in the price's currency.
Update a price › Responses
OK
Restore a removed price
Deprecated: use PATCH /prices/{id} instead. Removed on 2026-11-30.
Undoes a removal that has not been released yet: the price is added back to the owning product's working draft, on the same id and with the head's values. Refused (409) when the current head does not carry the price — there is nothing to restore it from; add a new price instead. Replaced by PATCH /prices/{id} with {"status": "active"}.
Required scope: price.write (Write prices).
path Parameters
idPrice UUID
query Parameters
includeExtra data to add to the response: product, referenced_costs. Send a comma-separated list or repeat the parameter. An unknown name is refused with 400. referenced_costs 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.
Restore a removed price › Responses
OK