Create one or more booking sheet exceptions
POST /api/booking-sheets/exception
- Products: Operator (
FOH) - Features: Front of House Generic Access (
FOHGENERIC)
Overview
Blocks out time on a booking sheet, so no new booking can be taken against the named resource products during the periods you describe — maintenance, a closure, a school hire, anything that takes the resource off sale.
One request describes a recurring pattern, not a single block. You give a window
(times[].dateTimeFrom to times[].dateTimeTo), a frequency of Daily, Weekly or Monthly,
and the recurrence fields that go with that frequency; the endpoint expands the pattern into
individual exceptions — one per generated date, per entry in times, per entry in productIds —
and creates them all together under a single schedule id, returned as scheduleId. Keep that
id: it is how you later amend or remove the whole set, with
Delete booking sheet exceptions within a date range.
Nothing is created unless everything can be created. Before creating anything, the endpoint checks the generated dates against each other, against exceptions that already exist and against bookings that have already been taken:
- exceptions that overlap each other come back as
400, with the pairs listed inoverlappingExceptions; - clashes with existing exceptions or bookings come back as
409, listed inexistingExceptionsandexistingBookings, each with the date range, time band and product involved — enough to fix the request or to cancel the offending bookings first.
Both bodies are the same shape as the success body, so you can read errors in every case.
One case to guard against: if the pattern generates no dates at all — every occurrence fell on
an excluded date, or on a weekday not listed in days — the call still returns 201 Created, but
with exceptionsCreated of 0 and No valid dates found for the specified days in errors.
Check exceptionsCreated rather than trusting the status code.
Exceptions cannot be created in the past, and each generated block ends one second before the
time of day you give in dateTimeTo, so consecutive blocks butt up against each other rather than
overlapping.
Endpoint
POST {base_url}/api/booking-sheets/exception
{base_url} is the environment URL issued to your integration. See Authenticating with Gladstone APIs.
Request body
| Field | Type | Required | Description |
|---|---|---|---|
siteId | string | Yes | The site whose booking sheet is being blocked out; every product in productIds is interpreted within it. Required, and must be an existing site — an unknown value fails with Invalid Site ID '{siteId}', as a 400 rather than a 404. Omitting it fails with Site ID must be specified. |
productIds | array of string | Yes | The resource products to take off sale — a court, a lane, a studio. The whole generated pattern is applied to each one, so a fortnight's closure across three courts is one request, not three. 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 not repeat itself (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 pattern to particular weekdays. Use full English day names — monday through sunday, matched case-insensitively; anything else fails with Invalid day in request. Valid days are: …, and repeats with Duplicate Days found in request. What it means depends on frequency: with Daily, leaving it out means every day and listing days keeps only those; with Weekly at least one day is required (At least one day must be specified for Weekly frequency); with Monthly and recurranceType of ByDay exactly one is required (Exactly 1 day must be specified when RecurranceType is 'ByDay'); with Monthly and ByDate it must be left out entirely (Days cannot be specified when RecurranceType is 'ByDate'). |
times | array of object | Yes | The time bands to block and the window each pattern runs over. At least one entry is required (One or more Exception Times must be specified, or At least one Exception Time must be specified for an empty array). Each entry is expanded independently, so listing three time bands over a four-week daily pattern across two products creates 3 × 28 × 2 exceptions in one go. |
times[].timeBandId | string | Yes | The time band on the booking sheet to block — take it from timeBandId in Get timebands. The band must exist and be marked as entry restricted; if any band in the request fails either test the whole request is rejected with One or more TimeBand IDs do not exist or are not marked as entry restricted '{ids}'. Omitting it fails with Timeband ID must be specified. |
times[].dateTimeFrom | string (date-time) | Yes | Does double duty: the date the pattern starts, and the time of day every generated block begins at. Required in practice — omitting it fails with Invalid DateTimeFrom or DateTimeTo for time band {timeBandId}. It must be in the future (DateTimeFrom ({value}) cannot be in the past for time band {timeBandId}), earlier than dateTimeTo, and its time of day must be earlier than dateTimeTo's, which is what stops a block spanning midnight. For a restriction that crosses midnight, send one entry for each side of it. With frequency of Monthly and recurranceType of ByDate, the day number of the first entry in times is the day of the month the pattern repeats on. |
times[].dateTimeTo | string (date-time) | Yes | The date the pattern stops and the time of day every generated block ends at. Required in practice, on the same grounds as dateTimeFrom, and subject to the same two comparisons. Note that each block ends one second before this time of day — give 18:00 and the exception runs to 17:59:59 — so a block ending at 18:00 and the next starting at 18:00 do not count as overlapping. |
notes | string | No | Free text stored against every exception this request creates, for whoever looks at the booking sheet later — the reason for the closure, a job number, a contact. It has no effect on availability, and is not used in clash detection. |
frequency | string | Yes | How the pattern repeats: Daily, Weekly or Monthly, matched case-insensitively. Required in practice even though it is not marked as required — omitting it fails with Invalid Frequency '', must be one of 'Daily', 'Weekly', 'Monthly'. It decides which of the other recurrence fields are allowed: Daily accepts only days; Weekly requires days and accepts recursEvery; Monthly requires recurranceType and accepts recursEvery, with recursOn and days governed by that type. |
recursEvery | integer (int32) | No | How many periods to skip between occurrences: with Weekly, 2 means every other week; with Monthly, every other month. Defaults to 1 when omitted for those two frequencies, and must be greater than zero (RecursEvery must be greater than zero). Meaningless for Daily — send it as anything other than 1 and the request fails with RecursEvery cannot be specified for Daily frequency. |
recurranceType | string | No | Monthly only, and required there (RecurranceType must be specified for Monthly frequency). ByDate repeats on the same day number each month, taken from dateTimeFrom of the first entry in times and clamped to the length of shorter months, so the 31st becomes the 30th in April. ByDay repeats on the nth named weekday of the month — the second Tuesday, say — set through recursOn and days. Matched case-insensitively; any other value fails with Invalid RecurranceType '{value}', must be one of 'ByDate', 'ByDay'. Sending it with Daily or Weekly fails with RecurranceType cannot be specified for {frequency} frequency. Note the spelling of the field name — one r, two ns. |
recursOn | integer (int32) | No | Which occurrence of the weekday in the month to use, when recurranceType is ByDay: 1 for the first, through 5 for the fifth. Required in that case (RecursOn must be specified when RecurranceType is 'ByDay') and limited to that range (RecursOn must be between 1 and 5). If a month has no fifth occurrence the pattern moves forward to the next month that does, rather than falling back to the last one. Must be left out everywhere else — with Daily, with Weekly, and with recurranceType of ByDate — each of which has its own rejection message. |
exclusionDates | array of string | No | Dates to drop from the generated pattern — bank holidays, a week the works were cancelled. Only the date part is compared, so the time you send is ignored, and a date that the pattern would not have produced anyway is simply not used. If exclusions remove every occurrence, the request returns 201 having created nothing; see the Overview. |
additionalDates | array of string | No | Accepted but not used: dates listed here are silently discarded and no extra exceptions are created for them. To block a date the pattern does not reach, send a second request. |
Sample request
curl -X POST '{base_url}/api/booking-sheets/exception' \
-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"
]
}'Errors
| Status | Meaning | When |
|---|---|---|
400 | Bad request | A field is missing or malformed: Site ID must be specified, One or more Resource Products must be specified, One or more Exception Times must be specified, Timeband ID must be specified, Invalid DateTimeFrom or DateTimeTo for time band {timeBandId}. The body is a plain string. |
400 | Bad request | A date range is unusable: DateTimeTo ({to}) must be after DateTimeFrom ({from}) for time band {timeBandId}, DateTimeTo time ({to}) must be after DateTimeFrom time ({from}) for time band {timeBandId}, or DateTimeFrom ({from}) cannot be in the past for time band {timeBandId}. |
400 | Bad request | The recurrence fields do not match the frequency: Invalid Frequency …, RecursEvery cannot be specified for Daily frequency, At least one day must be specified for Weekly frequency, RecurranceType must be specified for Monthly frequency, RecursOn must be between 1 and 5, and the other combination rules listed against each field above. |
400 | Bad request | A list is malformed: No Product IDs found in request, Duplicate Product IDs found in request, Invalid day in request. Valid days are: …, Duplicate Days found in request. |
400 | Bad request | A referenced record does not exist: Invalid Site ID '{siteId}', One or more Product IDs are invalid '{ids}', or One or more TimeBand IDs do not exist or are not marked as entry restricted '{ids}'. These are 400, not 404. |
400 | Bad request | The exceptions the pattern generated overlap each other. The body is the full response object, with the offending pairs in overlappingExceptions and One or more exceptions overlap with each other in errors. Nothing was created. |
400 | Bad request | Saving the exceptions failed — A database error occurred. Please ensure that the ID supplied is unique. |
404 | Not found | This endpoint does not return 404: every missing site, product and time band is reported as 400. |
409 | Conflict | The generated exceptions clash with exceptions or bookings that already exist. The body is the full response object: existingExceptions and existingBookings name the date range, time band and product for each clash, and existingBookings also carries the bookingId you would need to cancel. Nothing was created — resolve the clashes and send the request again. |
500 | Internal server error | An unexpected server-side failure, with an empty body. |