Skip to main content
The dunning case endpoints expose the Admin dunning queue and let you manage individual payment recovery cases. You can list open or scheduled cases for DataTable views, fetch a full detail payload including attempt history and retry schedule, trigger an immediate retry, close a case manually as recovered or unrecovered, and override the retry schedule for a single case. All routes require an authenticated Medusa Admin user. All mutations are workflow-backed and return the refreshed detail payload.
All routes require an authenticated Medusa Admin user. Unauthenticated requests return 401.

Status values

ValueMeaning
openCase is newly opened
retry_scheduledLast retry failed with a retryable error; next retry is scheduled
retryingRetry is currently in flight
awaiting_manual_resolutionAutomatic retries are exhausted; manual action required
recoveredPayment was collected; case is closed
unrecoveredCase is closed as permanently unrecoverable
Attempt status values: processing, succeeded, failed.

GET /admin/dunning

Returns the paginated dunning queue for Admin DataTable views.

Query parameters

limit
number
Number of results per page.
offset
number
Zero-based result offset for pagination.
q
string
Free-text search across subscription reference, customer name, and product title.
order
string
Field to sort by. Database-backed: updated_at, status, next_retry_at, attempt_count, max_attempts, last_attempt_at. In-memory: last_attempt_status, subscription_reference, customer_name, product_title, order_display_id.
direction
string
Sort direction. One of asc or desc.
status
string | string[]
Filter by case status. Accepts a single value or an array.
subscription_id
string
Filter to cases for a specific subscription.
renewal_cycle_id
string
Filter by the originating renewal cycle ID.
renewal_order_id
string
Filter by the renewal order ID.
payment_provider_id
string
Filter by payment provider ID.
last_payment_error_code
string
Filter by the last payment error code (e.g. card_declined).
attempt_count_min
number
Minimum number of attempts filter.
attempt_count_max
number
Maximum number of attempts filter.
next_retry_from
string
ISO datetime lower bound for next_retry_at.
next_retry_to
string
ISO datetime upper bound for next_retry_at.
last_attempt_status
string | string[]
Filter by the status of the most recent attempt.

Response

dunning_cases
object[]
required
Array of dunning case list items.
count
number
required
Total matching records.
limit
number
required
Page size used.
offset
number
required
Result offset used.
Response example
{
  "dunning_cases": [
    {
      "id": "dc_123",
      "status": "retry_scheduled",
      "subscription": {
        "subscription_id": "sub_123",
        "reference": "SUB-001",
        "status": "past_due",
        "customer_name": "Jane Doe",
        "product_title": "Coffee Subscription",
        "variant_title": "1 kg",
        "sku": "COFFEE-1KG",
        "payment_provider_id": "pp_stripe_stripe"
      },
      "renewal": {
        "renewal_cycle_id": "re_123",
        "status": "failed",
        "scheduled_for": "2026-04-15T10:00:00.000Z",
        "generated_order_id": "order_123"
      },
      "order": {
        "order_id": "order_123",
        "display_id": 1001,
        "status": "pending"
      },
      "attempt_count": 1,
      "max_attempts": 3,
      "next_retry_at": "2026-04-16T10:00:00.000Z",
      "last_attempt_at": "2026-04-15T10:02:00.000Z",
      "last_payment_error_code": "card_declined",
      "updated_at": "2026-04-15T10:02:00.000Z"
    }
  ],
  "count": 1,
  "limit": 20,
  "offset": 0
}

Errors

CodeErrorMeaning
400invalid_dataInvalid query parameter shape, unsupported query value, or unsupported sort field

GET /admin/dunning/:id

Returns the full detail payload for a single dunning case, including the full attempt history and retry schedule.

Path parameters

id
string
required
Dunning case ID.

Response

Returns a dunning_case object with all list fields plus:
dunning_case
object
required
Response example
{
  "dunning_case": {
    "id": "dc_123",
    "status": "retry_scheduled",
    "subscription": {
      "subscription_id": "sub_123",
      "reference": "SUB-001",
      "status": "past_due",
      "customer_name": "Jane Doe",
      "product_title": "Coffee Subscription",
      "variant_title": "1 kg",
      "sku": "COFFEE-1KG",
      "payment_provider_id": "pp_stripe_stripe"
    },
    "renewal": {
      "renewal_cycle_id": "re_123",
      "status": "failed",
      "scheduled_for": "2026-04-15T10:00:00.000Z",
      "generated_order_id": "order_123"
    },
    "order": {
      "order_id": "order_123",
      "display_id": 1001,
      "status": "pending"
    },
    "attempt_count": 1,
    "max_attempts": 3,
    "retry_schedule": {
      "strategy": "fixed_intervals",
      "intervals": [1440, 4320, 10080],
      "timezone": "UTC",
      "source": "default_policy"
    },
    "next_retry_at": "2026-04-16T10:00:00.000Z",
    "last_payment_error_code": "card_declined",
    "last_payment_error_message": "Declined",
    "last_attempt_at": "2026-04-15T10:02:00.000Z",
    "recovered_at": null,
    "closed_at": null,
    "recovery_reason": null,
    "attempts": [
      {
        "id": "da_123",
        "attempt_no": 1,
        "status": "failed",
        "started_at": "2026-04-15T10:00:00.000Z",
        "finished_at": "2026-04-15T10:02:00.000Z",
        "error_code": "card_declined",
        "error_message": "Declined",
        "payment_reference": null,
        "metadata": null
      }
    ],
    "metadata": {
      "origin": "renewal_payment_failure"
    },
    "created_at": "2026-04-15T10:00:00.000Z",
    "updated_at": "2026-04-15T10:02:00.000Z"
  }
}

Errors

CodeErrorMeaning
404not_foundDunning case does not exist

POST /admin/dunning/:id/retry-now

Immediately runs the dunning payment retry workflow for a case, ignoring its scheduled next_retry_at. Retryable failures keep the case in retry_scheduled; permanent failures close it as unrecovered.

Path parameters

id
string
required
Dunning case ID.

Body parameters

reason
string
Optional reason for the manual retry.
Request example
{
  "reason": "manual retry from admin"
}

Errors

CodeErrorMeaning
404not_foundDunning case does not exist
409conflictRetry is already processing, the case is terminal, or the transition is otherwise illegal

POST /admin/dunning/:id/mark-recovered

Closes a dunning case as recovered through a manual operator action. Use this when payment was collected outside the normal retry flow.

Path parameters

id
string
required
Dunning case ID.

Body parameters

reason
string
Optional reason for the manual recovery.
Request example
{
  "reason": "paid outside normal retry flow"
}

Errors

CodeErrorMeaning
404not_foundDunning case does not exist
409conflictCase is already recovered, already unrecovered, or a retry is in flight

POST /admin/dunning/:id/mark-unrecovered

Closes a dunning case as permanently unrecovered through a manual operator action. reason is required for this action.

Path parameters

id
string
required
Dunning case ID.

Body parameters

reason
string
required
Reason for closing the case as unrecovered. Required.
Request example
{
  "reason": "customer refused to update payment method"
}

Errors

CodeErrorMeaning
404not_foundDunning case does not exist
409conflictCase is already recovered, already unrecovered, or a retry is in flight

POST /admin/dunning/:id/retry-schedule

Overrides the retry policy for a single dunning case and updates future automatic retries. max_attempts must equal the number of entries in intervals.

Path parameters

id
string
required
Dunning case ID.

Body parameters

intervals
number[]
required
Array of retry intervals in minutes. Each value must be a positive integer.
max_attempts
number
required
Maximum number of retry attempts. Must be a positive integer equal to the length of intervals.
reason
string
Optional reason for the schedule override.
Request example
{
  "reason": "short manual retry schedule",
  "intervals": [60, 120],
  "max_attempts": 2
}

Errors

CodeErrorMeaning
400invalid_dataInvalid payload shape, non-positive interval values, or max_attempts does not match the number of intervals
404not_foundDunning case does not exist
409conflictCase is terminal, a retry is in flight, or the override would create an illegal transition