> For the complete documentation index, see [llms.txt](https://docs.podplay.app/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.podplay.app/api/use-cases/use-case-public-membership-info.md).

# Use Case: Public Membership Info

### Memberships API vs Membership Signup Page

The membership signup page and `GET /apis/v2/memberships` share the same backend data, but the page applies location or slug filters, active/listed rules, and billing tabs in the browser — and it groups products by membership plan instead of listing every Membership as its own card. An unfiltered API call from an admin key will return more results than what a Customer sees on the signup page.

#### Why the API key is not the end user

The API key runs as whichever admin user it was created for — not as an anonymous or logged-in Customer on the website. That means an admin key can return:

* Unlisted ("hidden") Memberships when using `includeUnlisted=true`
* Inactive Memberships when using `status=INACTIVE`
* Unlisted one-time price options inside `expand=items._links.oneTimePriceOptions` when the API user has manage permissions

Do not compare an unfiltered `GET /memberships` (no query parameters) from an admin API key to the public Membership page.

#### Step 1 — Request with explicit filters

Call `GET /memberships` with the following query parameters to match what the signup page shows:

| Parameter         | Value                                                       | Purpose                                                                                                                                                     |
| ----------------- | ----------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `status`          | `ACTIVE`                                                    | Excludes archived Memberships; same as the public page                                                                                                      |
| `areaId`          | `<area-id>`                                                 | Matches a location on the website. Resolve the slug via `GET /apis/v2/areas` first — the `id` is often the same as the slug (e.g. `les`) but always confirm |
| `ipp`             | `200`                                                       | The web client loads many plans; the API default page size is 30. Paginate if `_total` exceeds `ipp`                                                        |
| `expand`          | `items._links.areas` and `items._links.oneTimePriceOptions` | Same expansions the web client uses                                                                                                                         |
| `includeUnlisted` | Omit                                                        | Parity with the public listed catalog                                                                                                                       |
| `slug`            | `<membership-slug>`                                         | Only when mirroring a direct membership link (bypasses the listed filter for that slug)                                                                     |

**Example: resolve area, then list Memberships**

**Step 1 — List areas** (replace `TENANT` and `<API_KEY>`):

```
curl -sS -H "x-api-key: <API_KEY>" \
  "https://TENANT.podplay.app/apis/v2/areas?ipp=200"
```

**Step 2 — List Memberships** (website-aligned):

```
curl -sS -H "x-api-key: <API_KEY>" \
  "https://TENANT.podplay.app/apis/v2/memberships?status=ACTIVE&areaId=les&ipp=200&expand=items._links.areas&expand=items._links.oneTimePriceOptions"
```

> **Note for franchise / area-scoped API keys:** If the key is restricted to certain locations, always pass `areaId` explicitly. Omitting it may return Memberships for all areas that key's user can manage — broader than a single `location=` on the web.

#### Step 2 — Match the URL pattern

Installations differ. Match the API call to the URL pattern the Customer actually uses.

**Pattern A — Location and region (multi-location picker)**

Used when the tenant enables location filtering (`feature.membershipLocationFilter`). The URL carries slugs:

```
/account/membership?location=les&region=new-york
```

Optional `chargeType` URL parameters select the billing tab in the UI (`MONTHLY`, `YEARLY`, `NON_RECURRING`).

> **Important:** `chargeType` values are applied in the browser only. They are not sent to `GET /memberships`. Filter by `allowedChargeTypes` on the response instead — see Filter by charge type below.

**Matching API call:**

* Method: `GET /apis/v2/memberships`
* Query: `status=ACTIVE`, `areaId=les`, `ipp=200`, plus both `expand` parameters
* Use `areaId` (the area's `id` for `les`), not `region`

**Pattern B — Membership slug (direct product link)**

Used for a specific Membership landing page (common on newer setups):

```
https://app-staging.pingpod.com/account/membership?membership=home-astoria
/account/membership/home-astoria
```

The Customer is not always asked to pick region/location first; the Membership itself defines the offer and areas.

**Matching API call:**

* Query: `slug=home-astoria`, `expand=items._links.areas`, `expand=items._links.oneTimePriceOptions`
* Using `slug=` can return a plan even if it is unlisted on the public grid — same behavior as a "hidden" direct link

**Summary:** `areaId=` mirrors the location picker catalog. `slug=` mirrors a direct Membership URL.

#### Step 3 — Match the frontend grouping

The API returns a flat `items[]` array (one object per Membership). The website groups and filters that list in JavaScript.

**Memberships vs membership plans**

* **Membership** — The sellable product: price, areas, benefits, slug. Examples: `BASIC_NYC` ("New York"), `GLOBAL` ("Global").
* **Membership plan** — A group label stored on each Membership (`membershipPlan.id`, `membershipPlan.displayName`). Several Memberships can share one plan.

Example: `BASIC_NYC` and `BASIC_NYC_NJ` both belong to the plan `PingPod Access (BASIC_MEMBER)`. The website shows one card titled "PingPod Access" with a dropdown to choose between them — not two separate cards.

Group `items` by `membershipPlan.id` (or `membershipPlan.displayName`). Each group corresponds to one card on the signup page. Multiple Memberships in the same group appear as options in a dropdown on that card.

**Filter by charge type**

Billing tabs are not API query parameters. Filter the `items` array in code after you receive the response:

| Tab      | Filter                                                                                                                                         |
| -------- | ---------------------------------------------------------------------------------------------------------------------------------------------- |
| Monthly  | `allowedChargeTypes` includes `"MONTHLY"`                                                                                                      |
| Yearly   | `allowedChargeTypes` includes `"YEARLY"`                                                                                                       |
| One-time | `allowedChargeTypes` includes `"NON_RECURRING"`. For display parity with the UI, prefer `oneTimePriceOptions` entries where `listed` is `true` |

A Membership with `["MONTHLY", "NON_RECURRING"]` can appear on both the Monthly and One-time tabs depending on the selected tab.

<details>

<summary>Example API response and how it maps to the website (LES, Monthly tab)</summary>

Illustrative payload for `GET .../memberships?status=ACTIVE&areaId=les&ipp=200&expand=...` (fields shortened):

```json
{
  "items": [
    {
      "id": "GLOBAL",
      "displayName": "Global",
      "slug": "global",
      "status": "ACTIVE",
      "monthlyPrice": 120,
      "allowedChargeTypes": ["MONTHLY"],
      "membershipPlan": {
        "id": "GLOBAL_MEMBER",
        "displayName": "PingPod Fanatics"
      },
      "areas": { "items": [{ "id": "les" }, { "id": "w37" }], "_total": 23 }
    },
    {
      "id": "BASIC_NYC",
      "displayName": "New York",
      "slug": "basic-nyc",
      "status": "ACTIVE",
      "monthlyPrice": 90,
      "allowedChargeTypes": ["MONTHLY"],
      "membershipPlan": {
        "id": "BASIC_MEMBER",
        "displayName": "PingPod Access"
      },
      "areas": { "items": [{ "id": "les" }, { "id": "w37" }], "_total": 9 }
    },
    {
      "id": "BASIC_NYC_NJ",
      "displayName": "New York + New Jersey",
      "slug": "basic-nyc-nj",
      "status": "ACTIVE",
      "monthlyPrice": 100,
      "allowedChargeTypes": ["MONTHLY"],
      "membershipPlan": {
        "id": "BASIC_MEMBER",
        "displayName": "PingPod Access"
      },
      "areas": { "items": [{ "id": "les" }, { "id": "fort-lee" }], "_total": 10 }
    },
    {
      "id": "cb144c48-1d68-48cf-911a-637a5a237dd3",
      "displayName": "Membership Pricing Test",
      "slug": "membership-pricing-test",
      "status": "ACTIVE",
      "monthlyPrice": 110,
      "allowedChargeTypes": ["MONTHLY", "NON_RECURRING"],
      "membershipPlan": { "id": "test", "displayName": "Test" },
      "oneTimePriceOptions": {
        "items": [
          { "id": "option-1", "price": 0, "durationDays": 90, "listed": true }
        ],
        "_total": 1
      }
    }
  ],
  "_total": 6
}
```

How this maps to the website (LES, Monthly tab):

* `GLOBAL` → one card: **PingPod Fanatics** (standalone)
* `BASIC_NYC` + `BASIC_NYC_NJ` → one card: **PingPod Access**, with a dropdown ("New York", "New York + New Jersey")
* `membership-pricing-test` → card **Test**; also visible on the One-time tab

"New York" appears on the site under PingPod Access, not as its own top-level card next to Global.

</details>

#### Common questions

**Why does the API list more Memberships than the website?** The API key is running as an admin — it may have `includeUnlisted=true`, no `status` or `areaId` filter, or a franchise key without an explicit location. Also: the API returns flat items while the website shows grouped plan cards.

**Why is Global visible but not "New York"?** Global often has its own plan card ("PingPod Fanatics"). "New York" is usually inside the "PingPod Access" card as a dropdown option, not a separate top-level card.

**Why does the API include New York on Monthly but not on One-time?** `BASIC_NYC` is typically monthly-only (`allowedChargeTypes: ["MONTHLY"]`). Filter by tab after the `GET`.

**What is the difference between `?location=les` and `?membership=home-astoria`?** `location` / `region` drives the regional catalog — use `areaId=` in the API. `membership` / path slug is a direct product link — use `slug=` in the API.

#### Integrator checklist

1. Call `GET /memberships` with `status=ACTIVE`, `areaId` or `slug`, `ipp=200`, and both `expand` parameters.
2. Do not use `includeUnlisted=true` when comparing to the public signup page.
3. Remember the API key user may see more than an end Customer.
4. Group results by `membershipPlan.id`.
5. Filter by `allowedChargeTypes` for Monthly, Yearly, or One-time tabs.
6. Use `slug=` only for direct Membership URLs; use `areaId=` for location-picker flows.

#### API reference

* Base path: `/apis/v2/`
* Auth header: `x-api-key: <API_KEY>`
* List Memberships: `GET /memberships`
* List areas: `GET /areas`


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.podplay.app/api/use-cases/use-case-public-membership-info.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
