> For the complete documentation index, see [llms.txt](https://docs.akinon.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.akinon.com/tutorials/commerce/how-to-schedule-basket-offer-windows.md).

# How to Schedule Basket Offer Windows

A basket offer normally runs over one fixed date range: it turns on at its start date, off at its end date, and that is the whole of its life. A campaign that is supposed to come back — every evening, every weekend, on the first of each month — has to be either recreated each time or left running and switched on and off by hand.

**Scheduling** replaces that single range with a rule and lets Commerce work out the dates for you, over and over, for as long as the rule holds. Once the rule is right nobody needs to touch the offer again.

This guide is about deciding **what** to schedule and **how** the rule should be shaped. For the exact request and response bodies, field types and validation responses, see **Admin → Basket Offers** in the Commerce OpenAPI reference.

{% hint style="info" %}
Scheduling needs a background routine to be running on your deployment. If it is not, any attempt to save a schedule is rejected and nothing is stored — see [7. Troubleshooting](#id-7-troubleshooting). Ask your platform team to enable it before you start.
{% endhint %}

## <mark style="color:red;">1. Overview</mark> <a href="#id-1-overview" id="id-1-overview"></a>

A schedule is attached to a basket offer — one schedule per offer, either on creation or on an offer that already exists. From it, Commerce continuously works out **windows**: the stretches of time during which the offer should be live. It keeps two of them at hand, the window the offer is in now and the one coming up next, and hands the offer over from one to the next on its own.

A schedule is built from three things, and you can use any one of them or all three together:

| Field        | Description                                                                                          |
| ------------ | ---------------------------------------------------------------------------------------------------- |
| `cron`       | The repeating pattern — "every weekday at 09:00", "every Saturday".                                  |
| `inclusions` | Individual date ranges that are always part of the schedule, whether or not the pattern covers them. |
| `exclusions` | Blackout ranges that are always cut out of it.                                                       |

Two more fields bound and anchor the whole thing: `timezone` decides what local time the pattern is read in — `UTC` unless you set it — and `start_date` / `end_date` decide the period over which the schedule produces anything at all.

Key characteristics of scheduling:

* **The offer's own dates are managed for you.** The current window is written onto the offer's start and end date, so everything that already reads those dates — the storefront, the Campaigns list, reports — keeps working unchanged. In exchange those two dates become read-only: while a schedule is attached, any attempt to set them by hand is rejected.
* **The handover is automatic.** A routine checks every minute whether a window has closed and moves the offer onto the next one when it has. Nobody has to be watching.
* **The status stays yours.** Scheduling never changes an offer's status. A scheduled offer sells only while its status is `active` *and* the current moment falls inside its window.
* **Nothing is applied retroactively.** A schedule describes when the offer is live from now on. Changing it never rewrites what happened in windows that have already closed.

## <mark style="color:red;">2. Do You Need a Schedule?</mark> <a href="#id-2-do-you-need-a-schedule" id="id-2-do-you-need-a-schedule"></a>

Scheduling earns its keep when a campaign has a shape in time. When it does not, plain start and end dates are simpler and easier for everyone to read.

| What the campaign does                                              | How to set it up                                 |
| ------------------------------------------------------------------- | ------------------------------------------------ |
| Runs once, continuously, between two dates                          | Plain start and end dates — no schedule          |
| Comes back on a pattern (daily, weekly, monthly)                    | A schedule with a recurrence rule                |
| Runs on a handful of dates somebody picked                          | A schedule with inclusions only                  |
| Runs continuously but must go quiet on certain days                 | A schedule with an always-on rule and exclusions |
| Comes back on a pattern, plus a few extra dates, minus a few others | A schedule using all three sources               |

{% hint style="info" %}
An always-on rule with nothing cut out of it is just an offer with a start date and no end date. If there is no pattern and no blackout to express, do not reach for a schedule.
{% endhint %}

## <mark style="color:red;">3. Shaping the Recurrence Rule</mark> <a href="#id-3-shaping-the-recurrence-rule" id="id-3-shaping-the-recurrence-rule"></a>

The rule is a standard 5-field cron expression. Each field says when the rule applies:

```
 ┌── minute        (0-59, or * for every minute)
 │ ┌── hour          (0-23)
 │ │ ┌── day-of-month (1-31)
 │ │ │ ┌── month        (1-12)
 │ │ │ │ ┌── day-of-week (0-6, where 0 is Sunday)
 │ │ │ │ │
 0 9 * * 1-5   ← every weekday at 09:00
```

Each field takes a single value (`5`), a list (`1,4`), a range (`9-17`), a step (`*/15`), or `*` for "every". The `L`, `W` and `#n` shorthands that some cron dialects support are not accepted — if you need "the last day of the month" or "the second Tuesday", list those dates as inclusions instead.

### <mark style="color:red;">3.1. From moments to windows</mark>

A cron expression on its own describes *moments*, not stretches of time. There are two ways to turn those moments into windows, and you do not choose between them with a flag — it follows from the **minute** field.

| The minute field is...               | What the rule means                                                                               | `duration`       |
| ------------------------------------ | ------------------------------------------------------------------------------------------------- | ---------------- |
| A single minute — `0`, `30`          | Each match **opens** a window, and `duration` says how long it stays open                         | Required         |
| Exactly `*` — as in `* 9-17 * * 1-5` | The rule describes every minute the offer is live; the window length follows from the rule itself | Must be left out |

Both describe simple daily patterns equally well, so pick whichever reads more plainly. Beyond those:

* **Windows that span days need the first style.** No minute-matching rule can express "Friday 23:00 until Monday 00:00" — you need a start moment plus a long duration.
* **Windows that are really a run of hours read better in the second.** `* 9-16 * * 1-5` says "weekdays, business hours" more plainly than a start time plus a nine-hour duration.

{% hint style="warning" %}
An hour range does not make a window on its own. `0 20-22 * * *` names three separate moments — 20:00, 21:00 and 22:00 — so it opens three windows a day, not one stretch from 20:00 to 22:00. For a single 20:00–22:00 window, use `0 20 * * *` with a two-hour duration, or `* 20-21 * * *` with no duration at all.
{% endhint %}

{% hint style="warning" %}
**Keep the minute field to a single value.** A window-opening rule fires at most once per matching hour, so listing several minutes — `0,30` — or stepping through them — `*/15` — does not open several windows in that hour. Only the earliest of those minutes takes effect: `*/15 9-16 * * 1-5` behaves as `0 9-16 * * 1-5`. Several *hours* are fine (`0 12,19 * * *` opens two windows a day); it is only minutes within one hour that collapse. Sub-hourly patterns cannot be expressed today — use one window per hour, or list the slots as inclusions.
{% endhint %}

### <mark style="color:red;">3.2. Expressing a duration</mark>

Durations are written in the ISO 8601 format — a `P`, then a number of days, then a `T` followed by hours, minutes and seconds:

| You want                                         | Write     |
| ------------------------------------------------ | --------- |
| 15 minutes                                       | `PT15M`   |
| 90 minutes                                       | `PT1H30M` |
| 2 hours                                          | `PT2H`    |
| 9 hours                                          | `PT9H`    |
| 1 day                                            | `P1D`     |
| 1 day and 6 hours                                | `P1DT6H`  |
| 2 days and 6 hours (Friday 18:00 → Monday 00:00) | `P2DT6H`  |
| 1 week                                           | `P7D`     |

Only days, hours, minutes and seconds are accepted. There is no designator for weeks, months or years, so express those in days — a week is `P7D`, not `P1W`.

### <mark style="color:red;">3.3. How the three sources come together</mark>

When a schedule uses more than one source, Commerce combines them in a fixed order every time it works out a window:

1. The recurrence rule produces its stream of windows.
2. Inclusions add their windows on top.
3. Windows from those two steps that overlap or touch are **merged into one**.
4. Exclusions are cut out of every merged window — splitting a window in two where a blackout lands inside it.
5. `end_date` trims any window that would run past it.

{% hint style="warning" %}
Step 3 is a merge, not an intersection. Where a rule window and an inclusion overlap, the offer runs across **both**: a rule saying Monday 09:00–17:00 plus an inclusion saying Monday 06:00–10:00 leaves the offer live from 06:00 to 17:00 — the earlier start and the later end. Inclusions can only ever widen a schedule. To narrow one, use exclusions.
{% endhint %}

Two related rules to keep in mind while writing the ranges: inclusions must not overlap **each other** — merge them into one range before saving — while exclusions are free to overlap.

## <mark style="color:red;">4. Common Scenarios</mark> <a href="#id-4-common-scenarios" id="id-4-common-scenarios"></a>

Each scenario below lists exactly what to set. Anything not listed should be left unset — remember that a schedule is stored as a whole, so a field you leave out is a field at its default, not one kept from before.

`timezone` is the field worth setting deliberately. Left unset it is `UTC`, so a rule written for local hours runs at those hours in UTC — in Türkiye that puts the campaign three hours off. Set it whenever the recurrence rule names a time of day; it is listed below wherever that applies.

{% hint style="warning" %}
`timezone` applies to the recurrence rule and to nothing else. Inclusion and exclusion ranges are absolute moments, so they are **not** shifted by it: with `timezone` set to `Europe/Istanbul`, an inclusion written as `10:00Z` still starts at 10:00 UTC, which is 13:00 in Istanbul. Write those ranges with the offset you mean — `10:00+03:00` — or convert them to UTC yourself.
{% endhint %}

### <mark style="color:red;">4.1. A happy hour, every evening</mark>

**The campaign:** a discount that runs from 17:00 to 19:00, every day.

| Field      | Set it to                                      |
| ---------- | ---------------------------------------------- |
| `cron`     | `* 17-18 * * *`                                |
| `duration` | leave unset                                    |
| `timezone` | e.g. `Europe/Istanbul` — `UTC` when left unset |

A run of hours every day, so the minute-matching style reads best. The hour range is the part worth checking: the window runs to the end of the last matching hour, so `17-18` gives you 17:00 to 19:00 — not `17-19`.

**Watch out for:** writing `0 17-19 * * *` instead. That names three moments and opens three windows a day.

### <mark style="color:red;">4.2. A weekday campaign, business hours only</mark>

**The campaign:** live Monday to Friday between 09:00 and 18:00, nothing at the weekend.

| Field      | Set it to                                      |
| ---------- | ---------------------------------------------- |
| `cron`     | `0 9 * * 1-5`                                  |
| `duration` | `PT9H`                                         |
| `timezone` | e.g. `Europe/Istanbul` — `UTC` when left unset |

`* 9-17 * * 1-5` with no duration gives the same window and reads more plainly if the campaign is really about opening hours. Prefer the start-plus-duration form when the start moment is what matters.

**Watch out for:** the two forms are not interchangeable once a duration is stored. If you switch the rule to the minute-matching form, drop the duration in the same change — a minute-matching rule with a duration attached is rejected.

### <mark style="color:red;">4.3. Two slots a day — lunch and dinner</mark>

**The campaign:** one hour at midday and one hour in the evening, every day.

| Field      | Set it to                                      |
| ---------- | ---------------------------------------------- |
| `cron`     | `0 12,19 * * *`                                |
| `duration` | `PT1H`                                         |
| `timezone` | e.g. `Europe/Istanbul` — `UTC` when left unset |

Listing several **hours** works exactly as you would expect: this opens 12:00–13:00 and 19:00–20:00 every day. Both slots have to be the same length, because one schedule carries one duration.

**Watch out for:** wanting slots of different lengths, or more than one slot inside the same hour. Neither fits one rule — see 4.12, and the note on the minute field in 3.1.

### <mark style="color:red;">4.4. An overnight window that crosses midnight</mark>

**The campaign:** live from 22:00 until 02:00 the next morning.

| Field      | Set it to                                      |
| ---------- | ---------------------------------------------- |
| `cron`     | `0 22 * * *`                                   |
| `duration` | `PT4H`                                         |
| `timezone` | e.g. `Europe/Istanbul` — `UTC` when left unset |

The date rolls over on its own: each firing at 22:00 stays live until 02:00 the following day, as one window rather than two.

`* 22-1 * * *` with no duration gives the same window. A descending hour range is read as the set 22, 23, 0, 1, and the window runs straight through midnight to the end of the last matching hour — so this is one of the few places where either form works for a window that changes date.

**Watch out for:** the end of that hour range. `* 22-2 * * *` is 22:00 to **03:00**, not 02:00 — the last hour you list is fully live. If counting that through midnight feels error-prone, use the start-plus-duration form, where the end is explicit.

### <mark style="color:red;">4.5. A weekend-long campaign</mark>

**The campaign:** live from Friday evening straight through to Monday morning.

| Field      | Set it to                                      |
| ---------- | ---------------------------------------------- |
| `cron`     | `0 18 * * 5`                                   |
| `duration` | `P2DT6H`                                       |
| `timezone` | e.g. `Europe/Istanbul` — `UTC` when left unset |

This window spans days, so it has to be a start moment plus a duration. Count the hours rather than the days when you write it — Friday 18:00 to Monday 00:00 is 54 hours, which is two days and six hours, not three days.

**Watch out for:** trying to express it as `* * * * 5,6,0`. That is live from Friday 00:00, not Friday evening.

### <mark style="color:red;">4.6. The first of every month</mark>

**The campaign:** a payday promotion, all day on the 1st.

| Field      | Set it to                                      |
| ---------- | ---------------------------------------------- |
| `cron`     | `0 0 1 * *`                                    |
| `duration` | `P1D`                                          |
| `timezone` | e.g. `Europe/Istanbul` — `UTC` when left unset |

The day-of-month field carries the pattern and the duration covers the day. Leave the day-of-week field as `*` — setting both day fields narrows the schedule to dates that satisfy each of them.

### <mark style="color:red;">4.7. A seasonal campaign</mark>

**The campaign:** weekday mornings, but only for this summer.

| Field        | Set it to                                      |
| ------------ | ---------------------------------------------- |
| `cron`       | `0 10 * * 1-5`                                 |
| `duration`   | `PT4H`                                         |
| `start_date` | first day of the season                        |
| `end_date`   | last day of the season                         |
| `timezone`   | e.g. `Europe/Istanbul` — `UTC` when left unset |

The pattern goes in the rule and the season goes in the bounds. The schedule produces nothing outside them, and a window that would straddle the end date is trimmed to it.

**Watch out for:** putting the season in the rule's month field instead. `0 10 * 6-8 *` does describe summer, but it comes back every year — right for an annual campaign, wrong for a one-off season.

### <mark style="color:red;">4.8. A campaign that starts later</mark>

**The campaign:** a pattern that should not begin until a launch date some months away.

| Field        | Set it to                                      |
| ------------ | ---------------------------------------------- |
| `cron`       | your pattern                                   |
| `duration`   | as the pattern needs                           |
| `start_date` | the launch date                                |
| `timezone`   | e.g. `Europe/Istanbul` — `UTC` when left unset |

Nothing is produced before the start date, and the first window reported back is the first one on or after it — so you can set the whole thing up now and check it reads correctly, months ahead of launch.

**Watch out for:** leaving the offer's status as `active` and assuming that makes it live. It is the window that decides, and until the launch date there is no window covering now.

### <mark style="color:red;">4.9. A permanent campaign that goes quiet on holidays</mark>

**The campaign:** always on, except on public holidays and during a maintenance slot.

| Field        | Set it to                                        |
| ------------ | ------------------------------------------------ |
| `cron`       | `* * * * *`                                      |
| `duration`   | leave unset                                      |
| `exclusions` | one range per holiday, plus the maintenance slot |

This is the one case where an always-on rule earns its place — without exclusions it is just an offer with no end date.

**Watch out for:** nothing, really — a blackout in the middle of a live stretch splits it rather than cancelling it, which is exactly what you want here. A half-day holiday leaves the offer live either side of it.

### <mark style="color:red;">4.10. A campaign on a handful of dates</mark>

**The campaign:** the Black Friday weekend, Christmas, and New Year's Eve. No pattern at all.

| Field        | Set it to           |
| ------------ | ------------------- |
| `cron`       | leave unset         |
| `duration`   | leave unset         |
| `inclusions` | one range per event |

The offer is live across those ranges and inactive at every other time.

**Watch out for:** overlapping ranges. A Christmas range and a New Year range that both cover 31 December are rejected — merge them into one range that covers both.

### <mark style="color:red;">4.11. A recurring campaign with a few extra dates</mark>

**The campaign:** the usual weekday pattern, plus the whole Black Friday weekend, minus New Year's Day.

| Field        | Set it to                                      |
| ------------ | ---------------------------------------------- |
| `cron`       | `0 9 * * 1-5`                                  |
| `duration`   | `PT8H`                                         |
| `inclusions` | the Black Friday weekend                       |
| `exclusions` | New Year's Day                                 |
| `timezone`   | e.g. `Europe/Istanbul` — `UTC` when left unset |

All three sources in one schedule. There is no need to work the extra dates into the pattern; they are added on top of it and merged.

### <mark style="color:red;">4.12. Different hours at the weekend</mark>

**The campaign:** 09:00–17:00 on weekdays but 10:00–16:00 at the weekend.

| Field        | Set it to                                               |
| ------------ | ------------------------------------------------------- |
| `cron`       | `0 9 * * 1-5`                                           |
| `duration`   | `PT8H`                                                  |
| `inclusions` | one 10:00–16:00 range per weekend day you want to cover |
| `timezone`   | e.g. `Europe/Istanbul` — `UTC` when left unset          |

One schedule carries one rule and one duration, so two different daily shapes cannot both be patterns. Put the shape that repeats in the rule and list the other one as inclusions.

**Watch out for:** how far ahead this covers. Inclusions are fixed dates, so they run out — either list enough weekends to cover the campaign, or run two separate offers, each with its own pattern.

### <mark style="color:red;">4.13. A pattern cron cannot express</mark>

**The campaign:** every other Tuesday, or the last day of every month.

| Field        | Set it to                |
| ------------ | ------------------------ |
| `cron`       | leave unset              |
| `duration`   | leave unset              |
| `inclusions` | one range per occurrence |

Cron has no notion of "every other" or "the last one", and the `L`, `W` and `#n` shorthands other dialects use for this are not accepted. Work out the dates and list them.

**Watch out for:** `0 9 * * */2` looking like "every other week". A step on the day-of-week field steps through the days themselves, not the weeks — it means Sunday, Tuesday, Thursday and Saturday.

### <mark style="color:red;">4.14. A campaign in a specific local time</mark>

**The campaign:** live at 09:00 local time in the market it serves, whatever the server is set to.

| Field      | Set it to                                      |
| ---------- | ---------------------------------------------- |
| `cron`     | your pattern, written in local time            |
| `timezone` | e.g. `Europe/Istanbul` — `UTC` when left unset |

The rule is read in that local time, so the campaign keeps its wall-clock hours across daylight-saving changes instead of drifting by an hour twice a year.

**Watch out for:** the dates that come back are always in UTC, whatever zone you set, so a 09:00 Istanbul window reads as `06:00Z`. Zone abbreviations like `EST` and fixed offsets like `+03:00` are not accepted — the point of a zone name is that it knows about daylight saving, and an offset does not.

### <mark style="color:red;">4.15. Making an existing campaign recurring</mark>

**The campaign:** an offer that has been running on fixed dates should now come back on a pattern.

| Field                             | Set it to                                       |
| --------------------------------- | ----------------------------------------------- |
| `cron`                            | your pattern                                    |
| `duration`                        | as the pattern needs                            |
| `timezone`                        | e.g. `Europe/Istanbul` — `UTC` when left unset  |
| `start_datetime` / `end_datetime` | do not send them — the schedule takes them over |

Attach a schedule to the offer as it stands. Its dates stop being yours to set from that moment and are overwritten with the first window the rule produces, so check that window is where you expect before saving — especially if the offer is live right now.

**Watch out for:** an offer whose status is `used` or `revoked` cannot be scheduled. If the campaign is finished, schedule a fresh offer instead of reviving that one.

## <mark style="color:red;">5. What the Schedule Reports Back</mark> <a href="#id-5-what-the-schedule-reports-back" id="id-5-what-the-schedule-reports-back"></a>

Reading an offer back — after saving it, or when retrieving or listing offers — returns its schedule together with what it has worked out. Four dates describe the windows and one describes the handover:

| What it reports    | How to read it                                                                                                                                     |
| ------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------- |
| The current window | The window the offer is in — or, in a gap between windows, the one it is waiting for. These are the dates mirrored onto the offer itself.          |
| The next window    | The window after that one. Empty when the schedule has nothing further to offer.                                                                   |
| The next update    | When the offer will next be re-evaluated and handed over — normally the end of the current window. Empty once there is nothing further to move to. |

This is the quickest way to check that a rule says what you meant. If a rule is meant to make the offer live tomorrow morning and the current window starts next month, the rule is wrong — and you can see that before anyone shops against it. See **Admin → Basket Offers** in the Commerce OpenAPI reference for the field names and an example response.

A few things worth knowing when reading this back:

* **Windows are worked out up to a year ahead** of the point the schedule is read from — which is the start date when that is still in the future, so a schedule set up well in advance still reports its first window correctly.
* **An empty set of windows means the schedule has nothing left.** It happens when the end date has passed, when every inclusion is in the past, or when the rule matches nothing in the coming year. The offer keeps the dates of its last window and quietly stops being live once they pass.
* **Durations read back differently from how you wrote them.** They are returned as days, hours, minutes and seconds rather than in the ISO format, so a nine-hour duration comes back as `09:00:00`.

## <mark style="color:red;">6. Living with a Schedule</mark> <a href="#id-6-living-with-a-schedule" id="id-6-living-with-a-schedule"></a>

### <mark style="color:red;">6.1. Changing a schedule</mark>

Saving a schedule works out the current and next windows again straight away, and the offer's dates follow immediately.

{% hint style="warning" %}
**Always send the whole rule, not just the part you are changing.** A schedule is stored as a whole, so anything left out of the change goes back to its default — the rule to empty, the duration and end date to nothing, inclusions and exclusions to empty lists, the timezone back to UTC. The start date is the one exception and keeps the value it already had.
{% endhint %}

In practice this means adding a single blackout is not a one-line change: resend the rule, the duration and the timezone alongside the new exclusion, or you will silently drop them. Read the offer first, change what you need in what comes back, and send that.

### <mark style="color:red;">6.2. Removing a schedule</mark>

Clear the schedule and the offer goes back to manual control. You can hand it fresh start and end dates in the same change — and that is the only way to give those dates a value on an offer that currently has a schedule. Sending a date on its own, without clearing the schedule, is rejected.

### <mark style="color:red;">6.3. Pausing a scheduled offer</mark>

Set the offer's status to `passive`. It stops selling immediately, whatever its window says, while the schedule carries on advancing in the background — so setting the status back to `active` picks up on whichever window is current by then. This is the right way to pause a scheduled campaign; editing the rule to route around today is not.

Two consequences worth knowing:

* An offer with a schedule is **not** switched to `passive` when one of its windows closes. Waiting for the next window is normal, so the routine that retires expired offers leaves scheduled ones alone.
* Because the status is never touched by the schedule, an offer left `passive` stays `passive` through every window until somebody activates it.

## <mark style="color:red;">7. Troubleshooting</mark> <a href="#id-7-troubleshooting" id="id-7-troubleshooting"></a>

Nothing is stored when a change is rejected — an existing schedule is left exactly as it was.

| Message                                                                                   | What it means                                                                                                                                                    |
| ----------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `Scheduling is not available on this deployment. Contact the platform team to enable it.` | The background routine that drives schedules is not running here, so a schedule would never fire.                                                                |
| `Provide at least one of cron or inclusions.`                                             | The schedule has no rule and no explicit dates, so there is nothing to work out a window from. Usually the result of sending a partial change — see section 6.1. |
| `duration is required when cron fires at specific minutes.`                               | The rule opens windows at named minutes, so it needs to know how long each one lasts.                                                                            |
| `Do not set duration when cron uses minute-matching mode (minute=*).`                     | The rule already describes which minutes are live, so a duration would contradict it. Drop the duration, or name a minute in the rule.                           |
| `cron must have 5 space-separated fields.`                                                | Count the fields: minute, hour, day-of-month, month, day-of-week.                                                                                                |
| `Duration has wrong format.`                                                              | The duration could not be read. Check it against the table in section 3.2 — `P1W` is a common cause, since weeks are not accepted.                               |
| `Ranges must not overlap.`                                                                | Two inclusions cover some of the same time. Merge them into a single range.                                                                                      |
| `start must be before end`                                                                | One of the ranges ends at or before it starts.                                                                                                                   |
| `end_date must be after start_date.`                                                      | The bounds are the wrong way round, so the schedule could never produce a window.                                                                                |
| `Unknown timezone.`                                                                       | Not a recognised IANA zone name. Use a full name such as `Europe/Istanbul`.                                                                                      |
| `Managed by scheduler; clear scheduler to set start_datetime/end_datetime manually.`      | The offer's dates belong to its schedule. Clear the schedule in the same change to take them back — see section 6.2.                                             |
| `Cannot attach a scheduler to a USED or REVOKED offer.`                                   | The offer has reached the end of its life and cannot be scheduled.                                                                                               |

If a schedule saves cleanly but the offer is not live when you expect, work through these in order:

1. **Check the offer's status.** A `passive` offer never sells, whatever its window says.
2. **Check the current window it reports.** If it is not the window you meant, the rule is wrong — not the schedule.
3. **Check the timezone.** A rule written for local time but left on UTC is the most common cause of an offer that goes live a few hours out.
4. **Check the bounds.** A start date in the future or an end date in the past both leave a perfectly good rule producing nothing.
5. **Check the exclusions.** A blackout that is wider than you thought can swallow a whole window.

## <mark style="color:red;">8. Reference</mark> <a href="#id-8-reference" id="id-8-reference"></a>

In the Commerce OpenAPI reference:

* **Admin → Basket Offers** — every schedule field, the request and response bodies, the rule-shape examples, and the validation responses

Elsewhere in the documentation:

* [How to Create and Manage Campaigns in Omnitron](/tutorials/omnitron/sales-channels/marketing/how-to-create-and-manage-campaigns-in-omnitron.md) — managing the offers themselves in Omnitron


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.akinon.com/tutorials/commerce/how-to-schedule-basket-offer-windows.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
