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

# Implement Self-Service Upgrades

> Integrate product group-based upgrades and downgrades into your application

This guide shows you how to implement self-service upgrades and downgrades using product groups in your application.

## Overview

The upgrade/downgrade flow consists of three main steps:

1. **Check eligibility** - Get available change options for a subscription item
2. **Display options** - Show the customer available upgrades/downgrades
3. **Apply change** - Execute the selected package change

***

## Prerequisites

Before implementing upgrades:

1. Create a **Product Group** with a tier for each package level
2. Assign the product group to the subscription item via `PUT /subscription-items/{id}/product-group`

***

## Step 1: Get Change Options

Fetch available upgrade/downgrade options for a subscription item:

```bash theme={null}
curl -X GET "https://coreapi.io/subscription-items/{itemId}/change-options" \
  -H "Authorization: Bearer YOUR_API_KEY"
```

### Response

```json theme={null}
{
  "current": {
    "id": "tier-starter-uuid",
    "name": "Starter Plan",
    "pricePlan": {
      "id": "price-starter-uuid",
      "name": "Starter Monthly",
      "price": 2900,
      "billingInterval": "monthly"
    },
    "product": {
      "id": "product-starter-uuid",
      "name": "Starter"
    }
  },
  "options": [
    {
      "id": "tier-pro-uuid",
      "label": "Pro",
      "product": {
        "id": "product-pro-uuid",
        "name": "Pro Plan"
      },
      "pricePlans": [
        {
          "id": "price-pro-monthly-uuid",
          "name": "Pro Monthly",
          "price": 4900,
          "billingInterval": "monthly"
        }
      ],
      "changeTiming": "immediately",
      "creditType": "pro_rata",
      "isUpgrade": true,
      "isDowngrade": false,
      "quantitySetting": null
    }
  ],
  "hasPendingChange": false
}
```

### Response Fields

| Field                    | Description                             |
| ------------------------ | --------------------------------------- |
| `current`                | Currently active tier and price plan    |
| `options`                | Available upgrade/downgrade options     |
| `options[].isUpgrade`    | `true` if this is an upgrade            |
| `options[].isDowngrade`  | `true` if this is a downgrade           |
| `options[].changeTiming` | When the change takes effect            |
| `options[].creditType`   | How unused time is credited             |
| `hasPendingChange`       | `true` if a change is already scheduled |

<Warning>
  If `hasPendingChange` is `true`, no further changes can be made until the pending change is applied.
</Warning>

***

## Step 2: Display Options to Customer

Use the response to build a selection UI:

```javascript theme={null}
async function getUpgradeOptions(subscriptionItemId) {
  const response = await fetch(
    `/subscription-items/${subscriptionItemId}/change-options`,
    { headers: { Authorization: `Bearer ${token}` } }
  );

  const data = await response.json();

  // Check if changes are possible
  if (data.hasPendingChange) {
    return { error: 'A change is already pending' };
  }

  if (data.options.length === 0) {
    return { error: 'No upgrade options available' };
  }

  // Separate upgrades and downgrades
  const upgrades = data.options.filter(o => o.isUpgrade);
  const downgrades = data.options.filter(o => o.isDowngrade);

  return { current: data.current, upgrades, downgrades };
}
```

***

## Step 3: Apply the Change

When the customer selects an option, apply the change:

```bash theme={null}
curl -X POST "https://coreapi.io/product-group-memberships/{tierId}/apply" \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -d '{
    "subscriptionItem": "subscription-item-uuid",
    "quantity": 1
  }'
```

### Request Parameters

| Parameter           | Type    | Required | Description                                                                                                     |
| ------------------- | ------- | -------- | --------------------------------------------------------------------------------------------------------------- |
| `subscriptionItem`  | string  | Yes      | UUID of the subscription item to change                                                                         |
| `selectedPricePlan` | string  | No       | UUID of the price plan. Only required if the tier has multiple price plans. In most cases, this can be omitted. |
| `quantity`          | integer | No       | New quantity. Only applied if the tier allows quantity changes.                                                 |
| `sendInvoiceEmail`  | boolean | No       | Send credit note via email (default: `true`)                                                                    |

### Response

```json theme={null}
{
  "subscriptionItem": {
    "id": "item-uuid",
    "product": { "id": "...", "name": "Pro Plan" },
    "pricePlan": { "id": "...", "name": "Pro Monthly" },
    "status": "active"
  },
  "creditNote": {
    "id": "credit-note-uuid",
    "number": "CN-2026-0001",
    "amount": 1450
  }
}
```

***

## Change Timing Behavior

### Immediately

The change takes effect right away:

1. Current subscription item is ended
2. Credit note is created (based on `creditType`)
3. New subscription item is created and starts immediately
4. New invoice is generated

### End of Period

The change is scheduled for the next billing cycle:

1. Current subscription item continues until period end
2. At period end, item switches to new product/plan
3. No credit note is created
4. Next invoice uses new pricing

***

## Credit Types

When `changeTiming` is `immediately`, a credit is issued based on `creditType`:

| Type            | Formula                                   | Example                |
| --------------- | ----------------------------------------- | ---------------------- |
| `pro_rata`      | Price × (Days remaining / Days in period) | €29 × (15/30) = €14.50 |
| `full`          | Full period price                         | €29.00                 |
| `last_invoiced` | Amount from last invoice                  | Varies                 |
| `none`          | No credit                                 | €0.00                  |

***

## Complete Example

```javascript theme={null}
async function upgradeSubscription(subscriptionItemId, targetTierId) {
  // 1. Get current options to validate
  const options = await fetch(
    `/subscription-items/${subscriptionItemId}/change-options`,
    { headers: { Authorization: `Bearer ${token}` } }
  ).then(r => r.json());

  if (options.hasPendingChange) {
    throw new Error('Cannot change: pending change exists');
  }

  // 2. Find the selected option
  const selectedOption = options.options.find(o => o.id === targetTierId);
  if (!selectedOption) {
    throw new Error('Invalid tier selected');
  }

  // 3. Apply the change
  const result = await fetch(
    `/product-group-memberships/${targetTierId}/apply`,
    {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        Authorization: `Bearer ${token}`
      },
      body: JSON.stringify({
        subscriptionItem: subscriptionItemId,
        quantity: 1
      })
    }
  ).then(r => r.json());

  return {
    newItem: result.subscriptionItem,
    creditNote: result.creditNote
  };
}
```

***

## Error Handling

| HTTP Status | Cause                       | Solution                            |
| ----------- | --------------------------- | ----------------------------------- |
| 400         | Invalid request             | Check required fields               |
| 404         | Subscription item not found | Verify item UUID                    |
| 422         | Pending change exists       | Wait for pending change to complete |
| 422         | Not eligible for change     | Check tier eligibility settings     |

***

## Webhooks

Listen for these events to track changes:

| Event                  | Description                           |
| ---------------------- | ------------------------------------- |
| `subscription.updated` | Subscription items changed            |
| `invoice.created`      | New invoice for upgraded subscription |
| `credit_note.created`  | Credit note for prorated amount       |

***

## Related Documentation

<CardGroup cols={2}>
  <Card title="Product Groups" icon="layer-group" href="/guide/catalogue/product-groups">
    Set up product groups
  </Card>

  <Card title="Subscription Transitions" icon="arrow-right-arrow-left" href="/guide/subscriptions/transitions">
    Manual transitions
  </Card>

  <Card title="Webhooks" icon="webhook" href="/guide/webhooks/introduction">
    Listen for events
  </Card>
</CardGroup>
