Skip to main content

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

FieldTypeRequiredDescription
idstringYesUnique identifier
locationIdstring | nullNoLocation scope. Null = all locations
namestringYesRule display name (1-120 chars)
applyLevelenumYesORDER or ITEM (default: ORDER)
isStackablebooleanYesCan combine with other rules (default: true)
prioritynumberYesHigher = applied first (default: 0)
conditionobjectYesMatching conditions
actionobjectYesPricing adjustment
effectiveFromnumberNoStart timestamp (Unix)
effectiveTonumberNoEnd timestamp (Unix)
isActivebooleanYesWhether rule is active (default: true)

Action

FieldRequiredDescription
adjustmentTypeYesPERCENTAGE, FIXED, or OVERRIDE
adjustmentValueYesAdjustment amount (PERCENTAGE cannot exceed 100)
currencyNo3-letter code (default: USD)
maxAdjustmentAmountNoCap on the adjustment amount

Condition

FieldRequiredDescription
allServicesYesApply to all services (default: false)
serviceIdsAnyYesMatch if ANY of these services (default: [])
serviceIdsAllYesMatch only if ALL these services present (default: [])
daysOfWeekNoDays of week (0=Sunday … 6=Saturday)
startMinuteNoStart minute of day (0-1439)
endMinuteNoEnd minute of day (0-1439)
customerSegmentIdsNoTarget customer segment IDs
channelYesALL, 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

  1. Filter rules by isActive: true
  2. Filter by effectiveFrom <= now <= effectiveTo
  3. Filter by locationId match (or null for all locations)
  4. Sort by priority (descending — highest first)
  5. Evaluate conditions in order
  6. For non-stackable rules, stop after the first match
  7. 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
ScenarioRulesResult
Happy hour + loyalty20% (stackable) + 10% (stackable)30% total
Flash sale vs. regular50% (non-stackable, priority 20) + 15% (stackable)50% only
Bundle25% if serviceIdsAll presentApplies only when all are booked