v2 → v3 Migration Guide
BreakingStep-by-step guide for migrating from DUAL API v2 to v3. Covers authentication, endpoint renames, pagination, and error handling changes with before/after code examples.
Deprecation Timeline
v2 endpoints return 301 redirects to their v3 equivalents. This compatibility layer will be removed in June 2026. Migrate before then to avoid breaking changes.
Changes at a Glance
| Area | v2 | v3 |
|---|---|---|
| Base URL | https://api.dual.io | https://gateway-48587430648.europe-west6.run.app |
| Authentication | App-Id + App-Secret headers | Bearer JWT or API key |
| Error Format | {message: string} | {error: {code, message, details}, x_request_id} |
| Pagination | Offset-based (offset/limit) | Cursor-based (next) |
| User Endpoints | /user/* | /wallets/* |
| Digital Assets | /vatoms/* | /objects/* |
| Asset Actions | /vatoms/:id/actions | /ebus/execute |
1. Authentication
v2 used static App-Id and App-Secret headers. v3 supports API keys (sent via the x-api-key header for server-to-server) and JWT bearer tokens (sent via the Authorization header for user sessions).
curl https://api.dual.io/user/profile \ -H "App-Id: YOUR_APP_ID" \ -H "App-Secret: YOUR_APP_SECRET"
# Option 1: API key (recommended for server-to-server)
curl https://gateway-48587430648.europe-west6.run.app/wallets/me \
-H "x-api-key: sk_live_1a2b3c4d5e6f7g8h"
# Option 2: JWT bearer token (for user sessions)
# Step 1: Request OTP
curl -X POST https://gateway-48587430648.europe-west6.run.app/auth/otp \
-H "Content-Type: application/json" \
-d '{"email": "user@example.com"}'
# Step 2: Login with OTP
curl -X POST https://gateway-48587430648.europe-west6.run.app/auth/login \
-H "Content-Type: application/json" \
-d '{"email": "user@example.com", "otp": "123456"}'
# Use the JWT in subsequent requests
curl https://gateway-48587430648.europe-west6.run.app/wallets/me \
-H "Authorization: Bearer eyJhbGciOiJIUzI1..."2. Error Handling
v3 errors return a structured envelope with an error object containing a numeric code, human-readable message, and an optional details array — plus a request ID for support tracing.
// v2 error response
{
"message": "Not found"
}// v3 error response
{
"error": {
"code": 404,
"message": "Object obj_123 not found",
"details": [
{ "field": "objectId", "reason": "No object with this ID exists" }
]
},
"x_request_id": "req_abc123def456"
}3. Pagination
v3 uses cursor-based pagination for consistent results. Offset pagination is deprecated and will be removed in v4.
// v2: Offset pagination
GET /vatoms?offset=20&limit=10
// Response
{
"items": [...],
"total": 150,
"offset": 20,
"limit": 10
}// v3: Cursor pagination
GET /objects?limit=10&next=eyJpZCI6Im9ial8xMjMifQ
// Response
{
"items": [...],
"next": "eyJpZCI6Im9ial8xMzMifQ",
"has_more": true
}4. Endpoint Renames
| v2 Endpoint | → | v3 Endpoint |
|---|---|---|
GET /user/profile | → | GET /wallets/me |
POST /user/login | → | POST /auth/login (OTP flow) |
POST /user/register | → | POST /wallets/register |
GET /vatoms | → | GET /objects |
GET /vatoms/:id | → | GET /objects/:id |
POST /vatoms/:id/actions | → | POST /ebus/execute |
GET /templates | → | GET /templates |
POST /user/token/refresh | → | POST /auth/refresh-token |
GET /activity | → | GET /ebus/action-logs |
POST /vatoms/:id/transfer | → | POST /ebus/execute (transfer action) |
5. Automated Codemod
Run this bash script from your project root to automatically rename endpoints and update headers. Always review the diff before committing.
#!/bin/bash # Run from your project root find src -name "*.ts" -o -name "*.js" | xargs sed -i '' \ -e 's|api.dual.io|gateway-48587430648.europe-west6.run.app|g' \ -e 's|/user/profile|/wallets/me|g' \ -e 's|/user/login|/auth/login|g' \ -e 's|/user/register|/wallets/register|g' \ -e 's|/vatoms|/objects|g' \ -e 's|/ebus/actions|/ebus/execute|g' \ -e 's|App-Id: "|x-api-key: "|g' \ -e 's|App-Secret: "[^"]*"||g' \ -e 's|/activity|/ebus/action-logs|g' \ -e 's|/user/token/refresh|/auth/refresh-token|g' echo "Migration complete — review changes with: git diff"
Need help migrating? Open a support ticket or ask in the Discord.