Customer Booking API
This document describes the pricing-engine HTTP endpoints that support the customer-facing Dynamic Pricings 2.0 (DPv2) booking flow in hh-pegasus-monorepo.
For the frontend flow, see:
Overview
| Endpoint | Method | Purpose | Caller |
|---|---|---|---|
/api/v1/packages_pricing_rules/:restaurant_id | GET | Best rules per package for restaurant/package cards. | hh-pegasus-monorepo (restaurant detail, package cards) |
/api/v1/get_available_rules_by_date | POST | Best rules per package for a date range. | hh-pegasus-monorepo (date picker) |
/api/v1/get_available_rules_by_time | POST | Rules per 15-minute slot for a single date. | hh-pegasus-monorepo (time picker) |
/api/v1/verify_active_rules | POST | Validate that a selected rule is still active at reservation time. | hh-server (booking creation/update) |
/api/v1/get_modifier_history | POST | Return all modifiers (including inactive) for a date/time/package set. | Internal / future use |
Routes
pricing-engine/config/routes.rb:87-103
get "packages_pricing_rules/:restaurant_id", to: "pricing_rules#packages_pricing_rules"
post "get_available_rules_by_date", to: "pricing_rules#get_available_rules_by_date"
post "get_available_rules_by_time", to: "pricing_rules#get_available_rules_by_time"
post "verify_active_rules", to: "pricing_rules#verify_active_rules"
post "get_modifier_history", to: "pricing_rules#get_modifier_history"
Controller: pricing-engine/app/controllers/api/v1/pricing_rules_controller.rb:27-156
Service: pricing-engine/app/services/pricing/modifier_service.rb:95-316
GET /api/v1/packages_pricing_rules/:restaurant_id
Returns up to two best rules per package for display on restaurant detail and package cards.
Request
Path parameter
| Field | Type | Required | Description |
|---|---|---|---|
restaurant_id | string/number | Yes | Restaurant identifier. |
Query parameter
| Field | Type | Required | Description |
|---|---|---|---|
restaurant_package_ids | array/string | Yes | Comma-separated or array of package IDs. |
Response
{
"success": true,
"status": true,
"data": [
{
"restaurant_package_id": "4300",
"currency": "THB",
"pricing_rules": [
{
"id": 26,
"action_type": "decrease",
"value_type": "percentage",
"value": 3.0
},
{
"id": 30,
"action_type": "decrease",
"value_type": "fixed",
"value": 210.0
}
]
}
],
"message": null
}
Selection logic
ModifierService#packages_pricing_rules (modifier_service.rb:95-133) selects up to 2 best offers per package:
- 1 percentage rule
- 1 fixed rule
- Decrease rules win over increase rules.
- For decrease: highest value wins.
- For increase: lowest value wins.
POST /api/v1/get_available_rules_by_date
Returns the best rules per package for each date in the requested range. Used by the date picker to render per-day prices.
Request
{
"restaurant_id": "uuid",
"start_date": "2026-01-01",
"end_date": "2026-01-31",
"restaurant_package_ids": ["123", "456"]
}
Validation
Controller enforces (pricing_rules_controller.rb:41-55):
start_dateandend_dateare required.restaurant_package_idsis required and non-blank.- Dates must parse as
YYYY-MM-DD.
Response
{
"success": true,
"data": [
{
"date": "2026-01-15",
"packages": [
{
"restaurant_package_id": "123",
"currency": "THB",
"pricing_rules": [
{
"id": 26,
"action_type": "decrease",
"value_type": "percentage",
"value": 3.0
}
]
}
]
}
],
"status": true,
"message": null
}
Selection logic
ModifierService#get_available_rules_by_date (modifier_service.rb:162-198):
- For each date × package, scan every 15-minute time slot.
- Resolve priority winners per slot.
- From those winners, pick up to 2 best offers: 1 percentage + 1 fixed.
- Decrease rules win over increase rules; for decrease, highest value wins; for increase, lowest value wins.
POST /api/v1/get_available_rules_by_time
Returns rules per 15-minute slot for a single date. Used by the time picker to render per-slot prices and badges.
Request
{
"restaurant_id": "uuid",
"date": "2026-01-15",
"restaurant_package_ids": ["123", "456"]
}
Validation
Controller enforces (pricing_rules_controller.rb:74-85):
dateis required.restaurant_package_idsis required and non-blank.datemust parse asYYYY-MM-DD.
Response
{
"success": true,
"data": [
{
"start_time": "11:00",
"packages": [
{
"restaurant_package_id": "123",
"currency": "THB",
"pricing_rules": [
{
"id": 26,
"action_type": "decrease",
"value_type": "percentage",
"value": 3.0
}
]
}
]
}
],
"status": true,
"message": null
}
The response always contains 96 slots (one per 15 minutes from 00:00 to 23:45).
Selection logic
ModifierService#get_available_rules_by_time (modifier_service.rb:218-254):
- Per package, per slot, pick up to 2 rules (1 percentage + 1 fixed) using priority resolution.
- Priority order (
modifier_service.rb:792-813):- Lowest
priorityvalue - Highest
weight - Newest
created_at
- Lowest
POST /api/v1/verify_active_rules
Validates that the rule selected by the customer is still active and unchanged at reservation time. Called by hh-server during booking creation/update.
Request
{
"restaurant_id": "uuid",
"date": "2026-01-30",
"time": "19:00",
"packages": [
{
"id": "4578",
"pricing_rules": {
"id": 2732,
"action_type": "decrease",
"value_type": "percentage",
"value": 500
}
}
]
}
Response
All valid
{
"success": true,
"valid": true,
"results": [...],
"message": null
}
Any invalid
{
"success": false,
"valid": false,
"results": [...],
"message": "One or more pricing rules are no longer valid"
}
Validation reasons
ModifierService#verify_active_rules (modifier_service.rb:273-316) can return:
rule_not_foundnot_applicable_to_packagenot_active_on_datenot_active_on_daynot_active_at_timevalues_changed
POST /api/v1/get_modifier_history
Returns all modifiers (including inactive) that apply to a given date/time/package set. Currently not wired to the frontend booking flow.
Request
{
"restaurant_id": "uuid",
"date": "2026-01-30",
"time": "19:00",
"restaurant_package_ids": ["123"]
}
Response
{
"success": true,
"data": [ ... ],
"meta": { ... },
"message": null
}
Parameter Naming Convention
The public API standardised on restaurant_package_ids (not package_ids). This change is documented in pricing-engine/API_CHANGES_V2.md:6-18.
Numeric Format
value fields are returned as JSON numbers, not strings. Backend converts BigDecimal to Float via .to_f (modifier_service.rb:591-614, modifier_service.rb:849-856).
Frontend Integration
hh-pegasus-monorepo wraps these endpoints in typed clients:
| File | Endpoint |
|---|---|
packages/shared-api/src/restaurant/getAvailableRulesByDate.ts:59-120 | POST /api/v1/get_available_rules_by_date |
packages/shared-api/src/restaurant/getAvailableRulesByTime.ts:52-110 | POST /api/v1/get_available_rules_by_time |
packages/shared-api/src/restaurant/getPackagePricingRules.ts:38-102 | GET /api/v1/packages_pricing_rules/:restaurant_id |
All three read VITE_API_PRICE_ENGINE_DOMAIN from import.meta.env.
Frontend schemas are defined in packages/shared-type/src/Pack.ts:305-316:
export const DynamicPricingV2Schema = object({
id: number(),
actionType: picklist(["increase", "decrease"]),
valueType: picklist(["fixed", "percentage"]),
value: number(),
});
File Map
| Concern | File |
|---|---|
| Routes | pricing-engine/config/routes.rb:87-103 |
| Controller | pricing-engine/app/controllers/api/v1/pricing_rules_controller.rb:27-156 |
| Service (modifier selection / validation) | pricing-engine/app/services/pricing/modifier_service.rb:95-316 |
| Priority resolution logic | pricing-engine/app/services/pricing/modifier_service.rb:750-843 |
| API change notes | pricing-engine/API_CHANGES_V2.md |
| Request specs | pricing-engine/spec/requests/api/v1/pricing_rules_controller_spec.rb |
| Service specs | pricing-engine/spec/services/pricing/modifier_service_spec.rb |
| Postman collection | pricing-engine/docs/postman/Pricing Engine API.postman_collection.json |