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
| 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
- 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
}
}
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"
}
}
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 |
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",
}
}
v3 user object:
{
"user": {
"id": 67890,
"uuid": "a1b2c3d4-e5f6-...",
"external_id": "user-ext-123",
}
}
If your integration uses the id field from v1, note that:
- v1
id(order) = v3uuid - v1
user.id= v3user.external_id - v1
products[].id= v3products[].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_atandupdated_atare now full ISO 8601 timestampsexpiration_dateremains inYYYY-MM-DDformatexpiration_datemust 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:
{
"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:
idnow returns the internal numeric ID; useexternal_idfor the ISBN/external referencecover→cover_urldescription,pages_quantity,file_type→ not available (use Content API v3 for metadata)- New field:
product_url
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