> ## Documentation Index
> Fetch the complete documentation index at: https://docs.whop.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Migrate from Previous Versions

> Move an integration off API v2 or the public GraphQL API. Find the Current API replacement for each route or operation and what behaves differently.

Whop is retiring two older APIs. Both move to the Current API at `https://api.whop.com/api/v1`, with the same API keys.

| If your integration calls | It stops working | Guide |
| - | - | - |
| `https://api.whop.com/api/v2` or `https://api.whop.com/v2` | November 13, 2026 | [API v2](#v2) |
| `https://api.whop.com/public-graphql`, or uses the `@whop/api` package | October 26, 2026 | [Public GraphQL](#public-graphql) |

## Switch to the Current API

**1. Keep your API key**

The Current API uses the same account and app API keys. If a request returns `403`, the key is missing the permission that endpoint needs. Add it in **Developer → API keys**, or request it in your app's permissions.

**2. Install the SDK and pin a version**

The official SDKs call the Current API and send the API version they were built against. If you use `@whop/api`, replace it with `@whop/sdk`. Its `withUser` and `withCompany` helpers have no equivalent: pass the user or account ID on each request instead.

```typescript TypeScript theme={null}
import { WhopClient } from "@whop/sdk";

const client = new WhopClient({ token: process.env.WHOP_API_KEY });

const access = await client.users.checkAccess({
  id: "user_xxxxxxxxxxxxx",
  resource_id: "exp_xxxxxxxxxxxxx",
});
```

```python Python theme={null}
import os

from whop_sdk import Whop

client = Whop(token=os.environ["WHOP_API_KEY"])

membership = client.memberships.retrieve("mem_xxxxxxxxxxxxx")
```

```bash cURL theme={null}
curl https://api.whop.com/api/v1/memberships/mem_xxxxxxxxxxxxx \
  -H "Authorization: Bearer $WHOP_API_KEY" \
  -H "Api-Version-Date: 2026-08-21-1"
```

If you call the API without an SDK, send `Api-Version-Date` on every request. [API versions](/developer/api/versioning) explains pinning.

**3. Replace each call**

Use the tab for the API you're leaving, then remove the old base URL or package from your code.

**API v2**

### Find your v2 calls

Every v2 response carries three headers:

| Header | Example | Meaning |
| - | - | - |
| `Deprecation` | `@1791417600` | Whop deprecated this endpoint. The value is the deprecation date as Unix seconds. |
| `Sunset` | `Fri, 13 Nov 2026 00:00:00 GMT` | API v2 stops working on November 13, 2026. |
| `Link` | `<https://api.whop.com/api/v1/memberships/mem_xxxxxxxxxxxxx>; rel="successor-version", <https://docs.whop.com/developer/api/migrate-from-previous-versions#v2>; rel="deprecation"` | The `successor-version` URL is the Current API request that replaces this one. The `deprecation` URL is this guide. |

Log responses that include `Deprecation` to find every v2 call your integration still makes.

### Route map

#### Memberships and licenses

| v2 route | Current API | Notes |
| - | - | - |
| `GET /memberships` | `GET /memberships` | Cursor pagination. See [Pagination](#pagination). |
| `GET /memberships/{id}` | `GET /memberships/{id}` | Also accepts a license key in place of the `mem_` ID. |
| `POST /memberships/{id}` | `PATCH /memberships/{id}` | Send `metadata` to merge keys. Send `{}` to clear it. |
| `POST /memberships/{id}/cancel` | `POST /memberships/{id}/cancel` | Pass `cancel_at_period_end: true` to keep access until the renewal date. |
| `POST /memberships/{id}/terminate` | `POST /memberships/{id}/cancel` | Omit `cancel_at_period_end` to revoke access immediately. |
| `POST /memberships/{id}/add_free_days` | `POST /memberships/{id}/extend` | |
| `POST /memberships/{id}/validate_license` | `GET /memberships/{id}` and `PATCH /memberships/{id}` | Check the key and bind a machine from your server. See [License keys](#license-keys). |
| `GET /licenses` | `GET /memberships` | Each membership has a `license_key` when its product includes the Software app. |

#### Members and customers

| v2 route | Current API | Notes |
| - | - | - |
| `GET /members` | `GET /members` | |
| `GET /customers` | `GET /members` | Customers and members are the same resource. |
| `GET /members/{search}` | `GET /members` | Filter with `user_ids` or `query`. `GET /members/{id}` takes only a `mber_` ID. |
| `GET /customers/{search}` | `GET /members` | Same as above. |

#### Payments and checkout

| v2 route | Current API | Notes |
| - | - | - |
| `GET /payments` | `GET /payments` | Filters are singular, such as `status` and `membership_id`. |
| `GET /payments/{id}` | `GET /payments/{id}` | |
| `POST /payments/{id}/refund` | `POST /payments/{id}/refund` | |
| `GET /payment_fees?payment_id=` | `GET /payments/{id}/fees` | |
| `GET /payment_methods` | `GET /payment_methods` | |
| `POST /checkout_requests` | `POST /payments` | Pass `member_id` (`mber_`) and `payment_method_id`. The response is the payment. |
| `GET /checkout_requests/{id}` | `GET /payments/{id}` | `POST /payments` returns the payment directly, so you don't need to poll. |
| `GET /checkout_sessions` | `GET /checkout_configurations` | Checkout sessions are now called checkout configurations. |
| `GET /checkout_sessions/{id}` | `GET /checkout_configurations/{id}` | |
| `POST /checkout_sessions` | `POST /checkout_configurations` | The response includes `purchase_url`. |
| `DELETE /checkout_sessions/{id}` | `DELETE /checkout_configurations/{id}` | |

#### Products and plans

Plans are now called variants. Variant IDs keep the `plan_` prefix, so stored IDs still work.

| v2 route | Current API | Notes |
| - | - | - |
| `GET /products`, `GET /access_passes` | `GET /products` | Access passes are now called products. |
| `GET /products/{id}`, `GET /access_passes/{id}` | `GET /products/{id}` | |
| `POST /products/{id}`, `POST /access_passes/{id}` | `PATCH /products/{id}` | |
| `GET /plans` | `GET /variants` | |
| `GET /plans/{id}` | `GET /variants/{id}` | |
| `POST /plans` | `POST /variants` | |
| `POST /plans/{id}` | `PATCH /variants/{id}` | |
| `POST /plans/{id}/create_quick_link` | `POST /variants` | Create a variant with `visibility: "quick_link"`, `stock`, and `short_link`. The Current API doesn't copy an existing plan, so send its price fields too. |
| `GET /promo_codes` | `GET /promo_codes` | |
| `GET /promo_codes/{id}` | `GET /promo_codes/{id}` | |
| `POST /promo_codes` | `POST /promo_codes` | |
| `POST /promo_codes/{id}` | `POST /promo_codes/{id}/activate`, `POST /promo_codes/{id}/deactivate` | Covers `status` only. Create a new promo code to change its variants, stock, or metadata. Then deactivate the old one. |
| `DELETE /promo_codes/{id}` | `DELETE /promo_codes/{id}` | |

#### Account, experiences, reviews, and webhooks

| v2 route | Current API | Notes |
| - | - | - |
| `GET /company` | `GET /accounts/me` | |
| `GET /experiences` | `GET /experiences` | |
| `GET /experiences/{id}` | `GET /experiences/{id}` | |
| `POST /experiences/{id}` | `PATCH /experiences/{id}` | |
| `DELETE /experiences/{id}` | `DELETE /experiences/{id}` | |
| `GET /reviews` | `GET /reviews` | |
| `GET /reviews/{id}` | `GET /reviews/{id}` | |
| `GET /webhooks` | `GET /webhooks` | |
| `GET /webhooks/{id}` | `GET /webhooks/{id}` | |
| `POST /webhooks` | `POST /webhooks` | See [Webhooks](#webhooks). |
| `POST /webhooks/{id}` | `PATCH /webhooks/{id}` | |
| `DELETE /webhooks/{id}` | `DELETE /webhooks/{id}` | |

### What changed from v2

#### Partial updates

v2 updated a resource with `POST /resource/{id}`. The Current API uses `PATCH /resource/{id}` and changes only the fields you send.

#### License keys

v2 `validate_license` checked a key and bound it to a machine in one call. In the Current API, your server retrieves the membership by license key, checks its `status`, and writes a hardware ID to `metadata` the first time someone uses the key. The [SaaS guide](/supported-business-models/saas#license-key-integration) has the full example.

Keep these calls on your server. Your account API key can change every membership in your business, so never ship it inside downloadable software.

#### Webhooks in the v2 format

Webhooks created through v2 keep sending v2-format payloads. To receive the current format, create a new webhook from the dashboard or with `POST /webhooks`, move your handler to it, then delete the v2 webhook. [Webhooks](/developer/guides/webhooks) covers signature checks and event types.

**Public GraphQL**

### Find your GraphQL calls

If your app imports `WhopServerSdk` or `makeWhopServerSdk` from `@whop/api`, it calls the GraphQL API. Every method on that client, such as `whopSdk.access.checkIfUserHasAccessToExperience`, sends a GraphQL operation with the same name.

Every GraphQL response also carries these headers:

| Header | Example | Meaning |
| - | - | - |
| `Deprecation` | `@1789948800` | Whop deprecated this API. The value is the deprecation date as Unix seconds. |
| `Sunset` | `Mon, 26 Oct 2026 00:00:00 GMT` | The GraphQL API stops working on October 26, 2026. |
| `Link` | `<https://docs.whop.com/developer/api/migrate-from-previous-versions#public-graphql>; rel="deprecation"` | This guide. |

Search your code for `@whop/api`, or log responses that include `Deprecation`, to find every call your app still makes.

> [!NOTE]
>
> `verifyUserToken` from `@whop/api` checks the `x-whop-user-token` header locally and never calls the GraphQL API, so it keeps working after the sunset. [Authentication](/developer/guides/authentication) covers verifying the token in other languages.

### Operation map

#### Access checks

Most apps call one of these on every page load. All three map to one endpoint that takes the user ID and the experience, product, or account ID.

| GraphQL operation | Current API | Notes |
| - | - | - |
| `checkIfUserHasAccessToExperience` | `GET /users/{id}/access/{resource_id}` | Pass an `exp_` ID as `resource_id`. |
| `checkIfUserHasAccessToAccessPass` | `GET /users/{id}/access/{resource_id}` | Pass a `prod_` ID. Access passes are now called products. |
| `checkIfUserHasAccessToCompany` | `GET /users/{id}/access/{resource_id}` | Pass a `biz_` ID. |

The response has `has_access` and `access_level`, which is `customer`, `admin`, or `no_access`, the same values as `hasAccess` and `accessLevel` in GraphQL.

#### Chat and messages

| GraphQL operation | Current API | Notes |
| - | - | - |
| `listMessagesFromChat` | `GET /messages` | Pass the chat experience ID as `channel_id`. See [Stop polling chat](#stop-polling-chat). |
| `sendMessageToChat` | `POST /messages` | Pass the chat experience ID as `channel_id` and the text as `content`. |
| `sendDirectMessageToUser` | `POST /dm_channels`, then `POST /messages` | Create the conversation with `with_user_ids`, then send to its ID as `channel_id`. |
| `listDirectMessageConversations` | `GET /dm_channels` | |

#### Users

| GraphQL operation | Current API | Notes |
| - | - | - |
| `getUser` | `GET /users/{id}` | |
| `getCurrentUser` | `GET /users/me` | Returns the user the token belongs to. With an app API key, read the user ID from `verifyUserToken` and call `GET /users/{id}`. |
| `listUsersForExperience` | `GET /members` | Filter with `product_ids`. |

#### Experiences and products

| GraphQL operation | Current API | Notes |
| - | - | - |
| `getExperience` | `GET /experiences/{id}` | |
| `listExperiences` | `GET /experiences` | Filter with `product_id` or `app_id`. |
| `getAccessPass` | `GET /products/{id}` | |
| `listAccessPasses` | `GET /products` | |
| `listAccessPassesForExperience` | `GET /products` | |
| `createAccessPass` | `POST /products` | |
| `updateAccessPass` | `PATCH /products/{id}` | |
| `deleteAccessPass` | `DELETE /products/{id}` | |
| `listPlans` | `GET /variants` | Plans are now called variants. Variant IDs keep the `plan_` prefix. |

#### Your company

| GraphQL operation | Current API | Notes |
| - | - | - |
| `getCompany` | `GET /accounts/me` | Companies are now called accounts. |
| `listMemberships` | `GET /memberships` | |
| `listMembers`, `getMember` | `GET /members`, `GET /members/{id}` | |
| `listAuthorizedUsers` | `GET /authorized_users` | |
| `listWaitlistEntries` | `GET /waitlist_entries` | |
| `listReceiptsForCompany` | `GET /payments` | |

#### Payments

| GraphQL operation | Current API | Notes |
| - | - | - |
| `chargeUser` | `POST /payments` | |
| `createCheckoutSession` | `POST /checkout_configurations` | Checkout sessions are now called checkout configurations. The response includes `purchase_url`. |
| `payUser` | `POST /transfers` | |
| `listInvoices`, `getInvoice`, `createInvoice` | `GET /invoices`, `GET /invoices/{id}`, `POST /invoices` | |
| `listPromoCodes`, `createPromoCode` | `GET /promo_codes`, `POST /promo_codes` | |
| `listAffiliates`, `createAffiliate` | `GET /affiliates`, `POST /affiliates` | |

#### Files and notifications

| GraphQL operation | Current API | Notes |
| - | - | - |
| `uploadMedia`, `processAttachment` | `POST /files`, then `POST /files/{id}/complete` | |
| `getAttachment` | `GET /files/{id}` | |
| `sendPushNotification` | `POST /notifications` | Target an `experience_id` or a list of `user_ids`. |

#### Courses and forums

| GraphQL operation | Current API | Notes |
| - | - | - |
| `listCoursesForExperience` | `GET /courses` | Filter with `experience_id`. |
| `listCoursesForCompany` | `GET /courses` | Filter with `account_id`. |
| `getCourse` | `GET /courses/{id}` | |
| `getLesson` | `GET /course_lessons/{id}` | |
| `getUserLessonInteractions` | `GET /course_lesson_interactions` | Filter with `user_id`, `lesson_id`, or `course_id`. |
| `markLessonAsCompleted` | `POST /course_lessons/{lesson_id}/mark_as_completed` | |
| `listForumPostsFromForum` | `GET /forum_posts` | Filter with `experience_id`. |
| `createForumPost` | `POST /forum_posts` | |

#### Webhook operations

| GraphQL operation | Current API | Notes |
| - | - | - |
| `listWebhooks`, `getWebhook` | `GET /webhooks`, `GET /webhooks/{id}` | |
| `createWebhook` | `POST /webhooks` | |
| `updateWebhook` | `PATCH /webhooks/{id}` | |
| `deleteWebhook` | `DELETE /webhooks/{id}` | |
| `testWebhook` | `POST /webhooks/{id}/test` | |

If an operation your app uses isn't listed, search the [API reference](/api-reference) for the resource, or contact support with the operation name.

### Behavior changes

#### Stop polling chat

Apps that react to chat messages often called `listMessagesFromChat` on a timer. Subscribe to the `chat.message.created` webhook instead: Whop sends each new message to your server as it's posted, so you don't need to poll. Use `GET /messages` only to load history.

#### Field names

Current API fields use `snake_case` (`has_access`, `access_level`) where GraphQL used `camelCase` (`hasAccess`, `accessLevel`). Resource IDs keep their prefixes, so IDs you stored still work.

## Pagination

v2 paged with `page` and `per` and returned `pagination.current_page` and `pagination.total_page`. Current API lists return a `data` array and a `page_info` object. Pass `first` to set the page size and `after` with the previous `page_info.end_cursor` to get the next page. Stop when `page_info.has_next_page` is `false`. The SDKs page for you.

```typescript TypeScript theme={null}
for await (const membership of client.memberships.list({ product_id: "prod_xxxxxxxxxxxxx" })) {
  console.log(membership.id);
}
```

```python Python theme={null}
for membership in client.memberships.list(product_id="prod_xxxxxxxxxxxxx"):
    print(membership.id)
```

## Next steps

> **[API versions](/developer/api/versioning)**
>
> Pin a version so later changes never break your integration.

> **[Webhooks](/developer/guides/webhooks)**
>
> Verify signatures, handle the current event format, and receive chat messages instead of polling.

> **[Manage memberships](/developer/guides/memberships)**
>
> Cancel, pause, extend, and update memberships.

> **[App views](/developer/guides/app-views)**
>
> Check member access in experience and dashboard views.


## Related topics

- [API Versions](/developer/api/versioning.md)
- [Webhooks](/developer/guides/webhooks.md)
- [List Ad Pixels](/api-reference/beta/ad-pixels/list-ad-pixels.md)


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.