~/bpabatao
← back to index

Multi-Tenant Core API

The middleware every utility-customer portal in the fleet stands on - two generations of it, and the migration from one to the other.

Role
Primary author of both generations, architecture and security owner
Period
2023 - present
Ownership
58% of v1 · 76% of v2
Stack
Fastify 5 · TypeScript (strict) · Zod · MongoDB · AWS SDK v3 · ECS Fargate · Vitest
deltamvunepdelcoalexrenewcarmelaruba7 portalsv1 → v2 on paritycore api v16 of 7 launchedexpresspatched + auditedcore api v27th + 1st migrationaccount-route authzper-tenant configoauth 2.0oracle ccs
7 tenant portals reach Oracle CCS over OAuth 2.0 through two generations of the core API: v1 serves six, v2 serves the seventh and runs beside v1 for the first migration, and a tenant moves when its parity checks pass

Problem

A fleet of utility companies each needed a customer portal talking to Oracle Utilities CCS, the billing system of record. The v1 server I inherited when I joined in 2023 - an Express codebase that predates me, 58% mine by commit today - had authorization gaps and no clean way to vary behavior per tenant. Every new client meant forked code and re-audited security.

The v2 rebuild, started in early 2026, had one mandate: a single API where a new tenant is configuration, not code, and where authorization failure is hard to ship by accident. Six of the seven launched tenants run production on v1 today; the seventh launched directly on v2 in June 2026. The first v1-to-v2 migration runs v2 beside its v1 service, certified, with the cutover held. The fleet moves one tenant at a time.

Constraints

Utility customers pay bills through this thing - it has to be up and it has to be right. Oracle CCS is reached over OAuth 2.0 with per-tenant credentials. The team is small, so the architecture had to make the secure path the easy path, and the migration cannot pause billing.

Decisions

Architecture

Fastify 5 on Node 22, ESM-only, TypeScript strict with no any types. Zod validates every input at the boundary; responses use one fixed envelope so clients never parse ad-hoc shapes.

On v2, admin reads and impersonated sessions are audited as well as writes, attributed to the acting admin, with field-level capture of what a write actually changed and, on the v2 capture path, deep redaction of tokens, passwords and identifiers. Admin writes are kept for a year, admin reads for ninety days.

Where the billing system of record and the payment provider disagree, a reconciler surfaces the drift first; repairs run guarded, in shadow before they write, and the account-linking path guards against duplicate registrations and signup conflicts. Enforcing the ten-digit account number on one tenant's sign-in forms targeted about 215 CCS errors a day that partial or oversized numbers had been generating.

Typecheck, lint, tests and a Snyk gate run in CI; Bitbucket Pipelines builds to ECR and deploys to ECS Fargate behind a shared ALB.

Outcome

view resume ↗ (opens in new tab)email me