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 Tracking Number: DMX10482913 |
| Headers |
Authorization: Bearer <accessToken>
X-Client: doormile-cx/<version>+<build> `X-Platform: android |
ios<br>Idempotency-Key: ` |
| 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)
The field is code, not otp. This section said otp until 11 Sep 2026
and the customer app was built against it, while the server only ever read
code. Every sign-in therefore failed with a 400 "Enter the code we sent you" — a correct code failed exactly like a wrong one. openapi-customer.yaml
had it right all along; the two disagreed and this one was wrong.
The server now also accepts otp as a deprecated alias, so builds already
in customers' hands keep working. Send code. If both are present, code
wins.
2) Fare Estimate (POST /customer/fare/estimate)
Corrected 11 Sep 2026 against cxEstimateRequest
(controllers/cxFareController.go). The old shape used
pickup.latitude/longitude (parsed as lat/lng), gave pickup a
stateCode/districtCode it does not have, and described destinations as
carrying a packages array of weights. The estimate is priced on
packageCount; per-package weight is not known until the miler weighs it at
the door.
3) Booking Creation (POST /customer/bookings)
Corrected 11 Sep 2026. The shape below previously did not match the
parser (cxCreateBookingRequest, controllers/cxBookingController.go), and
every mismatch failed silently through BodyParser — no error, just a
zero value:
| Was documented |
Actually parsed |
Effect of following the old doc |
pickup.latitude / longitude |
pickup.lat / lng |
pickup coordinates 0 |
pickup.contactName / contactPhone |
not read at all |
dropped |
| destination fields flat |
nested under details |
every recipient/address field dropped |
destination latitude / longitude |
details.pin.lat / lng |
drop coordinates 0 |
estimate absent from the doc |
is read |
the quote shown to the customer was not recorded |
// Request
{
"slotId": "slot_20260908_t2",
"pickup": {
"title": "Home",
"sub": "Flat 4B, Green Towers, Anna Nagar",
"lat": 13.0827,
"lng": 80.2707
},
"destinations": [
{
"stateCode": "TN",
"districtCode": "CHN",
"packageCount": 1,
"details": {
"street": "MG Road",
"building": "12/A",
"landmark": "Near Metro",
"recipientName": "Priya S",
"recipientPhone": "+919840123456",
"instructions": "Ring the bell",
"pin": { "lat": 13.0850, "lng": 80.2100 },
"codAmount": 450
}
}
],
"estimate": { "min": 240, "max": 310 },
"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",
"lat": 13.0827,
"lng": 80.2707
},
"destinations": [
{
"index": 0,
"stateCode": "TN",
"stateName": "Tamil Nadu",
"districtCode": "CHN",
"districtName": "Chennai",
"packageCount": 1,
"details": { "recipientName": "Priya S", "codAmount": 450 },
"trackingId": null,
"stage": null
}
]
}
}
4. Lifecycle & Stage Machine
Stage Progression Sequence
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 |
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:
| 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