Files
doormile_backend/docs/customer-app-api-crisp.md

292 lines
11 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Doormile Customer App (`doormile_cx`) API — Quick Reference
> **Base URL:** `https://api.doormile.com/api/v1`
> **Namespace:** `/customer/*` | **Auth Role:** `9` (Customer) | **Data Envelope:** `{ "success": true, "data": { ... } }`
---
## 1. Global Conventions
| Aspect | Specification | Details / Rules |
|---|---|---|
| **Naming** | `camelCase` | All request & response JSON fields use camelCase. |
| **Timestamps** | Epoch milliseconds (UTC, `int64`) | Parse directly with `DateTime.fromMillisecondsSinceEpoch(ts)`. |
| **Identifiers** | Sequence-backed Feistel permutation | **Booking Reference:** `DM-482913`<br>**Tracking Number:** `DMX10482913` |
| **Headers** | `Authorization: Bearer <accessToken>`<br>`X-Client: doormile-cx/<version>+<build>`<br>`X-Platform: android | ios`<br>`Idempotency-Key: <uuid>` | • Required on all routes except pre-auth & catalogue.<br>• Client telemetry logged on every request.<br>• Idempotency supported on booking creation & OTP verification. |
| **Token Lifetime** | Access: `1 hour` \| Refresh: `60 days` | Refresh tokens rotate on every use. Replaying a revoked token revokes the entire chain. |
---
## 2. Complete Endpoint Reference (28 Routes)
### 🔐 Authentication (Pre-Auth & Session)
| Method | Endpoint | Auth | Description |
|---|---|:---:|---|
| `POST` | `/customer/auth/otp/request` | ❌ | Request OTP via SMS/Email (returns uniform success regardless of account existence). |
| `POST` | `/customer/auth/otp/verify` | ❌ | Verify OTP, returns JWT tokens & customer profile. |
| `POST` | `/customer/auth/signup` | ❌ | Register new customer or treat existing phone as sign-in. |
| `POST` | `/customer/auth/refresh` | ❌ | Rotate refresh token to issue a new access token. |
| `POST` | `/customer/auth/logout` | ✅ | Revoke session (omit `refreshToken` to sign out everywhere). |
| `GET` | `/customer/auth/me` | ✅ | Fetch currently authenticated customer identity. |
### 📦 Serviceability & Catalogue
| Method | Endpoint | Auth | Description |
|---|---|:---:|---|
| `GET` | `/customer/serviceability/states` | ❌ | List serviceable states (`ETag` / 304 supported). |
| `GET` | `/customer/serviceability/states/:code/districts` | ❌ | List serviceable districts in state (`ETag` / 304). |
| `GET` | `/customer/pickup-slots` | ❌ | List today's & tomorrow's time slots with zone capacity (`ETag` / 304). |
| `GET` | `/customer/config/booking-limits` | ❌ | Get booking limits (`maxDestinations`, `maxPackages`, `maxCodAmount`). |
### 📍 Places & Geocoding
| Method | Endpoint | Auth | Description |
|---|---|:---:|---|
| `GET` | `/customer/places/search?q=:query&lat=:lat&lng=:lng` | ✅ | Place search / autocomplete (empty query returns recent/saved). |
| `GET` | `/customer/places/reverse-geocode?lat=:lat&lng=:lng` | ✅ | Reverse geocode coordinates to structured address. |
### 💰 Fare Estimation
| Method | Endpoint | Auth | Description |
|---|---|:---:|---|
| `POST` | `/customer/fare/estimate` | ✅ | Compute estimated fare band (`minRupees`–`maxRupees`) & route distance. |
### 🚚 Bookings & Tracking
| Method | Endpoint | Auth | Description |
|---|---|:---:|---|
| `POST` | `/customer/bookings` | ✅ | Create multi-destination pickup booking (`Idempotency-Key` supported). |
| `GET` | `/customer/bookings?limit=20&cursor=:cursor&status=active` | ✅ | Keyset paginated customer bookings list. |
| `GET` | `/customer/bookings/:reference` | ✅ | Get full booking detail & live tracking snapshot (`ETag` supported). |
| `POST` | `/customer/bookings/:reference/cancel` | ✅ | Cancel booking (allowed strictly before rider status `arrived`). |
| `PATCH` | `/customer/bookings/:reference/destinations/:index` | ✅ | Update recipient details on a pending destination stop. |
| `GET` | `/customer/orders/:trackingId` | ✅ | Look up single order/parcel status by tracking number. |
### 👤 Profile & Saved Locations
| Method | Endpoint | Auth | Description |
|---|---|:---:|---|
| `GET` | `/customer/profile` | ✅ | Get customer profile details. |
| `PUT` | `/customer/profile` | ✅ | Update customer name/email. |
| `GET` | `/customer/locations` | ✅ | List up to 10 saved delivery/pickup addresses. |
| `POST` | `/customer/locations` | ✅ | Save a new location. |
| `PUT` | `/customer/locations/:id` | ✅ | Update an existing saved location. |
| `DELETE` | `/customer/locations/:id` | ✅ | Delete a saved location. |
### 🔔 Devices & Push Notifications
| Method | Endpoint | Auth | Description |
|---|---|:---:|---|
| `POST` | `/customer/devices` | ✅ | Register FCM device token for milestone push updates. |
| `DELETE` | `/customer/devices/:token` | ✅ | Unregister device token on logout. |
### 🛠️ Ops QA Testing (Non-Production Only)
| Method | Endpoint | Auth | Description |
|---|---|:---:|---|
| `POST` | `/customer/ops/bookings/:reference/stage` | ✅ | Double-gated test helper to walk a booking through stages. |
---
## 3. Core Request & Response Payloads
### 1) OTP Verification (`POST /customer/auth/otp/verify`)
```json
// Request
{
"identifier": "+919876543210",
"otp": "1234"
}
// Response (200 OK)
{
"success": true,
"data": {
"accessToken": "eyJhbGciOi...",
"refreshToken": "d8f1e2a3...",
"expiresIn": 3600,
"customer": {
"id": 1042,
"name": "Alex Kumar",
"phone": "+919876543210",
"email": "alex@example.com"
}
}
}
```
### 2) Fare Estimate (`POST /customer/fare/estimate`)
```json
// Request
{
"pickup": {
"latitude": 13.0827,
"longitude": 80.2707,
"stateCode": "TN",
"districtCode": "CHN"
},
"destinations": [
{
"stateCode": "TN",
"districtCode": "CHN",
"packages": [{ "weightKg": 2.5 }]
},
{
"stateCode": "KA",
"districtCode": "BLR",
"packages": [{ "weightKg": 1.0 }]
}
]
}
// Response (200 OK)
{
"success": true,
"data": {
"minRupees": 240,
"maxRupees": 310,
"routeKm": 348.5,
"breakdown": {
"baseFare": 180,
"additionalStopsUplift": 60,
"estimatedTax": 0
}
}
}
```
### 3) Booking Creation (`POST /customer/bookings`)
```json
// Request
{
"slotId": "slot_20260908_t2",
"pickup": {
"title": "Home",
"sub": "Flat 4B, Green Towers, Anna Nagar",
"latitude": 13.0827,
"longitude": 80.2707,
"contactName": "Alex Kumar",
"contactPhone": "+919876543210"
},
"destinations": [
{
"recipientName": "Priya S",
"recipientPhone": "+919840123456",
"building": "12/A",
"street": "MG Road",
"landmark": "Near Metro",
"districtCode": "CHN",
"stateCode": "TN",
"latitude": 13.0850,
"longitude": 80.2100,
"packageCount": 1,
"codAmount": 450
}
],
"remarks": "Handle with care"
}
// Response (201 Created)
{
"success": true,
"data": {
"reference": "DM-482913",
"stage": "booked",
"status": "active",
"cancellable": true,
"createdAt": 1788775499000,
"slotId": "slot_20260908_t2",
"pickup": {
"title": "Home",
"sub": "Flat 4B, Green Towers, Anna Nagar",
"latitude": 13.0827,
"longitude": 80.2707
},
"destinations": [
{
"index": 0,
"stateName": "Tamil Nadu",
"districtName": "Chennai",
"packageCount": 1,
"codAmount": 450,
"trackingId": null,
"stage": null
}
]
}
}
```
---
## 4. Lifecycle & Stage Machine
### Stage Progression Sequence
```
[ booked ] ──► [ assigned ] ──► [ arrived ] ──► [ picked_up ] ──► [ order_created ]
│ (cancel window closes)
▼
[ delivered ] ◄── [ out_for_delivery ] ◄── [ in_transit ]
```
### Stage Vocabulary
| Stage Key | Meaning / Trigger | Cancellable? | Scope |
|---|---|:---:|---|
| `booked` | Order submitted by customer | ✅ Yes | Booking |
| `assigned` | Rider assigned to visit | ✅ Yes | Booking |
| `on_the_way` | Rider accepted assignment | ✅ Yes | Booking |
| `arrived` | Rider arrived at pickup point (**cancellation cutoff**) | ❌ No | Booking |
| `picked_up` | Parcels collected and weighed | ❌ No | Booking |
| `order_created` | Tracking IDs generated per destination | ❌ No | Per Destination |
| `in_transit` | Parcels sorted / inwarded at hub | ❌ No | Per Destination |
| `out_for_delivery` | Dispatched with delivery agent | ❌ No | Per Destination |
| `delivered` | Successfully delivered to recipient | ❌ No | Per Destination |
| `cancelled` | Cancelled by customer or ops before arrival | — | Terminal |
> [!IMPORTANT]
> **Rollup Rule:** The overall booking stage reflects the **slowest order**. If Destination 1 is `delivered` but Destination 2 is `in_transit`, the booking rollup remains `in_transit`.
---
## 5. Cross-App Impact & Compatibility
| Client / Component | Observable Change | Impact / Handling |
|---|---|---|
| **Miler App (Flutter)** | Multi-stop collection | Post-collection returns multiple stops sharing one `bookingid`. Keys must resolve by `consignmentid`. |
| **Admin Console** | Booking numbers & Search | Display format is now `DM-482913`. Searches match exact substring. |
| **Hub Console** | Tracking IDs & Inbound | Tracking numbers are now `DMX10482913`. |
| **Safety Mitigation** | `maxDestinations` Gate | Configured in DB (`customerbookinglimits.maxdestinations = 1`) to keep fanout single-destination until mobile updates deploy. |
---
## 6. Standard Error Codes & Envelopes
All errors return JSON in standard format:
```json
{
"success": false,
"error": {
"code": "SLOT_UNAVAILABLE",
"message": "That pickup time has passed — pick a new slot"
}
}
```
| HTTP Status | Error Code (`error.code`) | Meaning / Recommended Client Action |
|---|---|---|
| `400` | `INVALID_INPUT` / `SLOT_EXPIRED` | Validation error or expired slot date. Prompt user to re-select. |
| `401` | `UNAUTHORIZED` | Token missing or expired. Redirect to OTP login / refresh session. |
| `403` | `FORBIDDEN` | Caller lacks role 9 customer access. |
| `404` | `NOT_FOUND` | Booking reference or tracking ID does not exist. |
| `409` | `SLOT_CAPACITY_FULL` | Slot filled up during checkout race. Prompt user to choose another time. |
| `409` | `BOOKING_NOT_CANCELLABLE` | Customer attempted cancel after rider `arrived`. Show un-cancellable alert. |
| `422` | `UNSERVICEABLE_PINCODE` | Location is outside active operating zones. |
| `429` | `RATE_LIMITED` | OTP requests exceeded limit (max 5/hour). Show countdown timer. |
| `500` | `INTERNAL_ERROR` | Generic server error (`X-Request-Id` logged). |
---
## 7. Environment Variables & Deploy Flags
```bash
GEOCODER_URL=https://nominatim.openstreetmap.org # Geocoding proxy
GEOCODER_EMAIL=ops@doormile.com # Nominatim contact policy
MILER_CALL_PROXY= # Set to proxy number in production (protects rider PII)
CX_STAGING_OTP=1234 # Fixed OTP for staging QA (disabled if ENV=production)
CX_ID_SCRAMBLE_KEY= # Optional custom key for Feistel sequence permutation
CX_ALLOW_STAGE_OVERRIDE=false # Double-gated QA stage override tool
```