Create price level

POST /api/configuration/price-levels

Overview

Creates a price level. A price level is a named pricing category — "Adult", "Junior", "Corporate" and so on — that members and casual customers are assigned to, and that activity and membership prices are then quoted against.

A price level is identified by a short string id. You choose that id yourself by sending id, or you omit id and one is allocated for you: the endpoint takes the highest existing id beginning PLVL, adds one, and zero-pads the number to a total length of 15 characters, giving ids of the form PLVL00000000001.

A successful call returns 201 Created. The body is a small JSON object whose only property is the id that was actually used — the one you sent, upper-cased and stripped of spaces, or the one allocated for you:

201 Created
{ "id": "PLVL00000000001" }

Read that property case-insensitively, and take the id from it rather than assuming the value you sent was stored verbatim. The endpoint also sets a Location header.

Retrying is only safe when you choose the id yourself. If you send an id that already exists the request is rejected with 409 Conflict and nothing is created, so a retry after a timeout is harmless. If you omit id, every retry allocates a fresh id and creates another price level — there is no idempotency key and no duplicate detection on description.

Uniqueness is enforced on the id only: id must be unique across all price levels, but two price levels may carry the same description. Within a single request, each alternativeDescriptions[].alternativeDescription must be distinct, and no site id or activity id may appear more than once across the whole alternativeDescriptions array.

Two of the fields on this request are routinely confused, and they do different things: infringementPriceLevel is the price level a member is moved to when they breach the terms of their agreement — falling into debt, missing payments — whereas nextPriceLevel is the price level a member progresses to automatically, typically when they age out of one band into another (junior to adult). Setting one does not affect the other.

Creation is not all-or-nothing. The price level is saved first, and any alternativeDescriptions are saved afterwards, one entry at a time. All validation runs before anything is written, so this rarely bites, but if saving the alternative descriptions fails mid-way the price level itself still exists and the call returns 500. Retrieve the price level before retrying rather than assuming nothing was created.

Endpoint

POST {base_url}/api/configuration/price-levels

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

Request body

FieldTypeRequiredDescription
idstringNoThe identifier you want the price level to have, up to 15 characters. It is upper-cased and has spaces stripped before it is stored, so gym adult is saved and must later be referenced as GYMADULT. It must not already be in use — a clash is rejected with 409. Omit the field and the endpoint allocates the next id in the PLVL… sequence instead, and returns it in the response body.
descriptionstringYesThe name of the price level as staff and customers see it, 1–255 characters. Descriptions are not required to be unique, so nothing stops you creating two price levels called "Adult". Use alternativeDescriptions if a particular site or activity needs to show a different name for this same price level.
ageRangeStartinteger (int32)NoLower bound of the age band this price level covers, in whole years. Despite the error message ("AgeRangeStart has to be greater than 0"), the accepted range is 0 to 32767 inclusive, and 0 is valid. Defaults to 0 when omitted. Must not be greater than ageRangeEnd, or the request is rejected with 400.
ageRangeEndinteger (int32)NoUpper bound of the age band, in whole years, on the same 0–32767 scale as ageRangeStart. Defaults to 0 when omitted — so a request that sets only ageRangeStart to a non-zero value is rejected, because the start would then exceed the end.
backColourHexstringNoBackground colour used when the price level is shown in Gladstone's own front-of-house screens. Send a hex string such as #E0E0E0; a value that cannot be parsed as a colour fails the request with 500 rather than 400. Send null or "" to get the default #E0E0E0. The default is applied only when the field is actually present in the JSON: send it explicitly as null rather than leaving the key out.
textColourHexstringNoForeground (text) colour used alongside backColourHex on the same screens, with identical parsing and identical handling of null and empty values. Its default is #000000.
membersAllowedbooleanNoMarks the price level as applying to members. Defaults to false when omitted. Price levels with this set are the ones returned by GET /api/configuration/price-levels?filter=Member.
nonMembersAllowedbooleanNoMarks the price level as applying to non-members — casual customers with no membership. Defaults to false, and is independent of membersAllowed; a price level may have both, one, or neither. Price levels with this set are returned by GET /api/configuration/price-levels?filter=Non-Member.
webNonMembersAllowedbooleanNoMarks the price level as available to non-members booking through the consumer web channel, as opposed to at the centre. Defaults to false. It is a separate flag from nonMembersAllowed, and is what the webNonMembers=true query parameter on the list endpoint filters on.
infringementPriceLevelstringNoThe id of the price level a member on this price level is moved to when they breach the terms of their agreement — for example falling into debt or missing payments. Must be the id of a price level that already exists, matched exactly as stored (upper case), or the request fails with 400. Defaults to empty, meaning no infringement level is configured. Do not confuse this with nextPriceLevel: this one is punitive, that one is progression.
nextPriceLevelstringNoThe id of the price level a member progresses to automatically as part of normal lifecycle, most commonly an age-based transition such as junior to adult. Must be the id of an existing price level, matched exactly as stored (upper case), or the request fails with 400. Defaults to empty, meaning no onward level is configured. There is no cycle detection, and you cannot point it at the price level you are creating in this request, because the target must already exist.
translationsarray of objectNoTranslations of this price level's description into other cultures, for bilingual sites. Omit or send an empty array if you only need the description. The entries are stored as supplied and are not validated, so mistakes here surface as a 500 rather than a 400.
translations[].typestringNoThe name of the field being translated. Use description — that is the only property the price level retrieval and list endpoints read back, so an entry with any other value is stored but never returned.
translations[].culturestringNoThe culture code the translation is for, for example cy-GB. It must be a culture code the platform already knows about; an unrecognised code fails the request with 500.
translations[].textstringNoThe translated text itself — what description should read as in that culture.
alternativeDescriptionsarray of objectNoPer-site and per-activity overrides of the price level's name, for cases where the same price level is called something different at a particular centre or for a particular activity. Omit or send an empty array if no overrides are needed. All entries are validated before the price level is written, so an invalid entry means nothing at all is created.
alternativeDescriptions[].alternativeDescriptionstringNoThe name to display instead of description for the sites and activities listed in this entry, up to 25 characters — a much tighter limit than description's 255. It must not be blank, and comparing case-insensitively and ignoring surrounding whitespace, it must differ from every other alternativeDescription in the same request.
alternativeDescriptions[].activityIdsarray of stringNoThe activities this override applies to. Each id must be an activity that already exists, and no activity id may be repeated anywhere in the alternativeDescriptions array — including across two different entries. Ids are upper-cased before use. May be empty, but only if siteIds is populated: every entry needs at least one site or activity.
alternativeDescriptions[].siteIdsarray of stringNoThe sites (centres) this override applies to. Each id must be a site that already exists, and no site id may be repeated anywhere in the alternativeDescriptions array. Ids are upper-cased before use. May be empty, but only if activityIds is populated.

Sample request

cURL
curl -X POST '{base_url}/api/configuration/price-levels' \
  -H 'Authorization: Bearer {accessToken}' \
  -H 'X-Use-SSO: true' \
  -H 'Content-Type: application/json' \
  -d '{
  "id": "string",
  "description": "string",
  "ageRangeStart": 1,
  "ageRangeEnd": 1,
  "backColourHex": "string",
  "textColourHex": "string",
  "membersAllowed": true,
  "nonMembersAllowed": true,
  "webNonMembersAllowed": true,
  "infringementPriceLevel": "string",
  "nextPriceLevel": "string",
  "translations": [
    {
      "type": "string",
      "culture": "string",
      "text": "string"
    }
  ],
  "alternativeDescriptions": [
    {
      "alternativeDescription": "string",
      "activityIds": [
        "string"
      ],
      "siteIds": [
        "string"
      ]
    }
  ]
}'

Errors

Error responses carry the reason as the response body. Request validation failures return a validation problem document (application/problem+json) with an errors map keyed by field name; every other failure returns the message below as a plain string.

StatusMeaningWhen
400Bad requestRequest validation failed before any other check: id longer than 15 characters, description missing, empty or longer than 255 characters, ageRangeStart or ageRangeEnd outside 0–32767, or an alternativeDescription longer than 25 characters. The body is a validation document listing the offending fields.
400Bad requestMalformed JSON, or a value of the wrong type for its field.
400Bad requestageRangeStart is greater than ageRangeEnd. Body: The starting age range is greater than the ending age range.
400Bad requestnextPriceLevel or infringementPriceLevel names a price level that does not exist. Body: PriceLevel {id} does not exist. Remember ids are stored upper-cased.
400Bad requestAn alternativeDescriptions entry has a blank or missing alternativeDescription. Body: One or more alternative descriptions does not have a description.
400Bad requestAn alternativeDescriptions entry supplies neither siteIds nor activityIds. Body: One or more alternative descriptions does not have a activity ID or site ID defined.
400Bad requestTwo entries in alternativeDescriptions use the same text, compared case-insensitively and trimmed. Body: Duplicate alternateDescriptions within the same PriceLevel is not allowed. Please ensure each alternateDescription is unique.
400Bad requestThe same site id appears more than once across alternativeDescriptions, whether inside one entry or spread across several. Body: Duplicate Site IDs are not allowed across alternative descriptions.
400Bad requestThe same activity id appears more than once across alternativeDescriptions. Body: Duplicate Activity IDs are not allowed across alternative descriptions.
400Bad requestOne or more ids in siteIds do not match an existing site. Body: Some or all of the sites supplied are not valid. The message does not say which id was wrong.
400Bad requestOne or more ids in activityIds do not match an existing activity. Body: Some or all of the activities supplied are not valid.
409ConflictThe id you supplied is already in use by another price level. Body: The PriceLevel : {id} already exists. Nothing is created, so this is safe to treat as "already done".
409ConflictA concurrent request created a price level with the same id at the same moment. Body: This entry already exists in the database.
500Internal server errorbackColourHex or textColourHex could not be parsed as a colour. This is a bad request in everything but status code, so validate the hex yourself before sending.
500Internal server errorA translations[].culture is not a culture code the platform recognises, so the translation cannot be saved.
500Internal server errorAny other unexpected server-side failure, including one while saving the alternative descriptions — in which case the price level itself may already exist.

This page as markdown · All pages · llms.txt