---
title: "Create YieldRules"
method: POST
path: "/shop/yield-rules"
tags: ["YieldRules"]
---

# Create YieldRules

`POST /shop/yield-rules`

Creates a `YieldRule` for each offering supplied.

Rules are per-offering. Applying one definition to several offerings is a
convenience at creation only: it produces an independent rule for each, so
editing or deleting one afterwards leaves the rest untouched, and offerings
created later do not pick the rule up.

Every offering must belong to the site and have a standard price for the
adjustment to work from, and they must all be the same `offering_type`: what a
rule may contain differs between types, since an appointment has a
practitioner to measure utilisation against and an area booking type does not.
If any offering is rejected, no rules are created.

Selections of more than 200 offerings are written by a background job and
answered with `202` and the `created_batch_id` the rules will carry, rather
than holding the response open for several seconds. Poll the list endpoint
with that id to follow them appearing.

A rule that lowers a price needs the offering to say when reductions may
start. Send `reduction_advance_interval` to set that window on every offering
the rule is created for, or leave it out where they are already configured.
Creating a rule is the only time it can be set through this resource; changing
it afterwards is done from the offering.

Requires the `advanced_yield_management` feature and the same permission as
editing an offering's price rules.

## Request body

- object
  - `site_id` string, uuid, required — The site the rules belong to.
  - `offerings` YieldRuleOfferingIdentifier[], required — The offerings to apply this rule to. One independent rule is created for each, so editing one afterwards does not affect the others. Repeating an offering creates a single rule for it. Each offering must belong to `site_id` and have a standard price for the adjustment to work from, and they must all share one `offering_type` — a single rule cannot span types, because what a rule may contain differs between them. More than 200 offerings are created by a background job and answered with `202`.
    - `offering_type` 'appointment' | 'area_booking' | 'session' | 'package', required — The kind of offering a rule applies to. Limited to offerings booked against a service date, which is what demand is measured over.
    - `offering_id` string, uuid, required
  - `weekdays` Weekday[], required
  - `time_from` string, nullable, required — 24-hour `HH:MM`, in the site's local time. Send `null` for both this and `time_to` for a rule that applies all day; one without the other is rejected.
  - `time_to` string, nullable, required — 24-hour `HH:MM`, in the site's local time. Must differ from `time_from`. An earlier value means the window runs past midnight into the following day, and each selected weekday is the day its window starts on — Monday with `22:30`–`00:30` runs Monday 22:30 to Tuesday 00:30. Send `null` for both this and `time_from` for a rule that applies all day.
  - `triggers` YieldRuleTriggers, required — The demand conditions under which a rule fires. At least one band must be set. When both are set the rule fires if either is satisfied, so setting both widens a rule rather than narrowing it.
    - `practitioner_utilisation` UtilisationBand, required — A percentage band a demand measurement is tested against. The band is half-open: `from` is inclusive and `to` is exclusive, so consecutive bands can share a boundary without both matching it. Given `0–30` and `30–60`, a measurement of exactly 30 matches only the second. A `to` of 100 is the exception and is inclusive, so a top band of `60–100` matches a fully utilised site rather than leaving 100 uncovered. "Only when completely full" is expressed by leaving `to` unset, which is unbounded upwards, rather than by `100`–`100`. Because the upper bound is exclusive, bounds that meet describe nothing and are rejected — `30`–`30` would never apply. An absent bound is unbounded in that direction. Both bounds `null` means the band is not set.
      - `from` integer, nullable, required — Lowest percentage this band matches, inclusive.
      - `to` integer, nullable, required — Upper bound of the band, exclusive — unless it is 100, which is inclusive. Must be greater than or equal to `from` when both are set.
    - `room_occupancy` UtilisationBand, required — A percentage band a demand measurement is tested against. The band is half-open: `from` is inclusive and `to` is exclusive, so consecutive bands can share a boundary without both matching it. Given `0–30` and `30–60`, a measurement of exactly 30 matches only the second. A `to` of 100 is the exception and is inclusive, so a top band of `60–100` matches a fully utilised site rather than leaving 100 uncovered. "Only when completely full" is expressed by leaving `to` unset, which is unbounded upwards, rather than by `100`–`100`. Because the upper bound is exclusive, bounds that meet describe nothing and are rejected — `30`–`30` would never apply. An absent bound is unbounded in that direction. Both bounds `null` means the band is not set.
      - `from` integer, nullable, required — Lowest percentage this band matches, inclusive.
      - `to` integer, nullable, required — Upper bound of the band, exclusive — unless it is 100, which is inclusive. Must be greater than or equal to `from` when both are set.
  - `adjustment` YieldRuleAdjustment, required — The change a rule makes to the price it applies to. Signed, so a rule can discount to shift demand as well as add a premium at peak.
    - `type` 'amount' | 'percentage', required — Whether the adjustment is a fixed amount in minor units, or a percentage of the price it applies to.
    - `value` integer, required — Integer minor units when `type` is `amount`, matching `standard_price`; whole percent when `type` is `percentage`.
  - `rounding` 'one' | 'five' | 'ten', nullable — Rounds the adjusted price to the nearest whole unit of currency. `null` leaves the adjusted price unrounded.
  - `reporting_tier` 'low' | 'high' | 'peak', required — Groups yielded sales in reporting. Descriptive only — it never affects the adjustment a rule makes. Sales made when no rule applied report as an implicit standard tier, which is why it is absent here. The [yielded sales report](/endpoints/Reports#listYieldedSales) lists and totals sales by this tier.
  - `reduction_advance_interval` string — How far ahead of a slot a rule that lowers the price may start applying, as an ISO-8601 duration. The window belongs to the offering rather than the rule: every rule that lowers an offering's price shares the one window. Sending it here sets it on all of the offerings the rule is being created for, overwriting whatever each already had, so a value set from one offering can be replaced by a rule created across many. A rule that lowers a price cannot exist on an offering with no window, so one must either be sent here or already be configured on every offering listed. Rules that raise a price ignore the window and need not send it. Omit the key to leave each offering's window as it is. It cannot be sent empty: a window is taken away from the offering itself, where the check that no rule still depends on it lives.

## Response `201`

The yield rules were successfully created. One rule is returned per offering
supplied, each independent of the others.

- object
  - `data` YieldRule[], required
    - `id` string, uuid, required
    - `offering_type` 'appointment' | 'area_booking' | 'session' | 'package', required — The kind of offering a rule applies to. Limited to offerings booked against a service date, which is what demand is measured over.
    - `offering_id` string, uuid, required — The offering this rule adjusts the price of.
    - `site_id` string, uuid, required
    - `created_batch_id` string, uuid, nullable, required — Shared by every rule created in the same request. The rules remain independent of one another — editing one does not affect the others — so this only records that they were created together.
    - `weekdays` Weekday[], required — The days of the week this rule applies on.
    - `time_from` string, nullable, required — Start of the daily window this rule applies within, in the site's local time. 24-hour `HH:MM`. Each selected weekday is the day its window starts on. Null alongside a null `time_to` means the rule applies all day.
    - `time_to` string, nullable, required — End of the daily window this rule applies within, in the site's local time. 24-hour `HH:MM`. A value earlier than `time_from` means the window runs past midnight into the following day. Null alongside a null `time_from` means the rule applies all day.
    - `triggers` YieldRuleTriggers, required — The demand conditions under which a rule fires. At least one band must be set. When both are set the rule fires if either is satisfied, so setting both widens a rule rather than narrowing it.
      - `practitioner_utilisation` UtilisationBand, required — A percentage band a demand measurement is tested against. The band is half-open: `from` is inclusive and `to` is exclusive, so consecutive bands can share a boundary without both matching it. Given `0–30` and `30–60`, a measurement of exactly 30 matches only the second. A `to` of 100 is the exception and is inclusive, so a top band of `60–100` matches a fully utilised site rather than leaving 100 uncovered. "Only when completely full" is expressed by leaving `to` unset, which is unbounded upwards, rather than by `100`–`100`. Because the upper bound is exclusive, bounds that meet describe nothing and are rejected — `30`–`30` would never apply. An absent bound is unbounded in that direction. Both bounds `null` means the band is not set.
        - `from` integer, nullable, required — Lowest percentage this band matches, inclusive.
        - `to` integer, nullable, required — Upper bound of the band, exclusive — unless it is 100, which is inclusive. Must be greater than or equal to `from` when both are set.
      - `room_occupancy` UtilisationBand, required — A percentage band a demand measurement is tested against. The band is half-open: `from` is inclusive and `to` is exclusive, so consecutive bands can share a boundary without both matching it. Given `0–30` and `30–60`, a measurement of exactly 30 matches only the second. A `to` of 100 is the exception and is inclusive, so a top band of `60–100` matches a fully utilised site rather than leaving 100 uncovered. "Only when completely full" is expressed by leaving `to` unset, which is unbounded upwards, rather than by `100`–`100`. Because the upper bound is exclusive, bounds that meet describe nothing and are rejected — `30`–`30` would never apply. An absent bound is unbounded in that direction. Both bounds `null` means the band is not set.
        - `from` integer, nullable, required — Lowest percentage this band matches, inclusive.
        - `to` integer, nullable, required — Upper bound of the band, exclusive — unless it is 100, which is inclusive. Must be greater than or equal to `from` when both are set.
    - `adjustment` YieldRuleAdjustment, required — The change a rule makes to the price it applies to. Signed, so a rule can discount to shift demand as well as add a premium at peak.
      - `type` 'amount' | 'percentage', required — Whether the adjustment is a fixed amount in minor units, or a percentage of the price it applies to.
      - `value` integer, required — Integer minor units when `type` is `amount`, matching `standard_price`; whole percent when `type` is `percentage`.
    - `rounding` 'one' | 'five' | 'ten', nullable, required — Rounds the adjusted price to the nearest whole unit of currency. `null` leaves the adjusted price unrounded.
    - `reporting_tier` 'low' | 'high' | 'peak', required — Groups yielded sales in reporting. Descriptive only — it never affects the adjustment a rule makes. Sales made when no rule applied report as an implicit standard tier, which is why it is absent here. The [yielded sales report](/endpoints/Reports#listYieldedSales) lists and totals sales by this tier.
    - `created_at` string, date-time, required
    - `updated_at` string, date-time, required — When several rules could apply to the same slot, the most recently updated one wins.

## Other responses

- `202` — The selection was too large to write during the request, so the rules are being created by a background job. Poll the list endpoint with `created_batch_id` to follow them appearing.
- `401` — The user is unauthenticated
- `403` — The authenticated user does not have permission.
- `404` — The resource couldn't be found
- `422` — The request didn't pass validation

---

[API](https://skmtc.dev/try/apis/trybe-api.md) · [All operations](https://skmtc.dev/try/apis/trybe-api/llms.txt) · [OpenAPI document](https://skmtc-service-production.skmtc.workers.dev/v1/apis/try/trybe-api/revisions/f37f92702da5/schema)
