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

# Subscriptions

> Manage subscription lifecycle from the Medusa Admin: list, inspect, pause, resume, cancel, schedule plan changes, and update shipping addresses.

The subscriptions endpoints give you full control over individual subscription records. You can query the paginated list for DataTable views, fetch a full detail payload for a specific subscription, and trigger lifecycle mutations — pause, resume, cancel, schedule a future plan change, or update the shipping address snapshot. All routes require an authenticated Medusa Admin user.

<Info>All routes require an authenticated Medusa Admin user. Unauthenticated requests return `401`.</Info>

## Status values

| Value       | Meaning                                    |
| ----------- | ------------------------------------------ |
| `active`    | Subscription is billing normally           |
| `paused`    | Subscription is on hold                    |
| `cancelled` | Subscription has been terminated           |
| `past_due`  | Last renewal failed; dunning may be active |

***

## GET /admin/subscriptions

Returns the paginated subscription list for Admin DataTable views.

### Query parameters

<ParamField query="limit" type="number">
  Number of results per page. Defaults to 20.
</ParamField>

<ParamField query="offset" type="number">
  Zero-based result offset for pagination.
</ParamField>

<ParamField query="q" type="string">
  Free-text search across customer name, email, and reference.
</ParamField>

<ParamField query="order" type="string">
  Field to sort by. Supported database-backed fields: `created_at`, `updated_at`, `status`, `frequency_interval`, `frequency_value`, `next_renewal_at`, `trial_ends_at`, `skip_next_cycle`. Supported in-memory fields: `customer_name`, `customer_email`, `product_title`, `variant_title`, `discount_value`.
</ParamField>

<ParamField query="direction" type="string">
  Sort direction. One of `asc` or `desc`.
</ParamField>

<ParamField query="status" type="string | string[]">
  Filter by subscription status. Accepts a single value or an array.
</ParamField>

<ParamField query="customer_id" type="string">
  Filter by customer ID.
</ParamField>

<ParamField query="product_id" type="string">
  Filter by product ID.
</ParamField>

<ParamField query="variant_id" type="string">
  Filter by variant ID.
</ParamField>

<ParamField query="next_renewal_from" type="string">
  ISO datetime lower bound for `next_renewal_at`.
</ParamField>

<ParamField query="next_renewal_to" type="string">
  ISO datetime upper bound for `next_renewal_at`.
</ParamField>

<ParamField query="is_trial" type="boolean">
  Filter to trial subscriptions only.
</ParamField>

<ParamField query="skip_next_cycle" type="boolean">
  Filter by whether the next cycle is skipped.
</ParamField>

### Response

<ResponseField name="subscriptions" type="object[]" required>
  Array of subscription list items.

  <Expandable title="subscription properties">
    <ResponseField name="id" type="string">Subscription ID.</ResponseField>
    <ResponseField name="reference" type="string">Human-readable reference (e.g. `SUB-001`).</ResponseField>
    <ResponseField name="status" type="string">Current subscription status.</ResponseField>
    <ResponseField name="customer" type="object">Customer summary including `id`, `full_name`, and `email`.</ResponseField>
    <ResponseField name="product" type="object">Product summary including `product_id`, `product_title`, `variant_id`, `variant_title`, and `sku`.</ResponseField>
    <ResponseField name="frequency" type="object">Cadence with `interval`, `value`, and `label`.</ResponseField>
    <ResponseField name="next_renewal_at" type="string">Technical billing anchor date.</ResponseField>
    <ResponseField name="effective_next_renewal_at" type="string">Projected next renewal shown in Admin when `skip_next_cycle` is enabled.</ResponseField>
    <ResponseField name="trial" type="object">Trial info with `is_trial` and `trial_ends_at`.</ResponseField>
    <ResponseField name="discount" type="object">Applied discount with `type`, `value`, and `label`.</ResponseField>
    <ResponseField name="skip_next_cycle" type="boolean">Whether the next billing cycle is skipped.</ResponseField>
    <ResponseField name="updated_at" type="string">Last update timestamp.</ResponseField>
  </Expandable>
</ResponseField>

<ResponseField name="count" type="number" required>Total matching records.</ResponseField>
<ResponseField name="limit" type="number" required>Page size used.</ResponseField>
<ResponseField name="offset" type="number" required>Result offset used.</ResponseField>

```json Response example theme={null}
{
  "subscriptions": [
    {
      "id": "sub_123",
      "reference": "SUB-001",
      "status": "active",
      "customer": {
        "id": "cus_123",
        "full_name": "Jane Doe",
        "email": "jane@example.com"
      },
      "product": {
        "product_id": "prod_123",
        "product_title": "Coffee Subscription",
        "variant_id": "variant_123",
        "variant_title": "1 kg",
        "sku": "COFFEE-1KG"
      },
      "frequency": {
        "interval": "month",
        "value": 1,
        "label": "Every month"
      },
      "next_renewal_at": "2026-04-15T10:00:00.000Z",
      "effective_next_renewal_at": "2026-04-15T10:00:00.000Z",
      "trial": {
        "is_trial": false,
        "trial_ends_at": null
      },
      "discount": {
        "type": "percentage",
        "value": 10,
        "label": "10% off"
      },
      "skip_next_cycle": false,
      "updated_at": "2026-03-28T12:00:00.000Z"
    }
  ],
  "count": 1,
  "limit": 20,
  "offset": 0
}
```

### Errors

| Code  | Error          | Meaning                                                                           |
| ----- | -------------- | --------------------------------------------------------------------------------- |
| `400` | `invalid_data` | Invalid query parameter shape, unsupported query value, or unsupported sort field |

***

## GET /admin/subscriptions/:id

Returns the full detail payload for a single subscription.

### Path parameters

<ParamField path="id" type="string" required>
  Subscription ID.
</ParamField>

### Response

Returns a `subscription` object with all list fields plus:

<ResponseField name="subscription" type="object" required>
  <Expandable title="additional detail fields">
    <ResponseField name="created_at" type="string">Creation timestamp.</ResponseField>
    <ResponseField name="started_at" type="string">When the subscription became active.</ResponseField>
    <ResponseField name="paused_at" type="string | null">When the subscription was paused, or `null`.</ResponseField>
    <ResponseField name="cancelled_at" type="string | null">When the subscription was cancelled, or `null`.</ResponseField>
    <ResponseField name="last_renewal_at" type="string | null">Last successful renewal timestamp.</ResponseField>
    <ResponseField name="shipping_address" type="object">Full shipping address snapshot.</ResponseField>
    <ResponseField name="pending_update_data" type="object | null">Scheduled plan change data, or `null` when none is pending.</ResponseField>
  </Expandable>
</ResponseField>

```json Response example theme={null}
{
  "subscription": {
    "id": "sub_123",
    "reference": "SUB-001",
    "status": "active",
    "customer": { "id": "cus_123", "full_name": "Jane Doe", "email": "jane@example.com" },
    "product": {
      "product_id": "prod_123",
      "product_title": "Coffee Subscription",
      "variant_id": "variant_123",
      "variant_title": "1 kg",
      "sku": "COFFEE-1KG"
    },
    "frequency": { "interval": "month", "value": 1, "label": "Every month" },
    "next_renewal_at": "2026-04-15T10:00:00.000Z",
    "effective_next_renewal_at": "2026-04-15T10:00:00.000Z",
    "trial": { "is_trial": false, "trial_ends_at": null },
    "discount": { "type": "percentage", "value": 10, "label": "10% off" },
    "skip_next_cycle": false,
    "updated_at": "2026-03-28T12:00:00.000Z",
    "created_at": "2026-03-01T10:00:00.000Z",
    "started_at": "2026-03-01T10:00:00.000Z",
    "paused_at": null,
    "cancelled_at": null,
    "last_renewal_at": "2026-03-15T10:00:00.000Z",
    "shipping_address": {
      "first_name": "Jane",
      "last_name": "Doe",
      "company": null,
      "address_1": "Main Street 1",
      "address_2": null,
      "city": "Warsaw",
      "postal_code": "00-001",
      "province": "Mazowieckie",
      "country_code": "PL",
      "phone": "+48123123123"
    },
    "pending_update_data": {
      "variant_id": "variant_456",
      "variant_title": "2 kg",
      "frequency_interval": "month",
      "frequency_value": 2,
      "effective_at": "2026-05-01T00:00:00.000Z"
    }
  }
}
```

### Errors

| Code  | Error       | Meaning                     |
| ----- | ----------- | --------------------------- |
| `404` | `not_found` | Subscription does not exist |

***

## GET /admin/orders/:id/subscription-summary

Returns the lightweight subscription context used by the custom `Subscription` widget on the native Medusa order detail page.

### Path parameters

<ParamField path="id" type="string" required>
  Medusa order ID.
</ParamField>

### Response

<ResponseField name="summary.is_subscription_order" type="boolean" required>
  Whether the order is linked to a subscription.
</ResponseField>

<ResponseField name="summary.subscription" type="object | null">
  Subscription widget payload, or `null` for one-time orders.
</ResponseField>

When linked, `summary.subscription` includes:

* `id`
* `reference`
* `status`
* `frequency_label`
* `discount`
* `next_renewal_at`
* `effective_next_renewal_at`

```json Response example theme={null}
{
  "summary": {
    "is_subscription_order": true,
    "subscription": {
      "id": "sub_123",
      "reference": "SUB-001",
      "status": "active",
      "frequency_label": "Every 2 weeks",
      "discount": {
        "type": "percentage",
        "value": 5,
        "label": "5% off"
      },
      "next_renewal_at": "2026-05-07T10:00:00.000Z",
      "effective_next_renewal_at": "2026-05-07T10:00:00.000Z"
    }
  }
}
```

<Note>
  The `discount` field is derived from the subscription's `pricing_snapshot`, not from Medusa's built-in order summary rows.
</Note>

***

## POST /admin/subscriptions/:id/pause

Pauses an active subscription. All mutation routes return the refreshed full detail payload.

### Path parameters

<ParamField path="id" type="string" required>
  Subscription ID.
</ParamField>

### Body parameters

<ParamField body="reason" type="string">
  Optional reason for the pause.
</ParamField>

<ParamField body="effective_at" type="string">
  ISO datetime for when the pause takes effect. Optional.
</ParamField>

```json Request example theme={null}
{
  "reason": "customer requested temporary stop",
  "effective_at": "2026-04-01T00:00:00.000Z"
}
```

### Errors

| Code  | Error          | Meaning                                              |
| ----- | -------------- | ---------------------------------------------------- |
| `400` | `invalid_data` | Invalid body payload                                 |
| `404` | `not_found`    | Subscription does not exist                          |
| `409` | `conflict`     | Subscription cannot be paused from its current state |

***

## POST /admin/subscriptions/:id/resume

Resumes a paused subscription.

### Path parameters

<ParamField path="id" type="string" required>
  Subscription ID.
</ParamField>

### Body parameters

<ParamField body="resume_at" type="string">
  ISO datetime for when the subscription resumes. Optional.
</ParamField>

<ParamField body="preserve_billing_anchor" type="boolean">
  When `true`, keeps the original billing anchor date. Optional.
</ParamField>

```json Request example theme={null}
{
  "resume_at": "2026-04-15T00:00:00.000Z",
  "preserve_billing_anchor": true
}
```

### Errors

| Code  | Error          | Meaning                                               |
| ----- | -------------- | ----------------------------------------------------- |
| `400` | `invalid_data` | Invalid body payload                                  |
| `404` | `not_found`    | Subscription does not exist                           |
| `409` | `conflict`     | Subscription cannot be resumed from its current state |

***

## POST /admin/subscriptions/:id/cancel

Cancels a subscription immediately or at end of cycle.

### Path parameters

<ParamField path="id" type="string" required>
  Subscription ID.
</ParamField>

### Body parameters

<ParamField body="reason" type="string">
  Optional cancellation reason.
</ParamField>

<ParamField body="effective_at" type="string">
  When to apply the cancellation. One of `immediately` or `end_of_cycle`. Optional.
</ParamField>

```json Request example theme={null}
{
  "reason": "retention flow failed",
  "effective_at": "end_of_cycle"
}
```

### Errors

| Code  | Error          | Meaning                                                 |
| ----- | -------------- | ------------------------------------------------------- |
| `400` | `invalid_data` | Invalid body payload                                    |
| `404` | `not_found`    | Subscription does not exist                             |
| `409` | `conflict`     | Subscription cannot be cancelled from its current state |

***

## POST /admin/subscriptions/:id/schedule-plan-change

Stores a future plan or cadence update in `pending_update_data`. The change is applied at the next renewal or at the specified `effective_at` date.

### Path parameters

<ParamField path="id" type="string" required>
  Subscription ID.
</ParamField>

### Body parameters

<ParamField body="variant_id" type="string" required>
  Target variant ID for the plan change.
</ParamField>

<ParamField body="frequency_interval" type="string" required>
  New billing interval. One of `week`, `month`, or `year`.
</ParamField>

<ParamField body="frequency_value" type="number" required>
  New billing cadence as a positive integer.
</ParamField>

<ParamField body="effective_at" type="string">
  ISO datetime when the change becomes effective. Optional.
</ParamField>

```json Request example theme={null}
{
  "variant_id": "variant_456",
  "frequency_interval": "month",
  "frequency_value": 2,
  "effective_at": "2026-05-01T00:00:00.000Z"
}
```

<Note>`pending_update_data` is returned inside the refreshed subscription detail payload after the change is scheduled.</Note>

### Errors

| Code  | Error          | Meaning                                                       |
| ----- | -------------- | ------------------------------------------------------------- |
| `400` | `invalid_data` | Invalid body payload                                          |
| `404` | `not_found`    | Subscription does not exist                                   |
| `409` | `conflict`     | Plan change is not allowed for the current subscription state |

***

## POST /admin/subscriptions/:id/update-shipping-address

Updates the subscription shipping address snapshot used by Admin and future operational flows.

### Path parameters

<ParamField path="id" type="string" required>
  Subscription ID.
</ParamField>

### Body parameters

<ParamField body="first_name" type="string" required>
  First name.
</ParamField>

<ParamField body="last_name" type="string" required>
  Last name.
</ParamField>

<ParamField body="address_1" type="string" required>
  Primary address line.
</ParamField>

<ParamField body="city" type="string" required>
  City.
</ParamField>

<ParamField body="postal_code" type="string" required>
  Postal or ZIP code.
</ParamField>

<ParamField body="country_code" type="string" required>
  Two-letter ISO country code.
</ParamField>

<ParamField body="company" type="string">
  Company name. Optional.
</ParamField>

<ParamField body="address_2" type="string">
  Secondary address line. Optional.
</ParamField>

<ParamField body="province" type="string">
  Province or state. Optional.
</ParamField>

<ParamField body="phone" type="string">
  Phone number. Optional.
</ParamField>

```json Request example theme={null}
{
  "first_name": "Anna",
  "last_name": "Nowak",
  "company": "ACME",
  "address_1": "Nowa 2",
  "address_2": "lok. 4",
  "city": "Krakow",
  "postal_code": "30-001",
  "province": "Malopolskie",
  "country_code": "PL",
  "phone": "+48111111111"
}
```

### Errors

| Code  | Error          | Meaning                     |
| ----- | -------------- | --------------------------- |
| `400` | `invalid_data` | Invalid body payload        |
| `404` | `not_found`    | Subscription does not exist |
