DemozPay — Fresh API Inventory
Snapshot: 2026-05-31. Built by grepping
@Controller,@(Get|Post|Put|Delete),@RequireOrgRole,@RequirePlatformAdmin,@Publicacross the full repo (apps/api/src/+packages/*/backend/presentation/). The earlierdocs/API_INVENTORY.mdsurvived 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 fromAuthGuard(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 byOrgRoleGuard(apps/api/src/identity/auth/auth.module.ts:74). - PlatformAdmin —
@RequirePlatformAdmin(). Enforced byOrgRoleGuard+AdminMfaGuard(TOTP required).
1. Health, metrics, app root
| Method | Path | Auth | Source | Status |
|---|---|---|---|---|
| GET | / (no prefix) | Public | apps/api/src/app/app.controller.ts:12 | Live |
| GET | /healthz | Public | apps/api/src/_infra/health/health.controller.ts:25 | Live |
| GET | /readyz | Public | apps/api/src/_infra/health/health.controller.ts:35 | Live |
| GET | /api/metrics | Public | apps/api/src/_infra/observability/metrics/metrics.controller.ts:19 | Live |
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:
| Plugin | Routes |
|---|---|
emailAndPassword | sign-up, sign-in, sign-out, change-password, get-session |
emailVerification | send-verification-email, verify-email, request-password-reset, reset-password |
organization | create-organization, list-organizations, list-members, invite-member, accept-invitation, etc. |
phoneNumber | send-otp, verify-otp |
magicLink | request-magic-link, verify-magic-link |
twoFactor plugin | Not 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
| Method | Path | Auth | DTO | Status |
|---|---|---|---|---|
| POST | /api/business | Session | CreateBusinessDto | Live |
| GET | /api/business | Session | — | Live |
| GET | /api/business/:id | Session | — | Live |
| GET | /api/business/:id/employee-count | Session | — | Live |
| PUT | /api/business/:id | RequireOrgRole / RequirePlatformAdmin (mixed) | UpdateBusinessDto | Live |
| PUT | /api/business/:id/verify-kyc | RequirePlatformAdmin | — | Live |
| PUT | /api/business/:id/status | RequirePlatformAdmin | UpdateStatusDto | Live |
| DELETE | /api/business/:id | RequirePlatformAdmin | — | Live |
Implementation status: Live.
4. Employees (/api/employees) — apps/api/src/workforce/employee/employee.controller.ts
| Method | Path | Auth | DTO | Status |
|---|---|---|---|---|
| POST | /api/employees | RequireOrgRole | CreateEmployeeDto | Live |
| POST | /api/employees/bulk-import | RequireOrgRole | CSV body | Live |
| GET | /api/employees | RequireOrgRole | — | Live |
| GET | /api/employees/active/:businessId | RequireOrgRole | — | Live |
| GET | /api/employees/department/:businessId/:department | RequireOrgRole | — | Live |
| GET | /api/employees/:id | RequireOrgRole | — | Live |
| PUT | /api/employees/:id | RequireOrgRole | UpdateEmployeeDto | Live |
| PUT | /api/employees/:id/status | RequireOrgRole | UpdateStatusDto | Live |
| DELETE | /api/employees/:id | RequireOrgRole | — | Live |
5. Self-service (/api/me) — apps/api/src/identity/me/me.controller.ts + me-equb.controller.ts
| Method | Path | Auth | Source | Status |
|---|---|---|---|---|
| GET | /api/me/profile | Session | me.controller.ts:57 | Live |
| GET | /api/me/loans | Session | me.controller.ts:75 | Live |
| GET | /api/me/loans/:id | Session | me.controller.ts:95 | Live |
| GET | /api/me/equb/cycles | Session | me-equb.controller.ts:67 | Live |
| GET | /api/me/equb/invitations | Session | me-equb.controller.ts:79 | Live |
| POST | /api/me/equb/cycles | Session | me-equb.controller.ts:104 | Live |
| POST | /api/me/equb/cycles/:id/invitations/accept | Session | me-equb.controller.ts:134 | Live |
| POST | /api/me/equb/cycles/:id/invitations/decline | Session | me-equb.controller.ts:145 | Live |
| POST | /api/me/equb/cycles/:id/contribute | Session | me-equb.controller.ts:167 | Live |
| GET | /api/me/ewa/eligibility | Session | me-ewa.controller.ts — caller's "how much can I take?" (employeeId from session, current period) | Live |
| GET | /api/me/ewa/requests | Session | me-ewa.controller.ts — caller's own EWA requests | Live |
| POST | /api/me/ewa/requests | Session + Idempotency-Key | me-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
| Method | Path | Auth | Use case | Status |
|---|---|---|---|---|
| GET | /api/ewa/eligibility | RequireOrgRole(admin,owner) | GetEligibility | Live |
| GET | /api/ewa/requests | RequireOrgRole(admin,owner) | ListEwaRequests — tenant list, newest-first, optional ?status= CSV + ?limit= | Live |
| POST | /api/ewa/requests | RequireOrgRole(admin,owner) | RequestEwa | Live |
| POST | /api/ewa/requests/:id/approve | RequireOrgRole(admin,owner) | ApproveEwa — PENDING→APPROVED | Live |
| POST | /api/ewa/requests/:id/reject | RequireOrgRole(admin,owner) | RejectEwa — PENDING→REJECTED (reason), emits ewa.rejected.v1 | Live |
| POST | /api/ewa/requests/:id/disburse | RequireOrgRole(admin,owner) | DisburseEwa — requires APPROVED; gated by KYC + account lookup + sanctions | Live |
| POST | /api/ewa/requests/:id/record-repayment | RequireOrgRole(admin,owner) | RecordEwaRepayment | Live |
| POST | /api/ewa/requests/:id/remit | RequireOrgRole(admin,owner) | RemitEwa — remit principal PayrollClearing → Business pool (REPAID only) | Live |
| GET | /api/platform/ewa/requests | RequirePlatformAdmin + finance_ops:view | PlatformEwaService — 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
| Method | Path | Auth | Use case | Status |
|---|---|---|---|---|
| GET | /api/loans | RequireOrgRole(member,admin,owner) | ListLoans | Live |
| GET | /api/loans/:id | RequireOrgRole(member,admin,owner) | GetLoan | Live |
| POST | /api/loans/quote | RequireOrgRole(admin,owner) | QuoteLoan | Live |
| POST | /api/loans | RequireOrgRole(admin,owner) | RequestLoan | Live |
| POST | /api/loans/:id/disburse | RequireOrgRole(admin,owner) | DisburseLoan | Live |
| POST | /api/loans/:id/installments/:idx/remit-to-fi | RequireOrgRole(admin,owner) | RemitInstallmentToFi | Live |
| POST | /api/loans/:id/installments/:idx/record-repayment | RequireOrgRole(admin,owner) | RecordRepayment | Live |
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
| Method | Path | Auth | Use case | Status |
|---|---|---|---|---|
| POST | /api/kyc | (decorator absent — Session) | SubmitKyc | Live |
| POST | /api/kyc/:id/claim | Session | ClaimForReview (maker-checker) | Live |
| POST | /api/kyc/:id/approve | Session | ApproveKyc (calls sanctions inline) | Live |
| POST | /api/kyc/:id/reject | Session | RejectKyc | Live |
| POST | /api/kyc/:id/request-more-info | Session | RequestMoreInfo | Live |
| GET | /api/kyc/queue | Session | review queue | Live |
| GET | /api/kyc/status | Session | GetKycStatus | Live |
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
| Method | Path | Auth | Use case | Status |
|---|---|---|---|---|
| POST | /api/sanctions/screen | (decorator absent — Session) | ScreenIdentity | Live |
| GET | /api/sanctions/checks | Session | list ScreeningCheck | Live |
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
| Method | Path | Auth | Use case | Status |
|---|---|---|---|---|
| POST | /api/payroll | (decorator absent — Session) | CreatePayrollRun | Live |
| POST | /api/payroll/:id/calculate | Session | CalculatePayrollRun | Live |
| POST | /api/payroll/:id/approve | Session | ApprovePayrollRun (emits payroll.deductions_taken.v1) | Live |
| POST | /api/payroll/:id/disburse | Session | DisbursePayrollRun | Live |
| POST | /api/payroll/:id/cancel | Session | CancelPayrollRun | Live |
| POST | /api/payroll/:id/lock | Session | LockPayrollRun | Live |
| GET | /api/payroll/:id/report | Session | report JSON | Live |
| GET | /api/payroll/:id/report.csv | Session | report CSV | Live |
| GET | /api/payroll/:id/report.mor.xml | Session | MoR (tax) XML report | Live |
| GET | /api/payroll/:id/report.pension.xml | Session | pension XML report | Live |
| GET | /api/payroll/:id/payslips/:employeeId | Session | payslip JSON | Live |
| GET | /api/payroll/:id/payslips/:employeeId.html | Session | payslip HTML | Live |
| GET | /api/payroll | Session | list runs | Live |
| GET | /api/payroll/:id | Session | get run | Live |
10.2 Adjustments — packages/payroll/backend/presentation/payroll-adjustment.controller.ts
Base: /api/payroll/adjustments.
| Method | Path | Status |
|---|---|---|
| POST | /api/payroll/adjustments | Live |
| POST | /api/payroll/adjustments/:id/approve | Live |
| POST | /api/payroll/adjustments/:id/post | Live |
| POST | /api/payroll/adjustments/:id/reverse | Live |
| POST | /api/payroll/adjustments/:id/void | Live |
| GET | /api/payroll/adjustments | Live |
| GET | /api/payroll/adjustments/:id | Live |
10.3 Deduction mandates — packages/payroll/backend/presentation/deduction-mandate.controller.ts
Base: /api/payroll/deduction-mandates.
| Method | Path | Status |
|---|---|---|
| POST | /api/payroll/deduction-mandates | Live |
| POST | /api/payroll/deduction-mandates/:id/activate | Live |
| POST | /api/payroll/deduction-mandates/:id/suspend | Live |
| POST | /api/payroll/deduction-mandates/:id/resume | Live |
| POST | /api/payroll/deduction-mandates/:id/cancel | Live |
| GET | /api/payroll/deduction-mandates | Live |
| GET | /api/payroll/deduction-mandates/:id | Live |
10.4 Protection / earnings / overtime (Phase 2) — payroll-p2.controller.ts
Base: /api/payroll.
| Method | Path | Status |
|---|---|---|
| POST | /api/payroll/protection-policies | Live |
| POST | /api/payroll/protection-policies/:id/activate | Live |
| GET | /api/payroll/protection-policies | Live |
| POST | /api/payroll/one-time-earnings | Live |
| POST | /api/payroll/one-time-earnings/:id/approve | Live |
| POST | /api/payroll/one-time-earnings/:id/void | Live |
| POST | /api/payroll/one-time-earnings/:id/reverse | Live |
| GET | /api/payroll/one-time-earnings | Live |
| POST | /api/payroll/overtime-policies | Live |
| POST | /api/payroll/overtime-policies/:id/activate | Live |
| GET | /api/payroll/overtime-policies | Live |
| POST | /api/payroll/overtime-entries | Live |
| POST | /api/payroll/overtime-entries/:id/approve | Live |
| GET | /api/payroll/overtime-entries | Live |
10.5 Audit + settlement + PDFs (apps/api side controllers)
| Method | Path | Auth | Source | Status |
|---|---|---|---|---|
| GET | /api/payroll/:id/audit | RequireOrgRole(admin,owner) | apps/api/src/payroll/payroll-audit.controller.ts:83 | Live |
| GET | /api/payroll/:id/settlement | RequireOrgRole(admin,owner) | apps/api/src/payroll/payroll-settlement.controller.ts:25 | Live |
| GET | /api/payroll/:id/payslips/:employeeId.pdf | Session | apps/api/src/payroll/payroll-pdf.controller.ts:51 | Live |
| GET | /api/payroll/:id/payslips.zip | Session | apps/api/src/payroll/payroll-pdf.controller.ts:100 | Live |
10.6 Tenant settings — apps/api/src/payroll/tenant-settings.controller.ts
Base: /api/payroll/tenant-settings. Class-level @RequireOrgRole('admin','owner').
| Method | Path | Auth | Status |
|---|---|---|---|
| POST | /api/payroll/tenant-settings | OrgRole(admin,owner) | Live |
| GET | /api/payroll/tenant-settings | OrgRole(admin,owner) | Live |
| GET | /api/payroll/tenant-settings/effective | OrgRole(admin,owner) | Live |
| DELETE | /api/payroll/tenant-settings | OrgRole(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').
| Method | Path | Auth | Status |
|---|---|---|---|
| POST | /api/payroll/auto-lock-policy | OrgRole(admin,owner) | Live |
| GET | /api/payroll/auto-lock-policy | OrgRole(admin,owner) | Live |
| GET | /api/payroll/auto-lock-policy/effective | OrgRole(admin,owner) | Live |
| DELETE | /api/payroll/auto-lock-policy | OrgRole(admin,owner) | Live |
10.8 Court-order remit — apps/api/src/payroll/consumers/court-order-remit.controller.ts
Base: /api/payroll.
| Method | Path | Auth | Status |
|---|---|---|---|
| GET | /api/payroll/:id/report.court-orders.json | Session | Live |
| GET | /api/payroll/:id/report.court-orders.xml | Session | Live |
| POST | /api/payroll/:id/report.court-orders/submit | Session (idempotent) | Live |
10.9 Platform-admin — apps/api/src/payroll/platform-admin.controller.ts
Base: /api/platform/payroll. All routes carry @RequirePlatformAdmin() → TOTP enforced.
| Method | Path | Status |
|---|---|---|
| POST | /api/platform/payroll/auto-lock-policy/:tenantId | Live |
| GET | /api/platform/payroll/auto-lock-policy/:tenantId | Live |
| DELETE | /api/platform/payroll/auto-lock-policy/:tenantId | Live |
| GET | /api/platform/payroll/auto-lock-policies | Live |
| GET | /api/platform/payroll/tenant-settings | Live |
| POST | /api/platform/payroll/tenant-settings/:tenantId | Live |
| GET | /api/platform/payroll/tenant-settings/:tenantId | Live |
| DELETE | /api/platform/payroll/tenant-settings/:tenantId | Live |
10.10 Payroll health — apps/api/src/payroll/payroll-health.controller.ts
| Method | Path | Auth | Status |
|---|---|---|---|
| GET | /api/health/payroll | Public | Live |
11. Equb
11.1 Domain endpoints — packages/equb/backend/presentation/equb.controller.ts
Base: /api/equb/cycles.
| Method | Path | Auth | Use case | Status |
|---|---|---|---|---|
| POST | /api/equb/cycles | RequireOrgRole(admin,owner) | CreateEqubCycle | Live |
| GET | /api/equb/cycles | RequireOrgRole(member,admin,owner) | list cycles | Live |
| GET | /api/equb/cycles/:id | RequireOrgRole(member,admin,owner) | get cycle | Live |
| POST | /api/equb/cycles/:id/members | RequireOrgRole(admin,owner) | AddEqubMember | Live |
| POST | /api/equb/cycles/:id/activate | RequireOrgRole(admin,owner) | ActivateEqubCycle (commits seed hash) | Live |
| POST | /api/equb/cycles/:id/invitations/accept | RequireOrgRole(member,admin,owner) | AcceptEqubInvitation | Live |
| POST | /api/equb/cycles/:id/invitations/decline | RequireOrgRole(member,admin,owner) | DeclineEqubInvitation | Live |
| POST | /api/equb/cycles/:id/contribute | RequireOrgRole(admin,owner) | RecordEqubContribution | Live |
| POST | /api/equb/cycles/:id/draw | RequireOrgRole(admin,owner) | RunEqubDraw (reveals seed + nonce) | Live |
| POST | /api/equb/cycles/:id/payout | RequireOrgRole(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').
| Method | Path | Status |
|---|---|---|
| POST | /api/equb/escrow/bindings | Live |
| GET | /api/equb/escrow/bindings | Live |
| POST | /api/equb/escrow/bindings/:id/reconcile | Live |
| GET | /api/equb/escrow/runs | Live |
12. Integration / webhook ingress
12.1 Bank webhook — apps/api/src/money/integration/bank-webhook.controller.ts
| Method | Path | Auth | Status |
|---|---|---|---|
| POST | /api/integration/bank-callback/:partner | Public + HMAC + nonce | Live |
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().
| Method | Path | Status |
|---|---|---|
| GET | /api/integration/webhooks | Live |
| GET | /api/integration/webhooks/:id | Live |
| POST | /api/integration/webhooks/:id/replay | Live |
13. Cross-cutting: missing / gaps / duplicates
13.1 Missing endpoints (called out in code or backlog)
| Wanted route | Reason it isn't shipped |
|---|---|
GET /api/me/ewa/requests | EwaRepository.findByEmployee not implemented (referenced as Planned in API_INVENTORY.md) |
GET /api/me/payslips | Payroll repo extension needed |
POST /api/loans/:id/restructure | Planned — no use case exists |
POST /api/loans/:id/write-off | Planned |
POST /api/loans/:id/hardship-pause | Planned |
POST /api/sanctions/ingest | CSV 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
| Concern | Routes |
|---|---|
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 formats | Three 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)
| Controller | Routes | Today | Should be |
|---|---|---|---|
| KYC | all 7 | Session | RequireOrgRole(admin,owner) on approve/reject/claim/request-more-info |
| Sanctions | screen + checks | Session | RequirePlatformAdmin (compliance ops) |
| Payroll run lifecycle | create/calc/approve/disburse/cancel/lock | Session | RequireOrgRole(admin,owner) |
| OrgRole(admin,owner) | Closed 2026-05-31 | ||
| OrgRole(admin,owner) | Closed 2026-05-31 | ||
| Payroll PDF + court-orders | GET/POST | Session | OK 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).