Configuration
In this section, the configuration of the control plane is explained. Configuration is used based on a config.yaml file which should be placed in the apps/backend/src folder. This config.yaml file is loaded when booting the application. The values get type checked, and it gives a clear error message if there is a configuration field missing or provided incorrectly. Next to the config.yaml file, you can also set environment variables. These override the values that are listed in the config.yaml file.
Databases
By default, the development database is sqlite. We use postgres databases for production type instances of the control planes.
Authentication
Authentication for frontend services can be done via the SSO Bridge. This helps users who need to login to several components to authenticate themselves faster.
Tenants
A control-plane process serves one or more tenants. A tenant is a dataspace
participant in its own right: it has its own DID and wallet configuration
(iam), its own catalog, its own registry crawl, and its own data. There is no
special "default" tenant — the first tenant is declared exactly like the
hundredth, under initTenants, and at least one entry is required.
initTenants:
- name: Alfa
slug: alfa
iam:
type: tsg
didId: did:web:wallet.example
walletUrl: https://wallet.example
siopUrl: https://wallet.example/management/dcp/holder/token
verifyUrl: https://wallet.example/management/dcp/verifier/verify
initCatalog:
participantId: did:web:wallet.example
creator: did:web:wallet.example
publisher: did:web:wallet.example
title: Alfa
description: Alfa connector
Addressing
Every tenant is addressed by its slug, whether the process serves one tenant or a hundred:
- DSP protocol endpoints:
https://cp.example/tenants/<slug>/... - Management API:
X-Tenant-Slug: <slug>(orX-Tenant-Id) on every call.
A request that carries no tenant (no path segment and no header) resolves to
nothing: management endpoints answer 403 and DSP protocol endpoints answer
404, rather than silently picking a participant identity. The control-plane UI
has no tenant switcher yet, so it is single-tenant-only for now.
Upgrading from a pre-multi-tenancy deployment
The multi-tenancy migration backfilled all existing rows onto a tenant with slug
default. To keep that data reachable, either:
- keep
slug: defaultin your newinitTenantsentry — the bootstrap adopts the existing row and nothing moves; or - rename the tenant first with
PATCH /management/tenants/defaultand then declare the new slug.
Declaring a fresh slug without renaming leaves the old data on an unmanaged
tenant row, and the connector comes up with an empty catalog. The control plane
logs a warning at boot for any tenant row that no initTenants entry claims.
Configuration parameters
| Key | Required | Type | Description | Default |
|---|---|---|---|---|
DatabaseConfig | ||||
db | Yes | DatabaseConfig | Database configuration | |
db.type | Yes | "sqlite" | "postgres" | Type of database | |
db.database | Yes | String | Name of the database | |
db.synchronize | Boolean | Synchronize database schema | ||
db{type=sqlite} | Yes | SQLiteConfig | Database configuration | |
db{type=sqlite}.type | "sqlite" | "postgres" | Type of database | "sqlite" | |
db{type=sqlite}.database | Yes | String | Name of the database | |
db{type=sqlite}.synchronize | Boolean | Synchronize database schema | ||
db{type=postgres} | Yes | PostgresConfig | Database configuration | |
db{type=postgres}.host | Yes | String | Host of the database | |
db{type=postgres}.port | Yes | Number | Port of the database | |
db{type=postgres}.username | Yes | String | Username of the database | |
db{type=postgres}.password | Yes | String | Password of the database | |
db{type=postgres}.ssl | Unknown | SSL configuration of the database | ||
db{type=postgres}.type | "sqlite" | "postgres" | Type of database | "postgres" | |
db{type=postgres}.database | Yes | String | Name of the database | |
db{type=postgres}.synchronize | Boolean | Synchronize database schema | ||
ServerConfig | ||||
server | ServerConfig | Server configuration | ||
server.listen | String | IP address the server listens on | "0.0.0.0" | |
server.port | Number | Port the server listens on | 3000 | |
server.publicDomain | String | Public domain of the server | "localhost" | |
server.publicAddress | String | Public address of the server | "http://localhost:3000" | |
server.subPath | String | Sub path of the server | ||
AuthConfig | ||||
auth | Yes | AuthConfig | Management authentication configuration | |
auth.enabled | Boolean | Enable authentication | true | |
auth.openIdConfigurationURL | String | OpenID configuration URL | ||
auth.callbackURL | URL | Callback URL the auth service will redirect users | ||
auth.redirectURL | URL | Redirect URL to UI after login/logout | ||
auth.clientId | String | Client ID | ||
auth.clientSecret | String | Client secret (required for client_secret_post authentication) | ||
auth.tokenEndpointAuthMethod | "client_secret_post" | "private_key_jwt" | Token endpoint authentication method: client_secret_post (default) or private_key_jwt | "client_secret_post" | |
auth.privateKeyJwk | Object | Private key in JWK format for private_key_jwt authentication. Alternative to privateKeyJwkFile. | ||
auth.privateKeyJwkFile | String | Path to file containing private key JWK for private_key_jwt authentication. Alternative to privateKeyJwk. | ||
auth.permissionPath | String | JSON path to extract permissions from the token | "$.permissions[*]" | |
RegistryConfig | ||||
registry | Yes | RegistryConfig | Registry configuration | |
registry.useRegistry | Yes | Boolean | Use registry to crawl catalogs | |
registry.registryUrl | String | URL of the registry | ||
registry.registryDid | String | DID of the registry | ||
registry.registryIntervalInMilliseconds | Number | Interval in milliseconds to fetch registry | 30000 | |
InitTenantConfig | ||||
initTenants | Yes | InitTenantConfig[] | Tenants served by this process, created on startup. At least one tenant must be declared; every tenant is addressed as /tenants/:slug/... | |
IamConfig | ||||
initTenants[].iam | Yes | IamConfig | IAM/wallet configuration for this tenant | |
initTenants[].iam.type | Yes | "tsg" | "dev" | Type of IAM service | |
initTenants[].iam.didId | Yes | String | DID identifier of the IAM service | |
initTenants[].iam.protocol | String | Protocol of the IAM service | "DCP" | |
initTenants[].iam.version | String | Protocol of the IAM service | "1.0" | |
initTenants[].iam.profile | String | Profiles of the IAM service | ["vc11-bssl/jsonld"] | |
initTenants[].iam{type=dev} | Yes | DevWalletConfig | IAM/wallet configuration for this tenant | |
initTenants[].iam{type=dev}.type | "tsg" | "dev" | Type of IAM service | "dev" | |
initTenants[].iam{type=dev}.didId | Yes | String | DID identifier of the IAM service | |
initTenants[].iam{type=dev}.protocol | String | Protocol of the IAM service | "DCP" | |
initTenants[].iam{type=dev}.version | String | Protocol of the IAM service | "1.0" | |
initTenants[].iam{type=dev}.profile | String | Profiles of the IAM service | ["vc11-bssl/jsonld"] | |
initTenants[].iam{type=tsg} | Yes | TsgWalletConfig | IAM/wallet configuration for this tenant | |
initTenants[].iam{type=tsg}.walletUrl | Yes | URL | URL of the wallet management endpoint | |
initTenants[].iam{type=tsg}.siopUrl | Yes | URL | URL of the SIOP token endpoint | |
initTenants[].iam{type=tsg}.verifyUrl | Yes | URL | URL of the verification endpoint | |
initTenants[].iam{type=tsg}.typeFilter | String | Credential type filter used as default | ||
initTenants[].iam{type=tsg}.issuerFilter | String | Issuer filter used as default | ||
initTenants[].iam{type=tsg}.customFields | Array | Custom presentation definition fields | ||
initTenants[].iam{type=tsg}.walletTenantSlug | String | Tenant slug sent as X-Tenant-Slug header to the wallet (multi-tenant mode) | ||
initTenants[].iam{type=tsg}.dcpPresentationQueryMode | "presentation-definition" | "scope" | DCP presentation query mode. The default expands TSG scopes to a Presentation Definition; scope forwards the requested scopes unchanged. | "presentation-definition" | |
initTenants[].iam{type=tsg}.dcpDefaultScopes | String | DCP scopes included in every self-issued token | [] | |
DcpScopeMappingConfig | ||||
initTenants[].iam{type=tsg}.dcpScopeMappings | DcpScopeMappingConfig[] | Fixed DCP scopes activated by exact ODRL left-operand matches | ||
initTenants[].iam{type=tsg}.dcpScopeMappings[].leftOperand | Yes | String | Exact ODRL left operand that activates this DCP scope | |
initTenants[].iam{type=tsg}.dcpScopeMappings[].scope | Yes | String | DCP credential scope granted for matching policies | |
initTenants[].iam{type=tsg}.type | "tsg" | "dev" | Type of IAM service | "tsg" | |
initTenants[].iam{type=tsg}.didId | Yes | String | DID identifier of the IAM service | |
initTenants[].iam{type=tsg}.protocol | String | Protocol of the IAM service | "DCP" | |
initTenants[].iam{type=tsg}.version | String | Protocol of the IAM service | "1.0" | |
initTenants[].iam{type=tsg}.profile | String | Profiles of the IAM service | ["vc11-bssl/jsonld"] | |
InitCatalog | ||||
initTenants[].initCatalog | InitCatalog | Initial catalog configuration for this tenant | ||
initTenants[].initCatalog.participantId | Yes | String | Participant id of the catalog | |
initTenants[].initCatalog.creator | Yes | String | Creator of the catalog | |
initTenants[].initCatalog.publisher | Yes | String | Publisher of the catalog | |
initTenants[].initCatalog.title | Yes | String | Title of the catalog | |
initTenants[].initCatalog.description | Yes | String | Description of the catalog | |
initTenants[].initCatalog.datasets | String | Serialized initial datasets | ||
initTenants[].name | Yes | String | Display name of the tenant | |
initTenants[].slug | Yes | String | URL-safe slug for the tenant | |
InitTenantMemberConfig | ||||
initTenants[].members | InitTenantMemberConfig[] | Users granted access to this tenant on startup | ||
initTenants[].members[].identifier | Yes | String | Username, email or auth subject id (sub) of the user | |
initTenants[].members[].username | String | Display name to record for the user, when known | ||
initTenants[].members[].role | "owner" | "admin" | "member" | Role granted to the user within the tenant | "member" | |
PolicyConfig | ||||
defaultPolicy | PolicyConfig | Default policy configuration | ||
defaultPolicy.type | "rules" | "manual" | Definition type of the policy | "rules" | |
PolicyRuleConfig | ||||
defaultPolicy.permissions | PolicyRuleConfig[] | Permissions of the policy | ||
defaultPolicy.permissions[].action | Yes | String | Action of the rule | |
RuleConstraintConfig | ||||
defaultPolicy.permissions[].constraints | RuleConstraintConfig[] | Constraints of the rule | ||
defaultPolicy.permissions[].constraints[].type | Yes | String | Type of the constraint | |
defaultPolicy.permissions[].constraints[].value | Yes | String | Value of the constraint | |
PolicyRuleConfig | ||||
defaultPolicy.prohibitions | PolicyRuleConfig[] | Prohibitions of the policy | ||
defaultPolicy.prohibitions[].action | Yes | String | Action of the rule | |
RuleConstraintConfig | ||||
defaultPolicy.prohibitions[].constraints | RuleConstraintConfig[] | Constraints of the rule | ||
defaultPolicy.prohibitions[].constraints[].type | Yes | String | Type of the constraint | |
defaultPolicy.prohibitions[].constraints[].value | Yes | String | Value of the constraint | |
defaultPolicy.raw | Object | Raw ODRL policy | ||
RuntimeConfig | ||||
runtime | Yes | RuntimeConfig | Runtime configuration | |
runtime.controlPlaneInteractions | "automatic" | "semi-manual" | "manual" | Mode of control plane interactions | "automatic" | |
runtime.color | String | Primary UI color | "#3B8BF6" | |
runtime.lightThemeUrl | String | Light theme logo URL | ||
runtime.darkThemeUrl | String | Dark theme logo URL | ||
AuditModuleConfig | ||||
audit | AuditModuleConfig | Audit logging configuration | ||
audit.enabled | Boolean | Enable audit logging | true | |
audit.minSeverity | "debug" | "info" | "warning" | "error" | "critical" | Minimum severity level to log | "info" | |
audit.logDenied | Boolean | Always log denied access attempts regardless of action type | true | |
audit.logDelegated | Boolean | Log delegated access attempts when enabled, while still respecting action-specific success filters | true | |
audit.logMutations | Boolean | Log successful mutation actions (create, update, delete, manage) | true | |
audit.logExecute | Yes | Boolean | Log successful execute actions | |
audit.logReads | Yes | Boolean | Log successful read actions — disabled by default as reads are frequent and rarely relevant for auditing | |
audit.sensitiveResources | Unknown | Resources that are always logged regardless of action-specific success filters | ["w.key","w.credential","sso.user"] | |
AuditHandlersConfig | ||||
audit.handlers | AuditHandlersConfig | Handler configuration | ||
audit.handlers.console | Yes | Boolean | Enable console audit log handler | |
audit.handlers.database | Boolean | Enable database audit log handler | true | |
audit.handlers.otlp | Yes | Boolean | Enable OTLP audit log handler | |
OtlpAuditHandlerConfig | ||||
audit.otlp | OtlpAuditHandlerConfig | OTLP audit log handler configuration | ||
audit.otlp.url | String | OTLP HTTP logs endpoint URL | "http://localhost:4318/v1/logs" | |
audit.otlp.headers | Object | Additional HTTP headers to include in OTLP requests | ||
audit.otlp.timeoutMillis | Unknown | OTLP export timeout in milliseconds | 5000 | |
audit.otlp.concurrencyLimit | Unknown | Maximum number of concurrent OTLP export requests | 1 | |
audit.otlp.serviceName | String | Service name included on OTLP log attributes | "tsg-dsp-api" | |
audit.otlp.serviceVersion | String | Optional service version included on OTLP log attributes |