# Generate direct debit mandate document

`POST /api/members/{memberId}/documents/pre-purchase-dd-mandate`

- **Success:** `201 Created`
- **Products:** Operator (`FOH`), Consumer (`CONSUMER`) (any one of these products, not all)
- **Features:** Online Joining (`ONLINEJOIN`), Front of House Generic Access (`FOHGENERIC`) (any one of these features, not all)

HTML version: [Generate direct debit mandate document](https://support.gladstonesoftware.cloud/reference/members/generate-direct-debit-mandate-document)

## Overview

Produces the Direct Debit Instruction PDF a member signs when they buy a membership paid by direct debit, and returns the URL of the generated file. The document is *unconfirmed*: it records the mandate the member is agreeing to, before any invoice has been paid. A second, confirmed mandate is generated automatically by the platform once the membership invoice is paid — you do not call anything for that.

Call this immediately after [Create members with memberships](../joining/create-members-with-memberships.md), which is where the entire request body comes from. That call returns a signed payload; you send it back here unaltered. The payload is signed with an HMAC that Gladstone verifies, so you cannot hand-build this request, edit a value in it, or reuse a payload from a different purchase — any of those fail with `400` and `The request is invalid.`

The call is also time-limited: the `timeStamp` inside the signed payload is checked, and a payload whose timestamp is more than ten minutes in the future is rejected.

To build the mandate, the platform pulls together the tenant's direct debit service user number, the member's stored bank details and the member's profile. If any of those are missing — most commonly bank details that were never recorded — the call fails rather than producing a partial document, so record the member's bank details before you call this.

The document lists only the memberships in the payload flagged `membershipIsDirectDebit`, and it is stamped with today's date, so generate it at the point the member is ready to sign.

On success you get `201 Created` and the response body is a single JSON string: the URL of the generated PDF.

## Endpoint

`POST {base_url}/api/members/{memberId}/documents/pre-purchase-dd-mandate`

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

## Path parameters

| Name | Type | Required | Description |
|------|------|----------|-------------|
| `memberId` | integer (int32) | Yes | The member the mandate is for — the account holder whose bank details are printed on the document. Must be greater than `0`; `0` and `-999` are rejected with `400`. This member must also appear as one of the `membersCreated[].memberId` values in the body, or the call fails with `400`; where a purchase created several members, call this endpoint once per member who needs a mandate. |

## Request body

The body is the response you received from [Create members with memberships](../joining/create-members-with-memberships.md), sent back verbatim. Every field below is part of the signed payload, so changing, reordering the meaning of, or dropping any populated value invalidates `signature` and the request is rejected with `400 The request is invalid.` Treat the whole object as opaque.

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `membersCreated` | array of object | Yes | One entry per member created or updated by the purchase, copied from the `membersCreated` array of the create call. It must contain an entry whose `memberId` equals the `memberId` in the path, otherwise you get `400` and `The given member Id is not present in the request.` |
| `membersCreated[].orderKey` | integer (int32) | No | The line reference the purchase used to tie this member to the memberships being bought, echoed back from the create call. Not used to build the mandate, but it is covered by the signature, so send it as received. |
| `membersCreated[].memberId` | integer (int32) | No | The member this entry refers to. One of these must match the `memberId` in the path — that is the only check made against this array. |
| `membersCreated[].bankAccountReference` | string | No | The reference of the bank account recorded for the member during the purchase. The mandate itself is built from the member's stored bank details rather than from this value, so it may legitimately be `null` here — but if the create call returned a value, send it back unchanged. |
| `membersCreated[].salesInvoiceIds` | array of integer | No | The sales invoices raised for this member by the purchase, echoed back from the create call. Not used to build the mandate; present because it is part of the signed payload. |
| `membershipsCreated` | array of object | Yes | The memberships the purchase created, copied from the create call. At least one entry must have `membershipIsDirectDebit` set to `true`, otherwise there is nothing to mandate and the call fails with `400` and `No memberships with direct debit payment were present in the request.` |
| `membershipsCreated[].memberId` | integer (int32) | No | The member the membership was allocated to. This array is filtered by `membershipIsDirectDebit` only — entries for other members are not excluded, so a mandate can cover memberships bought for linked members in the same transaction. |
| `membershipsCreated[].membershipIsDirectDebit` | boolean | Yes | Whether this membership is paid by direct debit. This is the field that decides what goes on the document: only memberships with `true` are recorded against the generated mandate. If no entry is `true` the request is rejected. Do not set it yourself — it comes from the create call and is covered by the signature. |
| `membershipsCreated[].membershipId` | integer (int32) | No | The membership the mandate will be recorded against. Collected for every entry flagged `membershipIsDirectDebit` and stored with the generated document, which is how the platform later matches the confirmed mandate to the paid membership. |
| `membershipsCreated[].salesInvoiceIds` | array of integer | No | The sales invoices raised for this membership, echoed back from the create call. Not used to build the mandate; present because it is part of the signed payload. |
| `timeStamp` | string | Yes | The UTC time the create call produced this payload, in ISO 8601 round-trip format (`2026-08-19T14:03:11.1234567Z`). It is rejected if it is more than ten minutes ahead of the server's clock, which is what stops a payload being minted with a future date. There is no lower bound — an old payload is not refused on age alone — but it is covered by the signature, so you cannot refresh it yourself. |
| `signature` | string | Yes | The base64 HMAC-SHA512 that Gladstone put on the payload when the purchase completed. It is recomputed here over the rest of the body and compared; a mismatch is the single most common failure on this endpoint and always surfaces as `400 The request is invalid.` The check is on the values, not on your formatting — whitespace and property order in what you send do not matter — but every value must survive the round trip unchanged, including nulls and empty arrays. Do not attempt to compute it yourself; the signing key is not issued to integrators. |

## Sample request

```bash title="cURL"
curl -X POST '{base_url}/api/members/{memberId}/documents/pre-purchase-dd-mandate' \
  -H 'Authorization: Bearer {accessToken}' \
  -H 'X-Use-SSO: true' \
  -H 'Content-Type: application/json' \
  -d '{
  "membersCreated": [
    {
      "orderKey": 1,
      "memberId": 12345,
      "bankAccountReference": "string",
      "salesInvoiceIds": [
        12345
      ]
    }
  ],
  "membershipsCreated": [
    {
      "memberId": 12345,
      "membershipIsDirectDebit": true,
      "membershipId": 12345,
      "salesInvoiceIds": [
        12345
      ]
    }
  ],
  "timeStamp": "string",
  "signature": "string"
}'
```

## Sample response

```json title="201 Created"
"string"
```

## Errors

| Status | Meaning | When |
|--------|---------|------|
| `400` | Bad request | `signature` does not match the payload. The body is `The request is invalid.` Any edit to the body — including rebuilding it from your own model and losing a null or an empty array — produces this. |
| `400` | Bad request | `timeStamp` is more than ten minutes ahead of the server clock. The body is `The time stamp of the request can not be in the future.` |
| `400` | Bad request | No entry in `membersCreated` has a `memberId` matching the path. The body is `The given member Id is not present in the request.` |
| `400` | Bad request | No entry in `membershipsCreated` has `membershipIsDirectDebit` set to `true`. The body is `No memberships with direct debit payment were present in the request.` |
| `400` | Bad request | `memberId` in the path is `0` or negative. The body is `{memberId} is an invalid member ID`. |
| `422` | Unprocessable entity | The member has no usable bank details recorded. The body is `The member's bank details were not obtained successfully.` Record the member's bank account before calling this. |
| `422` | Unprocessable entity | The tenant has no direct debit service user number configured, or it is configured but blank. The bodies are `The Service User Number was not successfully obtained.` and `The Service User Number obtained has a null or blank value.` This is a configuration problem at the operator's end, not something you can fix in the request. |
| `422` | Unprocessable entity | The member has no active membership paid by direct debit. The body is `None of the active memberships returned contain a payment method of direct debit for the given member`. A related variant, `Error in configuration. None of the payment methods returned are configured with an EFT system specific for direct debits.`, means direct debit is not set up as a payment method for this tenant at all. |
| `422` | Unprocessable entity | The member's profile could not be read. The body is `The member's details were not obtained successfully.` |
| `422` | Unprocessable entity | The document could not be generated. The body is `The direct debit mandate could not be generated successfully.` Nothing is rolled back — the purchase and the memberships still exist, only the document is missing. |
| `500` | Internal server error | An unexpected server-side failure, including an unexpected response from one of the platform services this call depends on. Also returned when the platform's signing key is missing from configuration, in which case no request to this endpoint can succeed. |
| `503` | Service unavailable | One of the services this call depends on — system configuration, member account, member profile or documents — could not be reached at all. The body is `At least one of the services is unavailable.` Nothing is written, so the call is safe to retry. |
