019 - Internal Active Price Modifiers API
Status: ✅ Implementation Complete
Last Updated: September 24, 2026
Version: 1.0
Overview
GET /api/internal/v1/price_modifiers supplies trusted backend consumers with
the active, denormalized PriceModifier records needed to evaluate package
pricing outside Pricing Engine.
The endpoint is deliberately a data source, not a pricing calculator. It does not choose a winning modifier, apply a modifier to a price, or expose the complete database relation graph. The consuming service evaluates package, date/time, demand-capacity, priority, and value semantics from the returned definitions.
Endpoint and Authentication
GET /api/internal/v1/price_modifiers
X-Internal-Api-Key: <INTERNAL_API_KEY>
This is an Internal API endpoint. The key must match INTERNAL_API_KEY; an IP
allowlist may also reject the request when it is enabled by deployment
configuration. It must never be called from a browser or expose the key to a
client application.
Request
restaurant_ids is required and accepts a single ID, comma-separated IDs, or
an array.
GET /api/internal/v1/price_modifiers?restaurant_ids=1631,1640
GET /api/internal/v1/price_modifiers?restaurant_ids[]=1631&restaurant_ids[]=1640
Returned Modifier Selection
The endpoint returns only records meeting all of the following conditions:
- The modifier belongs to one of the requested restaurants.
- The modifier has
status: "active". - Its own end date is open-ended or is today or later.
- If it belongs to a parent
PricingRule, that rule is active and its end date is open-ended or is today or later.
Future start_date values are intentionally returned. This lets a consumer
cache or evaluate a modifier that becomes applicable later. The consumer must
still compare its target date/time with the returned schedule before applying
the modifier.
Snapshot and Pagination
The endpoint intentionally has no pagination. Each requested restaurant returns
its complete matching modifier set so a consumer can atomically replace its
cached snapshot; an empty data result means the cached set must be cleared.
PricingRule Kafka Refresh Event
Pricing Engine publishes an invalidation only after a PricingRule create,
update, or delete commits. This includes a display-only rule_name update. It
does not publish this event for a direct PriceModifier, DateTimeCondition,
or ConditionItem change that does not save the parent PricingRule.
Topic and Key
The topic is derived from the final segment of NAMESPACE; it is not supplied
by a separate environment variable.
NAMESPACE | Topic |
|---|---|
local | local.hh.pricing.modifiers.changed |
hh-engineering | engineering.hh.pricing.modifiers.changed |
| unset | production.hh.pricing.modifiers.changed |
The Kafka record key is the string restaurant_id, preserving order for one
restaurant within a partition.
Event Contract
{
"event_id": "b1e6cc76-39da-45de-bad7-7d70b28bd3ac",
"event_type": "pricing_modifiers.changed",
"schema_version": 1,
"source": "pricing-engine",
"restaurant_id": "1631",
"pricing_rule_id": 45,
"action": "updated",
"occurred_at": "2026-09-25T08:00:00Z"
}
action is one of created, updated, or deleted. The event deliberately
does not contain modifier definitions, package IDs, or price values.
pricing_rule_id is diagnostic context only: consumers always refresh by
restaurant_id.
Consumer Contract
Felidae (or another downstream evaluator) must:
- Subscribe to the namespace-derived topic using its own durable consumer group and read permission.
- On an event, call
GET /api/internal/v1/price_modifierswith that singlerestaurant_idand its server-side internal API key. - Atomically replace the restaurant’s cached modifier snapshot. An empty
dataresponse clears the cache for that restaurant. - Treat repeated or out-of-order deliveries safely. Publishing is retried, so refreshing must be idempotent; the event is a re-fetch signal, not a delta.
Pricing Engine requires producer permission for this topic. The consuming service requires consumer permission and a deployed consumer/subscription; deploying Pricing Engine alone does not start a Felidae consumer.
Response Contract
{
"success": true,
"message": "",
"data": [
{
"id": 123,
"restaurant_id": "1631",
"pricing_rule_id": 45,
"rule_name": "Weekday dinner promotion",
"strategy_type": "specific_date_time",
"priority": 10,
"weight": 50,
"created_at": "2026-09-24T10:00:00Z",
"date_type": "specific_date_time",
"period_mode": "range",
"start_date": "2026-09-23",
"end_date": null,
"is_no_end_date": true,
"start_time": "18:00",
"end_time": "20:00",
"is_no_end_time": false,
"is_anytime": false,
"days_of_week": [1, 2, 3, 4, 5],
"action_type": "decrease",
"value_type": "percentage",
"value": 10.0,
"apply_to_all_packages": false,
"package_ids": ["pkg_a", "pkg_b"],
"demand_capacity": null
}
],
"meta": {
"restaurant_ids": ["1631"],
"count": 1
}
}
Field Groups
| Group | Fields | Consumer use |
|---|---|---|
| Identifier | id, restaurant_id | Stable modifier identity and restaurant ownership. |
| Portal cross-check | pricing_rule_id, rule_name | Link the returned modifier to the Partner Portal/Admin pricing rule for investigation. They do not affect pricing evaluation. |
| Classification and precedence | strategy_type, priority, weight, created_at | Identify the modifier strategy and resolve precedence. |
| Schedule | date_type, period_mode, start_date, end_date, is_no_end_date, start_time, end_time, is_no_end_time, is_anytime, days_of_week | Determine whether the modifier applies at the target date/time. |
| Price change | action_type, value_type, value | Determine how the price changes. |
| Package scope | apply_to_all_packages, package_ids | Determine whether the modifier applies to the package. |
| Capacity scope | demand_capacity | Evaluate a demand-capacity modifier when present. |
demand_capacity, when non-null, contains condition_type, date_type,
period_mode, and capacity_condition_items. Each item provides
relation_type, value_min, value_max, is_no_end_value, and is_anytime.
Intentionally Omitted Fields
The response does not expose database-only relation IDs such as date/time
condition IDs, condition item IDs, demand-capacity condition IDs, or demand
capacity time-window IDs. It also omits rule_type, source, and status:
the first duplicates the strategy classification, while source/status are not
needed after the endpoint has already selected active records.
Evaluation Order
Records are sorted by:
restaurant_idascendingpriorityascendingweightdescendingcreated_atdescendingidascending
The consumer should first select the relevant restaurant and package, evaluate the returned schedule and optional capacity predicates for the target booking, then use this order when resolving applicable modifiers. The endpoint does not define how a consuming service combines multiple applicable modifiers; it only provides the deterministic input order.
Errors
| Status | Meaning |
|---|---|
400 Bad Request | restaurant_ids is missing or blank. |
401 Unauthorized | The API key is missing, invalid, or rejected by the configured IP allowlist. |
500 Internal Server Error | The modifier definitions could not be loaded. |