← Files Zuora Coding AgentARCHIVED FILE

references/action-subscribe-api-mapping.md

45.1 KB · Oct 2, 2026 · 00:30 UTC

↓ Download file

# Subscribe Action API to Order API Migration Guide

## Overview

This document provides field-accurate mappings for migrating from Zuora's Subscribe Action API (`POST /v1/action/subscribe`) to the Order API (`POST /v1/orders` with `CreateSubscription` action).

The Subscribe API is a "one-stop" API that creates accounts, contacts, payment methods, and subscriptions in a single call. The Order API provides equivalent functionality with more flexibility and modern capabilities.

## API Comparison

| Aspect | Subscribe Action API | Order API (Create) | Order API (Preview) |
|--------|---------------------|-------------------|---------------------|
| Endpoint | `POST /v1/action/subscribe` | `POST /v1/orders` | `POST /v1/orders/preview` |
| Mode Control | `PreviewOptions` parameter | N/A (always creates) | `previewOptions` required |
| Primary Use | Create or preview account + subscription | Create account + subscription | Preview billing/metrics without creating |
| Account Creation | Embedded in request (`Account` object) | `newAccount` or `existingAccountNumber` | `previewAccountInfo` or `existingAccountNumber` |
| Action Type | Implicit (subscribe) | Explicit (`CreateSubscription`) | Explicit (`CreateSubscription`) |
| Billing Control | `SubscribeOptions.generateInvoice` | `processingOptions.runBilling` | N/A (preview only) |
| Payment Control | `SubscribeOptions.processPayments` | `processingOptions.collect` | N/A (preview only) |
| Future Dating | Limited | Native support with `orderDate` | Native support with `orderDate` |
| Atomic Operations | Single subscription only | Multiple actions per order | Multiple actions per order |

## Subscribe API Modes

The Subscribe Action API supports two modes determined by the `PreviewOptions` parameter:

### Mode 1: Create (Actual Subscription Creation)
When `PreviewOptions` is **NOT** provided or is empty, the API creates the actual subscription.

**Migration Path:** → `POST /v1/orders` (Order Create API)

### Mode 2: Preview (No Actual Creation)
When `PreviewOptions` is provided with preview settings, the API returns preview results without creating the subscription.

**Migration Path:** → `POST /v1/orders/preview` (Order Preview API)

## Migration Scenarios

### Scenario 1: New Account + Subscription

When the Subscribe API creates a new account along with the subscription.

**Subscribe API Request:**
```json
POST /v1/action/subscribe
{
  "Account": {
    "name": "Example Corp",
    "currency": "USD",
    "billCycleDay": 1,
    "autoPay": true,
    "batch": "Batch1",
    "billToContact": {
      "firstName": "John",
      "lastName": "Doe",
      "address1": "123 Main St",
      "city": "San Francisco",
      "state": "CA",
      "postalCode": "94105",
      "country": "USA",
      "workEmail": "john.doe@example.com"
    },
    "paymentMethod": {
      "type": "CreditCard",
      "creditCardNumber": "4111111111111111",
      "creditCardType": "Visa",
      "expirationMonth": 12,
      "expirationYear": 2025,
      "securityCode": "123"
    }
  },
  "SubscribeOptions": {
    "generateInvoice": true,
    "processPayments": true
  },
  "SubscriptionData": {
    "Subscription": {
      "termType": "TERMED",
      "contractEffectiveDate": "2026-04-20",
      "serviceActivationDate": "2026-04-20",
      "customerAcceptanceDate": "2026-04-20",
      "initialTerm": 12,
      "renewalTerm": 12,
      "autoRenew": true
    },
    "RatePlanData": [{
      "RatePlan": {
        "productRatePlanId": "2c92a0fd8c7f6e5b018c7f9a12345678"
      },
      "RatePlanChargeData": [{
        "RatePlanCharge": {
          "quantity": 5
        }
      }]
    }]
  }
}
```

**Order API Equivalent:**
```json
POST /v1/orders
{
  "orderDate": "2026-04-20",
  "processingOptions": {
    "runBilling": true,
    "collect": true
  },
  "newAccount": {
    "accountNumber": "A00000123",
    "name": "Example Corp",
    "currency": "USD",
    "billCycleDay": 1,
    "autoPay": true,
    "batch": "Batch1",
    "billToContact": {
      "firstName": "John",
      "lastName": "Doe",
      "address1": "123 Main St",
      "city": "San Francisco",
      "state": "CA",
      "postalCode": "94105",
      "country": "USA",
      "workEmail": "john.doe@example.com"
    },
    "paymentMethod": {
      "type": "CreditCard",
      "cardNumber": "4111111111111111",
      "cardType": "Visa",
      "expirationMonth": 12,
      "expirationYear": 2025,
      "securityCode": "123"
    }
  },
  "subscriptions": [{
    "orderActions": [{
      "type": "CreateSubscription",
      "triggerDates": [{
        "name": "ContractEffective",
        "triggerDate": "2026-04-20"
      }, {
        "name": "ServiceActivation",
        "triggerDate": "2026-04-20"
      }, {
        "name": "CustomerAcceptance",
        "triggerDate": "2026-04-20"
      }],
      "createSubscription": {
        "terms": {
          "initialTerm": {
            "period": 12,
            "periodType": "Month",
            "termType": "TERMED"
          },
          "autoRenew": true,
          "renewalSetting": "RENEW_WITH_SPECIFIC_TERM",
          "renewalTerms": [{
            "period": 12,
            "periodType": "Month"
          }]
        },
        "subscribeToRatePlans": [{
          "productRatePlanId": "2c92a0fd8c7f6e5b018c7f9a12345678",
          "chargeOverrides": [{
            "pricing": {
              "recurringPerUnit": {
                "quantity": 5
              }
            }
          }]
        }]
      }
    }]
  }]
}
```

### Scenario 2: Existing Account + Subscription

When the Subscribe API uses an existing account (by passing `accountKey` in Account object).

**Subscribe API Request:**
```json
POST /v1/action/subscribe
{
  "Account": {
    "accountKey": "A00000001"
  },
  "SubscribeOptions": {
    "generateInvoice": true,
    "processPayments": false
  },
  "SubscriptionData": {
    "Subscription": {
      "termType": "EVERGREEN",
      "contractEffectiveDate": "2026-04-20"
    },
    "RatePlanData": [{
      "RatePlan": {
        "productRatePlanId": "2c92a0fd8c7f6e5b018c7f9a12345678"
      }
    }]
  }
}
```

**Order API Equivalent:**
```json
POST /v1/orders
{
  "orderDate": "2026-04-20",
  "existingAccountNumber": "A00000001",
  "processingOptions": {
    "runBilling": true,
    "collect": false
  },
  "subscriptions": [{
    "orderActions": [{
      "type": "CreateSubscription",
      "triggerDates": [{
        "name": "ContractEffective",
        "triggerDate": "2026-04-20"
      }],
      "createSubscription": {
        "terms": {
          "initialTerm": {
            "termType": "EVERGREEN"
          },
          "autoRenew": false
        },
        "subscribeToRatePlans": [{
          "productRatePlanId": "2c92a0fd8c7f6e5b018c7f9a12345678"
        }]
      }
    }]
  }]
}
```

### Scenario 3: Preview Mode (No Actual Creation)

When the Subscribe API is called with `PreviewOptions`, it returns preview results without creating the subscription. This maps to the Order Preview API.

**Subscribe API Request (with PreviewOptions):**
```json
POST /v1/action/subscribe
{
  "Account": {
    "name": "Example Corp",
    "currency": "USD",
    "billCycleDay": 1,
    "billToContact": {
      "firstName": "Jane",
      "lastName": "Smith",
      "address1": "456 Oak Ave",
      "city": "Boston",
      "state": "MA",
      "postalCode": "02101",
      "country": "USA",
      "workEmail": "jane@example.com"
    }
  },
  "SubscribeOptions": {
    "generateInvoice": true,
    "processPayments": false
  },
  "PreviewOptions": {
    "enablePreviewMode": true,
    "numberOfPeriods": 3
  },
  "SubscriptionData": {
    "Subscription": {
      "termType": "TERMED",
      "contractEffectiveDate": "2026-05-01",
      "initialTerm": 12,
      "autoRenew": true,
      "renewalTerm": 12
    },
    "RatePlanData": [{
      "RatePlan": {
        "productRatePlanId": "2c92a0fd8c7f6e5b018c7f9a12345678"
      }
    }]
  }
}
```

**Order Preview API Equivalent:**
```json
POST /v1/orders/preview
{
  "orderDate": "2026-05-01",
  "previewAccountInfo": {
    "name": "Example Corp",
    "currency": "USD",
    "billCycleDay": 1,
    "billToContact": {
      "firstName": "Jane",
      "lastName": "Smith",
      "address1": "456 Oak Ave",
      "city": "Boston",
      "state": "MA",
      "postalCode": "02101",
      "country": "USA",
      "workEmail": "jane@example.com"
    }
  },
  "previewOptions": {
    "previewTypes": ["BillingDocs", "ChargeMetrics"],
    "previewNumberOfPeriods": 3
  },
  "subscriptions": [{
    "orderActions": [{
      "type": "CreateSubscription",
      "triggerDates": [{
        "name": "ContractEffective",
        "triggerDate": "2026-05-01"
      }],
      "createSubscription": {
        "terms": {
          "initialTerm": {
            "period": 12,
            "periodType": "Month",
            "termType": "TERMED"
          },
          "autoRenew": true,
          "renewalSetting": "RENEW_WITH_SPECIFIC_TERM",
          "renewalTerms": [{
            "period": 12,
            "periodType": "Month"
          }]
        },
        "subscribeToRatePlans": [{
          "productRatePlanId": "2c92a0fd8c7f6e5b018c7f9a12345678"
        }]
      }
    }]
  }]
}
```

**Key Differences for Preview Mode:**

1. **Endpoint Changes**: `POST /v1/orders` → `POST /v1/orders/preview`
2. **Account Field**: `newAccount` → `previewAccountInfo` (for new accounts)
3. **Required Field**: `previewOptions` is **required** for preview API
4. **No Processing Options**: `processingOptions` is not used in preview mode (no actual billing/collection)
5. **Preview Types**: Must specify `previewTypes: ["BillingDocs", "ChargeMetrics"]` (Subscribe API only supports these two types)

## Field Mapping Tables

### Top-Level Request Fields

| Subscribe API Field | Order API Field | Notes |
|---------------------|-----------------|-------|
| `Account` | `newAccount`, `existingAccountNumber`, or `previewAccountInfo` | Use `newAccount` (create mode) or `previewAccountInfo` (preview mode) for new accounts; `existingAccountNumber` for existing |
| `SubscribeOptions` | `processingOptions` | Billing and payment options move to order level (only in create mode, not in preview) |
| `SubscriptionData` | `subscriptions[].orderActions[]` | Subscription becomes a `CreateSubscription` action |
| `PreviewOptions` | `previewOptions` + endpoint change | **If present**: triggers preview mode → use `/v1/orders/preview` endpoint and `previewOptions` field. **If absent**: use `/v1/orders` endpoint (create mode) |
| N/A | `orderDate` | **New required field** - typically same as `contractEffectiveDate` |

### Account Object Mapping

#### For New Accounts

| Subscribe API Field | Order API Field | Notes |
|---------------------|-----------------|-------|
| `Account.name` | `newAccount.name` | Same |
| `Account.accountNumber` | `newAccount.accountNumber` | Optional in both |
| `Account.currency` | `newAccount.currency` | Required in both |
| `Account.billCycleDay` | `newAccount.billCycleDay` | Same |
| `Account.autoPay` | `newAccount.autoPay` | Same |
| `Account.batch` | `newAccount.batch` | Same |
| `Account.billToContact` | `newAccount.billToContact` | See Contact mapping below |
| `Account.soldToContact` | `newAccount.soldToContact` | Optional, defaults to billToContact |
| `Account.paymentMethod` | `newAccount.paymentMethod` | See Payment Method mapping below |
| `Account.taxInfo` | `newAccount.taxInfo` | Same structure |
| `Account.customField__c` | `newAccount.customField__c` | Custom fields preserved |

#### For Existing Accounts

| Subscribe API Field | Order API Field | Notes |
|---------------------|-----------------|-------|
| `Account.accountKey` | `existingAccountNumber` | Account number or ID |
| `Account.paymentMethod` | Use existing or create via separate API | Order API doesn't create payment methods for existing accounts |

### Contact Object Mapping

| Subscribe API Field | Order API Field | Notes |
|---------------------|-----------------|-------|
| `billToContact.firstName` | `newAccount.billToContact.firstName` | Same |
| `billToContact.lastName` | `newAccount.billToContact.lastName` | Same |
| `billToContact.address1` | `newAccount.billToContact.address1` | Same |
| `billToContact.address2` | `newAccount.billToContact.address2` | Same |
| `billToContact.city` | `newAccount.billToContact.city` | Same |
| `billToContact.state` | `newAccount.billToContact.state` | Same |
| `billToContact.postalCode` | `newAccount.billToContact.postalCode` | Same |
| `billToContact.country` | `newAccount.billToContact.country` | Same |
| `billToContact.workEmail` | `newAccount.billToContact.workEmail` | Same |
| `billToContact.workPhone` | `newAccount.billToContact.workPhone` | Same |

Note: `soldToContact` follows the same mapping pattern.

### Payment Method Mapping

| Subscribe API Field | Order API Field | Notes |
|---------------------|-----------------|-------|
| `paymentMethod.type` | `newAccount.paymentMethod.type` | Same values: "CreditCard", "ACH", etc. |
| `paymentMethod.creditCardNumber` | `newAccount.paymentMethod.cardNumber` | **Field name changed** |
| `paymentMethod.creditCardType` | `newAccount.paymentMethod.cardType` | **Field name changed** |
| `paymentMethod.expirationMonth` | `newAccount.paymentMethod.expirationMonth` | Same |
| `paymentMethod.expirationYear` | `newAccount.paymentMethod.expirationYear` | Same |
| `paymentMethod.securityCode` | `newAccount.paymentMethod.securityCode` | Same |
| `paymentMethod.creditCardHolderName` | `newAccount.paymentMethod.cardHolderName` | **Field name changed** |

**Important:** For existing accounts, payment methods cannot be created via Order API. Use the Payment Methods API separately if needed.

### Subscribe Options Mapping

| Subscribe API Field | Order API Field | Notes |
|---------------------|-----------------|-------|
| `SubscribeOptions.generateInvoice` | `processingOptions.runBilling` | Generates invoice and invoice items. **Default: `false`** |
| `SubscribeOptions.processPayments` | `processingOptions.collect` | Processes payment if invoice generated. **Default: `false`** |
| `SubscribeOptions.applyCreditBalance` | `processingOptions.applyCreditBalance` | Apply account credit balance |
| `SubscribeOptions.electronicPaymentMethodId` | `electronicPaymentOptions.paymentMethodId` | Specify which payment method to use for electronic payment collection |

**Important Notes:**
- These mappings apply only to Create mode. In Preview mode, `processingOptions` is not used.
- **Default values**: Both `generateInvoice` and `processPayments` default to `false` in the Subscribe API implementation. If omitted, no invoice or payment processing occurs.
- When migrating to Order API, if the Subscribe API code omits these fields, you should also omit `processingOptions` or explicitly set `runBilling: false` and `collect: false`.

### Preview Options Mapping

| Subscribe API Field | Order API Field | Notes |
|---------------------|-----------------|-------|
| `PreviewOptions.enablePreviewMode` | Endpoint change | Presence triggers preview mode → use `/v1/orders/preview` |
| `PreviewOptions.numberOfPeriods` | `previewOptions.previewNumberOfPeriods` | Number of billing periods to preview |
| N/A | `previewOptions.previewTypes` | **Required** array: `["BillingDocs", "ChargeMetrics"]` for Subscribe API migration |
| N/A | `previewOptions.previewThruType` | Optional: "SpecificDate", "NumberOfPeriods" |
| N/A | `previewOptions.specificPreviewThruDate` | Optional: Specific end date for preview |
| N/A | `previewOptions.chargeTypeToExclude` | Optional: Array of charge types to exclude |
| N/A | `previewOptions.skipTax` | Optional: Skip tax calculation (default: false) |
| N/A | `previewOptions.validateScheduledOrders` | Optional: Validate scheduled orders (default: false) |

**Available Preview Types (Order API supports all, Subscribe API migration uses subset):**
- `BillingDocs` - Preview billing documents (invoices, credit memos) ✅ **Use for Subscribe API**
- `ChargeMetrics` - Preview charge-level metrics ✅ **Use for Subscribe API**
- `OrderMetrics` - Preview order-level metrics ❌ **Not available in Subscribe API preview**
- `RampMetrics` - Preview ramp metrics (for ramp deals) ❌ **Not available in Subscribe API preview**
- `RampDeltaMetrics` - Preview ramp delta metrics ❌ **Not available in Subscribe API preview**
- `OrderDeltaMetrics` - Preview order delta metrics ❌ **Not available in Subscribe API preview**

**Important Preview Mode Notes:**
1. `previewTypes` is **required** in Order Preview API (must specify at least one type)
2. **For Subscribe API migration**: Only use `["BillingDocs", "ChargeMetrics"]` - these match Subscribe API's preview capabilities
3. For new accounts, use `previewAccountInfo` instead of `newAccount`
4. For existing accounts, use `existingAccountNumber` as usual
5. `processingOptions` is not applicable in preview mode
5. Preview API returns projected billing data without creating actual records

### Subscription Object Mapping

| Subscribe API Field | Order API Field | Notes |
|---------------------|-----------------|-------|
| `Subscription.termType` | `createSubscription.terms.initialTerm.termType` | "TERMED" or "EVERGREEN" |
| `Subscription.contractEffectiveDate` | `triggerDates[name="ContractEffective"].triggerDate` | Also set as `orderDate` typically |
| `Subscription.serviceActivationDate` | `triggerDates[name="ServiceActivation"].triggerDate` | Optional trigger date |
| `Subscription.customerAcceptanceDate` | `triggerDates[name="CustomerAcceptance"].triggerDate` | Optional trigger date |
| `Subscription.initialTerm` | `createSubscription.terms.initialTerm.period` | Number of periods |
| `Subscription.initialTermPeriodType` | `createSubscription.terms.initialTerm.periodType` | "Month", "Year", etc. (defaults to Month) |
| `Subscription.renewalTerm` | `createSubscription.terms.renewalTerms[0].period` | Number of periods for renewal |
| `Subscription.renewalTermPeriodType` | `createSubscription.terms.renewalTerms[0].periodType` | "Month", "Year", etc. |
| `Subscription.autoRenew` | `createSubscription.terms.autoRenew` | Boolean |
| `Subscription.notes` | `createSubscription.notes` | Same |
| `Subscription.customField__c` | `createSubscription.customField__c` | Custom fields preserved |

**Important Term Mapping Notes:**

For **TERMED** subscriptions:
- Must specify `initialTerm.period` and `initialTerm.periodType`
- If `autoRenew` is true, must specify `renewalSetting` and `renewalTerms`

For **EVERGREEN** subscriptions:
- Set `initialTerm.termType` to "EVERGREEN"
- Set `autoRenew` to false
- No `renewalTerms` needed

### Rate Plan Data Mapping

| Subscribe API Field | Order API Field | Notes |
|---------------------|-----------------|-------|
| `RatePlanData[].RatePlan.productRatePlanId` | `subscribeToRatePlans[].productRatePlanId` | Same |
| `RatePlanData[].RatePlanChargeData[]` | `subscribeToRatePlans[].chargeOverrides[]` | Charge-level overrides |

### Rate Plan Charge Overrides

| Subscribe API Field | Order API Field | Notes |
|---------------------|-----------------|-------|
| `RatePlanCharge.quantity` | `chargeOverrides[].pricing.{chargeModel}.quantity` | **Charge model dependent:** `recurringPerUnit.quantity`, `recurringTiered.quantity`, etc. Not applicable for flat fee charges |
| `RatePlanCharge.price` | `chargeOverrides[].pricing.{chargeModel}.listPrice` | **Charge model dependent:** `recurringFlatFee.listPrice`, `recurringPerUnit.listPrice`, etc. |
| `RatePlanCharge.discountPercentage` | `chargeOverrides[].pricing.{chargeModel}.discountPercentage` | Percentage discount (if applicable to charge model) |
| `RatePlanCharge.discountAmount` | `chargeOverrides[].pricing.{chargeModel}.discountAmount` | Fixed discount amount (if applicable to charge model) |
| `RatePlanCharge.billingPeriod` | `chargeOverrides[].billingPeriod` | Override billing frequency |
| `RatePlanCharge.specificBillingPeriod` | `chargeOverrides[].specificBillingPeriod` | Custom billing period |
| `RatePlanCharge.endDateCondition` | `chargeOverrides[].endDateCondition` | When charge ends |
| `RatePlanCharge.customField__c` | `chargeOverrides[].customField__c` | Custom fields preserved |

**Important:** The `pricing` structure within `chargeOverrides` varies by charge model:
- **Flat Fee**: `pricing.recurringFlatFee.listPrice` (no quantity)
- **Per Unit**: `pricing.recurringPerUnit.quantity` and `listPrice`
- **Tiered**: `pricing.recurringTiered.quantity` and `tiers[]`
- **Volume**: `pricing.recurringVolume.tiers[]` (quantity determined by tier)

### Charge Override Examples by Charge Model

#### Flat Fee Charge Override

```json
{
  "subscribeToRatePlans": [{
    "productRatePlanId": "2c92...",
    "chargeOverrides": [{
      "pricing": {
        "recurringFlatFee": {
          "listPrice": 100.00
        }
      }
    }]
  }]
}
```

#### Per Unit Charge Override (with quantity)

```json
{
  "subscribeToRatePlans": [{
    "productRatePlanId": "2c92...",
    "chargeOverrides": [{
      "pricing": {
        "recurringPerUnit": {
          "listPrice": 10.00,
          "quantity": 5
        }
      }
    }]
  }]
}
```

#### Tiered Charge Override (with quantity)

```json
{
  "subscribeToRatePlans": [{
    "productRatePlanId": "2c92...",
    "chargeOverrides": [{
      "pricing": {
        "recurringTiered": {
          "quantity": 15,
          "tiers": [{
            "tier": 1,
            "startingUnit": 1,
            "endingUnit": 10,
            "price": 30.00
          }, {
            "tier": 2,
            "startingUnit": 11,
            "endingUnit": 20,
            "price": 20.00
          }]
        }
      }
    }]
  }]
}
```

#### Volume Charge Override

```json
{
  "subscribeToRatePlans": [{
    "productRatePlanId": "2c92...",
    "chargeOverrides": [{
      "pricing": {
        "recurringVolume": {
          "tiers": [{
            "tier": 1,
            "startingUnit": 1,
            "endingUnit": 100,
            "price": 1000.00
          }, {
            "tier": 2,
            "startingUnit": 101,
            "endingUnit": 500,
            "price": 4000.00
          }]
        }
      }
    }]
  }]
}
```

## Response Structure Changes

### Subscribe API Response

```json
{
  "success": true,
  "accountId": "2c92a0fd8c7f6e5b018c7f9a12345678",
  "accountNumber": "A00000123",
  "subscriptionId": "2c92a0fd8c7f6e5b018c7f9a98765432",
  "subscriptionNumber": "A-S00000456",
  "invoiceId": "2c92a0fd8c7f6e5b018c7f9a55555555",
  "invoiceNumber": "INV00000789",
  "paymentId": "2c92a0fd8c7f6e5b018c7f9a66666666",
  "totalInvoiceBalance": 100.00,
  "invoiceResult": {
    "invoiceId": "2c92a0fd8c7f6e5b018c7f9a55555555",
    "invoiceNumber": "INV00000789"
  }
}
```

### Order API Response

```json
{
  "success": true,
  "orderNumber": "O-00000123",
  "accountNumber": "A00000123",
  "accountId": "2c92a0fd8c7f6e5b018c7f9a12345678",
  "subscriptions": [{
    "subscriptionNumber": "A-S00000456",
    "subscriptionId": "2c92a0fd8c7f6e5b018c7f9a98765432",
    "status": "Active",
    "orderActions": [{
      "type": "CreateSubscription",
      "sequence": 0
    }]
  }],
  "invoiceNumbers": ["INV00000789"],
  "invoices": [{
    "invoiceNumber": "INV00000789",
    "invoiceId": "2c92a0fd8c7f6e5b018c7f9a55555555",
    "amount": 100.00,
    "status": "Posted"
  }],
  "paymentNumbers": ["P-00000111"],
  "payments": [{
    "paymentNumber": "P-00000111",
    "paymentId": "2c92a0fd8c7f6e5b018c7f9a66666666",
    "amount": 100.00,
    "status": "Processed"
  }]
}
```

### Response Field Mapping

| Subscribe API Response Field | Order API Response Field | Notes |
|------------------------------|--------------------------|-------|
| `accountId` | `accountId` | Same |
| `accountNumber` | `accountNumber` | Same |
| `subscriptionId` | `subscriptions[0].subscriptionId` | Now nested in array |
| `subscriptionNumber` | `subscriptions[0].subscriptionNumber` | Now nested in array |
| `invoiceId` | `invoices[0].invoiceId` | Now nested in array |
| `invoiceNumber` | `invoiceNumbers[0]` or `invoices[0].invoiceNumber` | Multiple locations |
| `paymentId` | `payments[0].paymentId` | Now nested in array |
| `totalInvoiceBalance` | `invoices[0].amount` | Now in invoice object |
| N/A | `orderNumber` | **New field** - unique order identifier |

## Key Differences and Considerations

### 1. Account Creation Logic

**Subscribe API:**
- If `Account.accountKey` is provided → use existing account
- If `Account.accountKey` is not provided → create new account

**Order API:**
- Use `existingAccountNumber` → use existing account
- Use `newAccount` → create new account
- Cannot mix both in same request

### 2. Payment Method Handling

**Subscribe API:**
- Can create payment method for new or existing accounts

**Order API:**
- Can create payment method only for new accounts (via `newAccount.paymentMethod`)
- For existing accounts, must use existing payment method or create separately via Payment Methods API

### 3. Multiple Subscriptions

**Subscribe API:**
- Creates one subscription per call
- Must make multiple API calls for multiple subscriptions

**Order API:**
- Can create multiple subscriptions in one order
- Each subscription can have multiple actions

### 4. Trigger Dates

**Subscribe API:**
- Uses specific date fields: `contractEffectiveDate`, `serviceActivationDate`, `customerAcceptanceDate`

**Order API:**
- Uses flexible `triggerDates` array with named triggers
- Common trigger names:
  - "ContractEffective"
  - "ServiceActivation"
  - "CustomerAcceptance"

### 5. Term Configuration

**Subscribe API:**
- Simple fields: `initialTerm`, `renewalTerm`, `autoRenew`

**Order API:**
- Structured `terms` object with explicit settings
- Must specify `renewalSetting` when `autoRenew` is true
- Options: "RENEW_WITH_SPECIFIC_TERM", "RENEW_TO_EVERGREEN"

### 6. Error Handling

**Subscribe API:**
- Returns single `success` boolean
- Errors in `reasons` array

**Order API:**
- Returns `success` boolean for overall order
- More detailed error information per subscription and action
- Better support for partial success scenarios

## Migration Examples

### Example 1: Simple New Account + Subscription

**Before (Subscribe API):**
```python
import requests

response = requests.post(
    "https://rest.zuora.com/v1/action/subscribe",
    json={
        "Account": {
            "name": "New Customer",
            "currency": "USD",
            "billCycleDay": 1,
            "billToContact": {
                "firstName": "Jane",
                "lastName": "Smith",
                "address1": "456 Oak Ave",
                "city": "Boston",
                "state": "MA",
                "postalCode": "02101",
                "country": "USA",
                "workEmail": "jane@example.com"
            }
        },
        "SubscribeOptions": {
            "generateInvoice": True,
            "processPayments": False
        },
        "SubscriptionData": {
            "Subscription": {
                "termType": "TERMED",
                "contractEffectiveDate": "2026-05-01",
                "initialTerm": 12,
                "autoRenew": True,
                "renewalTerm": 12
            },
            "RatePlanData": [{
                "RatePlan": {
                    "productRatePlanId": "2c92a0fd8c7f6e5b018c7f9a12345678"
                }
            }]
        }
    },
    headers=headers
)

subscription_id = response.json()['subscriptionId']
account_number = response.json()['accountNumber']
```

**After (Order API):**
```python
import requests

response = requests.post(
    "https://rest.zuora.com/v1/orders",
    json={
        "orderDate": "2026-05-01",
        "processingOptions": {
            "runBilling": True,
            "collect": False
        },
        "newAccount": {
            "name": "New Customer",
            "currency": "USD",
            "billCycleDay": 1,
            "billToContact": {
                "firstName": "Jane",
                "lastName": "Smith",
                "address1": "456 Oak Ave",
                "city": "Boston",
                "state": "MA",
                "postalCode": "02101",
                "country": "USA",
                "workEmail": "jane@example.com"
            }
        },
        "subscriptions": [{
            "orderActions": [{
                "type": "CreateSubscription",
                "triggerDates": [{
                    "name": "ContractEffective",
                    "triggerDate": "2026-05-01"
                }],
                "createSubscription": {
                    "terms": {
                        "initialTerm": {
                            "period": 12,
                            "periodType": "Month",
                            "termType": "TERMED"
                        },
                        "autoRenew": True,
                        "renewalSetting": "RENEW_WITH_SPECIFIC_TERM",
                        "renewalTerms": [{
                            "period": 12,
                            "periodType": "Month"
                        }]
                    },
                    "subscribeToRatePlans": [{
                        "productRatePlanId": "2c92a0fd8c7f6e5b018c7f9a12345678"
                    }]
                }
            }]
        }]
    },
    headers=headers
)

subscription_id = response.json()['subscriptions'][0]['subscriptionId']
account_number = response.json()['accountNumber']
order_number = response.json()['orderNumber']
```

### Example 2: Existing Account + Subscription

**Before (Subscribe API):**
```python
response = requests.post(
    "https://rest.zuora.com/v1/action/subscribe",
    json={
        "Account": {
            "accountKey": "A00000001"
        },
        "SubscribeOptions": {
            "generateInvoice": False
        },
        "SubscriptionData": {
            "Subscription": {
                "termType": "EVERGREEN",
                "contractEffectiveDate": "2026-05-01"
            },
            "RatePlanData": [{
                "RatePlan": {
                    "productRatePlanId": "2c92a0fd8c7f6e5b018c7f9a12345678"
                }
            }]
        }
    },
    headers=headers
)
```

**After (Order API):**
```python
response = requests.post(
    "https://rest.zuora.com/v1/orders",
    json={
        "orderDate": "2026-05-01",
        "existingAccountNumber": "A00000001",
        "processingOptions": {
            "runBilling": False
        },
        "subscriptions": [{
            "orderActions": [{
                "type": "CreateSubscription",
                "triggerDates": [{
                    "name": "ContractEffective",
                    "triggerDate": "2026-05-01"
                }],
                "createSubscription": {
                    "terms": {
                        "initialTerm": {
                            "termType": "EVERGREEN"
                        },
                        "autoRenew": False
                    },
                    "subscribeToRatePlans": [{
                        "productRatePlanId": "2c92a0fd8c7f6e5b018c7f9a12345678"
                    }]
                }
            }]
        }]
    },
    headers=headers
)
```

## Common Migration Patterns

### Pattern 0: Detecting Preview vs Create Mode

```python
# Detect if Subscribe API is in preview mode
subscribe_request = {...}

# Check for PreviewOptions presence
has_preview_options = 'PreviewOptions' in subscribe_request and subscribe_request['PreviewOptions']

if has_preview_options:
    # Use Order Preview API
    endpoint = "https://rest.zuora.com/v1/orders/preview"
    
    # Build preview request
    order_request = {
        "orderDate": subscribe_request['SubscriptionData']['Subscription']['contractEffectiveDate'],
        "previewOptions": {
            "previewTypes": ["BillingDocs", "ChargeMetrics"],  # Subscribe API only supports these two
            "previewNumberOfPeriods": subscribe_request['PreviewOptions'].get('numberOfPeriods', 1)
        }
    }
    
    # Handle account info
    if 'accountKey' not in subscribe_request['Account']:
        # New account - use previewAccountInfo
        order_request['previewAccountInfo'] = subscribe_request['Account']
    else:
        # Existing account
        order_request['existingAccountNumber'] = subscribe_request['Account']['accountKey']
    
    # No processingOptions in preview mode
    
else:
    # Use Order Create API
    endpoint = "https://rest.zuora.com/v1/orders"
    
    # Build create request
    order_request = {
        "orderDate": subscribe_request['SubscriptionData']['Subscription']['contractEffectiveDate'],
        "processingOptions": {
            "runBilling": subscribe_request['SubscribeOptions'].get('generateInvoice', False),
            "collect": subscribe_request['SubscribeOptions'].get('processPayments', False)
        }
    }
    
    # Handle account info
    if 'accountKey' not in subscribe_request['Account']:
        # New account - use newAccount
        order_request['newAccount'] = subscribe_request['Account']
    else:
        # Existing account
        order_request['existingAccountNumber'] = subscribe_request['Account']['accountKey']
```

### Pattern 1: Detecting New vs. Existing Account

```python
# In Subscribe API code
if 'accountKey' in subscribe_request['Account']:
    # Using existing account
    account_identifier = subscribe_request['Account']['accountKey']
else:
    # Creating new account
    new_account_data = subscribe_request['Account']

# Convert to Order API
if account_identifier:
    order_request = {
        "existingAccountNumber": account_identifier,
        # ... rest of order
    }
else:
    order_request = {
        "newAccount": new_account_data,
        # ... rest of order
    }
```

### Pattern 2: Converting Term Configuration

```python
# Subscribe API term data
subscription = subscribe_request['SubscriptionData']['Subscription']

# Convert to Order API terms
if subscription['termType'] == 'TERMED':
    terms = {
        "initialTerm": {
            "period": subscription['initialTerm'],
            "periodType": subscription.get('initialTermPeriodType', 'Month'),
            "termType": "TERMED"
        },
        "autoRenew": subscription.get('autoRenew', False)
    }
    
    if subscription.get('autoRenew'):
        terms["renewalSetting"] = "RENEW_WITH_SPECIFIC_TERM"
        terms["renewalTerms"] = [{
            "period": subscription.get('renewalTerm', subscription['initialTerm']),
            "periodType": subscription.get('renewalTermPeriodType', 'Month')
        }]
else:  # EVERGREEN
    terms = {
        "initialTerm": {
            "termType": "EVERGREEN"
        },
        "autoRenew": False
    }
```

### Pattern 3: Converting Charge Overrides

**Important:** The charge override structure depends on the product's charge model. You need to know the charge model type to construct the correct `pricing` structure.

```python
# Subscribe API charge data
charge_data_list = rate_plan_data['RatePlanChargeData']

# Convert to Order API charge overrides
charge_overrides = []
for charge_data in charge_data_list:
    charge = charge_data['RatePlanCharge']
    override = {}
    
    # Note: You need to know the charge model for each product rate plan charge
    # This information should come from product catalog or be specified by user
    # Common charge models: FlatFee, PerUnit, Tiered, Volume
    
    # Example: Assuming Per Unit charge model
    if 'quantity' in charge or 'price' in charge:
        override['pricing'] = {
            "recurringPerUnit": {}
        }
        if 'quantity' in charge:
            override['pricing']['recurringPerUnit']['quantity'] = charge['quantity']
        if 'price' in charge:
            override['pricing']['recurringPerUnit']['listPrice'] = charge['price']
    
    # For Flat Fee charges (no quantity)
    # override['pricing'] = {
    #     "recurringFlatFee": {
    #         "listPrice": charge['price']
    #     }
    # }
    
    # For Tiered charges
    # override['pricing'] = {
    #     "recurringTiered": {
    #         "quantity": charge['quantity'],
    #         "tiers": [...]  # Tier configuration
    #     }
    # }
    
    # Other charge override fields (charge model independent)
    if 'billingPeriod' in charge:
        override['billingPeriod'] = charge['billingPeriod']
    
    if 'discountPercentage' in charge:
        # Add discount to the appropriate pricing model
        if 'pricing' in override:
            for model in override['pricing'].values():
                model['discountPercentage'] = charge['discountPercentage']
    
    charge_overrides.append(override)
```

**Best Practice:** Query the product catalog to determine the charge model before constructing the charge override structure.

### Pattern 4: Using Electronic Payment Method

When the Subscribe API specifies which payment method to use for electronic payment collection via `electronicPaymentMethodId`, this maps to the Order API's `electronicPaymentOptions`.

```python
# Subscribe API with electronicPaymentMethodId
subscribe_request = {
    "Account": {
        "accountKey": "A00000001"  # Existing account with multiple payment methods
    },
    "SubscribeOptions": {
        "generateInvoice": True,
        "processPayments": True,
        "electronicPaymentMethodId": "2c92a0fd8c7f6e5b018c7f9a99999999"  # Specific payment method to use
    },
    "SubscriptionData": {...}
}

# Convert to Order API
order_request = {
    "orderDate": "2026-04-20",
    "existingAccountNumber": "A00000001",
    "processingOptions": {
        "runBilling": True,
        "collect": True
    },
    "electronicPaymentOptions": {
        "paymentMethodId": "2c92a0fd8c7f6e5b018c7f9a99999999"  # Specify which payment method to use
    },
    "subscriptions": [{
        "orderActions": [{
            "type": "CreateSubscription",
            "createSubscription": {...}
        }]
    }]
}
```

**Use Cases:**
- Account has multiple payment methods, need to specify which one to use
- Override default payment method for this specific order
- Use a specific payment method for electronic payment collection when `processingOptions.collect` is true

**Note:** `electronicPaymentOptions` is only applicable when:
- `processingOptions.collect` is set to `true`
- The account has a valid payment method
- The payment method ID is active and belongs to the account

## Validation Checklist

When migrating Subscribe API to Order API:

### General Checks
- [ ] **Detect mode first**: Check if `PreviewOptions` exists to determine preview vs create mode
- [ ] Identify if creating new account or using existing account
- [ ] Set `orderDate` (new required field)

### For Create Mode (no PreviewOptions)
- [ ] Use endpoint: `POST /v1/orders`
- [ ] Map account fields to `newAccount` or `existingAccountNumber`
- [ ] Map `billToContact` fields (required for new accounts)
- [ ] Map `soldToContact` fields (optional, defaults to billToContact)
- [ ] Handle payment method creation (only for new accounts in Order API)
- [ ] Convert `SubscribeOptions` to `processingOptions` (runBilling, collect)
- [ ] If `electronicPaymentMethodId` present: map to `electronicPaymentOptions.paymentMethodId`
- [ ] Update response handling for nested subscription data
- [ ] Handle new `orderNumber` field in response
- [ ] Verify invoice and payment generation behavior

### For Preview Mode (PreviewOptions present)
- [ ] Use endpoint: `POST /v1/orders/preview`
- [ ] For new accounts: use `previewAccountInfo` (NOT `newAccount`)
- [ ] For existing accounts: use `existingAccountNumber` as usual
- [ ] **Required**: Add `previewOptions` with `previewTypes` array
- [ ] Map `PreviewOptions.numberOfPeriods` to `previewOptions.previewNumberOfPeriods`
- [ ] **Do NOT include** `processingOptions` (not applicable in preview mode)
- [ ] Handle preview response structure (different from create response)
- [ ] Verify preview results (billing docs, metrics) match expectations

### Common to Both Modes
- [ ] Map subscription term configuration correctly (TERMED vs EVERGREEN)
- [ ] Convert `initialTerm` and `renewalTerm` to structured `terms` object
- [ ] Set `renewalSetting` when `autoRenew` is true (for TERMED subscriptions)
- [ ] Map contract dates to `triggerDates` array
- [ ] Convert `RatePlanData` to `subscribeToRatePlans`
- [ ] Map charge overrides with correct pricing structure (quantity inside pricing model)
- [ ] Ensure charge override structure matches charge model (FlatFee, PerUnit, Tiered, Volume)
- [ ] Test with sandbox environment before production

## Common Gotchas

### ❌ Incorrect: Payment Method for Existing Account

```json
// This doesn't work - Order API can't create payment methods for existing accounts
{
  "existingAccountNumber": "A00000001",
  "newAccount": {
    "paymentMethod": {
      "type": "CreditCard",
      "cardNumber": "4111111111111111"
    }
  }
}
```

### ✅ Correct: Use Existing Payment Method or Create Separately

```json
// Use existing payment method on the account
{
  "existingAccountNumber": "A00000001",
  "subscriptions": [...]
}

// Or create payment method separately via Payment Methods API first
```

### ❌ Incorrect: Credit Card Field Names

```json
// Old Subscribe API field names don't work in Order API
{
  "newAccount": {
    "paymentMethod": {
      "creditCardNumber": "4111111111111111",  // Wrong
      "creditCardType": "Visa",                 // Wrong
      "creditCardHolderName": "John Doe"        // Wrong
    }
  }
}
```

### ✅ Correct: Order API Field Names

```json
{
  "newAccount": {
    "paymentMethod": {
      "cardNumber": "4111111111111111",     // Correct
      "cardType": "Visa",                   // Correct
      "cardHolderName": "John Doe"          // Correct
    }
  }
}
```

### ❌ Incorrect: Missing Renewal Configuration for TERMED

```json
// Incomplete - TERMED with autoRenew needs renewalSetting and renewalTerms
{
  "terms": {
    "initialTerm": {
      "period": 12,
      "periodType": "Month",
      "termType": "TERMED"
    },
    "autoRenew": true
    // Missing renewalSetting and renewalTerms!
  }
}
```

### ✅ Correct: Complete TERMED Configuration

```json
{
  "terms": {
    "initialTerm": {
      "period": 12,
      "periodType": "Month",
      "termType": "TERMED"
    },
    "autoRenew": true,
    "renewalSetting": "RENEW_WITH_SPECIFIC_TERM",
    "renewalTerms": [{
      "period": 12,
      "periodType": "Month"
    }]
  }
}
```

### ❌ Incorrect: Quantity Directly in chargeOverrides

```json
// Wrong - quantity must be inside the pricing model object
{
  "subscribeToRatePlans": [{
    "productRatePlanId": "2c92...",
    "chargeOverrides": [{
      "quantity": 5  // This is incorrect!
    }]
  }]
}
```

### ✅ Correct: Quantity Inside Pricing Model

```json
// Correct - quantity is inside the appropriate pricing model
{
  "subscribeToRatePlans": [{
    "productRatePlanId": "2c92...",
    "chargeOverrides": [{
      "pricing": {
        "recurringPerUnit": {
          "quantity": 5  // Correct for Per Unit charges
        }
      }
    }]
  }]
}
```

**Note:** The pricing structure depends on the charge model:
- Flat Fee charges don't have a quantity field
- Per Unit charges: `pricing.recurringPerUnit.quantity`
- Tiered charges: `pricing.recurringTiered.quantity`
- Volume charges: quantity is determined by tier ranges

### ❌ Incorrect: Using Wrong Endpoint for Preview Mode

```python
# Wrong - using create endpoint for preview
if 'PreviewOptions' in subscribe_request:
    response = requests.post(
        "https://rest.zuora.com/v1/orders",  # Wrong endpoint!
        json=order_request
    )
```

### ✅ Correct: Using Preview Endpoint

```python
# Correct - using preview endpoint when PreviewOptions present
if 'PreviewOptions' in subscribe_request:
    response = requests.post(
        "https://rest.zuora.com/v1/orders/preview",  # Correct!
        json={
            "orderDate": "2026-05-01",
            "previewOptions": {
                "previewTypes": ["BillingDocs", "ChargeMetrics"]
            },
            "subscriptions": [...]
        }
    )
```

### ❌ Incorrect: Missing previewTypes in Preview Mode

```json
// Wrong - previewTypes is required for Order Preview API
POST /v1/orders/preview
{
  "orderDate": "2026-05-01",
  "previewOptions": {
    "previewNumberOfPeriods": 3
    // Missing previewTypes!
  },
  "subscriptions": [...]
}
```

### ✅ Correct: Including Required previewTypes

```json
// Correct - previewTypes is provided
POST /v1/orders/preview
{
  "orderDate": "2026-05-01",
  "previewOptions": {
    "previewTypes": ["BillingDocs", "ChargeMetrics"],
    "previewNumberOfPeriods": 3
  },
  "subscriptions": [...]
}
```

### ❌ Incorrect: Using newAccount in Preview Mode

```json
// Wrong - should use previewAccountInfo for new accounts in preview
POST /v1/orders/preview
{
  "newAccount": {  // Wrong field name for preview!
    "name": "Example Corp",
    "currency": "USD"
  },
  "previewOptions": {...}
}
```

### ✅ Correct: Using previewAccountInfo in Preview Mode

```json
// Correct - previewAccountInfo for new accounts in preview mode
POST /v1/orders/preview
{
  "previewAccountInfo": {  // Correct for preview mode
    "name": "Example Corp",
    "currency": "USD",
    "billCycleDay": 1
  },
  "previewOptions": {
    "previewTypes": ["BillingDocs"]
  }
}
```

## Additional Resources

- [Order API Documentation](https://www.zuora.com/developer/api-references/api/tag/Orders)
- [Subscribe Action API Documentation](https://developer.zuora.com/v1-api-reference/older-api/actions/action_postsubscribe)
- [Payment Methods API](https://www.zuora.com/developer/api-references/api/tag/Payment-Methods)
- [Order API Best Practices](https://knowledgecenter.zuora.com/)

## Source Code Verification

This mapping has been verified against Zuora Billing source code meta classes:
- `com.zuora.api.action.SubscribeMeta` (Subscribe Action API)
- `com.zuora.rest.meta.order.PostOrderMeta` (Order API)
- `com.zuora.rest.meta.order.PostOrderActionCreateSubscriptionMeta` (CreateSubscription action)

SHA-256: 28a14d0ae558cfa21509fb075abf16dd8c21f79dfddad89bf594f82c1bac24b0