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

# Orders API v1 → v3 Migration Guide

This document provides a comprehensive migration guide from Orders API v1 to v3, including breaking changes, schema differences, and practical migration steps.

## Overview

Orders API v3 introduces significant improvements in pagination, filtering, response shaping, and overall API consistency. While the core order types remain the same, the API structure has been modernized for better performance and developer experience.

**Reference Documentation:**

- [Orders API v3 Overview](./orders-v3-overview.md)

---

## At-a-Glance Comparison

| Feature                | v1                               | v3                                           |
| ---------------------- | -------------------------------- | -------------------------------------------- |
| **Base path**          | `/integration-api/v1/orders`     | `/api/v3/orders`                             |
| **Authentication**     | `X-User-Token` header            | `X-User-Token` header (unchanged)            |
| **Pagination**         | Page-based (`page`, `last_page`) | Cursor-based (`links.next`, `meta.has_more`) |
| **Filtering**          | Query string params              | Structured `filter[field]` syntax            |
| **Sorting**            | Not supported                    | `sort` parameter with direction              |
| **Response shaping**   | Fixed payload                    | `include` and `fields` parameters            |
| **Bulk operations**    | Sequential requests              | Dedicated `/orders/bulk` endpoint            |
| **Date format**        | `YYYY-MM-DD`                     | ISO 8601 (`YYYY-MM-DDTHH:mm:ss.000000Z`)     |
| **External ID lookup** | `id_type=external` query param   | Same (unchanged)                             |
| **ID fields**          | Transformed names                | No transformations (matches DB columns)      |

---

## Breaking Changes

### 1. Endpoint Path Change

```diff
- GET /integration-api/v1/orders
+ GET /api/v3/orders

- GET /integration-api/v1/orders/{id}
+ GET /api/v3/orders/{id}

- POST /integration-api/v1/orders
+ POST /api/v3/orders

- PUT /integration-api/v1/orders/{id}
+ PUT /api/v3/orders/{id}

- DELETE /integration-api/v1/orders/{id}
+ DELETE /api/v3/orders/{id}
```

### 2. Pagination Change

**v1 (Page-based):**

```json
{
  "data": {
    "paginator": {
      "current_page": 1,
      "data": [...],
      "last_page": 10,
      "per_page": 100,
      "total": 950
    }
  }
}
```

**v3 (Cursor-based):**

```json
{
  "data": [...],
  "links": {
    "next": "https://yourstore.publica.la/api/v3/orders?cursor=eyJjcmVhdGVkX2F0...",
    "prev": null
  },
  "meta": {
    "has_more": true
  }
}
```

:::warning Important
v3 does not provide `total` count or `last_page`. Use `meta.has_more` to detect end of results.
:::

### 3. Filter Syntax Change

**v1:**

```
GET /integration-api/v1/orders?status=approved&type=permission
```

**v3:**

```
GET /api/v3/orders?filter[status]=approved&filter[type]=permission
```

### 4. Response Structure Change

**v1:**

```json
{
  "data": {
    "id": "275ca6c4-a815-4f64-8198-759a296dd495",
    "external_reference": "ORDER-123",
    "type": "permission",
    "status": "approved",
    "created_at": "2025-12-03",
    "user": { ... },
    "products": [ ... ]
  }
}
```

**v3 (base response):**

```json
{
  "data": {
    "id": 12345,
    "uuid": "275ca6c4-a815-4f64-8198-759a296dd495",
    "external_id": "ORDER-123",
    "type": "permission",
    "status": "approved",
    "unit_price": 29.99,
    "currency_id": "USD",
    "created_at": "2025-12-03T22:43:44.000000Z",
    "updated_at": "2025-12-03T22:43:44.000000Z"
  }
}
```

:::note Response Shaping
In v3, `user` and `products` are not included by default. Use `include=user,products` to add them.
:::

### 5. Field Name Changes (No Transformations)

v3 uses field names that match the database columns directly, without transformations:

| v1 Field                    | v3 Field                                           | Description                               |
| --------------------------- | -------------------------------------------------- | ----------------------------------------- |
| `id` (was uuid)             | `id` (int) + `uuid` (string)                       | Both internal ID and UUID are now exposed |
| `external_reference`        | `external_id`                                      | Renamed to match DB column                |
| `user.id` (was external_id) | `user.id` (int) + `user.uuid` + `user.external_id` | All user identifiers exposed              |
| `products[].id` (request)   | `products[].external_id` (request)                 | **Request field renamed**                 |
| `products[].id` (response)  | `products[].id` (int) + `products[].external_id`   | Both identifiers in response              |

:::warning Product ID Breaking Change
In v3 **requests**, use `products[].external_id` instead of `products[].id`:

```diff
- "products": [{ "type": "content", "id": "ISBN-123" }]
+ "products": [{ "type": "content", "external_id": "ISBN-123" }]
```

In **responses**, both `id` (internal) and `external_id` are returned.
:::

**v1 user object:**

```json
{
  "user": {
    "id": "user-ext-123",
    "email": "customer@example.com"
  }
}
```

**v3 user object:**

```json
{
  "user": {
    "id": 67890,
    "uuid": "a1b2c3d4-e5f6-...",
    "external_id": "user-ext-123",
    "email": "customer@example.com"
  }
}
```

:::warning Breaking Change
If your integration uses the `id` field from v1, note that:

- v1 `id` (order) = v3 `uuid`
- v1 `user.id` = v3 `user.external_id`
- v1 `products[].id` = v3 `products[].external_id`
  :::

### 6. Date Format Change

**v1:**

```json
{
  "created_at": "2025-12-03",
  "expiration_date": "2026-12-31"
}
```

**v3:**

```json
{
  "created_at": "2025-12-03T22:43:44.000000Z",
  "updated_at": "2025-12-03T22:43:44.000000Z",
  "expiration_date": "2026-12-31"
}
```

- `created_at` and `updated_at` are now full ISO 8601 timestamps
- `expiration_date` remains in `YYYY-MM-DD` format
- `expiration_date` must be later than the current day in your store's timezone when you create an order. You cannot import a legacy record with an expiration date that already passed, so decide whether to drop the date or set a new one

### 7. Product Response Fields

**v1:**

```json
{
  "products": [
    {
      "id": "9781234567890",
      "type": "content",
      "name": "Book Title",
      "status": "approved",
      "cover": "https://cdn.publica.la/covers/book.jpg",
      "reader_url": "https://yourstore.publica.la/reader/book",
      "description": "Book description",
      "pages_quantity": 250,
      "file_type": "epub",
      "unit_price": 29.99,
      "currency_id": "USD"
    }
  ]
}
```

**v3:**

```json
{
  "products": [
    {
      "id": 111,
      "external_id": "9781234567890",
      "type": "content",
      "name": "Book Title",
      "status": "approved",
      "expiration_date": null,
      "unit_price": 29.99,
      "currency_id": "USD",
      "cover_url": "https://cdn.publica.la/covers/book.jpg",
      "reader_url": "https://yourstore.publica.la/reader/book",
      "product_url": "https://yourstore.publica.la/library/publication/book"
    }
  ]
}
```

**Key field changes:**

- `id` now returns the internal numeric ID; use `external_id` for the ISBN/external reference
- `cover` → `cover_url`
- `description`, `pages_quantity`, `file_type` → not available (use Content API v3 for metadata)
- New field: `product_url`

:::tip Content API Integration
The `products[].id` in v3 can be used directly with Content API v3:

```
GET /api/v3/content/111
```

Or use `external_id` with a filter:

```
GET /api/v3/content?filter[external_id]=9781234567890
```

:::

### 8. Checkout URL for Sale Orders

**v1:**

```json
{
  "data": {
    "checkout": {
      "token": "https://yourstore.publica.la/auth/token?external-auth-token=eyJ0eXAi...",
      "ttl": 3600
    }
  }
}
```

**v3:**

```json
{
  "data": {
    "pending_checkout": {
      "url": "https://yourstore.publica.la/auth/token?external-auth-token=eyJ0eXAi...",
      "ttl": 3600
    }
  }
}
```

:::warning Field Renamed
The checkout object has been renamed and restructured:

- `checkout` → `pending_checkout`
- `checkout.token` → `pending_checkout.url`
- In v3, requires `include=pending_checkout` to be returned
  :::

---

## New Features in v3

### 1. Response Shaping with `include`

Request only the data you need:

```
GET /api/v3/orders?include=user,products
```

| Include            | Description                                     |
| ------------------ | ----------------------------------------------- |
| `user`             | User information (id, uuid, external_id, email) |
| `products`         | Product details with URLs and pricing           |
| `pending_checkout` | Checkout URL for pending sale orders            |

:::note Pricing
`unit_price` and `currency_id` are included in the base order response and in each product.
:::

### 2. Sparse Fieldsets with `fields`

Reduce payload size by selecting specific fields:

```
GET /api/v3/orders?fields=id,status,type
```

### 3. Advanced Filtering

**Filter by date ranges:**

```
GET /api/v3/orders?filter[created_at][from]=2025-01-01 00:00:00&filter[created_at][to]=2025-12-31 23:59:59
```

**Filter by user:**

```
GET /api/v3/orders?filter[user_id]=12345                        # By internal ID
GET /api/v3/orders?filter[user_external_id]=user-123            # By external ID
GET /api/v3/orders?filter[user_email]=customer@example.com      # By email
```

**Filter by product:**

```
GET /api/v3/orders?filter[product_id]=67890                     # By internal ID
GET /api/v3/orders?filter[product_external_id]=9781234567890    # By external ID (ISBN, SKU)
GET /api/v3/orders?filter[product_type]=content                 # By type
```

### 4. Sorting

```
GET /api/v3/orders?sort=-created_at    # Newest first (default)
GET /api/v3/orders?sort=created_at     # Oldest first
GET /api/v3/orders?sort=-updated_at    # Recently updated first
```

### 5. Bulk Operations

Create up to 100 orders in a single request:

```
POST /api/v3/orders/bulk
{
  "orders": [
    { "type": "permission", ... },
    { "type": "permission", ... }
  ]
}
```

---

## Migration Checklist

### 1. Update Endpoints

Replace all v1 endpoint paths with v3 paths:

| Operation   | v1                                | v3                    |
| ----------- | --------------------------------- | --------------------- |
| List        | `/integration-api/v1/orders`      | `/api/v3/orders`      |
| Get         | `/integration-api/v1/orders/{id}` | `/api/v3/orders/{id}` |
| Create      | `/integration-api/v1/orders`      | `/api/v3/orders`      |
| Bulk Create | N/A                               | `/api/v3/orders/bulk` |
| Update      | `/integration-api/v1/orders/{id}` | `/api/v3/orders/{id}` |
| Delete      | `/integration-api/v1/orders/{id}` | `/api/v3/orders/{id}` |

### 2. Update Pagination Logic

**Before (v1):**

```javascript
let page = 1;
let hasMore = true;

while (hasMore) {
  const response = await fetch(`/integration-api/v1/orders?page=${page}`);
  const data = await response.json();

  processOrders(data.data.paginator.data);

  hasMore = page < data.data.paginator.last_page;
  page++;
}
```

**After (v3):**

```javascript
let cursor = null;
let hasMore = true;

while (hasMore) {
  const url = cursor ? `/api/v3/orders?cursor=${cursor}` : '/api/v3/orders';

  const response = await fetch(url);
  const data = await response.json();

  processOrders(data.data);

  hasMore = data.meta.has_more;
  cursor = data.links.next
    ? new URL(data.links.next).searchParams.get('cursor')
    : null;
}
```

### 3. Update Filter Syntax

**Before (v1):**

```javascript
const params = new URLSearchParams({
  status: 'approved',
  type: 'permission',
});
```

**After (v3):**

```javascript
const params = new URLSearchParams({
  'filter[status]': 'approved',
  'filter[type]': 'permission',
});
```

### 4. Add Includes for Full Data

If you need user and product data, add includes:

**Before (v1):**

```
GET /integration-api/v1/orders
```

**After (v3):**

```
GET /api/v3/orders?include=user,products
```

Note: `unit_price` and `currency_id` are always included in the base response.

### 5. Update Field Mappings

Update your code to handle renamed and new fields:

```javascript
// v1 → v3 order field mapping
const mapOrder = (v1Order, v3Order) => ({
  // v1 'id' (uuid string) is now v3 'uuid'
  id: v3Order.uuid, // or use v3Order.id for numeric ID
  external_reference: v3Order.external_id, // renamed from external_reference
  // ... other fields
});

// v1 → v3 user field mapping
const mapUser = (v1User, v3User) => ({
  // v1 'id' (external_id string) is now v3 'external_id'
  id: v3User.external_id, // or use v3User.id for numeric ID
  email: v3User.email,
  // New fields in v3:
  // uuid: v3User.uuid
});

// v1 → v3 product field mapping
const mapProduct = (v1Product, v3Product) => ({
  // v1 'id' (external_id string) is now v3 'external_id'
  id: v3Product.external_id, // or use v3Product.id for numeric ID
  type: v3Product.type,
  name: v3Product.name,
  status: v3Product.status,
  expiration_date: v3Product.expiration_date,
  cover: v3Product.cover_url, // renamed from cover
  reader_url: v3Product.reader_url,
  unit_price: v3Product.unit_price,
  currency_id: v3Product.currency_id,
  // New field in v3:
  // product_url: v3Product.product_url
});
```

### 6. Update Date Parsing

```javascript
// v1 dates
const v1Date = '2025-12-03';
const parsed = new Date(v1Date);

// v3 dates (ISO 8601)
const v3Date = '2025-12-03T22:43:44.000000Z';
const parsed = new Date(v3Date); // Works the same, but includes time
```

### 7. Test Error Handling

v3 returns more detailed validation errors:

```json
{
  "message": "The given data was invalid.",
  "errors": {
    "filter.status": [
      "Invalid status. Allowed: pending, approved, paused, cancelled"
    ],
    "include": [
      "Invalid include: invalid. Allowed: user, products, pending_checkout"
    ]
  }
}
```

---

## Removed Features

| Feature                      | v1                     | v3 Alternative                          |
| ---------------------------- | ---------------------- | --------------------------------------- |
| `total` count                | Available in paginator | Not available; use streaming pagination |
| `last_page`                  | Available in paginator | Not available; use `meta.has_more`      |
| `description` in products    | Direct field           | Use Content API v3                      |
| `pages_quantity` in products | Direct field           | Use Content API v3                      |
| `file_type` in products      | Direct field           | Use Content API v3                      |
| `external_reference`         | Field name             | Renamed to `external_id`                |

---

## Practical Examples

### List Orders with Full Data (v3)

```bash
curl -X GET "https://yourstore.publica.la/api/v3/orders?include=user,products&per_page=100" \
  -H "X-User-Token: your-api-token"
```

### Incremental Sync by Update Date (v3)

```bash
curl -X GET "https://yourstore.publica.la/api/v3/orders?\
filter[updated_at][from]=2025-12-01 00:00:00&\
filter[updated_at][to]=2025-12-03 23:59:59&\
sort=-updated_at&\
include=user,products" \
  -H "X-User-Token: your-api-token"
```

### Create Order (v3)

```bash
curl -X POST "https://yourstore.publica.la/api/v3/orders" \
  -H "X-User-Token: your-api-token" \
  -H "Content-Type: application/json" \
  -d '{
    "type": "permission",
    "external_reference": "ORDER-123",
    "user": {
      "id": "user-123",
      "email": "customer@example.com"
    },
    "products": [
      {
        "type": "content",
        "external_id": "9781234567890"
      }
    ]
  }'
```

### Get Order by External Reference (v3)

```bash
curl -X GET "https://yourstore.publica.la/api/v3/orders/ORDER-123?id_type=external&include=user,products" \
  -H "X-User-Token: your-api-token"
```

---

## Troubleshooting FAQ

### Why is my response missing user/products data?

In v3, `user` and `products` are not included by default. Add `include=user,products` to your request.

### How do I get the total count of orders?

v3 uses cursor pagination without total counts. For counting, make a separate count query or track counts in your application.

### Why am I getting 422 errors?

Check:

- Filter syntax: `filter[field]=value`, not `field=value`
- Date format: `YYYY-MM-DD HH:mm:ss` for date ranges
- Include values: only `user`, `products`, `pending_checkout` are valid

### How do I migrate bulk imports?

Use the new `/api/v3/orders/bulk` endpoint instead of sequential requests. It's faster and supports up to 100 orders per request.

### Where is the product description?

Product metadata (description, page count, file type) is not included in the Orders API v3 response. Use the Content API v3 to get full product details:

```
GET /api/v3/content/{products[].id}
```

### What happened to `external_reference`?

The field has been renamed to `external_id` to match the database column name. Update your code to use `external_id` instead.

### How do I map v1 IDs to v3?

| v1 Field                   | v3 Equivalent |
| -------------------------- | ------------- |
| Order `id`                 | `uuid`        |
| Order `external_reference` | `external_id` |
| User `id`                  | `external_id` |
| Product `id`               | `external_id` |

---

## Parallel Running Strategy

For a safe migration:

1. **Feature flag**: Route traffic to v1 or v3 based on a feature flag
2. **Dual writes**: If creating orders, consider writing to both APIs initially
3. **Compare responses**: Log and compare v1 vs v3 responses for the same queries
4. **Gradual rollout**: Start with read-only operations, then move to writes
5. **Monitor**: Track error rates, latencies, and payload sizes

---

## References

- [Orders API v3 Overview](./orders-v3-overview.md)
- [List Orders (v3)](./orders-v3-list.md)
- [Get Order (v3)](./orders-v3-get.md)
- [Permission Orders (v3)](./create-order/orders-v3-permission.md)
- [Report Orders (v3)](./create-order/orders-v3-report.md)
- [Sale Orders (v3)](./create-order/orders-v3-sale.md)
- [Bulk Operations (v3)](./create-order/orders-v3-bulk.md)
