Dataspace Profiles

Register custom dataspace profiles, attach them to participant contexts, and wire their DCP scopes and JSON-LD contexts through the Management API.

Overview

A dataspace profile describes everything a connector needs to talk to one dataspace. It bundles:

  • the wire protocol — the Dataspace Protocol (DSP) version, binding, and JSON-LD namespace used on the wire;
  • the JSON-LD contexts contributed by the dataspace’s policy vocabulary, loaded for compaction and expansion under the profile;
  • the trusted issuers whose Verifiable Credentials are accepted under the profile;
  • and, alongside the profile, the DCP scopes the connector requests to obtain those credentials.

In virtual mode a single EDC runtime can serve several profiles at once, so one connector can participate in multiple dataspaces side by side without leaking one dataspace’s protocol, contexts, or credential requests into another. Each registered profile is served for a participant only once that participant is associated with it.

Multi-profile virtual mode is experimental. It builds on the profile-context concept and requires the EDC virtual bundles (BOMs). See the decision records for dataspace profile context and the multi-profile virtual connector for the rationale.

Throughout this chapter a fictional dataspace named Aurora is used for the examples, with a profile named aurora-2025. Replace the names, namespaces, and credential types with the ones your dataspace governance defines.

Anatomy of a profile

A profile is a DataspaceProfile object with the following fields:

FieldRequiredDescription
nameyesThe profile id. Appears as a URL segment on the DSP endpoints and as the protocol string in DSP handshakes.
protocol.versionyesThe DSP protocol version handled by the profile (e.g. 2025-1).
protocol.bindingyesThe protocol binding, e.g. HTTPS.
protocol.namespaceyesThe default JSON-LD namespace for the DSP version.
protocol.pathnoThe URL path segment for the profile. Defaults to /{name} when omitted.
jsonLdContextsUrlnoThe JSON-LD contexts loaded for compaction/expansion under the profile.
trustedIssuersnoThe issuers accepted under the profile, each { "@id": <did>, "supportedTypes": [<credential type>...] }.

Registering a profile

A profile can be created at runtime through the Management API, or seeded from configuration at boot. Both paths write to the same profile store, so a profile seeded from configuration can later be updated through the API and vice versa.

With the Management API

Profiles are managed under the /v5beta/dataspaceprofiles path of the Management API.

OperationRequestAuthorization scope
CreatePOST /v5beta/dataspaceprofilesmanagement-api:profiles:write
UpdatePUT /v5beta/dataspaceprofilesmanagement-api:profiles:write
QueryPOST /v5beta/dataspaceprofiles/requestmanagement-api:profiles:read
GetGET /v5beta/dataspaceprofiles/{name}management-api:profiles:read
DeleteDELETE /v5beta/dataspaceprofiles/{name}management-api:profiles:write

The request body is a JSON-LD DataspaceProfile (dataspaceprofile.aurora.json):

{
  "@context": [
    "https://w3id.org/edc/connector/management/v2"
  ],
  "@type": "DataspaceProfile",
  "name": "aurora-2025",
  "protocol": {
    "version": "2025-1",
    "binding": "HTTPS",
    "namespace": "https://w3id.org/dspace/2025/1/"
  },
  "jsonLdContextsUrl": [
    "https://w3id.org/dspace/2025/1/context.jsonld",
    "https://w3id.org/edc/dspace/v0.0.1",
    "https://w3id.org/aurora/2025/policy/context.jsonld",
    "https://w3id.org/aurora/2025/policy/odrl.jsonld"
  ],
  "trustedIssuers": [
    {
      "@id": "did:web:issuer.aurora.example",
      "supportedTypes": [
        "MembershipCredential",
        "DataProcessorCredential"
      ]
    }
  ]
}

POST creates the profile and returns it; it responds with 409 Conflict if a profile with the same name already exists. PUT updates an existing profile only — the profile name is taken from the body, and a PUT against a name that does not exist returns 404 Not Found (it is not an upsert). If you seed profiles idempotently from an external job, create with POST and, on 409, follow up with PUT to reconcile the desired state (for example to change trustedIssuers).

Once the profile is registered, the DSP endpoints served by the virtual controllers become reachable under the profile’s path, per participant:

POST /{participantContextId}/aurora-2025/catalog/request
POST /{participantContextId}/aurora-2025/negotiations/...
POST /{participantContextId}/aurora-2025/transfers/...

With configuration

The same profile can be declared at boot through the DataspaceProfileConfigurationExtension. Each profile is declared under edc.dataspace.profiles.<alias>.*; the <alias> is a local config grouping key, not the profile id itself (the id is the name value).

edc.dataspace.profiles.aurora.name=aurora-2025
edc.dataspace.profiles.aurora.protocol.version=2025-1
edc.dataspace.profiles.aurora.protocol.binding=HTTPS
edc.dataspace.profiles.aurora.protocol.namespace=https://w3id.org/dspace/2025/1/
edc.dataspace.profiles.aurora.jsonld.context.urls=https://w3id.org/dspace/2025/1/context.jsonld,https://w3id.org/edc/dspace/v0.0.1,https://w3id.org/aurora/2025/policy/context.jsonld,https://w3id.org/aurora/2025/policy/odrl.jsonld
# Trusted issuers (repeat the block per issuer alias)
edc.dataspace.profiles.aurora.trustedissuers.aurora-issuer.id=did:web:issuer.aurora.example
edc.dataspace.profiles.aurora.trustedissuers.aurora-issuer.supportedtypes=MembershipCredential,DataProcessorCredential

Configured profiles are upserted into the profile store at boot, so a later Management API PUT or a subsequent config change updates the same profile.

Associating a profile with a participant context

A profile registered at the runtime level is only served for a participant once that participant is associated with it. Association is per-participant and is managed under /v5beta/participants/{participantContextId}/profiles.

OperationRequestAuthorization scope
AssociatePUT /v5beta/participants/{participantContextId}/profilesmanagement-api:admin
ListGET /v5beta/participants/{participantContextId}/profilesmanagement-api:profiles:read

The associate request carries an AssociateDataspaceProfile with the profile ids to attach (associateprofile.aurora.json):

{
  "@context": [
    "https://w3id.org/edc/connector/management/v2"
  ],
  "@type": "AssociateDataspaceProfile",
  "profiles": [
    "aurora-2025"
  ]
}

The call replaces the participant’s associated profile list with the one supplied, so send the full set of ids the participant should serve. Every id is validated against the registered profiles — an unknown id is rejected with 400 Bad Request — and a successful call returns 204 No Content.

GET on the same path returns the currently associated profiles as full DataspaceProfile objects:

[
  {
    "@context": [
      "https://w3id.org/edc/connector/management/v2"
    ],
    "@type": "DataspaceProfile",
    "name": "aurora-2025",
    "protocol": {
      "version": "2025-1",
      "path": "/aurora-2025",
      "binding": "HTTPS",
      "namespace": "https://w3id.org/dspace/2025/1/"
    },
    "jsonLdContextsUrl": [
      "https://w3id.org/dspace/2025/1/context.jsonld",
      "https://w3id.org/edc/dspace/v0.0.1",
      "https://w3id.org/aurora/2025/policy/context.jsonld",
      "https://w3id.org/aurora/2025/policy/odrl.jsonld"
    ]
  }
]

Enabling all profiles

Test and sample runtimes can skip the association step entirely by setting the runtime-wide flag:

edc.dataspace.enable.profiles.all=true

When enabled, every participant sees every registered profile and the per-participant association is ignored. Production deployments should not enable this — it defeats the isolation between dataspaces.

Configuring DCP scopes

When the connector performs the Decentralized Claims Protocol (DCP) handshake, it asks the counterparty’s wallet for a set of credentials expressed as scopes. Scopes are declared per profile, so the connector only requests the credentials a given dataspace needs.

Two categories exist:

  • DEFAULT — requested on every DCP handshake under the profile.
  • POLICY — requested only when a constraint whose left operand matches the scope’s prefix mapping appears in the policy being evaluated.

With the Management API

Scopes are managed under /v5beta/dcpscopes (POST to create, PUT /{id} to update, DELETE /{id} to remove, POST /request to query). All operations require the management-api:admin scope.

An always-on membership scope (dcpscope.membership.json):

{
  "@context": [
    "https://w3id.org/edc/connector/management/v2"
  ],
  "@type": "DcpScope",
  "@id": "membership-scope",
  "value": "org.eclipse.dspace.dcp.vc.type:MembershipCredential:read",
  "type": "DEFAULT",
  "profile": "aurora-2025"
}

The DcpScope fields are:

FieldRequiredDescription
@idnoIdentifier for the scope entry.
valueyesThe scope string sent on the DCP handshake, e.g. org.eclipse.dspace.dcp.vc.type:MembershipCredential:read.
profileno (default *)The profile this scope belongs to. Keep it set so scopes are not requested against other dataspaces.
typeno (default DEFAULT)DEFAULT or POLICY.
prefixMappingwhen type=POLICYMaps a policy left-operand prefix to the scope; required for POLICY scopes and must be absent for DEFAULT scopes.

With configuration

The same scopes can be seeded from configuration under edc.iam.dcp.scopes.<alias>.*:

edc.iam.dcp.scopes.membership.id=membership-scope
edc.iam.dcp.scopes.membership.type=DEFAULT
edc.iam.dcp.scopes.membership.value=org.eclipse.dspace.dcp.vc.type:MembershipCredential:read
edc.iam.dcp.scopes.membership.profile=aurora-2025

edc.iam.dcp.scopes.processor.id=processor-scope
edc.iam.dcp.scopes.processor.type=DEFAULT
edc.iam.dcp.scopes.processor.value=org.eclipse.dspace.dcp.vc.type:DataProcessorCredential:read
edc.iam.dcp.scopes.processor.profile=aurora-2025

For a POLICY scope, add edc.iam.dcp.scopes.<alias>.prefix.mapping with the left-operand prefix that triggers the request.

Caching JSON-LD contexts

The jsonLdContextsUrl declared on the profile point at the dataspace’s policy vocabulary. EDC does not fetch external JSON-LD contexts at runtime by default, so the JSON-LD processor must be able to resolve those URLs locally. There are two ways to make them available.

With the Management API

The document cache API stores a document against a URL so the processor resolves it locally instead of reaching out to the network. Documents are managed under /v5beta/cacheddocuments (POST to create, GET/PUT/DELETE /{id} to read, update, and remove, and POST /{id}/refresh to force a re-fetch). All operations require the management-api:admin scope.

Cache a context by URL — EDC fetches and stores it (cacheddocument.context.json):

{
  "@context": [
    "https://w3id.org/edc/connector/management/v2"
  ],
  "@type": "CachedDocument",
  "url": "https://w3id.org/aurora/2025/policy/context.jsonld",
  "documentType": "JSON_LD"
}

The CachedDocument fields are:

FieldRequiredDescription
urlyesThe document URL, as referenced from the profile or policy.
contentnoThe document inlined as raw JSON. Supply it to cache an air-gapped document without any network fetch.
documentTypeno (default JSON_LD)JSON_LD or JSON_SCHEMA.
pullStrategynoNEVER, IF_NOT_PRESENT, or ALWAYS. Defaults to NEVER when content is supplied, otherwise IF_NOT_PRESENT.

To cache a document without any network access, provide its body inline via content:

{
  "@context": [
    "https://w3id.org/edc/connector/management/v2"
  ],
  "@type": "CachedDocument",
  "url": "https://w3id.org/aurora/2025/policy/odrl.jsonld",
  "documentType": "JSON_LD",
  "content": {
    "@context": {
      "@version": 1.1,
      "@protected": true,
      "aurora": "https://w3id.org/aurora/2025/policy/"
    }
  }
}

With mounted files

Alternatively, contexts can be cached at boot from files mounted into the runtime, under edc.jsonld.document.<alias>.*:

edc.jsonld.document.aurora-policy.url=https://w3id.org/aurora/2025/policy/context.jsonld
edc.jsonld.document.aurora-policy.path=/app/jsonld/aurora-policy-context.jsonld
edc.jsonld.document.aurora-odrl.url=https://w3id.org/aurora/2025/policy/odrl.jsonld
edc.jsonld.document.aurora-odrl.path=/app/jsonld/aurora-odrl.jsonld

where /app/jsonld is a directory containing the context files. The runtime resolves the URLs from the mounted files and does not fetch them at runtime.

Validating profile policies with JSON Schema

Optionally, policies submitted for a profile can be validated against a JSON Schema at the Management API boundary, so malformed policies are rejected on ingest. Validators are declared through the management-api-schema-validator extension under edc.mgmt.api.schema.<alias>.* and can be scoped to a profile: the validator activates only when the submitted input’s policy.profile matches one of the configured profiles.

# Group declaration: register the validators under the 'v4' version prefix
edc.mgmt.api.schema.aurora.version=v4
# Redirect the upstream schema prefix to a locally bundled copy so it is not fetched at runtime
edc.mgmt.api.schema.aurora.mapping.from=https://w3id.org/aurora/2025/policy/schema
edc.mgmt.api.schema.aurora.mapping.to=classpath:/aurora/schema
# Validate PolicyDefinition inputs, but only when policy.profile == aurora-2025
edc.mgmt.api.schema.aurora.validator.policy.type=PolicyDefinition
edc.mgmt.api.schema.aurora.validator.policy.schema=https://w3id.org/aurora/2025/policy/schema/policy-schema.json#/definitions/PolicyDefinition
edc.mgmt.api.schema.aurora.validator.policy.profiles=aurora-2025

Because the validator is bound to aurora-2025, a submitted PolicyDefinition is validated only when its policy object carries profile: aurora-2025. Bundle the schema files with the connector (or mount them and map mapping.to to the mount path) so validation never depends on an upstream URL being reachable.

Putting it together

To bring a participant online in the Aurora dataspace:

  1. Register the profilePOST /v5beta/dataspaceprofiles with dataspaceprofile.aurora.json.
  2. Cache its JSON-LD contextsPOST /v5beta/cacheddocuments for https://w3id.org/aurora/2025/policy/context.jsonld and .../odrl.jsonld (see cacheddocument.context.json), so the profile’s contexts resolve locally.
  3. Declare the DCP scopesPOST /v5beta/dcpscopes for the credentials Aurora requires (see dcpscope.membership.json), each with profile: aurora-2025.
  4. Associate the profile with the participantPUT /v5beta/participants/{participantContextId}/profiles with associateprofile.aurora.json.

The participant now serves the Aurora DSP endpoints — for example the catalog can be requested at POST /{participantContextId}/aurora-2025/catalog/request — requesting the Aurora credentials on the DCP handshake and resolving the Aurora policy contexts from the cache.


Last modified September 4, 2026: feat: dataspace profile docs (#207) (23fc6a7)