Create a membership for a member

POST /api/memberships

Success: 201 Created

Overview

Creates memberships for one or more members from a pricing quote, and raises the unpaid sales invoices that pay for them. It is the second step of the joining flow: the members must already exist, and the quote must already have been obtained.

The order is fixed, and none of it is optional:

  1. Get a pricing quote for the memberships you want to sell. The quote is returned signed.
  2. Create or identify the members who will hold them.
  3. Call this endpoint with the quote unaltered and a map from each quote line to a member id.
  4. Pay the returned salesInvoiceIds with Pay for sales invoices.

The quote is signed and short-lived. signature is a keyed hash over the entire quote object with signature itself removed. Change any value inside pricingQuote — a price, a date, a description, a discount — or drop any field, and the hash no longer matches and the call is rejected with The pricing quote is invalid. You cannot adjust a price on the way through. Echo the quote back exactly as you received it. The quote is also only valid for one hour from its timeStamp, and its timeStamp may not be more than ten minutes in the future relative to the server's clock.

This call leaves work half-done by design. A 201 means the memberships and their invoices exist, not that anything has been paid. The invoices are created unpaid, and the membership is not active until they are settled. If your process stops between this call and payment, you have created a real membership with an outstanding balance against it — there is no expiry and no rollback here, so reconcile rather than simply retrying. Retrying is not idempotent either: a second call with the same quote creates a second set of memberships and invoices, unless a duplicate or concurrency rule happens to block it.

The prices in the quote are what get billed. memberships[].pricing.now.breakdown becomes the invoice lines, memberships[].pricing.now.totalPrice.amount becomes the invoice total, and the first entry of memberships[].pricing.recurring.schedule sets up the first instalment.

Endpoint

POST {base_url}/api/memberships

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

Request body

FieldTypeRequiredDescription
isJuniorMembershipWithGuardianbooleanNoSet true when a guardian is buying a membership on behalf of a junior member, which relaxes one check: the order keys in orderKeyToMemberIdMap are then no longer required to appear in pricingQuote.memberships. Defaults to false when omitted, in which case every order key you map must exist in the quote. It also changes the wording of two error messages (junior member rather than secondary member), but does not otherwise loosen the age or eligibility rules — a junior still has to fall inside the price level's age range.
primaryPaysIndbooleanNoFor a linked member buying a direct debit membership, says whose bank details pay for it: true means the primary member's, false means the linked member's own. Defaults to false when omitted. It is only consulted when at least one membership in the quote is direct debit and the buyer is a linked member, and it decides which member's bank account is checked — get it wrong and the call fails with 422 and either Primary member must have bank details as this is a linked member and Primary Pays flag is set to true or Member must have bank details as this is a linked member and Primary Pays flag is set to false.
pricingQuoteobjectYesThe signed quote, returned whole by the pricing call. Treat it as opaque: it is hashed on receipt and the result compared against signature, so every field must survive the round trip unchanged — including ones your code does not care about. Dropping a nested object, rounding an amount or reformatting a date all produce the same 400 with The pricing quote is invalid. The safest implementation stores the quote as received and posts it back verbatim.
pricingQuote.siteIdstringYesThe site the memberships are being sold at, as it appeared in the quote. It determines the site the new memberships are created against. Must not be empty (Site ID cannot be empty), and like everything else inside pricingQuote it must match the quote exactly or the signature check fails.
pricingQuote.promotionCodestringNoThe promotion code the quote was priced under, if any. Part of the signed quote: send it back exactly as the pricing quote gave it to you. Altering or omitting it breaks signature and the call fails with The pricing quote is invalid. It is used to record promotion usage against the memberships, so removing it because it looks cosmetic both invalidates the signature and would lose the promotion record.
pricingQuote.overviewobjectYesThe basket-level totals for the whole quote. Required (Pricing quote overview cannot be empty) even though this endpoint bills from the per-membership figures — it is part of the signed payload, so it cannot be trimmed out of the request to save bytes.
pricingQuote.overview.nowobjectNoThe total payable immediately across every membership in the quote, as opposed to the per-membership figure in memberships[].pricing.now. Useful for showing a basket total. Part of the signed quote: send it back exactly as the pricing quote gave it to you. Altering or omitting it breaks signature and the call fails with The pricing quote is invalid.
pricingQuote.overview.now.amountstringNoThe whole-basket amount payable now, as a decimal string with two decimal places, so parse it as a decimal rather than a float. Part of the signed quote: send it back exactly as the pricing quote gave it to you. Altering or omitting it breaks signature and the call fails with The pricing quote is invalid.
pricingQuote.overview.now.currencystringNoThe ISO 4217 currency code of the basket total, resolved from the site's configuration — do not assume GBP. Part of the signed quote: send it back exactly as the pricing quote gave it to you. Altering or omitting it breaks signature and the call fails with The pricing quote is invalid.
pricingQuote.overview.now.discountobjectNoThe discount applied to the basket total, or null when the quote was priced without a promotion code. Part of the signed quote: send it back exactly as the pricing quote gave it to you. Altering or omitting it breaks signature and the call fails with The pricing quote is invalid.
pricingQuote.overview.now.discount.priceBeforeDiscountstringNoWhat the basket would have cost without the discount, as a decimal string with two decimal places, so parse it as a decimal rather than a float. Part of the signed quote: send it back exactly as the pricing quote gave it to you. Altering or omitting it breaks signature and the call fails with The pricing quote is invalid.
pricingQuote.overview.now.discount.discountValuestringNoThe amount taken off the basket, as a decimal string with two decimal places, so parse it as a decimal rather than a float. Part of the signed quote: send it back exactly as the pricing quote gave it to you. Altering or omitting it breaks signature and the call fails with The pricing quote is invalid.
pricingQuote.overview.now.discount.discountIdinteger (int32)NoThe discount record that was applied. Part of the signed quote: send it back exactly as the pricing quote gave it to you. Altering or omitting it breaks signature and the call fails with The pricing quote is invalid.
pricingQuote.overview.recurringobjectNoThe ongoing cost across every membership in the quote, once the initial payment is out of the way. null for a quote containing only one-off memberships. Part of the signed quote: send it back exactly as the pricing quote gave it to you. Altering or omitting it breaks signature and the call fails with The pricing quote is invalid.
pricingQuote.overview.recurring.headlinePriceobjectNoThe headline recurring amount for the basket — the "£x a month" figure a shopper is agreeing to. Part of the signed quote: send it back exactly as the pricing quote gave it to you. Altering or omitting it breaks signature and the call fails with The pricing quote is invalid.
pricingQuote.overview.recurring.headlinePrice.amountstringNoThe recurring basket amount, as a decimal string with two decimal places, so parse it as a decimal rather than a float. Part of the signed quote: send it back exactly as the pricing quote gave it to you. Altering or omitting it breaks signature and the call fails with The pricing quote is invalid.
pricingQuote.overview.recurring.headlinePrice.currencystringNoThe ISO 4217 currency code of the recurring amount. Part of the signed quote: send it back exactly as the pricing quote gave it to you. Altering or omitting it breaks signature and the call fails with The pricing quote is invalid.
pricingQuote.overview.recurring.headlinePrice.discountobjectNoThe discount applied to the recurring amount, or null when none applies. Part of the signed quote: send it back exactly as the pricing quote gave it to you. Altering or omitting it breaks signature and the call fails with The pricing quote is invalid.
pricingQuote.overview.recurring.headlinePrice.discount.priceBeforeDiscountstringNoThe recurring amount before the discount, as a decimal string with two decimal places, so parse it as a decimal rather than a float. Part of the signed quote: send it back exactly as the pricing quote gave it to you. Altering or omitting it breaks signature and the call fails with The pricing quote is invalid.
pricingQuote.overview.recurring.headlinePrice.discount.discountValuestringNoThe amount taken off each recurring payment, as a decimal string with two decimal places, so parse it as a decimal rather than a float. Part of the signed quote: send it back exactly as the pricing quote gave it to you. Altering or omitting it breaks signature and the call fails with The pricing quote is invalid.
pricingQuote.overview.recurring.headlinePrice.discount.discountIdinteger (int32)NoThe discount record applied to the recurring amount. Part of the signed quote: send it back exactly as the pricing quote gave it to you. Altering or omitting it breaks signature and the call fails with The pricing quote is invalid.
pricingQuote.overview.recurring.frequencystringNoHow often the recurring payment is taken. Part of the signed quote: send it back exactly as the pricing quote gave it to you. Altering or omitting it breaks signature and the call fails with The pricing quote is invalid.
pricingQuote.overview.recurring.schedulearray of objectNoThe forecast payment dates and amounts for the basket as a whole. Presentational here — the schedule that actually drives billing is the per-membership one in memberships[].pricing.recurring.schedule. Part of the signed quote: send it back exactly as the pricing quote gave it to you. Altering or omitting it breaks signature and the call fails with The pricing quote is invalid.
pricingQuote.overview.recurring.schedule[].datestring (date-time)NoThe date this scheduled basket payment falls due. Part of the signed quote: send it back exactly as the pricing quote gave it to you. Altering or omitting it breaks signature and the call fails with The pricing quote is invalid.
pricingQuote.overview.recurring.schedule[].totalPriceobjectNoThe total due across the basket on this date. Part of the signed quote: send it back exactly as the pricing quote gave it to you. Altering or omitting it breaks signature and the call fails with The pricing quote is invalid.
pricingQuote.overview.recurring.schedule[].totalPrice.amountstringNoThe amount due on this date, as a decimal string with two decimal places, so parse it as a decimal rather than a float. Part of the signed quote: send it back exactly as the pricing quote gave it to you. Altering or omitting it breaks signature and the call fails with The pricing quote is invalid.
pricingQuote.overview.recurring.schedule[].totalPrice.currencystringNoThe ISO 4217 currency code for this scheduled payment. Part of the signed quote: send it back exactly as the pricing quote gave it to you. Altering or omitting it breaks signature and the call fails with The pricing quote is invalid.
pricingQuote.overview.recurring.schedule[].totalPrice.discountobjectNoThe discount applied to this scheduled payment, or null when none applies. Part of the signed quote: send it back exactly as the pricing quote gave it to you. Altering or omitting it breaks signature and the call fails with The pricing quote is invalid.
pricingQuote.overview.recurring.schedule[].breakdownarray of objectNoHow this scheduled basket payment splits across payment methods — one entry per method contributing to the date. Part of the signed quote: send it back exactly as the pricing quote gave it to you. Altering or omitting it breaks signature and the call fails with The pricing quote is invalid.
pricingQuote.overview.recurring.schedule[].breakdown[].priceobjectNoThe portion of this scheduled payment taken by the accompanying payment method, with the same amount, currency and discount shape used elsewhere in the quote. Part of the signed quote: send it back exactly as the pricing quote gave it to you. Altering or omitting it breaks signature and the call fails with The pricing quote is invalid.
pricingQuote.overview.recurring.schedule[].breakdown[].paymentMethodobjectNoThe payment method that portion is collected by, carrying its id, description and translations. The id matches a payment method from List all payment methods. Part of the signed quote: send it back exactly as the pricing quote gave it to you. Altering or omitting it breaks signature and the call fails with The pricing quote is invalid.
pricingQuote.membershipsarray of objectYesOne entry per membership being sold. Required and non-empty (Memberships cannot be empty). The entries are matched to members by orderKey, and the same membership type may appear more than once — subject to the duplicate and concurrency rules, which reject a second copy of a membership type that does not allow duplicates, or an overlapping membership of the same priority where the type prevents concurrency.
pricingQuote.memberships[].orderKeyinteger (int32)NoThe line number of this membership within the quote, and the join key to orderKeyToMemberIdMap — it is how the platform decides which member receives which membership. Unless isJuniorMembershipWithGuardian is true, every orderKey you send in the map must appear here, or the call fails with At least one order key from the mapping object does not relate to any order key in the given pricing quote. Several quote lines may share an order key, in which case that member receives all of them. Part of the signed quote: send it back exactly as the pricing quote gave it to you. Altering or omitting it breaks signature and the call fails with The pricing quote is invalid.
pricingQuote.memberships[].idstringNoThe membership type being bought, as returned in membershipTypes[].id by Get membership types grouped by tag. Every distinct id here must still exist when this call runs, or you get At least one membership was not found in database. Part of the signed quote: send it back exactly as the pricing quote gave it to you. Altering or omitting it breaks signature and the call fails with The pricing quote is invalid.
pricingQuote.memberships[].descriptionstringNoThe membership type's display name as it stood when the quote was priced. Carried for presentation. Part of the signed quote: send it back exactly as the pricing quote gave it to you. Altering or omitting it breaks signature and the call fails with The pricing quote is invalid.
pricingQuote.memberships[].coolingOffPeriodinteger (int32)NoThe cancellation cooling-off period configured against this membership type, carried through from the quote. null when the type has none. Part of the signed quote: send it back exactly as the pricing quote gave it to you. Altering or omitting it breaks signature and the call fails with The pricing quote is invalid.
pricingQuote.memberships[].translationsarray of objectNoLocalised versions of description, one per configured culture. Part of the signed quote: send it back exactly as the pricing quote gave it to you. Altering or omitting it breaks signature and the call fails with The pricing quote is invalid.
pricingQuote.memberships[].translations[].typestringNoThe record type the translation belongs to. Part of the signed quote: send it back exactly as the pricing quote gave it to you. Altering or omitting it breaks signature and the call fails with The pricing quote is invalid.
pricingQuote.memberships[].translations[].culturestringNoThe culture code the text is for, such as en-GB. Part of the signed quote: send it back exactly as the pricing quote gave it to you. Altering or omitting it breaks signature and the call fails with The pricing quote is invalid.
pricingQuote.memberships[].translations[].textstringNoThe translated text. Part of the signed quote: send it back exactly as the pricing quote gave it to you. Altering or omitting it breaks signature and the call fails with The pricing quote is invalid.
pricingQuote.memberships[].startDatestring (date-time)NoWhen this membership begins. It is used for the overlap checks against the member's existing memberships, so it is not merely decorative — but it comes from the quote and cannot be changed here. To move the start date, get a new quote. Use Get earliest start date for membership before quoting so the date is valid. Part of the signed quote: send it back exactly as the pricing quote gave it to you. Altering or omitting it breaks signature and the call fails with The pricing quote is invalid.
pricingQuote.memberships[].endDatestring (date-time)NoWhen this membership ends. Also used for the overlap checks; for an auto-renewing membership type the platform treats the effective end as a hundred years later when testing for overlap, so an auto-renewing membership collides with almost anything of the same priority. Part of the signed quote: send it back exactly as the pricing quote gave it to you. Altering or omitting it breaks signature and the call fails with The pricing quote is invalid.
pricingQuote.memberships[].pricingobjectNoWhat this individual membership costs, split into what is due now and what recurs. This is the part of the quote that becomes real money: its contents are turned directly into invoice lines and instalments. Part of the signed quote: send it back exactly as the pricing quote gave it to you. Altering or omitting it breaks signature and the call fails with The pricing quote is invalid.
pricingQuote.memberships[].pricing.nowobjectNoWhat is payable immediately for this membership — joining fee plus any up-front portion of the subscription. Part of the signed quote: send it back exactly as the pricing quote gave it to you. Altering or omitting it breaks signature and the call fails with The pricing quote is invalid.
pricingQuote.memberships[].pricing.now.totalPriceobjectNoThe total due now for this membership. Part of the signed quote: send it back exactly as the pricing quote gave it to you. Altering or omitting it breaks signature and the call fails with The pricing quote is invalid.
pricingQuote.memberships[].pricing.now.totalPrice.amountstringNoThe amount due now, as a decimal string with two decimal places, so parse it as a decimal rather than a float. This value becomes the total of the sales invoice raised for this membership, so it is the figure the member will be charged. Part of the signed quote: send it back exactly as the pricing quote gave it to you. Altering or omitting it breaks signature and the call fails with The pricing quote is invalid.
pricingQuote.memberships[].pricing.now.totalPrice.currencystringNoThe ISO 4217 currency code for the amount due now. Part of the signed quote: send it back exactly as the pricing quote gave it to you. Altering or omitting it breaks signature and the call fails with The pricing quote is invalid.
pricingQuote.memberships[].pricing.now.totalPrice.discountobjectNoThe discount applied to the amount due now, or null when none applies. Its discountId is one of the sources used to record which discount the resulting membership was sold under. Part of the signed quote: send it back exactly as the pricing quote gave it to you. Altering or omitting it breaks signature and the call fails with The pricing quote is invalid.
pricingQuote.memberships[].pricing.now.breakdownarray of objectNoThe individual products making up the amount due now — typically the subscription product plus any one-off joining products. Each entry becomes a line on the sales invoice, so this is the most load-bearing array in the quote. Part of the signed quote: send it back exactly as the pricing quote gave it to you. Altering or omitting it breaks signature and the call fails with The pricing quote is invalid.
pricingQuote.memberships[].pricing.now.breakdown[].priceLevelIdstringNoThe price level this line was priced at — the pricing band the member qualified for. Part of the signed quote: send it back exactly as the pricing quote gave it to you. Altering or omitting it breaks signature and the call fails with The pricing quote is invalid.
pricingQuote.memberships[].pricing.now.breakdown[].productIdstringNoThe product being charged for on this line. Part of the signed quote: send it back exactly as the pricing quote gave it to you. Altering or omitting it breaks signature and the call fails with The pricing quote is invalid.
pricingQuote.memberships[].pricing.now.breakdown[].typestringNoWhat kind of charge this line is, which is how the platform tells the recurring subscription product apart from one-off joining products when building the invoice and the instalment. Part of the signed quote: send it back exactly as the pricing quote gave it to you. Altering or omitting it breaks signature and the call fails with The pricing quote is invalid.
pricingQuote.memberships[].pricing.now.breakdown[].descriptionstringNoThe product's display name, used as the invoice line description. Part of the signed quote: send it back exactly as the pricing quote gave it to you. Altering or omitting it breaks signature and the call fails with The pricing quote is invalid.
pricingQuote.memberships[].pricing.now.breakdown[].translationsarray of objectNoLocalised versions of the line description, each with type, culture and text. Part of the signed quote: send it back exactly as the pricing quote gave it to you. Altering or omitting it breaks signature and the call fails with The pricing quote is invalid.
pricingQuote.memberships[].pricing.now.breakdown[].priceobjectNoThe amount for this line, with the same amount, currency and discount shape used elsewhere in the quote. Part of the signed quote: send it back exactly as the pricing quote gave it to you. Altering or omitting it breaks signature and the call fails with The pricing quote is invalid.
pricingQuote.memberships[].pricing.recurringobjectNoThe ongoing cost of this membership after the initial payment. null for a one-off membership type. Part of the signed quote: send it back exactly as the pricing quote gave it to you. Altering or omitting it breaks signature and the call fails with The pricing quote is invalid.
pricingQuote.memberships[].pricing.recurring.membershipIdinteger (int32)No Part of the signed quote: send it back exactly as the pricing quote gave it to you. Altering or omitting it breaks signature and the call fails with The pricing quote is invalid.
pricingQuote.memberships[].pricing.recurring.headlinePriceobjectNoThe headline recurring amount for this membership — the figure a shopper thinks of as the price. Part of the signed quote: send it back exactly as the pricing quote gave it to you. Altering or omitting it breaks signature and the call fails with The pricing quote is invalid.
pricingQuote.memberships[].pricing.recurring.headlinePrice.amountstringNoThe recurring amount, as a decimal string with two decimal places, so parse it as a decimal rather than a float. Part of the signed quote: send it back exactly as the pricing quote gave it to you. Altering or omitting it breaks signature and the call fails with The pricing quote is invalid.
pricingQuote.memberships[].pricing.recurring.headlinePrice.currencystringNoThe ISO 4217 currency code of the recurring amount. Part of the signed quote: send it back exactly as the pricing quote gave it to you. Altering or omitting it breaks signature and the call fails with The pricing quote is invalid.
pricingQuote.memberships[].pricing.recurring.headlinePrice.discountobjectNoThe discount applied to the recurring amount, or null when none applies. Consulted when recording which discount the membership was sold under. Part of the signed quote: send it back exactly as the pricing quote gave it to you. Altering or omitting it breaks signature and the call fails with The pricing quote is invalid.
pricingQuote.memberships[].pricing.recurring.frequencystringNoHow often the recurring payment falls due for this membership. Part of the signed quote: send it back exactly as the pricing quote gave it to you. Altering or omitting it breaks signature and the call fails with The pricing quote is invalid.
pricingQuote.memberships[].pricing.recurring.schedulearray of objectNoThe forecast future payments for this membership. The first entry is used to set up the membership's instalment record, so this array is functional rather than informational — an empty or reordered schedule changes what is billed as well as breaking the signature. Part of the signed quote: send it back exactly as the pricing quote gave it to you. Altering or omitting it breaks signature and the call fails with The pricing quote is invalid.
pricingQuote.memberships[].pricing.recurring.schedule[].datestring (date-time)NoWhen this scheduled payment falls due. The date on the first entry becomes the membership's first instalment date. Part of the signed quote: send it back exactly as the pricing quote gave it to you. Altering or omitting it breaks signature and the call fails with The pricing quote is invalid.
pricingQuote.memberships[].pricing.recurring.schedule[].paymentsarray of objectNoThe payments due on this date, each carrying a price and the payment method collecting it. The discount on the first payment of the first entry is one of the sources used to record which discount the membership was sold under. Part of the signed quote: send it back exactly as the pricing quote gave it to you. Altering or omitting it breaks signature and the call fails with The pricing quote is invalid.
pricingQuote.timeStampstringYesWhen the quote was issued, as returned by the pricing call. It is the quote's expiry clock: the call is rejected once more than one hour has elapsed since this moment (The time frame where the request is considered valid has expired.), and also if it is more than ten minutes ahead of the server's UTC clock (The time stamp of the request can not be in the future.). Both comparisons are against the server's time, not yours, so leave a margin. Must not be empty, and must not be edited — it is inside the signature.
pricingQuote.signaturestringYesThe keyed hash computed over the rest of the quote when it was issued. The platform recomputes it from the quote you send, with signature itself excluded, and rejects any mismatch with The pricing quote is invalid. It is opaque — do not decode, regenerate or re-case it, and do not attempt to sign a quote yourself; the key is not issued to integrators. If you are hitting this error, the cause is almost always that something in pricingQuote was normalised, rounded or dropped in transit. Must not be empty.
orderKeyToMemberIdMaparray of objectYesWhich member receives which line of the quote. The quote itself names membership types and order keys but no members, so without this the platform does not know who is buying. Every member id here must be either the logged-in member or one of their linked members, or the whole call is rejected with 403. The first entry's memberId is treated as the principal member for the duplicate and direct-debit checks, so order it deliberately.
orderKeyToMemberIdMap[].orderKeyinteger (int32)YesThe quote line this member is taking, matching a pricingQuote.memberships[].orderKey. Must not be null — a null in either property of any entry fails with The properties of the orderKeyToMemberIdMap object cannot be null. Unless isJuniorMembershipWithGuardian is true, a key with no matching quote line fails with At least one order key from the mapping object does not relate to any order key in the given pricing quote.
orderKeyToMemberIdMap[].memberIdinteger (int32)YesThe member who will hold the membership, as returned when the member was created. Must not be null. It must be the authenticated member or one of their linked members — anything else returns 403 with The member logged in is only allowed to purchase memberships for themselves or their linked members. A linked member cannot be given a membership type whose applyToLinks is set; that fails with A secondary member cannot purchase a group membership. (or A junior member… when isJuniorMembershipWithGuardian is true).

Sample request

cURL
curl -X POST '{base_url}/api/memberships' \
  -H 'Authorization: Bearer {accessToken}' \
  -H 'X-Use-SSO: true' \
  -H 'Content-Type: application/json' \
  -d '{
  "isJuniorMembershipWithGuardian": true,
  "primaryPaysInd": true,
  "pricingQuote": {
    "siteId": "string",
    "promotionCode": "string",
    "overview": {
      "now": {
        "amount": "string",
        "currency": "GBP",
        "discount": {
          "priceBeforeDiscount": "string",
          "discountValue": "string",
          "discountId": 12345
        }
      },
      "recurring": {
        "headlinePrice": {
          "amount": "string",
          "currency": "GBP",
          "discount": {
            "priceBeforeDiscount": "string",
            "discountValue": "string",
            "discountId": 12345
          }
        },
        "frequency": "string",
        "schedule": [
          {
            "date": "2026-08-14T19:00:00Z",
            "totalPrice": {
              "amount": "string",
              "currency": "GBP",
              "discount": {
                "priceBeforeDiscount": "string",
                "discountValue": "string",
                "discountId": 12345
              }
            },
            "breakdown": [
              {
                "price": {
                  "amount": "string",
                  "currency": "GBP",
                  "discount": {}
                },
                "paymentMethod": {
                  "id": "string",
                  "description": "string",
                  "translations": []
                }
              }
            ]
          }
        ]
      }
    },
    "memberships": [
      {
        "orderKey": 1,
        "id": "string",
        "description": "string",
        "coolingOffPeriod": 1,
        "translations": [
          {
            "type": "string",
            "culture": "string",
            "text": "string"
          }
        ],
        "startDate": "2026-08-14T19:00:00Z",
        "endDate": "2026-08-14T19:00:00Z",
        "pricing": {
          "now": {
            "totalPrice": {
              "amount": "string",
              "currency": "GBP",
              "discount": {
                "priceBeforeDiscount": "string",
                "discountValue": "string",
                "discountId": 12345
              }
            },
            "breakdown": [
              {
                "priceLevelId": "string",
                "productId": "string",
                "type": "string",
                "description": "string",
                "translations": [
                  {}
                ],
                "price": {
                  "amount": "string",
                  "currency": "GBP",
                  "discount": {}
                }
              }
            ]
          },
          "recurring": {
            "membershipId": 12345,
            "headlinePrice": {
              "amount": "string",
              "currency": "GBP",
              "discount": {
                "priceBeforeDiscount": "string",
                "discountValue": "string",
                "discountId": 12345
              }
            },
            "frequency": "string",
            "schedule": [
              {
                "date": "2026-08-14T19:00:00Z",
                "payments": [
                  {}
                ]
              }
            ]
          }
        }
      }
    ],
    "timeStamp": "string",
    "signature": "string"
  },
  "orderKeyToMemberIdMap": [
    {
      "orderKey": 1,
      "memberId": 12345
    }
  ]
}'

Response fields

FieldTypeDescription
salesInvoiceIdsarray of integerEvery sales invoice raised by this call, across all memberships and members, flattened into one list. These invoices are unpaid. Pass them straight to Pay for sales invoices as salesInvoiceIds — that call requires them to still be unpaid and to belong to the member you name. Persist them before doing anything else: if you lose them, you have memberships with outstanding balances and no handle on them.
membershipsCreatedarray of objectThe same information broken down per membership, so you can tell which invoice belongs to which member. Use this rather than the flat salesInvoiceIds when the quote covered several members, because the payment call is scoped to a single memberId and you will need one payment per member.
membershipsCreated[].memberIdinteger (int32)The member this membership was created for, echoing one of the ids you sent in orderKeyToMemberIdMap.
membershipsCreated[].membershipIsDirectDebitbooleanWhether this membership collects by direct debit. When true the initial invoice still has to be paid through the payment endpoint; the direct debit governs the recurring collections that follow, and it is why the member needed bank details on file for the call to succeed at all.
membershipsCreated[].membershipIdinteger (int32)The platform's reference for the membership record that was created. This is a new membership instance, not the membership type id you sent in the quote.
membershipsCreated[].salesInvoiceIdsarray of integerThe invoices raised for this membership alone — the subset of the top-level salesInvoiceIds attributable to it. Group these by memberId to build the payment calls.

Sample response

201 Created
{
  "salesInvoiceIds": [
    12345
  ],
  "membershipsCreated": [
    {
      "memberId": 12345,
      "membershipIsDirectDebit": true,
      "membershipId": 12345,
      "salesInvoiceIds": [
        12345
      ]
    }
  ]
}

Errors

StatusMeaningWhen
400Bad requestThe pricing quote is invalid. — the recomputed signature does not match. Something inside pricingQuote was altered, reformatted or omitted.
400Bad requestThe time frame where the request is considered valid has expired. — more than an hour has passed since pricingQuote.timeStamp. Get a fresh quote; the old one cannot be revived.
400Bad requestThe time stamp of the request can not be in the future. — pricingQuote.timeStamp is more than ten minutes ahead of the server's UTC clock.
400Bad requestThe properties of the orderKeyToMemberIdMap object cannot be null. — an entry has a null orderKey or memberId.
400Bad requestAt least one order key from the mapping object does not relate to any order key in the given pricing quote. — only checked when isJuniorMembershipWithGuardian is not true.
400Bad requestA required field is missing: Site ID cannot be empty, Pricing quote overview cannot be empty, Memberships cannot be empty, TimeStamp cannot be empty or Signature cannot be empty.
400Bad requestAt least one membership was not found in database. — a membership type named in the quote no longer exists, or the quote lists ids that do not all resolve.
400Bad requestThe products of the memberships were not found. — the membership types exist but have no products configured, so nothing can be invoiced.
400Bad requestA secondary member cannot purchase a group membership. (or A junior member cannot purchase a group membership.) — a linked member was mapped to a membership type whose applyToLinks is set.
400Bad requestThe member does not meet the age range of the price level {priceLevelId} to be issued with the membership type {description}. — the member's date of birth falls outside the price level's age range. Members with no date of birth recorded skip this check entirely.
400Bad requestOne or more memberships cannot be created as they do not allow duplicate memberships — either the quote contains the same non-duplicable membership type twice, or the member already holds an active one.
400Bad requestOne or more of the memberships "{ids}" cannot be created as duplicate concurrent memberships or concurrent memberships with the same priority cannot be specified, or … cannot be created as concurrent memberships already exist with the same priority — overlapping dates on membership types that prevent concurrency. Note that an auto-renewing membership is treated as running a hundred years, so it overlaps almost everything of the same priority.
400Bad requestDirect debit memberships must have the same start day and duration. — the new direct debit memberships are incompatible with ones the member already pays for.
403ForbiddenThe member logged in is only allowed to purchase memberships for themselves or their linked members. — a memberId in orderKeyToMemberIdMap is neither the authenticated member nor one of their links.
422Unprocessable entityBank details are missing or unusable for a direct debit membership: Primary member must have bank details as this is a linked member and Primary Pays flag is set to true, Member must have bank details as this is a linked member and Primary Pays flag is set to false, Member does not have any bank accounts and one or more requested memberships require a bank account, Member does not have any suitable bank accounts for one or more of the requested memberships, or Member has no active bank accounts that are required for one or more of the requested memberships. Collect bank details first and retry with the same quote if it has not expired.
422Unprocessable entityUnable to process this request due to a configuration issue — the memberships could not be saved. Nothing is created.
500Internal server errorAn unexpected server-side failure. The memberships and invoices are saved together, so a 500 while saving leaves nothing behind — but a 500 after they are saved may not. Check for the memberships before retrying.

This page as markdown · All pages · llms.txt