API integration

Numeralens never talks to a backend directly from a component. Every read goes through one interface:

UI → hooks (TanStack Query) → service facade → DataAdapter → your data

That means connecting your backend touches one layer, not 37 pages.

Option 1 — point at a REST API (no code)

Settings → Data source. Choose "REST API", enter your base URL, pick an auth mode, press Test connection. The test calls GET {baseUrl}/reference-data and reports exactly what it found or what failed. Settings persist in the browser and invalidate all cached queries when saved.

For a permanent default, set in .env.local:

NEXT_PUBLIC_API_URL=https://api.yourcompany.com/v1
NEXT_PUBLIC_ENABLE_DEMO_MODE=false
NEXT_PUBLIC_AUTH_MODE=bearer

Option 2 — adapt the response shapes

If your endpoints exist but return different field names, edit src/services/mappers.ts. It ships as identity functions and is deliberately the only place where "their shape" becomes "our shape":

export const mapDashboard = (raw: unknown): DashboardSummary => {
  const d = raw as Record<string, unknown>;
  return {
    kpis: (d.metrics as RawKpi[]).map(mapKpi),
    revenueTrend: (d.revenue_series as RawPoint[]).map(mapPoint),
    // …
  };
};

Option 3 — write an adapter

For GraphQL, gRPC, a database client or a legacy SOAP gateway, implement DataAdapter (src/services/adapters/types.ts) and return it from getAdapter() in src/services/index.ts. 23 methods, all Promise-returning, all receiving normalised FilterParams.

export function createGraphqlAdapter(client: Client): DataAdapter {
  return {
    getReferenceData: () => client.request(REFERENCE_QUERY).then(mapReferenceData),
    getDashboard: (filters) => client.request(DASHBOARD_QUERY, toVars(filters)).then(mapDashboard),
    // …
  };
}

Endpoints the HTTP adapter calls

All relative to the base URL, all GET.

Path Returns
/reference-data Companies, branches, departments, accounts, products, customers, vendors, currencies, categories
/dashboard DashboardSummary
/revenue, /expenses, /profitability, /cash-flow Analytics metrics
/reports/profit-loss, /reports/balance-sheet, /reports/cash-flow-statement FinancialReport
/general-ledger Paginated<LedgerEntry>
/trial-balance TrialBalance
/accounts-receivable, /accounts-payable Ageing reports
/customers, /customers/:id CustomersReport, CustomerDetail
/vendors, /vendors/:id VendorsReport, VendorDetail
/transactions Paginated<Transaction>
/transactions/:id { transaction, related } — related is the complete journal entry, including the requested line, so debits and credits balance
/budget BudgetReport
/inventory InventoryReport
/search?q= SearchResult[]
/notifications Notification[]

The demo API routes under src/app/api/ implement all of these against the mock engine — read them as a reference implementation, or as fixtures for your own backend's contract tests.

Query parameters

Filters are serialised by buildQuery. A typical request:

GET /revenue?from=2026-01-01&to=2026-12-31&comparison=previous_year
            &companyId=co-1&departmentId=dp-sales&granularity=monthly

Table endpoints add page, pageSize, search, sortBy, sortDir. Transactions add type, status, minAmount, maxAmount, reference.

Response envelope

Both of these are accepted:

{ "data": { … } }
{ … }

Errors

Non-2xx responses become a typed ApiError with a code:

Code Trigger UI behaviour
unauthorized 401 / 403 "You don't have access to this data"
not_found 404 "Not found"
timeout Request exceeded the timeout Retry plus a link to data-source settings
network Fetch threw "Can't reach the data source" plus settings link
server Other non-2xx Generic failure with retry
invalid_data Payload failed validation "Returned something unexpected"

Each widget catches its own error, so one failing endpoint does not blank the page.

Authentication

  • bearer — the adapter awaits getAuthToken() from src/services/auth-token.ts and sends Authorization: Bearer …. Replace the body with your provider's accessor.
  • cookie — requests go out with credentials: "include". Your API must send permissive CORS headers plus Access-Control-Allow-Credentials: true.

Checklist

  1. GET /reference-data returns accounts, customers, vendors, departments.
  2. Amounts are numbers, not strings; dates are ISO yyyy-MM-dd.
  3. Percentages are whole numbers (14.8 = 14.8%).
  4. Paginated endpoints return { items, total, page, pageSize }.
  5. CORS allows your dashboard origin.
  6. Test connection in Settings → Data source succeeds.