
Catalog > Price Tables
Definition
Where do I use it?
- Rental rate cards — Define
1 day @ €40,3 days @ €100,1 week @ €180, etc. - Sales pricing — Set the outright sale price for a Listing, optionally with a compare-at price
- Subscription plans — Configure commitment period, payment cycle and auto-renewal cadence
- Promotional / seasonal pricing — Attach an extra dated Price Table whose rows are considered inside its window (a cheaper promotional row is selected over the default — see Price resolution)
- Per-variant overrides — Adjust each pricing row by a
rateMultiplierfor specific variant values - Bulk re-pricing — Maintain one shared table and link it to many Listings in a category
Price Table model
A shared default Price Table returned by the API looks like this:catalogItemId is null on shared tables not bound to one Listing, and availabilityRange is null on a default table that is always valid.
A single Price Table holds three orthogonal sets of rows — booking, sales and subscription — gated by bookingsEnabled, salesEnabled and subscriptionsEnabled. Disable the modes you don’t sell to keep the editing surface focused.
Shared vs. standalone
link-catalog-item, unlink-catalog-items) so a Listing keeps a clean record of which shared tables apply.
Pricing row types
A Price Table contains three row types, each driving a different purchase mode.Booking pricing rows
Used when the order is a rental booking.Sales pricing rows
Used for outright sale orders.Subscription pricing rows
Used for subscription-mode orders. See Subscriptions for the full billing lifecycle.Per-variant pricing
If a Listing has variants, a Price Table can carry an optionalvariants array of CatalogItemVariantPricingTable entries. Each entry binds a variantName plus one or more values to a rateMultiplier and per-row multipliers for booking, sales and subscription rows.
A CatalogItemVariantPricingTable entry has the following fields:
basePricingRowId) and applies a rateMultiplier (and optional additionalRateMultiplier) on top of that base row’s price. A multiplier of 1.0 matches base; 1.2 adds 20 %; 0.8 discounts by 20 %.
The admin marks any combination with a CatalogItemVariantPricingTable entry as “Variant pricing” instead of “Base”.
Starting at pricing
Every booking pricing row has atimeBasis field that controls how duration is counted for billing. Two modes exist:
How “Starting at” billing works
The pricing engine identifies each calendar period the booking touches on the row’s own grid. A day unit is always midnight to midnight, an hour unit is clock-hour to clock-hour, and a week unit starts on the tenant’s configured first day of the week. Each touched period is billed as one unit — unless a finer rate covers the booked portion of that period more cheaply. The engine picks the cheapest valid composition recursively. Example — day and week rates: A 10-day booking starting Thursday, with a day rate of €50 and a week rate of €250:- Thursday through Sunday: 4 started days = €200
- Monday through the following Sunday: 1 started week = €250
- Total: €450
- Monday: 1 started day = €100
- Tuesday: 1 started day = €100
- Total: €200 (2 calendar days)
”Starting at” for fixed-duration packages
Fixed-duration booking rows also supporttimeBasis. A capped fixed package anchors the return date to the calendar period rather than to the exact pickup time.
A “2 days” fixed package picked up on Monday at 14:00:
- Elapsed (
rolling): return Wednesday at 14:00 — exactly 48 hours later - Started (
capped): return on Tuesday by the last available return time — the booking touches Monday and Tuesday, so two calendar days are covered
Constraints
- Sub-hour units always bill rolling. Rows using
secondsorminutesare not affected by thetimeBasissetting. They coexist freely in a “Starting at” table. - All rate-based rows on a table share one
timeBasis. The admin enforces this with a single “Starting at pricing” checkbox that applies to all eligible rate-based rows. Fixed-duration rows each have their own independenttimeBasissetting. - Week rates use the tenant’s week start day. A Saturday–Sunday booking counts as one week with a Monday start (both days are in the same Mon–Sun week), but as two weeks with a Sunday start.

Rate-based booking pricing with the Starting at pricing checkbox

Price resolution
availabilityRange controls which tables are candidates for a booking — it does not give a table priority. There is no per-table priority or override field. When a price is calculated:
All pricing calculations run in the Location’s timezone. Day boundaries, week boundaries, weekdays constraints, and timeOfDayRange windows are evaluated against the location’s local clock — not UTC or the customer’s timezone. A booking near local midnight is matched to the correct local day, and DST transitions are handled by calendar arithmetic in the location zone.
- Collect the active tables. A table is active when it has no
availabilityRange, or its range overlaps the booking dates. The default table (no range) is therefore always active alongside any dated table whose window the booking falls in. - Pool the rows. Every enabled row from every active table is gathered into a single candidate pool. The engine does not first pick one “winning” table — rows from different tables compete directly.
- Select the row(s).
- Rate-based (dynamic) bookings — the engine builds the cheapest valid coverage of the booking duration from the pool. For each segment it prefers the row with the longest duration that still fits, breaking ties by the lowest price, then fills any remainder with shorter rows and additional-time rules (weekday, time-of-day and
availabilityRangeconstraints still apply per row). When rows use Starting at pricing (timeBasis: capped), the engine counts calendar periods touched instead of elapsed time, and picks the cheapest composition across granularities. - Fixed-price bookings — the shopper selects a specific rate row (for example,
1 day @ €40) and that row’s price is used directly.
- Rate-based (dynamic) bookings — the engine builds the cheapest valid coverage of the booking duration from the pool. For each segment it prefers the row with the longest duration that still fits, breaking ties by the lowest price, then fills any remainder with shorter rows and additional-time rules (weekday, time-of-day and
- Apply variant multipliers. If a
CatalogItemVariantPricingTableentry matches the selected variant value combination, multiply the row’s contribution by the row-level multiplier (and the table-levelrateMultiplier). - Return totals. The endpoints
…/rate-based-priceand…/fixed-pricereturn apriceBreakdownplustotalPrice, with each contribution itemised for the storefront and order summary.
Applying tables to Listings and Collections
A shared Price Table is linked to a Listing through dedicated endpoints:POST /pricing-tables/pricing-table/:pricingTableId/link-catalog-item— link one ListingDELETE /pricing-tables/pricing-table/:pricingTableId/unlink-catalog-items— bulk unlinkPOST /pricing-tables/add-pricing-table-to-catalog-item— discriminated union for the four linking modes:mode = 'link'— link to an existing shared tablemode = 'create'— create a new standalone table inlinemode = 'sharedToShared'— replace one shared link with anothermode = 'sharedToStandalone'— fork a shared table into a standalone one on the Listingmode = 'standaloneToShared'/'standaloneUpdate'— flip ownership or rename in place
listCatalogItems and link the table to each Listing.
Key Properties (Price Table)
Relationships
Linked to one or more Listings
Linked to one or more Listings
Holds booking, sales and subscription rows
Holds booking, sales and subscription rows
Overlays variant pricing on a Listing's variants
Overlays variant pricing on a Listing's variants
Evaluated against the Listing's purchase mode at checkout
Evaluated against the Listing's purchase mode at checkout
…/rate-based-price or …/fixed-price endpoint to compute the total.Lifecycle
Create
POST /pricing-tables/ with label and (for shared tables) isDefault: true. New tables start with empty row arrays.Can I make a dated table the default?
Can I make a dated table the default?
availabilityRange = null can be isDefault = true (enforced by the admin UI; the model permits the flag but the link logic skips dated tables when resolving the default).Add rows
/pricing-table/:id/row, …/sales-row, …/subscription-row). Add per-variant overrides via /pricing-table/:id/variant-pricing-table.Link to a Listing
add-pricing-table-to-catalog-item with the right mode. The Pricing tab on a Listing shows linked tables in a sidebar and lets you create new ones inline.Override per variant
CatalogItemVariantPricingTable block for the variant values that need different pricing.Promote or replace
Delete
DELETE /pricing-tables/delete-table/:pricingTableId. Removing a shared table unlinks it from every Listing.Frequently Asked Questions
Can one Listing have several Price Tables?
Can one Listing have several Price Tables?
availabilityRange decides which tables are candidates, not which one wins.Where does the rental period price come from?
Where does the rental period price come from?
timeUnit × timeUnitAmount and picks the closest fit, then applies any additional-time rules for the remainder.How do I run a 20 % weekend promotion?
How do I run a 20 % weekend promotion?
availabilityRange covering the weekend, with rows priced 20 % below the default, and link it to the affected Listings. Because the promotional rows are cheaper and cover the same durations, the engine selects them inside the window and falls back to the default afterwards. This works precisely because the promo is cheaper — see the next question for higher/surcharge pricing.Why isn't my higher seasonal / holiday price being applied?
Why isn't my higher seasonal / holiday price being applied?
availabilityRange to exclude those dates, so only the higher row covers them.What is the difference between rolling and Starting at pricing?
What is the difference between rolling and Starting at pricing?
capped) counts every calendar period the booking touches. A “day” is midnight to midnight. The same Monday 14:00 to Wednesday 14:00 booking touches Monday, Tuesday, and Wednesday — 3 started days.Use “Starting at” when you charge per calendar day (like parking garages or hotel nights). Use rolling when you charge for exact elapsed time (like hourly equipment rental).Can prices differ per customer segment?
Can prices differ per customer segment?
Which timezone does the pricing engine use?
Which timezone does the pricing engine use?
Are existing orders re-priced when I edit a table?
Are existing orders re-priced when I edit a table?
What's the difference between `priceContribution` and `totalPrice` in the response?
What's the difference between `priceContribution` and `totalPrice` in the response?
priceContribution is what one row added to the total; totalPrice is the sum across all rows the engine selected (base row + additional-time rows + variant multipliers).Developer Reference
Price tables are exposed aspricing-tables in the API.