Finance Portal
TigerBeetle ledger, AR aging, statutory reports, and report builder.
The Compliance Portal (apps/compliance-portal, oi-sys-compliance) is a dedicated Next.js application for the Compliance / Regulatory team. It provides read-only access to producer licensing, state filing workflows, compliance analytics, and the full audit log — all scoped behind the compliance_officer JWT role.
| Environment | URL |
|---|---|
| Production | https://compliance.openinsure.dev |
| Local dev | http://localhost:3007 |
Use the standard OpenInsure sign-in flow. The Compliance Portal sets the oi_compliance_token cookie, verified against COMPLIANCE_JWT_SECRET.
GET https://auth-dev.openinsure.dev/api/auth/sign-in/microsoft ?callbackURL=https://compliance.openinsure.dev/api/auth/callback| JWT role | SpiceDB org relation | Description |
|---|---|---|
compliance_officer | compliance | Standard compliance team access |
superadmin | — | Full access |
system | — | Machine-to-machine (M2M) |
The compliance SpiceDB org relation grants read permissions across org resources but explicitly does not grant manage_fiduciary — compliance officers cannot alter financial records.
The compliance_officer role is enforced in apps/compliance-portal/lib/auth.ts. Unauthenticated requests redirect to /login.
| Section | Page | Route |
|---|---|---|
| Overview | Dashboard | / |
| Licensing | Producers | /producers/licensing |
| Analytics | Compliance | /analytics/compliance |
| Analytics | Claims | /analytics/claims |
| Regulatory | Filings | /compliance/filings |
| Regulatory | Rules (view) | /rules |
| Audit | Activity Log | /audit |
/)Three-pane layout (flex h-full overflow-hidden):
Left pane (w-64) — KPI summary cards:
KPICard with status='warn' if > 0KPICard with status='warn' if > 0KPICard neutral/producers/licensing, /compliance/filings, /auditCenter pane (flex-1) — Compliance Calendar:
Right pane (w-72) — Urgent Items:
API calls (parallel via Promise.all):
GET /v1/compliance/:orgId/summary — left pane KPIsGET /v1/compliance/:orgId/deadlines — center + right pane data/producers/licensing)Table of all producers with their license status, expiry dates, and appointment counts by state. Links through to individual producer detail pages at /producers/:id.
Fetches from:
GET /v1/producers — paginated producer listGET /v1/producers/:id/licenses — license records per producerGET /v1/producers/:id/licenses/expiring — upcoming expirations/analytics/compliance)Filing status breakdown by state and line of business. Data from GET /v1/analytics/compliance. Status categories: overdue → due_soon → on_track → filed.
/analytics/claims)Aggregate claims metrics (frequency, severity, reserve development) used in regulatory reporting. Data from GET /v1/analytics/claims.
/compliance/filings)Three-pane layout with URL-driven status filter:
Left pane (w-56) — Filter sidebar:
?status=overdue, ?status=due_soon, etc.) — no JavaScript, pure URL paramssearchParams.status — highlighted with bg-primary/10Center pane (flex-1) — FilingsTable client component:
refetchInterval: 60_000) with initialData from server renderDataTable (TanStack Table) with columns: Filing Name, Due Date, Days Remaining, Status badge, Penalty/Day, ActionDataTable — the component does not filter internally)Dialog): filedAt date input, confirmation number, notes — validates with Zod schema, POST /api/v1/compliance/filings, invalidates query on successRight pane (w-64) — Server-computed summary stats:
penaltyPerDay for overdue filingsdaysRemaining ≤ 7|daysRemaining| among overdue filingsSource: apps/compliance-portal/app/(dashboard)/compliance/filings/filings-table.tsx (client island)
/rules)Read-only view of the underwriting rules engine. Compliance officers can inspect rules for regulatory review but cannot create, edit, or delete rules — those operations require an Admin login.
/audit)Immutable audit trail. Fetches from GET /v1/activities. Filterable by user, action type, and date range. Exportable to CSV for regulatory submission.
The Compliance Portal proxies API requests through apps/compliance-portal/app/api/[...path]/route.ts, gated by lib/proxy-allowlist.ts:
GET /v1/analytics/complianceGET /v1/analytics/claimsGET /v1/producersGET /v1/producers/:idGET /v1/producers/:id/licensesGET /v1/producers/:id/licenses/expiringGET /v1/producers/:id/appointmentsGET /v1/activitiesGET /v1/rulesGET /v1/complianceGET /v1/compliance/:orgId/summaryGET /v1/compliance/:orgId/deadlinesGET /v1/compliance/filingsPOST /v1/compliance/filingsAny request not in the allowlist returns 403 Forbidden.
The compliance org relation in SpiceDB (packages/auth/spice/schema.zed) grants:
relation compliance: userPermissions derived:
view_org, view_submissions, view_policies, view_claims, view_producers, view_audit_log, manage_filingsmanage_org, manage_rate_tables, manage_programs, manage_fiduciary, bind_policyThis ensures that a compliance_officer with elevated API access cannot accidentally trigger write operations on financial or underwriting resources.
| Variable | Description |
|---|---|
COMPLIANCE_JWT_SECRET | JWT signing secret — set via wrangler secret put |
API_URL | API worker base URL (e.g., https://api.openinsure.dev) |
NEXT_PUBLIC_APP_NAME | "Compliance Portal" — set in wrangler.toml [vars] |
# Set the JWT secret (production)wrangler secret put COMPLIANCE_JWT_SECRET --name oi-sys-compliance
# Local dev (.env.local)COMPLIANCE_JWT_SECRET=9d79c38aa7d57bd24a1afe213848b2b935519afb08a3923ac112aed71fd5bc21API_URL=http://localhost:8787DEMO_MODE=trueThe Compliance Portal deploys as an OpenNext Cloudflare Worker (oi-sys-compliance):
# Build + deploycd apps/compliance-portalnpx opennextjs-cloudflare deploy -- --keep-vars
# Or via pnpm filterpnpm --filter @openinsure/compliance-portal deployCI deploys automatically from master when apps/compliance-portal/ or shared packages change. See CI/CD for the full pipeline.
OFAC / international sanctions screening is enforced at the API layer via packages/compliance/src/sanctions.ts. Applied at three enforcement points:
POST /v1/submissions/:id/bind) — screens insured name before creating a policyPOST /v1/producers) — screens legal name before inserting the recordPOST /v1/disbursements) — screens payee name with mandatory DB audit logRequest └─► KV cache check (sanctions:v2:{name}:{country}) ├─ HIT → return cached decision (block → 403/451, review/pass → continue) └─ MISS → live API call → cache result → emit event if block/reviewTwo backends:
| Backend | When active | Coverage |
|---|---|---|
| OpenSanctions hosted API (production) | OPENSANCTIONS_API_URL = https://api.opensanctions.org | OFAC SDN + EU FSF + UN 1267 + UK FCDO + SECO + 40 other lists. Nomenklatura cross-dataset deduplication — the same entity across multiple lists returns one canonical result with a confidence score. |
| Static SDN list (dev / CI) | OPENSANCTIONS_API_URL absent | 8 representative entries, no network call. All 35 unit tests pass without a live endpoint. |
| Score | Decision | Effect |
|---|---|---|
| ≥ 0.85 | block | HTTP 403/451, compliance.sanctions_hit queue event, KV cached for 10 years |
| 0.60–0.84 | review | Request allowed, compliance.sanctions_review queue event, KV cached 24h |
| < 0.60 | pass | Request continues, no event |
Sanctions matching runs through the OpenSanctions hosted API at api.opensanctions.org. This is the same yente-based matching engine used by OpenSanctions — the API call format and response schema are identical to self-hosted yente.
POST https://api.opensanctions.org/match/sanctionsAuthorization: ApiKey <OPENSANCTIONS_API_KEY>Company / Person / Vessel / Aircraft)responses.main.results[] — each result has id, caption, score, match, datasets, propertiesA self-hosted yente instance (openinsure-opensanctions) is deployed on Fly.io with Elasticsearch 8 for future migration to the OpenSanctions bulk delivery plan:
iad (US East), performance-2x, 4 GB RAM, 10 GB persistent volumeinfra/opensanctions/OPENSANCTIONS_DELIVERY_TOKEN (bulk license)fly secrets set OPENSANCTIONS_DELIVERY_TOKEN=<token> -a openinsure-opensanctions && cd infra/opensanctions && fly deploy# Already set on oi-sys-api Workerwrangler secret put OPENSANCTIONS_API_URL # https://api.opensanctions.orgwrangler secret put OPENSANCTIONS_API_KEY # from opensanctions.org/account
# Local dev — leave OPENSANCTIONS_API_URL unset# Static SDN fallback activates automatically; all 35 unit tests pass without a live endpointFinance Portal
TigerBeetle ledger, AR aging, statutory reports, and report builder.
Authentication
JWT roles, portal cookies, and SpiceDB authorization.