API Endpoints
The Pivot backend ships its own auto-generated, always-in-sync OpenAPI spec. Don't duplicate it here; link to it.
The live API reference
The docs module (functions/modules/docs/) builds an OpenAPI 3.1 spec at boot from every Zod DTO registered in functions/modules/docs/logic/registry.ts. Served two ways:
- Interactive Scalar UI — /api-reference (embedded in this site; gated by
@pivotapp.caOAuth) - Raw JSON — /api/openapi.json (committed snapshot at
pivot-docs/static/api/openapi.json, refresh vianpm run fetch-openapi-spec)
The upstream cloudfunctions.net/docs endpoint requires a Firebase Bearer token; the embedded viewer above is the way engineers should access the spec.
The router lives at functions/modules/docs/endpoints/apis/docs.router.ts:24-51. Spec is cached in-process and regenerated only on cold-start. If you add a new route to a module, register it in registry.ts or it won't appear in /openapi.json.
Modules covered by the OpenAPI spec
All 17 modules are registered. The OpenAPI tags list at the bottom of registry.ts is the source of truth.
| Module | Notable routes |
|---|---|
sales | GET /sales |
integrations | GET /integrations, GET /integrations/status, GET /integrations/available[/:category], GET /integrations/:category/:slug |
companies | GET/PATCH /companies/:id, GET/PATCH /companies/:id/settings, GET/PATCH /companies/:id/policies, GET/POST/PATCH/DELETE /roles*, AI-tips-rules (/companies/:id/ai-tips-rules*), AI usage + config (/companies/:id/ai-usage, /ai-config/*) |
schedule | GET /shifts, POST/PUT/DELETE /shifts*, GET/PATCH /companies/:id/schedule-template |
employees | GET/POST /employees, GET/PATCH/DELETE /employees/:id, plus availability / days-off / rates / positions / integration-ids sub-resources |
attendance | GET /attendance*, POST /attendance/punches[/batch], PATCH/DELETE /attendance/punches/:id |
payroll | GET/PATCH /payroll/settings, GET /payroll/tip-outs, GET /payroll/issues |
applicants | GET /applicants, GET/PATCH /applicants/:id |
posts | GET /posts, GET/POST/PATCH/DELETE /posts/:id |
chat | GET /chats, GET/POST /chats/:chatId/messages, PATCH/DELETE /chats/:chatId/messages/:messageId |
ai-memory | GET/PATCH /ai-memory/company-profile, GET/POST/PATCH/DELETE /ai-memory/user-rules*, GET/POST/PATCH/DELETE /ai-memory/cron-tasks* |
documents | GET /employees/:id/documents, POST /employees/:id/documents/drafts, POST /employees/:id/documents/upload, finalize / delete |
calculator | POST /calculate — deterministic arithmetic (used by the AI's calculate tool). |
requests | GET /requests, POST /requests, PUT /requests/:id — shift-request CRUD. |
pos-sync | POST /sync-pos-employees, POST /load-cached-pos-employees, POST /link-pos-employee, POST /create-bulk-pos-employees, POST /get-pivot-employees. |
onboarding | GET /onboarding/session/:userId, POST /onboarding/session/:userId/complete — auth via x-user-token header, not the standard Authorization: Bearer. |
docs | GET /openapi.json, GET /docs, GET / — the spec server itself. |
auth | POST /send-reset-link, /verify-authorization-code, /accept-{head-office,employee}-invitation (public) plus /init-session, /signup, /setup-password, /send-{head-office,employee}-invitation, /revoke-head-office-invitation, /admin/impersonate. Lives at functions/systems/auth/, outside functions/modules/. |
integration-engine | POST /connection/{connect,disconnect,reconnect}, GET /connection/status/:companyId, POST /sync, POST /integration-id/:companyId, POST /partner-ingest/:provider, OpenTable partner flow. Distinct from the integrations catalog tag. Lives at functions/integration-engine/. |
Note: schemas for calculator, pos-sync, onboarding, and integration-engine are mirrored inline in registry.ts rather than imported from common/dtos/. Keep them in sync when their runtime schemas change. auth and requests properly import from common/dtos/. Consolidating the inline schemas is open work.
Cloud Run service (pivotAiAgent)
Separately deployed Express app, not part of the Functions monolith. Source: functions/pivotAiAgent/index.ts. Local port: :3457.
| Method | Path | Purpose |
|---|---|---|
| POST | /api/chat | Streaming AI chat (SSE) — see Normal Conversations |
| POST | /api/chat-async | Alias for /api/chat |
| POST | /api/widget-log | Frontend widget-interaction telemetry |
| POST | /upload | Multipart upload (CSV / image / sheet) for tool use |
| GET | /download/:fileId | Download generated Excel / PDF |
Not currently in the OpenAPI spec. Auth is via firebaseToken in the request body (the SSE response means the EventSource API can't set custom headers, so the token rides in the payload).
Outside the OpenAPI spec (legacy / non-HTTP surfaces)
The repo has substantial code outside functions/modules/ that hasn't been migrated. These aren't part of the OpenAPI doc and most aren't MIA-relevant, but they exist:
- Legacy
onRequestendpoints (~35) —functions/http/**(clock in/out, posts, notifications, scheduled posts),functions/cron/**(Pub/Sub-fired cron jobs likesaveYearlySalaryAttendance,autoClosePayroll,fetchWeatherForecast),functions/dashboard/**(labor cost, sales, SPLH metrics), integration webhooks (Clover, MYR, Givex, MaîtreD). onCall(Firebase Callable) (~30) —functions/on-call/**(labor, sales, messages, legal/EULA, print PDF, copy company, etc.),functions/integrations/**(POS / payroll integration connect/disconnect/sync flows). These are invoked from the frontend via the Firebase SDK, not as REST endpoints.- Pub/Sub and DB triggers (40+) —
functions/db/**, scheduled triggers across modules. RTDB write listeners that propagate changes (e.g.,onPublishedSchedulePgSync,onMessageCreate).
If you need to wire MIA to one of these, see if a module-based equivalent exists first. If you do call legacy, use the same auth contract (Firebase ID token in Authorization header).
Shared middleware
Located at functions/shared/middleware/. Every Hono router in a module mounts these:
| Middleware | What it does |
|---|---|
authMiddleware() | Verifies Authorization: Bearer <token>, sets c.set('uid', ...) and c.set('token', ...). Throws 401 on missing/invalid. |
companyScopeMiddleware({ from, key? }) | Reads companyId from query / param / body, validates against token.currentCompanyId. Allows cross-tenant for admin and isMiaAgent callers. Throws 403 on mismatch. |
requireClaim(name) | Generic "must have custom claim name set to true" gate. Used for isHeadOffice and similar. |
adminMiddleware() | Shorthand for requireClaim('admin'). |
requestCacheMiddleware | In-memory cache layer (used by the requests module). |
traceMiddleware() | Attaches a trace ID to each request. |
loggerMiddleware(moduleName) | Structured JSON request logging, tagged with the module. |
errorHandler | Global error-to-JSON formatter — produces consistent status + body shapes. |
Note: the AI-usage / AI-config endpoints in the companies module don't use companyScopeMiddleware (admins on /stats aren't company members). They gate with an inline isAdmin() check on the admin custom claim — see functions/modules/companies/endpoints/apis/ai-usage.router.ts:91.
See Security Model for how custom claims and ownership overrides work.