Create members with memberships

POST /api/memberships/add-members-with-memberships

Success: 201 Created

Overview

Creates a primary member, optionally a set of linked members alongside them, and the memberships they are buying — all from one signed pricing quote. It is the single call behind a self-service join: everything from "here are my details" to "here are the invoices to pay" happens here.

Get a pricing quote first. The quote is signed and must come back unaltered; change anything inside pricingQuote and the call fails with The pricing quote is invalid. It is valid for one hour from its timeStamp and may not be more than ten minutes ahead of the server's UTC clock. siteId at the top level must equal pricingQuote.siteId.

What this call does and does not finish. It is presented as one operation, but it runs as a sequence of steps: create the members, link them, write user fields, bank details, contact details, addresses, marketing consents and signed agreements, then create the memberships and confirm the members. If any step fails, the members created so far are deleted and you get the failing step's status back. That rollback can itself fail — when it does you are left with members that exist but have no memberships, and they must be cleaned up by hand.

What the rollback never covers is the money. A 201 means members, memberships and unpaid sales invoices exist. Nothing is paid, and no membership is active, until you settle membersCreated[].salesInvoiceIds with Pay for sales invoices — one call per member, since that endpoint is scoped to a single memberId. Stop between the two and you have real members holding real memberships with an outstanding balance. The call is not idempotent: retrying creates a second set of everything, or fails on duplicate-member validation.

Order keys are how the quote lines are matched to the people in this request. Each member carries an orderKey that must correspond to a pricingQuote.memberships[].orderKey, so the platform knows who is buying what. A member with orderKey 0 or absent is created but given no membership.

Endpoint

POST {base_url}/api/memberships/add-members-with-memberships

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

Request body

FieldTypeRequiredDescription
siteIdstringYesThe site the members are being created at and the memberships sold at. Maximum 15 characters and must not be empty. It must be identical to pricingQuote.siteId — a mismatch is the first thing checked and fails with The site ID of the member must match the site ID in the membership pricing quote. It also overwrites every profile.siteId in the request.
primaryMemberobjectYesPrimaryMember cannot be empty.
primaryMember.orderKeyinteger (int32)NoWhich line of the pricing quote this primary member is buying, matching a pricingQuote.memberships[].orderKey. At least one member in the request must carry a non-zero order key, or the call fails with At least one orderKey for the Primary or Linked members must be specified. Order keys must be unique across the primary and all linked members (The orderKeys for the Primary and Linked members cannot be repeated.). Omitting it, or sending 0, creates the primary member without any membership — useful for a child who is only being added to the account. Unless hasGuardian puts the request into guardian mode, every non-zero key must exist in the quote.
primaryMember.profileobjectYesProfile cannot be empty.
primaryMember.profile.titleIdstringNoTitle ID cannot be more than 15 characters.
primaryMember.profile.firstNamestringYesFirst name cannot be more than 25 characters.
primaryMember.profile.emailstringNoThe primary member's email address. Effectively required: an empty value or one over 255 characters is rejected with The primary member's email must be supplied and be less than 256 characters., despite the field being optional in the schema. It is also used for the duplicate-member check, so an address already on file can fail the whole request.
primaryMember.profile.lastNamestringYesLast name cannot be more than 25 characters.
primaryMember.profile.dateOfBirthstring (date-time)NoDate of Birth cannot be in the future.
primaryMember.profile.siteIdstringNoIgnored. Whatever you send here is overwritten with the top-level siteId before the member is created, so there is no way to place a member at a different site from the one the quote was priced for.
primaryMember.profile.pendingConfirmationbooleanNoMarks the member as awaiting confirmation at the moment they are created. It does not survive a successful call: once the memberships are created this endpoint confirms every member it made, so a 201 always leaves confirmed members. It matters only in that a member left behind by a failed rollback may still be in the pending state.
primaryMember.profile.memberLanguagestringNoIgnored. Overwritten with the top-level memberLanguage before the member is created. Set the language once at the top level; there is no way to give members in the same request different languages.
primaryMember.addressDetailsobjectNoThe primary member's addresses, up to two. The whole object is optional — omit it and no address is recorded and no address validation runs at all. But once you supply it, exactly one of address1 and address2 must be marked isPrimary, or you get The primary member needs to have exactly one primary contact address.
primaryMember.addressDetails.address1objectNoThe primary member's first address. Sending it entirely empty while marking it primary is rejected — The primary contact address of the primary member cannot be empty. Individual fields are limited to 75 characters, except postalCode and country at 25.
primaryMember.addressDetails.address1.addressLine1stringYesAddressLine1 cannot be more than 75 characters.
primaryMember.addressDetails.address1.addressLine2stringNoAddressLine2 cannot be more than 75 characters.
primaryMember.addressDetails.address1.addressLine3stringNoAddressLine3 cannot be more than 75 characters.
primaryMember.addressDetails.address1.townCitystringYesTownCity cannot be more than 75 characters.
primaryMember.addressDetails.address1.regionstringNoRegion cannot be more than 75 characters.
primaryMember.addressDetails.address1.postalCodestringYesPostalCode cannot be more than 25 characters.
primaryMember.addressDetails.address1.countrystringNoCountry cannot be more than 25 characters.
primaryMember.addressDetails.address1.isPrimarybooleanNoMarks this as the primary member's main address. Exactly one address must carry true whenever addressDetails is supplied; both true or neither true fails validation. Defaults to false when omitted, so a single address object with this left out will fail.
primaryMember.addressDetails.address2objectNoA second address for the primary member, such as a term-time or correspondence address. Optional. Subject to the same emptiness and length rules as address1, and it counts towards the exactly-one-primary rule.
primaryMember.addressDetails.address2.addressLine1stringYesAddressLine1 cannot be more than 75 characters.
primaryMember.addressDetails.address2.addressLine2stringNoAddressLine2 cannot be more than 75 characters.
primaryMember.addressDetails.address2.addressLine3stringNoAddressLine3 cannot be more than 75 characters.
primaryMember.addressDetails.address2.townCitystringYesTownCity cannot be more than 75 characters.
primaryMember.addressDetails.address2.regionstringNoRegion cannot be more than 75 characters.
primaryMember.addressDetails.address2.postalCodestringYesPostalCode cannot be more than 25 characters.
primaryMember.addressDetails.address2.countrystringNoCountry cannot be more than 25 characters.
primaryMember.addressDetails.address2.isPrimarybooleanNoMarks this as the primary member's main address instead of address1. Only one of the two may be true.
primaryMember.contactDetailsobjectNoThe primary member's phone numbers. The whole object is optional — omit it and no numbers are recorded and the primary-number rule is not applied. Supply it and exactly one of telephone1, telephone2 and mobile must be marked isPrimary: zero gives The primary member needs to have one primary contact phone number. and more than one gives The primary member cannot have multiple primary contact phone numbers. Note the rule counts flags, not numbers: an entry flagged primary with a blank number satisfies validation but records nothing.
primaryMember.contactDetails.telephone1objectNoA landline or general contact number for the primary member.
primaryMember.contactDetails.telephone1.numberstringNoThe number itself, as free text. Only entries with a non-empty number are saved, so a blank number here means nothing is recorded even though the entry may still count towards the primary-flag rule.
primaryMember.contactDetails.telephone1.isPrimarybooleanNoMarks this as the primary member's main contact number. Exactly one of the three numbers must carry true whenever contactDetails is supplied. Defaults to false.
primaryMember.contactDetails.telephone2objectNoA second landline or alternative contact number for the primary member.
primaryMember.contactDetails.telephone2.numberstringNoThe number itself, as free text. A blank number records nothing.
primaryMember.contactDetails.telephone2.isPrimarybooleanNoMarks this as the primary member's main contact number. Only one number across the three may be true.
primaryMember.contactDetails.mobileobjectNoA mobile number for the primary member.
primaryMember.contactDetails.mobile.numberstringNoThe number itself, as free text. A blank number records nothing.
primaryMember.contactDetails.mobile.isPrimarybooleanNoMarks the mobile as the primary member's main contact number. Only one number across the three may be true.
primaryMember.bankDetailsobjectNoBank details for collecting direct debit payments. Whether it is required is decided from the quote, not from a flag you set: if any membership's recurring schedule names a BACS direct debit payment method other than the operator's default recurring-card method, omitting this fails with Membership types "{ids}" require the primary member to provide bank details. Only the primary member's bank details are ever taken — linked members cannot supply their own here — and they are only saved when the quote actually needs them, so sending them for a card-only purchase is silently ignored. accountNumber must be exactly 8 digits and sortCode exactly 6, both as strings; a leading zero dropped by a JSON number type is a common cause of rejection.
primaryMember.bankDetails.accountNamestringYesMaximum 50 characters.
primaryMember.bankDetails.accountNumberstringYesValue must be a string of 8 digits.
primaryMember.bankDetails.sortCodestringYesValue must be a string of 6 digits.
primaryMember.userFieldsarray of objectNoValues for the operator's custom member fields. Optional; omit or send an empty array when there are none. Each entry must carry both an id and a value — there is no way to send a field with a deliberately blank value through this endpoint.
primaryMember.userFields[].userFieldIdinteger (int32)YesUser field ID cannot be empty.
primaryMember.userFields[].valuestringYesUser field value cannot be empty.
primaryMember.optInMarketingbooleanNoA single switch that opts the primary member in to every marketing campaign and every communication channel the operator has configured, at siteId. There is no per-channel or per-campaign control here: it is all or nothing. Defaults to false, which records no consent at all rather than an explicit refusal.
primaryMember.signedAgreementsobjectNoWhich agreements the primary member has accepted, and the object that makes the call succeed or fail: privacyPolicy and termsAndConditions must both be true. Omitting the object entirely is treated the same as refusing, and fails with The primary member needs to agree to the privacy policy and terms and conditions. The date of acceptance is recorded by the platform at the time of the call; you cannot backdate it.
primaryMember.signedAgreements.privacyPolicybooleanNoWhether the primary member has accepted the privacy policy. Must be true — an omitted or false value fails the whole request before any member is created.
primaryMember.signedAgreements.termsAndConditionsbooleanNoWhether the primary member has accepted the terms and conditions. Must be true — an omitted or false value fails the whole request before any member is created.
primaryMember.signedAgreements.healthCommitmentStatementbooleanNoWhether the primary member has accepted the health commitment statement. Genuinely optional, unlike the other two: false or omitted is accepted and simply records no acceptance.
linkedMembersarray of objectNoAdditional members created alongside the primary member and linked to them, such as family on a joint membership. Optional; omit or send an empty array for a single-member join. Every linked member is subject to the same agreement, contact and address rules as the primary. They are linked to the primary member as part of this call, and if any later step fails they are deleted along with the primary. A linked member cannot be given a membership type that applies to links — that is what the primary member's membership does.
linkedMembers[].orderKeyinteger (int32)NoWhich line of the pricing quote this linked member is buying, matching a pricingQuote.memberships[].orderKey. At least one member in the request must carry a non-zero order key, or the call fails with At least one orderKey for the Primary or Linked members must be specified. Order keys must be unique across the primary and all linked members (The orderKeys for the Primary and Linked members cannot be repeated.). Omitting it, or sending 0, creates the linked member without any membership — useful for a child who is only being added to the account. Unless hasGuardian puts the request into guardian mode, every non-zero key must exist in the quote.
linkedMembers[].profileobjectYesProfile cannot be empty.
linkedMembers[].profile.titleIdstringNoTitle ID cannot be more than 15 characters.
linkedMembers[].profile.firstNamestringYesFirst name cannot be more than 25 characters.
linkedMembers[].profile.emailstringNoThe linked member's email address. Optional only for members young enough: if this is empty, the platform compares the member's dateOfBirth against the operator's age-of-online-account setting and rejects the request with One or more linked members are missing an email address. if they are old enough to hold their own account. A member with no dateOfBirth and no email is treated as old enough and so fails. When supplied it must be a well-formed address, or you get The email address "{email}"is not a valid email address.
linkedMembers[].profile.lastNamestringYesLast name cannot be more than 25 characters.
linkedMembers[].profile.dateOfBirthstring (date-time)NoDate of Birth cannot be in the future.
linkedMembers[].profile.siteIdstringNoIgnored. Whatever you send here is overwritten with the top-level siteId before the member is created, so there is no way to place a member at a different site from the one the quote was priced for.
linkedMembers[].profile.pendingConfirmationbooleanNoMarks the member as awaiting confirmation at the moment they are created. It does not survive a successful call: once the memberships are created this endpoint confirms every member it made, so a 201 always leaves confirmed members. It matters only in that a member left behind by a failed rollback may still be in the pending state.
linkedMembers[].profile.memberLanguagestringNoIgnored. Overwritten with the top-level memberLanguage before the member is created. Set the language once at the top level; there is no way to give members in the same request different languages.
linkedMembers[].addressDetailsobjectNoThe linked member's addresses, up to two. The whole object is optional — omit it and no address is recorded and no address validation runs at all. But once you supply it, exactly one of address1 and address2 must be marked isPrimary, or you get The linked member does not have a primary contact address. or … contains multiple primary contact addresses., each naming the offending orderKey.
linkedMembers[].addressDetails.address1objectNoThe linked member's first address. Sending it entirely empty while marking it primary is rejected — The linked member cannot have an address with empty values. Individual fields are limited to 75 characters, except postalCode and country at 25.
linkedMembers[].addressDetails.address1.addressLine1stringYesAddressLine1 cannot be more than 75 characters.
linkedMembers[].addressDetails.address1.addressLine2stringNoAddressLine2 cannot be more than 75 characters.
linkedMembers[].addressDetails.address1.addressLine3stringNoAddressLine3 cannot be more than 75 characters.
linkedMembers[].addressDetails.address1.townCitystringYesTownCity cannot be more than 75 characters.
linkedMembers[].addressDetails.address1.regionstringNoRegion cannot be more than 75 characters.
linkedMembers[].addressDetails.address1.postalCodestringYesPostalCode cannot be more than 25 characters.
linkedMembers[].addressDetails.address1.countrystringNoCountry cannot be more than 25 characters.
linkedMembers[].addressDetails.address1.isPrimarybooleanNoMarks this as the linked member's main address. Exactly one address must carry true whenever addressDetails is supplied; both true or neither true fails validation. Defaults to false when omitted, so a single address object with this left out will fail.
linkedMembers[].addressDetails.address2objectNoA second address for the linked member, such as a term-time or correspondence address. Optional. Subject to the same emptiness and length rules as address1, and it counts towards the exactly-one-primary rule.
linkedMembers[].addressDetails.address2.addressLine1stringYesAddressLine1 cannot be more than 75 characters.
linkedMembers[].addressDetails.address2.addressLine2stringNoAddressLine2 cannot be more than 75 characters.
linkedMembers[].addressDetails.address2.addressLine3stringNoAddressLine3 cannot be more than 75 characters.
linkedMembers[].addressDetails.address2.townCitystringYesTownCity cannot be more than 75 characters.
linkedMembers[].addressDetails.address2.regionstringNoRegion cannot be more than 75 characters.
linkedMembers[].addressDetails.address2.postalCodestringYesPostalCode cannot be more than 25 characters.
linkedMembers[].addressDetails.address2.countrystringNoCountry cannot be more than 25 characters.
linkedMembers[].addressDetails.address2.isPrimarybooleanNoMarks this as the linked member's main address instead of address1. Only one of the two may be true.
linkedMembers[].contactDetailsobjectNoThe linked member's phone numbers. The whole object is optional — omit it and no numbers are recorded and the primary-number rule is not applied. Supply it and exactly one of telephone1, telephone2 and mobile must be marked isPrimary: zero gives The linked member does not have a primary contact phone number. and more than one gives The linked member contains multiple primary contact phone numbers., each naming the offending orderKey. Note the rule counts flags, not numbers: an entry flagged primary with a blank number satisfies validation but records nothing.
linkedMembers[].contactDetails.telephone1objectNoA landline or general contact number for the linked member.
linkedMembers[].contactDetails.telephone1.numberstringNoThe number itself, as free text. Only entries with a non-empty number are saved, so a blank number here means nothing is recorded even though the entry may still count towards the primary-flag rule.
linkedMembers[].contactDetails.telephone1.isPrimarybooleanNoMarks this as the linked member's main contact number. Exactly one of the three numbers must carry true whenever contactDetails is supplied. Defaults to false.
linkedMembers[].contactDetails.telephone2objectNoA second landline or alternative contact number for the linked member.
linkedMembers[].contactDetails.telephone2.numberstringNoThe number itself, as free text. A blank number records nothing.
linkedMembers[].contactDetails.telephone2.isPrimarybooleanNoMarks this as the linked member's main contact number. Only one number across the three may be true.
linkedMembers[].contactDetails.mobileobjectNoA mobile number for the linked member.
linkedMembers[].contactDetails.mobile.numberstringNoThe number itself, as free text. A blank number records nothing.
linkedMembers[].contactDetails.mobile.isPrimarybooleanNoMarks the mobile as the linked member's main contact number. Only one number across the three may be true.
linkedMembers[].bankDetailsobjectNoIgnored. Only primaryMember.bankDetails is used; bank details sent against a linked member are validated for format but never saved and never used to satisfy a direct debit requirement. If a linked member's membership needs a direct debit, put the details on the primary member.
linkedMembers[].bankDetails.accountNamestringYesMaximum 50 characters.
linkedMembers[].bankDetails.accountNumberstringYesValue must be a string of 8 digits.
linkedMembers[].bankDetails.sortCodestringYesValue must be a string of 6 digits.
linkedMembers[].userFieldsarray of objectNoValues for the operator's custom member fields. Optional; omit or send an empty array when there are none. Each entry must carry both an id and a value — there is no way to send a field with a deliberately blank value through this endpoint.
linkedMembers[].userFields[].userFieldIdinteger (int32)YesUser field ID cannot be empty.
linkedMembers[].userFields[].valuestringYesUser field value cannot be empty.
linkedMembers[].optInMarketingbooleanNoA single switch that opts the linked member in to every marketing campaign and every communication channel the operator has configured, at siteId. There is no per-channel or per-campaign control here: it is all or nothing. Defaults to false, which records no consent at all rather than an explicit refusal.
linkedMembers[].signedAgreementsobjectNoWhich agreements the linked member has accepted, and the object that makes the call succeed or fail: privacyPolicy and termsAndConditions must both be true. Omitting the object entirely is treated the same as refusing, and fails with All the linked members need to agree to the privacy policy and terms and conditions. The date of acceptance is recorded by the platform at the time of the call; you cannot backdate it.
linkedMembers[].signedAgreements.privacyPolicybooleanNoWhether the linked member has accepted the privacy policy. Must be true — an omitted or false value fails the whole request before any member is created.
linkedMembers[].signedAgreements.termsAndConditionsbooleanNoWhether the linked member has accepted the terms and conditions. Must be true — an omitted or false value fails the whole request before any member is created.
linkedMembers[].signedAgreements.healthCommitmentStatementbooleanNoWhether the linked member has accepted the health commitment statement. Genuinely optional, unlike the other two: false or omitted is accepted and simply records no acceptance.
pricingQuoteobjectYesPricing quote cannot be empty.
pricingQuote.siteIdstringYesThe site the quote was priced for. It must equal the top-level siteId exactly, or the request is rejected up front with The site ID of the member must match the site ID in the membership pricing quote. Must not be empty. 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.promotionCodestringNoThe promotion code the quote was priced under, if any. Read when creating the memberships to record promotion usage, so removing it both loses that record and breaks 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.overviewobjectYesPricing quote overview cannot be empty.
pricingQuote.overview.nowobjectNoThe total payable immediately across the whole basket, as opposed to the per-membership figures below. 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 the whole basket 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 figure the shopper is agreeing to pay each period. 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 — the schedule that drives billing is the per-membership one below. 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 objectYesMemberships cannot be empty.
pricingQuote.memberships[].orderKeyinteger (int32)NoThe line number of this membership within the quote, and the join key to the orderKey on primaryMember and linkedMembers — it is how the platform decides who receives which membership. Unless hasGuardian puts the request into guardian mode, every non-zero member orderKey must appear here or the call fails with The orderKeys for the primary and linked members don't match the orderKeys in the given pricing 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[].idstringNoThe membership type being bought, as returned in membershipTypes[].id by Get membership types grouped by tag. 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. 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. 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, fixed by the quote. Used for the overlap checks against memberships the member already holds, so it is functional rather than decorative — but it cannot be changed here. To move the start date, get a new quote, and use Get earliest start date for membership to find a date that will be accepted. 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 effective end is treated as a hundred years later, so it 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 turn 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 becomes the total of the sales invoice raised for this membership, so it is the figure the member will actually 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. 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. Each entry becomes a line on the sales invoice. 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 separates the recurring subscription product from one-off joining products when building the invoice. 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. 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. 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 sets up the membership's instalment record, and the whole array is scanned to decide whether bank details are needed — see primaryMember.bankDetails. 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 paymentMethod collecting it. The payment method ids here are what decide whether primaryMember.bankDetails is compulsory. 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 expiry clock: the request is rejected once more than one hour has passed (The time frame where the request is considered valid has expired.), or 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 use the server's time, not yours. Must not be empty, and is inside the signature so it cannot be adjusted.
pricingQuote.signaturestringYesThe keyed hash computed over the rest of the quote when it was issued. The platform recomputes it from what you send, with signature itself excluded, and rejects any mismatch with The pricing quote is invalid. Opaque — do not decode, regenerate or re-case it; the signing key is not issued to integrators. If you hit this error, something inside pricingQuote was normalised, rounded or dropped in transit. Must not be empty.
hasGuardianbooleanNoDeclares that the primary member is a guardian buying on behalf of the linked members rather than for themselves. Defaults to false. Setting it true only takes effect when there is at least one linked member, siteId matches the quote, and every linked member's order key is the one the quote's memberships carry; when all of that holds, the check that member order keys appear in the quote is skipped and the members are linked in guardian mode. In every other case a true here has no effect — so do not rely on it to bypass order key validation.
memberLanguagestringNoThe language to record against every member created by this call. It overwrites any memberLanguage set on an individual profile, so all members in one request get the same value.

Sample request

cURL
curl -X POST '{base_url}/api/memberships/add-members-with-memberships' \
  -H 'Authorization: Bearer {accessToken}' \
  -H 'X-Use-SSO: true' \
  -H 'Content-Type: application/json' \
  -d '{
  "siteId": "string",
  "primaryMember": {
    "orderKey": 1,
    "profile": {
      "titleId": "string",
      "firstName": "string",
      "email": "member@example.com",
      "lastName": "string",
      "dateOfBirth": "2026-08-14T19:00:00Z",
      "siteId": "string",
      "pendingConfirmation": true,
      "memberLanguage": "string"
    },
    "addressDetails": {
      "address1": {
        "addressLine1": "string",
        "addressLine2": "string",
        "addressLine3": "string",
        "townCity": "string",
        "region": "string",
        "postalCode": "string",
        "country": "string",
        "isPrimary": true
      },
      "address2": {
        "addressLine1": "string",
        "addressLine2": "string",
        "addressLine3": "string",
        "townCity": "string",
        "region": "string",
        "postalCode": "string",
        "country": "string",
        "isPrimary": true
      }
    },
    "contactDetails": {
      "telephone1": {
        "number": "string",
        "isPrimary": true
      },
      "telephone2": {
        "number": "string",
        "isPrimary": true
      },
      "mobile": {
        "number": "string",
        "isPrimary": true
      }
    },
    "bankDetails": {
      "accountName": "string",
      "accountNumber": "string",
      "sortCode": "string"
    },
    "userFields": [
      {
        "userFieldId": 12345
      }
    ],
    "optInMarketing": true,
    "signedAgreements": {
      "privacyPolicy": true,
      "termsAndConditions": true,
      "healthCommitmentStatement": true
    }
  },
  "linkedMembers": [
    {
      "orderKey": 1,
      "profile": {
        "titleId": "string",
        "firstName": "string",
        "email": "member@example.com",
        "lastName": "string",
        "dateOfBirth": "2026-08-14T19:00:00Z",
        "siteId": "string",
        "pendingConfirmation": true,
        "memberLanguage": "string"
      },
      "addressDetails": {
        "address1": {
          "addressLine1": "string",
          "addressLine2": "string",
          "addressLine3": "string",
          "townCity": "string",
          "region": "string",
          "postalCode": "string",
          "country": "string",
          "isPrimary": true
        },
        "address2": {
          "addressLine1": "string",
          "addressLine2": "string",
          "addressLine3": "string",
          "townCity": "string",
          "region": "string",
          "postalCode": "string",
          "country": "string",
          "isPrimary": true
        }
      },
      "contactDetails": {
        "telephone1": {
          "number": "string",
          "isPrimary": true
        },
        "telephone2": {
          "number": "string",
          "isPrimary": true
        },
        "mobile": {
          "number": "string",
          "isPrimary": true
        }
      },
      "bankDetails": {
        "accountName": "string",
        "accountNumber": "string",
        "sortCode": "string"
      },
      "userFields": [
        {
          "userFieldId": 12345
        }
      ],
      "optInMarketing": true,
      "signedAgreements": {
        "privacyPolicy": true,
        "termsAndConditions": true,
        "healthCommitmentStatement": 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"
  },
  "hasGuardian": true,
  "memberLanguage": "string"
}'

Response fields

FieldTypeDescription
membersCreatedarray of objectOne entry per member this call created, including any created without a membership. Match entries to your request by orderKey, except for members you sent with no order key — those come back with orderKey 0 and can only be identified by elimination, so keep the creation order if you need to tell them apart.
membersCreated[].orderKeyinteger (int32)The order key you sent for this member, echoed back. 0 for a member created without a membership, and 0 for a primary member sent without one — so it is not a reliable identifier on its own.
membersCreated[].memberIdinteger (int32)The platform's id for the newly created member. This is the value you pass as memberId when paying their invoices, and the handle you need if a later failure leaves the account to be cleaned up. Persist it before doing anything else.
membersCreated[].bankAccountReferencestringThe reference for the bank account stored against the primary member, returned only when bank details were both supplied and actually needed by the quote. null on every linked member, and null on the primary member when the purchase needed no direct debit — which is not an error.
membersCreated[].salesInvoiceIdsarray of integerThe unpaid invoices raised for this member. Pass them, together with this entry's memberId, to Pay for sales invoices — that endpoint is scoped to one member, so a family join needs one payment call per member with a non-empty list here. Empty for a member created without a membership.
membershipsCreatedarray of objectOne entry per membership created, which is the view you need when a single member bought more than one. A member appears once here per membership but only once in membersCreated.
membershipsCreated[].memberIdinteger (int32)The member this membership belongs to, matching a membersCreated[].memberId.
membershipsCreated[].membershipIsDirectDebitbooleanWhether this membership collects by direct debit. Even when true, the initial invoice still has to be paid through the payment endpoint — the direct debit governs the recurring collections that follow, and is why bank details were required for the call to succeed.
membershipsCreated[].membershipIdinteger (int32)The platform's reference for the membership record 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 union of these across a member equals that member's membersCreated[].salesInvoiceIds.
timeStampstringWhen this response was produced, in the same form as the pricing quote's timestamp. It is covered by signature and exists so the response can be shown to be fresh rather than replayed.
signaturestringA keyed hash over this response, computed the same way the pricing quote is signed. It lets a downstream step confirm the response has not been tampered with. You cannot verify it yourself — the signing key is not issued to integrators — so treat it as data to pass on rather than to check.

Sample response

201 Created
{
  "membersCreated": [
    {
      "orderKey": 1,
      "memberId": 12345,
      "bankAccountReference": "string",
      "salesInvoiceIds": [
        12345
      ]
    }
  ],
  "membershipsCreated": [
    {
      "memberId": 12345,
      "membershipIsDirectDebit": true,
      "membershipId": 12345,
      "salesInvoiceIds": [
        12345
      ]
    }
  ],
  "timeStamp": "string",
  "signature": "string"
}

Errors

StatusMeaningWhen
400Bad requestThe site ID of the member must match the site ID in the membership pricing quote. — checked first, before anything else.
400Bad requestThe pricing quote is invalid. — the recomputed signature does not match, because something inside pricingQuote was altered, reformatted or dropped.
400Bad requestThe time frame where the request is considered valid has expired. — more than an hour since pricingQuote.timeStamp. Get a fresh quote.
400Bad requestThe time stamp of the request can not be in the future. — the quote's timestamp is over ten minutes ahead of the server's UTC clock.
400Bad requestThe primary member's email must be supplied and be less than 256 characters.
400Bad requestOne or more linked members are missing an email address. — a linked member with no email is at or above the operator's age-of-online-account threshold. A linked member with no dateOfBirth and no email also trips this.
400Bad requestThe email address "{email}"is not a valid email address — a linked member's address is malformed.
400Bad requestAt least one orderKey for the Primary or Linked members must be specified. or The orderKeys for the Primary and Linked members cannot be repeated.
400Bad requestThe orderKeys for the primary and linked members don't match the orderKeys in the given pricing quote. — skipped only when hasGuardian puts the request into guardian mode.
400Bad requestContact number rules: The primary member needs to have one primary contact phone number., The primary member cannot have multiple primary contact phone numbers., or the linked-member equivalents naming an orderKey. Only applied when contactDetails is present.
400Bad requestAddress rules: The primary member needs to have exactly one primary contact address., The primary contact address of the primary member cannot be empty., or the linked-member equivalents naming an orderKey. Only applied when addressDetails is present.
400Bad requestThe primary member needs to agree to the privacy policy and terms and conditions. or All the linked members need to agree to the privacy policy and terms and conditions.
400Bad requestMembership types "{ids}" require the primary member to provide bank details — the quote's recurring schedule names a BACS direct debit payment method and primaryMember.bankDetails is absent.
400Bad requestThe primary member's memberCounter must be 0 — an internal field was populated on the request. Do not send memberCounter.
400Bad requestField validation failures: Site ID cannot be empty, Site ID cannot be more than 15 characters, PrimaryMember cannot be empty, Profile cannot be empty, Pricing quote cannot be empty, First name cannot be empty, First name cannot be more than 25 characters, Last name cannot be empty, Last name cannot be more than 25 characters, Title ID cannot be more than 15 characters, Date of Birth cannot be in the future, AddressLine1 cannot be more than 75 characters and its siblings, Value must be a string of 8 digits., Value must be a string of 6 digits., User field ID cannot be empty, User field value cannot be empty.
400Bad requestA later step rejected the data — for example a member that duplicates one already on file, or a membership rule such as a duplicate or concurrent membership. The message describes what was refused, and the members created so far have been deleted.
422Unprocessable entityA later step could not complete: a member's contact details could not be updated because of a concurrent change, or a membership could not be created. Also returned when the rollback could not identify a primary member to remove, which means members may remain.
500Internal server errorAn unexpected server-side failure, or a failure whose rollback itself failed. In the second case members have been created and not removed: look them up by the details you submitted and delete them before retrying, or you will hit duplicate-member validation. The body is empty in this case.
503Service unavailableA service this call depends on could not be reached — including Failed to retrieve payment method configuration. when the bank details requirement cannot be determined. Members created so far are rolled back.

This page as markdown · All pages · llms.txt