# Transporter APIs — Fleet Driver (`TRANSPORTER_DRIVER`)

This document is for **fleet drivers**: users with role `TRANSPORTER`, profile **`account_type=TRANSPORTER_DRIVER`**, and an **active** link to a fleet owner (`TransporterDriverLink`).

Fleet drivers execute trips where **`Trip.assigned_driver`** is their user id. **`Trip.transporter`** remains the fleet owner.

Also covers shared **`/api/auth/`**, **`/api/chats/`**, and **`/api/auth/fcm-token/`**.

---

## Who qualifies

| Requirement | Details |
|-------------|---------|
| Role | `TRANSPORTER` |
| Account type | `TRANSPORTER_DRIVER` (preferred) or legacy `DRIVER` with active fleet link |
| Fleet link | `TransporterDriverLink` with `is_active=true` for a fleet owner |
| Traccar | `tc_id` / `tc_u_id` set when the fleet owner creates the driver (Traccar device auto-provisioned) |

**Login:** `persona_type` = `TRANSPORTER_FLEET_DRIVER`, `account_type` = `TRANSPORTER_DRIVER`.

---

## How accounts are created (not self-register)

Fleet drivers are created by the **fleet owner**, not via public registration.

### Fleet owner: `POST /api/transporter/drivers/`

See **`docs/TRANSPORTER_FLEET_OWNER_API.md`**.

The server:

1. Creates user + `TRANSPORTER` role  
2. Sets `account_type=TRANSPORTER_DRIVER`  
3. Creates Traccar device (`tc_id`, `tc_u_id`)  
4. Creates `TransporterDriverLink` to the fleet owner  
5. Optionally saves `avatar_url` when `avatar` is sent  
6. Optionally upserts KYC **`documents`** (same types/expiry as individual drivers)

Optional write-only **`avatar`**: base64 image (optional `data:image/...;base64,` prefix). The backend decodes it, stores the file under `media/avatars/user/`, and persists the absolute URL as **`avatar_url`** on the driver’s transporter profile. Same field is accepted on **`PATCH /api/transporter/drivers/{id}/`**. Responses include `avatar_url`.

Optional **`documents`** array on create/patch (and dedicated **`GET/POST /api/transporter/drivers/{id}/documents/`**):

| `document_type` | Sides | Notes |
|-----------------|-------|-------|
| `DRIVER_LICENSE` | `file` + `file_back` | Required on first upload |
| `COUNTRY_GCC` | `file` + `file_back` | Required on first upload; also send `gcc_country` |
| `PASSPORT_COPY` | `file` only | Requires `passport_number` |
| `PERMIT` | `file` only | If applicable |
| `NOC` | `file` only | Optional |

`VEHICLE_REGISTRATION` is **not** accepted (vehicle docs use `NOC` on the vehicle, not driver KYC).

Each document may include optional **`expiry_date`** (`YYYY-MM-DD`). Re-posting the same `document_type` upserts that row (files and/or expiry). Driver responses include a **`documents`** list with `file_url`, `file_back_url`, `expiry_date`, etc.

`POST /api/transporter/register/` with `account_type=DRIVER` is for **individual drivers**, not fleet drivers. Individual register/profile update also accept optional `avatar` the same way.

---

## Standard response envelope

```json
{
  "status_code": 200,
  "message": "Human-readable message.",
  "error": null,
  "data": {}
}
```

HTTP status matches `status_code`. Validation failures usually set `error` and `data: null`.

---

## Authentication

Header: `Authorization: Bearer <token>`

### POST `/api/auth/login/`

**Auth:** none

**Request (JSON):**

```json
{
  "email": "fleet-driver@example.com",
  "password": "your-password"
}
```

**Response `200` — `data`:**

```json
{
  "token": "9944b09199c62bcf9418ad846dd0e4bbdfc6ee4b",
  "user_id": 88,
  "email": "fleet-driver@example.com",
  "first_name": "Driver",
  "last_name": "One",
  "role": "TRANSPORTER",
  "persona_type": "TRANSPORTER_FLEET_DRIVER",
  "account_type": "TRANSPORTER_DRIVER",
  "tc_id": "444",
  "tc_u_id": "XY99ZZ11"
}
```

### POST `/api/auth/logout/`

**Auth:** authenticated  

**Request:** empty body.  

**Response `200`:** success message; `data` often `null`.

### POST `/api/auth/forgot-password/`

Request a 6-digit password reset verification code sent to the driver's registered email.

**Aliases:** `POST /api/auth/password/forgot/`, `POST /api/transporter/forgot-password/`  
**Auth:** none (public)

**Request (JSON):**

```json
{
  "email": "driver@example.com"
}
```

**Response `200` — `data`:**

```json
{
  "email": "driver@example.com"
}
```

### POST `/api/auth/verify-otp/`

Verify the 6-digit OTP code before setting a new password.

**Aliases:** `POST /api/auth/password/verify-otp/`  
**Auth:** none (public)

**Request (JSON):**

```json
{
  "email": "driver@example.com",
  "otp": "492018"
}
```

**Response `200` — `data`:**

```json
{
  "email": "driver@example.com",
  "reset_token": "random_secure_token_string",
  "verified": true
}
```

### POST `/api/auth/reset-password/`

Set a new password using the verified `otp` or `reset_token`.

**Aliases:** `POST /api/auth/password/reset/`, `POST /api/transporter/reset-password/`  
**Auth:** none (public)

**Request (JSON):**

```json
{
  "email": "driver@example.com",
  "otp": "492018",
  "new_password": "NewSecurePassword123"
}
```

**Response `200` — `data`:**

```json
{
  "token": "new-auth-token",
  "user_id": 14,
  "email": "driver@example.com",
  "role": "TRANSPORTER",
  "persona_type": "TRANSPORTER_FLEET_DRIVER",
  "account_type": "TRANSPORTER_DRIVER"
}
```

### PATCH `/api/auth/fcm-token/`

**Auth:** authenticated  

**Request (JSON):**

```json
{
  "fcm_token": "<fcm-registration-token>"
}
```

**Response `200` — `data`:**

```json
{
  "fcm_token_updated": true
}
```

### GET `/api/auth/notifications/`

**Auth:** authenticated  

**Query:** `user_id` (required), `offset` (optional, default `0`)

**Response `200` — `data`:** paginated notification list (`results`, `total`, `offset`, `limit`).

---

## Profile & documents

**Auth:** `IsTransporter` (no `documents_verified` required for profile/KYC upload).

### GET `/api/transporter/profile/`

**Response `200` — `data`:**

```json
{
  "user_id": 88,
  "email": "fleet-driver@example.com",
  "first_name": "Driver",
  "last_name": "One",
  "phone": "+923001234567",
  "language": "en",
  "account_type": "TRANSPORTER_DRIVER",
  "company_name": "",
  "documents_verified": false,
  "tc_id": "444",
  "tc_u_id": "XY99ZZ11",
  "created_at": "2026-04-01T10:00:00Z",
  "updated_at": "2026-04-20T08:00:00Z"
}
```

### PATCH `/api/transporter/profile/`

**Request (JSON, partial):** `first_name`, `last_name`, `phone`, `language`, `company_name`

**Response `200`:** same shape as GET.

### GET `/api/transporter/documents/`

**Response `200` — `data`:** array of KYC documents (`id`, `document_type`, `file`, `file_url`, `file_back`, `file_back_url`, `verified`, `expiry_date`, `submitted_at`, `reviewed_at`).

### POST `/api/transporter/documents/`

**Request (JSON):** `document_type`, `file` (front / sole file, base64), `file_back` (required on first upload for `DRIVER_LICENSE`, `COUNTRY_GCC`), optional `expiry_date`

**Types:** `DRIVER_LICENSE`, `PASSPORT_COPY`, `PERMIT`, `COUNTRY_GCC`, optional `NOC` (`VEHICLE_REGISTRATION` and `INSURANCE` removed).

**Response `201`:** single document object.

---

## Vehicles

### GET `/api/transporter/vehicles/assigned/` (primary for fleet drivers)

Returns vehicles the fleet owner assigned to this driver (`Vehicle.assigned_driver = request.user`).

**Auth:** transporter  

**Response `200` — `data`:**

```json
{
  "count": 1,
  "vehicles": [
    {
      "id": 5,
      "owner_id": 50,
      "vehicle_type": "Flatbed",
      "registration_number": "REG-500",
      "load_capacity": "4000.00",
      "max_length_m": "12.000",
      "max_width_m": null,
      "max_height_m": null,
      "special_features": "",
      "is_active": true,
      "is_verified": true,
      "assigned_driver_id": 88,
      "assigned_driver_email": "fleet-driver@example.com",
      "verified_at": "2026-04-10T09:00:00Z",
      "created_at": "2026-04-01T10:00:00Z",
      "updated_at": "2026-04-20T08:00:00Z"
    }
  ]
}
```

If none assigned: `{ "count": 0, "vehicles": [] }`.

### Owned vehicles (optional)

Fleet drivers may also own vehicles (`owner=request.user`):

| Method | Path |
|--------|------|
| GET | `/api/transporter/vehicles/` |
| POST | `/api/transporter/vehicles/` |
| GET | `/api/transporter/vehicles/{vehicle_id}/` |
| PATCH | `/api/transporter/vehicles/{vehicle_id}/` |
| GET/POST | `/api/transporter/vehicles/{vehicle_id}/documents/` |

New owned vehicles are `is_active=false`, `is_verified=false` until admin verifies.

---

## Load discovery & bidding (optional)

Requires **`documents_verified=true`** and **`tc_id`** on profile. Most fleet workflows assign loads at the owner; drivers execute assigned trips.

| Method | Path | Notes |
|--------|------|--------|
| GET | `/api/transporter/load-discovery/` | GPS from driver's Traccar `tc_id`; requires `country_code` |
| GET | `/api/transporter/available-shipments/` | Published loads by transporter load type only (local vs local+C2C); no country/radius filters |
| POST | `/api/transporter/shipments/{shipment_id}/bid/` | Submit bid |
| GET | `/api/transporter/shipments/{shipment_id}/bids/` | Bid history for one shipment |

See **`docs/TRANSPORTER_INDIVIDUAL_DRIVER_API.md`** for request/response shapes.

---

## Fleet driver — trip buckets (primary)

**Auth:** `IsFleetDriverTransporter` — active fleet link + `account_type` is `TRANSPORTER_DRIVER` or legacy `DRIVER`.

**Does not require** `documents_verified`.

Returns **full trip detail** (`TransporterTripDetailSerializer`) per item: nested `shipment`, `accepted_bid`, `shipper`, `assigned_driver`, `status_timeline`, `tc_id`/`tc_u_id` (fleet owner's Traccar ids on trip).

### GET `/api/transporter/driver/trips/assigned/`

`assigned_driver = request.user`, `status = ASSIGNED`.

### GET `/api/transporter/driver/trips/active/`

`assigned_driver = request.user`, status in:  
`EN_ROUTE`, `ARRIVED_PICKUP`, `LOADED`, `IN_TRANSIT`, `ARRIVED_DELIVERY`, `DELIVERED`.

### GET `/api/transporter/driver/trips/completed/`

`assigned_driver = request.user`, `status = COMPLETED`.

**Response `200` — `data`:** array of full trip objects (example abbreviated):

```json
[
  {
    "id": 12,
    "shipment": {
      "id": 200,
      "pickup_address": "Depot A",
      "pickup_lat": "25.2048000",
      "pickup_lon": "55.2708000",
      "delivery_address": "Site B",
      "delivery_lat": "24.4539000",
      "delivery_lon": "54.3773000",
      "cargo_type": "Pallets",
      "weight": "2 tons",
      "vehicle_type_required": "Flatbed",
      "suggested_price": "800.00",
      "status": "ASSIGNED"
    },
    "accepted_bid": {
      "id": 70,
      "amount": "750.00",
      "status": "ACCEPTED"
    },
    "transporter": 50,
    "status": "ASSIGNED",
    "shipper_rating": false,
    "status_timeline": [
      { "status": "ASSIGNED", "recorded_at": "2026-04-28T08:00:00Z" }
    ],
    "shipper": {
      "id": 3,
      "email": "shipper@example.com",
      "first_name": "Sara",
      "last_name": "Khan",
      "phone": "+923001111111"
    },
    "assigned_driver": {
      "id": 88,
      "email": "fleet-driver@example.com",
      "first_name": "Driver",
      "last_name": "One",
      "account_type": "TRANSPORTER_DRIVER"
    },
    "tc_id": "333",
    "tc_u_id": "FL00WN01",
    "current_lat": null,
    "current_lon": null,
    "eta": null,
    "created_at": "2026-04-28T08:00:00Z",
    "updated_at": "2026-04-28T08:00:00Z"
  }
]
```

`tc_id` / `tc_u_id` on trip objects refer to the **fleet owner's** Traccar device (trip owner), not the driver's device.

---

## Trip detail & execution

Caller must be **`Trip.transporter`** OR **`Trip.assigned_driver`**. Fleet drivers use the **assigned_driver** path.

**Auth:** `IsTransporter` only (assigned trips work without `documents_verified`).

| Method | Path | Purpose |
|--------|------|---------|
| GET | `/api/transporter/trips/{trip_id}/` | Full trip detail |
| GET | `/api/transporter/trips/{trip_id}/navigation/` | Pickup/delivery + maps hints |
| POST | `/api/transporter/trips/{trip_id}/location/` | Push GPS point |
| PATCH | `/api/transporter/trips/{trip_id}/status/` | Advance trip status |
| POST | `/api/transporter/trips/{trip_id}/pod/` | Proof of delivery |

### PATCH `/api/transporter/trips/{trip_id}/status/`

**Request (JSON):**

```json
{
  "status": "EN_ROUTE",
  "lat": 25.2048,
  "lon": 55.2708,
  "recorded_at": "2026-04-28T10:00:00Z"
}
```

**Response `200` — `data`:** trip summary (`TripListSerializer`: `id`, `shipment_id`, `status`, `status_timeline`, `assigned_driver_id`, `shipper_rating`, `current_lat`, `current_lon`, …).

### POST `/api/transporter/trips/{trip_id}/pod/`

**Request:** `multipart/form-data` — see **`docs/TRANSPORTER_INDIVIDUAL_DRIVER_API.md`** for fields (`receiver_name`, `photos`, `delivery_lat`, …).

---

## Chats

Fleet drivers can use the same trip chat as the shipper and fleet owner **after** the owner assigns them on the trip (`Trip.assigned_driver`).

The conversation remains shipper + fleet owner (`transporter_id` = owner). The assigned driver is a third participant derived from the trip link — no second conversation is created.

| Method | Path | Notes |
|--------|------|-------|
| GET | `/api/chats/` | Includes chats where `trip.assigned_driver = me` |
| POST | `/api/chats/open/` | Allowed with `shipment_id` + owner `transporter_id` when assigned |
| GET | `/api/chats/{conversation_id}/messages/` | Allowed when assigned |
| POST | `/api/chats/{conversation_id}/messages/` | Allowed when assigned; notifies shipper + owner |

**Note:** `POST /api/chats/{conversation_id}/accept-bid/` is **shipper-only**.

---

## Billing & earnings (driver wallet)

Operates on the **authenticated fleet driver's** wallet (not the fleet owner's).

| Method | Path |
|--------|------|
| GET | `/api/transporter/wallet/` |
| GET | `/api/transporter/ledger/` |
| GET | `/api/transporter/earnings/` |
| GET | `/api/transporter/earnings/by-trip/` |
| GET/POST | `/api/transporter/withdrawals/` |

---

## Ratings, analytics, compliance

**Auth:** `IsTransporter` (metrics for the logged-in user).

| Method | Path |
|--------|------|
| POST | `/api/transporter/trips/{trip_id}/shipper-review/` |
| GET | `/api/transporter/ratings/summary/` |
| GET | `/api/transporter/ratings/received/` |
| GET | `/api/transporter/analytics/` |
| GET | `/api/transporter/compliance/summary/` |

`POST /api/transporter/trips/{trip_id}/shipper-review/` submits one one-time shipper review for a `DELIVERED`, `COMPLETED`, or `CLOSED` trip. Body: `shipper_rating` (1–5) and optional `comment`. The authenticated fleet driver must be assigned to the trip. Trip payloads include `shipper_rating: false` until this review exists, then `shipper_rating: true`.

---

## Not available to fleet drivers

| Route | Reason |
|-------|--------|
| `POST /api/transporter/register/` with `TRANSPORTER_DRIVER` | Use fleet-owner `POST /api/transporter/drivers/` |
| `POST /api/transporter/trips/{trip_id}/assign-driver/` | Fleet owner only |
| `GET /api/transporter/trips/my/`, `/assigned/`, `/active/`, `/completed/` | Filter `Trip.transporter` = owner, not driver buckets |
| `GET /api/transporter/trips/{trip_id}/payment-summary/` | `Trip.transporter` only → **404** for assigned driver |
| `POST /api/transporter/trips/{trip_id}/confirm-cash/` | Fleet owner only |
| `POST /api/transporter/drivers/` | Fleet owner only |

See **`docs/TRANSPORTER_FLEET_OWNER_API.md`**.

---

## API access summary (`TRANSPORTER_DRIVER`)

| Area | `documents_verified` required? |
|------|------------------------------|
| Login, profile, documents, wallet | No |
| `/api/transporter/driver/trips/*` | No (requires active fleet link) |
| Trip detail, navigation, location, status, POD | No (must be `assigned_driver` or owner) |
| Load discovery, bidding | **Yes** (+ `tc_id`) |
| Fleet-owner trip buckets | No access (wrong persona) |

---

## Reference

- Fleet owner creates drivers: **`docs/TRANSPORTER_FLEET_OWNER_API.md`**  
- Individual driver (self-register `DRIVER`): **`docs/TRANSPORTER_INDIVIDUAL_DRIVER_API.md`**  
- Master index: **`API_DOCS.md`**
