# BizzCab User App API

Base URL: `http://127.0.0.1:5001/api`

Auth: `Authorization: Bearer <user JWT>` except public routes.

Envelope:

```json
{ "success": true, "message": "…", "data": {} }
```

Errors use the same shape with `success: false` and `data: null`:

```json
{ "success": false, "message": "Ride not found", "data": null }
```

Postman: import [BizzCab Local](postman/BizzCab.postman_environment.json), then [User App](postman/BizzCab-User-App.postman_collection.json). Run **Send OTP** then **Verify OTP** — the tests save `otp`, `userToken`, and later `rideId` / `addressId` / `ticketId`. Regenerate with `npm run postman` in `Backend`. Shared rules are in [api.md](api.md).

Screen names are the exported frames in `Bizz Cab/`. The Figma file is [Bizz Cab](https://www.figma.com/proto/w1HxDtqWcQhslSP0rpTfZ2/Bizz-Cab?node-id=15-2874&starting-point-node-id=8%3A18&page-id=7%3A2) (page `7:2`, start frame `8:18`). The connected Figma account could not list child node ids (editor access was not granted), so each screen below uses the export file name.

Login and signup OTP is **4 digits** (`data.otpLength`). `data.maskedPhone` is the line on the Verification screen (`+91 ********52`). `data.resendInSec` is the countdown before Resend OTP can be tapped. In development, send-otp also returns `data.devOtp`.

## Screen map

| Screen | What the app does | API | Admin / seed |
|---|---|---|---|
| Splash | Logo and tagline | `GET /public/app-config` | Settings. Seeded branding. |
| Login | Mobile + Terms + Data Processing Agreement, Get OTP | `POST /user/auth/send-otp`, `GET /public/cms/terms`, `GET /public/cms/data-processing` | Existing account only. |
| OTP | 4 boxes, Login, Resend OTP | `POST /user/auth/verify-otp`, `POST /user/auth/resend-otp` | — |
| Sign Up Mob | New mobile, Get OTP | `POST /user/auth/register/send-otp` | Rejects numbers that already have an account. |
| OTP (Verification) | 4 boxes, Continue, Resend OTP | `POST /user/auth/register/verify-otp`, `POST /user/auth/register/resend-otp` | Returns `signupToken`. Does not create the account yet. |
| Sign Up | Name, mobile, optional email and referral, Get Started | `POST /user/auth/register` | Referral reward is wallet credit. |
| Home | City, pickup, drop, vehicle cards, SOS, bell | `GET /public/cities`, `GET /user/places/reverse`, `GET /public/vehicle-types`, `POST /user/rides/estimate`, `GET /user/notifications`, `POST /user/sos` | Pricing, Locations, Notifications. Seeded cities and vehicle types including Toto. |
| Booking Overview | Route, Toto/Auto quotes, fare | `POST /user/rides/estimate` | Pricing. `shortName` + `label` on each quote. |
| Booking Overview-1 | Coupon and confirm | `GET /user/rides/offers`, `POST /user/rides` | Coupons. Seeded WELCOME50, RIDE10, BIZZ20, FLAT100. |
| Booking Details | Ongoing / Completed / Cancelled | `GET /user/rides?status=ongoing\|completed\|cancelled` | Booking Management. |
| Searching for Driver | Waiting for assignment | `GET /user/rides/:id` or `GET /user/rides/active` | Live booking. |
| Searching for Driver-1 / -2 | Driver, OTP, fare, SOS, cancel, chat | `GET /user/rides/:id`, `POST /user/rides/:id/cancel`, `POST /user/sos`, `GET/POST /user/rides/:id/messages` | Ride `otp` is 4 digits. `sharePath` is the live-share link. |
| Chat | Driver chat and call | Messages above. Call `ride.driver.phone`. | — |
| Ride Completed | Fare, pay cash / wallet / UPI | `POST /user/rides/:id/pay`, `POST /user/rides/:id/verify-payment`, wallet pay by `paymentMethod` | UPI is `paymentMethod: "online"` and displays as `paymentLabel: "UPI"`. |
| Wallet | Balance, All/Credit/Debit, add amount | `GET /user/wallet`, `GET /user/wallet/transactions?type=credit\|debit`, `POST /user/wallet/add-money` | Wallets. |
| Saved Address | Home / Office list | `GET/POST/PUT/DELETE /user/addresses` | User-owned. Demo addresses seeded. |
| Profile | Menu | `GET /user/profile` | User Management. |
| Edit Profile | Name, phone, email, avatar, referral | `PUT /user/profile`, phone OTP routes | User Management. |
| Bank Details | Holder, number, IFSC, bank, type | `GET/PUT /user/bank-account` | User detail bank form. Seeded for demo riders. |
| Notifications | Inbox | `GET /user/notifications` returns `{ items, unreadCount }` | Notifications composer. |
| Notifications-1 / -2 (SOS) | Emergency contact, share ride, report | Profile `emergencyContacts`, `sharePath`, `POST /user/sos`, `POST /user/reports` | SOS + Settings SOS numbers. |
| Add Support Ticket | Category, subject, description, image | `GET /user/support/categories`, `POST /user/support/tickets` | Support categories. Seeded Lost & Found, Ride issue, Payment, Safety, Driver, KYC, Other. |
| Help & Support | Ticket cards and chat | `GET /user/support/tickets`, `GET /user/support/tickets/:id`, message routes | Support page reply. `open` displays as `statusLabel: "Pending"`. |
| FAQs | Accordion | `GET /user/support/faq` | FAQs page. Seeded questions. |
| Privacy Policy | CMS | `GET /public/cms/privacy` | CMS. `updatedAt` is “Updated On”. |
| Terms & Conditions | CMS | `GET /public/cms/terms` | CMS. |

Splash, Login, and Sign Up Mob have no private data. Do not call authenticated routes before verify-otp.

## Auth

`acceptedTerms` must be `true` on both Get OTP buttons. If it is missing, the API returns `400` with "Please agree to the Terms of Service and Data Processing Agreement". Resend OTP does not ask for the checkbox again.

Terms links: `GET /public/cms/terms` and `GET /public/cms/data-processing`.

### POST `/user/auth/send-otp`

Login screen. The number must already have an account. Unknown numbers return `404` "No account found for this number. Please sign up".

Request:

```json
{ "phone": "+91 9512345780", "acceptedTerms": true }
```

Response `200`:

```json
{
  "success": true,
  "message": "OTP sent",
  "data": {
    "phone": "919512345780",
    "maskedPhone": "+91 ********80",
    "otpLength": 4,
    "expiresInSec": 300,
    "resendInSec": 30,
    "devOtp": "8471"
  }
}
```

`devOtp` is only present in development when MSG91 is not configured. Resend uses `{ "phone": "+91 9512345780" }` on `POST /user/auth/resend-otp`.

### POST `/user/auth/verify-otp`

Login button on the 4-box OTP screen. Body is only the mobile number and the 4-digit OTP.

```json
{ "phone": "+91 9512345780", "otp": "8471" }
```

Response `200`:

```json
{
  "success": true,
  "message": "Login successful",
  "data": {
    "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
    "user": {
      "id": "66f0aa11bb22cc33dd44ee55",
      "name": "Aarav Sharma",
      "phone": "919512345780",
      "email": "aarav@example.com",
      "avatar": "",
      "city": "Indore",
      "status": "active",
      "walletBalance": 0,
      "rating": 5,
      "referralCode": "BZ7K2MNP",
      "emergencyContacts": [],
      "notificationSettings": { "booking": true, "promo": true, "payment": true }
    }
  }
}
```

This does not create an account. Name, email, and referral belong on the signup screens below.

### POST `/user/auth/register/send-otp`

Sign Up Mob. The number must not already have an account. An existing number returns `409` "Already have an account. Please login".

```json
{ "phone": "+91 9512345780", "acceptedTerms": true }
```

Response matches login send-otp, including `maskedPhone` for the Verification subtitle. Resend is `POST /user/auth/register/resend-otp` with `{ "phone": "+91 9512345780" }`.

### POST `/user/auth/register/verify-otp`

Continue on the Verification screen. Does not create the user.

```json
{ "phone": "+91 9512345780", "otp": "8471" }
```

Response `200`:

```json
{
  "success": true,
  "message": "Mobile number verified",
  "data": {
    "signupToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
    "phone": "919512345780",
    "expiresInSec": 900
  }
}
```

Keep `signupToken` for Get Started. It expires in 15 minutes and cannot call other user routes.

### POST `/user/auth/register`

Create Your Account. Name is required. Email and referral code are optional. The mobile number comes from `signupToken`, not from the body.

```json
{
  "signupToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "name": "Aarav Sharma",
  "email": "aarav@example.com",
  "referralCode": "BZ7K2MNP"
}
```

Response `201`, message `"Account created"`, data is `{ token, user }` in the same shape as login. An unknown referral code returns `400` "Invalid referral code". Omit `email` or `referralCode` when the fields are left blank.

### POST `/user/auth/logout`

Response `200`:

```json
{ "success": true, "message": "Logged out", "data": null }
```

## Home and booking

### GET `/public/app-config`

Response `200` (Splash uses `mobileLogo`, `name`, and `tagline`):

```json
{
  "success": true,
  "message": "App config fetched",
  "data": {
    "name": "BizzCab",
    "tagline": "Quick, comfortable, and spacious rides",
    "mobileLogo": "/uploads/mobile-logo.png",
    "logoLight": "",
    "email": "support@bizzcab.com",
    "contactNo": "0731-4001000",
    "address": "Vijay Nagar, Indore, Madhya Pradesh 452010",
    "sosNumbers": ["112", "100"],
    "defaultCity": "Indore"
  }
}
```

### GET `/public/cities`

Response `200`. Admin: Locations. Seeded Indore, Bhopal, Ujjain.

```json
{
  "success": true,
  "message": "Cities fetched",
  "data": [
    { "_id": "66f0city0001", "name": "Bhopal" },
    { "_id": "66f0city0002", "name": "Indore" },
    { "_id": "66f0city0003", "name": "Ujjain" }
  ]
}
```

### GET `/user/places/search?q=` and GET `/user/places/geocode?address=`

Response `200`:

```json
{
  "success": true,
  "message": "Places fetched",
  "data": [
    {
      "address": "Vijay Nagar, Indore, Madhya Pradesh, India",
      "lat": 22.7533,
      "lng": 75.8937,
      "placeId": "ChIJexample",
      "name": "Vijay Nagar"
    }
  ]
}
```

Geocode returns one object with `address`, `lat`, and `lng`.

### GET `/user/places/reverse?lat=&lng=`

Used by the Home location chip. Without a Maps key it returns Vijay Nagar, Indore.

Response `200`:

```json
{
  "success": true,
  "message": "Location resolved",
  "data": {
    "address": "Vijay Nagar, Indore",
    "lat": 22.7533,
    "lng": 75.8937,
    "city": "Indore"
  }
}
```

### GET `/public/vehicle-types` and POST `/user/rides/estimate`

Vehicle types seeded to match the frames:

| name | shortName | tagline |
|---|---|---|
| 2-Wheeler | Bike | Quick & Affordable |
| 3-Wheeler | Auto | Comfortable Ride |
| 4-Wheeler | Cab | Spacious & Luxury |
| Toto | Toto | Everyday Affordable Ride |

Vehicle types response `200`:

```json
{
  "success": true,
  "message": "Vehicle types fetched",
  "data": [
    {
      "_id": "66f0type0003",
      "name": "3-Wheeler",
      "shortName": "Auto",
      "label": "3-Wheeler (Auto)",
      "tagline": "Comfortable Ride",
      "baseFare": 30,
      "perKm": 12,
      "minFare": 40,
      "taxPercent": 0,
      "capacity": 3,
      "active": true
    }
  ]
}
```

Estimate request:

```json
{
  "pickup": { "address": "Vijay Nagar, Indore", "lat": 22.7533, "lng": 75.8937 },
  "drop": { "address": "Dewas, Madhya Pradesh", "lat": 22.9623, "lng": 76.0508 },
  "couponCode": "WELCOME50"
}
```

Estimate response `200`:

```json
{
  "success": true,
  "message": "Fare estimated",
  "data": [
    {
      "vehicleType": "3-Wheeler",
      "tagline": "Comfortable Ride",
      "shortName": "Auto",
      "label": "3-Wheeler (Auto)",
      "capacity": 3,
      "distanceKm": 3,
      "durationMin": 8,
      "fare": 41,
      "breakdown": {
        "baseFare": 49,
        "distanceFare": 0,
        "waitingCharge": 0,
        "tax": 1,
        "discount": 10,
        "total": 41
      }
    }
  ]
}
```

`tax` is the GST line. `discount` is Coupon Applied.

### GET `/user/rides/offers`

Response `200`:

```json
{
  "success": true,
  "message": "Offers fetched",
  "data": [
    {
      "_id": "66f0coupon0001",
      "code": "WELCOME50",
      "description": "Flat Rs 50 off your first ride",
      "discountType": "flat",
      "discountValue": 50,
      "minAmount": 80,
      "maxDiscount": 50,
      "active": true
    }
  ]
}
```

### POST `/user/rides`

Request adds `vehicleType` and `paymentMethod` (`cash` | `wallet` | `online`).

```json
{
  "pickup": { "address": "Vijay Nagar, Indore", "lat": 22.7533, "lng": 75.8937 },
  "drop": { "address": "Dewas, Madhya Pradesh", "lat": 22.9623, "lng": 76.0508 },
  "vehicleType": "3-Wheeler",
  "paymentMethod": "cash",
  "couponCode": "WELCOME50"
}
```

Response `201`:

```json
{
  "success": true,
  "message": "Ride created",
  "data": {
    "ride": {
      "_id": "66f0ride0001",
      "bookingId": "2405201030",
      "bookingCode": "2405201030",
      "status": "searching",
      "statusLabel": "Searching",
      "otp": "0204",
      "vehicleType": "3-Wheeler",
      "paymentMethod": "cash",
      "paymentLabel": "Cash",
      "paymentStatus": "pending",
      "distanceKm": 3,
      "durationMin": 8,
      "etaMinutes": 2,
      "dropEta": "2026-09-17T11:08:00.000Z",
      "fare": 41,
      "fareBreakdown": { "baseFare": 49, "tax": 1, "discount": 10, "total": 41 },
      "couponCode": "WELCOME50",
      "couponDiscount": 10,
      "pickup": { "address": "Vijay Nagar, Indore", "lat": 22.7533, "lng": 75.8937 },
      "drop": { "address": "Dewas, Madhya Pradesh", "lat": 22.9623, "lng": 76.0508 },
      "driver": null,
      "vehicle": null,
      "shareToken": "a1b2c3d4e5f6",
      "sharePath": "/public/rides/share/a1b2c3d4e5f6",
      "createdAt": "2026-09-17T10:30:00.000Z"
    },
    "payment": null
  }
}
```

After a driver accepts, `GET /user/rides/:id` returns the same ride object with:

```json
{
  "status": "accepted",
  "statusLabel": "On the way",
  "etaMinutes": 2,
  "driver": {
    "name": "Ramesh Yadav",
    "phone": "918000000002",
    "rating": 4.8,
    "avatar": ""
  },
  "vehicle": {
    "model": "Bajaj RE",
    "registrationNumber": "MP09CD2002",
    "color": "Yellow"
  }
}
```

`sharePath` is appended to the API origin for the SOS share link. Online create also returns `payment` with a Razorpay order.

### GET `/user/rides?status=ongoing|completed|cancelled`

Matches the My Bookings tabs. `ongoing` includes searching, accepted, arrived, and ongoing.

Response `200`:

```json
{
  "success": true,
  "message": "Rides fetched",
  "data": {
    "items": [],
    "total": 1,
    "page": 1,
    "limit": 20,
    "pages": 1
  }
}
```

Each item is the same ride object as ride detail.

### GET `/user/rides/active`

The current trip, or `null`. Home SOS can share this ride.

```json
{ "success": true, "message": "Active ride fetched", "data": null }
```

### POST `/user/rides/:id/cancel`

Request: `{ "reason": "Changed plans" }`

Response `200`: same ride object with `status: "cancelled"` and `statusLabel: "Cancelled"`.

### POST `/user/rides/:id/pay` and POST `/user/rides/:id/verify-payment`

Pay response `200`:

```json
{
  "success": true,
  "message": "Payment order created",
  "data": {
    "paymentId": "66f0pay0001",
    "order": { "id": "order_dev", "amount": 4100, "currency": "INR" }
  }
}
```

Verify request: `{ "razorpay_order_id", "razorpay_payment_id", "razorpay_signature" }`

Verify response `200`: `{ "ride", "payment" }` with `ride.paymentStatus: "paid"` and `paymentLabel: "UPI"` when `paymentMethod` is `online`.

### GET/POST `/user/rides/:rideId/messages`

List response `200`:

```json
{
  "success": true,
  "message": "Messages fetched",
  "data": [
    {
      "_id": "66f0msg0001",
      "ride": "66f0ride0001",
      "fromRole": "driver",
      "text": "I have arrived",
      "createdAt": "2026-09-17T11:42:00.000Z"
    }
  ]
}
```

Send request: `{ "text": "I am at the gate" }`. Response `201` is one message object.

### GET `/public/rides/share/:token`

Response `200`:

```json
{
  "success": true,
  "message": "Shared ride fetched",
  "data": {
    "status": "accepted",
    "pickup": { "address": "Vijay Nagar, Indore", "lat": 22.7533, "lng": 75.8937 },
    "drop": { "address": "Dewas, Madhya Pradesh", "lat": 22.9623, "lng": 76.0508 },
    "vehicleType": "3-Wheeler",
    "driverName": "Ramesh Yadav",
    "vehicleNumber": "MP09CD2002",
    "currentLocation": { "lat": 22.74, "lng": 75.88 }
  }
}
```

## Wallet, profile, bank

### GET `/user/wallet`

```json
{
  "success": true,
  "message": "Wallet fetched",
  "data": { "balance": 1250 }
}
```

### GET `/user/wallet/transactions?type=credit|debit`

Each row has `title` (`Added Money` / `Wallet Payment`), `subtitle`, `amount`, `balanceAfter`, `type`.

```json
{
  "success": true,
  "message": "Transactions fetched",
  "data": {
    "items": [
      {
        "_id": "66f0txn0001",
        "type": "credit",
        "title": "Added Money",
        "subtitle": "Wallet Recharge",
        "amount": 500,
        "balanceAfter": 2750,
        "reason": "wallet_topup",
        "createdAt": "2024-05-21T05:00:00.000Z"
      }
    ],
    "total": 1,
    "page": 1,
    "limit": 20,
    "pages": 1
  }
}
```

### POST `/user/wallet/add-money`

Request: `{ "amount": 500 }`

Response `200`:

```json
{
  "success": true,
  "message": "Top-up order created",
  "data": {
    "paymentId": "66f0pay0002",
    "order": { "id": "order_dev", "amount": 50000, "currency": "INR" }
  }
}
```

`POST /user/wallet/verify` returns `{ "balance", "transaction" }` after a successful signature check.

### GET `/user/profile`

Response `200`:

```json
{
  "success": true,
  "message": "Profile fetched",
  "data": {
    "id": "66f0aa11bb22cc33dd44ee55",
    "name": "Rahul Sharma",
    "phone": "919876543210",
    "email": "rahul.s@example.com",
    "avatar": "/uploads/avatar.jpg",
    "city": "Indore",
    "status": "active",
    "walletBalance": 1250,
    "rating": 5,
    "ratingCount": 0,
    "referralCode": "BZ7K2MNP",
    "emergencyContacts": [{ "name": "Neha Sharma", "phone": "919000000011" }],
    "bankAccount": {
      "accountName": "Rahul Sharma",
      "accountNumber": "501234567890",
      "ifsc": "HDFC0001234",
      "bankName": "HDFC Bank",
      "accountType": "savings"
    },
    "notificationSettings": { "booking": true, "promo": true, "payment": true },
    "deleteRequested": false
  }
}
```

`PUT /user/profile` returns the same object. Phone change uses `POST /user/profile/phone/send-otp` (same OTP response as login) then `POST /user/profile/phone/confirm` with `{ "phone", "otp" }`.

`GET /user/bank-account` is the same profile object. Demo riders Aarav, Diya, and Rohan are seeded with bank details.

### PUT `/user/bank-account`

Request:

```json
{
  "accountName": "Rahul Sharma",
  "accountNumber": "501234567890",
  "ifsc": "HDFC0001234",
  "bankName": "HDFC Bank",
  "accountType": "savings"
}
```

`accountType` is `savings` or `current`. Response `200` is the profile object with `message: "Bank details updated"`. Same fields can be saved from Admin user detail via `PATCH /admin/users/:id` with `bankAccount`.

### GET/POST/PUT/DELETE `/user/addresses`

List response `200`:

```json
{
  "success": true,
  "message": "Addresses fetched",
  "data": [
    {
      "_id": "66f0addr0001",
      "label": "home",
      "name": "Home",
      "address": "1234, ABC Colony, Near Lal Bagh, Indore, M.P 452005",
      "lat": 22.7196,
      "lng": 75.8577
    }
  ]
}
```

Create request: `{ "label": "work", "name": "Office", "address": "…", "lat": 22.72, "lng": 75.88 }`. Response `201` is one address. Delete response `200` has `data: null`.

## Support, FAQ, CMS

### GET `/user/notifications`

```json
{
  "success": true,
  "message": "Notifications fetched",
  "data": {
    "unreadCount": 2,
    "items": [
      {
        "_id": "66f0note0001",
        "title": "Ride Confirmed",
        "body": "Your ride is confirmed! Booking ID: YRSV24052 is scheduled for today at 10:30 AM.",
        "type": "booking",
        "read": false,
        "createdAt": "2026-09-17T10:28:00.000Z"
      }
    ]
  }
}
```

`PATCH /user/notifications/:id/read` returns the same item with `read: true`.

### POST `/user/sos`

Request: `{ "rideId": "66f0ride0001", "lat": 22.74, "lng": 75.88, "message": "Need help" }`

Response `201`:

```json
{
  "success": true,
  "message": "SOS raised",
  "data": {
    "_id": "66f0sos0001",
    "status": "open",
    "raisedBy": "user",
    "message": "Need help",
    "lat": 22.74,
    "lng": 75.88,
    "contactsNotified": [{ "name": "Neha Sharma", "phone": "919000000011" }]
  }
}
```

Emergency numbers for the big SOS button also come from `app-config.sosNumbers`. Report safety issue is `POST /user/reports` with `{ "rideId", "subject", "message" }` and returns a ticket.

### GET `/user/support/faq`

Admin: **FAQs**. Seeded on boot even when demo data is off.

```json
{
  "success": true,
  "message": "FAQ fetched",
  "data": {
    "page": { "slug": "faq", "title": "FAQ", "content": "Book a ride by choosing pickup, drop, and vehicle type." },
    "items": [
      {
        "_id": "66f0faq0001",
        "question": "How do I book a ride?",
        "answer": "Set pickup and drop on Home, choose a vehicle, review the fare, then confirm.",
        "sort": 1,
        "active": true
      }
    ]
  }
}
```

### GET `/user/support/categories`

Admin: **Support** category form. Required `category` slug when creating a ticket.

```json
{
  "success": true,
  "message": "Support categories fetched",
  "data": [
    { "_id": "66f0cat0001", "slug": "lost_found", "name": "Lost & Found", "sort": 1, "active": true }
  ]
}
```

### POST `/user/support/tickets`

JSON or multipart. Multipart field `image` becomes `attachment`.

Request: `{ "category": "lost_found", "subject": "Lost & Found Request", "message": "Left a phone in the cab" }`

Response `201`:

```json
{
  "success": true,
  "message": "Ticket created",
  "data": {
    "_id": "66f0ticket0001",
    "ticketCode": "SUP-2026-874521",
    "category": "lost_found",
    "subject": "Lost & Found Request",
    "message": "Left a phone in the cab",
    "attachment": "",
    "status": "open",
    "statusLabel": "Pending",
    "createdAt": "2026-06-21T14:15:00.000Z"
  }
}
```

`statusLabel` `Pending` means `status: "open"`. List returns an array of these objects.

### GET `/user/support/tickets/:id`

```json
{
  "success": true,
  "message": "Ticket fetched",
  "data": {
    "ticketCode": "SUP-2026-874521",
    "subject": "Lost & Found Request",
    "statusLabel": "Pending",
    "messages": [
      {
        "_id": "66f0smsg0001",
        "fromRole": "admin",
        "text": "We are checking this with the driver.",
        "createdAt": "2026-06-21T15:00:00.000Z"
      }
    ]
  }
}
```

`POST /user/support/tickets/:id/messages` with `{ "text": "It is a black case." }` returns `201` and one message. Admin reply uses `POST /admin/tickets/:id/messages`.

### Public CMS

| Screen | Slug |
|---|---|
| Terms of Service / Terms & Conditions | `terms` |
| Data Processing Agreement | `data-processing` |
| Privacy Policy | `privacy` |

Response `200` for `GET /public/cms/privacy`:

```json
{
  "success": true,
  "message": "Page fetched",
  "data": {
    "slug": "privacy",
    "title": "Privacy Policy",
    "content": "We store account, location, and ride data…",
    "updatedAt": "2026-04-01T00:00:00.000Z"
  }
}
```

`updatedAt` is the “Updated On” line. `GET /public/cms` returns `[{ slug, title }]`.

## What was not added

These were not on a named frame, so they were not built:

- Promo banners
- Onboarding slides
- Cancel-reason catalog (cancel still accepts free-text `reason`)
- Saved UPI catalog (completed-ride UPI is online payment)

## Admin pages added

- [Admin/src/pages/Faqs.jsx](../../Admin/src/pages/Faqs.jsx) — `/faqs`
- Support categories and ticket chat reply on [Admin/src/pages/Support.jsx](../../Admin/src/pages/Support.jsx)
- Bank details on the user profile tab

Run `npm run seed` in `Backend` (also runs on API boot) to fill CMS, FAQs, categories, Toto, and booking/ticket codes.
