Configuration
Set CONFIG_PATH to an absolute configuration file path. Without it, the file
loader searches for config.yaml or another supported config file format in
the process's current directory and its parents. The application validates the
loaded configuration at startup.
Environment variables override file settings. Use the uppercase TSG__ prefix,
double underscores between path segments and snake case for camel-case keys.
For example, TSG__SERVER__PUBLIC_ADDRESS overrides server.publicAddress.
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.
Managed organizations
A Control Plane process serves managed participants. Each has its own DID, Wallet binding, catalog and data-plane registrations. Tenant Management creates or explicitly imports the organization, provisions its runtime identity and keeps authorization snapshots fresh. The process starts with no tenants and has no local tenant creation, membership or invitation writer.
Operator-controlled tenancy.profiles hold the IAM, data-plane and initial
catalog options used during provisioning. Published Wallet URLs, participant
DIDs and workload credentials come from the manager. Use the
managed HTTP sample or
deployment and import guide for the full
service registration and browser-client setup.
Addressing
DSP endpoints use https://cp.example/tenants/<local-slug>/....
Management requests select the local tenant through X-Tenant-Slug or
X-Tenant-Id; OAuth tenant_id identifies the canonical organization. A
selector grants no access. The UI lists accessible organizations and obtains a
separate service authorization when the user switches organization.
Existing participant data
Old seeded configuration no longer starts. Remove the unsupported seed fields, register the manager and explicitly import the existing local IDs, URL slugs, DIDs, keys, resource ownership and service grants. Keep the published URLs and backed-up databases. A matching slug is not proof of identity or ownership. The import procedure preserves existing participant data without creating replacement identities.
DCP presentation query mode
Incoming DSP requests are verified with the tenant's wallet. By default
(dcpPresentationQueryMode: presentation-definition) the control plane sends a
Presentation Definition built from typeFilter, issuerFilter and
customFields. Counterparties such as EDC IdentityHub only support scope
queries; for them, set dcpPresentationQueryMode: scope. Incoming requests are
then verified with dcpRequiredScopes. If omitted, it falls back to
dcpDefaultScopes, which also supplies the scopes in outgoing tokens. Set the
two lists separately when you need different incoming and outgoing scopes.
Explicit agreement scopes are unchanged. Returned credentials must still
satisfy typeFilter, issuerFilter and customFields and be issued to the
requester.
In scope mode, every tenant must have a non-empty effective incoming scope
list. An explicitly empty dcpRequiredScopes does not fall back to the defaults.
The control plane rejects invalid configurations at startup. When upgrading a
deployment with dcpPresentationQueryMode: scope, configure either
dcpRequiredScopes or a non-empty dcpDefaultScopes before restarting.
iam:
type: tsg
dcpPresentationQueryMode: scope
dcpDefaultScopes:
- org.eclipse.dspace.dcp.vc.type:ParticipantCredential:read
dcpRequiredScopes:
- org.eclipse.dspace.dcp.vc.type:MembershipCredential:read
Transfer context for data planes
When a provider accepts a transfer request, it stores a snapshot of the transfer context and sends it to the data plane in every signaling message for that transfer:
participantIdandcounterPartyId: the agreement assigner and the verified requester.dataspaceContext:iam.dataspaceContext(defaultdataspace-protocol-http:2025-1).claims: a flat projection of the requester's verified credentials, limited toiam.transferClaims. When omitted, the default allow-list issubjectId,credentialTypesandissuers. A configured list replaces all defaults;transferClaims: []sends no claims. This does not remove the separatecounterPartyIdfield. List claims collect all values; a single claim with different values across credentials is left out.
To add membership.status while retaining all default claims, include the
complete list. Both issuer mappings are needed to support string and object
issuer forms:
iam:
transferClaims:
- name: subjectId
path: credentialSubject.id
- name: credentialTypes
path: type
list: true
- name: issuers
path: issuer
list: true
- name: issuers
path: issuer.id
list: true
- name: membership.status
path: credentialSubject.membership.status
Configuration parameters
| Key | Required | Type | Description | Default |
|---|---|---|---|---|
ControlPlaneTenancyConfig | ||||
tenancy | ControlPlaneTenancyConfig | undefined | ||
tenancy.mode | "managed" | Tenant state is provisioned by Tenant Management | "managed" | |
tenancy.walletUrls | String | Operator-allowed Wallet management API bases for runtime IAM | [] | |
tenancy.tokenEndpoints | String | Operator-allowed SSO token endpoints for runtime IAM | [] | |
tenancy.profiles | Object | Operator-owned Control Plane IAM/data-plane/catalog profiles | {} | |
tenancy.serviceInstanceId | String | Operator-registered receiving service instance | "" | |
tenancy.managementUrl | String | Tenant Management UI URL | ||
tenancy.provisioningIdentities | Object | Exact manager issuer/subject, audience and allowed provisioning operations | [] | |
tenancy.platformPrincipals | Object | Exact human principals permitted to change shared process settings; organization roles grant no platform authority | [] | |
DatabaseConfig | ||||
db | Yes | DatabaseConfig | Database configuration | |
db.retryAttempts | Number | Connection attempts during application startup, three seconds apart | 10 | |
db.type | Yes | "sqlite" | "postgres" | Type of database | |
db.database | Yes | String | Name of the database | |
db.synchronize | Boolean | Synchronize database schema | false | |
db{type=sqlite} | Yes | SQLiteConfig | Database configuration | |
db{type=sqlite}.retryAttempts | Number | Connection attempts during application startup, three seconds apart | 10 | |
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 | false | |
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 | Object | SSL configuration of the database | false | |
db{type=postgres}.retryAttempts | Number | Connection attempts during application startup, three seconds apart | 10 | |
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 | false | |
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.signalingId | String | Receiving plane identity for signaling tokens. Defaults to clientId. | ||
auth.expectedIssuer | String | Expected API token issuer; defaults to the discovered OpenID issuer | ||
auth.expectedAudience | String | Expected API token audience; defaults to clientId. | ||
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 | RegistryConfig | Registry configuration | ||
registry.useRegistry | Boolean | Use registry to crawl catalogs | false | |
registry.participantDids | String | Participant DIDs to resolve directly for registry discovery | [] | |
registry.registryUrl | String | URL of the registry | ||
registry.registryDid | String | DID of the registry | ||
registry.registryIntervalInMilliseconds | Number | Interval in milliseconds to fetch registry | 30000 | |
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 | ||
PolicyEvaluationConfig | ||||
policy | PolicyEvaluationConfig | Policy evaluation interoperability configuration | ||
policy.consumerSkippedLeftOperands | String | ODRL left-operand IRIs a consumer may skip when they are direct permission constraints. The provider remains responsible for enforcing them. | [] | |
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 | ||
InteropConfig | ||||
interop | InteropConfig | Interoperability configuration | ||
EdcInteropConfig | ||||
interop.edc | EdcInteropConfig | EDC interoperability workarounds | ||
interop.edc.dataAddressEndpointProperty | Boolean | Also advertise the provider endpoint in the EDC endpoint property on outgoing DSP transfer messages, for peers that ignore the standard DataAddress endpoint. Stored and data-plane addresses are unchanged. | false | |
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 | Boolean | Log successful execute actions | false | |
audit.logReads | Boolean | Log successful read actions — disabled by default as reads are frequent and rarely relevant for auditing | false | |
audit.sensitiveResources | Array | Resources logged regardless of minimum severity, while still respecting action-specific success filters | ["w.key","w.credential","sso.user"] | |
AuditHandlersConfig | ||||
audit.handlers | AuditHandlersConfig | Handler configuration | ||
audit.handlers.console | Boolean | Enable console audit log handler | false | |
audit.handlers.database | Boolean | Enable database audit log handler | true | |
audit.handlers.otlp | Boolean | Enable OTLP audit log handler | false | |
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 | Number | OTLP export timeout in milliseconds | 5000 | |
audit.otlp.concurrencyLimit | Number | 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 |