Files
Krow-workspace/docs/BACKEND/API_GUIDES/V2/unified-api.md

9.6 KiB

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.

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

Example GET /staff/clock-in/shifts/today item:

{
  "assignmentId": "uuid",
  "shiftId": "uuid",
  "title": "Assigned espresso shift",
  "clientName": "Google Mountain View Cafes",
  "hourlyRate": 23,
  "roleName": "Barista",
  "location": "Google MV Cafe Clock Point",
  "locationAddress": "1600 Amphitheatre Pkwy, Mountain View, CA",
  "latitude": 37.4221,
  "longitude": -122.0841,
  "startTime": "2026-03-17T13:48:23.482Z",
  "endTime": "2026-03-17T21:48:23.482Z",
  "clockInMode": "GEO_REQUIRED",
  "allowClockInOverride": true,
  "geofenceRadiusMeters": 120,
  "nfcTagId": "NFC-DEMO-ANA-001",
  "attendanceStatus": "NOT_CLOCKED_IN",
  "clockInAt": null
}

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

{
  "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

{
  "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

{
  "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

{
  "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