Part of the Payroll Engine open-source payroll automation framework. Full documentation at payrollengine.org.
The Backend is the ASP.NET Core REST API server at the core of the Payroll Engine. It exposes the full payroll object model over HTTP, compiles and executes C# payroll scripts via Roslyn, processes payrun jobs asynchronously, and persists all results in a SQL Server database.
| Requirement | Minimum |
|---|---|
| .NET | 10.0 |
| SQL Server | 2019 (or Azure SQL) |
| Database collation | SQL_Latin1_General_CP1_CS_AS |
| Database isolation | READ_COMMITTED_SNAPSHOT ON |
Run the provided script to create the database schema:
Commands\Db.ModelCreate.cmdOr use Docker Compose (recommended for first-time setup): see Container Setup.
Verify database settings after creation:
SELECT name, collation_name, is_read_committed_snapshot_on AS rcsi
FROM sys.databases WHERE name = 'PayrollEngine';Set the connection string via environment variable (recommended):
# Windows
set PayrollDatabaseConnection=Server=localhost;Database=PayrollEngine;Integrated Security=SSPI;
# Docker / Linux
export PayrollDatabaseConnection="Server=localhost;Database=PayrollEngine;User Id=sa;Password=...;TrustServerCertificate=True;"Or add it to appsettings.json (for local development only):
{
"ConnectionStrings": {
"PayrollDatabaseConnection": "Server=localhost;Database=PayrollEngine;Integrated Security=SSPI;"
}
}Store sensitive configuration in User Secrets for local development.
# Run from the binary output folder
dotnet PayrollEngine.Backend.Server.dll --urls=https://localhost:44354/
# Run from the project folder (Backend.Server/)
dotnet run --urls=https://localhost:44354/
# Visual Studio
Open PayrollEngine.Backend.sln and start with the debuggerThe API is accessible at the configured URL. When EnableSwagger is set to true, the Swagger UI is available at /swagger.
The Payroll Engine API supports the Open API specification and describes the interface to the Swagger tool. The document REST Service Endpoints document describes the available endpoints.
Payroll Engine swagger.json
| Endpoint | Method | Description |
|---|---|---|
.../payruns/jobs |
POST | Start a payrun job (asynchronous, returns HTTP 202 with location header) |
.../payruns/jobs/preview |
POST | Synchronous single-employee payrun preview without persisting results |
.../employees/bulk |
POST | Bulk employee creation via SqlBulkCopy for high-throughput import |
Starting with the 1.0 release, no version header is required in the HTTP request. Future major versions will require the HTTP header X-Version with the version number.
The Payroll REST API supports HTTP requests in JSON format.
PayrunJobInvocation has been refactored from id-based to name/identifier-based references:
- Removed:
PayrunIdandUserIdproperties from API and domain models - Required:
PayrunNameandUserIdentifierare now the required fields
Clients must update payrun job invocations to use name-based references instead of numeric ids.
Payrun jobs are now processed asynchronously by default via a background queue:
- Job is pre-created and persisted before enqueue
- Returns HTTP 202 with a location header for status polling
- Webhook notification on job completion or abort
- Bounded channel with backpressure (capacity: 100)
The server configuration file appsettings.json contains the following settings:
| Setting | Description | Type | Default |
|---|---|---|---|
StartupCulture |
Culture of the backend process (RFC 4646) | string | System culture |
HttpsRedirection |
Enable HTTPS redirection | bool | false |
LogHttpRequests |
Log HTTP requests to log file | bool | false |
Serilog |
Logger settings | Serilog | file and console log |
| Setting | Description | Type | Default |
|---|---|---|---|
EnableSwagger |
Enable Swagger UI and JSON endpoint 1) | bool | false |
DarkTheme |
Use Swagger dark theme | bool | false |
VisibleControllers |
Visible API controllers 2) 3) | string[] | all |
HiddenControllers |
Hidden API controllers 2) 3) | string[] | none |
XmlCommentFileNames |
XML documentation files for Swagger | string[] | none |
| Setting | Description | Type | Default |
|---|---|---|---|
Authentication:Mode |
Authentication mode: None, ApiKey, OAuth |
enum | None |
Authentication:ApiKey |
Static API key (only for mode ApiKey) |
string | none |
Authentication:OAuth:Authority |
Token authority / issuer URL | string | none |
Authentication:OAuth:Audience |
Expected audience claim | string | none |
Authentication:OAuth:RequireHttpsMetadata |
Require HTTPS metadata discovery | bool | true |
Authentication:OAuth:ClientSecret |
OAuth client secret for Swagger UI token flow 9) | string | none |
| Setting | Description | Type | Default |
|---|---|---|---|
AuditTrail:Script |
Audit trail for scripts | bool | false |
AuditTrail:Lookup |
Audit trail for lookups and lookup values 4) | bool | false |
AuditTrail:Input |
Audit trail for cases, case fields and case relations | bool | false |
AuditTrail:Payrun |
Audit trail for collectors and wage types | bool | false |
AuditTrail:Report |
Audit trail for reports, templates and parameters | bool | false |
| Setting | Description | Type | Default |
|---|---|---|---|
InitializeScriptCompiler |
Initialize Roslyn at startup to reduce first execution time | bool | false |
DumpCompilerSources |
Store compiler source files to disk 5) | bool | false |
ScriptSafetyAnalysis |
Static safety analysis of scripts during compilation 8) | bool | false |
AssemblyCacheTimeout |
Timeout for cached script assemblies | timespan | 30 minutes |
| Setting | Description | Type | Default |
|---|---|---|---|
DbCollation |
Expected database collation, verified on startup 10) | string | SQL_Latin1_General_CP1_CS_AS |
DbCommandTimeout |
Database command timeout | timespan | 2 minutes |
DbTransactionTimeout |
Database transaction timeout | timespan | 10 minutes |
WebhookTimeout |
Webhook HTTP request timeout | timespan | 1 minute |
FunctionLogTimeout |
Timeout threshold for logging long function executions 11) | timespan | off |
| Setting | Description | Type | Default |
|---|---|---|---|
MaxParallelEmployees |
Parallelism for employee processing 6) | string | 0 (sequential) |
MaxRetroPayrunPeriods |
Maximum retro payrun periods per employee 7) | int | 0 (unlimited) |
LogEmployeeTiming |
Log employee processing timing | bool | false |
| Setting | Description | Type | Default |
|---|---|---|---|
Cors:AllowedOrigins |
Allowed origins (empty = CORS inactive) | string[] | [] (inactive) |
Cors:AllowedMethods |
Allowed HTTP methods | string[] | GET, POST, PUT, DELETE, PATCH, OPTIONS |
Cors:AllowedHeaders |
Allowed request headers | string[] | Content-Type, Authorization, Api-Key, Auth-Tenant |
Cors:AllowCredentials |
Include credentials in cross-origin requests | bool | false |
Cors:PreflightMaxAgeSeconds |
Preflight response cache duration | int | 600 |
| Setting | Description | Type | Default |
|---|---|---|---|
RateLimiting:Global:PermitLimit |
Max requests per window (0 = inactive) | int | 0 (inactive) |
RateLimiting:Global:WindowSeconds |
Time window in seconds | int | 60 |
RateLimiting:PayrunJobStart:PermitLimit |
Max payrun job starts per window | int | 0 (inactive) |
RateLimiting:PayrunJobStart:WindowSeconds |
Time window in seconds | int | 60 |
1) Should be disabled in production to prevent exposing the full API surface and OAuth client credentials.
2) Wildcard support for * and ?.
3) HiddenControllers cannot be combined with VisibleControllers.
4) Audit trail is not supported on bulk lookup values import.
5) Stores compilation scripts to the ScriptDump folder. Analysis feature only.
6) Values: 0 or off = sequential, half = ProcessorCount/2, max = ProcessorCount, -1 = automatic, 1–N = explicit thread count.
7) Safety guard against runaway retro calculations. 0 = no limit.
8) When enabled, scripts are checked for banned API usage (System.IO, System.Net, System.Diagnostics, System.Reflection, etc.) before the assembly is emitted. Adds ~300 ms per compilation. Enable to harden script execution against unauthorized BCL access.
9) Used exclusively by Swagger UI to obtain tokens for the interactive API explorer. Not required for production API authentication.
10) Verified on startup before the schema version check. Prevents silent data integrity issues from mismatched collation.
11) When set, functions exceeding this duration are logged at Warning level. Useful for identifying slow scripts or database operations.
It is recommended that you save the application settings within your local User Secrets.
Minimal development configuration:
{
"StartupCulture": "de-CH",
"EnableSwagger": true,
"Authentication": {
"Mode": "None"
}
}Production configuration with OAuth, CORS and rate limiting:
{
"EnableSwagger": false,
"HttpsRedirection": true,
"Authentication": {
"Mode": "OAuth",
"OAuth": {
"Authority": "https://login.example.com/realms/payroll",
"Audience": "payroll-api",
"RequireHttpsMetadata": true
}
},
"AuditTrail": {
"Script": true,
"Input": true,
"Payrun": true
},
"DbCommandTimeout": "00:03:00",
"DbTransactionTimeout": "00:15:00",
"MaxParallelEmployees": "half",
"MaxRetroPayrunPeriods": 24,
"Cors": {
"AllowedOrigins": [ "https://app.example.com" ],
"AllowCredentials": true
},
"RateLimiting": {
"Global": {
"PermitLimit": 200,
"WindowSeconds": 60
},
"PayrunJobStart": {
"PermitLimit": 5,
"WindowSeconds": 60
}
}
}The backend database connection string is determined by the following priority:
- Environment variable
PayrollDatabaseConnection. - Program configuration file
appsettings.json.
In Docker, the connection string uses the ASP.NET Core hierarchical key format:
ConnectionStrings__PayrollDatabaseConnection. This is the standard mechanism for overriding nested configuration values via environment variables.
The backend server stores its logs in the application folder logs.
The API supports three authentication modes, configured via Authentication:Mode in appsettings.json:
None — No authentication. All requests are accepted. Development/internal use only.
ApiKey — Static API key. The client must send the key in the Api-Key HTTP header. The key is resolved in the following order:
- Environment variable
PayrollApiKey - Configuration value
Authentication:ApiKeyinappsettings.json
OAuth — OAuth 2.0 / JWT Bearer token. Requires Authority and Audience configuration. The client must send a valid Bearer token in the Authorization header. Authority and audience are validated at startup to prevent token confusion.
When authentication is active and Swagger is enabled, Swagger UI requires the corresponding credentials.
The business logic defined by the business in C# is compiled into binary files (assemblies) by the backend using Roslyn. This procedure has a positive effect on the runtime performance, so that even extensive calculations can be performed sufficiently quickly. At runtime, the backend keeps the assemblies in a cache. To optimize memory usage, unused assemblies are periodically deleted (application setting AssemblyCacheTimeout).
You can use the InitializeScriptCompiler application setting to start the Roslyn engine when the application starts, thereby eliminating the runtime delay.
To perform a more in-depth analysis, set the DumpCompilerSources application setting to force the C# script compiler to save the source scripts of the compilation as disk files. These files are stored in the ScriptDump folder within the application folder, ordered by function type and dump date.
When ScriptSafetyAnalysis is enabled, user scripts are statically checked for banned API usage before the assembly is emitted. See footnote 8 in the application settings for details.
| Name | Type | Description |
|---|---|---|
PayrollEngine.Domain.Model |
Library | Domain objects and repositories |
PayrollEngine.Domain.Scripting |
Library | Scripting services |
PayrollEngine.Domain.Application |
Library | Application service |
PayrollEngine.Persistence |
Library | Repository implementations |
PayrollEngine.Persistence.SqlServer |
Library | SQL Server implementation |
PayrollEngine.Api.Model |
Library | REST API data transfer objects |
PayrollEngine.Api.Core |
Library | REST core services (query, filter, serialization) |
PayrollEngine.Api.Map |
Library | Mapping between REST and domain objects |
PayrollEngine.Api.Controller |
Library | REST controllers (business logic per resource) |
PayrollEngine.Backend.Controller |
Library | ASP.NET routing controllers (HTTP routing and model binding) |
PayrollEngine.Backend.Server |
Exe | Web application server with REST API |
The recommended way to run the Backend is as part of the full Docker Compose stack. See the Container Setup documentation.
⚠️ The examples below use sample credentials for local development only. Never use these values in production.
Pull and run the pre-built image:
docker run -p 5001:8080 \
-e ASPNETCORE_URLS="http://+:8080" \
-e ConnectionStrings__PayrollDatabaseConnection="Server=host.docker.internal;Database=PayrollEngine;User Id=sa;Password=PayrollStrongPass789;TrustServerCertificate=True;" \
ghcr.io/payroll-engine/payrollengine.backend:latestVerify API is accessible at http://localhost:5001
docker build -t payroll-backend .
docker run -p 5001:8080 \
-e ASPNETCORE_URLS="http://+:8080" \
-e ConnectionStrings__PayrollDatabaseConnection="Server=host.docker.internal;Database=PayrollEngine;User Id=sa;Password=PayrollStrongPass789;TrustServerCertificate=True;" \
payroll-backendHelper scripts in the Commands folder:
| Command | Description |
|---|---|
Db.ExportModelCreate.cmd |
Export the live database schema to Database\Create-Model.sql 1) |
Db.ExportModelDrop.cmd |
Export the live database drop script to Database\Drop-Model.sql 1) |
Db.ModelCreate.cmd |
Execute Create-Model.sql against the database |
Db.ModelDrop.cmd |
Execute Drop-Model.sql against the database |
Db.ModelUpdate.cmd |
Execute drop then create (full schema reset) |
Db.Publish.cmd |
Create the combined setup script SetupModel.sql |
Db.VersionCreate.cmd |
Insert the version record via VersionCreate.sql |
DotNet.Swagger.Install.cmd |
Install the Swashbuckle CLI tool (dotnet-swagger) |
Swagger.Build.cmd |
Generate docs/swagger.json from the running backend |
SqlScripter.cmd |
Script the live database to a SQL file via mssql-scripter |
SqlFormatter.cmd |
Format a raw SQL export via PoorMansTSqlFormatter |
Domian.Model.Unit.Tests.cmd |
Run the domain model unit tests |
1) After export, manually remove the CREATE DATABASE / ALTER DATABASE block at the top and the SET READ_WRITE statement at the bottom before committing the file.
- OData — query syntax reference
- Database — schema, scripts, and maintenance
- Developer Guidelines — adding new objects and fields