Etap 4.6 — Mobile-parity API¶
The kiosk runs every flow through machine-API-key auth — the kiosk is the machine. For the future Android apps (courier app, employee app) we ship a parallel JWT-authenticated surface that drives the same backend state machine. This file lists every endpoint a mobile client would hit.
All endpoints are JWT-bearer auth (Keycloak smartbox realm). Per-shipment authorisation is enforced inside the handler — @Roles is too coarse to express "the user is the recipient of this specific shipment".
Lifecycle of a parcel from a courier's phone¶
``` courier POST /shipments → status=CREATED POST /shipments/:id/assign-locker → status=ASSIGNED, locker reserved POST /me/shipments/:id/open-locker → OPEN_LOCKER task fired on the machine (operator drops parcel, closes door) courier POST /me/shipments/:id/confirm-deposit → status=DEPOSITED, locker OCCUPIED
…notification to recipient (etap 7)…
recipient POST /me/shipments/:id/open-locker → OPEN_LOCKER task fired on the machine (recipient retrieves parcel, closes door) recipient POST /me/shipments/:id/confirm-pickup → status=PICKED_UP, locker FREE ```
All transitions are audited with actorType=USER, distinguishable from the kiosk machine flow (actorType=MACHINE) by their action prefix:
shipment.create(existing) — courier createdshipment.assign_locker(existing)shipment.remote_open—me/shipments/:id/open-lockershipment.remote_confirm_depositshipment.remote_confirm_pickup
Endpoint reference¶
POST /shipments¶
Create a new shipment. Allowed for any authenticated user (employee, admin, courier). Generates trackingId + signed QR payload.
```json // request
// response 201
{
"id": "
Audit: shipment.create.
POST /shipments/:id/assign-locker¶
Reserve the first FREE locker on a target machine for an existing CREATED shipment. Allowed for the creator or admin.
json
// request
{ "machineId": "<uuid>" }
After this call the shipment is ASSIGNED and the locker is RESERVED. Audit: shipment.assign_locker.
POST /me/shipments/:id/open-locker¶
Issue an OPEN_LOCKER task on the assigned machine. The kiosk's agent (or future Modbus driver) picks the task up within 30 seconds and drives LockerHardware.open().
Authorisation rules:
| Status | Who can call |
|---|---|
ASSIGNED (deposit) |
sender, courier, admin |
DEPOSITED (pickup) |
recipient, admin |
| anything else | rejected as 409 Conflict |
json
// response 201
{ "ok": true, "taskId": "<uuid>", "lockerNumber": 7, "reason": "deposit" }
Audit: shipment.remote_open.
POST /me/shipments/:id/confirm-deposit¶
Sender / courier presses "I closed the door" in the mobile app. Flips ASSIGNED → DEPOSITED + locker OCCUPIED. Mobile counterpart of the kiosk's machine-key POST /shipments/:id/deposited.
json
// response 200 — full Shipment row
{ "id": "...", "status": "DEPOSITED", "depositedAt": "...", ... }
Audit: shipment.remote_confirm_deposit.
POST /me/shipments/:id/confirm-pickup¶
Recipient presses "I took my parcel". Flips DEPOSITED → PICKED_UP + locker FREE.
Audit: shipment.remote_confirm_pickup.
POST /shipments/:id/cancel (existing)¶
Sender or admin cancels an in-flight shipment. Frees the locker if one was assigned.
GET /shipments?scope=mine&status=…&search=…&page=…&perPage=… (existing)¶
User's own shipments. Admin / courier with scope=all see every row, others see only theirs (created or received). Returns the paginated envelope {shipments, total, page, perPage}. Supported query params:
scope—mine(default) orall(ADMIN / COURIER only).status— repeatable; matches any of the listed statuses. Omitting it implies the active subset (CREATED,ASSIGNED,DEPOSITED).search— ILIKE on tracking ID / recipient / sender (case-insensitive).page— 0-indexed, default0.perPage— one of25,50,100,250(clamped server-side), default25.
Notes for the Android client¶
- Use server-issued OPEN_LOCKER tasks. Don't try to open the locker from the phone directly — the agent is the sole owner of
LockerHardware. Mobile flow is: kick offopen-locker→ pollGET /shipments/:iduntilstatusflips (DEPOSITED for sender, PICKED_UP for recipient) → confirm via the matching endpoint when the user closes the door. - No offline mode on mobile. The kiosk's offline cache is a special case for the physical install on a wall. Mobile phones have networks; if there's no network the user just retries.
- Audit / events fire identically. Notifications (real e-mail / SMS dispatch is queued for etap 8 — today
NotificationWorkeronly audits "would-send" rows) will treat mobile-driven and kiosk-driven transitions the same way: the recipient's "your parcel is ready" message will go out onshipment.depositedregardless of who flipped it.