# Creates a new resource product with configuration and availability settings

`POST /api/configuration/resource-products`

- **Products:** Operator (`FOH`)
- **Features:** Front of House Generic Access (`FOHGENERIC`)

HTML version: [Creates a new resource product with configuration and availability settings](https://support.gladstonesoftware.cloud/reference/configuration/creates-a-new-resource-product-with-configuration-and-availability-settings)

## Overview

Creates a resource product — a bookable facility such as a court, studio or piece of equipment — together with the weekly opening pattern that says when it can be booked.

**Creating a resource product also creates a calendar behind it.** The `openingTimes` you send are stored as a new calendar, and the product points at it. That is why at least one opening time is required: a product with no calendar has no bookable time at all. The call is all-or-nothing, so a failure leaves neither behind.

**A successful call answers `201 Created` with an empty body.** The identifier is not returned to you. If you leave `id` out, the platform generates the next one in sequence with an `RPROD` prefix — and because the body is empty, you would then have to list resource products to discover what it was called. Supply your own `id` if you need to know it, or to keep identifiers aligned with your own system.

Validation runs in a fixed order and stops at the first failure: request validation (required fields, lengths) first, then the `id` character check, then each opening time in the order you sent it, then the duplicate-day check across the whole set. Expect one error at a time rather than a complete list.

`siteGroupId` must already exist. It is not checked up front, so an unknown value returns the generic `400` `Foreign keys being referenced do not exist` rather than a specific message naming the field.

## Endpoint

`POST {base_url}/api/configuration/resource-products`

`{base_url}` is the environment URL issued to your integration. See [Authenticating with Gladstone APIs](../../guides/authenticating-with-gladstone-apis.md).

## Request body

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `id` | string | No | The identifier you want the product to have, which is also how you address it in every later call. Maximum 15 characters, and it must not contain any of ``!£$%^&*()_+=[]{};:@~#?.,<>\|`` — offending characters return `400` with `ProductId contains invalid characters`. Omit it, or send an empty string, and the platform generates the next free identifier prefixed `RPROD`; because the response has no body, you will not be told what it is. Reusing an existing identifier returns `409`. |
| `active` | boolean | No | Whether the product is available to be booked. **Defaults to `true`** when you omit it, so send `false` explicitly to stage a product before opening it up. The retrieve endpoints report it back as `active`. |
| `description` | string | Yes | The product's display name, and the text a booker sees. Required — omitting it or sending null returns `400` with `Description cannot be blank` — and at most 255 characters. It is also copied to the description of the calendar created alongside the product, so a meaningful value here makes the generated calendar identifiable too. |
| `siteGroupId` | string | Yes | The site group the product belongs to, which determines where it can be booked. Required (`SiteGroupId cannot be blank`) and at most 15 characters. It must match a site group that already exists; an unknown value is not caught up front and fails with `400` and `Foreign keys being referenced do not exist`. |
| `openingTimes` | array of object | Yes | The weekly opening pattern, one entry per day the product is bookable. At least one entry is needed — an empty array, or omitting the field, returns `400` with `One or more OpeningTimes must be specified`. Each day may appear at most once; a repeat returns `400` with `An opening time cannot be specified more than once for the same day`. Days you do not list are simply not bookable. Sending an explicit `null` gets past request validation and then fails with `500`, so send an array. This list becomes a new calendar, so to change the pattern later you send the whole list again rather than adding to it. |
| `openingTimes[].day` | string | Yes | The day of the week this entry covers, as an English day name — `Sunday` through `Saturday`. The match is **case-sensitive**: `Monday` is accepted, `monday` returns `400` with `Invalid day monday specified in opening times`. Blank or missing returns `400` with `Blank or missing days in opening times is not allowed`. Numeric strings are also accepted, so `1` means Monday; a number outside `0`–`6` gets past validation and then fails with `500`. |
| `openingTimes[].startTime` | string | Yes | The time the product opens on that day, as 24-hour `H:mm` — `09:00` and `9:00` are both accepted, `9am` and `09:00:00` are not. A value that will not parse returns `400` with `Start time or End time is not formatted correctly`. It must be strictly earlier than `endTime`. |
| `openingTimes[].endTime` | string | Yes | The time the product closes on that day, in the same 24-hour `H:mm` format. It must be strictly later than `startTime`; equal times are rejected, as is any time earlier, with `400` and `End time for {day} must be later than start time`. There is no overnight handling — a session that runs past midnight cannot be expressed as one entry. |

## Sample request

```bash title="cURL"
curl -X POST '{base_url}/api/configuration/resource-products' \
  -H 'Authorization: Bearer {accessToken}' \
  -H 'X-Use-SSO: true' \
  -H 'Content-Type: application/json' \
  -d '{
  "id": "string",
  "active": true,
  "description": "string",
  "siteGroupId": "string",
  "openingTimes": [
    {
      "day": "string",
      "startTime": "string",
      "endTime": "string"
    }
  ]
}'
```

## Errors

| Status | Meaning | When |
|--------|---------|------|
| `400` | Bad request | Request validation failed: `description` or `siteGroupId` missing (`Description cannot be blank`, `SiteGroupId cannot be blank`), a value over its maximum length, or `openingTimes` empty (`One or more OpeningTimes must be specified`). The body is a validation error object keyed by field name. |
| `400` | Bad request | `id` contains one of the forbidden characters — `ProductId contains invalid characters`. The body is a plain string, not a validation error object. |
| `400` | Bad request | An opening time is wrong: `Blank or missing days in opening times is not allowed`, `Invalid day {day} specified in opening times` (including correctly spelled days in the wrong case), `Start time or End time is not formatted correctly`, or `End time for {day} must be later than start time`. Checking stops at the first bad entry. |
| `400` | Bad request | Two entries in `openingTimes` name the same day — `An opening time cannot be specified more than once for the same day`. This check runs after every individual entry has passed. |
| `400` | Bad request | `siteGroupId` does not match an existing site group — `Foreign keys being referenced do not exist`. Nothing is created. |
| `409` | Conflict | A resource product already uses the `id` you supplied — `This entry already exists in the database`. Retrying a create that already succeeded produces this, so it is a safe signal that the first attempt worked. |
| `500` | Internal server error | `openingTimes` was sent as an explicit `null`, or a `day` was a number outside `0`–`6`; both get past the earlier checks and fail. Also any other unexpected server-side failure. The body is empty. |
