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

8.8 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

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

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