Occupancy Pacing: Calendar Evidence and Rule Matrices

Jun Zhou, Founder at AirROI
by Jun ZhouFounder at AirROI
Published: September 4, 2026
Updated: September 5, 2026

Occupancy pacing changes prices according to how booked the supplied property calendar is within a lead-time window. It is not market-wide occupancy, and it does not treat missing dates as vacant nights.

Read an occupancy matrix visually

Find the intersection: occupancy row × lead-time column

Select a cell to see the adjustment and its standalone nightly price. This expanded teaching matrix is shown in the guide's JSON example.

Occupancy ↓ / lead time →0–4 days5–14 days15–30 days
0–49%
50–79%
80–100%
0–49% occupancy · 0–4 days
$200 × (1 0.1) = $180.00 per eligible available night.
Illustrative $200 post-model price. Complete calendar coverage and at least one reserved or available night are required. Blocked nights are excluded from occupancy.

Select a cell. The row describes how booked that window is; the column describes how far ahead the stay date falls. A cell is a percentage policy, not a target occupancy and not a probability of a booking.

For a 0–4-day window with two reserved nights and three available nights, occupancy is 40%. The upper-left cell applies −10% to eligible available nights: $200 becomes $180. If a third night becomes reserved, occupancy is 60%; the next row applies +5% to the remaining available nights: $200 becomes $210.

That transition does not reprice the already reserved nights with the occupancy rule.

The expanded matrix as request settings

Add this fragment to a complete request with current calendar evidence. It is an alternative to the simpler one-column example later in this guide; do not send two occupancy_pacing objects.

{
  "pricing_rules": {
    "occupancy_pacing": {
      "mode": "custom",
      "settings": {
        "lead_time_ranges": [
          {"min_days": 0, "max_days": 4},
          {"min_days": 5, "max_days": 14},
          {"min_days": 15, "max_days": 30}
        ],
        "occupancy_ranges": [
          {"min_percent": 0, "max_percent": 49},
          {"min_percent": 50, "max_percent": 79},
          {"min_percent": 80, "max_percent": 100}
        ],
        "adjustment_percent": [[-10, -5, 0], [5, 0, 0], [10, 5, 3]]
      }
    }
  }
}

Each window uses its own evidence. A fully supplied 0–4-day window does not prove sufficient coverage for days 5–14. A zero cell is a valid “no adjustment” policy; an uncovered band also produces no occupancy adjustment.

Start with calendar evidence

The JSON below is an addition to this complete Calendar Prices request. Merge fields into one request; do not send a rule fragment by itself.

{
  "location": {
    "latitude": 25.7907,
    "longitude": -80.13
  },
  "currency": "USD",
  "base_price": 200
}

All worked prices use USD and assume a $200 price after model effects, with no other adjustments unless stated. They demonstrate arithmetic, not live forecasts.

Gap-day, adjacent-day, and occupancy-pacing rules need calendar. Each row requires a unique date and a status: available, reserved, or blocked. For this historical illustration, assume today is June 13, 2026:

{
  "calendar": [
    {"date": "2026-06-12", "status": "reserved", "reservation_id": "booking-a"},
    {"date": "2026-06-13", "status": "available", "price": 240, "min_stay": 1,
     "check_in_allowed": true, "check_out_allowed": true},
    {"date": "2026-06-14", "status": "reserved", "reservation_id": "booking-b"},
    {"date": "2026-06-15", "status": "available"},
    {"date": "2026-06-16", "status": "available"},
    {"date": "2026-06-17", "status": "reserved", "reservation_id": "booking-c"}
  ]
}

This creates a one-night gap on June 13 and a two-night gap on June 15–16. Use your current dates in a real request. Send up to 1,000 rows; order does not matter. Missing dates mean unknown, not available.

Optional min_stay (1–365), check_in_allowed, and check_out_allowed help test whether a gap is bookable when no corresponding custom stay rule is set; defaults are one night and both permissions true. price must be positive in the request currency but does not affect calculations. reservation_id must be nonblank and is allowed only on reserved or blocked rows; it is not currently used by pricing rules.

Configure the matrix

{
  "pricing_rules": {
    "occupancy_pacing": {
      "mode": "custom",
      "settings": {
        "lead_time_ranges": [{"min_days": 0, "max_days": 4}],
        "occupancy_ranges": [
          {"min_percent": 0, "max_percent": 49},
          {"min_percent": 50, "max_percent": 100}
        ],
        "adjustment_percent": [[-10], [5]]
      }
    }
  }
}

On June 13, the 0–4-day window is June 13–17: two reserved nights and three available nights. Occupancy is 2 / 5 = 40%, selecting the first row and its −10% adjustment (200 → 180) for available nights in that window. At 50–100% occupancy, the second row would apply +5% (200 → 210).

lead_time_ranges are the matrix columns (0–365 days); occupancy_ranges are its rows (0–100%). Each inner list needs one percentage per column, greater than −100 with at most two decimal places and no fixed upper limit. Bands must be ascending and non-overlapping; uncovered bands do nothing. Occupancy is reserved / (reserved + available), rounded to a whole percentage. Blocked nights are excluded from that ratio.

Every date in an evaluated window must be supplied. Otherwise that window is skipped, its nights are priced without the rule, and the response carries an INCOMPLETE_CALENDAR warning on pricing_rules.occupancy_pacing whose message names the window and how many dates are missing. Even one missing date skips that window, while other complete windows still apply, so send every night of every window before turning the rule on. A boolean availability flag cannot distinguish reserved nights from owner blocks, so supply available, reserved, or blocked.

Read rows and columns correctly

For the one-column example:

Occupancy rowLead time 0–4 days
0–49%−10%
50–100%+5%

At 40% occupancy, the first row supplies −10%. If you add a second lead-time band, add a second number to every inner array. The matrix must remain rectangular, with one row per occupancy band and one column per lead-time band.

Test missing and blocked dates separately

Five-date windowEvidence coverageOccupancyResult
2 reserved, 3 available100%40%−10% on available nights
2 reserved, 2 available, 1 blocked100%50%+5% on available nights
2 reserved, 2 available, 1 missing80%Not usedWindow skipped; INCOMPLETE_CALENDAR warning
5 blocked100%UndefinedNo open night to price, so the window is never evaluated

Blocked nights count as known evidence, but not as occupancy inventory. Whole-percentage rounding uses HALF_UP: 49.5% becomes 50%, which can select a different row.

Before publishing

Read warnings on HTTP 200: INCOMPLETE_CALENDAR on pricing_rules.occupancy_pacing means the rule was skipped on some or all nights, because no calendar was sent or a lead-time window had missing dates, and those nights were priced without it. Decide whether your workflow should accept a model-only recommendation; do not silently assume every requested rule ran.

See warning handling and publishing safeguards.