# Mobile Frontend Implementation Spec This is the shortest path for frontend to implement the v2 mobile clients against the unified backend. Base URL: - `https://krow-api-v2-e3g6witsvq-uc.a.run.app` Use this doc together with: - [Authentication](./authentication.md) - [Unified API](./unified-api.md) - [Staff Shifts](./staff-shifts.md) ## 1) Global rules - Use unified routes only. - Send `Authorization: Bearer ` on protected routes. - Send `Idempotency-Key` on all write routes. - Do not call `/query/*`, `/commands/*`, or `/core/*` directly from frontend. ## 2) Core model frontend should assume - `order` is the client-facing request for staffing. - `shift` is the concrete scheduled unit of work under an order. - `shiftRole` is the role slot inside a shift that staff apply to. - `assignment` is the worker-to-shift record once a worker is attached. Important consequences: - `GET /staff/shifts/open` returns open shift-role opportunities. - `POST /staff/shifts/:shiftId/apply` must send the `roleId` from that response. - `GET /staff/orders/available` returns grouped order opportunities for atomic booking. - `POST /staff/orders/:orderId/book` must send the `roleId` from that response. - if order booking returns `422`, use `details.blockers` to explain why the worker is not eligible - `GET /client/shifts/scheduled` is the canonical timeline/read model for the client app. - `GET /client/orders/view` is a deprecated compatibility alias. - `POST /client/orders/:orderId/edit` and `POST /client/orders/:orderId/cancel` only affect future shifts. ## 3) Auth implementation ### Client app - sign in with `POST /auth/client/sign-in` - sign up with `POST /auth/client/sign-up` - hydrate session with `GET /auth/session` - sign out with `POST /auth/client/sign-out` ### Staff app - start phone auth with `POST /auth/staff/phone/start` - complete phone auth with `POST /auth/staff/phone/verify` - hydrate session with `GET /auth/session` - sign out with `POST /auth/staff/sign-out` Token refresh: - keep using Firebase client SDK refresh behavior - there is no backend `/auth/refresh` route ## 4) Client app screen mapping ### Home / dashboard - `GET /client/session` - `GET /client/dashboard` - `GET /client/reorders` ### Billing / payments - `GET /client/billing/accounts` - `GET /client/billing/invoices/pending` - `GET /client/billing/invoices/history` - `GET /client/billing/current-bill` - `GET /client/billing/savings` - `GET /client/billing/spend-breakdown` - `POST /client/billing/invoices/:invoiceId/approve` - `POST /client/billing/invoices/:invoiceId/dispute` ### Coverage - `GET /client/coverage?date=YYYY-MM-DD` - `GET /client/coverage/stats?date=YYYY-MM-DD` - `GET /client/coverage/core-team?date=YYYY-MM-DD` - `GET /client/coverage/incidents?startDate=YYYY-MM-DD&endDate=YYYY-MM-DD` - `GET /client/coverage/blocked-staff` - `GET /client/coverage/swap-requests?status=OPEN` - `GET /client/coverage/dispatch-teams` - `GET /client/coverage/dispatch-candidates?shiftId=uuid&roleId=uuid` - `POST /client/coverage/reviews` - `POST /client/coverage/late-workers/:assignmentId/cancel` - `POST /client/coverage/swap-requests/:swapRequestId/resolve` - `POST /client/coverage/swap-requests/:swapRequestId/cancel` - `POST /client/coverage/dispatch-teams/memberships` - `DELETE /client/coverage/dispatch-teams/memberships/:membershipId` Use `POST /client/coverage/reviews` when the business is rating a worker after coverage review. Payload may include: ```json { "staffId": "uuid", "assignmentId": "uuid", "rating": 4, "feedback": "Strong performance on the shift", "markAsFavorite": true, "markAsBlocked": false } ``` If `markAsFavorite` is `true`, backend adds that worker to the business favorites list. If `markAsFavorite` is `false`, backend removes them from that list. If `markAsBlocked` is `true`, backend blocks that worker for that business and rejects future apply or assign attempts until a later review sets `markAsBlocked: false`. Swap-management rule: - use `GET /client/coverage/swap-requests` as the client review feed - use `GET /client/coverage/dispatch-candidates` for the ranked replacement list - use `POST /client/coverage/swap-requests/:swapRequestId/resolve` when ops selects a replacement - use `POST /client/coverage/swap-requests/:swapRequestId/cancel` when ops wants to close the swap request without replacement Dispatch-priority rule: 1. `CORE` 2. `CERTIFIED_LOCATION` 3. `MARKETPLACE` ### Orders - `GET /client/shifts/scheduled` - `GET /client/orders/view` deprecated alias - `GET /client/orders/:orderId/reorder-preview` - `POST /client/orders/one-time` - `POST /client/orders/recurring` - `POST /client/orders/permanent` - `POST /client/orders/:orderId/edit` - `POST /client/orders/:orderId/cancel` Rapid-order flow: - use `POST /rapid-orders/process` for the single-call transcribe-and-parse flow ### Hubs and managers - `GET /client/hubs` - `GET /client/cost-centers` - `GET /client/hubs/:hubId/managers` - `GET /client/team-members` - `POST /client/shift-managers` - `POST /client/hubs` - `PUT /client/hubs/:hubId` - `DELETE /client/hubs/:hubId` - `POST /client/hubs/:hubId/assign-nfc` - `POST /client/hubs/:hubId/managers` `POST /client/shift-managers` is the fastest path to create an invited manager identity for a business. If `hubId` is provided, backend also links that manager to the hub. ### Reports - `GET /client/reports/summary?date=YYYY-MM-DD` - `GET /client/reports/daily-ops?date=YYYY-MM-DD` - `GET /client/reports/spend?date=YYYY-MM-DD` - `GET /client/reports/coverage?date=YYYY-MM-DD` - `GET /client/reports/forecast?date=YYYY-MM-DD` - `GET /client/reports/performance?date=YYYY-MM-DD` - `GET /client/reports/no-show?date=YYYY-MM-DD` ## 5) Staff app screen mapping ### Home / dashboard - `GET /staff/session` - `GET /staff/dashboard` - `GET /staff/profile-completion` ### Availability - `GET /staff/availability` - `PUT /staff/availability` - `POST /staff/availability/quick-set` ### Find shifts - `GET /staff/orders/available` - `GET /staff/orders/:orderId` - `POST /staff/orders/:orderId/book` - `GET /staff/shifts/open` - `POST /staff/shifts/:shiftId/apply` Rule: - use `GET /staff/orders/:orderId` as the source of truth for the order details page - send the `roleId` from the order-detail response when booking an order - this `roleId` is the role catalog id for grouped order booking - if booking fails with `422`, render `details.blockers` and keep the worker on the review screen - send the `roleId` from the open-shifts response only when applying to one shift - that route still uses the concrete `shift_roles.id` ### My shifts - `GET /staff/shifts/pending` - `GET /staff/shifts/assigned` - `GET /staff/shifts/cancelled` - `GET /staff/shifts/completed` - `GET /staff/shifts/:shiftId` - `POST /staff/shifts/:shiftId/accept` - `POST /staff/shifts/:shiftId/decline` - `POST /staff/shifts/:shiftId/request-swap` - `POST /staff/shifts/:shiftId/submit-for-approval` Current swap behavior: - backend records the swap request - assignment moves to `SWAP_REQUESTED` - shift becomes visible in the replacement pool - client/ops can review and resolve swap requests through the coverage endpoints - if the swap request expires without coverage, backend auto-cancels it and alerts both the manager path and the original worker ### Clock in / clock out - `GET /staff/clock-in/shifts/today` - `GET /staff/clock-in/status` - `POST /staff/clock-in` - `POST /staff/clock-out` - `POST /staff/location-streams` Frontend should respect: - `clockInMode` - `allowClockInOverride` - `latitude` - `longitude` - `geofenceRadiusMeters` - `nfcTagId` Clock-in proof rules: - use `nfcTagId` for NFC clocking - use `latitude`, `longitude`, and `accuracyMeters` for geolocation clocking - send `overrideReason` only when a geofence override is allowed - send `proofNonce` and `proofTimestamp` on attendance writes ### Payments - `GET /staff/payments/summary` - `GET /staff/payments/history` - `GET /staff/payments/chart` ### Profile - `GET /staff/profile/sections` - `GET /staff/profile/personal-info` - `GET /staff/profile/industries` - `GET /staff/profile/skills` - `GET /staff/profile/documents` - `GET /staff/profile/attire` - `GET /staff/profile/tax-forms` - `GET /staff/profile/emergency-contacts` - `GET /staff/profile/certificates` - `GET /staff/profile/bank-accounts` - `GET /staff/profile/benefits` - `GET /staff/profile/benefits/history` - `GET /staff/profile/time-card` - `GET /staff/profile/privacy` - `PUT /staff/profile/personal-info` - `PUT /staff/profile/experience` - `PUT /staff/profile/locations` - `POST /staff/profile/emergency-contacts` - `PUT /staff/profile/emergency-contacts/:contactId` - `PUT /staff/profile/tax-forms/:formType` - `POST /staff/profile/tax-forms/:formType/submit` - `POST /staff/profile/bank-accounts` - `PUT /staff/profile/privacy` Document model rule: - `GET /staff/profile/documents` returns only documents - `GET /staff/profile/attire` returns attire items - `GET /staff/profile/tax-forms` returns tax-form rows - `GET /staff/profile/certificates` returns certificates ### FAQ - `GET /staff/faqs` - `GET /staff/faqs/search?q=...` ## 6) Upload implementation For documents, attire, and certificates: 1. `POST /upload-file` 2. `POST /create-signed-url` 3. upload file bytes to storage with the signed URL 4. `POST /verifications` 5. finalize with: - `PUT /staff/profile/documents/:documentId/upload` - `PUT /staff/profile/attire/:documentId/upload` - `POST /staff/profile/certificates` Use the verification-linked file as the source of truth. ## 7) What frontend should not assume - do not assume order edit mutates past shifts - do not assume swap resolution is complete beyond the request step - do not assume raw `/query/*` or `/commands/*` routes are stable for app integration - do not assume blocked workers can still apply to future shifts for that business ## 8) Demo reset To reset dev demo data: ```bash source ~/.nvm/nvm.sh nvm use 23.5.0 cd backend/command-api npm run seed:v2-demo ```