# 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
