Merge branch 'dev' into feature/session-persistence-new

This commit is contained in:
2026-03-17 18:40:22 +05:30
257 changed files with 32250 additions and 1272 deletions

View File

@@ -1,5 +1,9 @@
# KROW Workforce API Contracts
Legacy note:
Use `/Users/wiel/Development/krow-workforce/docs/BACKEND/API_GUIDES/V2/README.md` for the current v2 frontend/backend integration surface.
This document reflects the earlier Data Connect-oriented contract mapping and should not be the source of truth for new v2 client work.
This document captures all API contracts used by the Staff and Client mobile applications. The application backend is powered by **Firebase Data Connect (GraphQL)**, so traditional REST endpoints do not exist natively. For clarity and ease of reading for all engineering team members, the tables below formulate these GraphQL Data Connect queries and mutations into their **Conceptual REST Endpoints** alongside the actual **Data Connect Operation Name**.
---

View File

@@ -0,0 +1,14 @@
# Backend API Guides
## Use this for current frontend work
- [V2 Backend API Guide](./V2/README.md)
- [V2 Core API](./V2/core-api.md)
- [V2 Command API](./V2/command-api.md)
- [V2 Query API](./V2/query-api.md)
## Legacy reference
- [Initial API contracts](./00-initial-api-contracts.md)
The legacy contract doc reflects the older Data Connect-oriented application shape. Do not use it as the source of truth for new v2 frontend work.

View File

@@ -0,0 +1,149 @@
# V2 Backend API Guide
This is the frontend-facing source of truth for the v2 backend.
## 1) Use one base URL
Frontend should call one public gateway:
```env
API_V2_BASE_URL=https://krow-api-v2-933560802882.us-central1.run.app
```
Frontend should not call the internal `core`, `command`, or `query` Cloud Run services directly.
## 2) Current status
The unified v2 gateway is ready for frontend integration in `dev`.
What was validated live against the deployed stack:
- client sign-in
- staff auth bootstrap
- client dashboard, billing, coverage, hubs, vendors, managers, team members, orders, and reports
- client coverage incident feed for geofence and override review
- client hub, order, coverage review, device token, and late-worker cancellation flows
- client invoice approve and dispute
- staff dashboard, availability, payments, shifts, profile sections, documents, certificates, attire, bank accounts, benefits, and time card
- staff availability, profile, tax form, bank account, shift apply, shift accept, push token registration, clock-in, clock-out, location stream upload, and swap request
- direct file upload helpers and verification job creation through the unified host
- client and staff sign-out
The live validation command is:
```bash
export FIREBASE_WEB_API_KEY="$(gcloud secrets versions access latest --secret=firebase-web-api-key --project=krow-workforce-dev)"
source ~/.nvm/nvm.sh
nvm use 23.5.0
node backend/unified-api/scripts/live-smoke-v2-unified.mjs
```
The demo tenant can be reset with:
```bash
source ~/.nvm/nvm.sh
nvm use 23.5.0
cd backend/command-api
npm run seed:v2-demo
```
## 3) Auth and headers
Protected routes require:
```http
Authorization: Bearer <firebase-id-token>
```
Write routes also require:
```http
Idempotency-Key: <unique-per-user-action>
```
For now:
- backend wraps sign-in and sign-out
- frontend can keep using Firebase token refresh on the client
- backend is the only thing frontend should call for session-oriented API flows
All routes return the same error envelope:
```json
{
"code": "STRING_CODE",
"message": "Human readable message",
"details": {},
"requestId": "uuid"
}
```
## 4) Attendance policy and monitoring
V2 now supports an explicit attendance proof policy:
- `NFC_REQUIRED`
- `GEO_REQUIRED`
- `EITHER`
The effective policy is resolved as:
1. shift override if present
2. hub default if present
3. fallback to `EITHER`
For geofence-heavy staff flows, frontend should read the policy from:
- `GET /staff/clock-in/shifts/today`
- `GET /staff/shifts/:shiftId`
- `GET /client/hubs`
Important operational rules:
- outside-geofence clock-ins can be accepted only when override is enabled and a written reason is provided
- NFC mismatches are rejected and are not overrideable
- attendance proof logs are durable in SQL and raw object storage
- device push tokens are durable in SQL and can be registered separately for client and staff apps
- background location streams are stored as raw batch payloads in the private v2 bucket and summarized in SQL for query speed
- incident review lives on `GET /client/coverage/incidents`
- confirmed late-worker recovery is exposed on `POST /client/coverage/late-workers/:assignmentId/cancel`
- queued alerts are written to `notification_outbox`, dispatched by the private Cloud Run worker service `krow-notification-worker-v2`, and recorded in `notification_deliveries`
## 5) Route model
Frontend sees one base URL and one route shape:
- `/auth/*`
- `/client/*`
- `/staff/*`
- direct upload aliases like `/upload-file` and `/staff/profile/*`
Internally, the gateway still forwards to:
| Frontend use case | Internal service |
| --- | --- |
| auth/session wrapper | `krow-api-v2` |
| uploads, signed URLs, model calls, verification workflows | `core-api-v2` |
| writes and workflow actions | `command-api-v2` |
| reads and mobile read models | `query-api-v2` |
## 6) Frontend integration rule
Use the unified routes first.
Do not build new frontend work on:
- `/query/tenants/*`
- `/commands/*`
- `/core/*`
Those routes still exist for backend/internal compatibility, but mobile/frontend migration should target the unified surface documented in [Unified API](./unified-api.md).
## 7) Docs
- [Authentication](./authentication.md)
- [Unified API](./unified-api.md)
- [Core API](./core-api.md)
- [Command API](./command-api.md)
- [Query API](./query-api.md)
- [Mobile API Reconciliation](./mobile-api-gap-analysis.md)

View File

@@ -0,0 +1,343 @@
# V2 Authentication Guide
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
```
## 1) What is implemented
### Client app
Client authentication is implemented through backend endpoints:
- `POST /auth/client/sign-in`
- `POST /auth/client/sign-up`
- `POST /auth/client/sign-out`
- `GET /auth/session`
The backend signs the user in with Firebase Identity Toolkit, validates the user against the V2 database, and returns the full auth envelope.
### Staff app
Staff authentication is implemented, but it is a hybrid flow.
Routes:
- `POST /auth/staff/phone/start`
- `POST /auth/staff/phone/verify`
- `POST /auth/staff/sign-out`
- `GET /auth/session`
Important:
- the default mobile path is **not** a fully backend-managed OTP flow
- the usual mobile path uses the Firebase Auth SDK on-device for phone verification
- after the device gets a Firebase `idToken`, frontend sends that token to `POST /auth/staff/phone/verify`
So if someone expects `POST /auth/staff/phone/start` to always send the SMS and always return `sessionInfo`, that expectation is wrong for the current implementation
## 2) Auth refresh
There is currently **no** backend `/auth/refresh` endpoint.
That is intentional for now.
Current refresh model:
- frontend keeps Firebase Auth local session state
- frontend lets the Firebase SDK refresh the ID token
- frontend sends the latest Firebase ID token in:
```http
Authorization: Bearer <firebase-id-token>
```
Use:
- `authStateChanges()` / `idTokenChanges()` listeners
- `currentUser.getIdToken()`
- `currentUser.getIdToken(true)` only when a forced refresh is actually needed
`GET /auth/session` is **not** a refresh endpoint.
It is a context endpoint used to:
- hydrate role/tenant/business/staff context
- validate that the signed-in Firebase user is allowed in this app
## 3) Client auth flow
### Client sign-in
Request:
```http
POST /auth/client/sign-in
Content-Type: application/json
```
```json
{
"email": "legendary.owner+v2@krowd.com",
"password": "Demo2026!"
}
```
Response:
```json
{
"sessionToken": "firebase-id-token",
"refreshToken": "firebase-refresh-token",
"expiresInSeconds": 3600,
"user": {
"id": "user-uuid",
"email": "legendary.owner+v2@krowd.com",
"displayName": "Legendary Owner",
"phone": null
},
"tenant": {
"tenantId": "tenant-uuid",
"tenantName": "Legendary Event Staffing and Entertainment"
},
"business": {
"businessId": "business-uuid",
"businessName": "Google Mountain View Cafes"
},
"requestId": "uuid"
}
```
Frontend behavior:
1. Call `POST /auth/client/sign-in`
2. If success, sign in locally with Firebase Auth SDK using the same email/password
3. Use Firebase SDK token refresh for later API calls
4. Use `GET /auth/session` when role/session hydration is needed on app boot
### Client sign-up
Request:
```http
POST /auth/client/sign-up
Content-Type: application/json
```
```json
{
"companyName": "Legendary Event Staffing and Entertainment",
"email": "legendary.owner+v2@krowd.com",
"password": "Demo2026!"
}
```
What it does:
- creates Firebase Auth account
- creates V2 user
- creates tenant
- creates business
- creates tenant membership
- creates business membership
Frontend behavior after success:
1. call `POST /auth/client/sign-up`
2. sign in locally with Firebase Auth SDK using the same email/password
3. use Firebase SDK for later token refresh
## 4) Staff auth flow
## Step 1: start phone auth
Request:
```http
POST /auth/staff/phone/start
Content-Type: application/json
```
```json
{
"phoneNumber": "+15551234567"
}
```
Possible response A:
```json
{
"mode": "CLIENT_FIREBASE_SDK",
"provider": "firebase-phone-auth",
"phoneNumber": "+15551234567",
"nextStep": "Complete phone verification in the mobile client, then call /auth/staff/phone/verify with the Firebase idToken.",
"requestId": "uuid"
}
```
This is the normal mobile path when frontend does **not** send recaptcha or integrity tokens.
Possible response B:
```json
{
"mode": "IDENTITY_TOOLKIT_SMS",
"phoneNumber": "+15551234567",
"sessionInfo": "firebase-session-info",
"requestId": "uuid"
}
```
This is the server-managed SMS path.
## Step 2A: normal mobile path (`CLIENT_FIREBASE_SDK`)
Frontend must do this on-device:
1. call `FirebaseAuth.verifyPhoneNumber(...)`
2. collect the `verificationId`
3. collect the OTP code from the user
4. create a Firebase phone credential
5. call `signInWithCredential(...)`
6. get Firebase `idToken`
7. call `POST /auth/staff/phone/verify` with that `idToken`
Request:
```http
POST /auth/staff/phone/verify
Content-Type: application/json
```
```json
{
"mode": "sign-in",
"idToken": "firebase-id-token-from-device"
}
```
Response:
```json
{
"sessionToken": "firebase-id-token-from-device",
"refreshToken": null,
"expiresInSeconds": 3600,
"user": {
"id": "user-uuid",
"phone": "+15551234567"
},
"staff": {
"staffId": "staff-uuid"
},
"requiresProfileSetup": false,
"requestId": "uuid"
}
```
Important:
- `refreshToken` is expected to be `null` in this path
- refresh remains owned by Firebase Auth SDK on the device
## Step 2B: server SMS path (`IDENTITY_TOOLKIT_SMS`)
If `start` returned `sessionInfo`, frontend can call:
```json
{
"mode": "sign-in",
"sessionInfo": "firebase-session-info",
"code": "123456"
}
```
The backend exchanges `sessionInfo + code` with Identity Toolkit and returns the hydrated auth envelope.
## 5) Sign-out
Routes:
- `POST /auth/sign-out`
- `POST /auth/client/sign-out`
- `POST /auth/staff/sign-out`
All sign-out routes require:
```http
Authorization: Bearer <firebase-id-token>
```
What sign-out does:
- revokes backend-side Firebase sessions for that user
- frontend should still clear local Firebase Auth state with `FirebaseAuth.instance.signOut()`
## 6) Session endpoint
Route:
- `GET /auth/session`
Headers:
```http
Authorization: Bearer <firebase-id-token>
```
Use it for:
- app startup hydration
- role validation
- deciding whether this app should allow the current signed-in user
Do not use it as:
- a refresh endpoint
- a login endpoint
## 7) Error contract
All auth routes use the standard V2 error envelope:
```json
{
"code": "STRING_CODE",
"message": "Human readable message",
"details": {},
"requestId": "uuid"
}
```
Common auth failures:
- `UNAUTHENTICATED`
- `FORBIDDEN`
- `VALIDATION_ERROR`
- `AUTH_PROVIDER_ERROR`
## 8) Troubleshooting
### Staff sign-in does not work, but endpoints are reachable
The most likely causes are:
1. frontend expected `POST /auth/staff/phone/start` to always return `sessionInfo`
2. frontend did not complete Firebase phone verification on-device
3. frontend called `POST /auth/staff/phone/verify` without a valid Firebase `idToken`
4. frontend phone-auth setup in Firebase mobile config is incomplete
### `POST /auth/staff/phone/start` returns `CLIENT_FIREBASE_SDK`
That is expected for the normal mobile flow when no recaptcha or integrity tokens are sent.
### There is no `/auth/refresh`
That is also expected right now.
Refresh is handled by Firebase Auth SDK on the client.

View File

@@ -0,0 +1,229 @@
# V2 Command API
Use `command-api-v2` for write actions that change business state.
Base URL:
```text
https://krow-command-api-v2-e3g6witsvq-uc.a.run.app
```
## 1) Required headers
```http
Authorization: Bearer <firebase-id-token>
Idempotency-Key: <unique-per-user-action>
Content-Type: application/json
```
## 2) Route summary
| Method | Route | Purpose |
| --- | --- | --- |
| `POST` | `/commands/orders/create` | Create order with shifts and roles |
| `POST` | `/commands/orders/:orderId/update` | Update mutable order fields |
| `POST` | `/commands/orders/:orderId/cancel` | Cancel order and related eligible records |
| `POST` | `/commands/shifts/:shiftId/assign-staff` | Assign workforce to shift role |
| `POST` | `/commands/shifts/:shiftId/accept` | Accept an assigned shift |
| `POST` | `/commands/shifts/:shiftId/change-status` | Move shift to a new valid status |
| `POST` | `/commands/attendance/clock-in` | Record clock-in event |
| `POST` | `/commands/attendance/clock-out` | Record clock-out event |
| `POST` | `/commands/businesses/:businessId/favorite-staff` | Add favorite staff |
| `DELETE` | `/commands/businesses/:businessId/favorite-staff/:staffId` | Remove favorite staff |
| `POST` | `/commands/assignments/:assignmentId/reviews` | Create or update staff review |
| `GET` | `/readyz` | Ready check |
## 3) Order create
```text
POST /commands/orders/create
```
Request body:
```json
{
"tenantId": "uuid",
"businessId": "uuid",
"vendorId": "uuid",
"orderNumber": "ORD-1001",
"title": "Cafe Event Staffing",
"serviceType": "EVENT",
"shifts": [
{
"shiftCode": "SHIFT-1",
"title": "Morning Shift",
"startsAt": "2026-03-12T08:00:00.000Z",
"endsAt": "2026-03-12T16:00:00.000Z",
"requiredWorkers": 2,
"roles": [
{
"roleCode": "BARISTA",
"roleName": "Barista",
"workersNeeded": 2
}
]
}
]
}
```
## 4) Order update
```text
POST /commands/orders/:orderId/update
```
Required body fields:
- `tenantId`
- at least one mutable field such as `title`, `description`, `vendorId`, `serviceType`, `startsAt`, `endsAt`, `locationName`, `locationAddress`, `latitude`, `longitude`, `notes`, `metadata`
You can also send `null` to clear nullable fields.
## 5) Order cancel
```text
POST /commands/orders/:orderId/cancel
```
Example request:
```json
{
"tenantId": "uuid",
"reason": "Client cancelled"
}
```
## 6) Shift assign staff
```text
POST /commands/shifts/:shiftId/assign-staff
```
Example request:
```json
{
"tenantId": "uuid",
"shiftRoleId": "uuid",
"workforceId": "uuid",
"applicationId": "uuid"
}
```
## 7) Shift accept
```text
POST /commands/shifts/:shiftId/accept
```
Example request:
```json
{
"shiftRoleId": "uuid",
"workforceId": "uuid"
}
```
## 8) Shift status change
```text
POST /commands/shifts/:shiftId/change-status
```
Example request:
```json
{
"tenantId": "uuid",
"status": "PENDING_CONFIRMATION",
"reason": "Waiting for worker confirmation"
}
```
Allowed status values:
- `DRAFT`
- `OPEN`
- `PENDING_CONFIRMATION`
- `ASSIGNED`
- `ACTIVE`
- `COMPLETED`
- `CANCELLED`
## 9) Attendance
### Clock in
```text
POST /commands/attendance/clock-in
```
### Clock out
```text
POST /commands/attendance/clock-out
```
Example request body for both:
```json
{
"assignmentId": "uuid",
"sourceType": "NFC",
"sourceReference": "iphone-15-pro-max",
"nfcTagUid": "NFC-DEMO-ANA-001",
"deviceId": "device-123",
"latitude": 37.422,
"longitude": -122.084,
"accuracyMeters": 8,
"capturedAt": "2026-03-11T17:15:00.000Z"
}
```
## 10) Favorite staff
### Add favorite
```text
POST /commands/businesses/:businessId/favorite-staff
```
### Remove favorite
```text
DELETE /commands/businesses/:businessId/favorite-staff/:staffId
```
Request body when adding:
```json
{
"tenantId": "uuid",
"staffId": "uuid"
}
```
## 11) Staff review
```text
POST /commands/assignments/:assignmentId/reviews
```
Example request:
```json
{
"tenantId": "uuid",
"businessId": "uuid",
"staffId": "uuid",
"rating": 5,
"reviewText": "Strong shift performance",
"tags": ["punctual", "professional"]
}
```
## 12) Live status
These routes were live-tested on `2026-03-11` against the deployed dev service and `krow-sql-v2`.

View File

@@ -0,0 +1,203 @@
# V2 Core API
Use `core-api-v2` for backend capabilities that should not live in the client.
Base URL:
```text
https://krow-core-api-v2-e3g6witsvq-uc.a.run.app
```
## 1) Route summary
| Method | Route | Purpose |
| --- | --- | --- |
| `POST` | `/core/upload-file` | Upload file to Google Cloud Storage |
| `POST` | `/core/create-signed-url` | Generate a read URL for an uploaded file |
| `POST` | `/core/invoke-llm` | Run a model call |
| `POST` | `/core/rapid-orders/transcribe` | Turn uploaded audio into text |
| `POST` | `/core/rapid-orders/parse` | Turn order text into structured order data |
| `POST` | `/core/verifications` | Create a verification job |
| `GET` | `/core/verifications/:verificationId` | Fetch verification status |
| `POST` | `/core/verifications/:verificationId/review` | Apply manual review decision |
| `POST` | `/core/verifications/:verificationId/retry` | Retry a verification job |
| `GET` | `/health` | Health check |
## 2) Upload file
Route:
```text
POST /core/upload-file
```
Send multipart form data:
- `file`: required
- `category`: optional string
- `visibility`: `public` or `private`
Example response:
```json
{
"fileUri": "gs://krow-workforce-dev-private/uploads/<uid>/file.pdf",
"contentType": "application/pdf",
"size": 12345,
"bucket": "krow-workforce-dev-private",
"path": "uploads/<uid>/file.pdf",
"requestId": "uuid"
}
```
## 3) Create signed URL
Route:
```text
POST /core/create-signed-url
```
Example request:
```json
{
"fileUri": "gs://krow-workforce-dev-private/uploads/<uid>/file.pdf",
"expiresInSeconds": 300
}
```
Example response:
```json
{
"signedUrl": "https://...",
"expiresAt": "2026-03-11T18:30:00.000Z",
"requestId": "uuid"
}
```
## 4) Invoke model
Route:
```text
POST /core/invoke-llm
```
Example request:
```json
{
"prompt": "Summarize this staffing request",
"fileUrls": ["gs://krow-workforce-dev-private/uploads/<uid>/notes.pdf"],
"responseJsonSchema": {
"type": "object",
"properties": {
"summary": { "type": "string" }
},
"required": ["summary"]
}
}
```
Example response:
```json
{
"result": {
"summary": "..."
},
"model": "vertex model name",
"latencyMs": 1200,
"requestId": "uuid"
}
```
## 5) Rapid order helpers
### Transcribe
```text
POST /core/rapid-orders/transcribe
```
Example request:
```json
{
"audioFileUri": "gs://krow-workforce-dev-private/uploads/<uid>/note.m4a",
"locale": "en-US",
"promptHints": ["staffing order", "shift details"]
}
```
### Parse
```text
POST /core/rapid-orders/parse
```
Example request:
```json
{
"text": "Need two baristas tomorrow from 8am to 4pm at Google Mountain View Cafe",
"locale": "en-US",
"timezone": "America/Los_Angeles"
}
```
## 6) Verification routes
### Create verification
```text
POST /core/verifications
```
Example request:
```json
{
"type": "attire",
"subjectType": "staff",
"subjectId": "staff-uuid",
"fileUri": "gs://krow-workforce-dev-private/uploads/<uid>/attire.jpg",
"rules": {
"label": "black shoes"
}
}
```
### Get verification
```text
GET /core/verifications/:verificationId
```
### Manual review
```text
POST /core/verifications/:verificationId/review
```
Example request:
```json
{
"decision": "APPROVED",
"note": "Manual review passed"
}
```
### Retry
```text
POST /core/verifications/:verificationId/retry
```
## 7) Caveat
Verification state is not yet stored in `krow-sql-v2`.
Use these routes now for frontend integration, but do not depend on verification history being durable until the persistence work lands.

View File

@@ -0,0 +1,47 @@
# Mobile API Reconciliation
Source compared against implementation:
- `mobile-backend-api-specification.md`
## Result
The current mobile v2 surface is implemented behind the unified gateway and validated live in `dev`.
That includes:
- auth session routes
- client dashboard, billing, coverage, hubs, vendor lookup, managers, team members, orders, and reports
- client order, hub, coverage review, and invoice write flows
- staff dashboard, availability, payments, shifts, profile sections, documents, attire, certificates, bank accounts, benefits, privacy, and frequently asked questions
- staff availability, tax forms, emergency contacts, bank account, shift decision, clock-in/out, and swap write flows
- upload and verification flows for profile photo, government document, attire, and certificates
- attendance policy enforcement, geofence incident review, background location-stream ingest, and queued manager alerts
## What was validated live
The live smoke executed successfully against:
- `https://krow-api-v2-933560802882.us-central1.run.app`
- Firebase demo users
- `krow-sql-v2`
- `krow-core-api-v2`
- `krow-command-api-v2`
- `krow-query-api-v2`
The validation script is:
```bash
node backend/unified-api/scripts/live-smoke-v2-unified.mjs
```
## Remaining work
The remaining items are not blockers for current mobile frontend migration.
They are follow-up items:
- extend the same unified pattern to new screens added after the current mobile specification
- add stronger contract automation around the unified route surface
- add a device-token registry and dispatch worker on top of `notification_outbox`
- keep refining reporting and financial read models as product scope expands

View File

@@ -0,0 +1,151 @@
# V2 Query API
Use `query-api-v2` for implemented read screens in the v2 clients.
Base URL:
```text
https://krow-query-api-v2-e3g6witsvq-uc.a.run.app
```
## 1) Required header
```http
Authorization: Bearer <firebase-id-token>
```
## 2) Route summary
| Method | Route | Purpose |
| --- | --- | --- |
| `GET` | `/query/tenants/:tenantId/orders` | Order list |
| `GET` | `/query/tenants/:tenantId/orders/:orderId` | Order detail with shifts and roles |
| `GET` | `/query/tenants/:tenantId/businesses/:businessId/favorite-staff` | Favorite staff list |
| `GET` | `/query/tenants/:tenantId/staff/:staffId/review-summary` | Staff rating summary and recent reviews |
| `GET` | `/query/tenants/:tenantId/assignments/:assignmentId/attendance` | Attendance session and event detail |
| `GET` | `/readyz` | Ready check |
## 3) Order list
```text
GET /query/tenants/:tenantId/orders
```
Optional query params:
- `businessId`
- `status`
- `limit`
- `offset`
Response shape:
```json
{
"items": [
{
"id": "uuid",
"orderNumber": "ORD-1001",
"title": "Cafe Event Staffing",
"status": "OPEN",
"serviceType": "EVENT",
"startsAt": "2026-03-12T08:00:00.000Z",
"endsAt": "2026-03-12T16:00:00.000Z",
"businessId": "uuid",
"businessName": "Google Mountain View Cafes",
"vendorId": "uuid",
"vendorName": "Legendary Staffing Pool A",
"shiftCount": 1,
"requiredWorkers": 2,
"assignedWorkers": 1
}
],
"requestId": "uuid"
}
```
## 4) Order detail
```text
GET /query/tenants/:tenantId/orders/:orderId
```
Response shape:
```json
{
"id": "uuid",
"orderNumber": "ORD-1001",
"title": "Cafe Event Staffing",
"status": "OPEN",
"businessId": "uuid",
"businessName": "Google Mountain View Cafes",
"vendorId": "uuid",
"vendorName": "Legendary Staffing Pool A",
"shifts": [
{
"id": "uuid",
"shiftCode": "SHIFT-1",
"title": "Morning Shift",
"status": "OPEN",
"startsAt": "2026-03-12T08:00:00.000Z",
"endsAt": "2026-03-12T16:00:00.000Z",
"requiredWorkers": 2,
"assignedWorkers": 1,
"roles": [
{
"id": "uuid",
"roleCode": "BARISTA",
"roleName": "Barista",
"workersNeeded": 2,
"assignedCount": 1
}
]
}
],
"requestId": "uuid"
}
```
## 5) Favorite staff list
```text
GET /query/tenants/:tenantId/businesses/:businessId/favorite-staff
```
Optional query params:
- `limit`
- `offset`
## 6) Staff review summary
```text
GET /query/tenants/:tenantId/staff/:staffId/review-summary
```
Optional query params:
- `limit`
Response includes:
- staff identity
- average rating
- rating count
- recent reviews
## 7) Assignment attendance detail
```text
GET /query/tenants/:tenantId/assignments/:assignmentId/attendance
```
Response includes:
- assignment status
- shift info
- attendance session
- ordered attendance events
- NFC and geofence validation fields
## 8) Current boundary
Frontend should use only these documented reads on `query-api-v2`.
Do not point dashboard, reports, finance, or other undocumented list/detail views here yet.

View File

@@ -0,0 +1,288 @@
# 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
Full auth behavior, including staff phone flow and refresh rules, is documented in [Authentication](./authentication.md).
### 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

File diff suppressed because it is too large Load Diff

View File

@@ -1,232 +1,10 @@
# M4 API Catalog (Core Only)
# Moved
Status: Active
Date: 2026-02-24
Owner: Technical Lead
Environment: dev
The canonical v2 backend API docs now live here:
## Frontend source of truth
Use this file and `docs/MILESTONES/M4/planning/m4-core-api-frontend-guide.md` for core endpoint consumption.
- `docs/BACKEND/API_GUIDES/V2/README.md`
- `docs/BACKEND/API_GUIDES/V2/core-api.md`
- `docs/BACKEND/API_GUIDES/V2/command-api.md`
- `docs/BACKEND/API_GUIDES/V2/query-api.md`
## Related next-slice contract
Verification pipeline design (attire, government ID, certification):
- `docs/MILESTONES/M4/planning/m4-verification-architecture-contract.md`
## 1) Scope and purpose
This catalog defines the currently implemented core backend contract for M4.
## 2) Global API rules
1. Route group in scope: `/core/*`.
2. Compatibility aliases in scope:
- `POST /uploadFile` -> `POST /core/upload-file`
- `POST /createSignedUrl` -> `POST /core/create-signed-url`
- `POST /invokeLLM` -> `POST /core/invoke-llm`
3. Auth model:
- `GET /health` is public in dev
- all other routes require `Authorization: Bearer <firebase-id-token>`
4. Standard error envelope:
```json
{
"code": "STRING_CODE",
"message": "Human readable message",
"details": {},
"requestId": "optional-request-id"
}
```
5. Response header:
- `X-Request-Id`
## 3) Core routes
## 3.1 Upload file
1. Method and route: `POST /core/upload-file`
2. Request format: `multipart/form-data`
3. Fields:
- `file` (required)
- `visibility` (`public` or `private`, optional)
- `category` (optional)
4. Accepted types:
- `application/pdf`
- `image/jpeg`
- `image/jpg`
- `image/png`
5. Max size: `10 MB` (default)
6. Behavior: real upload to Cloud Storage.
7. Success `200`:
```json
{
"fileUri": "gs://krow-workforce-dev-private/uploads/<uid>/...",
"contentType": "application/pdf",
"size": 12345,
"bucket": "krow-workforce-dev-private",
"path": "uploads/<uid>/...",
"requestId": "uuid"
}
```
8. Errors:
- `UNAUTHENTICATED`
- `INVALID_FILE_TYPE`
- `FILE_TOO_LARGE`
## 3.2 Create signed URL
1. Method and route: `POST /core/create-signed-url`
2. Request:
```json
{
"fileUri": "gs://krow-workforce-dev-private/uploads/<uid>/file.pdf",
"expiresInSeconds": 300
}
```
3. Security checks:
- bucket must be allowed
- path must be owned by caller (`uploads/<caller_uid>/...`)
- object must exist
- `expiresInSeconds <= 900`
4. Success `200`:
```json
{
"signedUrl": "https://storage.googleapis.com/...",
"expiresAt": "2026-02-24T15:22:28.105Z",
"requestId": "uuid"
}
```
5. Errors:
- `VALIDATION_ERROR`
- `FORBIDDEN`
- `NOT_FOUND`
## 3.3 Invoke model
1. Method and route: `POST /core/invoke-llm`
2. Request:
```json
{
"prompt": "...",
"responseJsonSchema": {},
"fileUrls": []
}
```
3. Behavior:
- real Vertex AI call
- model default: `gemini-2.0-flash-001`
- timeout default: `20 seconds`
4. Rate limit:
- `20 requests/minute` per user (default)
- when exceeded: `429 RATE_LIMITED` and `Retry-After` header
5. Success `200`:
```json
{
"result": {},
"model": "gemini-2.0-flash-001",
"latencyMs": 367,
"requestId": "uuid"
}
```
6. Errors:
- `UNAUTHENTICATED`
- `VALIDATION_ERROR`
- `MODEL_TIMEOUT`
- `MODEL_FAILED`
- `RATE_LIMITED`
## 3.4 Create verification job
1. Method and route: `POST /core/verifications`
2. Auth: required
3. Request:
```json
{
"type": "attire",
"subjectType": "worker",
"subjectId": "worker_123",
"fileUri": "gs://krow-workforce-dev-private/uploads/<uid>/file.pdf",
"rules": {}
}
```
4. Behavior:
- validates `fileUri` ownership
- requires file existence when `UPLOAD_MOCK=false` and `VERIFICATION_REQUIRE_FILE_EXISTS=true`
- enqueues async verification
5. Success `202`:
```json
{
"verificationId": "ver_123",
"status": "PENDING",
"type": "attire",
"requestId": "uuid"
}
```
6. Errors:
- `UNAUTHENTICATED`
- `VALIDATION_ERROR`
- `FORBIDDEN`
- `NOT_FOUND`
## 3.5 Get verification status
1. Method and route: `GET /core/verifications/{verificationId}`
2. Auth: required
3. Success `200`:
```json
{
"verificationId": "ver_123",
"status": "NEEDS_REVIEW",
"type": "attire",
"requestId": "uuid"
}
```
4. Errors:
- `UNAUTHENTICATED`
- `FORBIDDEN`
- `NOT_FOUND`
## 3.6 Review verification
1. Method and route: `POST /core/verifications/{verificationId}/review`
2. Auth: required
3. Request:
```json
{
"decision": "APPROVED",
"note": "Manual review passed",
"reasonCode": "MANUAL_REVIEW"
}
```
4. Success `200`: status becomes `APPROVED` or `REJECTED`.
5. Errors:
- `UNAUTHENTICATED`
- `VALIDATION_ERROR`
- `FORBIDDEN`
- `NOT_FOUND`
## 3.7 Retry verification
1. Method and route: `POST /core/verifications/{verificationId}/retry`
2. Auth: required
3. Success `202`: status resets to `PENDING`.
4. Errors:
- `UNAUTHENTICATED`
- `FORBIDDEN`
- `NOT_FOUND`
## 3.8 Health
1. Method and route: `GET /health`
2. Success `200`:
```json
{
"ok": true,
"service": "krow-core-api",
"version": "dev",
"requestId": "uuid"
}
```
## 4) Locked defaults
1. Validation library: `zod`.
2. Validation schema location: `backend/core-api/src/contracts/`.
3. Buckets:
- `krow-workforce-dev-public`
- `krow-workforce-dev-private`
4. Model provider: Vertex AI Gemini.
5. Max signed URL expiry: `900` seconds.
6. LLM timeout: `20000` ms.
7. LLM rate limit: `20` requests/minute/user.
8. Verification access mode default: `authenticated`.
9. Verification file existence check default: enabled (`VERIFICATION_REQUIRE_FILE_EXISTS=true`).
10. Verification attire provider default in dev: `vertex` with model `gemini-2.0-flash-lite-001`.
11. Verification government/certification providers: external adapters via configured provider URL/token.
This file is kept only as a compatibility pointer.

View File

@@ -1,375 +1,9 @@
# M4 Core API Frontend Guide (Dev)
# Moved
Status: Active
Last updated: 2026-02-27
Audience: Web and mobile frontend developers
The canonical Core API frontend doc now lives here:
## 1) Base URLs (dev)
1. Core API: `https://krow-core-api-e3g6witsvq-uc.a.run.app`
- `docs/BACKEND/API_GUIDES/V2/core-api.md`
## 2) Auth requirements
1. Send Firebase ID token on protected routes:
```http
Authorization: Bearer <firebase-id-token>
```
2. Health route is public:
- `GET /health`
3. All other routes require Firebase token.
Start from:
## 3) Standard error envelope
```json
{
"code": "STRING_CODE",
"message": "Human readable message",
"details": {},
"requestId": "uuid"
}
```
## 4) Core API endpoints
## 4.1 Upload file
1. Route: `POST /core/upload-file`
2. Alias: `POST /uploadFile`
3. Content type: `multipart/form-data`
4. Form fields:
- `file` (required)
- `visibility` (optional: `public` or `private`, default `private`)
- `category` (optional)
5. Accepted file types:
- `application/pdf`
- `image/jpeg`
- `image/jpg`
- `image/png`
- `audio/webm`
- `audio/wav`
- `audio/x-wav`
- `audio/mpeg`
- `audio/mp3`
- `audio/mp4`
- `audio/m4a`
- `audio/aac`
- `audio/ogg`
- `audio/flac`
6. Max upload size: `10 MB` (default)
7. Current behavior: real upload to Cloud Storage (not mock)
8. Success `200` example:
```json
{
"fileUri": "gs://krow-workforce-dev-private/uploads/<uid>/173...",
"contentType": "application/pdf",
"size": 12345,
"bucket": "krow-workforce-dev-private",
"path": "uploads/<uid>/173..._file.pdf",
"requestId": "uuid"
}
```
## 4.2 Create signed URL
1. Route: `POST /core/create-signed-url`
2. Alias: `POST /createSignedUrl`
3. Request body:
```json
{
"fileUri": "gs://krow-workforce-dev-private/uploads/<uid>/file.pdf",
"expiresInSeconds": 300
}
```
4. Security checks:
- bucket must be allowed (`krow-workforce-dev-public` or `krow-workforce-dev-private`)
- path must be owned by caller (`uploads/<caller_uid>/...`)
- object must exist
- `expiresInSeconds` must be `<= 900`
5. Success `200` example:
```json
{
"signedUrl": "https://storage.googleapis.com/...",
"expiresAt": "2026-02-24T15:22:28.105Z",
"requestId": "uuid"
}
```
6. Typical errors:
- `400 VALIDATION_ERROR` (bad payload or expiry too high)
- `403 FORBIDDEN` (path not owned by caller)
- `404 NOT_FOUND` (object does not exist)
## 4.3 Invoke model
1. Route: `POST /core/invoke-llm`
2. Alias: `POST /invokeLLM`
3. Request body:
```json
{
"prompt": "Return JSON with keys summary and risk.",
"responseJsonSchema": {
"type": "object",
"properties": {
"summary": { "type": "string" },
"risk": { "type": "string" }
},
"required": ["summary", "risk"]
},
"fileUrls": []
}
```
4. Current behavior: real Vertex model call (not mock)
- model: `gemini-2.0-flash-001`
- timeout: `20 seconds`
5. Rate limit:
- per-user `20 requests/minute` (default)
- on limit: `429 RATE_LIMITED`
- includes `Retry-After` header
6. Success `200` example:
```json
{
"result": { "summary": "text", "risk": "Low" },
"model": "gemini-2.0-flash-001",
"latencyMs": 367,
"requestId": "uuid"
}
```
## 4.4 Rapid order transcribe (audio to text)
1. Route: `POST /core/rapid-orders/transcribe`
2. Auth: required
3. Purpose: transcribe uploaded RAPID voice note into text for the RAPID input box.
4. Request body:
```json
{
"audioFileUri": "gs://krow-workforce-dev-private/uploads/<uid>/rapid-request.webm",
"locale": "en-US",
"promptHints": ["server", "urgent"]
}
```
5. Security checks:
- `audioFileUri` must be in allowed bucket
- `audioFileUri` path must be owned by caller (`uploads/<caller_uid>/...`)
- file existence is required in non-mock upload mode
6. Success `200` example:
```json
{
"transcript": "Need 2 servers ASAP for 4 hours.",
"confidence": 0.87,
"language": "en-US",
"warnings": [],
"model": "gemini-2.0-flash-001",
"latencyMs": 412,
"requestId": "uuid"
}
```
7. Typical errors:
- `400 VALIDATION_ERROR` (invalid payload)
- `401 UNAUTHENTICATED` (missing/invalid bearer token)
- `403 FORBIDDEN` (audio path not owned by caller)
- `429 RATE_LIMITED` (model quota per user)
- `502 MODEL_FAILED` (upstream model output/availability)
## 4.5 Rapid order parse (text to structured draft)
1. Route: `POST /core/rapid-orders/parse`
2. Auth: required
3. Purpose: convert RAPID text into structured one-time order draft JSON for form prefill.
4. Request body:
```json
{
"text": "Need 2 servers ASAP for 4 hours",
"locale": "en-US",
"timezone": "America/New_York",
"now": "2026-02-27T12:00:00.000Z"
}
```
5. Success `200` example:
```json
{
"parsed": {
"orderType": "ONE_TIME",
"isRapid": true,
"positions": [
{ "role": "server", "count": 2 }
],
"startAt": "2026-02-27T12:00:00.000Z",
"endAt": null,
"durationMinutes": 240,
"locationHint": null,
"notes": null,
"sourceText": "Need 2 servers ASAP for 4 hours"
},
"missingFields": [],
"warnings": [],
"confidence": {
"overall": 0.72,
"fields": {
"positions": 0.86,
"startAt": 0.9,
"durationMinutes": 0.88
}
},
"model": "gemini-2.0-flash-001",
"latencyMs": 531,
"requestId": "uuid"
}
```
6. Contract notes:
- unknown request keys are rejected (`400 VALIDATION_ERROR`)
- when information is missing/ambiguous, backend returns `missingFields` and `warnings`
- frontend should use output to prefill one-time order and request user confirmation where needed
## 4.6 Create verification job
1. Route: `POST /core/verifications`
2. Auth: required
3. Purpose: enqueue an async verification job for an uploaded file.
4. Request body:
```json
{
"type": "attire",
"subjectType": "worker",
"subjectId": "<worker-id>",
"fileUri": "gs://krow-workforce-dev-private/uploads/<uid>/file.pdf",
"rules": {
"dressCode": "black shoes"
}
}
```
5. Success `202` example:
```json
{
"verificationId": "ver_123",
"status": "PENDING",
"type": "attire",
"requestId": "uuid"
}
```
6. Current machine processing behavior in dev:
- `attire`: live vision check using Vertex Gemini Flash Lite model.
- `government_id`: third-party adapter path (falls back to `NEEDS_REVIEW` if provider is not configured).
- `certification`: third-party adapter path (falls back to `NEEDS_REVIEW` if provider is not configured).
## 4.7 Get verification status
1. Route: `GET /core/verifications/{verificationId}`
2. Auth: required
3. Purpose: polling status from frontend.
4. Success `200` example:
```json
{
"verificationId": "ver_123",
"status": "NEEDS_REVIEW",
"type": "attire",
"review": null,
"requestId": "uuid"
}
```
## 4.8 Review verification
1. Route: `POST /core/verifications/{verificationId}/review`
2. Auth: required
3. Purpose: final human decision for the verification.
4. Request body:
```json
{
"decision": "APPROVED",
"note": "Manual review passed",
"reasonCode": "MANUAL_REVIEW"
}
```
5. Success `200` example:
```json
{
"verificationId": "ver_123",
"status": "APPROVED",
"review": {
"decision": "APPROVED",
"reviewedBy": "<uid>"
},
"requestId": "uuid"
}
```
## 4.9 Retry verification
1. Route: `POST /core/verifications/{verificationId}/retry`
2. Auth: required
3. Purpose: requeue verification to run again.
4. Success `202` example: status resets to `PENDING`.
## 5) Frontend fetch examples (web)
## 5.1 Signed URL request
```ts
const token = await firebaseAuth.currentUser?.getIdToken();
const res = await fetch('https://krow-core-api-e3g6witsvq-uc.a.run.app/core/create-signed-url', {
method: 'POST',
headers: {
Authorization: `Bearer ${token}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
fileUri: 'gs://krow-workforce-dev-private/uploads/<uid>/file.pdf',
expiresInSeconds: 300,
}),
});
const data = await res.json();
```
## 5.2 Model request
```ts
const token = await firebaseAuth.currentUser?.getIdToken();
const res = await fetch('https://krow-core-api-e3g6witsvq-uc.a.run.app/core/invoke-llm', {
method: 'POST',
headers: {
Authorization: `Bearer ${token}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
prompt: 'Return JSON with status.',
responseJsonSchema: {
type: 'object',
properties: { status: { type: 'string' } },
required: ['status'],
},
}),
});
const data = await res.json();
```
## 5.3 Rapid audio transcribe request
```ts
const token = await firebaseAuth.currentUser?.getIdToken();
const res = await fetch('https://krow-core-api-e3g6witsvq-uc.a.run.app/core/rapid-orders/transcribe', {
method: 'POST',
headers: {
Authorization: `Bearer ${token}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
audioFileUri: 'gs://krow-workforce-dev-private/uploads/<uid>/rapid-request.webm',
locale: 'en-US',
promptHints: ['server', 'urgent'],
}),
});
const data = await res.json();
```
## 5.4 Rapid text parse request
```ts
const token = await firebaseAuth.currentUser?.getIdToken();
const res = await fetch('https://krow-core-api-e3g6witsvq-uc.a.run.app/core/rapid-orders/parse', {
method: 'POST',
headers: {
Authorization: `Bearer ${token}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
text: 'Need 2 servers ASAP for 4 hours',
locale: 'en-US',
timezone: 'America/New_York',
}),
});
const data = await res.json();
```
## 6) Notes for frontend team
1. Use canonical `/core/*` routes for new work.
2. Aliases exist only for migration compatibility.
3. `requestId` in responses should be logged client-side for debugging.
4. For 429 on model route, retry with exponential backoff and respect `Retry-After`.
5. Verification routes are now available in dev under `/core/verifications*`.
6. Current verification processing is async and returns machine statuses first (`PENDING`, `PROCESSING`, `NEEDS_REVIEW`, etc.).
7. Full verification design and policy details:
`docs/MILESTONES/M4/planning/m4-verification-architecture-contract.md`.
- `docs/BACKEND/API_GUIDES/V2/README.md`

View File

@@ -178,11 +178,17 @@ Tables:
3. `workforce`
4. `applications`
5. `assignments`
6. `staff_reviews`
7. `staff_favorites`
Rules:
1. One active workforce relation per `(vendor_id, staff_id)`.
2. One application per `(shift_id, role_id, staff_id)` unless versioned intentionally.
3. Assignment state transitions only through command APIs.
4. Business quality signals are relational:
- `staff_reviews` stores rating and review text from businesses,
- `staff_favorites` stores reusable staffing preferences,
- aggregate rating is materialized on `staffs`.
## 4.5 Compliance and Verification
Tables:
@@ -222,19 +228,22 @@ Rules:
## 4.9 Attendance, Timesheets, and Offense Governance
Tables:
1. `attendance_events` (append-only: clock-in/out, source, correction metadata)
2. `attendance_sessions` (derived work session per assignment)
3. `timesheets` (approval-ready payroll snapshot)
4. `timesheet_adjustments` (manual edits with reason and actor)
5. `offense_policies` (tenant/business scoped policy set)
6. `offense_rules` (threshold ladder and consequence)
7. `offense_events` (actual violation events)
8. `enforcement_actions` (warning, suspension, disable, block)
1. `clock_points` (approved tap and geo validation points per business or venue)
2. `attendance_events` (append-only: clock-in/out, source, NFC, geo, correction metadata)
3. `attendance_sessions` (derived work session per assignment)
4. `timesheets` (approval-ready payroll snapshot)
5. `timesheet_adjustments` (manual edits with reason and actor)
6. `offense_policies` (tenant/business scoped policy set)
7. `offense_rules` (threshold ladder and consequence)
8. `offense_events` (actual violation events)
9. `enforcement_actions` (warning, suspension, disable, block)
Rules:
1. Attendance corrections are additive events, not destructive overwrites.
2. Offense consequences are computed from policy + history and persisted as explicit actions.
3. Manual overrides require actor, reason, and timestamp in audit trail.
2. NFC and geo validation happens against `clock_points`, not hardcoded client logic.
3. Rejected attendance attempts are still logged as events for audit.
4. Offense consequences are computed from policy + history and persisted as explicit actions.
5. Manual overrides require actor, reason, and timestamp in audit trail.
## 4.10 Stakeholder Network Extensibility
Tables:

View File

@@ -96,6 +96,8 @@ erDiagram
| `shift_managers` | `id` | `shift_id -> shifts.id`, `team_member_id -> team_members.id` | `(shift_id, team_member_id)` |
| `applications` | `id` | `tenant_id -> tenants.id`, `shift_id -> shifts.id`, `role_id -> roles.id`, `staff_id -> staffs.id` | `(shift_id, role_id, staff_id)` |
| `assignments` | `id` | `tenant_id -> tenants.id`, `shift_role_id -> shift_roles.id`, `workforce_id -> workforce.id` | `(shift_role_id, workforce_id)` active |
| `staff_reviews` | `id` | `tenant_id -> tenants.id`, `business_id -> businesses.id`, `staff_id -> staffs.id`, `assignment_id -> assignments.id` | `(business_id, assignment_id, staff_id)` |
| `staff_favorites` | `id` | `tenant_id -> tenants.id`, `business_id -> businesses.id`, `staff_id -> staffs.id` | `(business_id, staff_id)` |
### 4.2 Diagram
@@ -122,6 +124,11 @@ erDiagram
STAFFS ||--o{ APPLICATIONS : applies
SHIFT_ROLES ||--o{ ASSIGNMENTS : allocates
WORKFORCE ||--o{ ASSIGNMENTS : executes
BUSINESSES ||--o{ STAFF_REVIEWS : rates
STAFFS ||--o{ STAFF_REVIEWS : receives
ASSIGNMENTS ||--o{ STAFF_REVIEWS : references
BUSINESSES ||--o{ STAFF_FAVORITES : favorites
STAFFS ||--o{ STAFF_FAVORITES : selected
```
```
@@ -131,7 +138,8 @@ erDiagram
| Model | Primary key | Foreign keys | Important unique keys |
|---|---|---|---|
| `attendance_events` | `id` | `tenant_id -> tenants.id`, `assignment_id -> assignments.id` | `(assignment_id, source_event_id)` |
| `clock_points` | `id` | `tenant_id -> tenants.id`, `business_id -> businesses.id` | `(tenant_id, nfc_tag_uid)` nullable |
| `attendance_events` | `id` | `tenant_id -> tenants.id`, `assignment_id -> assignments.id`, `clock_point_id -> clock_points.id` | append-only event log |
| `attendance_sessions` | `id` | `tenant_id -> tenants.id`, `assignment_id -> assignments.id` | one open session per assignment |
| `timesheets` | `id` | `tenant_id -> tenants.id`, `assignment_id -> assignments.id`, `staff_id -> staffs.id` | `(assignment_id)` |
| `timesheet_adjustments` | `id` | `timesheet_id -> timesheets.id`, `actor_user_id -> users.id` | - |
@@ -144,6 +152,8 @@ erDiagram
```mermaid
erDiagram
BUSINESSES ||--o{ CLOCK_POINTS : defines
CLOCK_POINTS ||--o{ ATTENDANCE_EVENTS : validates
ASSIGNMENTS ||--o{ ATTENDANCE_EVENTS : emits
ASSIGNMENTS ||--o{ ATTENDANCE_SESSIONS : opens
ASSIGNMENTS ||--o{ TIMESHEETS : settles

View File

@@ -0,0 +1,10 @@
# Moved
The canonical frontend-facing v2 backend docs now live here:
- `docs/BACKEND/API_GUIDES/V2/README.md`
- `docs/BACKEND/API_GUIDES/V2/core-api.md`
- `docs/BACKEND/API_GUIDES/V2/command-api.md`
- `docs/BACKEND/API_GUIDES/V2/query-api.md`
This file is kept only as a compatibility pointer.