1. System Overview

EGU Common API is a unified .NET 8 API gateway that implements theΒ Bright Kit API specification. It provides a single, consistent interface to multiple utility billing and customer management backends using a multi-tenant adapter pattern.

Supported backends:

AdapterStatusNotes
CSTβœ… ProductionFull account operations, invoices, measurements, locations, agreements, users, features
Xellentβœ… ProductionOAuth2/Azure AD; full account operations, invoices, measurements, locations, agreements, users
ProBillβœ… ProductionFull account operations, invoices, measurements (4 resolutions), costs, locations, agreements
SonWinπŸ”¨ In ProgressOAuth2/Azure AD; account, locations, products, agreements, invoices (list), measurements & costs (4 resolutions), patch user
Zynergy⚠️ PartialSSN search only; dynamic field mapping
Gasell⚠️ MinimalSSN search only; no internal service layer

2. High-Level Architecture

                        β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
                        β”‚                    Bright App                        β”‚
                        β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                                              β”‚  HTTPS + JWT Bearer
                        β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
                        β”‚                 EGU Common API                       β”‚
                        β”‚                                                     β”‚
                        β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”‚
                        β”‚  β”‚               API Layer (WebApp)             β”‚  β”‚
                        β”‚  β”‚                                              β”‚  β”‚
                        β”‚  β”‚  AccountsApi  BankIdApi  AuthApi  EventsApi  β”‚  β”‚
                        β”‚  β”‚  SystemApi    WebViewApi                     β”‚  β”‚
                        β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚
                        β”‚                     β”‚                               β”‚
                        β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”‚
                        β”‚  β”‚           Orchestration Layer                β”‚  β”‚
                        β”‚  β”‚                                              β”‚  β”‚
                        β”‚  β”‚  AccountApiService    BankIdApiService       β”‚  β”‚
                        β”‚  β”‚  AuthenticationApiService  EventsApiService  β”‚  β”‚
                        β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚
                        β”‚             β”‚                 β”‚                     β”‚
                        β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”‚
                        β”‚  β”‚ Shared Services β”‚  β”‚   Adapter Factory       β”‚  β”‚
                        β”‚  β”‚                 β”‚  β”‚                         β”‚  β”‚
                        β”‚  β”‚ InvoiceArchive  β”‚  β”‚  Resolves adapter by    β”‚  β”‚
                        β”‚  β”‚  - EDB          β”‚  β”‚  AdapterType from       β”‚  β”‚
                        β”‚  β”‚  - IData        β”‚  β”‚  SubscriptionSetting    β”‚  β”‚
                        β”‚  β”‚  - Stralfors    β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚
                        β”‚  β”‚  - TietoEvry    β”‚             β”‚                  β”‚
                        β”‚  β”‚  - Url          β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”‚
                        β”‚  β”‚                 β”‚  β”‚      Adapter Layer      β”‚  β”‚
                        β”‚  β”‚ ZignSec BankID  β”‚  β”‚                         β”‚  β”‚
                        β”‚  β”‚  - V5 client    │◄──  CST  Xellent  Zynergy  β”‚  β”‚
                        β”‚  β”‚                 β”‚  β”‚  Gasell SonWin ProBill  β”‚  β”‚
                        β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚
                        β”‚                                   β”‚                 β”‚
                        β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                                                            β”‚
                        Backend Systems (External APIs)     β”‚
                    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
                    β”‚           β”‚          β”‚            β”‚          β”‚              β”‚
              β”Œβ”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”  β”Œβ”€β”€β”€β–Όβ”€β”€β”€β”€β”  β”Œβ”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”  β”Œβ”€β–Όβ”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”
              β”‚ CST API  β”‚  β”‚Xellent β”‚  β”‚ Zynergy β”‚  β”‚ Gasell β”‚  β”‚ SonWin β”‚  β”‚ ProBill β”‚
              β”‚          β”‚  β”‚  API   β”‚  β”‚   API   β”‚  β”‚   API  β”‚  β”‚   API  β”‚  β”‚Bright APβ”‚
              β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

3. Request Flow


1. Client sends request to: POST /subscriptions/{subscriptionId}/accounts/search 2. API endpoint (AccountsApi) receives the request └── extracts subscriptionId from route 3. AccountFactoryService is called └── ISubscriptionSettingService looks up the subscription └── reads AdapterType from configuration (e.g. AdapterType.CST) └── returns the matching IAccountAdapterService instance 4. Adapter executes the operation └── calls InternalServices for business logic └── InternalServices call ApiCallServices for HTTP communication └── maps backend response to standard Bright Kit DTO 5. Unified response returned to client

4. Layer Breakdown


Project Structure


EGU.CommonAPI.sln
β”œβ”€β”€ src/
β”‚   β”œβ”€β”€ App/
β”‚   β”‚   └── EGU.CommonAPI.WebApp          # ASP.NET Core Minimal API host
β”‚   β”‚       β”œβ”€β”€ Apis/                     # Endpoint definitions
β”‚   β”‚       └── Adapters/
β”‚   β”‚           β”œβ”€β”€ Factories/            # Adapter factory services
β”‚   β”‚           └── Services/             # Orchestration (API β†’ adapter)
β”‚   β”‚
β”‚   β”œβ”€β”€ Domain/
β”‚   β”‚   β”œβ”€β”€ EGU.CommonAPI.Domain.Contracts  # Interfaces, DTOs, enums, settings
β”‚   β”‚   β”œβ”€β”€ EGU.CommonAPI.Domain.Core       # Base classes, shared utilities
β”‚   β”‚   └── EGU.CommonAPI.Domain.InvoiceArchive  # Invoice PDF infrastructure
β”‚   β”‚
β”‚   └── Adapters/
β”‚       β”œβ”€β”€ EGU.CommonAPI.Adapters.CST
β”‚       β”œβ”€β”€ EGU.CommonAPI.Adapters.Xellent
β”‚       β”œβ”€β”€ EGU.CommonAPI.Adapters.Zynergy
β”‚       β”œβ”€β”€ EGU.CommonAPI.Adapters.Gasell
β”‚       β”œβ”€β”€ EGU.CommonAPI.Adapters.SonWin
β”‚       └── EGU.CommonAPI.Adapters.ProBill
β”‚
└── test/
    └── EGU.CommonAPI.UnitTests             # MSTest + NSubstitute

Each Adapter's Internal Structure


src/Adapters/{Adapter}/
β”œβ”€β”€ AdapterServices/          # Implements IAccountAdapterService, etc.
β”œβ”€β”€ InternalServices/
β”‚   β”œβ”€β”€ AdapterServices/      # Business logic per operation
β”‚   β”œβ”€β”€ ApiCallServices/      # HTTP calls to backend
β”‚   └── CommonServices/       # Mappers, helpers, constants
└── Models/                   # Backend-specific DTOs

Adapter Interfaces (Domain.Contracts)


InterfaceResponsibility
IAdapterServiceBaseBase β€” exposesΒ AdapterType
IAuthenticationAdapterServiceIssue and refresh JWT tokens
IAccountAdapterServiceAll account/location/invoice/measurement operations
IBankIdAdapterServiceBankID auth, sign, collect, cancel (via ZignSec)
IEventsAdapterServicePost user activity events


5. Multi-Tenancy & Subscription Routing


The system is multi-tenant via subscription configuration inΒ appsettings.json:


{
  "Subscriptions": [
    {
      "SubscriptionId": "my-tenant-id",
      "AdapterType": "CST",
      "CstSetting": { ... }
    },
    {
      "SubscriptionId": "another-tenant",
      "AdapterType": "Xellent",
      "XellentSetting": { ... }
    }
  ]
}

Routing logic:


  1. Every request path includesΒ {subscriptionId}.
  2. ISubscriptionSettingServiceΒ resolves the settings record for that ID.
  3. The factory (e.g.Β AccountFactoryService) selects the adapter whoseΒ AdapterTypeΒ matches.
  4. The selected adapter handles the request β€” callers never need to know which backend is used.

6. Public API Endpoints

All routes are prefixed withΒ /subscriptions/{subscriptionId}.

Auth

MethodPathDescriptionAuth Required
POST/auth/authorizeGet API tokenNo
POST/auth/refresh_tokenRefresh API tokenNo

Accounts

MethodPathDescription
POST/accounts/searchSearch accounts by personal ID or account ID
GET/accounts/{accountId}Get account details
GET/accounts/{accountId}/locationsGet locations/premises
GET/accounts/{accountId}/services/{serviceId}/productsGet products for a service
GET/accounts/{accountId}/services/{serviceId}/agreementsGet agreements for a service

Users & Roles

MethodPathDescription
GET/accounts/{accountId}/userrolesGet all users with roles for account
POST/accounts/{accountId}/userrolesAdd user to account
DELETE/accounts/{accountId}/userroles/{userId}Remove user from account
GET/accounts/{accountId}/users/{userId}Get user details
PATCH/accounts/{accountId}/users/{userId}Update user (email/phone)

Invoices

MethodPathDescription
GET/accounts/{accountId}/invoicesList invoices (supports date range + pagination)
GET/accounts/{accountId}/invoices/{invoiceId}/pdfDownload invoice as PDF
GET/accounts/{accountId}/invoices/{invoiceId}/htmlGet invoice HTML page URL

Measurements & Costs

MethodPathDescription
GET/accounts/{accountId}/measurements/{measurementId}Get consumption data (supportsΒ fromDate,Β toDate,Β resolution)
GET/accounts/{accountId}/cost/{costId}Get cost data (supportsΒ fromDate,Β toDate,Β resolution)

Resolution options:Β month \| day \| hour \| 15min

Features

MethodPathDescription
GET/accounts/{accountId}/featuresGet feature availability
POST/accounts/{accountId}/featuresActivate a feature
DELETE/accounts/{accountId}/featuresDeactivate a feature

BankID

MethodPathDescription
POST/bankid/authInitiate BankID authentication
POST/bankid/signInitiate BankID signing
POST/bankid/collectPoll BankID status
POST/bankid/cancelCancel BankID session

Events & System

MethodPathDescription
POST/eventsPost user activity event
GET/system/settingsGet system settings for subscription
Β 

7. Adapter Feature Matrix


βœ“Β = ImplementedΒ βœ“*Β = Implemented via shared base class ◐ = Partial/stubΒ βœ—Β = NotImplementedException


Account Operations


OperationCSTXellentZynergyGasellSonWinProBill
Searchβœ“βœ“βœ“ ΒΉβœ“ ΒΉβœ“βœ“
Get Accountβœ“βœ“βœ—βœ—βœ“βœ“
Get Productsβœ“βœ“βœ—βœ—βœ“βœ“
Get Locationsβœ“βœ“βœ—βœ—βœ“βœ“
Get Agreementsβœ“βœ“βœ—βœ—βœ“βœ“
Get Measurementsβœ“βœ“βœ—βœ—βœ“ Β²βœ“ Β²
Get Invoices (list)βœ“βœ“βœ—βœ—βœ“βœ“
Get Invoice (single)βœ“βœ—βœ—βœ—β— Β³βœ—
Invoice PDFβœ“ β€ βœ“ β€ βœ—βœ—βœ—βœ“ †
Invoice HTMLβœ“βœ“βœ—βœ—βœ—βœ—
Get Costsβœ—βœ“βœ—βœ—βœ“ Β²βœ“ Β²
Patch Userβœ“βœ“βœ—βœ—βœ“βœ“
Get Userβœ“βœ“βœ—βœ—βœ—βœ—
Delete Userβœ“βœ“βœ—βœ—βœ—βœ—
Get User Rolesβœ“βœ“βœ—βœ—βœ—βœ—
Add User Roleβœ“βœ“βœ—βœ—βœ—βœ—
Get Featuresβœ“βœ—βœ—βœ—βœ—βœ—
Activate Featuresβœ“βœ—βœ—βœ—βœ—βœ—
Delete Featuresβœ“βœ—βœ—βœ—βœ—βœ—

BankID Operations


All BankID operations route through ZignSec (configurable API version per subscription, defaults to V5).


OperationCSTXellentZynergyGasellSonWinProBill
Authβœ“*βœ“*βœ“*βœ—βœ—βœ—
Signβœ“*βœ“*βœ“*βœ—βœ—βœ—
Collectβœ“*βœ“*βœ“*βœ—βœ—βœ—
Cancelβœ“*βœ“*βœ“*βœ—βœ—βœ—

Events


OperationCSTXellentZynergyGasellSonWinProBill
Post Eventβœ“βœ“βœ—βœ—βœ—βœ—

ΒΉ Zynergy and Gasell support SSN search only Β² SonWin and ProBill support monthly, daily, hourly, and 15-minute (quarterly) resolution Β³ SonWin's single-invoice lookup returns a minimal stub (account/invoice id + empty OCR), not real backend data † Invoice PDF is served via the sharedΒ InvoiceArchiveΒ infrastructure (EDB, IData, Stralfors, TietoEvry, or Url) configured per subscription



8. Authentication Methods


AdapterMechanismDetails
CSTHTTP Basic AuthUsername/password in configuration
XellentOAuth2 (Azure AD)Client credentials flow β€”Β login.microsoftonline.com/{TenantId}/oauth2/v2.0/token
ZynergyHTTP Basic AuthUsername/password in configuration
GasellHTTP Basic AuthUsername/password in configuration
SonWinOAuth2 (Azure AD)Client credentials flow β€”Β login.microsoftonline.com/{TenantId}/oauth2/v2.0/token; backend calls use Bearer token
ProBillHTTP Basic AuthUsername/password in configuration



9. Backend API Endpoints


CST


EndpointOperation
GET /kunders?orgnummer={orgNr}Search account by org number
GET /kunders/{accountId}Get account
GET /kunders/{accountId}/externalauthenticationsGet external auth
GET /kunders/{accountId}/fakturorGet invoices
GET /kunders/{accountId}/locationsGet locations
GET /kunders/{accountId}/featuresGet features
POST /kunders/{accountId}/featuresActivate feature
PATCH /kunders/{accountId}/featuresUpdate feature
GET /{accountId}/moduletypes/{type}/subscriptions/base/{subId}Get subscription/product
GET /{accountId}/moduletypes/{type}/subscriptions/{subId}/consumptions/{id}Get measurements
GET /{accountId}/moduletypes/{type}/subscriptions/{subId}/costs/periodGet costs

Xellent


EndpointOperation
GET /accounts?{query}Search / get account
GET /accounts/{accountId}/agreementsGet agreements
GET /accounts/{accountId}/invoicesGet invoices
GET /accounts/{accountId}/locationsGet locations
GET /accounts/{accountId}/measurementsGet measurements
GET /accounts/{accountId}/productsGet products
GET /accounts/{accountId}/users/{userId}/rolesGet user roles
GET /accounts/{accountId}/users/{userId}Get user
POST /accounts/{accountId}/users/{userId}/rolesAdd user role
PATCH /accounts/{accountId}/users/{userId}Patch user
DELETE /accounts/{accountId}/users/{userId}Delete user
GET /accounts/{accountId}/costsGet costs

Gasell


EndpointOperation
GET /search?customer_soc_id={SSN}Search account by SSN

ProBillΒ 


EndpointOperation
GET brightAccounts?ssn={ssn}Search by SSN
GET brightAccounts?CustomerID={id}Search/get by customer ID
GET brightAccounts?email={email}Search by email
GET brightAgreements?customerId={id}&placementId={id}Get agreements
GET brightInvoices?customerId={id}[&DateFrom&DateTo&limit]Get invoices
GET brightLocations?customerId={id}&limit={n}Get locations
GET brightProducts?customerId={id}&placementId={id}[&DateFrom&DateTo]Get products
GET brightMeasurements?CustomerID={id}&PlacementID={id}[&DateFrom&DateTo]Monthly measurements
GET brightMeasurementDays?CustomerID={id}&PlacementID={id}[&DateFrom&DateTo]Daily measurements
GET brightMeasurementHours?CustomerID={id}&PlacementID={id}[&DateFrom&DateTo]Hourly measurements
GET brightMeasurementQuarters?CustomerID={id}&PlacementID={id}[&DateFrom&DateTo]15-min measurements
GET brightCosts?CustomerID={id}&PlacementID={id}[&DateFrom&DateTo]Monthly costs
GET brightCostDays?CustomerID={id}&PlacementID={id}[&DateFrom&DateTo]Daily costs
GET brightCostHours?CustomerID={id}&PlacementID={id}[&DateFrom&DateTo]Hourly costs
GET brightCostQuarters?CustomerID={id}&PlacementID={id}[&DateFrom&DateTo]15-min costs
PATCH v2/customersUpdate customer (email, phone)

SonWinΒ 


Dates are sent asΒ yyyy-MM-ddTHH:mm:ssΒ using the UTC components of the value, withΒ noΒ ZΒ suffix (SonWin's validator rejects aΒ Z/offset-bearing value).


EndpointOperation
GET bright/accounts/search?ssn={ssn}Search by SSN (also used for org number)
GET bright/accounts?CustomerID={id}Search/get by customer ID
GET bright/accounts/search?email={email}Search by email
GET bright/accounts/search?phone={phone}Search by phone
GET bright/agreements?customerId={id}&serviceId={id}Get agreements
GET bright/invoices?customerId={id}[&DateFrom&DateTo&limit]Get invoices
GET bright/locations?customerId={id}&limit={n}Get locations
GET bright/products?customerId={id}&serviceId={id}[&DateFrom&DateTo]Get products
GET bright/measurements?CustomerID={id}&ServiceID={id}[&DateFrom&DateTo]Monthly measurements
GET bright/measurementDays?CustomerID={id}&ServiceID={id}[&DateFrom&DateTo]Daily measurements
GET bright/measurementHours?CustomerID={id}&ServiceID={id}[&DateFrom&DateTo]Hourly measurements
GET bright/measurements15min?CustomerID={id}&ServiceID={id}[&DateFrom&DateTo]15-min measurements
GET bright/costs?CustomerID={id}&ServiceID={id}[&DateFrom&DateTo]Monthly costs
GET bright/costDays?CustomerID={id}&ServiceID={id}[&DateFrom&DateTo]Daily costs
GET bright/costHours?CustomerID={id}&ServiceID={id}[&DateFrom&DateTo]Hourly costs
GET bright/cost15min?CustomerID={id}&ServiceID={id}[&DateFrom&DateTo]15-min costs
PATCH v2/customersUpdate customer (email, phone)