Configuration
All configuration lives in appsettings.json (or environment-specific overrides like appsettings.Development.json).
AMS Router
The embedded ADS router provides cross-platform ADS connectivity without requiring a local TwinCAT installation.
{
"AmsRouter": {
"Name": "Adsify",
"NetId": "192.168.1.78.1.1",
"TcpPort": 48898,
"LoopbackIP": "127.0.0.1",
"LoopbackPort": 48898,
"ChannelPortType": "Loopback",
"RemoteConnections": [
{
"Name": "Line1-PLC",
"Address": "192.168.10.143",
"NetId": "5.80.201.1.1.1",
"Type": "TCP_IP"
}
]
}
}| Field | Description |
|---|---|
Name | Name of this ADS router instance |
NetId | AMS Net ID of this machine (typically <your-ip>.1.1) |
TcpPort | ADS TCP port (default: 48898) |
RemoteConnections | Array of PLC routes — each needs Name, Address (IP), NetId, and Type (TCP_IP) |
PLC Targets
Maps aliases to PLC connection details. The AmsNetId must match a RemoteConnections entry.
{
"PlcTargets": {
"Line1": {
"AmsNetId": "5.80.201.1.1.1",
"Port": 851,
"DisplayName": "Assembly Line 1",
"TimeoutMs": 5000,
"SymbolBrowseTimeoutMs": 30000
}
}
}| Field | Description | Default |
|---|---|---|
AmsNetId | AMS Net ID of the PLC | (required) |
Port | ADS port (851 = PLC runtime 1) | 851 |
DisplayName | Human-readable name shown in API responses | "" |
TimeoutMs | Timeout for ADS operations | 5000 |
SymbolBrowseTimeoutMs | Timeout for symbol browsing | 30000 |
EtherCat | EtherCAT diagnostics config (see below) | null (disabled) |
EtherCAT Diagnostics (per PLC)
Optional. When present, the EtherCAT polling service reads diagnostics for this PLC.
{
"PlcTargets": {
"Line1": {
"AmsNetId": "192.168.1.136.1.1",
"Port": 851,
"EtherCat": {
"PollingIntervalMs": 1000,
"CrcErrorThreshold": 100,
"EnableNotifications": true,
"TimeoutMs": 5000
}
}
}
}| Field | Description | Default |
|---|---|---|
PollingIntervalMs | Background polling interval for diagnostics | 1000 |
CrcErrorThreshold | Per-port CRC count that triggers CrcErrorThresholdExceeded notification | 100 |
EnableNotifications | Enable SignalR events for this PLC’s EtherCAT bus | true |
TimeoutMs | ADS read timeout for EtherCAT operations | 5000 |
Feature Flags
Enable or disable entire feature slices. Disabled features return 404 and are excluded from the OpenAPI spec.
{
"Features": {
"Variables": { "Enabled": true },
"Symbols": { "Enabled": true },
"DeviceInfo": { "Enabled": true },
"Lifecycle": { "Enabled": false },
"Notifications": { "Enabled": false },
"Files": { "Enabled": false },
"EtherCatDiagnostics": { "Enabled": false },
"Mcp": { "Enabled": false }
}
}Authentication
See Authentication for full details.
{
"Authentication": {
"Authority": "https://keycloak.example.com/realms/adsify",
"Audience": "adsify-api",
"RequireHttpsMetadata": true,
"RoleClaimType": "realm_access.roles",
"ValidIssuers": ["https://keycloak.example.com/realms/adsify"]
}
}Notifications
{
"Notifications": {
"DefaultCycleTimeMs": 100,
"MaxSymbolsPerClient": 50,
"HeartbeatIntervalSeconds": 30
}
}CORS
{
"Cors": {
"AllowedOrigins": ["https://hmi.example.com"],
"AllowCredentials": true
}
}Set AllowedOrigins to an empty array to allow any origin (development only).
Rate Limiting
Per-user, per-endpoint rate limiting protects the API from excessive requests. A global per-user limiter applies to all endpoints, and additional per-endpoint policies provide finer control.
{
"RateLimiting": {
"Enabled": true,
"GlobalPerUser": { "PermitLimit": 200, "WindowSeconds": 1 },
"VariableReads": { "PermitLimit": 100, "WindowSeconds": 1 },
"VariableWrites": { "PermitLimit": 20, "WindowSeconds": 1 },
"BatchOperations": { "PermitLimit": 5, "WindowSeconds": 1 },
"SymbolBrowsing": { "PermitLimit": 10, "WindowSeconds": 1 },
"FileOperations": { "PermitLimit": 5, "WindowSeconds": 1 },
"NotificationConnections": { "PermitLimit": 5, "WindowSeconds": 60 }
}
}The partition key is composed of the authenticated user ID (or client IP as fallback) combined with the plcId. When a limit is exceeded, the API returns 429 Too Many Requests with a Retry-After header indicating when the client may retry.
Security Headers
Response headers applied to all API responses for defense-in-depth:
{
"SecurityHeaders": {
"StrictTransportSecurity": "max-age=31536000; includeSubDomains",
"XContentTypeOptions": "nosniff",
"XFrameOptions": "DENY",
"ContentSecurityPolicy": "default-src 'none'; frame-ancestors 'none'",
"CacheControl": "no-store",
"ReferrerPolicy": "no-referrer",
"PermissionsPolicy": "()"
}
}Connection Limits
Controls the maximum number of real-time connections (SSE, WebSocket) per user:
{
"ConnectionLimits": {
"MaxSseConnectionsPerUser": 5,
"MaxWebSocketConnectionsPerUser": 5,
"MaxTotalSubscriptionsPerUser": 100,
"MaxConnectionLifetimeMinutes": 480,
"IdleTimeoutMinutes": 30
}
}| Field | Description | Default |
|---|---|---|
MaxSseConnectionsPerUser | Maximum concurrent SSE connections per user | 5 |
MaxWebSocketConnectionsPerUser | Maximum concurrent WebSocket connections per user | 5 |
MaxTotalSubscriptionsPerUser | Maximum variable subscriptions across all connections | 100 |
MaxConnectionLifetimeMinutes | Maximum lifetime of a single connection | 480 |
IdleTimeoutMinutes | Disconnect after this many minutes of inactivity | 30 |
Symbol Access Control
Controls which symbols can be written per PLC. Configured under each PLC target:
{
"PlcTargets": {
"plc1": {
"SymbolAccess": {
"Mode": "denylist",
"Writable": [],
"Denied": []
}
}
}
}| Field | Description |
|---|---|
Mode | allowlist — only symbols matching Writable patterns can be written. denylist — all symbols can be written except those matching Denied patterns. |
Writable | Glob patterns for writable symbols (used in allowlist mode) |
Denied | Glob patterns for denied symbols (used in denylist mode) |
Glob patterns: * matches one path segment, ** matches any depth. For example, MAIN.* matches MAIN.nCounter but not MAIN.stMotor.bEnabled, while MAIN.** matches both.
Value Constraints
Per-symbol validation rules that are enforced on writes. Configured under each PLC target:
{
"PlcTargets": {
"plc1": {
"ValueConstraints": {
"MAIN.nSpeed": {
"Min": 0,
"Max": 100
},
"MAIN.sMode": {
"Enum": [1, 2, 3]
},
"MAIN.sName": {
"Pattern": "^[A-Z].*"
}
}
}
}
}| Constraint | Description |
|---|---|
Min / Max | Numeric range validation. The write is rejected if the value falls outside the range. |
Enum | Discrete allowed values. The write is rejected if the value is not in the list. |
Pattern | Regular expression for string validation. The write is rejected if the value does not match. |
ReadOnly | When true, all writes to this symbol are rejected. |
Writes that violate constraints return 400 with error code VALUE_OUT_OF_RANGE.