Doormile Express Console — API reference
The console surface only (/admin/*). 89 routes: 1 login + 88 authenticated.
Everything is under https://api.doormile.com/api/v1.
Miler-app, hub-console, customer-app and CRM routes are not in this document.
Auth
Returns { success, token, user: { id, name, email, role, tenantid } }.
Send it on every other call as Authorization: Bearer <token>.
The token is a JWT carrying userid, email, roleid, tenantid, configid.
Roles allowed on this group: 1 admin, 3 manager, 4 executive. Anything else
gets 401.
Tenant scoping — read this before wiring any list screen
tenantid in the token decides what the account can see:
Token tenantid |
Who |
Sees |
0 / null |
Doormile's own staff |
everything, all tenants |
set (e.g. 13) |
a client's console login |
only that tenant's rows |
Scoping is applied server-side. A client login cannot widen it by sending
?tenantid= or a body tenantid — on writes the server overwrites the field
with the token's tenant. Build the UI as if the API returns exactly what the
account is allowed to see, because it does.
Note the group is registered under a stale // all open, no token required
comment in routes.go; the comment is wrong, AuthMiddleware is applied.
Dashboard, profile, reports
| Method |
Path |
Notes |
| GET |
/admin/dashboard |
counts + today's numbers |
| GET |
/admin/reports |
?from=YYYY-MM-DD&to=YYYY-MM-DD, defaults to today (IST) |
| GET |
/admin/profile |
current account |
| GET |
/admin/me |
alias of the above |
| PUT |
/admin/profile/password |
{ "current_password": "...", "new_password": "..." } — snake_case |
App users (staff logins)
| Method |
Path |
| GET |
/admin/users |
| POST |
/admin/users |
| PUT |
/admin/users/:id |
| DELETE |
/admin/users/:id |
Partners (fleet / rider suppliers)
Not the same thing as a tenant. A partner supplies vehicles and riders; a tenant
is a client Doormile delivers for.
| Method |
Path |
Body |
| GET |
/admin/partners |
|
| POST |
/admin/partners |
{ partnername, partnertypeid, contactno, status } |
| GET |
/admin/partners/:id |
|
| PUT |
/admin/partners/:id |
|
| DELETE |
/admin/partners/:id |
hard delete — no soft-delete column |
Tenants (client companies)
| Method |
Path |
Body |
| GET |
/admin/tenants |
|
| POST |
/admin/tenants |
{ tenantname, primaryemail, primarycontact, status, requiredeliveryotp } |
| GET |
/admin/tenants/:id |
|
| PUT |
/admin/tenants/:id |
requiredeliveryotp is a pointer — omit it to leave the setting alone |
| DELETE |
/admin/tenants/:id |
hard delete |
| GET |
/admin/tenants/:id/locations |
the client's sites (kitchens, branches, depots) |
| POST |
/admin/tenants/:id/locations |
{ locationname, address, city, state, pincode, latitude, longitude, isprimary, status } |
| PUT |
/admin/tenantlocations/:id |
note: not nested under the tenant |
requiredeliveryotp is opt-in per tenant and off by default. DailyGrubs runs
without delivery OTP by decision.
Tenant customers (a client's own end customers)
| Method |
Path |
Body |
| GET |
/admin/tenantcustomers |
|
| POST |
/admin/tenantcustomers |
{ firstname, lastname, phone, email } |
| GET |
/admin/tenantcustomers/:id |
|
| PUT |
/admin/tenantcustomers/:id |
|
| DELETE |
/admin/tenantcustomers/:id |
|
B2C app customers
| Method |
Path |
| GET |
/admin/customers |
| PATCH |
/admin/customers/:id |
Tenant-scoped through their bookings — a client login sees only customers who
have ordered through them.
Hubs
| Method |
Path |
Body |
| GET |
/admin/hubs |
|
| POST |
/admin/hubs |
{ hubname, hubtype, applocationid, contactno, address, latitude, longitude, pincode, status } |
| GET |
/admin/hubs/:id |
|
| PUT |
/admin/hubs/:id |
|
| DELETE |
/admin/hubs/:id |
soft delete |
hubtype: sorting_center | delivery_hub. applocationid is the city —
Nagercoil is 5; read the rest from GET /admin/hubs rather than hardcoding.
Vehicles
| Method |
Path |
Body |
| GET |
/admin/vehicles |
|
| POST |
/admin/vehicles |
{ vehicleno, vehicletype, maxweight, maxvolume, partnerid, batterypercentage, status } |
| GET |
/admin/vehicles/:id |
|
| PUT |
/admin/vehicles/:id |
|
| DELETE |
/admin/vehicles/:id |
soft delete |
Milers (riders)
| Method |
Path |
Body |
| GET |
/admin/milers |
tenant-scoped |
| POST |
/admin/milers |
see below |
| GET |
/admin/milers/:id |
|
| PUT |
/admin/milers/:id |
|
| PUT |
/admin/milers/:id/block |
|
| PUT |
/admin/milers/:id/assign-vehicle |
|
| POST |
/admin/milers/:id/notify |
{ title, message } — :id is the milerprofileid |
Bookings
| Method |
Path |
Notes |
| GET |
/admin/bookings |
tenant-scoped list |
| POST |
/admin/expressbooking |
create one — passes CityGate |
| POST |
/admin/expressbooking/bulk |
{ "bookings": [ ... ] }, max 200, per-row results |
| GET |
/admin/bookings/:id |
403 if outside your tenant |
| POST |
/admin/bookings/:id/assign-miler |
|
| POST |
/admin/bookings/:id/assign-vehicle |
|
| PUT |
/admin/bookings/:id/status |
|
| POST |
/admin/bookings/:id/cancel |
|
| POST |
/admin/bookings/bulk-cancel |
|
Rules worth knowing:
parcels must be non-empty and tenantid must exist.
- Pickup address + pincode are required unless
pickuplocationid supplies them.
- A
pickuplocationid belonging to another tenant is rejected.
- CityGate: the pickup pincode prefix must be an open city —
641
Coimbatore, 600 Chennai, 560 Bengaluru, 500 Hyderabad, 629 Nagercoil.
Any other prefix is refused at the middleware, before the handler runs.
- Matching 3-digit pickup and delivery prefixes = hyperlocal, and the parcel goes
straight to
Out_for_Delivery at pickup instead of routing via a hub.
- Auto-assignment fires after commit as a background retry loop (5 attempts,
2 min apart). The response returns before a rider is attached.
Consignments
| Method |
Path |
| GET |
/admin/consignments |
| GET |
/admin/consignments/:id |
| GET |
/admin/consignments/track/:trackingno |
| PUT |
/admin/consignments/:id/status |
Tripsheets (hub-to-hub transport)
| Method |
Path |
Body |
| GET |
/admin/tripsheets |
|
| POST |
/admin/tripsheets |
{ sourcehubid, destinationhubid, vehicleid, driveruserid } |
| GET |
/admin/tripsheets/:id |
|
| POST |
/admin/tripsheets/:id/items |
{ consignmentid } |
| DELETE |
/admin/tripsheets/:id/items/:itemid |
|
| PUT |
/admin/tripsheets/:id/dispatch |
|
| PUT |
/admin/tripsheets/:id/arrive |
|
Pricing
| Method |
Path |
Notes |
| GET |
/admin/pricing |
tenant pricing rules |
| POST |
/admin/pricing |
{ tenantid, applocationid, vehicletype, baseprice, baseweight, priceperkg, basedistance, priceperkm, handlingcharges, effectivefrom, effectiveto, currency, priority, status } |
| PUT |
/admin/pricing/:id |
|
| DELETE |
/admin/pricing/:id |
|
| POST |
/admin/pricing/simulate |
quote without creating anything |
| POST |
/admin/pricing/quote |
same handler as simulate |
| GET |
/admin/doormile-pricing |
Doormile's own bands |
| POST |
/admin/doormile-pricing |
|
| PUT |
/admin/doormile-pricing/:id |
|
| DELETE |
/admin/doormile-pricing/:id |
soft delete |
Exceptions
| Method |
Path |
Body |
| GET |
/admin/exceptions |
|
| POST |
/admin/exceptions |
{ consignmentid, tripsheetid, hubid, exceptiontype, severity, description } |
| GET |
/admin/exceptions/:id |
|
| PUT |
/admin/exceptions/:id/status |
{ resolution, status } — Resolved | Closed |
exceptiontype: Lost, Damaged, Misrouted, Receiver_Refused,
Missing_Contents, Undeliverable. severity: Low, Medium, High,
Critical.
Competitive intel
| Method |
Path |
| GET/POST |
/admin/competitor-branches |
| PUT/DELETE |
/admin/competitor-branches/:id |
| GET/POST |
/admin/carrier-pricing |
| PUT/DELETE |
/admin/carrier-pricing/:id |
Conventions across every endpoint
- Envelope:
{ "success": true, "data": ... } on success,
{ "success": false, "message": "..." } on failure. Lists add total,
paginated lists add page.
- Pagination:
?pageno=1&pagesize=100. Default 500, cap 1000.
- Rate limits: 300/min per IP globally, 10/min shared across all credential
endpoints. Behind the ingress this keys on the proxy IP unless
TRUSTED_PROXIES is set.
- Timestamps are IST (
Asia/Kolkata) wall-clock in timestamp without time zone columns. Send dates as YYYY-MM-DD, not epochs.
- Soft delete exists on Hub, Vehicle, Consignment, Tripsheet, TripsheetItem,
ConsignmentException, DoormilePricing, CarrierPricing. Partner and Tenant are
hard-deleted.
Not exercised yet
Roughly half these routes have never had a real request against them. Exercised
end-to-end so far: login, dashboard, reports, tenants + locations, milers
(create/list/notify), expressbooking (single), bookings list/detail,
assign-miler, consignments, profile password. Treat the rest as written but
unproven.