# Staff Shifts V2 This document is the frontend handoff for the `staff/shifts/*` routes on the unified v2 API. Base URL: - `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` - `GET /staff/shifts/cancelled` - `GET /staff/shifts/completed` - `GET /staff/shifts/:shiftId` ## Write routes - `POST /staff/orders/:orderId/book` - `POST /staff/shifts/:shiftId/apply` - `POST /staff/shifts/:shiftId/accept` - `POST /staff/shifts/:shiftId/decline` - `POST /staff/shifts/:shiftId/request-swap` - `POST /staff/shifts/:shiftId/submit-for-approval` All write routes require: - `Authorization: Bearer ` - `Idempotency-Key: ` ## 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` - use this for the worker marketplace feed - the worker applies to a concrete shift role - send the `roleId` returned by the open-shifts response - `roleId` here means `shift_roles.id`, not the role catalog id Apply request example: ```json { "roleId": "uuid", "instantBook": false } ``` ### Pending shifts `GET /staff/shifts/pending` - use `POST /staff/shifts/:shiftId/accept` to accept - use `POST /staff/shifts/:shiftId/decline` to decline ### Assigned shifts `GET /staff/shifts/assigned` Each item now includes: - `clientName` - `hourlyRate` - `totalRate` - `startTime` - `endTime` ### Shift detail `GET /staff/shifts/:shiftId` Each detail response now includes: - `clientName` - `latitude` - `longitude` - `hourlyRate` - `totalRate` Use this as the source of truth for the shift detail screen. ### Request swap `POST /staff/shifts/:shiftId/request-swap` Example: ```json { "reason": "Need coverage for a family emergency" } ``` Current backend behavior: - marks the assignment as `SWAP_REQUESTED` - stores the reason - emits `SHIFT_SWAP_REQUESTED` - exposes the shift in the replacement pool - starts the swap-expiry window used by backend auto-cancellation Manager/ops review happens through: - `GET /client/coverage/swap-requests` - `GET /client/coverage/dispatch-candidates` - `POST /client/coverage/swap-requests/:swapRequestId/resolve` - `POST /client/coverage/swap-requests/:swapRequestId/cancel` If the swap request expires without coverage, backend auto-cancels it and alerts the manager path plus the original worker. ### Submit completed shift for approval `POST /staff/shifts/:shiftId/submit-for-approval` Use this after the worker has clocked out. Example: ```json { "note": "Worked full shift and all tasks were completed" } ``` Current backend behavior: - only allows shifts in `CHECKED_OUT` or `COMPLETED` - creates or updates the assignment timesheet - sets the timesheet to `SUBMITTED` unless it is already `APPROVED` or `PAID` - emits `TIMESHEET_SUBMITTED_FOR_APPROVAL` Example response: ```json { "assignmentId": "uuid", "shiftId": "uuid", "timesheetId": "uuid", "status": "SUBMITTED", "submitted": true } ``` ## Completed shifts `GET /staff/shifts/completed` Each item now includes: - `date` - `clientName` - `startTime` - `endTime` - `hourlyRate` - `totalRate` - `timesheetStatus` - `paymentStatus` ## Clock-in support fields `GET /staff/clock-in/shifts/today` Each item now includes: - `clientName` - `hourlyRate` - `totalRate` - `latitude` - `longitude` - `clockInMode` - `allowClockInOverride` ## Frontend rule Use the unified routes only. Do not build new mobile work on: - `/query/*` - `/commands/*` - `/core/*`