Create a block booking schedule with recurring reservations

POST /api/bookings/block-bookings

Success: 200 OK

Overview

Books the same slot repeatedly for one member in a single call. You describe the series — a start and end date-time, a frequency, and optionally which days of the week — and the endpoint expands it into individual bookings, creates an activity to hold them, and raises a sales invoice per booking.

The expansion works like this: dateTimeFrom and dateTimeTo supply both the range of the series and the time-of-day of every occurrence, so a slot cannot cross midnight. frequency then decides how the range is walked, and days, recursEvery, recurranceType and recursOn refine it. Dates listed in exclusionDates are dropped.

You must price the series yourself. products carries one entry per product per occurrence, and every date the expansion produces must be covered by at least one entry, or the call fails. Send checkOnly=true first to expand and clash-check the series without writing anything.

Three failure shapes are worth designing for, and all of them return the full response body rather than a message:

On success the call returns 201 Created (or 200 OK for a checkOnly run) with the scheduleId that identifies the series for later updates and deletes.

Block bookings are invoiced, not paid at the till: the payment method must collect through an invoicing account and the member must have one configured, otherwise the request is rejected.

Use this endpoint to:

Endpoint

POST {base_url}/api/bookings/block-bookings

{base_url} is the environment URL issued to your integration. See Authenticating with Gladstone APIs.

Query parameters

NameTypeRequiredDescription
checkOnlybooleanNoWhen true, performs validation and conflict checking only without creating bookings. Returns 200 OK if no conflicts are found.

Request body

FieldTypeRequiredDescription
siteIdstringYesThe site the series belongs to. Must be non-empty (Site ID must be specified) and must exist (Invalid Site ID '{siteId}'). It also determines the default payment method used when paymentMethodId is omitted, and the counters that allocate booking ids and booking references.
memberIdinteger (int32)YesThe member every booking in the series, and every invoice raised for it, belongs to. Must be 1 or greater (Member ID must be specified) and must exist (Invalid Member ID '{memberId}'). Their name is used as each booking's display name and their surname supplies the default description. Where the payment method collects through an invoicing account, that account must exist on this member.
productsarray of objectNoThe priced lines that become each occurrence's sales invoice, one entry per product per date. Required whenever bookings are actually created (One or more Products must be specified); the per-product checks are skipped when checkOnly=true, but the time bands in it are still validated, so send it either way. Every date the series generates must be covered by at least one entry, otherwise the call fails with One or more booking dates have no matching products: {dates}. Entries whose date matches no generated occurrence are ignored.
products[].datestring (date-time)NoWhich occurrence this line belongs to. Sent as a real UTC date-time, but only the calendar date is matched against the occurrence's start date. A missing value is rejected with One or more products are missing a Date. It is re-based using dateTimeFrom's UTC offset, so a series crossing a daylight-saving change still lines up with the occurrences it generates.
products[].productIdstringNoThe product invoiced on that date. Must be an existing standard product (One or more Product IDs are invalid '{ids}'). The same product may repeat across different dates, but not twice on the same date — that is rejected with Duplicate product entries for the same date: {product} on {date}.
products[].quantityinteger (int32)NoHow many units of the product to invoice for that occurrence. Must be greater than zero (One or more products have an invalid quantity). The invoice line total is price multiplied by this.
products[].pricenumber (double)NoThe unit amount invoiced for the line. Must be zero or greater (One or more products have an invalid price). The series is not priced for you — the value you send is used on the invoice line as-is, and the invoice total is the sum of price × quantity across the lines for that occurrence.
products[].taxCodeIdstringNoThe tax code the line is charged under. Required on every entry (One or more products are missing a Tax Code ID); its rate is applied and both are recorded on the invoice line.
products[].timeBandIdstringNoThe time band the line is charged under, recorded on the invoice line. Required on every entry (One or more products are missing a Time Band ID) and must be an existing time band (One or more Time Band IDs are invalid '{ids}').
activityGroupIdstringNoThe activity group the activity created for this series is filed under. Required whenever bookings are actually created (Activity Group ID must be specified) and must exist (Invalid Activity Group ID '{id}'); both checks are skipped when checkOnly=true.
frequencystringYesHow the series repeats: Daily, Weekly or Monthly, matched case-insensitively. Anything else is rejected with Invalid Frequency '{value}', must be one of 'Daily', 'Weekly', 'Monthly'. This choice decides which of recursEvery, recurranceType and recursOn are permitted — sending one that does not apply is an error rather than being ignored.
recursEveryinteger (int32)NoThe gap between repeats: every N weeks for Weekly, every N months for Monthly. Defaults to 1 when omitted and must be greater than zero. Must not be sent for Daily, which always steps a day at a time (RecursEvery cannot be specified for Daily frequency).
recurranceTypestringNoOnly for frequency: Monthly, where it is mandatory (RecurranceType must be specified for Monthly frequency). ByDate repeats on the same day of the month as dateTimeFrom and forbids recursOn and days; ByDay repeats on the nth named weekday of the month and requires both. Any other value is rejected with Invalid RecurranceType '{value}', must be one of 'ByDate', 'ByDay'. Must not be sent for Daily or Weekly.
recursOninteger (int32)NoWhich occurrence of the weekday within the month, 1 to 5. Required, and only allowed, when frequency is Monthly and recurranceType is ByDay; a value outside 1–5 is rejected with RecursOn must be between 1 and 5. Must not be sent for Daily, Weekly or ByDate.
paymentMethodIdstringNoHow the invoices raised for the series are collected. Omit it and the site's default host payment method is used. A supplied value must exist (Invalid Payment Method ID '{id}'). Whether supplied or defaulted, it must be a method that collects through an invoicing account and the member must have that account configured — otherwise the request fails with Payment method '{id}' is not an invoicing account. Block bookings must be paid via an invoicing account., returned as a 400.
resourceProductIdsarray of stringYesThe resource products reserved on every occurrence in the series — the things being booked out, as opposed to the products being charged for. At least one is required (One or more Resource Products must be specified), duplicates are rejected (Duplicate Resource Product IDs found in request) and each must exist (One or more Resource Product IDs are invalid '{ids}'). These are also what clash detection runs against: an existing booking or schedule exception on any of them, overlapping any generated occurrence, blocks the request.
daysarray of stringNoRestricts the series to particular days of the week. Use full English day names, matched case-insensitively (monday, tuesday, …); an unrecognised name is rejected with Invalid day in request and repeats with Duplicate Days found in request. Omit it, or send an empty array, and every date in range that the frequency produces is used. For frequency: Monthly it is required with recurranceType: ByDay and forbidden with ByDate.
dateTimeFromstring (date-time)YesStart of the series, as a real UTC date-time. Its date is the first date an occurrence can fall on; its time of day is the start time of every occurrence. It must be supplied (Invalid DateTimeFrom or DateTimeTo) and must be earlier than dateTimeTo. Its UTC offset is also the reference used to interpret dateTimeTo, exclusionDates and products[].date, which keeps every occurrence at the same wall-clock time across a daylight-saving change.
dateTimeTostring (date-time)YesEnd of the series, as a real UTC date-time. Its date is the last date an occurrence can fall on; its time of day is the end time of every occurrence. It must be later than dateTimeFrom both overall and by time of day, so a single occurrence cannot run past midnight (DateTimeTo time ({time}) must be after DateTimeFrom time ({time})); model an overnight slot as separate series.
exclusionDatesarray of stringNoDates to skip — a closure day or a holiday, for instance. Only the calendar date is compared, so the time component is irrelevant. Every entry must fall between dateTimeFrom and dateTimeTo (Exclusion dates must fall within the booking date range…) and none may repeat (Duplicate exclusion dates found in request). Excluded dates are echoed back in the response's excludedDates, and no product entry is needed for them.
notesstringNoFree-text comments copied onto every booking in the series. Omit it and each booking's comments are an empty string.
referencestringNoYour own reference, stored on every booking the series creates. Maximum 255 characters.
activityIdstringNoAn identifier of your own for the activity that holds the series. Omit it and a unique one is generated. If supplied it must be 15 characters or fewer (ActivityId cannot exceed 15 characters) and must not already be in use (ActivityId '{id}' already exists); both checks also run under checkOnly=true, so you can validate a candidate id before committing. If every booking then fails to create, the activity is removed so the same id can be retried.
descriptionstringNoThe booking name, shown against every occurrence in the series. Maximum 25 characters: a longer value is rejected with 400 and the message Booking name cannot be greater than 25 characters, and nothing is created, even when checkOnly=true. Leave it blank and each booking is described as Hosted {member surname}. To rename the series later, send a new value to Update an existing block booking schedule with editType: All; a rename is not possible from a given date onwards.
invoiceGroupingPeriodinteger (int32)YesRequired — an absent value is rejected with Invoice Grouping Period must be specified, and it has no default. An integer from 0 to 255, set as the activity's invoice grouping period indicator.
invoiceItemisationbooleanYesRequired — an absent value is rejected with Invoice Itemisation must be specified, and it has no default. Sent as a boolean; the activity records it as 1 for itemised or 0 for not.
participantNumberinteger (int32)NoA theoretical participant capacity for the activity, held for reporting only — it is not enforced against the number of people actually booked. Set as both the minimum and maximum bookee count on the activity. Must be between 0 and 32767; omitted on create, it defaults to 0.

Sample request

cURL
curl -X POST '{base_url}/api/bookings/block-bookings' \
  -H 'Authorization: Bearer {accessToken}' \
  -H 'X-Use-SSO: true' \
  -H 'Content-Type: application/json' \
  -d '{
  "siteId": "string",
  "memberId": 12345,
  "products": [
    {
      "date": "2026-08-14T19:00:00Z",
      "productId": "string",
      "quantity": 1,
      "price": 10.5,
      "taxCodeId": "string",
      "timeBandId": "string"
    }
  ],
  "activityGroupId": "string",
  "frequency": "string",
  "recursEvery": 1,
  "recurranceType": "string",
  "recursOn": 1,
  "paymentMethodId": "string",
  "resourceProductIds": [
    "string"
  ],
  "days": [
    "string"
  ],
  "dateTimeFrom": "2026-08-14T19:00:00Z",
  "dateTimeTo": "2026-08-14T19:00:00Z",
  "exclusionDates": [
    "2026-08-14T19:00:00Z"
  ],
  "notes": "string",
  "reference": "string",
  "activityId": "string",
  "description": "string",
  "invoiceGroupingPeriod": 1,
  "invoiceItemisation": true,
  "participantNumber": 1
}'

Response fields

FieldTypeDescription
bookingIdsarray of integerThe ids of the bookings that were created, one per occurrence, in the order they were created. Empty for a checkOnly run, and empty if every booking failed.
datesarray of objectThe occurrences that were created, with their booking and invoice ids. On a checkOnly run the same list is returned to show what would be created, but with bookingId and salesInvoiceId of 0. Occurrences that failed appear in failedBookings instead.
dates[].bookingIdinteger (int32)The id of the booking created for this occurrence; use it against the single-booking endpoints. 0 on a checkOnly run.
dates[].salesInvoiceIdinteger (int32)The id of the sales invoice raised for this occurrence from its matching products entries. 0 on a checkOnly run.
dates[].startDatestring (date-time)When the occurrence starts, in real UTC — the occurrence's date at dateTimeFrom's time of day.
dates[].endDatestring (date-time)When the occurrence ends, in real UTC — the same date at dateTimeTo's time of day.
excludedDatesarray of stringThe exclusionDates you sent, echoed back as plain dates. Empty when you sent none.
scheduleIdstring (uuid)Identifies the series as a whole; keep it, as the update and delete block-booking endpoints work on this rather than on individual bookings. It is generated only when bookings are actually created, so a checkOnly run and any failed request return the empty GUID 00000000-0000-0000-0000-000000000000.
selectedDatesarray of objectEvery occurrence the recurrence rule produced, after days filtering and exclusionDates removal, whether or not it was created. It is returned even when the request fails, so it is the field to read when checking that your rule expands to the dates you expected.
selectedDates[].startDatestring (date-time)Start of the generated occurrence, in real UTC.
selectedDates[].endDatestring (date-time)End of the generated occurrence, in real UTC.
errorsarray of stringWhy the series was refused, as sentences — for example No valid dates found for the specified days, One or more bookings overlap with each other, One or more bookings overlap with existing bookings for the same product(s), One or more bookings overlap with existing exceptions for the same product(s) or All bookings failed to create. Present and non-empty with no bookingIds means nothing was written, and the call returns 400.
overlappingDatesarray of objectOccurrences in this request that overlap one another — usually a recurrence rule that produces two slots on the same day. Any entry here makes the response a 400 and nothing is created.
overlappingDates[].dateTimeFromstring (date-time)Start of the occurrence that clashes with a later one, in real UTC.
overlappingDates[].dateTimeTostring (date-time)End of that occurrence, in real UTC.
overlappingDates[].productIdstringNever included for this list, because a self-overlap is between two occurrences rather than against a particular product. Do not depend on it.
existingBookingsarray of objectBookings that already exist on one of your resourceProductIds and overlap a generated occurrence. Any entry here makes the response a 409 and nothing is created; free the resource, change the times, or exclude the date, then retry.
existingBookings[].bookingIdinteger (int32)The id of the clashing booking.
existingBookings[].recurringScheduleIdstring (uuid)The schedule the clashing booking belongs to, when it is itself part of a block booking. Null for an ad-hoc booking.
existingBookings[].dateTimeFromstring (date-time)Start of the clashing booking, in real UTC.
existingBookings[].dateTimeTostring (date-time)End of the clashing booking, in real UTC.
existingBookings[].productIdstringWhich of your resourceProductIds the clash is on. A single booking appears once per resource it clashes on.
existingExceptionsarray of objectSchedule exceptions — periods where a resource is unavailable — that overlap a generated occurrence. Treated like existingBookings: any entry makes the response a 409 and nothing is created.
existingExceptions[].dateTimeFromstring (date-time)Start of the blocking exception, in real UTC.
existingExceptions[].dateTimeTostring (date-time)End of the blocking exception, in real UTC.
existingExceptions[].productIdstringWhich of your resourceProductIds the exception applies to.
failedBookingsarray of objectOccurrences that passed validation but could not be written. Creation is per-occurrence, not all-or-nothing: a partly successful call returns 201 with entries in both dates and failedBookings, so check this list even on success. If every occurrence failed, errors carries All bookings failed to create and the activity created for the series is removed.
failedBookings[].startDatestring (date-time)Start of the occurrence that could not be created, in real UTC.
failedBookings[].endDatestring (date-time)End of that occurrence, in real UTC.
failedBookings[].errorstringThe failure message explaining why that occurrence could not be created.

Sample response

200 OK
{
  "bookingIds": [
    12345
  ],
  "dates": [
    {
      "bookingId": 12345,
      "salesInvoiceId": 12345,
      "startDate": "2026-08-14T19:00:00Z",
      "endDate": "2026-08-14T19:00:00Z"
    }
  ],
  "excludedDates": [
    "string"
  ],
  "scheduleId": "b7f3c1e2-9a44-4f2b-8c17-0d5e9a1b6c33",
  "selectedDates": [
    {
      "startDate": "2026-08-14T19:00:00Z",
      "endDate": "2026-08-14T19:00:00Z"
    }
  ],
  "errors": [
    "string"
  ],
  "overlappingDates": [
    {
      "dateTimeFrom": "2026-08-14T19:00:00Z",
      "dateTimeTo": "2026-08-14T19:00:00Z",
      "productId": "string"
    }
  ],
  "existingBookings": [
    {
      "bookingId": 12345,
      "recurringScheduleId": "b7f3c1e2-9a44-4f2b-8c17-0d5e9a1b6c33",
      "dateTimeFrom": "2026-08-14T19:00:00Z",
      "dateTimeTo": "2026-08-14T19:00:00Z",
      "productId": "string"
    }
  ],
  "existingExceptions": [
    {
      "dateTimeFrom": "2026-08-14T19:00:00Z",
      "dateTimeTo": "2026-08-14T19:00:00Z",
      "productId": "string"
    }
  ],
  "failedBookings": [
    {
      "startDate": "2026-08-14T19:00:00Z",
      "endDate": "2026-08-14T19:00:00Z",
      "error": "string"
    }
  ]
}

Errors

StatusMeaningWhen
400Bad requestValidation failed: a missing or malformed field, an unknown site, member, activity group, product, resource product, time band or payment method, a payment method that does not collect through an invoicing account, or a generated booking date with no matching product entry. Also returned when the generated occurrences overlap each other (body carries overlappingDates), when nothing was created and errors is non-empty, and when saving fails — A database error occurred. Please ensure that the ID supplied is unique.
400Bad requestdescription is longer than 25 characters (Booking name cannot be greater than 25 characters). Nothing is created.
404Not foundNot returned for unknown references — an unknown site, member, activity group, product or resource product returns 400.
409ConflictExisting bookings, or schedule exceptions, on the requested resourceProductIds overlap one or more generated occurrences. The body is the full response with existingBookings and existingExceptions populated, so you can resolve the clashes and retry. Nothing is created.
422Unprocessable entityNot returned — a payment method that is not an invoicing account returns 400.
500Internal server errorAn unexpected internal server error occurred.

This page as markdown · All pages · llms.txt