# Get earliest start date for membership

`GET /api/memberships/earlieststartdate`

- **Success:** `200 OK`
- **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: [Get earliest start date for membership](https://support.gladstonesoftware.cloud/reference/joining/get-earliest-start-date-for-membership)

## Overview

Works out the earliest date a membership may start, given the membership types being bought and
the site they are being bought at. Call it before you build a date picker or send a `startDate`
to [Create a membership for a member](create-a-membership-for-a-member.md) —
membership types can carry their own not-before date, and a site that has not opened yet imposes
one of its own.

The rule that catches people out is that the constraint is **relaxed, not tightened, by a missing
date**. If every membership type you name carries an earliest start date, the answer is the latest
of them. If even one of them carries none, the membership type dates are discarded entirely and
the answer falls back to the site's opening date. Either way the result is never in the past: it
is floored at today.

All dates are compared and returned with the time component stripped, and "today" is the server's
current date rather than yours — near midnight, or from another time zone, expect the boundary to
move.

## Endpoint

`GET {base_url}/api/memberships/earlieststartdate`

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

## Query parameters

| Name | Type | Required | Description |
|------|------|----------|-------------|
| `membershipTypeIds` | string | Yes | The membership types being bought, as a comma-separated list — for example `ADULT,CHILD`. Take the ids from `[].id` in [Get membership types grouped by tag](get-membership-types-grouped-by-tag.md). Matched case-insensitively. Although not marked as required, omitting it fails with `400` and `Please provide at least 1 or more membershipTypeIds`. The same id twice fails with `Request contains duplicate membershipTypeIds in route`, and any id that does not resolve fails the whole call with `404` and `One or more of the membershipTypeIds provided are invalid` — you are not told which one. |
| `siteId` | string | Yes | The site the membership will be sold at. Its opening date is the fallback constraint used whenever any of the named membership types has no earliest start date of its own. Despite being marked optional, omitting it fails with `400` and `Please provide a siteId`, and a site that does not exist fails with `404` and `siteId '{siteId}' does not exist`. |

## Sample request

```bash title="cURL"
curl -X GET '{base_url}/api/memberships/earlieststartdate' \
  -H 'Authorization: Bearer {accessToken}' \
  -H 'X-Use-SSO: true'
```

## Response fields

| Field | Type | Description |
|-------|------|-------------|
| `siteOpeningDate` | string (date-time) | The date the site opens or opened, with the time component zeroed. `null` when the site has no opening date recorded, which the platform treats as no constraint. Returned for context: it only feeds into `earliestStartDate` when at least one membership type has no earliest start date of its own. |
| `membershipTypes` | array of object | The membership types you asked about and the constraint each one carries, in the order the platform resolved them. Use it to explain the answer to a user — it is the only way to see which type is responsible for a late `earliestStartDate`, or that one has no constraint and so relaxed the calculation to the site opening date. |
| `membershipTypes[].id` | string | The membership type id, echoing one of the values you sent in `membershipTypeIds`. |
| `membershipTypes[].earliestStartDate` | string (date-time) | The not-before date configured against this membership type, with the time component zeroed. `null` means the type imposes no constraint — and a single `null` anywhere in this array changes the whole calculation, causing every other type's date here to be ignored in favour of `siteOpeningDate`. |
| `earliestStartDate` | string (date-time) | The answer: the earliest date a membership of these types may start at this site, with the time component zeroed. Never `null` and never in the past. When every entry in `membershipTypes` has an `earliestStartDate`, this is the latest of them, floored at today. When any entry has `null`, it is `siteOpeningDate` floored at today — which reduces to today whenever the site has already opened or has no opening date. |

## Sample response

```json title="200 OK"
{
  "siteOpeningDate": "2026-08-14T19:00:00Z",
  "membershipTypes": [
    {
      "id": "string",
      "earliestStartDate": "2026-08-14T19:00:00Z"
    }
  ],
  "earliestStartDate": "2026-08-14T19:00:00Z"
}
```

## Errors

| Status | Meaning | When |
|--------|---------|------|
| `400` | Bad request | `Please provide at least 1 or more membershipTypeIds` — the parameter is missing or empty. |
| `400` | Bad request | `Please provide a siteId` — the parameter is missing or empty. |
| `400` | Bad request | `Request contains duplicate membershipTypeIds in route` — the same id appears more than once, compared case-insensitively. |
| `404` | Not found | `One or more of the membershipTypeIds provided are invalid` — at least one id matched no membership type. The message does not say which. |
| `404` | Not found | `siteId '{siteId}' does not exist` |
| `500` | Internal server error | An unexpected server-side failure. Unusually for this platform, the response body contains the error message. |
