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

13 KiB
Raw Blame History

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.

// Request
{
  "identifier": "+919876543210",
  "code": "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)

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.

// Request
{
  "pickup": {
    "lat": 13.0827,
    "lng": 80.2707
  },
  "destinations": [
    { "stateCode": "TN", "districtCode": "CHN", "packageCount": 2 },
    { "stateCode": "KA", "districtCode": "BLR", "packageCount": 1 }
  ]
}

// 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)

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

[ 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:

{
  "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

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