Rate engine
Read-only metadata for calculation strategies, modifiers, presets, and composed config schema used by rate specifications.
Overview
The rate engine is the calculator behind catalogue pricing. A rate specification describes how a charge is counted (strategy, base period, modifiers). A catalogue item rate attaches that specification and an amount to a catalogue item. This page documents the four read-only rate engine meta endpoints that expose the same strategy, modifier, preset, and config-schema catalogue the admin rate-specification form uses.
These endpoints do not store prices and do not run a quote. For how definitions are edited in admin, see Rate specifications. For discount categories, price categories, surcharges, and bundle rules, see Pricing & Discounts — those pages live under Finance and are not duplicated here.
Token ability
Every meta route requires the rates:read token ability (rates.view on the session user).
Strategies
Route: GET /api/v1/rate_engine/strategies
Returns each registered calculation strategy: identifier, label, allowed base periods, and whether it supports multiplier / factor modifiers. Typical identifiers are period, fixed, and hybrid.
Modifiers
Route: GET /api/v1/rate_engine/modifiers
Returns registered rate modifiers in engine priority order (the same order used when a specification applies them). Each row is identifier, label, and priority.
Presets
Route: GET /api/v1/rate_engine/presets
Returns the framework-shipped rate-specification templates (slug, name, description, strategy, base period, enabled modifiers, and default configs). Presets are read-only starting points on this list; the specification you clone from a preset is a fully-editable configuration (see Rate specifications). Cloning is a write on rate_specifications, not on this list.
Schema
Route: GET /api/v1/rate_engine/schema
Composes the config-form sections for one strategy and an optional set of modifier identifiers (strategy is required; modifiers[] is optional). Unknown strategy or modifier identifiers fail validation with 422. The response is form metadata only — it does not persist a specification.
Rate preview
POST /api/v1/catalogue_items/{catalogue_item}/calculate_rate uses resolver specification 1.0.0 for a warehouse, transaction type, price category, currency, and start date, then runs the engine over the requested window. Selection is category-specific first, then an applicable explicit default, then a general rate. Inside the selected class it sorts by priority, warehouse specificity, newest published revision, and stable row ID. Invalid warehouse, date, category, and currency candidates do not win. When no rate is assigned the response is a zero-priced breakdown (meta.resolved is false). The preview does not persist a rate or a rental line. It requires rates:read or catalogue-items:read.
Every response includes the selected immutable rate revision ID and a compact rate_resolution summary. Set include_resolution_trace=true to request rejected-candidate evidence. Detailed traces require both the rates.view permission and rates:read token ability, are limited to 100 rows, and use trace_offset plus next_trace_offset for pagination. This prevents a catalogue-only reader from discovering negotiated pricing candidates.
The curated MCP tool calculate-rate wraps that same preview.
Price-book revisions
Rate specifications and catalogue item rates have stable logical UUIDs and immutable numbered revisions. A published revision records its schema version, content hash, effective interval, author, reason, publication time, and superseded revision. Legacy create and update actions publish a matching revision in the same database transaction. Draft revisions never change the legacy live columns and are not visible to runtime resolution.
Published rate revisions use effective-dated activation rows. Retiring a rate retires its activation; resolution then falls through to the next eligible candidate and does not require an explicit default. Existing rental lines store the revision that actually supplied their price, plus a compact resolver summary, so replay does not silently move to current pricing. If an explicit pin is invalid, its pin remains visible for compatibility while the stored revision identifies the valid dynamic fallback that supplied the amount.
Existing installations can inspect or apply the compatibility backfill with:
php artisan signals:price-book:backfill-revisions --dry-run
php artisan signals:price-book:backfill-revisions --apply --reason="Initial v1 price-book backfill."
The command preserves source identifiers and timestamps. It reports invalid definitions and does not silently repair or normalise them.