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
|
# 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`.
|
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.
|
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 |
|
| `0` / null | Doormile's own staff | everything, all tenants |
|
||||||
| set (e.g. `13`) | a client's console login | only that tenant's rows |
|
| 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
|
Scoping is applied server-side. Build the UI as if the API returns exactly what
|
||||||
`?tenantid=` or a body `tenantid` — on writes the server overwrites the field
|
the account is allowed to see, because it does.
|
||||||
with the token's tenant. 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`
|
Note the group is registered under a stale `// all open, no token required`
|
||||||
comment in `routes.go`; the comment is wrong, `AuthMiddleware` is applied.
|
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)
|
## 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 |
|
| 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` | |
|
||||||
| PUT | `/admin/milers/:id/block` | |
|
| 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** |
|
| POST | `/admin/milers/:id/notify` | `{ title, message }` — `:id` is the **milerprofileid** |
|
||||||
|
|
||||||
|
Every `:id` here is the **milerprofileid**, not the userid.
|
||||||
|
|
||||||
```jsonc
|
```jsonc
|
||||||
POST /admin/milers
|
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
|
## Bookings
|
||||||
|
|
||||||
| Method | Path | Notes |
|
| Method | Path | Notes |
|
||||||
@@ -167,7 +230,8 @@ POST /admin/milers
|
|||||||
| GET | `/admin/bookings` | tenant-scoped list |
|
| GET | `/admin/bookings` | tenant-scoped list |
|
||||||
| POST | `/admin/expressbooking` | create one — passes CityGate |
|
| POST | `/admin/expressbooking` | create one — passes CityGate |
|
||||||
| POST | `/admin/expressbooking/bulk` | `{ "bookings": [ ... ] }`, max 200, per-row results |
|
| 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-miler` | |
|
||||||
| POST | `/admin/bookings/:id/assign-vehicle` | |
|
| POST | `/admin/bookings/:id/assign-vehicle` | |
|
||||||
| PUT | `/admin/bookings/:id/status` | |
|
| PUT | `/admin/bookings/:id/status` | |
|
||||||
@@ -208,14 +272,42 @@ Rules worth knowing:
|
|||||||
- Auto-assignment fires after commit as a background retry loop (5 attempts,
|
- Auto-assignment fires after commit as a background retry loop (5 attempts,
|
||||||
2 min apart). The response returns before a rider is attached.
|
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
|
## Consignments
|
||||||
|
|
||||||
| Method | Path |
|
| Method | Path | Notes |
|
||||||
|---|---|
|
|---|---|---|
|
||||||
| GET | `/admin/consignments` |
|
| GET | `/admin/consignments` | `?tenantid=` for staff |
|
||||||
| GET | `/admin/consignments/:id` |
|
| GET | `/admin/consignments/:id` | |
|
||||||
| GET | `/admin/consignments/track/:trackingno` |
|
| GET | `/admin/consignments/:id/logs` | event history + telemetry + proof |
|
||||||
| PUT | `/admin/consignments/:id/status` |
|
| 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)
|
## Tripsheets (hub-to-hub transport)
|
||||||
|
|
||||||
@@ -283,10 +375,20 @@ Rules worth knowing:
|
|||||||
ConsignmentException, DoormilePricing, CarrierPricing. Partner and Tenant are
|
ConsignmentException, DoormilePricing, CarrierPricing. Partner and Tenant are
|
||||||
hard-deleted.
|
hard-deleted.
|
||||||
|
|
||||||
## Not exercised yet
|
## Verified against production, 2026-08-06
|
||||||
|
|
||||||
Roughly half these routes have never had a real request against them. Exercised
|
Run as both a client login (`info@dailygrubs.com`, tenant 13) and Doormile
|
||||||
end-to-end so far: login, dashboard, reports, tenants + locations, milers
|
staff (`developer@doormile.com`, tenant 0):
|
||||||
(create/list/notify), expressbooking (single), bookings list/detail,
|
|
||||||
assign-miler, consignments, profile password. Treat the rest as written but
|
- dashboard, reports, milers, milers/summary, milers/:id, milers/:id/logs,
|
||||||
unproven.
|
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