# Create a discount

`POST /api/configuration/discounts`

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

HTML version: [Create a discount](https://support.gladstonesoftware.cloud/reference/configuration/create-a-discount)

## Overview

Creates percentage discounts against one or more membership types. The endpoint creates **one discount per membership type** in `membershipTypeIds`, so a request naming three membership types creates three discounts and returns three identifiers.

The response body is a JSON array of the integer discount IDs the system generated — you do not choose them. Keep them: they are what you quote when attaching the discount to a promotion or promo code, and when updating or deleting it later.

**This call is not idempotent.** No check is made against existing discounts, so sending the same body twice creates a second, independent set of discounts that will both apply. If a request times out, retrieve the discounts for the membership type before retrying.

A discount created here is percentage-based only. Each of the three amounts is stored as a percentage discount when greater than zero and as "no discount" when zero, so send `0` for the discount kinds you do not want rather than omitting them.

## Endpoint

`POST {base_url}/api/configuration/discounts`

`{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 |
|-------|------|----------|-------------|
| `membershipTypeIds` | array of string | Yes | The membership types the discount applies to, each identified by its membership type code (for example `POSTDISMEM`). One discount is created per entry and the codes are upper-cased when saved, so casing in your request does not matter. The list must be non-empty, must contain no duplicates, and every code must already exist — each of those three failures returns a distinct `400`. |
| `discountRegularAmount` | number (double) | Yes | The percentage taken off the recurring subscription payments, as a number from `0` to `100` inclusive (`20` means 20% off). Send `0` to create a discount that does not touch regular payments. A value above zero is what makes `discountUntilUnit` meaningful. Values below `0` or above `100` are rejected with `400`. |
| `discountIntroductoryAmount` | number (double) | Yes | The percentage taken off the introductory (joining) payment, as a number from `0` to `100` inclusive. Send `0` if the discount should not affect the introductory payment. Values below `0` or above `100` are rejected with `400`. |
| `discountOneOffAmount` | number (double) | Yes | The percentage taken off one-off products sold alongside the membership, as a number from `0` to `100` inclusive. Send `0` if one-off products should be charged at full price. Values below `0` or above `100` are rejected with `400`. |
| `discountUntilUnit` | integer (int32) | Yes | How many months the regular discount runs for. Send `0` together with a `discountRegularAmount` above zero to make the regular discount perpetual — it is perpetual only when both of those hold. Only set a value above `0` when a regular discount is actually applied; it has no effect on the introductory or one-off amounts. Negative values are rejected with `400`. |
| `discountBanner` | string | No | Short promotional banner text shown alongside the discounted membership in customer-facing journeys. Maximum 510 characters. An empty or whitespace-only string is normalised to null, which is the same as omitting it. |
| `discountText` | string | No | Longer descriptive text explaining the discount to the customer. Maximum 510 characters. An empty or whitespace-only string is normalised to null, which is the same as omitting it. |
| `isActive` | boolean | No | Whether the discount takes effect immediately. Send `false` to stage a discount that exists but is not yet applied, then activate it later. Defaults to `false` when omitted — send `true` explicitly if you want the discount live. |
| `siteId` | string | No | Restricts the discount to a single site, identified by its site code (for example `WALLI`). Omit it, or send null, to apply the discount across every site. An empty or whitespace-only string is treated as omitted. A code that does not match an existing site is rejected with `400` — the site is not created for you. |

## Sample request

```bash title="cURL"
curl -X POST '{base_url}/api/configuration/discounts' \
  -H 'Authorization: Bearer {accessToken}' \
  -H 'X-Use-SSO: true' \
  -H 'Content-Type: application/json' \
  -d '{
  "membershipTypeIds": [
    "string"
  ],
  "discountRegularAmount": 10.5,
  "discountIntroductoryAmount": 10.5,
  "discountOneOffAmount": 10.5,
  "discountUntilUnit": 1,
  "discountBanner": "string",
  "discountText": "string",
  "isActive": true,
  "siteId": "string"
}'
```

## Sample response

```json title="201 Created"
[
  1,
  2
]
```

## Errors

| Status | Meaning | When |
|--------|---------|------|
| `400` | Bad request | `membershipTypeIds` is empty — `Discount could not be created as no membership types have been provided`. |
| `400` | Bad request | `membershipTypeIds` contains the same code twice — `Discount could not be created as duplicate MembershipTypeIds have been provided`. |
| `400` | Bad request | At least one membership type code does not exist — `Discount could not be created as at least one of the membership types does not exist.` |
| `400` | Bad request | A discount amount is negative, or above 100 — for example `Discount could not be created as the DiscountRegularAmount cannot be greater than 100`. The same pair of messages exists for `DiscountIntroductoryAmount` and `DiscountOneOffAmount`. |
| `400` | Bad request | `discountUntilUnit` is negative — `Discount could not be created as the DiscountUntilUnit is not greater than or equal to 0`. |
| `400` | Bad request | `siteId` was supplied but matches no site — `Discount could not be created as siteId does not exist`. |
| `500` | Internal server error | An unexpected server-side failure while saving the discounts. The response body is empty; nothing distinguishes a partial write, so re-read the membership type's discounts before retrying. |
