Files
doormile_hub_console/docs/Doormile-Hub-Console-API-Reference.md

20 KiB

Doormile Hub Console — Frontend Integration Guide

Base URL: https://api.doormile.com Auth: Bearer token in Authorization header on every authenticated request Content-Type: application/json on all POST / PATCH calls

How the flow works

  1. Staff opens the console → Login page
  2. POST /api/v1/hub/login → receives JWT + hub context
  3. Store token + hub context in localStorage
  4. Every page reads hub name/city from stored context (no extra call)
  5. Every API call sends Authorization: Bearer <token>
  6. On 401 → clear localStorage → redirect to /login

1. Authentication

POST /api/v1/hub/login

No auth header needed.

Request body:

{
  "email": "hub.coimbatore@doormile.in",
  "password": "password123"
}

Response:

{
  "success": true,
  "token": "eyJhbGci...",
  "hub": {
    "hubid": 1,
    "hubname": "Coimbatore Jupiter Hub",
    "hubtype": "sorting_center",
    "city": "Coimbatore",
    "capacity": 50
  },
  "staff": {
    "displayname": "Coimbatore Hub Staff",
    "email": "hub.coimbatore@doormile.in",
    "role": 6
  },
  "is_doormile_staff": true
}

What to store in localStorage after login:

localStorage.setItem('hub_token',       data.token)
localStorage.setItem('hub_context',     JSON.stringify(data.hub))
localStorage.setItem('hub_staff',       JSON.stringify(data.staff))
localStorage.setItem('hub_is_doormile', String(data.is_doormile_staff))

Test accounts:

Email Password Hub Access
hub.coimbatore@doormile.in password123 Coimbatore Jupiter Hub Full (Doormile staff)
hub.hyderabad@doormile.in password123 Hyderabad hub Full (Doormile staff)
hub.bangalore@doormile.in password123 Bangalore hub Full (Doormile staff)
hub.chennai@doormile.in password123 Chennai hub Full (Doormile staff)
hub@kpmtravels.in password123 Hub 1 Restricted (partner — no hub management)

2. Dashboard

GET /api/v1/hub/dashboard

Returns live KPI stats for the logged-in staff's hub.

Response:

{
  "success": true,
  "data": {
    "parcels_received_today": 142,
    "milers_available": 6,
    "milers_on_duty": 12,
    "pending_pickups": 8,
    "batches_sent_today": 3,
    "exceptions": 2
  }
}

Which UI element uses which field:

Dashboard card Field
Total Parcels Received parcels_received_today
Available Milers milers_available
Milers on Duty milers_on_duty
Pending Pickups pending_pickups
Batches Sent Today batches_sent_today
Needs Checking exceptions

Note: Hub name shown in the header/title comes from localStorage (hub_context.hubname) — not from this endpoint. No extra call needed.


3. Inbound — Receive Parcels

GET /api/v1/hub/inbound/today

Returns all parcels inbounded at this hub today. Now returns sendername, originname, destinationname, temperature (readable names instead of raw IDs and pincodes).

Response:

{
  "success": true,
  "data": [
    {
      "bookingid": 15,
      "trackingnumber": "DM-882201",
      "sendername": "Acme Corp",
      "originname": "Mumbai Hub",
      "destinationname": "Dwarka Sec 12, Coimbatore",
      "weight": "2.4 kg",
      "condition": "Good",
      "temperature": "N/A",
      "shelf": "Zone A (Shelf 1)",
      "inboundedat": "2026-07-04T09:15:00Z"
    }
  ],
  "total": 1
}

POST /api/v1/hub/bookings/:id/inbound

Scan a parcel in. :id is the booking/consignment ID.

Request body:

{
  "tracking_id": "DM-882201",
  "condition": "Good",
  "temperature": "N/A",
  "shelf": "Zone A (Shelf 1)",
  "weight": "2.4 kg"
}

Condition values: Good · Damaged Box · Wet / Crushed · Missing Label Temperature: Free text — "4.1°C" for cold chain items, "N/A" for dry goods

Response:

{
  "success": true,
  "data": {
    "bookingid": 15,
    "trackingnumber": "DM-882201",
    "recommended_shelf": "Zone A (Shelf 1)",
    "status": "At_Hub"
  }
}

Shelf recommendation logic (backend handles this automatically):

  • Condition contains Damaged / Wet / Missing → Exception Area
  • Temperature is not N/A → Zone C (Cold Room)
  • Otherwise → Zone A or Zone B based on destination

Error — tracking ID not found:

{
  "success": false,
  "message": "booking not found"
}

HTTP 404.


4. Order Assignment — Pickup Requests

GET /api/v1/hub/bookings/unassigned

Returns all pending pickup requests for this hub's city.

Response:

{
  "success": true,
  "data": [
    {
      "bookingid": 21,
      "customerName": "Ramesh K.",
      "pickupaddress": "Gandhipuram, Coimbatore",
      "deliveryaddress": "Hitech City, Hyderabad",
      "packagedescription": "Small Box, 2kg",
      "createdat": "2026-07-04T08:45:00Z",
      "status": "Pending_Pickup"
    }
  ],
  "total": 3
}

For assigning a miler to these bookings, see Section 14 — Hub Assignment Override.


5. Dispatch & Batches

GET /api/v1/hub/batches

Returns all outgoing batches for this hub.

Response:

{
  "success": true,
  "data": [
    {
      "tripsheetid": 1,
      "batchlabel": "BATCH-9281",
      "batchkind": "transfer",
      "destinationlabel": "Mumbai Hub (BOM-02)",
      "vehicle": "Truck DL-01-BZ-8055",
      "itemcount": 154,
      "status": "Draft",
      "createdat": "2026-07-04T08:00:00Z",
      "dispatchtime": null
    }
  ],
  "total": 1
}

Status values: DraftReadyDispatched Batchkind values: local · transfer

POST /api/v1/hub/batches

Create a new outgoing batch.

Request body:

{
  "route": "Transfer to Mumbai Hub",
  "destination": "Mumbai Hub (BOM-02)",
  "vehicle": "Truck DL-01-BZ-8055",
  "parcels_count": 154,
  "kind": "transfer"
}

Response:

{
  "success": true,
  "data": {
    "tripsheetid": 2,
    "batchlabel": "BATCH-9282",
    "status": "Draft"
  }
}

PATCH /api/v1/hub/batches/:id/status

Move a batch through its status flow.

Request body — mark as ready:

{ "status": "Ready" }

Request body — dispatch (send out):

{ "status": "Dispatched" }

Response:

{
  "success": true,
  "data": {
    "tripsheetid": 1,
    "status": "Ready",
    "dispatchtime": null
  }
}

Invalid status transition → 400:

{
  "success": false,
  "message": "invalid status"
}

6. Milers

GET /api/v1/hub/milers

Returns all milers assigned to this hub, now with operational stats per miler.

Response:

{
  "success": true,
  "data": [
    {
      "userid": 4,
      "displayname": "Rajesh Kumar",
      "phone": "+91 98765 43210",
      "vehicleid": 1,
      "hubid": 1,
      "availabilitystatus": "Available",
      "rating": 4.8,
      "completedorders": 45,
      "cancelledorders": 2,
      "currentlat": 11.0168,
      "currentlon": 76.9558,
      "device_token": "fcm_token_here",
      "zones": ["641001", "641012"],
      "assignedload": 2,
      "capacity": 30,
      "pickupspending": 1,
      "codcollected": 4500.00,
      "codpending": 1200.00,
      "checkinat": "2026-07-04T08:12:00Z",
      "hoursactive": 6.4,
      "isverified": true
    }
  ],
  "total": 6
}

Availability status values: Available · Assigned · On_Break · Offline

The zones, assignedload, capacity, pickupspending, codcollected, codpending, checkinat, hoursactive, isverified fields were the empty fields the Milers page was trying to show. Now populated from real data.

POST /api/v1/admin/milers

Create (onboard) a new miler. Hub JWT is accepted here.

Request body:

{
  "displayname": "Muthu Kumar",
  "phone": "+91 90011 22334",
  "hubid": 1,
  "vehicleid": 2,
  "availabilitystatus": "Available"
}

Response:

{
  "success": true,
  "data": {
    "userid": 10,
    "displayname": "Muthu Kumar"
  }
}

PATCH /api/v1/admin/milers/:id

Update a miler's details or status.

Request body (any subset of fields):

{
  "availabilitystatus": "On_Break",
  "hubid": 2
}

DELETE /api/v1/admin/milers/:id

Remove a miler from the roster.

Response:

{
  "success": true,
  "message": "miler removed"
}

7. Routing — Where Does It Go?

GET /api/v1/hub/routing/:trackingno

Scan a parcel and get its sort destination. :trackingno is the tracking number scanned at the sort station.

Response:

{
  "success": true,
  "data": {
    "trackingno": "DM-TRK-0015",
    "destination": "Peelamedu, 641004",
    "recommendedshelf": "Zone B",
    "nexthop": "Local delivery",
    "condition": "Good",
    "iscoldchain": false,
    "weight": "2.4 kg",
    "customername": "Ramesh K.",
    "bookingid": 15
  }
}

nexthop values: Local delivery · Transfer to Mumbai Hub (or whichever destination hub). Use recommendedshelf to show the big shelf indicator UI.

Error — tracking number not found → 404. Show a "Parcel not found" error state.

{
  "success": false,
  "message": "parcel not found"
}

8. Rider Routes

GET /api/v1/hub/rider-routes

Returns all milers at this hub with their planned stops today.

GET /api/v1/hub/milers/:id/route

Returns the planned stops for a single miler. :id is the miler user ID.

Response (same data[] shape for both; the single-miler endpoint returns one entry):

{
  "success": true,
  "data": [
    {
      "mileruserid": 4,
      "milername": "Rajesh Kumar",
      "mode": "pickup",
      "totalstops": 5,
      "completedstops": 2,
      "totaldistance_km": 12.4,
      "stops": [
        { "seq": 1, "address": "Gandhipuram, Coimbatore", "lat": 11.0168, "lon": 76.9558, "items": 1, "bookingid": 20, "status": "completed", "eta_minutes": 0 },
        { "seq": 2, "address": "RS Puram, Coimbatore", "lat": 11.001, "lon": 76.951, "items": 2, "bookingid": 21, "status": "pending", "eta_minutes": 12 }
      ]
    }
  ]
}

mode values: pickup · delivery Stop status values: pending · in_progress · completed

Pass stops[].lat/lon to OSRM to draw the route line — the OSRM call stays client-side.


9. Live Trucks

GET /api/v1/hub/tripsheets/in-transit

Returns transfer trucks currently in transit, with interpolated live positions. Poll this every 5 seconds alongside miler locations.

Response:

{
  "success": true,
  "data": [
    {
      "tripsheetid": 3,
      "tripsheetno": "DM-TS-0003",
      "label": "Chennai Jupiter Hub → Coimbatore Jupiter Hub",
      "originlat": 13.0827, "originlon": 80.2707,
      "destlat": 11.0168, "destlon": 76.9558,
      "currentlat": 12.1, "currentlon": 78.9,
      "status": "In_Transit",
      "progresspct": 40,
      "vehicleno": "TN-04-AX-8822",
      "itemcount": 154,
      "eta_minutes": 180
    }
  ]
}

currentlat/lon gives the truck's position on the map. Replace the fake animated truck with these real coordinates.


10. Arriving Vehicles

GET /api/v1/hub/inbound/vehicles

Returns trucks arriving at this hub (Dashboard → "Trucks Arriving" table).

Response:

{
  "success": true,
  "data": [
    {
      "vehicleno": "TN-04-AX-8822",
      "origin": "Chennai Jupiter Hub",
      "tripsheetid": 3,
      "status": "On the way",
      "eta": "2 hrs 30 min",
      "unloadedpct": 0,
      "itemcount": 154
    }
  ]
}

Status values: On the way · Unloading (Dispatched maps to On the way, Arrived maps to Unloading.)


11. Activity Feed

GET /api/v1/hub/activity?limit=10

Returns a real event feed for this hub (UNION across 4 sources: inbound, dispatch, exceptions, sorting).

Response:

{
  "success": true,
  "data": [
    { "time": "2026-07-04T11:24:00Z", "type": "exception", "text": "Exception raised: Damaged on DM-TRK-999999" },
    { "time": "2026-07-04T11:22:00Z", "type": "inbound",   "text": "Received parcel DM-TRK-999999 from Coimbatore Jupiter Hub" },
    { "time": "2026-07-04T11:20:00Z", "type": "dispatch",  "text": "Dispatched batch DM-TS-0003 to Mumbai Hub" }
  ],
  "total": 3
}

Type values: inbound · dispatch · exception · sorting Use type to pick the icon color — same color scheme as the existing mock.


12. Zones

GET /api/v1/hub/zones

Returns the delivery zone breakdown for this hub (Dashboard → "Delivery Areas").

Response:

{
  "success": true,
  "data": [
    { "zone": "641001", "zonename": "Gandhipuram", "parcels": 12, "milers": 2, "status": "Active" },
    { "zone": "641004", "zonename": "Peelamedu",   "parcels": 8,  "milers": 1, "status": "Active" },
    { "zone": "641012", "zonename": "Jupiter Nagar","parcels": 3,  "milers": 0, "status": "Need Milers" }
  ]
}

Status values: Active · Need Milers — use this to color the status chip.


13. Notifications

GET /api/v1/hub/notifications

Returns notifications for the header bell icon, generated from real events.

Response:

{
  "success": true,
  "data": [
    { "id": 1, "title": "Exception: Damaged", "type": "exception", "time": "3 min ago", "read": false },
    { "id": 2, "title": "Truck arriving from Chennai Hub", "type": "inbound", "time": "5 min ago", "read": false },
    { "id": 3, "title": "Pickup waiting 30+ min", "type": "alert", "time": "12 min ago", "read": false }
  ],
  "total": 3
}

Type values: exception · inbound · warning · alert

PATCH /api/v1/hub/notifications/:id/read

Mark a notification as read.

Response:

{ "success": true }

On bell click → GET /hub/notifications. On item click → PATCH /hub/notifications/:id/read, then mark it read in local state.


14. Hub Assignment Override

The old admin assign endpoint (POST /admin/bookings/:id/assign-miler) returned 403 for hub staff. Two new hub-scoped endpoints replace it.

POST /api/v1/hub/bookings/:id/assign-miler

Manual assign — hub staff picks the miler.

Request body:

{ "mileruserid": 4 }

Response:

{
  "success": true,
  "data": {
    "bookingid": 21,
    "mileruserid": 4,
    "milername": "Rajesh Kumar",
    "status": "Miler_Assigned"
  }
}

Use this on the Pickup Requests page when staff manually selects a miler from the dialog.

POST /api/v1/hub/bookings/:id/auto-assign

Auto-assign — AI engine picks the miler.

Request body:

{}

Response (success):

{
  "success": true,
  "data": {
    "mileruserid": 4,
    "milername": "Rajesh Kumar",
    "distance_km": 0.4
  }
}

Response (no miler) — HTTP 422:

{
  "success": false,
  "message": "No eligible miler found in range"
}

Response (timeout) — HTTP 202:

{
  "success": true,
  "message": "assignment in progress"
}

Use this for the Auto-Assign bulk button. Loop over selected booking IDs and call this for each one.

Backend implementation notes: shared AssignMilerToBooking() service (booking_assignment_service.go), TryAssignOnce() single-attempt wrapper on crm_assignment, assignedbyuserid is nullable for hub-initiated assignments, and AdminAssignMiler now sends FCM to the miler.


15. Hub Management (Doormile staff only)

These endpoints return 403 for partner accounts (is_doormile_staff = false). Use the is_doormile_staff flag from localStorage to show/hide the UI for these.

GET /api/v1/hub/hubs

List all hubs in the logged-in staff's city. Now includes lat/lon for map pins.

Response:

{
  "success": true,
  "data": [
    {
      "hubid": 1,
      "hubname": "Coimbatore Jupiter Hub",
      "hubtype": "sorting_center",
      "capacity": 50,
      "status": "active",
      "has_staff": true,
      "lat": 11.0168,
      "lon": 76.9558
    },
    {
      "hubid": 2,
      "hubname": "Coimbatore Spoke 1",
      "hubtype": "spoke",
      "capacity": 50,
      "status": "active",
      "has_staff": false,
      "lat": 11.0210,
      "lon": 76.9610
    }
  ]
}

Use lat/lon to place hub pins on the Live Map.

POST /api/v1/hub/hubs

Create a new hub in the staff's city.

Request body:

{
  "hubname": "Coimbatore RS Puram Hub",
  "hubtype": "spoke",
  "capacity": 30,
  "contact": "+91 98765 00000",
  "address": "RS Puram, Coimbatore",
  "pincode": "641002"
}

Note: applocationid (city) is set automatically from the logged-in staff's hub — cannot be overridden.

POST /api/v1/hub/staff

Create a hub staff login for any hub in the city.

Request body:

{
  "hubid": 2,
  "email": "hub.rspuram@doormile.in",
  "password": "securepassword",
  "displayname": "RS Puram Hub Staff",
  "tenantid": null
}

Set tenantid to the partner's tenant ID for partner accounts, null for Doormile staff.

Response:

{
  "success": true,
  "data": {
    "staffid": 6,
    "email": "hub.rspuram@doormile.in",
    "hubid": 2
  }
}

Error response shape

All errors follow the same shape:

{
  "success": false,
  "message": "human readable error description"
}
HTTP code Meaning
400 Bad request — invalid body or status transition
401 Token expired or missing → redirect to /login
202 Accepted — async assignment in progress
403 Forbidden — partner account trying Doormile-only endpoint
404 Resource not found (booking ID, miler ID, tracking no etc.)
422 Unprocessable — no eligible miler found in range
500 Server error — show generic "something went wrong" toast

Page-to-endpoint map (quick reference)

Page Endpoints called
Login POST /hub/login
Dashboard GET /hub/dashboard · GET /hub/inbound/vehicles · GET /hub/activity · GET /hub/zones
Receive Parcels GET /hub/inbound/today · POST /hub/bookings/:id/inbound
Pickup Requests GET /hub/bookings/unassigned · GET /hub/milers · POST /hub/bookings/:id/assign-miler · POST /hub/bookings/:id/auto-assign
Where Does It Go? (Routing) GET /hub/routing/:trackingno
Dispatch GET /hub/batches · POST /hub/batches · PATCH /hub/batches/:id/status
Milers GET /hub/milers · POST /admin/milers · PATCH /admin/milers/:id · DELETE /admin/milers/:id
Rider Routes GET /hub/rider-routes · GET /hub/milers/:id/route
Live Map GET /admin/milers/locations (poll 5s) · GET /hub/tripsheets/in-transit (poll 5s) · GET /hub/hubs
Header — Notifications GET /hub/notifications · PATCH /hub/notifications/:id/read
Hub Settings GET /hub/hubs · POST /hub/hubs · POST /hub/staff

Frontend implementation notes

Token storage:

// On login success:
localStorage.setItem('hub_token',       response.token)
localStorage.setItem('hub_context',     JSON.stringify(response.hub))
localStorage.setItem('hub_staff',       JSON.stringify(response.staff))
localStorage.setItem('hub_is_doormile', String(response.is_doormile_staff))

// On every API call:
headers: { 'Authorization': `Bearer ${localStorage.getItem('hub_token')}` }

// On logout or 401:
localStorage.clear()
navigate('/login')

Show hub name dynamically (not hardcoded):

const hub = JSON.parse(localStorage.getItem('hub_context'))
// hub.hubname → "Coimbatore Jupiter Hub"
// hub.city    → "Coimbatore"
// hub.hubid   → 1

Hide hub management UI for partners:

const isDoormile = localStorage.getItem('hub_is_doormile') === 'true'
// Show "Add New Hub" / "Add Staff" buttons only if isDoormile === true
// KPM partner account gets false → those buttons hidden

On 401 — token expired, force re-login:

if (response.status === 401) {
  localStorage.clear()
  window.location.href = '/login'
}

Polling intervals:

  • Miler locations → every 5 seconds
  • Truck positions → every 5 seconds (same call cycle)
  • Dashboard stats → every 30 seconds
  • Notifications → every 60 seconds

All endpoints live at https://api.doormile.com · Hub Console Backend v1.0 complete