Update one or more booking sheet exceptions
PUT /api/booking-sheets/exception/{scheduleId}
Success: 200 OK
- Products: Operator (
FOH) - Features: Front of House Generic Access (
FOHGENERIC)
Overview
Rewrites the exceptions produced by an existing recurring schedule. An exception blocks a
resource product for a period of time so that no booking can be taken against it — a maintenance
window, a staff meeting, a closure. A schedule is the recurrence definition that generates
those exceptions, identified by the GUID returned in scheduleId when you created it with
Create one or more booking sheet exceptions.
Send the schedule's definition in full. The request body is the same shape as the create call, and
it replaces the stored definition outright: the schedule's future exception records are deleted and
regenerated from the times and recurrence fields you send. Records that have already finished are
left alone, so past exceptions survive an update. Anything you omit — days, notes, exclusion
dates — is not carried over from the previous definition.
The endpoint expands your times into individual occurrences, one per date per entry in
productIds, and checks them for clashes before writing anything. Nothing is written unless every
check passes; there is no partial update.
Use this endpoint to:
- move or resize a closure that has already been scheduled
- change which resource products a schedule blocks out
- change the recurrence pattern, for example from every week to every other week
- add dates to skip, through
exclusionDates
Read errors and exceptionsUpdated on every 200 OK. A clash sends back 400 (your own
occurrences overlap each other) or 409 (they overlap existing bookings or other schedules'
exceptions), in both cases with the full response body so you can see what collided. But if the
recurrence produces no dates at all — for example days names a weekday that never falls inside
the date range — the call succeeds with 200 OK, exceptionsUpdated: 0, an empty scheduleId and
errors explaining why, and nothing has changed. Treating any 200 as success will leave you
believing a closure is in place when it is not.
Endpoint
PUT {base_url}/api/booking-sheets/exception/{scheduleId}
{base_url} is the environment URL issued to your integration. See Authenticating with Gladstone APIs.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
scheduleId | string | Yes | The schedule whose exceptions you are replacing, as a GUID in 8-4-4-4-12 form. Use the scheduleId returned by Create one or more booking sheet exceptions. An empty value is rejected with Schedule ID must be specified, a value that does not parse as a GUID with Schedule ID is not a valid GUID, and a well-formed GUID with no exception records against it with 404 and No exceptions found for Schedule ID '{scheduleId}'. |
Request body
| Field | Type | Required | Description |
|---|---|---|---|
siteId | string | Yes | The site (leisure centre or venue) the exceptions belong to. Missing or empty is rejected with Site ID must be specified; a value that matches no site with Invalid Site ID '{siteId}'. |
productIds | array of string | Yes | The resource products to block — the bookable items on the booking sheet, such as a court, lane or studio. Every occurrence the recurrence generates is written once for each product listed, so all of them are blocked over the same pattern. At least one is required (One or more Resource Products must be specified, or No Product IDs found in request for an empty array), the list must contain no duplicates (Duplicate Product IDs found in request), and every id must be an existing resource product (One or more Product IDs are invalid '{ids}'). |
days | array of string | No | Restricts the generated occurrences to particular weekdays. Use full English day names, case-insensitive — monday through sunday; abbreviations and anything else are rejected with Invalid day in request. Valid days are: {list}, and repeats with Duplicate Days found in request. Omit it, or send an empty array, to allow every day. What is permitted depends on frequency: at least one day is required for Weekly (At least one day must be specified for Weekly frequency), exactly one for Monthly with recurranceType ByDay, and none at all for Monthly with ByDate (Days cannot be specified when RecurranceType is 'ByDate'). |
times | array of object | Yes | The time bands to block, each with the window over which to repeat. The date parts of an entry bound the recurrence; the time-of-day parts set the start and end of each occurrence generated within it. At least one entry is required — One or more Exception Times must be specified for a missing or empty array, At least one Exception Time must be specified for a null one. This list replaces the schedule's existing times outright. |
times[].timeBandId | string | Yes | The time band to block: a named period of the day configured against the booking sheet. Missing or empty is rejected with Timeband ID must be specified. The band must exist and be configured as entry-restricted, otherwise the request fails with One or more TimeBand IDs do not exist or are not marked as entry restricted '{ids}' — a band that exists but is not entry-restricted produces the same message as one that does not exist at all. |
times[].dateTimeFrom | string (date-time) | No | The start of the window, and the start time of day of each generated occurrence. Marked optional, but omitting it defaults to 0001-01-01T00:00:00, which is rejected with Invalid DateTimeFrom or DateTimeTo for time band {timeBandId}. It must be in the future — DateTimeFrom ({date}) cannot be in the past for time band {timeBandId}. Seconds are rounded to the nearest second before validation. |
times[].dateTimeTo | string (date-time) | No | The end of the window, and the end time of day of each generated occurrence. Marked optional, but rejected with Invalid DateTimeFrom or DateTimeTo for time band {timeBandId} when omitted. It must be later than dateTimeFrom (DateTimeTo ({to}) must be after DateTimeFrom ({from}) for time band {timeBandId}) and its time of day must also be later than that of dateTimeFrom (DateTimeTo time ({hh:mm}) must be after DateTimeFrom time ({hh:mm}) for time band {timeBandId}), so a single occurrence cannot cross midnight — split an overnight closure into one entry per day. Each stored occurrence ends one second before this time of day, so consecutive blocks do not register as overlapping. |
notes | string | No | Free-text note copied onto every exception this request generates, for whoever looks at the booking sheet later. An empty string is saved as null. |
frequency | string | No | How the window in each times entry is expanded into occurrences: Daily, Weekly or Monthly, matched case-insensitively. Defaults to Daily when omitted; any other value is rejected with Invalid Frequency '{value}', must be one of 'Daily', 'Weekly', 'Monthly'. It also decides which of days, recursEvery, recurranceType and recursOn are allowed — each of those is rejected outright under the wrong frequency, rather than ignored. |
recursEvery | integer (int32) | No | The gap between occurrences: weeks for Weekly, months for Monthly. Defaults to 1 for those two frequencies, and must be greater than zero (RecursEvery must be greater than zero). For Daily it must be omitted or 1 — any other value is rejected with RecursEvery cannot be specified for Daily frequency. |
recurranceType | string | No | For Monthly only, how the monthly date is chosen — note the spelling of the field. ByDate repeats on the same day of the month as the first times entry's dateTimeFrom, clamped to the last day in shorter months; ByDay repeats on the nth named weekday, combining recursOn with the single entry in days. Matched case-insensitively; an unrecognised value gives Invalid RecurranceType '{value}', must be one of 'ByDate', 'ByDay'. Required for Monthly (RecurranceType must be specified for Monthly frequency) and forbidden otherwise (RecurranceType cannot be specified for Daily frequency, ... for Weekly frequency). |
recursOn | integer (int32) | No | Which occurrence of the weekday within the month, for Monthly with recurranceType ByDay — 3 together with days: ["friday"] means the third Friday. Must be between 1 and 5 (RecursOn must be between 1 and 5) and is required in that combination (RecursOn must be specified when RecurranceType is 'ByDay'). It is rejected everywhere else: RecursOn cannot be specified when RecurranceType is 'ByDate', RecursOn cannot be specified for Daily frequency, RecursOn cannot be specified for Weekly frequency. |
exclusionDates | array of string | No | Dates to skip. Any generated occurrence falling on one of these dates is dropped; only the date part is compared, so the time you send is irrelevant. Note that Delete booking sheet exceptions within a date range appends the deleted dates to the stored schedule's exclusion list — because this call overwrites the stored definition, omitting exclusionDates here silently reinstates dates that were previously deleted. |
additionalDates | array of string | No | Accepted and stored with the schedule definition, but no occurrences are generated from it — only times combined with the recurrence fields produce exception records. To block a one-off extra date, add another entry to times. |
Sample request
curl -X PUT '{base_url}/api/booking-sheets/exception/{scheduleId}' \
-H 'Authorization: Bearer {accessToken}' \
-H 'X-Use-SSO: true' \
-H 'Content-Type: application/json' \
-d '{
"siteId": "string",
"productIds": [
"string"
],
"days": [
"string"
],
"times": [
{
"timeBandId": "string",
"dateTimeFrom": "2026-08-14T19:00:00Z",
"dateTimeTo": "2026-08-14T19:00:00Z"
}
],
"notes": "string",
"frequency": "string",
"recursEvery": 1,
"recurranceType": "string",
"recursOn": 1,
"exclusionDates": [
"2026-08-14T19:00:00Z"
],
"additionalDates": [
"2026-08-14T19:00:00Z"
]
}'Response fields
| Field | Type | Description |
|---|---|---|
exceptionsUpdated | integer (int32) | How many exception records were written — one per generated date per entry in productIds, so a fortnight of weekday closures over three products is 30. 0 means nothing was written and the schedule is unchanged; check errors when you see it. |
scheduleId | string (uuid) | The schedule the new exception records belong to. It is the same GUID you sent in the path when records were written, and 00000000-0000-0000-0000-000000000000 when none were. |
errors | array of string | Why the update did not proceed. Empty on a successful update. The possible entries are No valid dates found for the specified days, One or more exceptions overlap with each other, One or more exceptions overlap with existing exceptions for the same product(s) and One or more exceptions overlap with existing bookings for the same product(s). If this array is non-empty, nothing was written — including on a 200 OK, which is what you get when the only entry is No valid dates found for the specified days. |
overlappingExceptions | array of object | Occurrences generated from your own request that collide with each other — for example two times entries covering the same time band on the same day. Accompanies a 400, and is empty otherwise. Fix the request; no existing data is involved. |
overlappingExceptions[].dateTimeFrom | string (date-time) | Start of the generated occurrence that was found to overlap another one in the same request. |
overlappingExceptions[].dateTimeTo | string (date-time) | End of that occurrence — the time band's end time of day, less one second. |
overlappingExceptions[].timeBandId | string | The time band the overlapping occurrence was generated for. |
overlappingExceptions[].productId | string | Always null here. Self-overlap is detected on the generated dates before they are multiplied out across productIds, so no single product can be named; the collision applies to every product in the request. |
existingExceptions | array of object | Exceptions already stored against the same resource products that overlap the occurrences you asked for. Accompanies a 409, and is empty otherwise. Exceptions belonging to this schedule are excluded, so a schedule never clashes with the records it is replacing — anything listed here belongs to a different schedule and must be dealt with before the update can succeed. |
existingExceptions[].dateTimeFrom | string (date-time) | Start of the exception already in place. |
existingExceptions[].dateTimeTo | string (date-time) | End of the exception already in place. |
existingExceptions[].timeBandId | string | The time band the existing exception was recorded against. |
existingExceptions[].productId | string | The resource product the existing exception blocks — one of the ids you sent in productIds. |
existingBookings | array of object | Bookings already taken against the same resource products that overlap the occurrences you asked for. Accompanies a 409, and is empty otherwise. The update cannot proceed over a live booking: cancel or move the bookings listed here, then retry. |
existingBookings[].bookingId | integer (int32) | The id of the clashing booking, for looking it up or cancelling it. |
existingBookings[].dateTimeFrom | string (date-time) | Start of the clashing booking. |
existingBookings[].dateTimeTo | string (date-time) | End of the clashing booking. |
existingBookings[].timeBandId | string | Always null for bookings. A booking is recorded against a date and time rather than a time band, so this field is not populated in this array. |
existingBookings[].productId | string | The resource product the clashing booking occupies — one of the ids you sent in productIds. |
Sample response
{
"exceptionsUpdated": 1,
"scheduleId": "b7f3c1e2-9a44-4f2b-8c17-0d5e9a1b6c33",
"errors": [
"string"
],
"overlappingExceptions": [
{
"dateTimeFrom": "2026-08-14T19:00:00Z",
"dateTimeTo": "2026-08-14T19:00:00Z",
"timeBandId": "string",
"productId": "string"
}
],
"existingExceptions": [
{
"dateTimeFrom": "2026-08-14T19:00:00Z",
"dateTimeTo": "2026-08-14T19:00:00Z",
"timeBandId": "string",
"productId": "string"
}
],
"existingBookings": [
{
"bookingId": 12345,
"dateTimeFrom": "2026-08-14T19:00:00Z",
"dateTimeTo": "2026-08-14T19:00:00Z",
"timeBandId": "string",
"productId": "string"
}
]
}Errors
| Status | Meaning | When |
|---|---|---|
400 | Bad request | The path parameter is unusable: Schedule ID must be specified or Schedule ID is not a valid GUID |
400 | Bad request | A required value is missing: Site ID must be specified, One or more Resource Products must be specified, No Product IDs found in request, One or more Exception Times must be specified, At least one Exception Time must be specified or Timeband ID must be specified |
400 | Bad request | A value does not resolve: Invalid Site ID '{siteId}', One or more Product IDs are invalid '{ids}', One or more TimeBand IDs do not exist or are not marked as entry restricted '{ids}', Duplicate Product IDs found in request, Invalid day in request. Valid days are: {list} or Duplicate Days found in request |
400 | Bad request | The dates are not usable: Invalid DateTimeFrom or DateTimeTo for time band {timeBandId}, DateTimeTo ({to}) must be after DateTimeFrom ({from}) for time band {timeBandId}, DateTimeTo time ({hh:mm}) must be after DateTimeFrom time ({hh:mm}) for time band {timeBandId} or DateTimeFrom ({from}) cannot be in the past for time band {timeBandId} |
400 | Bad request | The recurrence fields contradict frequency: Invalid Frequency '{value}', must be one of 'Daily', 'Weekly', 'Monthly', RecursEvery cannot be specified for Daily frequency, RecursEvery must be greater than zero, RecursOn cannot be specified for Daily frequency, RecurranceType cannot be specified for Daily frequency, At least one day must be specified for Weekly frequency, RecursOn cannot be specified for Weekly frequency, RecurranceType cannot be specified for Weekly frequency, RecurranceType must be specified for Monthly frequency, Invalid RecurranceType '{value}', must be one of 'ByDate', 'ByDay', RecursOn must be specified when RecurranceType is 'ByDay', RecursOn must be between 1 and 5, Exactly 1 day must be specified when RecurranceType is 'ByDay', Invalid day name '{day}', RecursOn cannot be specified when RecurranceType is 'ByDate' or Days cannot be specified when RecurranceType is 'ByDate' |
400 | Bad request | The occurrences generated from your request overlap each other. The body is the full response object, not a message string, with overlappingExceptions listing the collisions and errors containing One or more exceptions overlap with each other. Nothing was written. |
400 | Bad request | The write itself failed: A database error occurred. |
404 | Not found | No exception records exist for the schedule in the path: No exceptions found for Schedule ID '{scheduleId}'. This is also what you get for a schedule whose exceptions have all been deleted, so it does not distinguish "never existed" from "emptied". |
409 | Conflict | The occurrences you asked for overlap bookings already taken, or exceptions belonging to another schedule, on the same resource products. The body is the full response object, with existingBookings and existingExceptions naming what collided. Nothing was written; resolve the clashes and retry. |
500 | Internal server error | An unexpected server-side failure. Every validation failure returns 400, 404 or 409, so a 500 here is not something to work around in the request. |