Skip to main content
Tiers represent ranked progression on participants and groups. Each tier type (e.g., "loyalty", "status") is an independent track with ordered levels. You might define a loyalty track with Silver, Gold, and Platinum levels, or a status track for new, active, and churned members. Tiers can advance automatically based on counter thresholds, or be set directly by rules. Retention modes control how long a tier lasts, and downgrade policies determine what happens when qualification lapses.

Tier Types and Levels

A tier type defines a progression track. Each type contains ordered levels, where rank determines the hierarchy. Higher rank means a higher tier.
POST /v1/programs/{programId}/tiers
{
  "key": "loyalty",
  "display_name": "Loyalty Status",
  "levels": [
    {
      "key": "silver",
      "rank": 1,
      "display_name": "Silver",
      "qualification": {
        "mode": "ALL",
        "criteria": [
          {"counter": "ytd_spend", "operator": ">=", "threshold": 500}
        ]
      },
      "benefits": {"points_multiplier": 1.5}
    },
    {
      "key": "gold",
      "rank": 2,
      "display_name": "Gold",
      "qualification": {
        "mode": "ALL",
        "criteria": [
          {"counter": "ytd_spend", "operator": ">=", "threshold": 2000},
          {"counter": "ytd_nights", "operator": ">=", "threshold": 10}
        ]
      },
      "benefits": {"points_multiplier": 2.0, "lounge_access": true}
    },
    {
      "key": "platinum",
      "rank": 3,
      "display_name": "Platinum",
      "qualification": {
        "mode": "ALL",
        "criteria": [
          {"counter": "ytd_spend", "operator": ">=", "threshold": 5000},
          {"counter": "ytd_nights", "operator": ">=", "threshold": 25}
        ]
      },
      "benefits": {"points_multiplier": 3.0, "lounge_access": true, "suite_upgrade": true}
    }
  ],
  "lifecycle": {
    "retention": {"mode": "PERIOD_BASED", "duration": "8760h"},
    "qualification_period": {"type": "CALENDAR_YEAR"},
    "downgrade_policy": {"mode": "DROP_TO_QUALIFYING", "grace_days": 30},
    "counters": {"qualifying": ["ytd_spend", "ytd_nights"], "rollover": "NONE"}
  }
}

Tier Type Fields

FieldRequiredDescription
keyYesUnique identifier (lowercase alphanumeric and underscores, starts with a letter)
display_nameNoHuman-readable name
levelsYesOrdered list of tier levels
lifecycleNoRetention, qualification, and downgrade configuration. Omit for rules-only tiers.

Level Fields

FieldRequiredDescription
keyYesUnique within the tier type
rankYesInteger determining hierarchy. Higher rank = higher tier. Must be unique within the type.
display_nameNoHuman-readable name
qualificationNoCounter-based criteria for automatic advancement
benefitsNoArbitrary JSON returned with tier state
colorNoHex color code for UI rendering
icon_urlNoIcon URL for UI rendering
Tier types can be scoped to a program or defined at the organization level. Program-specific types take precedence over organization-wide types with the same key.

Qualification

Qualification criteria determine when a participant automatically advances to a tier level. Each criterion checks a participant counter against a threshold.
{
  "mode": "ALL",
  "criteria": [
    {"counter": "ytd_spend", "operator": ">=", "threshold": 2000},
    {"counter": "ytd_nights", "operator": ">=", "threshold": 10}
  ]
}
FieldDescription
modeALL (every criterion must be met) or ANY (at least one)
counterCounter key to evaluate on the participant
operator>=, >, ==, <=, <
thresholdNumeric value to compare against
After all rules fire for an event, Scrip evaluates qualification automatically. If the participant qualifies for a higher-ranked level than their current tier, they advance. Auto-evaluation only upgrades. Downgrades happen through the lifecycle system. If a SET_TIER rule action fires for the same tier type during the same event, auto-evaluation is skipped for that type. This lets rules take explicit control when needed.

Benefits

Each level can carry a benefits object, a freeform JSON payload that Scrip stores and returns whenever you query a participant’s tier state. Use benefits to attach level-specific data that your application acts on: multipliers, feature flags, discount rates, access grants, or anything else tied to the level.
{
  "key": "gold",
  "rank": 2,
  "benefits": {
    "points_multiplier": 2.0,
    "lounge_access": true,
    "support_priority": "high"
  }
}
Scrip does not interpret or enforce the benefits payload. When a participant reaches Gold, their tier state response includes "benefits": {"points_multiplier": 2.0, "lounge_access": true, "support_priority": "high"}. Your application reads these values and applies the corresponding behavior. Benefits are also accessible in rule conditions via participant.tiers.<key>.benefits, so you can write rules that check a participant’s current benefits before taking action:
// Apply multiplier from tier benefits
participant.tiers.loyalty.benefits.points_multiplier

// Gate a rule on a benefit flag
participant.tiers.loyalty.benefits.lounge_access == true

Retention Modes

The retention config in lifecycle controls how long a tier lasts once achieved.
ModeBehavior
PERIOD_BASEDTier is re-evaluated at the end of each qualification period. The participant keeps their tier until the next period boundary.
ACTIVITY_REFRESHTier expires after the specified duration of inactivity. Each event processed for the participant resets the timer.

Qualification Periods

For PERIOD_BASED retention, the qualification period defines the evaluation cycle:
TypeBehavior
CALENDAR_YEARJanuary 1 to December 31
FIXED_YEARCustom start date via start_month and start_day
NONENo periodic re-evaluation
At the end of each period, Scrip fires a tier_evaluation system event via an internal automation that re-evaluates all tiers and applies the downgrade policy.

Activity Refresh

For ACTIVITY_REFRESH, the duration is specified in hours (e.g., "8760h" for one year). The timer restarts on every external event processed for the participant. When the timer expires without new activity, Scrip fires a tier_expiration system event via an internal automation and applies the downgrade policy.

Downgrade Policies

When a tier expires or the qualification period ends, the downgrade policy determines the participant’s new level.
ModeBehavior
DROP_TO_QUALIFYINGFind the highest level where qualification criteria are currently met. If none qualify, the tier is removed entirely.
DROP_ONEDrop exactly one rank below the current level.
HOLDKeep the current tier indefinitely, regardless of qualification.
Most programs use DROP_TO_QUALIFYING. It re-evaluates the participant’s counters at downgrade time and places them at the level they actually qualify for. Set min_level to establish a floor that the participant can never drop below, regardless of qualification.

Grace Periods

Set grace_days on the downgrade policy to defer downgrades. When a downgrade would normally occur, the tier is extended by the grace period instead. If the participant re-qualifies during the grace window, the downgrade is cancelled. If the grace period expires without re-qualification, the downgrade proceeds.

Counter Rollover

The counters config controls what happens to qualifying counters at the end of a qualification period.
RolloverBehavior
NONEQualifying counters reset to 0 at period end
EXCESSQualifying counters carry over the amount above the current tier’s threshold
For example, if the Gold threshold is 2000 and a participant has 2500 at period end, EXCESS rollover sets the counter to 500 for the new period. The qualifying array lists which counter keys are affected by rollover. Counters not in this list are left unchanged.

SET_TIER Rule Action

Rules can assign a tier directly using the SET_TIER action. This is useful for promotions, overrides, or tier logic that goes beyond counter thresholds.
{
  "name": "VIP Override",
  "condition": "event.type == 'vip_granted'",
  "actions": [
    {"type": "SET_TIER", "tier": "loyalty", "level": "platinum", "expiry": "8760h"}
  ]
}
FieldRequiredDescription
tierYesTier type key (e.g., "loyalty")
levelYesLevel key to assign (e.g., "platinum")
expiryNoDuration (e.g., "8760h") or RFC 3339 timestamp. Schedules automatic expiration.
targetNoDefaults to the event’s participant. Use {"type": "GROUP", "group_id": "..."} for group tiers.
When expiry is set, Scrip schedules a tier_expiration system event at that time. If the participant qualifies at expiration, they keep the tier. Otherwise, the downgrade policy applies.

Tier State in CEL

Tier state is available in rule conditions via participant.tiers:
// Check current level
participant.tiers.loyalty.level == "gold"

// Check rank for tier comparisons
participant.tiers.loyalty.rank >= 2

// Access benefits
participant.tiers.loyalty.benefits.points_multiplier

// Check if tier has an expiry
participant.tiers.loyalty.expires != null
Each tier entry exposes:
FieldTypeDescription
levelstringCurrent level key
rankintCurrent level rank
benefitsmapBenefits JSON from the level definition
acquiredstringRFC 3339 timestamp of when the tier was achieved
expiresstringRFC 3339 timestamp of when the tier expires (null if no expiry)
Group tiers are available via groups[0].tiers.

Tier Transitions

Every tier change is recorded as a transition with the previous level, new level, timestamp, and what triggered the change (event ID or rule ID). Query a participant’s tier history:
GET /v1/participants/{id}/state/tiers/{key}/history?program_id=program-uuid

Viewing Tiers

Inspect tier type definitions and participant tier state through the API.
# List tier types for a program
GET /v1/programs/{programId}/tiers

# Get a specific tier type with its levels
GET /v1/programs/{programId}/tiers/{key}

# List a participant's current tiers
GET /v1/participants/{id}/state/tiers?program_id=program-uuid

# Get a participant's specific tier
GET /v1/participants/{id}/state/tiers/{key}?program_id=program-uuid

# Update a tier type and its levels
PATCH /v1/programs/{programId}/tiers/{key}