Skip to main content

DemozPay — Fresh API Inventory

Snapshot: 2026-05-31. Built by grepping @Controller, @(Get|Post|Put|Delete), @RequireOrgRole, @RequirePlatformAdmin, @Public across the full repo (apps/api/src/ + packages/*/backend/presentation/). The earlier docs/API_INVENTORY.md survived this audit; this doc supersedes it for the routes both cover.

All paths below carry the global prefix /api (apps/api/src/main.ts:87).

Auth model summary

Every route falls into one of these auth tiers:

  • Public@Public() decorator exempts from AuthGuard (e.g. /api/healthz, webhook ingress, /api/metrics). Webhooks verify HMAC themselves.
  • Session — no auth decorator; just requires a valid better-auth session. Tenant is pinned from session.
  • OrgRole@RequireOrgRole(...) with one or more of 'member' | 'admin' | 'owner'. Enforced by OrgRoleGuard (apps/api/src/identity/auth/auth.module.ts:74).
  • PlatformAdmin@RequirePlatformAdmin(). Enforced by OrgRoleGuard + AdminMfaGuard (TOTP required).

1. Health, metrics, app root

MethodPathAuthSourceStatus
GET/ (no prefix)Publicapps/api/src/app/app.controller.ts:12Live
GET/healthzPublicapps/api/src/_infra/health/health.controller.ts:25Live
GET/readyzPublicapps/api/src/_infra/health/health.controller.ts:35Live
GET/api/metricsPublicapps/api/src/_infra/observability/metrics/metrics.controller.ts:19Live

2. Better-auth (mounted before Nest router on /api/auth/*)

Mounted as an Express handler in apps/api/src/main.ts:41-84 ahead of the Nest router. All routes carry the auth rate-limit (in-memory token bucket per IP, apps/api/src/identity/auth/auth-rate-limit.ts). Plugins wired:

PluginRoutes
emailAndPasswordsign-up, sign-in, sign-out, change-password, get-session
emailVerificationsend-verification-email, verify-email, request-password-reset, reset-password
organizationcreate-organization, list-organizations, list-members, invite-member, accept-invitation, etc.
phoneNumbersend-otp, verify-otp
magicLinkrequest-magic-link, verify-magic-link
twoFactor pluginNot wired (table exists, plugin pending — STATUS_LEGEND.md:21)

Full route list is determined by the better-auth library and the plugins configured at apps/api/src/identity/auth/better-auth.factory.ts. Treat them as Live.


3. Business (/api/business) — apps/api/src/business/business.controller.ts

MethodPathAuthDTOStatus
POST/api/businessSessionCreateBusinessDtoLive
GET/api/businessSessionLive
GET/api/business/:idSessionLive
GET/api/business/:id/employee-countSessionLive
PUT/api/business/:idRequireOrgRole / RequirePlatformAdmin (mixed)UpdateBusinessDtoLive
PUT/api/business/:id/verify-kycRequirePlatformAdminLive
PUT/api/business/:id/statusRequirePlatformAdminUpdateStatusDtoLive
DELETE/api/business/:idRequirePlatformAdminLive

Implementation status: Live.


4. Employees (/api/employees) — apps/api/src/workforce/employee/employee.controller.ts

MethodPathAuthDTOStatus
POST/api/employeesRequireOrgRoleCreateEmployeeDtoLive
POST/api/employees/bulk-importRequireOrgRoleCSV bodyLive
GET/api/employeesRequireOrgRoleLive
GET/api/employees/active/:businessIdRequireOrgRoleLive
GET/api/employees/department/:businessId/:departmentRequireOrgRoleLive
GET/api/employees/:idRequireOrgRoleLive
PUT/api/employees/:idRequireOrgRoleUpdateEmployeeDtoLive
PUT/api/employees/:id/statusRequireOrgRoleUpdateStatusDtoLive
DELETE/api/employees/:idRequireOrgRoleLive

5. Self-service (/api/me) — apps/api/src/identity/me/me.controller.ts + me-equb.controller.ts

MethodPathAuthSourceStatus
GET/api/me/profileSessionme.controller.ts:57Live
GET/api/me/loansSessionme.controller.ts:75Live
GET/api/me/loans/:idSessionme.controller.ts:95Live
GET/api/me/equb/cyclesSessionme-equb.controller.ts:67Live
GET/api/me/equb/invitationsSessionme-equb.controller.ts:79Live
POST/api/me/equb/cyclesSessionme-equb.controller.ts:104Live
POST/api/me/equb/cycles/:id/invitations/acceptSessionme-equb.controller.ts:134Live
POST/api/me/equb/cycles/:id/invitations/declineSessionme-equb.controller.ts:145Live
POST/api/me/equb/cycles/:id/contributeSessionme-equb.controller.ts:167Live
GET/api/me/ewa/eligibilitySessionme-ewa.controller.ts — caller's "how much can I take?" (employeeId from session, current period)Live
GET/api/me/ewa/requestsSessionme-ewa.controller.ts — caller's own EWA requestsLive
POST/api/me/ewa/requestsSession + Idempotency-Keyme-ewa.controller.ts — employee self-request (employeeId from session; payout + period derived)Live

Missing per docs/API_INVENTORY.md notes: /api/me/payslips (needs PayrollRunEntryRepository.findByEmployee; flagged as Planned). /api/me/ewa/requests is now Live (Phase 3.2).


6. EWA (/api/ewa) — packages/ewa/backend/presentation/ewa.controller.ts

MethodPathAuthUse caseStatus
GET/api/ewa/eligibilityRequireOrgRole(admin,owner)GetEligibilityLive
GET/api/ewa/requestsRequireOrgRole(admin,owner)ListEwaRequests — tenant list, newest-first, optional ?status= CSV + ?limit=Live
POST/api/ewa/requestsRequireOrgRole(admin,owner)RequestEwaLive
POST/api/ewa/requests/:id/approveRequireOrgRole(admin,owner)ApproveEwa — PENDING→APPROVEDLive
POST/api/ewa/requests/:id/rejectRequireOrgRole(admin,owner)RejectEwa — PENDING→REJECTED (reason), emits ewa.rejected.v1Live
POST/api/ewa/requests/:id/disburseRequireOrgRole(admin,owner)DisburseEwa — requires APPROVED; gated by KYC + account lookup + sanctionsLive
POST/api/ewa/requests/:id/record-repaymentRequireOrgRole(admin,owner)RecordEwaRepaymentLive
POST/api/ewa/requests/:id/remitRequireOrgRole(admin,owner)RemitEwa — remit principal PayrollClearing → Business pool (REPAID only)Live
GET/api/platform/ewa/requestsRequirePlatformAdmin + finance_ops:viewPlatformEwaService — cross-tenant list (per-tenant RLS fan-out), newest-first, optional ?status= CSV + ?limit=Live

Note: disburse POST is money-moving — should carry Idempotency-Key header (enforced in use case, not at the route).

Note: ewa.repaid.v1 auto-triggers RemitEwa via the ewa-remit-on-repaid Kafka consumer (ADR-026), so the remit POST is the manual/ops fallback when KAFKA_CONSUMERS_ENABLED is off.


7. Lending (/api/loans) — packages/lending/backend/presentation/lending.controller.ts

MethodPathAuthUse caseStatus
GET/api/loansRequireOrgRole(member,admin,owner)ListLoansLive
GET/api/loans/:idRequireOrgRole(member,admin,owner)GetLoanLive
POST/api/loans/quoteRequireOrgRole(admin,owner)QuoteLoanLive
POST/api/loansRequireOrgRole(admin,owner)RequestLoanLive
POST/api/loans/:id/disburseRequireOrgRole(admin,owner)DisburseLoanLive
POST/api/loans/:id/installments/:idx/remit-to-fiRequireOrgRole(admin,owner)RemitInstallmentToFiLive
POST/api/loans/:id/installments/:idx/record-repaymentRequireOrgRole(admin,owner)RecordRepaymentLive

Missing from this controller (per existing API_INVENTORY.md): /api/loans/:id/restructure, /write-off, /hardship-pause — all Planned.


8. KYC (/api/kyc) — packages/kyc/backend/presentation/kyc.controller.ts

MethodPathAuthUse caseStatus
POST/api/kyc(decorator absent — Session)SubmitKycLive
POST/api/kyc/:id/claimSessionClaimForReview (maker-checker)Live
POST/api/kyc/:id/approveSessionApproveKyc (calls sanctions inline)Live
POST/api/kyc/:id/rejectSessionRejectKycLive
POST/api/kyc/:id/request-more-infoSessionRequestMoreInfoLive
GET/api/kyc/queueSessionreview queueLive
GET/api/kyc/statusSessionGetKycStatusLive

Auth gap: none of the KYC routes have explicit @RequireOrgRole — they rely on session middleware to pin tenant and authorise. Recommended: add @RequireOrgRole('admin','owner') on approve/reject/claim/request-more-info to surface intent.


9. Sanctions (/api/sanctions) — packages/sanctions/backend/presentation/sanctions.controller.ts

MethodPathAuthUse caseStatus
POST/api/sanctions/screen(decorator absent — Session)ScreenIdentityLive
GET/api/sanctions/checksSessionlist ScreeningCheckLive

CSV ingest is a CLI (apps/api/src/compliance/sanctions/cmd/), not an HTTP route.

Auth gap: sanctions endpoints accept any authenticated session today. Recommended: @RequirePlatformAdmin() on screen + checks (compliance ops actions).


10. Payroll (/api/payroll) — multiple controllers

10.1 Run lifecycle — packages/payroll/backend/presentation/payroll.controller.ts

MethodPathAuthUse caseStatus
POST/api/payroll(decorator absent — Session)CreatePayrollRunLive
POST/api/payroll/:id/calculateSessionCalculatePayrollRunLive
POST/api/payroll/:id/approveSessionApprovePayrollRun (emits payroll.deductions_taken.v1)Live
POST/api/payroll/:id/disburseSessionDisbursePayrollRunLive
POST/api/payroll/:id/cancelSessionCancelPayrollRunLive
POST/api/payroll/:id/lockSessionLockPayrollRunLive
GET/api/payroll/:id/reportSessionreport JSONLive
GET/api/payroll/:id/report.csvSessionreport CSVLive
GET/api/payroll/:id/report.mor.xmlSessionMoR (tax) XML reportLive
GET/api/payroll/:id/report.pension.xmlSessionpension XML reportLive
GET/api/payroll/:id/payslips/:employeeIdSessionpayslip JSONLive
GET/api/payroll/:id/payslips/:employeeId.htmlSessionpayslip HTMLLive
GET/api/payrollSessionlist runsLive
GET/api/payroll/:idSessionget runLive

10.2 Adjustments — packages/payroll/backend/presentation/payroll-adjustment.controller.ts

Base: /api/payroll/adjustments.

MethodPathStatus
POST/api/payroll/adjustmentsLive
POST/api/payroll/adjustments/:id/approveLive
POST/api/payroll/adjustments/:id/postLive
POST/api/payroll/adjustments/:id/reverseLive
POST/api/payroll/adjustments/:id/voidLive
GET/api/payroll/adjustmentsLive
GET/api/payroll/adjustments/:idLive

10.3 Deduction mandates — packages/payroll/backend/presentation/deduction-mandate.controller.ts

Base: /api/payroll/deduction-mandates.

MethodPathStatus
POST/api/payroll/deduction-mandatesLive
POST/api/payroll/deduction-mandates/:id/activateLive
POST/api/payroll/deduction-mandates/:id/suspendLive
POST/api/payroll/deduction-mandates/:id/resumeLive
POST/api/payroll/deduction-mandates/:id/cancelLive
GET/api/payroll/deduction-mandatesLive
GET/api/payroll/deduction-mandates/:idLive

10.4 Protection / earnings / overtime (Phase 2) — payroll-p2.controller.ts

Base: /api/payroll.

MethodPathStatus
POST/api/payroll/protection-policiesLive
POST/api/payroll/protection-policies/:id/activateLive
GET/api/payroll/protection-policiesLive
POST/api/payroll/one-time-earningsLive
POST/api/payroll/one-time-earnings/:id/approveLive
POST/api/payroll/one-time-earnings/:id/voidLive
POST/api/payroll/one-time-earnings/:id/reverseLive
GET/api/payroll/one-time-earningsLive
POST/api/payroll/overtime-policiesLive
POST/api/payroll/overtime-policies/:id/activateLive
GET/api/payroll/overtime-policiesLive
POST/api/payroll/overtime-entriesLive
POST/api/payroll/overtime-entries/:id/approveLive
GET/api/payroll/overtime-entriesLive

10.5 Audit + settlement + PDFs (apps/api side controllers)

MethodPathAuthSourceStatus
GET/api/payroll/:id/auditRequireOrgRole(admin,owner)apps/api/src/payroll/payroll-audit.controller.ts:83Live
GET/api/payroll/:id/settlementRequireOrgRole(admin,owner)apps/api/src/payroll/payroll-settlement.controller.ts:25Live
GET/api/payroll/:id/payslips/:employeeId.pdfSessionapps/api/src/payroll/payroll-pdf.controller.ts:51Live
GET/api/payroll/:id/payslips.zipSessionapps/api/src/payroll/payroll-pdf.controller.ts:100Live

10.6 Tenant settings — apps/api/src/payroll/tenant-settings.controller.ts

Base: /api/payroll/tenant-settings. Class-level @RequireOrgRole('admin','owner').

MethodPathAuthStatus
POST/api/payroll/tenant-settingsOrgRole(admin,owner)Live
GET/api/payroll/tenant-settingsOrgRole(admin,owner)Live
GET/api/payroll/tenant-settings/effectiveOrgRole(admin,owner)Live
DELETE/api/payroll/tenant-settingsOrgRole(admin,owner)Live

10.7 Auto-lock policy — apps/api/src/payroll/auto-lock-policy.controller.ts

Base: /api/payroll/auto-lock-policy. Class-level @RequireOrgRole('admin','owner').

MethodPathAuthStatus
POST/api/payroll/auto-lock-policyOrgRole(admin,owner)Live
GET/api/payroll/auto-lock-policyOrgRole(admin,owner)Live
GET/api/payroll/auto-lock-policy/effectiveOrgRole(admin,owner)Live
DELETE/api/payroll/auto-lock-policyOrgRole(admin,owner)Live

10.8 Court-order remit — apps/api/src/payroll/consumers/court-order-remit.controller.ts

Base: /api/payroll.

MethodPathAuthStatus
GET/api/payroll/:id/report.court-orders.jsonSessionLive
GET/api/payroll/:id/report.court-orders.xmlSessionLive
POST/api/payroll/:id/report.court-orders/submitSession (idempotent)Live

10.9 Platform-admin — apps/api/src/payroll/platform-admin.controller.ts

Base: /api/platform/payroll. All routes carry @RequirePlatformAdmin() → TOTP enforced.

MethodPathStatus
POST/api/platform/payroll/auto-lock-policy/:tenantIdLive
GET/api/platform/payroll/auto-lock-policy/:tenantIdLive
DELETE/api/platform/payroll/auto-lock-policy/:tenantIdLive
GET/api/platform/payroll/auto-lock-policiesLive
GET/api/platform/payroll/tenant-settingsLive
POST/api/platform/payroll/tenant-settings/:tenantIdLive
GET/api/platform/payroll/tenant-settings/:tenantIdLive
DELETE/api/platform/payroll/tenant-settings/:tenantIdLive

10.10 Payroll health — apps/api/src/payroll/payroll-health.controller.ts

MethodPathAuthStatus
GET/api/health/payrollPublicLive

11. Equb

11.1 Domain endpoints — packages/equb/backend/presentation/equb.controller.ts

Base: /api/equb/cycles.

MethodPathAuthUse caseStatus
POST/api/equb/cyclesRequireOrgRole(admin,owner)CreateEqubCycleLive
GET/api/equb/cyclesRequireOrgRole(member,admin,owner)list cyclesLive
GET/api/equb/cycles/:idRequireOrgRole(member,admin,owner)get cycleLive
POST/api/equb/cycles/:id/membersRequireOrgRole(admin,owner)AddEqubMemberLive
POST/api/equb/cycles/:id/activateRequireOrgRole(admin,owner)ActivateEqubCycle (commits seed hash)Live
POST/api/equb/cycles/:id/invitations/acceptRequireOrgRole(member,admin,owner)AcceptEqubInvitationLive
POST/api/equb/cycles/:id/invitations/declineRequireOrgRole(member,admin,owner)DeclineEqubInvitationLive
POST/api/equb/cycles/:id/contributeRequireOrgRole(admin,owner)RecordEqubContributionLive
POST/api/equb/cycles/:id/drawRequireOrgRole(admin,owner)RunEqubDraw (reveals seed + nonce)Live
POST/api/equb/cycles/:id/payoutRequireOrgRole(admin,owner)CloseEqubRoundPayout (gated by KYC + sanctions)Live

11.2 Escrow admin — apps/api/src/products/equb/equb-escrow.controller.ts

Base: /api/equb/escrow. All routes carry @RequireOrgRole('admin','owner').

MethodPathStatus
POST/api/equb/escrow/bindingsLive
GET/api/equb/escrow/bindingsLive
POST/api/equb/escrow/bindings/:id/reconcileLive
GET/api/equb/escrow/runsLive

12. Integration / webhook ingress

12.1 Bank webhook — apps/api/src/money/integration/bank-webhook.controller.ts

MethodPathAuthStatus
POST/api/integration/bank-callback/:partnerPublic + HMAC + nonceLive

Replay defence: WebhookNonceUse Prisma model + apps/api/src/money/integration/webhook-nonce.repository.ts.

12.2 Webhook replay surface — apps/api/src/money/integration/bank-webhook-replay.controller.ts

Base: /api/integration/webhooks. @RequirePlatformAdmin().

MethodPathStatus
GET/api/integration/webhooksLive
GET/api/integration/webhooks/:idLive
POST/api/integration/webhooks/:id/replayLive

13. Cross-cutting: missing / gaps / duplicates

13.1 Missing endpoints (called out in code or backlog)

Wanted routeReason it isn't shipped
GET /api/me/ewa/requestsEwaRepository.findByEmployee not implemented (referenced as Planned in API_INVENTORY.md)
GET /api/me/payslipsPayroll repo extension needed
POST /api/loans/:id/restructurePlanned — no use case exists
POST /api/loans/:id/write-offPlanned
POST /api/loans/:id/hardship-pausePlanned
POST /api/sanctions/ingestCSV ingest is currently CLI-only (apps/api/src/compliance/sanctions/cmd/)
POST /api/dispute/*ADR-016 Proposed; no code
POST /api/adjustments/*ADR-015 Proposed; no code (distinct from payroll-adjustments)

13.2 Duplicates / near-duplicates

ConcernRoutes
Org-scoped vs platform-admin variants of auto-lock-policy and tenant-settings/api/payroll/auto-lock-policy/* (own tenant) and /api/platform/payroll/auto-lock-policy/:tenantId (any tenant). This is intentional — different audiences — but the org-scoped ones lack @RequireOrgRole.
Payslip output formatsThree formats: JSON (payslips/:employeeId), HTML (.html), PDF (.pdf). Plus ZIP (payslips.zip). All intentional.
Equb member-self vs admin-org actions/api/me/equb/* (self) vs /api/equb/cycles/* (admin). Intentional separation.

13.3 Auth decorator gaps (should be tightened)

ControllerRoutesTodayShould be
KYCall 7SessionRequireOrgRole(admin,owner) on approve/reject/claim/request-more-info
Sanctionsscreen + checksSessionRequirePlatformAdmin (compliance ops)
Payroll run lifecyclecreate/calc/approve/disburse/cancel/lockSessionRequireOrgRole(admin,owner)
Payroll tenant-settingsPOST/DELETEOrgRole(admin,owner)Closed 2026-05-31
Payroll auto-lock-policyPOST/DELETEOrgRole(admin,owner)Closed 2026-05-31
Payroll PDF + court-ordersGET/POSTSessionOK as-is for member visibility

The session middleware still pins tenant — but explicit decorators make intent reviewable.

13.4 Deprecated / removed routes

The earlier "double-prefix" bug (@Controller('api/...') + globalPrefix='api'/api/api/...) is fixed (per docs/API_INVENTORY.md). All controllers now use bare base paths. No deprecated routes still served.


14. Counts

  • Public: 4 (health x2, app root, metrics) + better-auth subtree + 1 webhook ingress.
  • Session-only: ~50 (most payroll + KYC + sanctions + me + business + employees).
  • OrgRole: 30+ (EWA, lending, equb, business writes, payroll PDFs).
  • PlatformAdmin (TOTP): 11 (platform-admin payroll, webhook replay, business kyc-verify/status/delete).

Total HTTP endpoints across NestJS: ~110 (excluding better-auth, which adds another ~25 generated by plugins).