# Transporter Management API Documentation

**Version:** 1.0.0  
**Base URL:** `https://your-domain.com/api/v1`  
**Content-Type:** `application/json`

---

## Authentication

All protected endpoints require a **Bearer token** in the `Authorization` header:

```
Authorization: Bearer <access_token>
```

### Obtaining a Token

1. **Register** (`POST /auth/register`) or **Login** (`POST /auth/login`)
2. Use the `access_token` from the response
3. Refresh before expiry using `POST /auth/refresh-token` with `refresh_token`

### Token Response

```json
{
  "access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9...",
  "refresh_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9...",
  "expires_in": 1800,
  "user_id": 1,
  "role": "passenger"
}
```

---

## Common Data Types

### Location (pickup/dropoff)

```json
{
  "latitude": 0.0,
  "longitude": 0.0,
  "address": "Optional human-readable address",
  "place_id": "Optional map provider place ID"
}
```

- `latitude`: -90 to 90
- `longitude`: -180 to 180

### Paginated Response

```json
{
  "items": [],
  "total": 0,
  "page": 1,
  "limit": 20,
  "pages": 1,
  "has_next": false,
  "has_prev": false
}
```

- `page`: 1-based (min: 1)
- `limit`: 1–100 per page

---

## Error Codes

| Code | Error Type | Meaning |
|------|------------|---------|
| 400 | BadRequest | Invalid input, validation failed, or business rule violation |
| 401 | Unauthorized | Missing or invalid token, expired token |
| 403 | Forbidden | Valid token but insufficient permissions |
| 404 | NotFound | Resource not found |
| 422 | ValidationError | Request body/query validation failed |
| 500 | InternalServerError | Server error |

### Error Response Format

```json
{
  "error": "ValidationError",
  "message": "Invalid input data",
  "details": [
    {
      "field": "email",
      "message": "value is not a valid email address",
      "type": "value_error.email"
    }
  ],
  "status_code": 422
}
```

---

# Module: Auth

## POST /auth/register

Complete signup after OTP (`verify-otp`). **Auth:** Bearer (access token from `verify-otp`).

**Request Body:**
```json
{
  "email": "user@example.com",
  "phone": "+1234567890",
  "first_name": "John",
  "last_name": "Doe",
  "role": "passenger",
  "fcm_token": "optional_fcm_token",
  "profile_picture_base64": "iVBORw0KGgo... or data:image/jpeg;base64,..."
}
```

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| email | string | | Optional profile email |
| phone | string | | Must match verified phone if sent |
| first_name | string | | Max 100 chars |
| last_name | string | | Max 100 chars |
| role | string | | `passenger` or `driver` (default: passenger) |
| fcm_token | string | | FCM token for push |
| profile_picture_base64 | string | ✓ | Base64 or data URL; JPEG, PNG, or WebP (max 5MB decoded). Stored under `/uploads/profiles/...`. |

**Response:** `201` `TokenResponse`

---

## POST /auth/login

Login with email or phone (no password). The account must already be OTP-verified (`/auth/verify-otp`).

**Auth:** None

**Request Body:**
```json
{
  "email": "user@example.com",
  "phone": "+1234567890",
  "fcm_token": "optional_fcm_token"
}
```

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| email | string | * | Email (required if no phone) |
| phone | string | * | E.164 format |
| fcm_token | string | | FCM token |

**Response:** `200` `TokenResponse`

---

## POST /auth/send-otp

Send OTP to phone or email.

**Auth:** None

**Request Body:**
```json
{
  "phone": "+1234567890",
  "email": "user@example.com",
  "purpose": "verification"
}
```

**Response:** `200` `{"message": "OTP sent successfully"}`

---

## POST /auth/verify-otp

Verify OTP code.

**Auth:** None

**Request Body:**
```json
{
  "phone": "+1234567890",
  "email": "user@example.com",
  "otp": "123456"
}
```

**Response:** `200` `UserResponse`

---

## POST /auth/refresh-token

Refresh access token.

**Auth:** None

**Request Body:**
```json
{
  "refresh_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9..."
}
```

**Response:** `200` `TokenResponse`

---

## POST /auth/social-login

Social OAuth login (Google, Facebook, Apple).

**Auth:** None

**Request Body:**
```json
{
  "provider": "google",
  "access_token": "oauth_access_token",
  "id_token": "optional_for_apple",
  "device_token": "optional_fcm"
}
```

**Response:** `501` Not implemented

---

## POST /auth/logout

Logout and invalidate refresh token.

**Auth:** Bearer ✓

**Request Body:** None

**Response:** `200` `{"message": "Logged out successfully"}`

---

## DELETE /auth/account

Delete user account.

**Auth:** Bearer ✓

**Request Body:**
```json
{
  "confirm_delete": true
}
```

**Response:** `200` `{"message": "Account deleted successfully"}`

---

## GET /auth/me

Get current user profile.

**Auth:** Bearer ✓

**Response:** `200` `UserResponse`

```json
{
  "id": 1,
  "email": "user@example.com",
  "phone": "+1234567890",
  "first_name": "John",
  "last_name": "Doe",
  "full_name": "John Doe",
  "profile_picture": null,
  "role": "passenger",
  "is_active": true,
  "is_verified": false,
  "is_email_verified": false,
  "is_phone_verified": false,
  "wallet_balance": 0,
  "driver_status": null,
  "is_online": null,
  "average_rating": null,
  "created_at": "2025-01-01T00:00:00Z"
}
```

---

## PUT /auth/profile

Update current user profile. **profile_picture_base64** is required on every update.

**Auth:** Bearer ✓

**Request Body:**
```json
{
  "first_name": "John",
  "last_name": "Doe",
  "email": "new@example.com",
  "profile_picture_base64": "iVBORw0KGgo... or data:image/png;base64,..."
}
```

Responses return **profile_picture** as a URL path such as `/uploads/profiles/{user_id}/{file}.png` (same host as the API).

---

# Module: Passenger

**Auth:** Bearer ✓ (role: passenger)

## GET /passenger/profile

Get passenger profile.

**Response:** `200` `UserResponse`

---

## PUT /passenger/profile

Update passenger profile. Body matches `UserProfileUpdate`: optional name, address, gender, **fcm_token**, and required **profile_picture_base64** (same rules as `PUT /auth/profile`).

---

## GET /passenger/rides

Get ride history (paginated).

**Query Params:**

| Param | Type | Default | Description |
|-------|------|---------|-------------|
| status_filter | string | | `requested`, `searching`, `bidding`, `accepted`, `driver_arrived`, `started`, `completed`, `cancelled` |
| page | int | 1 | Page number |
| limit | int | 20 | Items per page (1–100) |
| sort_by | string | created_at | Sort field |
| order | string | desc | `asc` or `desc` |

**Response:** `200` `PaginatedResponse[RideListResponse]`

---

## GET /passenger/rides/active

Get current active ride (if any).

**Response:** `200` `{"active_ride": 123, "ride_code": "ABC123", "status": "accepted"}` or `{"active_ride": null}`

---

# Module: Driver

**Auth:** Bearer ✓ (role: driver)

## GET /driver/profile

Get driver profile with vehicle and documents.

**Response:** `200` `DriverResponse`

```json
{
  "id": 1,
  "user_id": 1,
  "full_name": "Driver Name",
  "email": "driver@example.com",
  "phone": "+1234567890",
  "profile_picture": null,
  "license_number": "DL123",
  "license_expiry": "2026-12-31",
  "status": "approved",
  "is_online": false,
  "is_on_ride": false,
  "current_latitude": 28.6,
  "current_longitude": 77.2,
  "average_rating": 5.0,
  "total_ratings": 0,
  "total_rides": 0,
  "completed_rides": 0,
  "total_earnings": 0,
  "current_balance": 0,
  "current_vehicle": {...},
  "documents": [],
  "created_at": "2025-01-01T00:00:00Z"
}
```

---

## PUT /driver/profile

Update driver profile.

**Request Body:**
```json
{
  "license_number": "DL123",
  "license_expiry": "2026-12-31",
  "license_state": "CA",
  "bank_name": "Bank",
  "bank_account_number": "****1234",
  "bank_routing_number": "123",
  "bank_account_holder": "Name"
}
```

---

## GET /driver/account-settings

Get driver account settings.

**Response:** `200` `{"id": 1, "driver_id": 1, "settings": {}}`

---

## PUT /driver/account-settings

Update account settings.

**Request Body:** `{"settings": {"key": "value"}}`

---

## PUT /driver/location

Update driver location.

**Request Body:**
```json
{
  "latitude": 28.6139,
  "longitude": 77.2090,
  "heading": 45.0
}
```

- `heading`: 0–360 degrees (optional)

**Response:** `200` `{"message": "Location updated"}`

---

## PUT /driver/status

Set online/offline status.

**Request Body:**
```json
{
  "is_online": true
}
```

**Response:** `200` `{"message": "Status updated to online"}`

---

## POST /driver/documents

Upload one or more driver documents in a single multipart request.

**Content-Type:** `multipart/form-data`

**File Fields:**
- `profile_photo`
- `drivers_license`
- `cnic`
- `vehicle_document`

**Optional Text Fields:**
- `license_number`
- `license_expiry`
- `cnic_number`
- `vehicle_document_number`
- `vehicle_document_expiry`

**Example multipart fields:**
```text
profile_photo: <file>
drivers_license: <file>
cnic: <file>
vehicle_document: <file>
license_number: DL123
license_expiry: 2026-12-31
```

---

## GET /driver/earnings

Get driver earnings summary.

**Response:** `200` `DriverEarningsResponse`

```json
{
  "total_earnings": 0,
  "current_balance": 0,
  "pending_withdrawals": 0,
  "today_earnings": 0,
  "this_week_earnings": 0,
  "this_month_earnings": 0,
  "completed_rides_today": 0,
  "completed_rides_this_week": 0,
  "completed_rides_this_month": 0
}
```

---

## GET /driver/rides/nearby

Get nearby ride requests for bidding.

**Query Params:** `limit` (1–100, default 20)

**Response:** `200` `{"rides": [{"id": 1, "ride_code": "ABC", "pickup_address": "...", "dropoff_address": "...", "estimated_fare": 1000, "estimated_distance_km": 5.2, "passenger_count": 1, "vehicle_category_id": 1}]}`

---

## POST /driver/rides/{ride_id}/bid

Submit fare bid for a ride.

**Request Body:**
```json
{
  "bid_amount": 1500,
  "message": "I can arrive in 5 mins",
  "estimated_arrival_minutes": 5
}
```

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| bid_amount | int | ✓ | Amount in cents |
| message | string | | Max 255 chars |
| estimated_arrival_minutes | int | | 1–60 |

**Response:** `200` `FareBidResponse`

---

## GET /driver/rides/active

Get driver's current active ride.

**Response:** `200` `{"active_ride": 1, "ride_code": "ABC", "status": "started"}` or `{"active_ride": null}`

---

# Module: Offer Profiles

**Auth:** Bearer ✓ (role: driver)

## GET /driver/offer-profiles

List offer profiles (paginated).

**Query Params:** `page`, `limit`, `sort_by`, `order`

**Response:** `200` `PaginatedResponse[OfferProfileResponse]`

---

## PUT /driver/offer-profiles/priority

Update order of offer profiles.

**Request Body:**
```json
{
  "profile_ids": [3, 1, 2]
}
```

---

## POST /driver/offer-profiles

Create offer profile.

**Request Body:**
```json
{
  "name": "Airport rides",
  "enabled": true,
  "confirm_offer_manually": true,
  "price_type": "price_per_km",
  "price_per_km": 100,
  "fixed_price": null,
  "distance_min_km": 5,
  "distance_max_km": 50,
  "for_all_vehicles": true
}
```

**price_type:** `price_per_km`, `fixed_price`

**Response:** `201` `OfferProfileResponse`

---

## GET /driver/offer-profiles/{profile_id}

Get offer profile by ID.

**Response:** `200` `OfferProfileResponse`

---

## PUT /driver/offer-profiles/{profile_id}

Update offer profile.

**Request Body:** Partial `OfferProfileUpdate`

---

## DELETE /driver/offer-profiles/{profile_id}

Delete offer profile.

**Response:** `200` `{"message": "Offer profile deleted successfully"}`

---

# Module: Rides

## GET /rides/categories

Get vehicle categories (no auth).

**Response:** `200` `[VehicleCategoryResponse]`

```json
[
  {
    "id": 1,
    "name": "economy",
    "display_name": "Economy",
    "description": "Standard sedan",
    "icon_url": null,
    "max_passengers": 4,
    "base_fare": 5000,
    "per_km_rate": 1500,
    "per_minute_rate": 200,
    "minimum_fare": 5000
  }
]
```

---

## POST /rides/estimate

Get fare estimate.

**Auth:** Bearer ✓

**Request Body:**
```json
{
  "pickup": {"latitude": 28.6, "longitude": 77.2},
  "dropoff": {"latitude": 28.7, "longitude": 77.1},
  "ride_type": "one_way",
  "vehicle_category_id": 1,
  "passenger_count": 1,
  "promo_code": "SAVE10",
  "scheduled_at": null
}
```

**ride_type:** `one_way`, `round_trip`, `scheduled`

**Response:** `200` `FareEstimateResponse`

```json
{
  "vehicle_category_id": 1,
  "vehicle_category_name": "Economy",
  "estimated_distance_km": 10.5,
  "estimated_duration_minutes": 25,
  "base_fare": 5000,
  "distance_fare": 15750,
  "time_fare": 5000,
  "surge_multiplier": 1.0,
  "promo_discount": 0,
  "estimated_fare": 25750,
  "currency": "USD"
}
```

---

## POST /rides/book

Book a ride.

**Auth:** Bearer ✓ (role: passenger)

**Request Body:**
```json
{
  "pickup": {"latitude": 28.6, "longitude": 77.2, "address": "123 Main St"},
  "dropoff": {"latitude": 28.7, "longitude": 77.1, "address": "456 Oak Ave"},
  "return_location": null,
  "ride_type": "one_way",
  "vehicle_category_id": 1,
  "payment_method": "wallet",
  "passenger_count": 1,
  "promo_code": null,
  "is_scheduled": false,
  "scheduled_at": null,
  "passenger_notes": "Please ring doorbell"
}
```

**payment_method:** `cash`, `wallet`, `card`

**Response:** `201` `RideResponse`

---

## GET /rides/{ride_id}

Get ride details.

**Auth:** Bearer ✓ (passenger or driver of ride)

**Response:** `200` `RideResponse`

---

## GET /rides/{ride_id}/bids

Get ride bids (passenger only).

**Auth:** Bearer ✓ (role: passenger)

**Query Params:** `page`, `limit`, `sort_by`, `order`

**Response:** `200` `PaginatedResponse[FareBidResponse]`

---

## POST /rides/{ride_id}/bids/{bid_id}/accept

Accept a bid (passenger only).

**Auth:** Bearer ✓ (role: passenger)

**Request Body:** None

**Response:** `200` `RideResponse`

---

## PUT /rides/{ride_id}/status

Update ride status (driver actions).

**Auth:** Bearer ✓ (driver of ride)

**Request Body:**
```json
{
  "status": "driver_arrived",
  "driver_notes": null
}
```

**status:** `driver_arrived`, `started`, `completed`

---

## POST /rides/{ride_id}/cancel

Cancel ride.

**Auth:** Bearer ✓ (passenger or driver of ride)

**Request Body:**
```json
{
  "reason": "passenger_changed_mind",
  "note": "Optional note"
}
```

**reason:** `passenger_changed_mind`, `driver_too_far`, `driver_not_responding`, `wrong_address`, `vehicle_issue`, `emergency`, `found_another_ride`, `driver_cancelled`, `passenger_no_show`, `other`

**Response:** `200` `{"message": "Ride cancelled successfully"}`

---

# Module: Payments

## GET /payments/wallet

Get wallet balance.

**Auth:** Bearer ✓

**Response:** `200` `WalletResponse`

```json
{
  "id": 1,
  "user_id": 1,
  "balance": 20000,
  "pending_balance": 0,
  "available_balance": 20000,
  "lifetime_credits": 50000,
  "lifetime_debits": 30000,
  "currency": "USD",
  "is_active": true
}
```

---

## POST /payments/wallet/topup

Add funds to wallet.

**Auth:** Bearer ✓

**Request Body:**
```json
{
  "amount": 1000,
  "payment_method": "card",
  "payment_token": "optional_gateway_token"
}
```

**amount:** In cents (min 1)

**payment_method:** `cash`, `wallet`, `card`, `upi`, `net_banking`

**Response:** `200` `TransactionResponse`

---

## GET /payments/transactions

Get transaction history.

**Auth:** Bearer ✓

**Query Params:** `transaction_type`, `page`, `limit`, `sort_by`, `order`

**Response:** `200` `PaginatedResponse[TransactionResponse]`

---

## POST /payments/withdraw

Request withdrawal (drivers only).

**Auth:** Bearer ✓ (role: driver)

**Request Body:**
```json
{
  "amount": 5000
}
```

**Response:** `200` `WithdrawalResponse`

---

## GET /payments/withdrawals

Get withdrawal history (drivers only).

**Auth:** Bearer ✓ (role: driver)

**Query Params:** `page`, `limit`, `sort_by`, `order`

**Response:** `200` `PaginatedResponse[WithdrawalResponse]`

---

# Module: Admin

**Auth:** Bearer ✓ (role: admin or sub_admin)

## GET /admin/dashboard

Get admin dashboard stats.

**Response:** `200` `AdminDashboardResponse`

---

## GET /admin/users

List users (paginated).

**Query Params:** `page`, `limit`, `sort_by`, `order`, `search`, `role`, `status`

**Response:** `200` `PaginatedResponse[UserListResponse]`

---

## GET /admin/users/{user_id}

Get user by ID.

**Response:** `200` `UserResponse`

---

## PUT /admin/users/{user_id}/block

Block or unblock user.

**Request Body:**
```json
{
  "is_blocked": true,
  "reason": "Violation of terms"
}
```

**Response:** `200` `{"message": "User blocked successfully"}`

---

## GET /admin/drivers/pending

Get pending driver verifications.

**Query Params:** `page`, `limit`, `sort_by`, `order`

**Response:** `200` `PaginatedResponse[DriverResponse]`

---

## PUT /admin/drivers/{driver_id}/verify

Verify driver.

**Request Body:**
```json
{
  "status": "approved",
  "rejection_reason": null
}
```

**status:** `approved`, `rejected`

**Response:** `200` `{"message": "approved"}`

---

## GET /admin/withdrawals/pending

Get pending withdrawal requests.

**Query Params:** `page`, `limit`, `sort_by`, `order`

**Response:** `200` `PaginatedResponse[WithdrawalResponse]`

---

## PUT /admin/withdrawals/{withdrawal_id}/process

Process withdrawal.

**Query Params:**

| Param | Type | Description |
|-------|------|-------------|
| approve | bool | true = approve, false = reject |
| rejection_reason | string | Required if approve=false |

**Response:** `200` `{"message": "Withdrawal approved"}`

---

## POST /admin/promo-codes

Create promo code.

**Request Body:**
```json
{
  "code": "SAVE20",
  "title": "20% off",
  "description": "First ride discount",
  "promo_type": "percentage",
  "discount_value": 20,
  "min_order_value": 1000,
  "max_discount": 5000,
  "total_usage_limit": 100,
  "per_user_limit": 1,
  "valid_from": "2025-01-01T00:00:00Z",
  "valid_until": "2025-12-31T23:59:59Z",
  "for_new_users_only": false,
  "is_active": true
}
```

**promo_type:** `percentage`, `fixed`, `free_ride`

**Response:** `200` `PromoCodeResponse`

---

## GET /admin/promo-codes

List promo codes.

**Query Params:** `active_only`, `page`, `limit`, `sort_by`, `order`

**Response:** `200` `PaginatedResponse[PromoCodeResponse]`

---

## GET /admin/rides/live

Get live/active rides.

**Query Params:** `page`, `limit`, `sort_by`, `order`

**Response:** `200` `PaginatedResponse[RideListResponse]`

---

## Dashboard Endpoints

**Base:** `/admin/dashboard`  
**Auth:** Bearer ✓ (admin)

| Method | Endpoint | Description |
|--------|----------|-------------|
| GET | /stats | Dashboard stats |
| GET | /revenue?days=30 | Revenue chart |
| GET | /map | Active ride locations |
| GET | /drivers?limit=10 | Driver performance |
| GET | /passengers | Passenger list (paginated) |
| GET | /driver-verifications | Driver verifications (paginated) |
| GET | /rides/stats | Ride statistics |
| GET | /rides | Ride list (paginated) |
| GET | /transactions | Transaction list (paginated) |
| GET | /reports/user-growth | User growth chart |
| POST | /driver-verifications/{id}/approve | Approve driver |
| POST | /driver-verifications/{id}/reject | Reject driver |

---

# Module: Config

**Auth:** Bearer ✓ (admin)

## GET /config/settings

Get system settings.

**Query Params:** `category` (optional)

**Response:** `200` `[SystemConfigResponse]`

---

## PUT /config/settings/{key}

Update system setting.

**Request Body:** `{"value": "new_value"}`

---

## GET /config/map-providers

Get map provider configs.

**Response:** `200` `[MapProviderConfigResponse]`

---

## GET /config/map-providers/active

Get active map provider.

**Response:** `200` `MapProviderConfigResponse` or `404`

---

## PUT /config/map-providers/{provider_name}

Update map provider.

**provider_name:** `mapbox`, `google_maps`, `openstreetmap`

**Request Body:**
```json
{
  "api_key": "xxx",
  "api_secret": "xxx",
  "api_url": null,
  "is_active": true,
  "priority": 1,
  "settings": {}
}
```

---

## POST /config/map-providers/activate

Activate map provider.

**Request Body:** `{"provider_name": "mapbox"}`

---

## POST /config/map-providers/test

Test map provider.

**Request Body:**
```json
{
  "provider_name": "mapbox",
  "test_address": "New York, NY"
}
```

---

# Android Integration Notes

1. **Retrofit/OkHttp:** Use `Interceptor` to add `Authorization: Bearer <token>` to all requests.
2. **Token storage:** Store `access_token` and `refresh_token` securely (e.g. EncryptedSharedPreferences).
3. **Token refresh:** On 401, call `/auth/refresh-token` and retry with new token.
4. **Amounts:** All monetary values are in **cents** (e.g. 1000 = $10.00).
5. **Dates:** Use ISO 8601 strings: `2025-01-01T00:00:00Z`.
6. **Pagination:** Use `page` and `limit` (max 100). Check `has_next` for more pages.
7. **Location:** Use `latitude` and `longitude`; `address` is optional for display.
