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) |