14 KiB
Xpeditis API Documentation
Complete API reference for the Xpeditis maritime freight booking platform.
Base URL: https://api.xpeditis.com (Production) | http://localhost:4000 (Development)
API Version: v1
Last Updated: February 2025
📑 Table of Contents
🔐 Authentication
Status: To be implemented in Phase 2
The API will use OAuth2 + JWT for authentication:
- Access tokens valid for 15 minutes
- Refresh tokens valid for 7 days
- All endpoints (except auth) require
Authorization: Bearer {token}header
Planned Endpoints:
POST /auth/register- Register new userPOST /auth/login- Login and receive tokensPOST /auth/refresh- Refresh access tokenPOST /auth/logout- Invalidate tokens
🔍 Rate Search API
Search Shipping Rates
Search for available shipping rates from multiple carriers.
Endpoint: POST /api/v1/rates/search
Authentication: Required (Phase 2)
Request Headers:
Content-Type: application/json
Request Body:
| Field | Type | Required | Description | Example |
|---|---|---|---|---|
origin |
string | ✅ | Origin port code (UN/LOCODE, 5 chars) | "NLRTM" |
destination |
string | ✅ | Destination port code (UN/LOCODE, 5 chars) | "CNSHA" |
containerType |
string | ✅ | Container type | "40HC" |
mode |
string | ✅ | Shipping mode | "FCL" or "LCL" |
departureDate |
string | ✅ | ISO 8601 date | "2025-02-15" |
quantity |
number | ❌ | Number of containers (default: 1) | 2 |
weight |
number | ❌ | Total cargo weight in kg | 20000 |
volume |
number | ❌ | Total cargo volume in m³ | 50.5 |
isHazmat |
boolean | ❌ | Is hazardous material (default: false) | false |
imoClass |
string | ❌ | IMO hazmat class (required if isHazmat=true) | "3" |
Container Types:
20DRY- 20ft Dry Container20HC- 20ft High Cube40DRY- 40ft Dry Container40HC- 40ft High Cube40REEFER- 40ft Refrigerated45HC- 45ft High Cube
Request Example:
{
"origin": "NLRTM",
"destination": "CNSHA",
"containerType": "40HC",
"mode": "FCL",
"departureDate": "2025-02-15",
"quantity": 2,
"weight": 20000,
"isHazmat": false
}
Response: 200 OK
{
"quotes": [
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"carrierId": "550e8400-e29b-41d4-a716-446655440001",
"carrierName": "Maersk Line",
"carrierCode": "MAERSK",
"origin": {
"code": "NLRTM",
"name": "Rotterdam",
"country": "Netherlands"
},
"destination": {
"code": "CNSHA",
"name": "Shanghai",
"country": "China"
},
"pricing": {
"baseFreight": 1500.0,
"surcharges": [
{
"type": "BAF",
"description": "Bunker Adjustment Factor",
"amount": 150.0,
"currency": "USD"
},
{
"type": "CAF",
"description": "Currency Adjustment Factor",
"amount": 50.0,
"currency": "USD"
}
],
"totalAmount": 1700.0,
"currency": "USD"
},
"containerType": "40HC",
"mode": "FCL",
"etd": "2025-02-15T10:00:00Z",
"eta": "2025-03-17T14:00:00Z",
"transitDays": 30,
"route": [
{
"portCode": "NLRTM",
"portName": "Port of Rotterdam",
"departure": "2025-02-15T10:00:00Z",
"vesselName": "MAERSK ESSEX",
"voyageNumber": "025W"
},
{
"portCode": "CNSHA",
"portName": "Port of Shanghai",
"arrival": "2025-03-17T14:00:00Z"
}
],
"availability": 85,
"frequency": "Weekly",
"vesselType": "Container Ship",
"co2EmissionsKg": 12500.5,
"validUntil": "2025-02-15T10:15:00Z",
"createdAt": "2025-02-15T10:00:00Z"
}
],
"count": 5,
"origin": "NLRTM",
"destination": "CNSHA",
"departureDate": "2025-02-15",
"containerType": "40HC",
"mode": "FCL",
"fromCache": false,
"responseTimeMs": 234
}
Validation Errors: 400 Bad Request
{
"statusCode": 400,
"message": [
"Origin must be a valid 5-character UN/LOCODE (e.g., NLRTM)",
"Departure date must be a valid ISO 8601 date string"
],
"error": "Bad Request"
}
Caching:
- Results are cached for 15 minutes
- Cache key format:
rates:{origin}:{destination}:{date}:{containerType}:{mode} - Cache hit indicated by
fromCache: truein response - Top 100 trade lanes pre-cached on application startup
Performance:
- Target: <2 seconds (90% of requests with cache)
- Cache hit: <100ms
- Carrier API timeout: 5 seconds per carrier
- Circuit breaker activates after 50% error rate
📦 Bookings API
Create Booking
Create a new booking based on a rate quote.
Endpoint: POST /api/v1/bookings
Authentication: Required (Phase 2)
Request Headers:
Content-Type: application/json
Request Body:
{
"rateQuoteId": "550e8400-e29b-41d4-a716-446655440000",
"shipper": {
"name": "Acme Corporation",
"address": {
"street": "123 Main Street",
"city": "Rotterdam",
"postalCode": "3000 AB",
"country": "NL"
},
"contactName": "John Doe",
"contactEmail": "john.doe@acme.com",
"contactPhone": "+31612345678"
},
"consignee": {
"name": "Shanghai Imports Ltd",
"address": {
"street": "456 Trade Avenue",
"city": "Shanghai",
"postalCode": "200000",
"country": "CN"
},
"contactName": "Jane Smith",
"contactEmail": "jane.smith@shanghai-imports.cn",
"contactPhone": "+8613812345678"
},
"cargoDescription": "Electronics and consumer goods for retail distribution",
"containers": [
{
"type": "40HC",
"containerNumber": "ABCU1234567",
"vgm": 22000,
"sealNumber": "SEAL123456"
}
],
"specialInstructions": "Please handle with care. Delivery before 5 PM."
}
Field Validations:
| Field | Validation | Error Message |
|---|---|---|
rateQuoteId |
Valid UUID v4 | "Rate quote ID must be a valid UUID" |
shipper.name |
Min 2 characters | "Name must be at least 2 characters" |
shipper.contactEmail |
Valid email | "Contact email must be a valid email address" |
shipper.contactPhone |
E.164 format | "Contact phone must be a valid international phone number" |
shipper.address.country |
ISO 3166-1 alpha-2 | "Country must be a valid 2-letter ISO country code" |
cargoDescription |
Min 10 characters | "Cargo description must be at least 10 characters" |
containers[].containerNumber |
4 letters + 7 digits (optional) | "Container number must be 4 letters followed by 7 digits" |
Response: 201 Created
{
"id": "550e8400-e29b-41d4-a716-446655440001",
"bookingNumber": "WCM-2025-ABC123",
"status": "draft",
"shipper": { ... },
"consignee": { ... },
"cargoDescription": "Electronics and consumer goods for retail distribution",
"containers": [
{
"id": "550e8400-e29b-41d4-a716-446655440002",
"type": "40HC",
"containerNumber": "ABCU1234567",
"vgm": 22000,
"sealNumber": "SEAL123456"
}
],
"specialInstructions": "Please handle with care. Delivery before 5 PM.",
"rateQuote": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"carrierName": "Maersk Line",
"carrierCode": "MAERSK",
"origin": { ... },
"destination": { ... },
"pricing": { ... },
"containerType": "40HC",
"mode": "FCL",
"etd": "2025-02-15T10:00:00Z",
"eta": "2025-03-17T14:00:00Z",
"transitDays": 30
},
"createdAt": "2025-02-15T10:00:00Z",
"updatedAt": "2025-02-15T10:00:00Z"
}
Booking Number Format:
- Pattern:
WCM-YYYY-XXXXXX - Example:
WCM-2025-ABC123 WCM= WebCargo Maritime prefixYYYY= Current yearXXXXXX= 6 random alphanumeric characters (excludes ambiguous: 0, O, 1, I)
Booking Statuses:
draft- Initial state, can be modifiedpending_confirmation- Submitted for carrier confirmationconfirmed- Confirmed by carrierin_transit- Shipment in progressdelivered- Shipment delivered (final)cancelled- Booking cancelled (final)
Get Booking by ID
Endpoint: GET /api/v1/bookings/:id
Path Parameters:
id(UUID) - Booking ID
Response: 200 OK
Returns same structure as Create Booking response.
Error: 404 Not Found
{
"statusCode": 404,
"message": "Booking 550e8400-e29b-41d4-a716-446655440001 not found",
"error": "Not Found"
}
Get Booking by Number
Endpoint: GET /api/v1/bookings/number/:bookingNumber
Path Parameters:
bookingNumber(string) - Booking number (e.g.,WCM-2025-ABC123)
Response: 200 OK
Returns same structure as Create Booking response.
List Bookings
Endpoint: GET /api/v1/bookings
Query Parameters:
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
page |
number | ❌ | 1 | Page number (1-based) |
pageSize |
number | ❌ | 20 | Items per page (max: 100) |
status |
string | ❌ | - | Filter by status |
Example: GET /api/v1/bookings?page=2&pageSize=10&status=draft
Response: 200 OK
{
"bookings": [
{
"id": "550e8400-e29b-41d4-a716-446655440001",
"bookingNumber": "WCM-2025-ABC123",
"status": "draft",
"shipperName": "Acme Corporation",
"consigneeName": "Shanghai Imports Ltd",
"originPort": "NLRTM",
"destinationPort": "CNSHA",
"carrierName": "Maersk Line",
"etd": "2025-02-15T10:00:00Z",
"eta": "2025-03-17T14:00:00Z",
"totalAmount": 1700.0,
"currency": "USD",
"createdAt": "2025-02-15T10:00:00Z"
}
],
"total": 25,
"page": 2,
"pageSize": 10,
"totalPages": 3
}
❌ Error Handling
Error Response Format
All errors follow this structure:
{
"statusCode": 400,
"message": "Error description or array of validation errors",
"error": "Bad Request"
}
HTTP Status Codes
| Code | Description | When Used |
|---|---|---|
200 |
OK | Successful GET request |
201 |
Created | Successful POST (resource created) |
400 |
Bad Request | Validation errors, malformed request |
401 |
Unauthorized | Missing or invalid authentication |
403 |
Forbidden | Insufficient permissions |
404 |
Not Found | Resource doesn't exist |
429 |
Too Many Requests | Rate limit exceeded |
500 |
Internal Server Error | Unexpected server error |
503 |
Service Unavailable | Carrier API down, circuit breaker open |
Validation Errors
{
"statusCode": 400,
"message": [
"Origin must be a valid 5-character UN/LOCODE (e.g., NLRTM)",
"Container type must be one of: 20DRY, 20HC, 40DRY, 40HC, 40REEFER, 45HC",
"Quantity must be at least 1"
],
"error": "Bad Request"
}
Rate Limit Error
{
"statusCode": 429,
"message": "Too many requests. Please try again in 60 seconds.",
"error": "Too Many Requests",
"retryAfter": 60
}
Circuit Breaker Error
When a carrier API is unavailable (circuit breaker open):
{
"statusCode": 503,
"message": "Maersk API is temporarily unavailable. Please try again later.",
"error": "Service Unavailable",
"retryAfter": 30
}
⚡ Rate Limiting
Status: To be implemented in Phase 2
Planned Limits:
- 100 requests per minute per API key
- 1000 requests per hour per API key
- Rate search: 20 requests per minute (resource-intensive)
Headers:
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 95
X-RateLimit-Reset: 1612345678
🔔 Webhooks
Status: To be implemented in Phase 3
Planned webhook events:
booking.confirmed- Booking confirmed by carrierbooking.in_transit- Shipment departedbooking.delivered- Shipment deliveredbooking.delayed- Shipment delayedbooking.cancelled- Booking cancelled
Webhook Payload Example:
{
"event": "booking.confirmed",
"timestamp": "2025-02-15T10:30:00Z",
"data": {
"bookingId": "550e8400-e29b-41d4-a716-446655440001",
"bookingNumber": "WCM-2025-ABC123",
"status": "confirmed",
"confirmedAt": "2025-02-15T10:30:00Z"
}
}
📊 Best Practices
Pagination
Always use pagination for list endpoints to avoid performance issues:
GET /api/v1/bookings?page=1&pageSize=20
Date Formats
All dates use ISO 8601 format:
- Request:
"2025-02-15"(date only) - Response:
"2025-02-15T10:00:00Z"(with timezone)
Port Codes
Use UN/LOCODE (5-character codes):
- Rotterdam:
NLRTM - Shanghai:
CNSHA - Los Angeles:
USLAX - Hamburg:
DEHAM
Find port codes: https://unece.org/trade/cefact/unlocode-code-list-country-and-territory
Error Handling
Always check statusCode and handle errors gracefully:
try {
const response = await fetch('/api/v1/rates/search', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(searchParams)
});
if (!response.ok) {
const error = await response.json();
console.error('API Error:', error.message);
return;
}
const data = await response.json();
// Process data
} catch (error) {
console.error('Network Error:', error);
}
📞 Support
For API support:
- Email: api-support@xpeditis.com
- Documentation: https://docs.xpeditis.com
- Status Page: https://status.xpeditis.com
API Version: v1.0.0 Last Updated: February 2025 Changelog: See CHANGELOG.md