578 lines
14 KiB
Markdown
578 lines
14 KiB
Markdown
# 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](#authentication)
|
|
- [Rate Search API](#rate-search-api)
|
|
- [Bookings API](#bookings-api)
|
|
- [Error Handling](#error-handling)
|
|
- [Rate Limiting](#rate-limiting)
|
|
- [Webhooks](#webhooks)
|
|
|
|
---
|
|
|
|
## 🔐 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 user
|
|
- `POST /auth/login` - Login and receive tokens
|
|
- `POST /auth/refresh` - Refresh access token
|
|
- `POST /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 Container
|
|
- `20HC` - 20ft High Cube
|
|
- `40DRY` - 40ft Dry Container
|
|
- `40HC` - 40ft High Cube
|
|
- `40REEFER` - 40ft Refrigerated
|
|
- `45HC` - 45ft High Cube
|
|
|
|
**Request Example:**
|
|
```json
|
|
{
|
|
"origin": "NLRTM",
|
|
"destination": "CNSHA",
|
|
"containerType": "40HC",
|
|
"mode": "FCL",
|
|
"departureDate": "2025-02-15",
|
|
"quantity": 2,
|
|
"weight": 20000,
|
|
"isHazmat": false
|
|
}
|
|
```
|
|
|
|
**Response:** `200 OK`
|
|
|
|
```json
|
|
{
|
|
"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`
|
|
|
|
```json
|
|
{
|
|
"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: true` in 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:**
|
|
|
|
```json
|
|
{
|
|
"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`
|
|
|
|
```json
|
|
{
|
|
"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 prefix
|
|
- `YYYY` = Current year
|
|
- `XXXXXX` = 6 random alphanumeric characters (excludes ambiguous: 0, O, 1, I)
|
|
|
|
**Booking Statuses:**
|
|
- `draft` - Initial state, can be modified
|
|
- `pending_confirmation` - Submitted for carrier confirmation
|
|
- `confirmed` - Confirmed by carrier
|
|
- `in_transit` - Shipment in progress
|
|
- `delivered` - 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`
|
|
```json
|
|
{
|
|
"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`
|
|
|
|
```json
|
|
{
|
|
"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:
|
|
|
|
```json
|
|
{
|
|
"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
|
|
|
|
```json
|
|
{
|
|
"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
|
|
|
|
```json
|
|
{
|
|
"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):
|
|
|
|
```json
|
|
{
|
|
"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 carrier
|
|
- `booking.in_transit` - Shipment departed
|
|
- `booking.delivered` - Shipment delivered
|
|
- `booking.delayed` - Shipment delayed
|
|
- `booking.cancelled` - Booking cancelled
|
|
|
|
**Webhook Payload Example:**
|
|
```json
|
|
{
|
|
"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:
|
|
|
|
```javascript
|
|
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
|