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

# Manage Recurring Plans

> Pause, resume, cancel, or manually retry a recurring plan

Once a plan is live, four lifecycle endpoints let you control it: **pause** billing temporarily, **resume** it, **cancel** it permanently, or **manually retry** a plan whose automatic retries are exhausted. All four operate on the `planId` returned when the plan was [created](/guides/payments/payapi/recurring/create-plan).

## Quick Start

Pause a plan for two months, with automatic resume:

```bash theme={null}
curl -X POST https://payapi.v2.ozurapay.com/api/v1/recurring/plans/RP2606020000167A143/pause \
  -H "x-api-key: your_merchant_api_key" \
  -H "Content-Type: application/json" \
  -d '{
    "pausedUntil": "2026-11-01T00:00:00.000Z",
    "pauseReason": "Customer requested two months off"
  }'
```

## The Basics

| Action | Method | URL |
| :- | :- | :- |
| **Pause** | `POST` | `https://payapi.v2.ozurapay.com/api/v1/recurring/plans/{planId}/pause` |
| **Resume** | `POST` | `https://payapi.v2.ozurapay.com/api/v1/recurring/plans/{planId}/resume` |
| **Cancel** | `DELETE` | `https://payapi.v2.ozurapay.com/api/v1/recurring/plans/{planId}` |
| **Retry** | `POST` | `https://payapi.v2.ozurapay.com/api/v1/recurring/plans/{planId}/retry` |

<Note>
  **Never call these from the browser.** Your API key must stay on the server.
  All four endpoints require only the `x-api-key` header, and only the merchant
  that owns the plan can manage it.
</Note>

## Which Actions Are Allowed When

Whether an action is allowed depends on the plan's current [status](/guides/payments/payapi/recurring/query-plans#recurringplanstatus):

| Plan status | Pause | Resume | Cancel | Retry |
| :- | :- | :- | :- | :- |
| `active` | ✅ | ✅ (no-op) | ✅ | ❌ |
| `paused` | ✅ (re-pause) | ✅ | ✅ | ❌ |
| `failed` | ❌ | ❌ | ✅ | ✅ |
| `completed` | ❌ | ❌ | ❌ | ❌ |
| `cancelled` | ❌ | ❌ | ✅ (no-op) | ❌ |

Disallowed combinations return a `400` with an error message pointing at the right endpoint.

<Warning>
  Plans paused **by the system** for payment reconciliation (rather than by you)
  cannot be modified through any of these endpoints and return a `409`. Contact
  support to resolve these — they indicate a charge whose outcome is being
  verified with the card processor. System-paused plans show
  `pauseReason: "stalled_pending_manual_reconciliation"`.

  The system only applies this pause to `active` or `paused` plans. If you had
  already paused the plan yourself, it is converted to a system pause and any
  scheduled automatic resume (`pausedUntil`) is cancelled. Cancelled, completed,
  and failed plans keep their status; the charge is still flagged for
  reconciliation.
</Warning>

***

## Pause a Plan

Halts all future billing while keeping the plan's schedule anchor intact. Pausing removes the plan's next scheduled charge; nothing bills until the plan is resumed.

### Request Body (all fields optional)

Send `{}` (with `Content-Type: application/json`) for an indefinite pause.

| Field | Type | Description | Example |
| :- | :- | :- | :- |
| `pausedUntil` | string | ISO 8601 timestamp. If provided, the plan **automatically resumes** at this time. Must be in the future. | `"2026-11-01T00:00:00.000Z"` |
| `pauseReason` | string | A note stored on the plan and included in the webhook event (max 100 characters). Defaults to `"merchant_requested"`. The value `"stalled_pending_manual_reconciliation"` is reserved for system pauses and is rejected with a `400`. | `"Customer requested a break"` |

### Behavior Notes

* **Pausing an already-paused plan is a re-pause**: `pausedUntil` and `pauseReason` are replaced with the values you send, and a new `paused` webhook event fires. Omitting `pausedUntil` cancels any scheduled automatic resume; omitting `pauseReason` resets it to `"merchant_requested"`. Treat webhook events as at-least-once.
* **If the automatic resume can't be scheduled**, the pause still succeeds (`200`), but the plan is paused indefinitely. `pausedUntil` is `null` in both the response and the webhook event, and the response `message` says so. Call resume when you're ready, or pause again with `pausedUntil`.
* **An in-flight charge cannot be stopped.** If a charge is already being processed at the card processor when you pause, that charge may still complete. The pause takes effect for all subsequent billing either way.
* `nextChargeAt` becomes `null` while paused; `nextCycleAt` keeps the plan's billing anchor so resume can compute the right date.

### Response

`200` with the full plan object (same shape as [Query Recurring Plans](/guides/payments/payapi/recurring/query-plans#example)). Key fields after a pause:

```json theme={null}
{
  "success": true,
  "data": {
    "planId": "RP2606020000167A143",
    "status": "paused",
    "pausedAt": "2026-09-01T15:30:00.000Z",
    "pausedUntil": "2026-11-01T00:00:00.000Z",
    "pauseReason": "Customer requested two months off",
    "nextChargeAt": null,
    "nextCycleAt": "2026-10-02T12:00:00.000Z"
  },
  "message": "Recurring plan paused successfully"
}
```

***

## Resume a Plan

Reactivates a paused plan. No request body.

```bash theme={null}
curl -X POST https://payapi.v2.ozurapay.com/api/v1/recurring/plans/RP2606020000167A143/resume \
  -H "x-api-key: your_merchant_api_key"
```

### Billing After a Resume

**Cycles missed during a pause are skipped — the customer is never retroactively charged.** Billing resumes on the next future billing date, preserving the plan's original anchor day.

Example: a monthly plan billing on the **31st** is paused in January and resumed on August 15. The missed February–July cycles are skipped, and the next charge lands on **August 31** — the original anchor day, not the resume date. (In months without that day, billing clamps to the last day of the month, exactly as it always has.)

* If the pause was short and the original billing date hasn't passed yet, the plan charges on that original date — a brief pause-and-resume changes nothing.
* If the plan's `endDate` passed during the pause, the plan is **completed** instead of resumed (no charge fires), and a `completed` webhook event is sent.
* If the plan was paused while a failed charge was waiting on an automatic retry, the first charge after resuming counts as that cycle's next retry attempt. The cycle does not get a fresh set of `maxAttempts`. It bills on the next future billing date, like any other resume.
* Resuming an already-active plan is a harmless no-op (`200`).
* If the next charge can't be scheduled, resume returns `500` and the plan **stays paused** with its previous settings, including any `pausedUntil`. It's safe to call resume again.
* If another change lands on the plan at the same moment (an automatic resume, a concurrent request, or a pause or cancel), resume returns `409`. Query the plan for its current state.

### Automatic Resume (`pausedUntil`)

If the plan was paused with `pausedUntil`, it resumes automatically at that time — same skip-missed-cycles rules, and a `recurringPayment.plan` event with `status: "active"` fires. An explicit resume before `pausedUntil` simply cancels the scheduled automatic one.

If the automatic resume hits a temporary error, it is retried automatically (up to 3 attempts, with backoff). If it still can't complete, the plan stays paused. Call resume to restart billing.

***

## Cancel a Plan

Permanently stops the plan. **Cancellation is terminal and cannot be undone.**

```bash theme={null}
curl -X DELETE https://payapi.v2.ozurapay.com/api/v1/recurring/plans/RP2606020000167A143 \
  -H "x-api-key: your_merchant_api_key"
```

* Allowed from `active`, `paused`, and `failed` (cancelling a failed plan is the normal way to close it out if you don't intend to retry).
* A `completed` plan cannot be cancelled — every contracted cycle was already billed.
* Cancelling an already-cancelled plan is a harmless no-op (`200`).

<Warning>
  Cancelling stops **future** billing only. It does not void or refund any
  charge that has already been processed or is currently in flight — use the
  [refund endpoint](/guides/payments/payapi/transactions#process-a-card-refund)
  for that.
</Warning>

Response: `200` with the plan object showing `"status": "cancelled"` and a populated `cancelledAt` timestamp.

***

## Manually Retry a Failed Plan

When a plan exhausts its automatic retries (`maxAttempts`), it moves to `failed` and stops billing. After resolving the underlying issue with your customer (e.g. they've updated their card balance), use this endpoint to retry the failed cycle immediately.

```bash theme={null}
curl -X POST https://payapi.v2.ozurapay.com/api/v1/recurring/plans/RP2606020000167A143/retry \
  -H "x-api-key: your_merchant_api_key"
```

### How It Works

1. The plan is reactivated and the failed cycle's charge is queued **immediately**.
2. The endpoint returns `202` right away — the charge itself runs asynchronously.
3. The charge outcome arrives via the `recurringPayment.charge` webhook event.
4. **On success**, the plan resumes its normal schedule on the original billing anchor. Cycles missed while the plan was failed are skipped, never retroactively charged.
5. **On failure**, the plan returns to `failed`. You can retry again or cancel.

Each call retries the cycle **once** — there is no automatic retry ladder behind a manual retry.

If the plan is paused or cancelled at the same moment the retry is being set up, the retry returns `409` and no `manual_retry_initiated` event fires. As with any pause or cancel, a charge that had already started may still complete, so query the plan to confirm its state. If the retry can't be scheduled at all, it returns `500` and the plan stays `failed`, so it's safe to try again.

### Eligibility

* Only plans with `status: "failed"`.
* Not available if the plan's `endDate` has passed (`400`) — a lapsed plan can only be cancelled.

### Response

`202 Accepted` with the plan object (now `"status": "active"`):

```json theme={null}
{
  "success": true,
  "data": {
    "planId": "RP2606020000167A143",
    "status": "active",
    "nextChargeAt": "2026-09-01T15:30:00.000Z"
  },
  "message": "Manual retry initiated. The charge result will be delivered via webhook (recurringPayment.charge)."
}
```

<Note>
  A `202` means the retry was **queued**, not that the charge succeeded. Always
  confirm the outcome via the `recurringPayment.charge` webhook or by [querying
  the plan](/guides/payments/payapi/recurring/query-plans). The charge runs
  immediately, so the charge result event can arrive before the `status:
      "manual_retry_initiated"` event.
</Note>

***

## Errors

All four endpoints share these responses:

| Code | Meaning |
| :- | :- |
| `400` | The action isn't allowed from the plan's current status, or a field failed validation (e.g. `pausedUntil` in the past). The error message says what to do instead. |
| `401` | Missing or invalid `x-api-key`. |
| `403` | Your API key lacks the required permission. |
| `404` | No plan with that `planId` exists under your merchant account. |
| `409` | Either the plan is system-paused for payment reconciliation (contact support), or another change to the plan landed while your request was processing — for example an automatic resume, a concurrent request, or a pause or cancel during a resume or retry. Query the plan for its current state before trying again. |
| `500` | The change couldn't be completed. For resume and retry, the plan is left as it was (still `paused` or `failed`) and it's safe to try again. If the error message asks you to contact support, the plan's state needs manual review. |

## Rate Limits

| Endpoint | Limit |
| :- | :- |
| Pause / Resume / Cancel | 50 requests / minute |
| Retry | 20 requests / minute |

## Webhook Events

Every lifecycle change fires a `recurringPayment.plan` event with the new `status`:

* `paused`, `active` (explicit or automatic resume), `cancelled`, and `completed`.
* `failed`, but only when the plan actually moves to `failed`. If you pause or cancel a plan while its final retry is in flight, the charge failure is still reported, but no `failed` plan event fires, because the plan keeps the status you set.

Charges fire `recurringPayment.charge` events: one per successful or failed charge attempt, plus one with `status: "manual_retry_initiated"` when you call retry. If a charge is flagged for payment reconciliation, a `recurringPayment.charge` event fires with `status: "stalled_pending_manual_reconciliation"`, and its `planPaused` field shows whether the plan was system-paused.

Events are delivered **at least once** and **may arrive out of order**. Handle duplicates idempotently, and treat the plan's current state (from [querying the plan](/guides/payments/payapi/recurring/query-plans)) as the source of truth.

## What's Next?

Track your plans' states with [Query Recurring Plans](/guides/payments/payapi/recurring/query-plans), or subscribe to the [webhook events](#webhook-events) above to react to lifecycle changes in real time.


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