Merge branch 'origin/dev' into feature/session-persistence-new
This commit is contained in:
@@ -7,7 +7,7 @@ This is the frontend-facing source of truth for the v2 backend.
|
||||
Frontend should call one public gateway:
|
||||
|
||||
```env
|
||||
API_V2_BASE_URL=https://krow-api-v2-933560802882.us-central1.run.app
|
||||
API_V2_BASE_URL=https://krow-api-v2-e3g6witsvq-uc.a.run.app
|
||||
```
|
||||
|
||||
Frontend should not call the internal `core`, `command`, or `query` Cloud Run services directly.
|
||||
@@ -81,6 +81,27 @@ All routes return the same error envelope:
|
||||
}
|
||||
```
|
||||
|
||||
## 3.1) Time handling
|
||||
|
||||
V2 stores operational timestamps in UTC using PostgreSQL `TIMESTAMPTZ`.
|
||||
|
||||
Rules:
|
||||
|
||||
- frontend sends UTC timestamps to backend
|
||||
- backend returns ISO 8601 UTC timestamps for source-of-truth fields
|
||||
- frontend converts those timestamps to local time for display
|
||||
|
||||
Source-of-truth timestamp fields include:
|
||||
|
||||
- `startsAt`
|
||||
- `endsAt`
|
||||
- `clockInAt`
|
||||
- `clockOutAt`
|
||||
- `createdAt`
|
||||
- `updatedAt`
|
||||
|
||||
Helper fields like `date`, `startTime`, and `endTime` are display helpers and should not replace the raw timestamp fields.
|
||||
|
||||
## 4) Attendance policy and monitoring
|
||||
|
||||
V2 now supports an explicit attendance proof policy:
|
||||
@@ -99,6 +120,7 @@ For geofence-heavy staff flows, frontend should read the policy from:
|
||||
|
||||
- `GET /staff/clock-in/shifts/today`
|
||||
- `GET /staff/shifts/:shiftId`
|
||||
- `GET /staff/orders/:orderId`
|
||||
- `GET /client/hubs`
|
||||
|
||||
Important operational rules:
|
||||
|
||||
@@ -5,7 +5,7 @@ This document is the source of truth for V2 authentication.
|
||||
Base URL:
|
||||
|
||||
```env
|
||||
API_V2_BASE_URL=https://krow-api-v2-933560802882.us-central1.run.app
|
||||
API_V2_BASE_URL=https://krow-api-v2-e3g6witsvq-uc.a.run.app
|
||||
```
|
||||
|
||||
## 1) What is implemented
|
||||
|
||||
@@ -22,7 +22,7 @@ That includes:
|
||||
|
||||
The live smoke executed successfully against:
|
||||
|
||||
- `https://krow-api-v2-933560802882.us-central1.run.app`
|
||||
- `https://krow-api-v2-e3g6witsvq-uc.a.run.app`
|
||||
- Firebase demo users
|
||||
- `krow-sql-v2`
|
||||
- `krow-core-api-v2`
|
||||
|
||||
@@ -6,7 +6,7 @@ Use this as the primary implementation brief.
|
||||
|
||||
Base URL:
|
||||
|
||||
- `https://krow-api-v2-933560802882.us-central1.run.app`
|
||||
- `https://krow-api-v2-e3g6witsvq-uc.a.run.app`
|
||||
|
||||
Supporting docs:
|
||||
|
||||
@@ -23,6 +23,8 @@ Supporting docs:
|
||||
- Send `Idempotency-Key` on every write route.
|
||||
- Treat `order`, `shift`, `shiftRole`, and `assignment` as different objects.
|
||||
- For staff shift applications, `roleId` must come from the response of `GET /staff/shifts/open`.
|
||||
- For staff order booking, `roleId` must come from the response of `GET /staff/orders/:orderId`.
|
||||
- Treat API timestamp fields as UTC and convert them to local time in the app.
|
||||
|
||||
## 2) What is implemented now
|
||||
|
||||
@@ -89,8 +91,10 @@ Do not assume staff auth is a fully backend-managed OTP flow.
|
||||
Rules:
|
||||
|
||||
- `GET /staff/shifts/open` returns opportunities, not assignments
|
||||
- `GET /staff/orders/available` returns grouped order opportunities for booking
|
||||
- `GET /staff/shifts/assigned` returns active assigned shifts
|
||||
- `GET /client/orders/view` is the timeline/read model for client
|
||||
- `GET /client/shifts/scheduled` is the canonical timeline/read model for client
|
||||
- `GET /client/orders/view` is now a deprecated compatibility alias
|
||||
- `POST /client/orders/:orderId/edit` and `POST /client/orders/:orderId/cancel` apply to future shifts only
|
||||
|
||||
## 5) Client app screen mapping
|
||||
@@ -145,6 +149,8 @@ Rules:
|
||||
- worker rating happens through `POST /client/coverage/reviews`
|
||||
- the same endpoint also supports `markAsFavorite` to add or remove a worker from business favorites
|
||||
- blocking a worker is done through the same endpoint using `markAsBlocked`
|
||||
- coverage shift items now include `locationName` and `locationAddress`
|
||||
- assigned worker items now include `hasReview`
|
||||
- dispatch ranking order is:
|
||||
1. `CORE`
|
||||
2. `CERTIFIED_LOCATION`
|
||||
@@ -162,7 +168,8 @@ Swap management flow:
|
||||
|
||||
### Orders
|
||||
|
||||
- `GET /client/orders/view`
|
||||
- `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`
|
||||
@@ -216,6 +223,7 @@ Important:
|
||||
|
||||
- `GET /staff/session`
|
||||
- `GET /staff/dashboard`
|
||||
- `GET /staff/profile/stats`
|
||||
- `GET /staff/profile-completion`
|
||||
|
||||
### Availability
|
||||
@@ -226,12 +234,20 @@ Important:
|
||||
|
||||
### 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 `roleId` from the open-shifts response
|
||||
- use `GET /staff/orders/:orderId` as the source of truth for the order details page
|
||||
- use `roleId` from the order-detail response when booking an order
|
||||
- that `roleId` is the role catalog id for the grouped order booking flow
|
||||
- if order booking returns `422`, render `details.blockers` and keep the worker on the order details page
|
||||
- use `roleId` from the open-shifts response only for shift-level apply
|
||||
- that `roleId` is the concrete `shift_roles.id`
|
||||
|
||||
### My shifts
|
||||
|
||||
@@ -247,9 +263,11 @@ Rule:
|
||||
|
||||
Staff shift detail and list rules:
|
||||
|
||||
- `GET /staff/orders/:orderId` returns the worker booking detail contract with `schedule`, `location`, `pay`, `staffing`, `managers`, and `eligibility`
|
||||
- assigned shifts include `clientName`, `hourlyRate`, `totalRate`, `startTime`, `endTime`
|
||||
- shift detail includes `clientName`, `latitude`, `longitude`, `hourlyRate`, `totalRate`
|
||||
- completed shifts include `date`, `clientName`, `startTime`, `endTime`, `hourlyRate`, `totalRate`
|
||||
- `GET /staff/profile/stats` returns `totalShifts`, `averageRating`, `ratingCount`, `onTimeRate`, `noShowCount`, `cancellationCount`, `reliabilityScore`
|
||||
|
||||
### Clock in / clock out
|
||||
|
||||
@@ -266,6 +284,7 @@ Clock-in payload rules:
|
||||
- send `overrideReason` only when geo override is allowed
|
||||
- send `proofNonce` and `proofTimestamp` on attendance writes
|
||||
- send `attestationProvider` and `attestationToken` only if the device has them
|
||||
- if backend returns `ALREADY_CLOCKED_IN`, treat it as a valid retry-state signal and refresh attendance/session state
|
||||
|
||||
Clock-in read rules:
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ This is the shortest path for frontend to implement the v2 mobile clients agains
|
||||
|
||||
Base URL:
|
||||
|
||||
- `https://krow-api-v2-933560802882.us-central1.run.app`
|
||||
- `https://krow-api-v2-e3g6witsvq-uc.a.run.app`
|
||||
|
||||
Use this doc together with:
|
||||
|
||||
@@ -30,7 +30,11 @@ Important consequences:
|
||||
|
||||
- `GET /staff/shifts/open` returns open shift-role opportunities.
|
||||
- `POST /staff/shifts/:shiftId/apply` must send the `roleId` from that response.
|
||||
- `GET /client/orders/view` is the timeline/read model for the client app.
|
||||
- `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
|
||||
@@ -122,7 +126,8 @@ Dispatch-priority rule:
|
||||
|
||||
### Orders
|
||||
|
||||
- `GET /client/orders/view`
|
||||
- `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`
|
||||
@@ -175,13 +180,20 @@ Rapid-order flow:
|
||||
|
||||
### 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:
|
||||
|
||||
- send the `roleId` from the open-shifts response
|
||||
- this is the concrete `shift_roles.id`
|
||||
- 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
|
||||
|
||||
|
||||
@@ -4,10 +4,12 @@ This document is the frontend handoff for the `staff/shifts/*` routes on the uni
|
||||
|
||||
Base URL:
|
||||
|
||||
- `https://krow-api-v2-933560802882.us-central1.run.app`
|
||||
- `https://krow-api-v2-e3g6witsvq-uc.a.run.app`
|
||||
|
||||
## Read routes
|
||||
|
||||
- `GET /staff/orders/available`
|
||||
- `GET /staff/orders/:orderId`
|
||||
- `GET /staff/shifts/assigned`
|
||||
- `GET /staff/shifts/open`
|
||||
- `GET /staff/shifts/pending`
|
||||
@@ -17,6 +19,7 @@ Base URL:
|
||||
|
||||
## Write routes
|
||||
|
||||
- `POST /staff/orders/:orderId/book`
|
||||
- `POST /staff/shifts/:shiftId/apply`
|
||||
- `POST /staff/shifts/:shiftId/accept`
|
||||
- `POST /staff/shifts/:shiftId/decline`
|
||||
@@ -30,6 +33,105 @@ All write routes require:
|
||||
|
||||
## Shift lifecycle
|
||||
|
||||
### Find work by order
|
||||
|
||||
`GET /staff/orders/available`
|
||||
|
||||
- use this for grouped recurring or permanent work cards
|
||||
- each item represents one order plus one role
|
||||
- this feed is already filtered to the current worker context
|
||||
- `schedule` gives the preview for the whole booking window
|
||||
|
||||
Example response:
|
||||
|
||||
```json
|
||||
{
|
||||
"orderId": "uuid",
|
||||
"orderType": "RECURRING",
|
||||
"roleId": "uuid",
|
||||
"roleCode": "BARISTA",
|
||||
"roleName": "Barista",
|
||||
"clientName": "Google Mountain View Cafes",
|
||||
"location": "Google MV Cafe Clock Point",
|
||||
"locationAddress": "1600 Amphitheatre Pkwy, Mountain View, CA",
|
||||
"hourlyRateCents": 2300,
|
||||
"hourlyRate": 23,
|
||||
"requiredWorkerCount": 1,
|
||||
"filledCount": 0,
|
||||
"instantBook": false,
|
||||
"dispatchTeam": "CORE",
|
||||
"dispatchPriority": 1,
|
||||
"schedule": {
|
||||
"totalShifts": 3,
|
||||
"startDate": "2026-03-24",
|
||||
"endDate": "2026-03-28",
|
||||
"daysOfWeek": ["WED", "FRI"],
|
||||
"startTime": "09:00",
|
||||
"endTime": "15:00",
|
||||
"timezone": "America/Los_Angeles",
|
||||
"firstShiftStartsAt": "2026-03-25T16:00:00.000Z",
|
||||
"lastShiftEndsAt": "2026-03-27T22:00:00.000Z"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`POST /staff/orders/:orderId/book`
|
||||
|
||||
- use this when the worker books the full order instead of one shift
|
||||
- booking is atomic across the future shifts in that order for the selected role
|
||||
- backend returns `PENDING` when the booking is reserved but not instant-booked
|
||||
- backend returns `CONFIRMED` when every future shift in that booking path is instant-booked
|
||||
- backend returns `422 UNPROCESSABLE_ENTITY` when the worker is not eligible to book that order
|
||||
|
||||
Example request:
|
||||
|
||||
```json
|
||||
{
|
||||
"roleId": "uuid"
|
||||
}
|
||||
```
|
||||
|
||||
Important:
|
||||
|
||||
- `GET /staff/orders/:orderId` is now the source of truth for the order detail screen before booking
|
||||
- `roleId` for the order-booking flow is the role catalog id returned by `GET /staff/orders/:orderId`
|
||||
- it is not the same thing as the per-shift `shift_roles.id`
|
||||
- when booking is rejected, use `details.blockers` from the error response to explain why
|
||||
|
||||
### Order detail
|
||||
|
||||
`GET /staff/orders/:orderId`
|
||||
|
||||
Use this as the source of truth for the worker order-review page before calling `POST /staff/orders/:orderId/book`.
|
||||
|
||||
Response shape includes:
|
||||
|
||||
- `orderId`
|
||||
- `orderType`
|
||||
- `roleId`
|
||||
- `roleCode`
|
||||
- `roleName`
|
||||
- `clientName`
|
||||
- `businessId`
|
||||
- `instantBook`
|
||||
- `dispatchTeam`
|
||||
- `dispatchPriority`
|
||||
- `jobDescription`
|
||||
- `instructions`
|
||||
- `status`
|
||||
- `schedule`
|
||||
- `location`
|
||||
- `pay`
|
||||
- `staffing`
|
||||
- `managers`
|
||||
- `eligibility`
|
||||
|
||||
Frontend rules:
|
||||
|
||||
- call this endpoint after a worker taps an order card from `GET /staff/orders/available`
|
||||
- use the returned `roleId` when calling `POST /staff/orders/:orderId/book`
|
||||
- if `eligibility.isEligible` is `false`, show the blocker messages and disable booking
|
||||
|
||||
### Find shifts
|
||||
|
||||
`GET /staff/shifts/open`
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
Frontend should use this service as the single base URL:
|
||||
|
||||
- `https://krow-api-v2-933560802882.us-central1.run.app`
|
||||
- `https://krow-api-v2-e3g6witsvq-uc.a.run.app`
|
||||
|
||||
The gateway keeps backend services separate internally, but frontend should treat it as one API.
|
||||
|
||||
@@ -54,7 +54,8 @@ Full auth behavior, including staff phone flow and refresh rules, is documented
|
||||
- `GET /client/vendors/:vendorId/roles`
|
||||
- `GET /client/hubs/:hubId/managers`
|
||||
- `GET /client/team-members`
|
||||
- `GET /client/orders/view`
|
||||
- `GET /client/shifts/scheduled`
|
||||
- `GET /client/orders/view` deprecated compatibility alias
|
||||
- `GET /client/orders/:orderId/reorder-preview`
|
||||
- `GET /client/reports/summary`
|
||||
- `GET /client/reports/daily-ops`
|
||||
@@ -88,6 +89,12 @@ Full auth behavior, including staff phone flow and refresh rules, is documented
|
||||
- `POST /client/coverage/dispatch-teams/memberships`
|
||||
- `DELETE /client/coverage/dispatch-teams/memberships/:membershipId`
|
||||
|
||||
Timeline route naming:
|
||||
|
||||
- `GET /client/shifts/scheduled` is the canonical client timeline route
|
||||
- it returns shift-level scheduled items, not order headers
|
||||
- `GET /client/orders/view` still returns the same payload for compatibility, but now emits a deprecation header
|
||||
|
||||
Coverage-review request payload may also send:
|
||||
|
||||
```json
|
||||
@@ -104,6 +111,11 @@ Coverage-review request payload may also send:
|
||||
|
||||
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 adds that staff member to the business-level blocked list and future apply or assign attempts are rejected until a later review sends `markAsBlocked: false`.
|
||||
|
||||
`GET /client/coverage` response notes:
|
||||
|
||||
- each shift item includes `locationName` and `locationAddress`
|
||||
- each assigned worker item includes `hasReview`
|
||||
|
||||
Swap-review routes:
|
||||
|
||||
- `GET /client/coverage/swap-requests?status=OPEN`
|
||||
@@ -163,6 +175,7 @@ The manager is created as an invited business membership. If `hubId` is present,
|
||||
|
||||
- `GET /staff/session`
|
||||
- `GET /staff/dashboard`
|
||||
- `GET /staff/profile/stats`
|
||||
- `GET /staff/profile-completion`
|
||||
- `GET /staff/availability`
|
||||
- `GET /staff/clock-in/shifts/today`
|
||||
@@ -170,6 +183,8 @@ The manager is created as an invited business membership. If `hubId` is present,
|
||||
- `GET /staff/payments/summary`
|
||||
- `GET /staff/payments/history`
|
||||
- `GET /staff/payments/chart`
|
||||
- `GET /staff/orders/available`
|
||||
- `GET /staff/orders/:orderId`
|
||||
- `GET /staff/shifts/assigned`
|
||||
- `GET /staff/shifts/open`
|
||||
- `GET /staff/shifts/pending`
|
||||
@@ -218,6 +233,32 @@ Example `GET /staff/clock-in/shifts/today` item:
|
||||
}
|
||||
```
|
||||
|
||||
Example `GET /staff/profile/stats` response:
|
||||
|
||||
```json
|
||||
{
|
||||
"staffId": "uuid",
|
||||
"totalShifts": 12,
|
||||
"averageRating": 4.8,
|
||||
"ratingCount": 7,
|
||||
"onTimeRate": 91.7,
|
||||
"noShowCount": 1,
|
||||
"cancellationCount": 0,
|
||||
"reliabilityScore": 92.3
|
||||
}
|
||||
```
|
||||
|
||||
Order booking route notes:
|
||||
|
||||
- `GET /staff/orders/available` is the canonical order-level marketplace feed for recurring and grouped work
|
||||
- `GET /staff/orders/:orderId` is the canonical staff order-detail route before booking
|
||||
- `GET /staff/shifts/open` remains available for shift-level opportunities and swap coverage
|
||||
- `POST /staff/orders/:orderId/book` books the future shifts of an order atomically for one role
|
||||
- if booking is rejected for eligibility reasons, backend returns `422 UNPROCESSABLE_ENTITY` with `details.blockers`
|
||||
- use the `roleId` returned by `GET /staff/orders/:orderId` when booking
|
||||
- that `roleId` is the role catalog id for the order booking flow
|
||||
- the `roleId` returned by `GET /staff/shifts/open` is still the concrete `shift_roles.id` for shift-level apply
|
||||
|
||||
### Staff writes
|
||||
|
||||
- `POST /staff/profile/setup`
|
||||
@@ -228,6 +269,7 @@ Example `GET /staff/clock-in/shifts/today` item:
|
||||
- `POST /staff/location-streams`
|
||||
- `PUT /staff/availability`
|
||||
- `POST /staff/availability/quick-set`
|
||||
- `POST /staff/orders/:orderId/book`
|
||||
- `POST /staff/shifts/:shiftId/apply`
|
||||
- `POST /staff/shifts/:shiftId/accept`
|
||||
- `POST /staff/shifts/:shiftId/decline`
|
||||
@@ -296,12 +338,14 @@ These are exposed as direct unified aliases even though they are backed by `core
|
||||
- `NFC_REQUIRED`
|
||||
- `GEO_REQUIRED`
|
||||
- `EITHER`
|
||||
- all source-of-truth timestamps are UTC ISO 8601 values. Frontend should convert them to local time for display.
|
||||
- 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
|
||||
- if the worker is already clocked in, backend returns `409` with code `ALREADY_CLOCKED_IN`
|
||||
- `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.
|
||||
- `GET /client/coverage/blocked-staff` is the review feed for workers currently blocked by that business.
|
||||
|
||||
Reference in New Issue
Block a user