# Unified API V2 Frontend should use this service as the single base URL: - `https://krow-api-v2-933560802882.us-central1.run.app` The gateway keeps backend services separate internally, but frontend should treat it as one API. ## 1) Auth routes ### Client auth - `POST /auth/client/sign-in` - `POST /auth/client/sign-up` - `POST /auth/client/sign-out` ### Staff auth - `POST /auth/staff/phone/start` - `POST /auth/staff/phone/verify` - `POST /auth/staff/sign-out` ### Shared auth - `GET /auth/session` - `POST /auth/sign-out` ## 2) Client routes ### Client reads - `GET /client/session` - `GET /client/dashboard` - `GET /client/reorders` - `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` - `GET /client/coverage` - `GET /client/coverage/stats` - `GET /client/coverage/core-team` - `GET /client/coverage/incidents` - `GET /client/hubs` - `GET /client/cost-centers` - `GET /client/vendors` - `GET /client/vendors/:vendorId/roles` - `GET /client/hubs/:hubId/managers` - `GET /client/team-members` - `GET /client/orders/view` - `GET /client/orders/:orderId/reorder-preview` - `GET /client/reports/summary` - `GET /client/reports/daily-ops` - `GET /client/reports/spend` - `GET /client/reports/coverage` - `GET /client/reports/forecast` - `GET /client/reports/performance` - `GET /client/reports/no-show` ### Client writes - `POST /client/devices/push-tokens` - `DELETE /client/devices/push-tokens` - `POST /client/orders/one-time` - `POST /client/orders/recurring` - `POST /client/orders/permanent` - `POST /client/orders/:orderId/edit` - `POST /client/orders/:orderId/cancel` - `POST /client/hubs` - `PUT /client/hubs/:hubId` - `DELETE /client/hubs/:hubId` - `POST /client/hubs/:hubId/assign-nfc` - `POST /client/hubs/:hubId/managers` - `POST /client/billing/invoices/:invoiceId/approve` - `POST /client/billing/invoices/:invoiceId/dispute` - `POST /client/coverage/reviews` - `POST /client/coverage/late-workers/:assignmentId/cancel` ## 3) Staff routes ### Staff reads - `GET /staff/session` - `GET /staff/dashboard` - `GET /staff/profile-completion` - `GET /staff/availability` - `GET /staff/clock-in/shifts/today` - `GET /staff/clock-in/status` - `GET /staff/payments/summary` - `GET /staff/payments/history` - `GET /staff/payments/chart` - `GET /staff/shifts/assigned` - `GET /staff/shifts/open` - `GET /staff/shifts/pending` - `GET /staff/shifts/cancelled` - `GET /staff/shifts/completed` - `GET /staff/shifts/:shiftId` - `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/time-card` - `GET /staff/profile/privacy` - `GET /staff/faqs` - `GET /staff/faqs/search` ### Staff writes - `POST /staff/profile/setup` - `POST /staff/devices/push-tokens` - `DELETE /staff/devices/push-tokens` - `POST /staff/clock-in` - `POST /staff/clock-out` - `POST /staff/location-streams` - `PUT /staff/availability` - `POST /staff/availability/quick-set` - `POST /staff/shifts/:shiftId/apply` - `POST /staff/shifts/:shiftId/accept` - `POST /staff/shifts/:shiftId/decline` - `POST /staff/shifts/:shiftId/request-swap` - `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` ## 4) Upload and verification routes These are exposed as direct unified aliases even though they are backed by `core-api-v2`. ### Generic core aliases - `POST /upload-file` - `POST /create-signed-url` - `POST /invoke-llm` - `POST /rapid-orders/transcribe` - `POST /rapid-orders/parse` - `POST /verifications` - `GET /verifications/:verificationId` - `POST /verifications/:verificationId/review` - `POST /verifications/:verificationId/retry` ### Staff upload aliases - `POST /staff/profile/photo` - `POST /staff/profile/documents/:documentId/upload` - `POST /staff/profile/attire/:documentId/upload` - `POST /staff/profile/certificates` - `DELETE /staff/profile/certificates/:certificateId` ## 5) Notes that matter for frontend - `roleId` on `POST /staff/shifts/:shiftId/apply` is the concrete `shift_roles.id` for that shift, not the catalog role definition id. - `accountType` on `POST /staff/profile/bank-accounts` accepts either lowercase or uppercase and is normalized by the backend. - File upload routes return a storage path plus a signed URL. Frontend uploads the file directly to storage using that URL. - Verification upload and review routes are live and were validated through document, attire, and certificate flows. Do not rely on long-lived verification history durability until the dedicated persistence slice is landed in `core-api-v2`. - Attendance policy is explicit. Reads now expose `clockInMode` and `allowClockInOverride`. - `clockInMode` values are: - `NFC_REQUIRED` - `GEO_REQUIRED` - `EITHER` - For `POST /staff/clock-in` and `POST /staff/clock-out`: - send `nfcTagId` when clocking with NFC - send `latitude`, `longitude`, and `accuracyMeters` when clocking with geolocation - send `proofNonce` and `proofTimestamp` for attendance-proof logging; these are most important on NFC paths - send `attestationProvider` and `attestationToken` only when the device has a real attestation result to forward - send `overrideReason` only when the worker is bypassing a geofence failure and the shift/hub allows overrides - `POST /staff/location-streams` is for the background tracking loop after a worker is already clocked in. - `GET /client/coverage/incidents` is the review feed for geofence breaches, missing-location batches, and clock-in overrides. - `POST /client/coverage/late-workers/:assignmentId/cancel` is the client-side recovery action when lateness is confirmed by incident evidence or elapsed grace time. - Raw location stream payloads are stored in the private v2 bucket; SQL only stores the summary and incident index. - Push delivery is backed by: - SQL token registry in `device_push_tokens` - durable queue in `notification_outbox` - per-attempt delivery records in `notification_deliveries` - private Cloud Run worker service `krow-notification-worker-v2` - Cloud Scheduler job `krow-notification-dispatch-v2` ### Push token request example ```json { "provider": "FCM", "platform": "IOS", "pushToken": "expo-or-fcm-device-token", "deviceId": "iphone-15-pro-max", "appVersion": "2.0.0", "appBuild": "2000", "locale": "en-US", "timezone": "America/Los_Angeles" } ``` Push-token delete requests may send `tokenId` or `pushToken` either: - as JSON in the request body - or as query params on the `DELETE` URL Using query params is safer when the client stack or proxy is inconsistent about forwarding `DELETE` bodies. ### Clock-in request example ```json { "shiftId": "uuid", "sourceType": "GEO", "deviceId": "iphone-15-pro", "latitude": 37.4221, "longitude": -122.0841, "accuracyMeters": 12, "proofNonce": "nonce-generated-on-device", "proofTimestamp": "2026-03-16T09:00:00.000Z", "overrideReason": "Parking garage entrance is outside the marked hub geofence", "capturedAt": "2026-03-16T09:00:00.000Z" } ``` ### Location-stream batch example ```json { "shiftId": "uuid", "sourceType": "GEO", "deviceId": "iphone-15-pro", "points": [ { "capturedAt": "2026-03-16T09:15:00.000Z", "latitude": 37.4221, "longitude": -122.0841, "accuracyMeters": 12 }, { "capturedAt": "2026-03-16T09:30:00.000Z", "latitude": 37.4301, "longitude": -122.0761, "accuracyMeters": 20 } ], "metadata": { "source": "background-workmanager" } } ``` ### Coverage incidents response shape ```json { "items": [ { "incidentId": "uuid", "assignmentId": "uuid", "shiftId": "uuid", "staffName": "Ana Barista", "incidentType": "OUTSIDE_GEOFENCE", "severity": "CRITICAL", "status": "OPEN", "clockInMode": "GEO_REQUIRED", "overrideReason": null, "message": "Worker drifted outside hub geofence during active monitoring", "distanceToClockPointMeters": 910, "withinGeofence": false, "occurredAt": "2026-03-16T09:30:00.000Z" } ], "requestId": "uuid" } ``` ## 6) Why this shape - frontend gets one host - backend keeps reads, writes, and service helpers separated - routing can change internally later without forcing frontend rewrites