> ## 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.

# Renewals

> List and inspect renewal cycles, approve or reject pending plan changes, and force-run cycles using the Reorder Admin renewals API endpoints.

The renewal cycle endpoints expose the Admin renewal queue and let you act on individual cycles. You can list scheduled or failed cycles for DataTable views, fetch a full detail payload including attempt history and approval state, force-run a cycle manually, and approve or reject pending subscription changes before a cycle executes. All routes require an authenticated Medusa Admin user. All mutations are workflow-backed and return the refreshed detail payload.

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

## Status values

| Value        | Meaning                                              |
| ------------ | ---------------------------------------------------- |
| `scheduled`  | Cycle is queued for its scheduled date               |
| `processing` | Cycle execution is in progress                       |
| `succeeded`  | Cycle completed successfully                         |
| `failed`     | Cycle failed; attempt history is available in detail |

**Approval status values:** `pending`, `approved`, `rejected`. When approval is not required, the approval object returns `status: null`.

**Attempt status values:** `processing`, `succeeded`, `failed`.

***

## GET /admin/renewals

Returns the paginated renewal queue for Admin DataTable views.

### Query parameters

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

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

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

<ParamField query="order" type="string">
  Field to sort by. Database-backed: `scheduled_for`, `updated_at`, `created_at`, `status`, `approval_status`, `processed_at`. In-memory: `last_attempt_status`, `subscription_reference`, `customer_name`, `product_title`, `order_display_id`.
</ParamField>

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

<ParamField query="status" type="string | string[]">
  Filter by cycle status.
</ParamField>

<ParamField query="approval_status" type="string | string[]">
  Filter by approval status.
</ParamField>

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

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

<ParamField query="last_attempt_status" type="string | string[]">
  Filter by the status of the most recent attempt.
</ParamField>

<ParamField query="subscription_id" type="string">
  Filter to renewals for a specific subscription.
</ParamField>

<ParamField query="generated_order_id" type="string">
  Filter by the ID of the order generated by a renewal.
</ParamField>

### Response

<ResponseField name="renewals" type="object[]" required>
  Array of renewal cycle list items.

  <Expandable title="renewal properties">
    <ResponseField name="id" type="string">Renewal cycle ID.</ResponseField>
    <ResponseField name="status" type="string">Cycle status.</ResponseField>
    <ResponseField name="subscription" type="object">Subscription summary including reference, status, customer name, and product info.</ResponseField>
    <ResponseField name="scheduled_for" type="string">Operational renewal date stored on the cycle.</ResponseField>
    <ResponseField name="effective_scheduled_for" type="string">Projected delivery date when the linked subscription has a skipped next cycle.</ResponseField>
    <ResponseField name="last_attempt_status" type="string | null">Status of the most recent attempt.</ResponseField>
    <ResponseField name="last_attempt_at" type="string | null">Timestamp of the most recent attempt.</ResponseField>
    <ResponseField name="approval" type="object">Approval summary with `required`, `status`, `decided_at`, `decided_by`, and `reason`.</ResponseField>
    <ResponseField name="generated_order" type="object | null">Generated order summary, or `null` if no order has been created.</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}
{
  "renewals": [
    {
      "id": "re_123",
      "status": "scheduled",
      "subscription": {
        "subscription_id": "sub_123",
        "reference": "SUB-001",
        "status": "active",
        "customer_name": "Jane Doe",
        "product_title": "Coffee Subscription",
        "variant_title": "1 kg",
        "sku": "COFFEE-1KG"
      },
      "scheduled_for": "2026-04-15T10:00:00.000Z",
      "effective_scheduled_for": "2026-04-15T10:00:00.000Z",
      "last_attempt_status": "failed",
      "last_attempt_at": "2026-04-15T10:02:00.000Z",
      "approval": {
        "required": true,
        "status": "pending",
        "decided_at": null,
        "decided_by": null,
        "reason": null
      },
      "generated_order": null,
      "updated_at": "2026-04-15T10:02: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/renewals/:id

Returns the full detail payload for a single renewal cycle, including the full attempt history and operational metadata.

### Path parameters

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

### Response

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

<ResponseField name="renewal" type="object" required>
  <Expandable title="additional detail fields">
    <ResponseField name="created_at" type="string">Creation timestamp.</ResponseField>
    <ResponseField name="processed_at" type="string | null">When the cycle finished processing.</ResponseField>
    <ResponseField name="last_error" type="string | null">Last error message, if any.</ResponseField>
    <ResponseField name="pending_changes" type="object | null">Subscription plan changes pending approval for this cycle.</ResponseField>
    <ResponseField name="attempts" type="object[]">Full attempt history with attempt number, status, timestamps, error code, error message, payment reference, and order ID.</ResponseField>
    <ResponseField name="metadata" type="object">Operational metadata including `last_trigger_type` and `last_correlation_id`.</ResponseField>
  </Expandable>
</ResponseField>

```json Response example theme={null}
{
  "renewal": {
    "id": "re_123",
    "status": "failed",
    "subscription": {
      "subscription_id": "sub_123",
      "reference": "SUB-001",
      "status": "active",
      "customer_name": "Jane Doe",
      "product_title": "Coffee Subscription",
      "variant_title": "1 kg",
      "sku": "COFFEE-1KG"
    },
    "scheduled_for": "2026-04-15T10:00:00.000Z",
    "effective_scheduled_for": "2026-04-15T10:00:00.000Z",
    "last_attempt_status": "failed",
    "last_attempt_at": "2026-04-15T10:02:00.000Z",
    "approval": {
      "required": true,
      "status": "approved",
      "decided_at": "2026-04-15T09:55:00.000Z",
      "decided_by": "user_123",
      "reason": "approved for processing"
    },
    "generated_order": {
      "order_id": "order_123",
      "display_id": 1001,
      "status": "pending"
    },
    "updated_at": "2026-04-15T10:03:00.000Z",
    "created_at": "2026-04-10T10:00:00.000Z",
    "processed_at": "2026-04-15T10:03:00.000Z",
    "last_error": null,
    "pending_changes": {
      "variant_id": "variant_456",
      "variant_title": "2 kg",
      "frequency_interval": "month",
      "frequency_value": 2,
      "effective_at": null
    },
    "attempts": [
      {
        "id": "reatt_123",
        "attempt_no": 1,
        "status": "failed",
        "started_at": "2026-04-15T10:00:00.000Z",
        "finished_at": "2026-04-15T10:02:00.000Z",
        "error_code": "renewal_failed",
        "error_message": "payment failed",
        "payment_reference": null,
        "order_id": null
      }
    ],
    "metadata": {
      "last_trigger_type": "manual",
      "last_correlation_id": "renewal-admin-force-uuid"
    }
  }
}
```

### Errors

| Code  | Error       | Meaning                      |
| ----- | ----------- | ---------------------------- |
| `404` | `not_found` | Renewal cycle does not exist |

***

## POST /admin/renewals/:id/force

Manually triggers execution for a renewal cycle, ignoring its scheduled date.

### Path parameters

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

### Body parameters

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

```json Request example theme={null}
{
  "reason": "manual retry after review"
}
```

### Errors

| Code  | Error          | Meaning                                                 |
| ----- | -------------- | ------------------------------------------------------- |
| `404` | `not_found`    | Renewal cycle does not exist                            |
| `409` | `conflict`     | Cycle is already processing                             |
| `409` | `conflict`     | Cycle already succeeded; duplicate execution is blocked |
| `409` | `conflict`     | Cycle is not in a forceable state                       |
| `409` | `conflict`     | Cycle requires approved changes before force-run        |
| `409` | `conflict`     | Linked subscription is not eligible for renewal         |
| `400` | `invalid_data` | Current Plans & Offers policy blocks the pending change |

***

## POST /admin/renewals/:id/approve-changes

Approves pending subscription changes for a renewal cycle that requires approval before execution.

### Path parameters

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

### Body parameters

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

```json Request example theme={null}
{
  "reason": "approved after review"
}
```

### Errors

| Code  | Error       | Meaning                                     |
| ----- | ----------- | ------------------------------------------- |
| `404` | `not_found` | Renewal cycle does not exist                |
| `409` | `conflict`  | Approval is not required for this cycle     |
| `409` | `conflict`  | Approval was already decided for this cycle |

***

## POST /admin/renewals/:id/reject-changes

Rejects pending subscription changes for a renewal cycle. `reason` is required for rejections.

### Path parameters

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

### Body parameters

<ParamField body="reason" type="string" required>
  Reason for rejection. Required.
</ParamField>

```json Request example theme={null}
{
  "reason": "pending changes are not valid for this cycle"
}
```

### Errors

| Code  | Error          | Meaning                                     |
| ----- | -------------- | ------------------------------------------- |
| `400` | `invalid_data` | Missing or invalid `reason`                 |
| `404` | `not_found`    | Renewal cycle does not exist                |
| `409` | `conflict`     | Approval is not required for this cycle     |
| `409` | `conflict`     | Approval was already decided for this cycle |
