# Update promo code

`PUT /api/configuration/promotion-codes/{id}`

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

HTML version: [Update promo code](https://support.gladstonesoftware.cloud/reference/configuration/update-promo-code)

## Overview

Rewrites an existing promo code: the dates it is valid between, how many times it may still be used,
whether it is live, whether it applies itself automatically, and which discounts it grants.

**This is a full replacement, not a partial update.** Every field is mandatory, so there is no way to
change one setting without restating the others — read the code first with
`GET /api/configuration/promotion-codes` and send everything back. `discountIds` is a full-set
replace too: the existing discount mappings are deleted and rebuilt from the array you send.

**The code string itself is not editable here.** The path takes the numeric id, not the code text.
(For retrieval, note that fetching by code text requires wrapping the code in curly braces in the
path; that quirk does not apply to this endpoint, which only accepts the numeric id.)

Three rules catch integrations out:

- **Dates cannot be moved into the past.** `validFrom` must be today or later and `validTo` must be
  later than today — unless the value you send is exactly the date already held, which is how
  you update an old code without shifting its dates.
- **`maxUsages` cannot be set below the usages still remaining**, and because `0` means unlimited it
  is compared as the number zero: switching a part-used limited code to unlimited is rejected while
  any usages remain.
- **Changing `maxUsages` adjusts the remaining count by the difference**, rather than resetting it.
  Raising a cap from 100 to 150 on a code with 20 left leaves 70 remaining, not 150.

Validation runs before anything is written; the promo code and its discount mappings are then
saved together. On success the response is `204 No Content` and the change is visible on the next
read.

## Endpoint

`PUT {base_url}/api/configuration/promotion-codes/{id}`

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

## Path parameters

| Name | Type | Required | Description |
|------|------|----------|-------------|
| `id` | integer (int32) | Yes | Numeric identifier of the promo code, as returned when it was created or listed — not the code text customers type. If it matches nothing the request fails with `404 Promotion code {id} does not exist.` |

## Request body

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `validFrom` | string (date-time) | Yes | First day the code can be used. Mandatory (`ValidFrom date must be provided`). Only the date part is kept; any time you send is discarded. It must be today or later, **except** when it exactly equals the date already stored and that stored date is in the past — otherwise the request fails with `The Valid From date must be from today onwards`. One quirk to be aware of: if you re-send an unchanged past `validTo` and the stored `validFrom` is empty, your `validFrom` is replaced with today's date. |
| `validTo` | string (date-time) | Yes | Last day the code can be used. Mandatory (`ValidTo date must be provided`), date part only, and must be **strictly later than today** — today itself is rejected — unless it exactly equals a stored date that is already today or earlier. Otherwise the request fails with `The Valid To date must be a future date`. It must also not be earlier than `validFrom`, which is checked first and fails with `The Valid To date must be after the Valid From date`. |
| `maxUsages` | integer (int32) | Yes | Total number of times the code may be used. Mandatory (`MaxUsages must be supplied`) and 0 or greater (`Max Usages must be 0 or greater`). **`0` means unlimited**, and is recorded as `-1`. When the value differs from the one stored, the remaining-usage count is adjusted by the difference rather than reset — so raising the cap adds headroom and lowering it removes headroom. A value below the usages still remaining is rejected with `There are currently {n} usages remaining, maxUsages cannot be less than the usages remaining`; because `0` is compared numerically, this also blocks switching a part-used code to unlimited. |
| `active` | boolean | Yes | Whether the code is live. Mandatory (`Active must be supplied`) — there is no default, so you must restate it on every update or the request is rejected. Setting it to `false` is how a code is withdrawn without deleting it or changing its dates. |
| `autoApply` | boolean | Yes | Whether the code is applied automatically rather than having to be typed in by the customer. Mandatory (`AutoApply must be supplied`). |
| `discountIds` | array of integer | Yes | The discounts this code grants. Mandatory and must contain at least one entry (`At least one discount ID must be supplied`) — a promo code cannot be left granting nothing. Every id must already exist, or the request fails with `The following discount IDs do not exist and can not be mapped to a promo-code: {ids}`, naming the ones it could not find. The existing mappings are deleted and rebuilt from this array, so send the complete set of discounts the code should grant, not just the new ones. |

## Sample request

```bash title="cURL"
curl -X PUT '{base_url}/api/configuration/promotion-codes/{id}' \
  -H 'Authorization: Bearer {accessToken}' \
  -H 'X-Use-SSO: true' \
  -H 'Content-Type: application/json' \
  -d '{
  "validFrom": "2026-08-14T19:00:00Z",
  "validTo": "2026-08-14T19:00:00Z",
  "maxUsages": 1,
  "active": true,
  "autoApply": true,
  "discountIds": [
    12345
  ]
}'
```

## Errors

| Status | Meaning | When |
|--------|---------|------|
| `400` | Bad request | A mandatory field is missing, or `maxUsages` is negative — `ValidFrom date must be provided`, `ValidTo date must be provided`, `MaxUsages must be supplied`, `Max Usages must be 0 or greater`, `Active must be supplied`, `AutoApply must be supplied`, `At least one discount ID must be supplied`. |
| `400` | Bad request | `validTo` is earlier than `validFrom` — `The Valid To date must be after the Valid From date`. Checked before the promo code is even looked up, so it masks a `404`. |
| `400` | Bad request | `validFrom` is in the past and differs from the stored value — `The Valid From date must be from today onwards`. |
| `400` | Bad request | `validTo` is today or earlier and differs from the stored value — `The Valid To date must be a future date`. |
| `400` | Bad request | `maxUsages` is lower than the usages still remaining, including `0` for unlimited — `There are currently {n} usages remaining, maxUsages cannot be less than the usages remaining`. |
| `400` | Bad request | One or more discount ids do not exist — `The following discount IDs do not exist and can not be mapped to a promo-code: {ids}`. |
| `404` | Not found | No promo code has that numeric id — `Promotion code {id} does not exist.` |
| `500` | Internal server error | An unexpected server-side failure, including a failure saving the code or its discount mappings. No detail is returned. |
