Get promotion by ID
GET /api/configuration/promotions/{id}
Success: 200 OK
- Products: Operator (
FOH) - Features: Front of House Generic Access (
FOHGENERIC)
Overview
Returns one promotion together with the promo codes attached to it and the discounts behind those codes. A promotion is the campaign — a name, a validity window and an on/off flag; the promo codes are the strings a customer actually enters; the discounts are what those codes are worth. This is the only endpoint that gives you all three in one call. Get all promotions lists campaigns with their code strings and stops there.
Get the id from that listing. It is an integer allocated per tenant, so it means nothing
in another tenant or environment — do not hardcode it.
Three things to know before you build against the response:
promotionTypeis alwaysnullhere. This endpoint never populates it, even though every promotion has a type. If you need it, read it from Get all promotions.- A promotion that does not exist returns
404, not400, with a plain-text body. A400from this endpoint means something else entirely — see the Errors table. - Unlike the listing, whose
statuscan be up to six hours old, this endpoint computesstatusat the moment you call.
The discounts are returned as one flat array for the whole promotion, not nested under each code.
Join them yourself using promoCodes[].discountIds.
Endpoint
GET {base_url}/api/configuration/promotions/{id}
{base_url} is the environment URL issued to your integration. See Authenticating with Gladstone APIs.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
id | integer (int32) | Yes | The promotion's numeric identifier, as returned in the id field of Get all promotions. Must be a whole number that fits in a 32-bit signed integer; anything else is rejected with 400 before the promotion is looked up. A well-formed but unknown id gives 404. |
Sample request
curl -X GET '{base_url}/api/configuration/promotions/{id}' \
-H 'Authorization: Bearer {accessToken}' \
-H 'X-Use-SSO: true'Response fields
| Field | Type | Description |
|---|---|---|
promoCodes | array of object | Every promo code mapped to this promotion, with the ids of the discounts attached to each. Always an array and never null; a promotion with no codes yet returns [], and in that case discounts is empty too. Each code carries its own validity window and active flag which are independent of the promotion's — a code can be switched off inside a campaign whose status is Active. This response does not include the code's own dates; fetch them from Get promo code by ID or code if you need them. |
promoCodes[].id | integer (int32) | The promo code's own numeric identifier, distinct from the promotion's id. This is what you pass to Get promo code by ID or code for the full record. |
promoCodes[].code | string | The string a customer types at checkout, at most 25 characters. Be aware that when you look a code up by its text rather than its id, the platform requires the text wrapped in curly braces in the path — /api/configuration/promotion-codes/{SUMMER25} — because an unwrapped segment is parsed as a numeric id. |
promoCodes[].active | boolean | Whether this specific code may currently be redeemed, independent of the promotion's status. Defaults to false, but a code whose flag has somehow been left unset makes the whole request fail with 500 rather than returning false. |
promoCodes[].discountIds | array of integer | The ids of the discounts this code applies. Empty when the code has been created but nothing has been attached to it yet — a valid state. Use these to look up entries in the top-level discounts array; the two are joined by id and by nothing else. |
discounts | array of object | Every discount referenced by any of the codes above, de-duplicated and flattened into a single array for the whole promotion. It is not grouped by code, so a discount shared by two codes appears once. Empty when no code has any discounts. It can also be shorter than the union of discountIds: an id whose discount no longer exists is silently dropped here rather than reported, so match on id and treat a missing entry as "no longer configured". |
discounts[].id | integer (int32) | The discount's identifier, matching the values in promoCodes[].discountIds. |
discounts[].discountText | string | The operator's description of the discount, maximum 255 characters, intended for display alongside the price. null when the operator left it blank. |
discounts[].discountBanner | string | A second, separate piece of promotional text for the discount, maximum 255 characters and also nullable. Held independently of discountText; the platform does not require either to be set. |
discounts[].regularAmount | number (double) | The discount applied to the recurring subscription price. Held to two decimal places and never null — 0 means "no regular discount", which is the value used when none is configured rather than a hundred-per-cent reduction. The unit is not in this response. The discount type is a separate setting that this endpoint does not return; for discounts written through Create a discount the type is set to percentage whenever the amount is above zero, so a non-zero value there is a percentage rather than a cash sum. |
discounts[].introAmount | number (double) | The discount applied to the introductory period of a membership, where the membership type has one. Same precision, same defaulting to 0 and the same missing-unit caveat as regularAmount. It is independent of regularAmount; a discount can set one, both or neither. |
discounts[].oneOffAmount | number (double) | The discount applied to the one-off joining charge. Same precision and caveats again. |
discounts[].membershipTypeId | string | The membership type the discount applies to, maximum 15 characters. A discount is tied to exactly one membership type — creating a discount for several types produces several discounts with distinct ids, which is why one promo code frequently carries a list of near-identical discounts differing only in this field. |
discounts[].membershipTypeDescription | string | The membership type's own description, included so you do not have to resolve membershipTypeId separately for display. |
id | integer (int32) | The promotion's numeric identifier, echoing the value you requested. Tenant-local. |
promotionName | string | The operator's name for the campaign. Mandatory and at most 50 characters. Not unique — nothing prevents two promotions sharing a name, so identify campaigns by id. |
validFrom | string (date-time) | The date the campaign is intended to open. null when none was recorded. Unlike Get all promotions, this endpoint returns the value as held rather than truncating it to midnight — values written through this API are already midnight, since writes drop the time, but anything written by other Gladstone tooling may carry one. Nothing on this endpoint reads it: status ignores validFrom entirely, so a campaign that has not started yet can still report Active. Compare it against the current date yourself. |
validTo | string (date-time) | The date the campaign is intended to close, returned as held. null means no end date, which status treats as never expiring. This is the only date status considers. Writes guarantee validFrom is no later than validTo, so you will not see them inverted. |
status | string (enum) | One of: Active, Inactive, Expired. Derived by the platform — the active flag itself is not returned, so this is your only view of it. Active means the flag is set and validTo is either absent or strictly later than today's UTC date. Expired means the flag is set but validTo is strictly earlier than today. Inactive is everything else, and it covers more than its name suggests: a campaign the operator switched off, an expired campaign that was also switched off (you never see Expired for those), and — because both comparisons are strict — an active campaign whose validTo falls on today. A campaign on its final day reports Inactive. Computed fresh on each call here, unlike the listing. Do not treat it as an authority on whether a code can be redeemed; derive that from the dates and the per-code active flag. |
promotionType | string | Always null on this endpoint. The value exists — every promotion has one, Membership or Booking, defaulting to Membership — but this endpoint does not return it. Read it from Get all promotions instead, where it is populated and can be filtered on. |
Sample response
{
"promoCodes": [
{
"id": 12345,
"code": "string",
"active": true,
"discountIds": [
12345
]
}
],
"discounts": [
{
"id": 12345,
"discountText": "string",
"discountBanner": "string",
"regularAmount": 10.5,
"introAmount": 10.5,
"oneOffAmount": 10.5,
"membershipTypeId": "string",
"membershipTypeDescription": "string"
}
],
"id": 12345,
"promotionName": "string",
"validFrom": "2026-08-14T19:00:00Z",
"validTo": "2026-08-14T19:00:00Z",
"status": "Active",
"promotionType": "string"
}Errors
| Status | Meaning | When |
|---|---|---|
400 | Bad request | The id segment is not a whole 32-bit integer — a code string, a decimal or a number beyond 2147483647. It is rejected before the lookup runs, so the body is a validation problem document (application/problem+json) with an errors map keyed by field name, rather than the plain-text message the 404 carries. This is not the response for an unknown promotion. |
403 | Forbidden | The caller is not entitled to read promotions. This applies to a member-scoped token and to an unauthenticated request alike — an anonymous call is refused with 403, not 401. |
404 | Not found | No promotion exists with that id. The body is the plain-text message The promotion with the ID {id} does not exist. |
500 | Internal server error | An unexpected server-side failure while reading the promotion, its codes or its discounts. The body is empty and carries no detail. |