# Legacy API Migration

Migrating from ShipStation Companion API (v1) to v2 requires understanding key differences in how orders, shipments, and identifiers work. This guide focuses on the practical migration steps and gotchas.

## Key ID Translations

### orderKey → external_shipment_id

The v1 `orderKey` field maps directly to v2's `external_shipment_id`:

| **v1 Field** | **v2 Field** | **Migration Notes** |
|  --- | --- | --- |
| `orderKey` | `external_shipment_id` | Same concept, just renamed |


**v1 Example:**


```json
{
  "orderKey": "MY-ORDER-123",
  "orderNumber": "ORD-123"
}
```

**v2 Equivalent:**


```json
{
  "external_shipment_id": "MY-ORDER-123",
  "shipment_number": "ORD-123"
}
```

### Internal ID Prefixes

All v2 internal IDs use the `se-` prefix:

| **v1 Field** | **v1 Value** | **v2 Field** | **v2 Value** |
|  --- | --- | --- | --- |
| `orderId` | `12345678` | `shipment_id` | `se-12345678` |
| `shipmentId` | `87654321` | `label_id` | `se-87654321` |


Always Prepend se-
When migrating ID references from v1 to v2, you must prepend `se-` to the numeric ID:

- v1 `orderId`: `12345678`
- v2 `shipment_id`: `se-12345678`


**JavaScript Example:**


```javascript
// Converting v1 orderId to v2 shipment_id
const v1OrderId = 12345678;
const v2ShipmentId = `se-${v1OrderId}`;  // "se-12345678"
```

## Status Value Mapping

v2 uses different status values than v1:

| **v1 orderStatus** | **v2 shipment_status** | **Notes** |
|  --- | --- | --- |
| `awaiting_payment` | `pending` | ⚠️ Many-to-one mapping |
| `awaiting_shipment` | `pending` | ⚠️ Many-to-one mapping |
| `on_hold` | `pending` | ⚠️ Many-to-one mapping |
| *(no v1 equivalent)* | `processing` | v2/ShipEngine only - transient status during async label creation |
| `shipped` | `label_purchased` | Different terminology |
| `cancelled` | `cancelled` | Same value |


Loss of Status Granularity
**Critical Issue:** Three distinct v1 statuses (`awaiting_payment`, `awaiting_shipment`, `on_hold`) all map to a single v2 status (`pending`).

This means you **cannot determine the specific v1 status** from a v2 `pending` status without storing additional metadata in your system.

**Workaround:** If you need to preserve v1 status granularity, store the original v1 status in custom metadata fields or your own database.

About the processing Status
The `processing` status is a **transient state** that only exists in v2/ShipEngine during async label creation. When you trigger label creation for a shipment, it briefly enters `processing` status before transitioning to `label_purchased`.

If you query a shipment and see `processing`, wait a few seconds and query again - it should have transitioned to its final state.

This status did not exist in v1.

## Legacy ShipStation UI v2 Behavior

If you're using the legacy ShipStation UI alongside the v2 API, there's an important behavior to understand.

### Shipments Appear as Orders in UI

When you create a shipment via the v2 API, **it will appear as an "order" in the ShipStation UI**, even if you do NOT pass `create_sales_order: true`.

**Why This Happens:**

The legacy ShipStation UI v2 system still uses the "order" terminology and treats API-created shipments as orders for display purposes. This is a backwards-compatibility feature.

**What You'll See:**


```
API: POST /v2/shipments
{
  "external_shipment_id": "ORDER-123",
  // create_sales_order not specified or false
  ...
}

ShipStation UI: Shows this as an "Order" with number "ORDER-123"
```

This is Expected Behavior
This is **not a bug**. The ShipStation UI v2 displays API-created shipments as orders to maintain consistency with how users expect the UI to work. The underlying data model is still a v2 shipment.

### The create_sales_order Flag

The `create_sales_order` flag has a specific purpose:


```json
{
  "external_shipment_id": "ORDER-123",
  "create_sales_order": true,  // Creates a sales order record
  ...
}
```

**When `create_sales_order` is true:**

- Creates both a shipment AND a separate sales order record in the UI system
- Populates `external_order_id` field automatically
- Links the shipment to the sales order


**When `create_sales_order` is false or omitted:**

- Creates only a shipment (which still appears as an "order" in UI)
- No separate sales order record is created
- `external_order_id` may be null or auto-populated depending on context


When to Use create_sales_order
Use `create_sales_order: true` when:

- You want to create a full sales order record in the ShipStation UI system
- You're integrating with sales channel imports
- You need the sales order/shipment separation in the UI


For most API-only integrations, you can omit this flag.

## Migration Checklist

### 1. Update Field Names


```javascript
// ❌ v1 field names
{
  orderKey: "ORDER-123",
  orderNumber: "ORD-123",
  orderDate: "2024-06-15T10:00:00Z"
}

// ✅ v2 field names
{
  external_shipment_id: "ORDER-123",
  shipment_number: "ORD-123",
  created_at: "2024-06-15T10:00:00Z"
}
```

### 2. Add se- Prefix to IDs


```javascript
// v1 to v2 ID conversion helper
function convertV1IdsToV2(v1Record) {
  return {
    shipment_id: `se-${v1Record.orderId}`,
    label_id: v1Record.shipmentId ? `se-${v1Record.shipmentId}` : null
  };
}
```

### 3. Handle Status Mapping


```javascript
// v1 to v2 status conversion
const statusMap = {
  'awaiting_payment': 'pending',
  'awaiting_shipment': 'pending',
  'on_hold': 'pending',
  'shipped': 'label_purchased',
  'cancelled': 'cancelled'
};

function convertStatus(v1Status) {
  return statusMap[v1Status] || 'pending';
}
```

### 4. Store Original v1 Statuses (if needed)


```json
{
  "external_shipment_id": "ORDER-123",
  "shipment_status": "pending",
  "metadata": {
    "legacy_v1_status": "awaiting_payment"  // Preserve original status
  }
}
```

### 5. Update API Endpoints

| **v1 Endpoint** | **v2 Endpoint** |
|  --- | --- |
| `GET /orders/{orderId}` | `GET /v2/shipments/{shipment_id}` |
| `GET /orders?orderNumber={num}` | `GET /v2/shipments?shipment_number={num}` |
| `GET /shipments/{shipmentId}` | `GET /v2/labels/{label_id}` |
| `POST /orders/createorder` | `POST /v2/shipments` |
| `POST /orders/createlabelfororder` | `POST /v2/labels/shipment/{shipment_id}` |


## Common Migration Pitfalls

### Pitfall 1: Forgetting se- Prefix


```javascript
// ❌ Wrong - Using numeric ID directly
const response = await fetch(`/v2/shipments/${12345678}`);

// ✅ Correct - Adding se- prefix
const response = await fetch(`/v2/shipments/se-${12345678}`);
```

### Pitfall 2: Assuming Status Equivalence


```javascript
// ❌ Wrong - Assuming you can reverse-map from v2 to v1
const v2Status = 'pending';
// Can't tell if this was awaiting_payment, awaiting_shipment, or on_hold!

// ✅ Correct - Store original status if you need it
const metadata = shipment.metadata?.legacy_v1_status;
```

### Pitfall 3: Not Understanding UI Behavior


```javascript
// This WILL show up in ShipStation UI as an "order"
// even though create_sales_order is false
await createShipment({
  external_shipment_id: "ORDER-123",
  create_sales_order: false  // Still appears in UI!
});
```

## Related Guides

- **[External Identifiers Guide](/orders/external-identifiers)** - Deep dive on `external_shipment_id` vs v1 `orderKey`
- **[Shipment Numbers & Uniqueness](/orders/shipment-numbers)** - Understanding `shipment_number` vs v1 `orderNumber`
- **[Understanding Orders & Shipments](/orders/understanding-orders-shipments)** - Core v2 concepts
- **[Products and Plans - Terminology Reference](/products-and-plans#api-version-terminology-reference)** - Complete field mapping table