> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.paymentkit.com/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.paymentkit.com/_mcp/server.

# Dunning profiles

# Overview

Dunning profiles let you customize payment recovery behavior beyond the default settings. Each profile defines:

* **Retry schedule**: How many retries and how often
* **Failure handling**: What happens when all retries fail
* **Email notifications**: Which emails to send at each retry step

Profiles are applied at the start of a dunning cycle and remain locked for that cycle. Changes to a profile don't affect in-flight dunning cycles.

***

# System default profiles

Every account comes with four system default profiles optimized for different billing cycles:

| Profile                             | Target Cycle | Max Retries | Retry Interval | Best For              |
| ----------------------------------- | ------------ | ----------- | -------------- | --------------------- |
| **Daily - Quick Recovery**          | Daily        | 3           | 23 hours       | Daily subscriptions   |
| **Short Cycle - Standard Recovery** | 2-6 days     | 4           | 48 hours       | Weekly subscriptions  |
| **Monthly - Standard Recovery**     | 7-30 days    | 8           | 96 hours       | Monthly subscriptions |
| **Long Cycle - Extended Recovery**  | 30+ days     | 10          | 96 hours       | Annual subscriptions  |

System defaults cannot be modified or deleted, but you can clone them to create custom variations.

***

# Custom profiles

Create custom profiles to tailor dunning behavior for specific use cases.

## Create a profile

```bash cURL
curl -X POST https://api.paymentkit.com/v1/dunning/profiles \
  -H "Authorization: Bearer sk_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Premium Customers",
    "description": "Extended recovery for high-value subscriptions",
    "max_retries": 12,
    "retry_interval_hours": 72,
    "termination_action": "leave_active",
    "invoice_status_on_failure": "leave_open",
    "enable_emails": true,
    "email_map": [
      {"retry_step": 0, "template": "payment_failed"},
      {"retry_step": 3, "template": "payment_reminder"},
      {"retry_step": -1, "template": "final_warning"}
    ]
  }'
```

### Profile settings

| Field                       | Type    | Default                | Description                              |
| --------------------------- | ------- | ---------------------- | ---------------------------------------- |
| `name`                      | string  | required               | Display name (max 100 chars)             |
| `description`               | string  | null                   | Optional description (max 500 chars)     |
| `max_retries`               | integer | 8                      | Number of retry attempts (1-15)          |
| `retry_interval_hours`      | integer | 96                     | Hours between retries (1-168)            |
| `termination_action`        | string  | `"cancel"`             | `"cancel"` or `"leave_active"`           |
| `invoice_status_on_failure` | string  | `"mark_uncollectible"` | `"mark_uncollectible"` or `"leave_open"` |
| `enable_emails`             | boolean | true                   | Whether to send dunning emails           |
| `email_map`                 | array   | \[]                    | Email templates for specific retry steps |

### Email map

The `email_map` array controls which email templates are sent at each retry step:

```json
{
  "email_map": [
    {"retry_step": 0, "template": "payment_failed"},     // First retry
    {"retry_step": 2, "template": "payment_reminder"},   // Third retry
    {"retry_step": -1, "template": "final_warning"}      // Final retry (special value)
  ]
}
```

Use `retry_step: -1` to target the final retry, regardless of how many retries are configured.

## Clone a profile

Create a copy of an existing profile (including system defaults):

```bash cURL
curl -X POST https://api.paymentkit.com/v1/dunning/profiles/{profile_id}/clone \
  -H "Authorization: Bearer sk_live_..." \
  -H "Content-Type: application/json" \
  -d '{"name": "My Custom Monthly Profile"}'
```

## Archive a profile

Archiving soft-deletes a profile. Archived profiles don't appear in the default list but remain accessible by ID. In-flight dunning cycles using the profile continue unaffected.

```bash cURL
curl -X DELETE https://api.paymentkit.com/v1/dunning/profiles/{profile_id} \
  -H "Authorization: Bearer sk_live_..."
```

***

# Profile assignments

By default, PaymentKit selects a system default profile based on the subscription's billing cycle. Use **profile assignments** to override this for specific prices or billing cycle categories.

## Resolution order

When a dunning cycle starts, PaymentKit resolves the profile using a 2-tier priority:

1. **Price assignment** — If the subscription's price has a profile assigned, use that profile
2. **Cycle-length assignment** — If no price assignment, check for a cycle\_length category assignment
3. **System default** — Fall back to the system default for the billing cycle type

```mermaid
flowchart TD
    A[Dunning starts] --> B{Price has assignment?}
    B -->|Yes| C[Use price-assigned profile]
    B -->|No| D{Cycle-length has assignment?}
    D -->|Yes| E[Use cycle-length profile]
    D -->|No| F[Use system default]
```

## Assign to a price

Target a specific product price for custom dunning behavior:

```bash cURL
curl -X POST https://api.paymentkit.com/v1/dunning/profiles/{profile_id}/assignments \
  -H "Authorization: Bearer sk_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "resource_type": "price",
    "resource_id": "prc_live_abc123"
  }'
```

## Assign to a cycle length

Target all subscriptions with a specific billing cycle category:

```bash cURL
curl -X POST https://api.paymentkit.com/v1/dunning/profiles/{profile_id}/assignments \
  -H "Authorization: Bearer sk_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "resource_type": "cycle_length",
    "resource_id": "monthly"
  }'
```

Valid cycle lengths: `daily`, `short`, `medium`, `long`

| Cycle Length | Billing Period |
| ------------ | -------------- |
| `daily`      | 1 day          |
| `short`      | 2-6 days       |
| `medium`     | 7-30 days      |
| `long`       | 31+ days       |

## List assignments

```bash cURL
curl https://api.paymentkit.com/v1/dunning/profiles/{profile_id}/assignments \
  -H "Authorization: Bearer sk_live_..."
```

## Delete an assignment

```bash cURL
curl -X DELETE https://api.paymentkit.com/v1/dunning/profiles/{profile_id}/assignments/{assignment_id} \
  -H "Authorization: Bearer sk_live_..."
```

***

# Profile snapshots

When a dunning cycle starts, PaymentKit captures a **snapshot** of the assigned profile's settings. This snapshot is immutable for the duration of the dunning cycle.

Changes to a profile (including archiving) don't affect dunning cycles already in progress. Each cycle uses its frozen snapshot.

This ensures predictable behavior:

* Updating `max_retries` from 8 to 5 won't suddenly cut short an in-progress cycle
* Archiving a profile won't disrupt active dunning cycles using it
* New dunning cycles always get the latest profile settings

The snapshot is stored in the dunning state and includes all profile fields:

```json
{
  "profile_snapshot": {
    "profile_id": "dp_live_abc123",
    "profile_name": "Premium Customers",
    "max_retries": 12,
    "retry_interval_hours": 72,
    "termination_action": "leave_active",
    "invoice_status_on_failure": "leave_open",
    "enable_emails": true,
    "email_map": [...]
  }
}
```

***

# Best practices

System defaults are optimized for common billing cycles. Clone them as a starting point for custom profiles.

Assign profiles to specific prices (e.g., annual enterprise plans) rather than creating many cycle-length assignments.

When testing, create a profile with `retry_interval_hours: 1` to see the full dunning cycle quickly.

Most merchants need 1-3 profiles. Complex assignment hierarchies are harder to reason about.