Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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

EndpointMethodPurposeCaller
/api/v1/packages_pricing_rules/:restaurant_idGETBest rules per package for restaurant/package cards.hh-pegasus-monorepo (restaurant detail, package cards)
/api/v1/get_available_rules_by_datePOSTBest rules per package for a date range.hh-pegasus-monorepo (date picker)
/api/v1/get_available_rules_by_timePOSTRules per 15-minute slot for a single date.hh-pegasus-monorepo (time picker)
/api/v1/verify_active_rulesPOSTValidate that a selected rule is still active at reservation time.hh-server (booking creation/update)
/api/v1/get_modifier_historyPOSTReturn 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

FieldTypeRequiredDescription
restaurant_idstring/numberYesRestaurant identifier.

Query parameter

FieldTypeRequiredDescription
restaurant_package_idsarray/stringYesComma-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_date and end_date are required.
  • restaurant_package_ids is 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):

  1. For each date × package, scan every 15-minute time slot.
  2. Resolve priority winners per slot.
  3. From those winners, pick up to 2 best offers: 1 percentage + 1 fixed.
  4. 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):

  • date is required.
  • restaurant_package_ids is required and non-blank.
  • date must parse as YYYY-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):
    1. Lowest priority value
    2. Highest weight
    3. Newest created_at

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_found
  • not_applicable_to_package
  • not_active_on_date
  • not_active_on_day
  • not_active_at_time
  • values_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:

FileEndpoint
packages/shared-api/src/restaurant/getAvailableRulesByDate.ts:59-120POST /api/v1/get_available_rules_by_date
packages/shared-api/src/restaurant/getAvailableRulesByTime.ts:52-110POST /api/v1/get_available_rules_by_time
packages/shared-api/src/restaurant/getPackagePricingRules.ts:38-102GET /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

ConcernFile
Routespricing-engine/config/routes.rb:87-103
Controllerpricing-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 logicpricing-engine/app/services/pricing/modifier_service.rb:750-843
API change notespricing-engine/API_CHANGES_V2.md
Request specspricing-engine/spec/requests/api/v1/pricing_rules_controller_spec.rb
Service specspricing-engine/spec/services/pricing/modifier_service_spec.rb
Postman collectionpricing-engine/docs/postman/Pricing Engine API.postman_collection.json