Skip to main content

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:


At-a-Glance Comparison

Featurev1v3
Base path/integration-api/v1/orders/api/v3/orders
AuthenticationX-User-Token headerX-User-Token header (unchanged)
PaginationPage-based (page, last_page)Cursor-based (links.next, meta.has_more)
FilteringQuery string paramsStructured filter[field] syntax
SortingNot supportedsort parameter with direction
Response shapingFixed payloadinclude and fields parameters
Bulk operationsSequential requestsDedicated /orders/bulk endpoint
Date formatYYYY-MM-DDISO 8601 (YYYY-MM-DDTHH:mm:ss.000000Z)
External ID lookupid_type=external query paramSame (unchanged)
ID fieldsTransformed namesNo transformations (matches DB columns)

Breaking Changes

1. Endpoint Path Change

- 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):

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

v3 (Cursor-based):

{
"data": [...],
"links": {
"next": "https://yourstore.publica.la/api/v3/orders?cursor=eyJjcmVhdGVkX2F0...",
"prev": null
},
"meta": {
"has_more": true
}
}
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:

{
"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):

{
"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"
}
}
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 Fieldv3 FieldDescription
id (was uuid)id (int) + uuid (string)Both internal ID and UUID are now exposed
external_referenceexternal_idRenamed to match DB column
user.id (was external_id)user.id (int) + user.uuid + user.external_idAll user identifiers exposed
products[].id (request)products[].external_id (request)Request field renamed
products[].id (response)products[].id (int) + products[].external_idBoth identifiers in response
Product ID Breaking Change

In v3 requests, use products[].external_id instead of products[].id:

- "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:

{
"user": {
"id": "user-ext-123",
"email": "[email protected]"
}
}

v3 user object:

{
"user": {
"id": 67890,
"uuid": "a1b2c3d4-e5f6-...",
"external_id": "user-ext-123",
"email": "[email protected]"
}
}
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:

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

v3:

{
"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

7. Product Response Fields

v1:

{
"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:

{
"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
  • covercover_url
  • description, pages_quantity, file_type → not available (use Content API v3 for metadata)
  • New field: product_url
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:

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

v3:

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

The checkout object has been renamed and restructured:

  • checkoutpending_checkout
  • checkout.tokenpending_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
IncludeDescription
userUser information (id, uuid, external_id, email)
productsProduct details with URLs and pricing
pending_checkoutCheckout URL for pending sale orders
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][email protected] # 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:

Operationv1v3
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 CreateN/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):

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):

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):

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

After (v3):

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:

// 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

// 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:

{
"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

Featurev1v3 Alternative
total countAvailable in paginatorNot available; use streaming pagination
last_pageAvailable in paginatorNot available; use meta.has_more
description in productsDirect fieldUse Content API v3
pages_quantity in productsDirect fieldUse Content API v3
file_type in productsDirect fieldUse Content API v3
external_referenceField nameRenamed to external_id

Practical Examples

List Orders with Full Data (v3)

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)

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)

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": "[email protected]"
},
"products": [
{
"type": "content",
"external_id": "9781234567890"
}
]
}'

Get Order by External Reference (v3)

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 Fieldv3 Equivalent
Order iduuid
Order external_referenceexternal_id
User idexternal_id
Product idexternal_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


X

Graph View