Pricing Rules
Service pricing rules apply dynamic adjustments based on conditions such as time of day, channel, and which services are booked.
Reference
This page documents the schema for service pricing rules. The SDK example guides do not include create/update calls for this domain.
- Time-based promotions — happy hour, seasonal discounts
- Channel-specific pricing — different prices online vs. walk-in
- Service targeting — all services, any of a set, or a required bundle
- Priority ordering — control which rules take precedence
- Stackable rules — combine multiple discounts or make them exclusive
ServicePricingRule
| Field | Type | Required | Description |
|---|---|---|---|
id | string | Yes | Unique identifier |
locationId | string | null | No | Location scope. Null = all locations |
name | string | Yes | Rule display name (1-120 chars) |
applyLevel | enum | Yes | ORDER or ITEM (default: ORDER) |
isStackable | boolean | Yes | Can combine with other rules (default: true) |
priority | number | Yes | Higher = applied first (default: 0) |
condition | object | Yes | Matching conditions |
action | object | Yes | Pricing adjustment |
effectiveFrom | number | No | Start timestamp (Unix) |
effectiveTo | number | No | End timestamp (Unix) |
isActive | boolean | Yes | Whether rule is active (default: true) |
Action
| Field | Required | Description |
|---|---|---|
adjustmentType | Yes | PERCENTAGE, FIXED, or OVERRIDE |
adjustmentValue | Yes | Adjustment amount (PERCENTAGE cannot exceed 100) |
currency | No | 3-letter code (default: USD) |
maxAdjustmentAmount | No | Cap on the adjustment amount |
Condition
| Field | Required | Description |
|---|---|---|
allServices | Yes | Apply to all services (default: false) |
serviceIdsAny | Yes | Match if ANY of these services (default: []) |
serviceIdsAll | Yes | Match only if ALL these services present (default: []) |
daysOfWeek | No | Days of week (0=Sunday … 6=Saturday) |
startMinute | No | Start minute of day (0-1439) |
endMinute | No | End minute of day (0-1439) |
customerSegmentIds | No | Target customer segment IDs |
channel | Yes | ALL, DIRECT, ONLINE, PHONE, or WALK_IN (default: ALL) |
You cannot combine allServices: true with serviceIdsAny or serviceIdsAll. Use serviceIdsAny for "match any" and serviceIdsAll for bundle discounts.
Example — weekday happy hour
{
"id": "spr_happy_hour",
"locationId": null,
"name": "Happy Hour - 20% Off All Services",
"applyLevel": "ORDER",
"isStackable": false,
"priority": 10,
"condition": {
"allServices": true,
"serviceIdsAny": [],
"serviceIdsAll": [],
"daysOfWeek": [1, 2, 3, 4, 5],
"startMinute": 840,
"endMinute": 1020,
"channel": "ALL"
},
"action": { "adjustmentType": "PERCENTAGE", "adjustmentValue": 20, "currency": "USD" },
"effectiveFrom": 1704067200,
"effectiveTo": 1735689600,
"isActive": true
}
Example — bundle discount (all required)
{
"allServices": false,
"serviceIdsAny": [],
"serviceIdsAll": ["svc_haircut", "svc_blowdry"],
"channel": "ALL"
}
Rule evaluation
- Filter rules by
isActive: true - Filter by
effectiveFrom <= now <= effectiveTo - Filter by
locationIdmatch (or null for all locations) - Sort by
priority(descending — highest first) - Evaluate conditions in order
- For non-stackable rules, stop after the first match
- For stackable rules, continue and combine discounts
Apply level
- ORDER — applies once to the entire appointment/order (e.g. "20% off your visit")
- ITEM — applies to each matching service line (e.g. "$5 off each haircut")
Stackability
- Stackable (
isStackable: true) — multiple rules can apply and combine - Non-stackable (
isStackable: false) — only the highest-priority match applies
| Scenario | Rules | Result |
|---|---|---|
| Happy hour + loyalty | 20% (stackable) + 10% (stackable) | 30% total |
| Flash sale vs. regular | 50% (non-stackable, priority 20) + 15% (stackable) | 50% only |
| Bundle | 25% if serviceIdsAll present | Applies only when all are booked |