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:
| Adapter | Status | Notes |
|---|
| CST | β
Production | Full account operations, invoices, measurements, locations, agreements, users, features |
| Xellent | β
Production | OAuth2/Azure AD; full account operations, invoices, measurements, locations, agreements, users |
| ProBill | β
Production | Full account operations, invoices, measurements (4 resolutions), costs, locations, agreements |
| SonWin | π¨ In Progress | OAuth2/Azure AD; account, locations, products, agreements, invoices (list), measurements & costs (4 resolutions), patch user |
| Zynergy | β οΈ Partial | SSN search only; dynamic field mapping |
| Gasell | β οΈ Minimal | SSN 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)
| Interface | Responsibility |
|---|
IAdapterServiceBase | Base β exposesΒ AdapterType |
IAuthenticationAdapterService | Issue and refresh JWT tokens |
IAccountAdapterService | All account/location/invoice/measurement operations |
IBankIdAdapterService | BankID auth, sign, collect, cancel (via ZignSec) |
IEventsAdapterService | Post 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:
- Every request path includesΒ
{subscriptionId}. ISubscriptionSettingServiceΒ resolves the settings record for that ID.- The factory (e.g.Β
AccountFactoryService) selects the adapter whoseΒ AdapterTypeΒ matches. - 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
| Method | Path | Description | Auth Required |
|---|
POST | /auth/authorize | Get API token | No |
POST | /auth/refresh_token | Refresh API token | No |
Accounts
| Method | Path | Description |
|---|
POST | /accounts/search | Search accounts by personal ID or account ID |
GET | /accounts/{accountId} | Get account details |
GET | /accounts/{accountId}/locations | Get locations/premises |
GET | /accounts/{accountId}/services/{serviceId}/products | Get products for a service |
GET | /accounts/{accountId}/services/{serviceId}/agreements | Get agreements for a service |
Users & Roles
| Method | Path | Description |
|---|
GET | /accounts/{accountId}/userroles | Get all users with roles for account |
POST | /accounts/{accountId}/userroles | Add 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
| Method | Path | Description |
|---|
GET | /accounts/{accountId}/invoices | List invoices (supports date range + pagination) |
GET | /accounts/{accountId}/invoices/{invoiceId}/pdf | Download invoice as PDF |
GET | /accounts/{accountId}/invoices/{invoiceId}/html | Get invoice HTML page URL |
Measurements & Costs
| Method | Path | Description |
|---|
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
| Method | Path | Description |
|---|
GET | /accounts/{accountId}/features | Get feature availability |
POST | /accounts/{accountId}/features | Activate a feature |
DELETE | /accounts/{accountId}/features | Deactivate a feature |
BankID
| Method | Path | Description |
|---|
POST | /bankid/auth | Initiate BankID authentication |
POST | /bankid/sign | Initiate BankID signing |
POST | /bankid/collect | Poll BankID status |
POST | /bankid/cancel | Cancel BankID session |
Events & System
| Method | Path | Description |
|---|
POST | /events | Post user activity event |
GET | /system/settings | Get system settings for subscription |
Β
7. Adapter Feature Matrix
βΒ = ImplementedΒ β*Β = Implemented via shared base classΒ βΒ = Partial/stubΒ βΒ = NotImplementedException
Account Operations
| Operation | CST | Xellent | Zynergy | Gasell | SonWin | ProBill |
|---|
| 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).
| Operation | CST | Xellent | Zynergy | Gasell | SonWin | ProBill |
|---|
| Auth | β* | β* | β* | β | β | β |
| Sign | β* | β* | β* | β | β | β |
| Collect | β* | β* | β* | β | β | β |
| Cancel | β* | β* | β* | β | β | β |
Events
| Operation | CST | Xellent | Zynergy | Gasell | SonWin | ProBill |
|---|
| 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
9. Backend API Endpoints
CST
| Endpoint | Operation |
|---|
GET /kunders?orgnummer={orgNr} | Search account by org number |
GET /kunders/{accountId} | Get account |
GET /kunders/{accountId}/externalauthentications | Get external auth |
GET /kunders/{accountId}/fakturor | Get invoices |
GET /kunders/{accountId}/locations | Get locations |
GET /kunders/{accountId}/features | Get features |
POST /kunders/{accountId}/features | Activate feature |
PATCH /kunders/{accountId}/features | Update 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/period | Get costs |
Xellent
| Endpoint | Operation |
|---|
GET /accounts?{query} | Search / get account |
GET /accounts/{accountId}/agreements | Get agreements |
GET /accounts/{accountId}/invoices | Get invoices |
GET /accounts/{accountId}/locations | Get locations |
GET /accounts/{accountId}/measurements | Get measurements |
GET /accounts/{accountId}/products | Get products |
GET /accounts/{accountId}/users/{userId}/roles | Get user roles |
GET /accounts/{accountId}/users/{userId} | Get user |
POST /accounts/{accountId}/users/{userId}/roles | Add user role |
PATCH /accounts/{accountId}/users/{userId} | Patch user |
DELETE /accounts/{accountId}/users/{userId} | Delete user |
GET /accounts/{accountId}/costs | Get costs |
Gasell
| Endpoint | Operation |
|---|
GET /search?customer_soc_id={SSN} | Search account by SSN |
ProBillΒ
| Endpoint | Operation |
|---|
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/customers | Update 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).
| Endpoint | Operation |
|---|
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/customers | Update customer (email, phone) |