---
title: Fetching Subscription Product Relationships
slug: fetching-subscription-product-relationships
docTags: 
createdAt: 2026-02-13T04:24:48.842Z
---

This guide explains how to retrieve available upgrade, downgrade, and swap (UDS) paths for customer subscriptions using the Nue Self-Service API. Understanding available product relationships enables you to build dynamic self-service experiences where customers can modify their subscriptions.

## Prerequisites

Before you begin, ensure you have:

- A valid Nue API key
- Subscription names for the subscriptions you want to query
- Basic understanding of REST APIs and JSON
- Familiarity with product relationship concepts (upgrade, downgrade, swap)

## Authentication

All product relationship operations require authentication using your Nue API key in the `nue-api-key` header:

```javascript
const myHeaders = new Headers();
myHeaders.append("nue-api-key", "YOUR_API_KEY_HERE");
myHeaders.append("Content-Type", "application/json");
```

## Understanding Product Relationships

Product relationships in Nue define how subscribed products can be changed. There are three types of relationships:

| Relationship Type | Description                        | Use Case                         |
| ----------------- | ---------------------------------- | -------------------------------- |
| **Upgrade**       | Move to a higher-tier product      | Basic Plan to Professional Plan  |
| **Downgrade**     | Move to a lower-tier product       | Enterprise Plan to Standard Plan |
| **Swap**          | Exchange for an equivalent product | US Region to EU Region           |

## REST Endpoint

```javascript
GET https://api.nue.io/subscriptions/product-relationships?subscriptionNames={names}
```

### Query Parameters

| Parameter           | Type           | Required | Description                                       |
| ------------------- | -------------- | -------- | ------------------------------------------------- |
| `subscriptionNames` | Array\[String] | Yes      | JSON-encoded array of subscription names to query |

## Basic Usage

### Fetch Product Relationships for a Single Subscription

```javascript
const myHeaders = new Headers();
myHeaders.append("nue-api-key", "YOUR_API_KEY_HERE");
myHeaders.append("Content-Type", "application/json");

// Fetch product relationships for a single subscription
const subscriptionNames = ["SUB-000115"];
const encodedNames = encodeURIComponent(JSON.stringify(subscriptionNames));

fetch(`https://api.nue.io/subscriptions/product-relationships?subscriptionNames=${encodedNames}`, {
  method: 'GET',
  headers: myHeaders
})
.then(response => response.json())
.then(result => {
  if (result.status === 'SUCCESS' && result.data) {
    console.log('Product relationships retrieved successfully');

    // Data is keyed by subscription name
    Object.entries(result.data).forEach(([subName, subscription]) => {
      console.log(`\nSubscription: ${subscription.subscriptionName}`);
      console.log(`   Current Product: ${subscription.productSku}`);
      console.log(`   Current UOM: ${subscription.subscriptionUomName}`);

      const options = subscription.options;

      // Display upgrade options
      if (options.upgrade && options.upgrade.length > 0) {
        console.log('\n   Upgrade Options:');
        options.upgrade.forEach(option => {
          option.toProducts.forEach(product => {
            console.log(`     - ${product.name} (${product.sku})`);
            console.log(`       Same UOM Only: ${option.sameUomOnly}`);
            console.log(`       Available from: ${option.startDate}`);
          });
        });
      }

      // Display downgrade options
      if (options.downgrade && options.downgrade.length > 0) {
        console.log('\n   Downgrade Options:');
        options.downgrade.forEach(option => {
          option.toProducts.forEach(product => {
            console.log(`     - ${product.name} (${product.sku})`);
          });
        });
      }

      // Display swap options
      if (options.swap && options.swap.length > 0) {
        console.log('\n   Swap Options:');
        options.swap.forEach(option => {
          option.toProducts.forEach(product => {
            console.log(`     - ${product.name} (${product.sku})`);
            console.log(`       Same Price Swap: ${option.samePriceSwap}`);
          });
        });
      }
    });
  }
})
.catch(error => console.log('Error:', error));
```

### Fetch Product Relationships for Multiple Subscriptions

Query relationships for multiple subscriptions in a single API call:

```javascript
// Fetch product relationships for multiple subscriptions
const subscriptionNames = ["SUB-000115", "SUB-000116"];
const encodedNames = encodeURIComponent(JSON.stringify(subscriptionNames));

fetch(`https://api.nue.io/subscriptions/product-relationships?subscriptionNames=${encodedNames}`, {
  method: 'GET',
  headers: myHeaders
})
.then(response => response.json())
.then(result => {
  if (result.status === 'SUCCESS' && result.data) {
    const subscriptions = Object.values(result.data);
    console.log(`Retrieved relationships for ${subscriptions.length} subscriptions`);

    // Analyze available options across all subscriptions
    const withUpgrades = subscriptions.filter(s => s.options.upgrade?.length > 0);
    const withDowngrades = subscriptions.filter(s => s.options.downgrade?.length > 0);
    const withSwaps = subscriptions.filter(s => s.options.swap?.length > 0);

    console.log(`\nSummary:`);
    console.log(`   Subscriptions with upgrades: ${withUpgrades.length}`);
    console.log(`   Subscriptions with downgrades: ${withDowngrades.length}`);
    console.log(`   Subscriptions with swaps: ${withSwaps.length}`);

    // Display details for each subscription
    subscriptions.forEach(subscription => {
      const options = subscription.options;
      const upgradeCount = options.upgrade?.length || 0;
      const downgradeCount = options.downgrade?.length || 0;
      const swapCount = options.swap?.length || 0;

      console.log(`\n${subscription.subscriptionName}:`);
      console.log(`   Product: ${subscription.productSku}`);
      console.log(`   Available: ${upgradeCount} upgrade(s), ${downgradeCount} downgrade(s), ${swapCount} swap(s)`);
    });
  }
})
.catch(error => console.log('Error:', error));
```

## Building Self-Service Experiences

### Extracting Product Options for UI Display

Build a selection interface for customers to choose their desired product change:

```javascript
function extractChangeOptions(subscriptionData) {
  const options = {
    subscriptionName: subscriptionData.subscriptionName,
    subscriptionId: subscriptionData.subscriptionId,
    currentProduct: {
      sku: subscriptionData.productSku,
      uom: subscriptionData.subscriptionUomName
    },
    availableChanges: []
  };

  const relationshipOptions = subscriptionData.options;

  // Process upgrade options
  if (relationshipOptions.upgrade) {
    relationshipOptions.upgrade.forEach(upgrade => {
      upgrade.toProducts.forEach(product => {
        options.availableChanges.push({
          type: 'upgrade',
          relationshipId: upgrade.id,
          targetProduct: {
            id: product.id,
            name: product.name,
            sku: product.sku,
            priceBookEntries: product.priceBookEntries
          },
          sameUomOnly: upgrade.sameUomOnly,
          startDate: upgrade.startDate
        });
      });
    });
  }

  // Process downgrade options
  if (relationshipOptions.downgrade) {
    relationshipOptions.downgrade.forEach(downgrade => {
      downgrade.toProducts.forEach(product => {
        options.availableChanges.push({
          type: 'downgrade',
          relationshipId: downgrade.id,
          targetProduct: {
            id: product.id,
            name: product.name,
            sku: product.sku,
            priceBookEntries: product.priceBookEntries
          },
          sameUomOnly: downgrade.sameUomOnly,
          startDate: downgrade.startDate
        });
      });
    });
  }

  // Process swap options
  if (relationshipOptions.swap) {
    relationshipOptions.swap.forEach(swap => {
      swap.toProducts.forEach(product => {
        options.availableChanges.push({
          type: 'swap',
          relationshipId: swap.id,
          targetProduct: {
            id: product.id,
            name: product.name,
            sku: product.sku,
            priceBookEntries: product.priceBookEntries
          },
          sameUomOnly: swap.sameUomOnly,
          samePriceSwap: swap.samePriceSwap,
          startDate: swap.startDate
        });
      });
    });
  }

  return options;
}

// Usage
fetch(`https://api.nue.io/subscriptions/product-relationships?subscriptionNames=${encodedNames}`, {
  method: 'GET',
  headers: myHeaders
})
.then(response => response.json())
.then(result => {
  if (result.status === 'SUCCESS' && result.data) {
    Object.values(result.data).forEach(subscription => {
      const changeOptions = extractChangeOptions(subscription);

      console.log(`\n${changeOptions.subscriptionName}`);
      console.log(`Current: ${changeOptions.currentProduct.sku} (${changeOptions.currentProduct.uom})`);

      if (changeOptions.availableChanges.length === 0) {
        console.log('   No changes available');
        return;
      }

      changeOptions.availableChanges.forEach((change, index) => {
        const typeIcon = change.type === 'upgrade' ? '(up)' :
                         change.type === 'downgrade' ? '(down)' : '(swap)';
        console.log(`   ${index + 1}. ${typeIcon} ${change.targetProduct.name}`);
        console.log(`      SKU: ${change.targetProduct.sku}`);

        // Show pricing options
        if (change.targetProduct.priceBookEntries) {
          const prices = change.targetProduct.priceBookEntries
            .map(p => `${p.listPrice} ${p.uom.name}`)
            .join(', ');
          console.log(`      Pricing: ${prices}`);
        }
      });
    });
  }
});
```

### Displaying Pricing Options for Target Products

The response includes full pricing details for target products, allowing you to show customers what their new subscription will cost:

```javascript
function displayPricingOptions(priceBookEntries) {
  console.log('   Available Pricing:');

  priceBookEntries.forEach(entry => {
    const uom = entry.uom;
    const recommended = entry.recommended ? ' (Recommended)' : '';

    console.log(`     - $${entry.listPrice} per ${uom.name}${recommended}`);
    console.log(`       Billing: ${entry.billingTiming}`);
    console.log(`       Price Book Entry ID: ${entry.id}`);
  });
}

// Usage within the change options flow
changeOptions.availableChanges.forEach(change => {
  console.log(`\nUpgrade to: ${change.targetProduct.name}`);
  displayPricingOptions(change.targetProduct.priceBookEntries);
});
```

## Response Structure

### Success Response (200 OK)

```json
{
  "status": "SUCCESS",
  "data": {
    "SUB-000115": {
      "currencyIsoCode": "USD",
      "subscriptionName": "SUB-000115",
      "subscriptionId": "a0udi00000484tlAAA",
      "productSku": "ORDER FORMS UDS",
      "subscriptionUomName": "User/Year",
      "options": {
        "upgrade": [
          {
            "id": "a0odi000000y2QVAAY",
            "relationshipType": "Upgrade",
            "sameUomOnly": false,
            "startDate": "2025-01-16",
            "productRelationshipPriceTags": [
              {
                "id": "a0xdi000001ABC",
                "name": "Upgrade Discount",
                "priceTagType": "Discount",
                "publishStatus": "Published"
              }
            ],
            "fromProduct": {
              "id": "01tdi000008IMqIAAW",
              "name": "Order Forms uds",
              "sku": "ORDER FORMS UDS",
              "priceBookEntries": [
                {
                  "id": "01udi00000341kWAAQ",
                  "listPrice": 100,
                  "billingTiming": "In Advance",
                  "uom": {
                    "id": "a0wdi0000020DSQAA2",
                    "name": "User/Year",
                    "quantityDimension": "User",
                    "termDimension": "Year"
                  }
                }
              ],
              "priceModel": "Recurring",
              "productCategory": "RecurringServices"
            },
            "toProducts": [
              {
                "id": "01tdi000008IMqXAAW",
                "name": "Nue Recurring Test",
                "sku": "NUE RECURRING TEST",
                "priceBookEntries": [
                  {
                    "id": "01udi00000341kXAAQ",
                    "listPrice": 120,
                    "billingTiming": "In Advance",
                    "uom": {
                      "id": "a0wdi0000020DSQAA2",
                      "name": "User/Year",
                      "quantityDimension": "User",
                      "termDimension": "Year"
                    }
                  }
                ],
                "priceModel": "Recurring",
                "priceTags": [...]
              }
            ]
          }
        ],
        "swap": [
          {
            "id": "a0odi000000y2QRAAY",
            "relationshipType": "Swap",
            "sameUomOnly": false,
            "samePriceSwap": false,
            "startDate": "2025-07-30",
            "fromProduct": {...},
            "toProducts": [...]
          }
        ]
      }
    }
  }
}
```

### Partial Success Response (200 OK)

When some subscriptions are found but others fail, the API returns a partial success with warnings:

```json
{
  "status": "PARTIAL_SUCCESS",
  "data": {
    "SUB-000115": {
      "subscriptionName": "SUB-000115",
      "subscriptionId": "a0udi00000484tlAAA",
      "productSku": "ORDER FORMS UDS",
      "subscriptionUomName": "User/Year",
      "options": {...}
    }
  },
  "warnings": [
    {
      "code": "SUBSCRIPTION_NOT_FOUND",
      "message": "Subscription not found: SUB-INVALID"
    }
  ]
}
```

### Response Fields

**Top-Level Response:**

| Field      | Type   | Description                                                      |
| ---------- | ------ | ---------------------------------------------------------------- |
| `status`   | String | Response status: `SUCCESS`, `PARTIAL_SUCCESS`, or error          |
| `data`     | Object | Object keyed by subscription name                                |
| `warnings` | Array  | (Optional) Array of warning objects when some subscriptions fail |

**Warning Object:**

| Field     | Type   | Description                                                                     |
| --------- | ------ | ------------------------------------------------------------------------------- |
| `code`    | String | Warning code (e.g., `SUBSCRIPTION_NOT_FOUND`, `SUBSCRIPTION_EVALUATION_FAILED`) |
| `message` | String | Human-readable warning message                                                  |

**Subscription Object:**

| Field                 | Type   | Description                                             |
| --------------------- | ------ | ------------------------------------------------------- |
| `currencyIsoCode`     | String | (Optional) Currency ISO code for multi-currency tenants |
| `subscriptionName`    | String | Name of the subscription                                |
| `subscriptionId`      | String | Unique subscription identifier                          |
| `productSku`          | String | SKU of the currently subscribed product                 |
| `subscriptionUomName` | String | Current unit of measure name                            |
| `options`             | Object | Contains `upgrade`, `downgrade`, and `swap` arrays      |

**Relationship Option Object:**

| Field                          | Type    | Description                                                                                     |
| ------------------------------ | ------- | ----------------------------------------------------------------------------------------------- |
| `id`                           | String  | Unique relationship identifier                                                                  |
| `relationshipType`             | String  | `Upgrade`, `Downgrade`, or `Swap`                                                               |
| `sameUomOnly`                  | Boolean | Whether only same UOM products are valid                                                        |
| `samePriceSwap`                | Boolean | (Swap only) Whether price is preserved                                                          |
| `startDate`                    | String  | Date when relationship becomes effective                                                        |
| `productRelationshipPriceTags` | Array   | (Optional) Price tags/discounts associated with this relationship                               |
| `fromProduct`                  | Object  | Product object for current product (contains only the subscription's specific price book entry) |
| `toProducts`                   | Array   | Array of target product objects (contains all available price book entries)                     |

**Product Object:**

| Field                        | Type    | Description                                |
| ---------------------------- | ------- | ------------------------------------------ |
| `id`                         | String  | Product ID                                 |
| `name`                       | String  | Product name                               |
| `sku`                        | String  | Product SKU                                |
| `priceBookEntries`           | Array   | Available pricing options (see note below) |
| `priceModel`                 | String  | `Recurring`, `OneTime`, etc.               |
| `priceTags`                  | Array   | Applied price/discount tags                |
| `productCategory`            | String  | Product category                           |
| `autoRenew`                  | Boolean | Default auto-renewal setting               |
| `creditType`                 | String  | `Cash` or `Credit`                         |
| `creditConversion`           | Object  | (Optional) Credit conversion settings      |
| `defaultRenewTerm`           | Number  | Default renewal term                       |
| `evergreen`                  | Boolean | Whether the product is evergreen           |
| `freeTrialType`              | String  | Free trial type                            |
| `freeTrialUnit`              | Number  | Free trial duration                        |
| `recordType`                 | String  | Record type                                |
| `showIncludedProductOptions` | Boolean | Whether to show included product options   |
| `taxCode`                    | String  | Tax code                                   |
| `taxMode`                    | String  | Tax mode                                   |

::::BlockQuote
**Note:** The `priceBookEntries` array differs between `fromProduct` and `toProducts`:

:::Paragraph{listStyleType="disc" indent="1"}
**fromProduct**: Contains only the subscription's specific price book entry (the one the customer is currently subscribed to)
:::

:::Paragraph{listStyleType="disc" indent="1"}
**toProducts**: Contains all available active price book entries for the target product
:::
::::

**Price Tag Object:**

| Field           | Type    | Description                          |
| --------------- | ------- | ------------------------------------ |
| `id`            | String  | Price tag ID                         |
| `name`          | String  | Price tag name                       |
| `code`          | String  | Price tag code                       |
| `priceTagType`  | String  | Type of price tag (e.g., `Discount`) |
| `priceType`     | String  | Price type                           |
| `publishStatus` | String  | Publish status                       |
| `active`        | Boolean | Whether the tag is active            |
| `recordType`    | String  | Record type                          |

**Price Book Entry Object:**

| Field                   | Type    | Description                            |
| ----------------------- | ------- | -------------------------------------- |
| `id`                    | String  | Price book entry ID                    |
| `listPrice`             | Number  | List price amount                      |
| `billingTiming`         | String  | `In Advance` or `In Arrears`           |
| `recommended`           | Boolean | Whether this is the recommended option |
| `uom`                   | Object  | Unit of measure details                |
| `uom.name`              | String  | UOM display name (e.g., `User/Month`)  |
| `uom.quantityDimension` | String  | Quantity dimension (e.g., `User`)      |
| `uom.termDimension`     | String  | Term dimension (e.g., `Month`)         |

### Empty Options Response

When a subscription has no available product relationships:

```json
{
  "status": "SUCCESS",
  "data": {
    "SUB-000117": {
      "subscriptionName": "SUB-000117",
      "subscriptionId": "a0udi00000485ABCDE",
      "productSku": "STANDALONE-PRODUCT",
      "subscriptionUomName": "License",
      "options": {}
    }
  }
}
```

## Error Handling

### Robust Error Handling

```javascript
async function safeGetProductRelationships(subscriptionNames) {
  const myHeaders = new Headers();
  myHeaders.append("nue-api-key", "YOUR_API_KEY_HERE");
  myHeaders.append("Content-Type", "application/json");

  try {
    // Validate input
    if (!Array.isArray(subscriptionNames) || subscriptionNames.length === 0) {
      throw new Error('subscriptionNames must be a non-empty array');
    }

    const encodedNames = encodeURIComponent(JSON.stringify(subscriptionNames));
    const url = `https://api.nue.io/subscriptions/product-relationships?subscriptionNames=${encodedNames}`;

    const response = await fetch(url, {
      method: 'GET',
      headers: myHeaders
    });

    if (!response.ok) {
      throw new Error(`HTTP ${response.status}: ${response.statusText}`);
    }

    const result = await response.json();

    if (result.status !== 'SUCCESS') {
      throw new Error(`API Error: ${result.message || 'Unknown error'}`);
    }

    return {
      success: true,
      data: result.data || {},
      subscriptions: Object.values(result.data || {})
    };

  } catch (error) {
    console.error('Failed to fetch product relationships:', error);
    return {
      success: false,
      data: {},
      subscriptions: [],
      error: error.message
    };
  }
}

// Usage with error handling
safeGetProductRelationships(["SUB-000115", "SUB-000116"])
  .then(result => {
    if (result.success) {
      console.log(`Retrieved relationships for ${result.subscriptions.length} subscriptions`);
      // Process result.subscriptions
    } else {
      console.error('Failed:', result.error);
      // Handle error state in UI
    }
  });
```

## Best Practices

### Performance

1. **Batch requests** - Query multiple subscriptions in a single API call
2. **Cache results** - Product relationships change infrequently
3. **Lazy load** - Only fetch relationships when the user accesses subscription management

### User Experience

1. **Clear labeling** - Use descriptive labels like "Upgrade to Enterprise" instead of just product names
2. **Show pricing impact** - Display price differences using the included `priceBookEntries`
3. **Indicate restrictions** - Show when `sameUomOnly` applies to set expectations
4. **Highlight swap benefits** - Indicate when `samePriceSwap` is true

### Integration

1. **Combine with subscription data** - Fetch subscriptions first, then query relationships for active ones
2. **Use for change orders** - After user selects an option, use the `relationshipId` and target product details to create a change order
3. **Handle empty states** - Gracefully handle subscriptions with no available changes

## Next Steps

After retrieving available product relationships, you can:

- **Create Change Orders** - Execute the selected upgrade, downgrade, or swap using the relationship data
- **Fetch Pricing Data** - Use the `priceBookEntries` to show customers accurate pricing
- **Update Subscriptions** - Modify subscription settings and custom fields

See the **Creating Draft Change Orders** guide to learn how to implement product changes based on the relationships retrieved from this endpoint.
