docs: refresh the express-console API reference
Adds the five rider/tracking endpoints, the ?tenantid= staff filter and the 403-vs-404 refusal rules, and replaces the guesswork coverage note with what was actually run against production on 2026-08-06. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,8 +1,10 @@
|
||||
# Doormile Express Console — API reference
|
||||
|
||||
The console surface only (`/admin/*`). 89 routes: 1 login + 88 authenticated.
|
||||
The console surface only (`/admin/*`). 94 routes: 1 login + 93 authenticated.
|
||||
Everything is under `https://api.doormile.com/api/v1`.
|
||||
|
||||
Verified against the build deployed 2026-08-06 12:41 IST.
|
||||
|
||||
Miler-app, hub-console, customer-app and CRM routes are not in this document.
|
||||
|
||||
---
|
||||
@@ -30,10 +32,28 @@ gets 401.
|
||||
| `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.
|
||||
Scoping is applied server-side. Build the UI as if the API returns exactly what
|
||||
the account is allowed to see, because it does.
|
||||
|
||||
**`?tenantid=` — the staff-only filter.** Doormile staff pass it to narrow any
|
||||
of the list/summary endpoints to one client:
|
||||
|
||||
```
|
||||
GET /admin/milers?tenantid=13 → DailyGrubs' 5 riders
|
||||
GET /admin/dashboard?tenantid=13 → DailyGrubs' counters
|
||||
GET /admin/reports?tenantid=13&from=…&to=…
|
||||
```
|
||||
|
||||
Supported on `dashboard`, `reports`, `milers`, `milers/summary`, `customers`,
|
||||
`bookings`, `consignments`. Omit it for the whole network.
|
||||
|
||||
A **client** login passing another tenant's id gets **403** — not their own data
|
||||
silently relabelled. Passing their own id, or omitting it, works normally.
|
||||
|
||||
Reading one resource by id that belongs to another tenant returns **404**, not
|
||||
403, so ids outside your own fleet aren't probeable. This covers bookings,
|
||||
consignments and riders, on reads *and* writes — `POST /admin/bookings/:id/
|
||||
assign-miler` on someone else's booking is refused before the write, not after.
|
||||
|
||||
Note the group is registered under a stale `// all open, no token required`
|
||||
comment in `routes.go`; the comment is wrong, `AuthMiddleware` is applied.
|
||||
@@ -133,16 +153,21 @@ Nagercoil is 5; read the rest from `GET /admin/hubs` rather than hardcoding.
|
||||
|
||||
## Milers (riders)
|
||||
|
||||
| Method | Path | Body |
|
||||
| Method | Path | Body / notes |
|
||||
|---|---|---|
|
||||
| GET | `/admin/milers` | tenant-scoped |
|
||||
| GET | `/admin/milers` | tenant-scoped; `?applocationid=&hubid=&tenantid=` |
|
||||
| GET | `/admin/milers/summary` | **the roster table** — see below |
|
||||
| POST | `/admin/milers` | see below |
|
||||
| GET | `/admin/milers/:id` | |
|
||||
| GET | `/admin/milers/:id` | 404 outside your fleet |
|
||||
| GET | `/admin/milers/:id/logs` | GPS trail — see below |
|
||||
| GET | `/admin/milers/:id/activity` | one rider's detail — see below |
|
||||
| PUT | `/admin/milers/:id` | |
|
||||
| PUT | `/admin/milers/:id/block` | |
|
||||
| PUT | `/admin/milers/:id/assign-vehicle` | |
|
||||
| PUT | `/admin/milers/:id/assign-vehicle` | `{ vehicleid }` |
|
||||
| POST | `/admin/milers/:id/notify` | `{ title, message }` — `:id` is the **milerprofileid** |
|
||||
|
||||
Every `:id` here is the **milerprofileid**, not the userid.
|
||||
|
||||
```jsonc
|
||||
POST /admin/milers
|
||||
{
|
||||
@@ -160,6 +185,44 @@ POST /admin/milers
|
||||
}
|
||||
```
|
||||
|
||||
### The rider screens
|
||||
|
||||
These are the express-console equivalents of jupiter's `getridersummary` and
|
||||
its rider/delivery logs.
|
||||
|
||||
**`GET /admin/milers/summary?from=&to=&applocationid=&hubid=&tenantid=`**
|
||||
|
||||
One row per rider — live state on the left, range totals on the right. Defaults
|
||||
to today. This is the rider list screen.
|
||||
|
||||
```jsonc
|
||||
{ "milerprofileid": 78, "userid": 41, "displayname": "Murali P",
|
||||
"phone": "…", "availabilitystatus": "Offline", "defaultvehicletype": "Bike",
|
||||
"hubid": null, "hubname": "", "rating": 5,
|
||||
"onduty": false, "dutystartedat": null,
|
||||
"currentlatitude": 11.0163, "currentlongitude": 77.0147,
|
||||
"lastlocationupdatedat": "…", "lastpingat": "2026-08-05T18:10:00Z",
|
||||
"assigned": 0, "accepted": 0, "rejected": 0,
|
||||
"completed": 1, "cancelled": 0, "delivered": 1,
|
||||
"riderkms": 0, "ridercharges": 0 }
|
||||
```
|
||||
|
||||
`lastpingat` comes from Redis telemetry, `lastlocationupdatedat` from the
|
||||
profile — they differ, and the first is the better staleness signal.
|
||||
|
||||
**`GET /admin/milers/:id/logs?from=&to=&limit=`**
|
||||
|
||||
The GPS trail. `limit` defaults to 500, caps at 5000. Returns
|
||||
`{ data: [MilerLog…], total, distancekm, miler: {…}, from, to }` where
|
||||
`distancekm` is the haversine sum over consecutive points. Telemetry coords are
|
||||
**strings**, since that's what the rider app sends.
|
||||
|
||||
**`GET /admin/milers/:id/activity?from=&to=`**
|
||||
|
||||
One rider's detail page: `assignments[]`, the `bookings[]` behind them,
|
||||
`dutylogs[]`, `breaklogs[]`, `lastpingat`, and `totals: { assignments,
|
||||
delivered, riderkms, ridercharges, dutyminutes }`.
|
||||
|
||||
## Bookings
|
||||
|
||||
| Method | Path | Notes |
|
||||
@@ -167,7 +230,8 @@ POST /admin/milers
|
||||
| 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 |
|
||||
| GET | `/admin/bookings/:id` | 404 if outside your tenant |
|
||||
| GET | `/admin/bookings/:id/track` | **the tracking screen** — see below |
|
||||
| POST | `/admin/bookings/:id/assign-miler` | |
|
||||
| POST | `/admin/bookings/:id/assign-vehicle` | |
|
||||
| PUT | `/admin/bookings/:id/status` | |
|
||||
@@ -208,14 +272,42 @@ Rules worth knowing:
|
||||
- Auto-assignment fires after commit as a background retry loop (5 attempts,
|
||||
2 min apart). The response returns before a rider is attached.
|
||||
|
||||
### Tracking one booking end to end
|
||||
|
||||
**`GET /admin/bookings/:id/track`** — one call for the whole lifecycle, so the
|
||||
tracking screen doesn't have to stitch five requests together:
|
||||
|
||||
```jsonc
|
||||
{
|
||||
"booking": { …with parcels, serviceoptions, payments… },
|
||||
"assignments": [ { mileruserid, assignmentstatus, assignedat, acceptedat,
|
||||
completedat, riderkms, ridercharges } ], // every attempt
|
||||
"riders": [ { userid, displayname, phone, … } ], // one per attempt
|
||||
"livelocation": { "latitude": …, "longitude": … }, // null if no ping in 30 min
|
||||
"lastpingat": "…",
|
||||
"consignment": { … }, // present once picked up
|
||||
"history": [ { eventstatus, remarks, createdat } ],
|
||||
"telemetry": [ ConsignmentLog… ],
|
||||
"deliveryproof": { deliveredtoname, photourl, geolatitude, … }
|
||||
}
|
||||
```
|
||||
|
||||
`assignments` holds **every** attempt, not just the current one — a rejected
|
||||
first assignment is exactly what ops needs when asking why a pickup was slow.
|
||||
|
||||
## Consignments
|
||||
|
||||
| Method | Path |
|
||||
|---|---|
|
||||
| GET | `/admin/consignments` |
|
||||
| GET | `/admin/consignments/:id` |
|
||||
| GET | `/admin/consignments/track/:trackingno` |
|
||||
| PUT | `/admin/consignments/:id/status` |
|
||||
| Method | Path | Notes |
|
||||
|---|---|---|
|
||||
| GET | `/admin/consignments` | `?tenantid=` for staff |
|
||||
| GET | `/admin/consignments/:id` | |
|
||||
| GET | `/admin/consignments/:id/logs` | event history + telemetry + proof |
|
||||
| GET | `/admin/consignments/track/:trackingno` | |
|
||||
| PUT | `/admin/consignments/:id/status` | |
|
||||
|
||||
`/:id/logs` returns `{ consignment, history[], telemetry[], deliveryproof? }` —
|
||||
the durable Postgres event log and the rider's Redis trail in one response,
|
||||
rather than making the console reconcile two stores.
|
||||
|
||||
## Tripsheets (hub-to-hub transport)
|
||||
|
||||
@@ -283,10 +375,20 @@ Rules worth knowing:
|
||||
ConsignmentException, DoormilePricing, CarrierPricing. Partner and Tenant are
|
||||
hard-deleted.
|
||||
|
||||
## Not exercised yet
|
||||
## Verified against production, 2026-08-06
|
||||
|
||||
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.
|
||||
Run as both a client login (`info@dailygrubs.com`, tenant 13) and Doormile
|
||||
staff (`developer@doormile.com`, tenant 0):
|
||||
|
||||
- dashboard, reports, milers, milers/summary, milers/:id, milers/:id/logs,
|
||||
milers/:id/activity, customers, bookings, bookings/:id, bookings/:id/track,
|
||||
consignments, consignments/:id/logs, tenants, tenants/:id/locations
|
||||
- `?tenantid=` narrowing on all of the above, for staff
|
||||
- 403 on a client requesting another tenant; 404 on cross-tenant reads *and*
|
||||
on `assign-miler` / `status` / `cancel` writes, with the target row confirmed
|
||||
unmodified afterwards
|
||||
|
||||
Still unproven: tripsheets, exceptions, vehicles, competitor-branches,
|
||||
carrier-pricing, doormile-pricing, app-users CRUD, partner CRUD, bulk booking
|
||||
create/cancel, `assign-vehicle`, `block`. Written, compiled, never called with
|
||||
a real request.
|
||||
|
||||
Reference in New Issue
Block a user