In order to be compatible with other connectors, the FIWARE Data Space Connector supports the Dataspace Protocol and the Decentralize Claims Protocol with the Dataspace Protocol.
- TMForum API provides the APIs for creating and managing Products and Offerings
- integrates with the Contract Management through Events
- FDSC-EDC is an implementation of the Eclipse Data Space Components that
- provides the Transfer Process API
- provides the Catalog API
- provides the Contract Negotiation API
- uses the TMForum API as storage backend
- uses the FIWARE Data Space Connector as the dataplane to provision transfers
- provides two flavors:
- OID4VC - uses the OpenID for Verifiable Credentials Protocols for authentication between connectors
- DCP - uses the Decentralized Claims Protocol for authentication between connectors
- Identity Hub - implementation of the EDC-Identity Services, currently using the Tractus-X IdentityHub
In order to integrate with EUDI Wallet ARF compatible wallets and better support for human-to-machine interaction, the FDSC-EDC implementation supports the usage of OpenID 4 Verifiable Presentations for Connector-to-Connector communication and for authentication of users accessing the actual data and services. Both sides need to have a full IAM-Framework in place(including Verifier and PEP). The following architecture only shows those components at the provider side, however the flow is the same on call from provider to consumer, thus the consumer needs to have them, too.
A request uses the flow:
- Get /.well-known/openid-configuration
- Extract the Authorization Endpoint and call it
- Verifier will return the Authorization Request, including information about the requested credentials(via DCQL)
- Consumer selects the claims and credentials to be presented, creates and signs a vp_token and sends it to the verifiers Token Endpoint
- Verifier verifies the token against, checks the issuer at the configured trusted lists and returns a Token Response containing the JWT in the configured(see Credentials-Config-Service) format
- Consumer requests the Provider Controlplane through its Policy-Enforcement-Point. PEP will apply policies, controling access to the DSP-API
- PEP forwards request to the Controlplane, in order to handle it
In order to authenticate itself, the FDSC-EDC requires access to the Verifiable Credentials it needs to present. Currently, the implementation only supports storage of the credentials inside a folder provided to the FDSC-EDC. The credentials need to be provided in plain fails, using .jwt for JWT-VC and .sd_jwtfor SD-JWT VC as file extension. See the FDSC-EDC Helm Chart documentation for more details on configuring the FDSC-EDC.
In order to authenticate via DCP, an instance of the Identity Hub has to be provided. The following diagram only includes it on the Consumer side, but its also needed on the Provider side to call back.
- Connector gets an Self-Issued ID-Token from its IdentiyHub(STS-Service), that includes the DID and the Access Token
- Sends request with both Tokens to the Provider connector
- Provider resolves the DID, extracted from the ID-Token, requests did-document at the Consumer's IdentityHub(DID-Service)
- Consumer returns did-document, containing the Credential Service address
- Provider requests Credentials at the IdentityHub(Credential Service), using the Access Token
- Consumer returns Verifiable Presentation, containing the Credentials. Provider side verifies them and checks issuers at the TrustedIssuersList.
For configuration details, see the FDSC-EDC Helm Chart documentation.
The FIWARE FDSC-EDC does not come with an explicit implementation of the dataplane, since the FIWARE Data Space Connector itself acts as such. Transfers are provisioned at Apisix and configured depending on the requested Authentication Protocol.
With an agreement in place, connectors can request data transfers using the Transfer Process Protocol. When a Transfer Process gets started, the provisioning happens as following
- Add AccessPolicies to the PAP, enforces them on usage of the transfer
- Add Credentials Configuration to the Credentials-Config-Service if configured
- Create a <TRANSFER_PROCESS_ID>/.well-known/openid-configuration route at Apisix, to allow OID4VP auth
- Create a <TRANSFER_PROCESS_ID>/ route to the data-service/data-set
- Return the created route-endpoint to the counterparty for usage
- Enforce AccessPolicies
- Create a <TRANSFER_PROCESS_ID>/ route to the data-service/data-set
- Generate a JWT, containing the transfer-process-id as scope, signed with the controlplanes key
- Return the route-endpoint and the jwt to the counterparty for usage
To run the local setup:
mvn clean deploy -Plocal,dspThe following steps will show how to create a ProductOffering throught the TMForum-APIs, buy access to it via TMForum, DSP+OID4VP and DSP+DCP and access the purchased data-service through all 3 methods. It requires a proper setup of Consumer and Provider identities as expected for the DCP, which also works for OID4VP.
The consumer-identity and key-material needs to be registered in the identityhub, in order to participate in DCP-based DSP interactions. Since all OID4VC base flows do not rely on any propriatary extensions to the did-standard, they can also work with that.
- Get the private key(managed by cert-manager, used for signing the certificates) as JWK(to allow signing tokens in the Secure Token Service):
export CONSUMER_JWK=$(./doc/scripts/get-private-jwk-from-k8s-secret.sh consumer fancy-marketplace.biz-tls); echo $CONSUMER_JWK- Insert the key into Vault:
curl -X POST 'http://vault-fancy-marketplace.127.0.0.1.nip.io:8080/v1/secret/data/key-1' \
--header 'X-Vault-Token: root' \
--data "$(jq -n --arg content "$CONSUMER_JWK" '{data:{content:$content}}')"- Insert the participants identity and public key into identity hub.
export CONSUMER_PARTICIPANT=$(./doc/scripts/get-participant-create.sh "${CONSUMER_JWK}" did:web:fancy-marketplace.biz "http://identityhub-fancy-marketplace.127.0.0.1.nip.io" "key-1"); echo ${CONSUMER_PARTICIPANT} | jq .
curl -X POST \
'http://identityhub-management-fancy-marketplace.127.0.0.1.nip.io:8080/api/identity/v1alpha/participants' \
--header 'Accept: */*' \
--header 'x-api-key: c3VwZXItdXNlcg==.random' \
--header 'Content-Type: application/json' \
--data "${CONSUMER_PARTICIPANT}"- Check that the identity(e.g. did-document) is available:
curl -x localhost:8888 https://fancy-marketplace.biz/.well-known/did.json -k | jq .The provider indentity has to be prepared exactly the same way:
- Get the private key(managed by cert-manager, used for signing the certificates) as JWK(to allow signing tokens in the Secure Token Service):
export PROVIDER_JWK=$(./doc/scripts/get-private-jwk-from-k8s-secret.sh provider mp-operations.org-tls); echo $PROVIDER_JWK- Insert the key into Vault:
curl -X POST 'http://vault-mp-operations.127.0.0.1.nip.io:8080/v1/secret/data/key-1' \
--header 'X-Vault-Token: root' \
--data "$(jq -n --arg content "$PROVIDER_JWK" '{data:{content:$content}}')"- Insert the participants identity and public key into identity hub.
export PROVIDER_PARTICIPANT=$(./doc/scripts/get-participant-create.sh "${PROVIDER_JWK}" "did:web:mp-operations.org" "http://identityhub-mp-operations.127.0.0.1.nip.io" "key-1"); echo ${PROVIDER_PARTICIPANT} | jq .
curl -X POST \
'http://identityhub-management-mp-operations.127.0.0.1.nip.io:8080/api/identity/v1alpha/participants' \
--header 'Accept: */*' \
--header 'x-api-key: c3VwZXItdXNlcg==.random' \
--header 'Content-Type: application/json' \
--data "${PROVIDER_PARTICIPANT}"- Check that the identity(e.g. did-document) is available:
curl -x localhost:8888 https://mp-operations.org/.well-known/did.json -k | jq .The demo deployment of the DSP is configured to require every participant to identify itself with a "MembershipCredential". In most use-cases, this credential will be issued by a central data-space authority. In order to keep the local deployment at managable size, we allow self-issuance of such credentials. The OID4VC based flows are automatically configured to get such credential in the local deployment, thus the Credential only needs to be issued for usage in DCP-based flows. The deployed Tractus-X Identityhub supports integration with compliant Issuer Services. However, we are reusing the default issuer from the FIWARE Data Space Connector and insert the credential manually into the identity hub:
- Get the credential and its decoded content:
export CONSUMER_CREDENTIAL=$(./doc/scripts/get_credential.sh https://keycloak-consumer.127.0.0.1.nip.io membership-credential employee); echo Consumer Credential: ${CONSUMER_CREDENTIAL}
export CONSUMER_CREDENTIAL_CONTENT=$(./doc/scripts/get-payload-from-jwt.sh "${CONSUMER_CREDENTIAL}" | jq -r '.vc'); echo ${CONSUMER_CREDENTIAL_CONTENT} | jq .- Insert the credential into the identity hub:
curl -X POST \
'http://identityhub-management-fancy-marketplace.127.0.0.1.nip.io:8080/api/identity/v1alpha/participants/ZGlkOndlYjpmYW5jeS1tYXJrZXRwbGFjZS5iaXo/credentials' \
--header 'Accept: */*' \
--header 'x-api-key: c3VwZXItdXNlcg==.random' \
--header 'Content-Type: application/json' \
--data-raw "{
\"id\": \"membership-credential\",
\"participantContextId\": \"did:web:fancy-marketplace.biz\",
\"verifiableCredentialContainer\": {
\"rawVc\": \"${CONSUMER_CREDENTIAL}\",
\"format\": \"VC1_0_JWT\",
\"credential\": ${CONSUMER_CREDENTIAL_CONTENT}
}
}"- Get the credential and its decoded content:
export PROVIDER_CREDENTIAL=$(./doc/scripts/get_credential.sh https://keycloak-provider.127.0.0.1.nip.io membership-credential employee); echo Provider Credential: ${PROVIDER_CREDENTIAL}
export PROVIDER_CREDENTIAL_CONTENT=$(./doc/scripts/get-payload-from-jwt.sh "${PROVIDER_CREDENTIAL}" | jq -r '.vc'); echo ${PROVIDER_CREDENTIAL_CONTENT} | jq .- Insert the credential into the identity hub:
curl -X POST \
'http://identityhub-management-mp-operations.127.0.0.1.nip.io:8080/api/identity/v1alpha/participants/ZGlkOndlYjptcC1vcGVyYXRpb25zLm9yZw/credentials' \
--header 'Accept: */*' \
--header 'x-api-key: c3VwZXItdXNlcg==.random' \
--header 'Content-Type: application/json' \
--data-raw "{
\"id\": \"membership-credential\",
\"participantContextId\": \"did:web:mp-operations.org\",
\"verifiableCredentialContainer\": {
\"rawVc\": \"${PROVIDER_CREDENTIAL}\",
\"format\": \"VC1_0_JWT\",
\"credential\": ${PROVIDER_CREDENTIAL_CONTENT}
}
}"Both participants are preconfigured to trust each other for membership credentials. All components(VCVerifier, FDSC-EDC Controlplane) use the same local trusted issuers list to verify issuers of credentials(check provider-side list for example):
curl -X GET http://til-provider.127.0.0.1.nip.io:8080/issuer/did:web:fancy-marketplace.biz | jq .The Dataservice provided in the Demo Environment is an NGSI-LD Context Broker. In order to have some data available, an entity will be inserted directly. In the demo-scenario, the provider participant "mp-operations.org" offers hosting capabilities and offers detailed data about those machines. As example, we provide an uptime report:
curl -X POST http://scorpio-provider.127.0.0.1.nip.io:8080/ngsi-ld/v1/entities \
-H 'Accept: application/json' \
-H 'Content-Type: application/json' \
-d '{
"id": "urn:ngsi-ld:UptimeReport:fms-1",
"type": "UptimeReport",
"name": {
"type": "Property",
"value": "Standard Server"
},
"uptime": {
"type": "Property",
"value": "99.9"
}
}'The Demo - Offering should be available for negotiation and usage through standard TMForum mechanisms and through DSP. There for all offerings are managed through TMForum-Standard APIs. In the Demo they are requested directly, while in real-world use-cases they most likely will be used through graphical interfaces like the BAE Marketplace.
- A category has to be created, in order to assing the offering to a catalog:
export CATEGORY_ID=$(curl -X 'POST' \
'http://tm-forum-api.127.0.0.1.nip.io:8080/tmf-api/productCatalogManagement/v4/category' \
-H 'accept: application/json;charset=utf-8' \
-H 'Content-Type: application/json;charset=utf-8' \
-d '{
"description": "Demo Category",
"name": "Demo Category"
}' | jq .id -r); echo ${CATEGORY_ID}- Create the catalog, that includes the category:
export CATALOG_ID=$(curl -X 'POST' \
'http://tm-forum-api.127.0.0.1.nip.io:8080/tmf-api/productCatalogManagement/v4/catalog' \
-H 'accept: application/json;charset=utf-8' \
-H 'Content-Type: application/json;charset=utf-8' \
-d "{
\"description\": \"Demo Catalog\",
\"name\": \"Demo Catalog\",
\"category\": [
{
\"id\": \"${CATEGORY_ID}\"
}
]
}" | jq .id -r); echo ${CATALOG_ID}- Create the product specification:
export PRODUCT_SPEC_ID=$(curl -X 'POST' \
'http://tm-forum-api.127.0.0.1.nip.io:8080/tmf-api/productCatalogManagement/v4/productSpecification' \
-H 'accept: application/json;charset=utf-8' \
-H 'Content-Type: application/json;charset=utf-8' \
-d '{
"name": "Demo Spec",
"externalId": "ASSET-1",
"@schemaLocation": "https://raw.githubusercontent.com/wistefan/edc-dsc/refs/heads/init/schemas/external-id.json",
"productSpecCharacteristic": [
{
"id": "dcp",
"name":"Endpoint, that the service can be negotiated at via DCP.",
"valueType":"endpointUrl",
"productSpecCharacteristicValue": [{
"value":"http://dcp-mp-operations.127.0.0.1.nip.io:8080/api/dsp/2025-1",
"isDefault": true
}]
},{
"id": "oid4vc",
"name":"Endpoint, that the service can be negotiated at via OID4VC.",
"valueType":"endpointUrl",
"productSpecCharacteristicValue": [{
"value":"http://dsp-mp-operations.127.0.0.1.nip.io:8080/api/dsp/2025-1",
"isDefault": true
}]
},
{
"id": "upstreamAddress",
"name":"Address of the upstream serving the data",
"valueType":"upstreamAddress",
"productSpecCharacteristicValue": [{
"value":"data-service-scorpio:9090",
"isDefault": true
}]
},
{
"id": "endpointDescription",
"name":"Service Endpoint Description",
"valueType":"endpointDescription",
"productSpecCharacteristicValue": [{
"value":"The Demo Service"
}]
},
{
"id": "targetSpecification",
"name":"Detailed specification of the ODRL target. Allows to over services via OID4VC",
"valueType":"targetSpecification",
"productSpecCharacteristicValue": [{
"value": {
"@type": "AssetCollection",
"refinement": [
{
"@type": "Constraint",
"leftOperand": "http:path",
"operator": "http:isInPath",
"rightOperand": "/*/ngsi-ld/v1/entities"
}
]
},
"isDefault": true
}]
},
{
"id": "serviceConfiguration",
"name": "Service config to be used in the credentials config service when provisioning transfers through OID4VC",
"valueType": "serviceConfiguration",
"productSpecCharacteristicValue": [
{
"isDefault": true,
"value": {
"defaultOidcScope": "openid",
"authorizationType": "DEEPLINK",
"oidcScopes": {
"openid": {
"credentials": [
{
"type": "MembershipCredential",
"trustedParticipantsLists": [
{
"type": "ebsi",
"url": "http://tir.127.0.0.1.nip.io"
}
],
"trustedIssuersLists": ["http://trusted-issuers-list:8080"],
"jwtInclusion": {
"enabled": true,
"fullInclusion": true
}
}
],
"dcql": {
"credentials": [
{
"id": "legal-person-query",
"format": "jwt_vc_json",
"multiple": false,
"meta": {
"type_values": [["MembershipCredential"]]
}
}
]
}
}
}
}
}
]
},
{
"id": "credentialsConfig",
"name": "Credentials Config",
"@schemaLocation": "https://raw.githubusercontent.com/FIWARE/contract-management/refs/heads/main/schemas/credentials/credentialConfigCharacteristic.json",
"valueType": "credentialsConfiguration",
"productSpecCharacteristicValue": [
{
"isDefault": true,
"value": {
"credentialsType": "OperatorCredential",
"claims": [{
"name": "roles",
"path": "$.roles[?(@.target==\"did:web:mp-operation.org\")].names[*]",
"allowedValues": [
"OPERATOR"
]
}]
}
}]
},
{
"id": "policyConfig",
"name": "Policy for creation of K8S clusters.",
"@schemaLocation": "https://raw.githubusercontent.com/FIWARE/contract-management/refs/heads/policy-support/schemas/odrl/policyCharacteristic.json",
"valueType": "authorizationPolicy",
"productSpecCharacteristicValue": [
{
"isDefault": true,
"value": {
"@context": {
"odrl": "http://www.w3.org/ns/odrl/2/"
},
"@id": "https://mp-operation.org/policy/common/k8s-small",
"odrl:uid": "https://mp-operation.org/policy/common/k8s-small",
"@type": "odrl:Policy",
"odrl:permission": {
"odrl:assigner": "https://www.mp-operation.org/",
"odrl:target": {
"@type": "odrl:AssetCollection",
"odrl:source": "urn:asset",
"odrl:refinement": [
{
"@type": "odrl:Constraint",
"odrl:leftOperand": "http:path",
"odrl:operator": "http:isInPath",
"odrl:rightOperand": "/ngsi-ld/v1/entities"
}
]
},
"odrl:assignee": {
"@type": "odrl:PartyCollection",
"odrl:source": "urn:user",
"odrl:refinement": {
"@type": "odrl:LogicalConstraint",
"odrl:and": [
{
"@type": "odrl:Constraint",
"odrl:leftOperand": "vc:role",
"odrl:operator": "odrl:hasPart",
"odrl:rightOperand": {
"@value": "OPERATOR",
"@type": "xsd:string"
}
},
{
"@type": "odrl:Constraint",
"odrl:leftOperand": "vc:type",
"odrl:operator": "odrl:hasPart",
"odrl:rightOperand": {
"@value": "OperatorCredential",
"@type": "xsd:string"
}
}
]
}
},
"odrl:action": "odrl:read"
}
}
}]
}]
}' | jq '.id' -r); echo ${PRODUCT_SPEC_ID}- Create the coresponding offering. It includes the policies required to make it accessible through DSP:
access_policy_id=$(uuidgen)
contract_policy_id=$(uuidgen)
curl -X 'POST' \
'http://tm-forum-api.127.0.0.1.nip.io:8080/tmf-api/productCatalogManagement/v4/productOffering' \
-H 'accept: application/json;charset=utf-8' \
-H 'Content-Type: application/json;charset=utf-8' \
-d "{
\"name\": \"Test Offering\",
\"description\": \"Test Offering description\",
\"isBundle\": false,
\"isSellable\": true,
\"lifecycleStatus\": \"Active\",
\"@schemaLocation\": \"https://raw.githubusercontent.com/wistefan/edc-dsc/refs/heads/init/schemas/external-id.json\",
\"externalId\": \"OFFER-1\",
\"productSpecification\":
{
\"id\": \"${PRODUCT_SPEC_ID}\",
\"name\":\"The Test Spec\"
},
\"category\": [{
\"id\": \"${CATEGORY_ID}\"
}],
\"productOfferingTerm\": [
{
\"name\": \"edc:contractDefinition\",
\"@schemaLocation\": \"https://raw.githubusercontent.com/wistefan/edc-dsc/refs/heads/init/schemas/contract-definition.json\",
\"accessPolicy\": {
\"@context\": \"http://www.w3.org/ns/odrl.jsonld\",
\"odrl:uid\": \"${access_policy_id}\",
\"assigner\": \"did:web:mp-operations.org\",
\"permission\": [{
\"action\": \"use\"
}],
\"@type\": \"Offer\"
},
\"contractPolicy\": {
\"@context\": \"http://www.w3.org/ns/odrl.jsonld\",
\"odrl:uid\": \"${contract_policy_id}\",
\"assigner\": \"did:web:mp-operations.org\",
\"permission\": [{
\"action\": \"use\",
\"constraint\": {
\"leftOperand\": \"odrl:dayOfWeek\",
\"operator\": \"lt\",
\"rightOperand\": 6
}
}],
\"@type\": \"Offer\"
}
}]
}" | jq .💡 While the purchase-process happens through direct API-Calls here, GUI solutions like the BAE-Marketplace can mask all those interactions.
The TMForum APIs are secured by the PEP(Apisix). In order to allow access, we need to put policies to allow that in place:
./doc/scripts/prepare-policies.shIn order to interact with the TMForum and buy access, a couple of credentials are required:
- The LegalPersonCredential for the representative allowed to buy products:
export REP_CREDENTIAL=$(./doc/scripts/get_credential.sh https://keycloak-consumer.127.0.0.1.nip.io user-credential representative); echo ${REP_CREDENTIAL}- The OperatorCredential for the operator allowed to use the product and access the service:
export OPERATOR_CREDENTIAL=$(./doc/scripts/get_credential.sh https://keycloak-consumer.127.0.0.1.nip.io operator-credential operator); echo ${OPERATOR_CREDENTIAL}- Register the consumer in the marketplace:
export CONSUMER_DID="did:web:fancy-marketplace.biz"
export ACCESS_TOKEN=$(./doc/scripts/get_access_token_oid4vp.sh http://mp-data-service.127.0.0.1.nip.io:8080 $REP_CREDENTIAL default); echo ${ACCESS_TOKEN}
export FANCY_MARKETPLACE_ID=$(curl -X POST http://tm-forum-api.127.0.0.1.nip.io:8080/tmf-api/party/v4/organization \
-H 'Accept: */*' \
-H 'Content-Type: application/json' \
-H "Authorization: Bearer ${ACCESS_TOKEN}" \
-d "{
\"name\": \"Fancy Marketplace Inc.\",
\"partyCharacteristic\": [
{
\"name\": \"did\",
\"value\": \"${CONSUMER_DID}\"
}
]
}" | jq '.id' -r); echo ${FANCY_MARKETPLACE_ID}- Buy access through TMForum:
- List offerings
export ACCESS_TOKEN=$(./doc/scripts/get_access_token_oid4vp.sh http://mp-data-service.127.0.0.1.nip.io:8080 $REP_CREDENTIAL default); echo $ACCESS_TOKEN curl -X GET http://tm-forum-api.127.0.0.1.nip.io:8080/tmf-api/productCatalogManagement/v4/productOffering -H "Authorization: Bearer ${ACCESS_TOKEN}" | jq .
- Get offer Id and order it:
export ACCESS_TOKEN=$(./doc/scripts/get_access_token_oid4vp.sh http://mp-data-service.127.0.0.1.nip.io:8080 $REP_CREDENTIAL default); echo $ACCESS_TOKEN export OFFER_ID=$(curl -X GET http://tm-forum-api.127.0.0.1.nip.io:8080/tmf-api/productCatalogManagement/v4/productOffering -H "Authorization: Bearer ${ACCESS_TOKEN}" | jq '.[0].id' -r); echo ${OFFER_ID} export ORDER_ID=$(curl -X POST http://tm-forum-api.127.0.0.1.nip.io:8080/tmf-api/productOrderingManagement/v4/productOrder \ -H 'Accept: */*' \ -H 'Content-Type: application/json' \ -H "Authorization: Bearer ${ACCESS_TOKEN}" \ -d "{ \"productOrderItem\": [ { \"id\": \"random-order-id\", \"action\": \"add\", \"productOffering\": { \"id\" : \"${OFFER_ID}\" } } ], \"relatedParty\": [ { \"id\": \"${FANCY_MARKETPLACE_ID}\" } ]}" | jq '.id' -r); echo ${ORDER_ID}
- Complete the order:
curl -X 'PATCH' \ -H "Authorization: Bearer ${ACCESS_TOKEN}" \ http://tm-forum-api.127.0.0.1.nip.io:8080/tmf-api/productOrderingManagement/v4/productOrder/${ORDER_ID} \ -H 'accept: application/json;charset=utf-8' \ -H 'Content-Type: application/json;charset=utf-8' \ -d "{ \"state\": \"completed\" }" | jq .
With that, the Data Space Connector will create: * an entry in the TrustedIssuers registry, to allow issuance of OperatorCredentials * register the policy to allow access for users with OperatorCredential in role "Operator"
- Access the entity:
export ACCESS_TOKEN=$(./doc/scripts/get_access_token_oid4vp.sh http://mp-data-service.127.0.0.1.nip.io:8080 $OPERATOR_CREDENTIAL operator); echo $ACCESS_TOKEN
curl -X GET http://mp-data-service.127.0.0.1.nip.io:8080/ngsi-ld/v1/entities/urn:ngsi-ld:UptimeReport:fms-1 \
--header 'Content-Type: application/json' \
--header "Authorization: Bearer ${ACCESS_TOKEN}"The same product now can be negotiatiated throught the Dataspace Protocol. The interactions will be controlled throught he management API of the FDSC-EDC Controlplane, authentication is done by the connector's and cannot be related to the actual actor.
- Read the catalog:
curl -X POST \
'http://dsp-dcp-management.127.0.0.1.nip.io:8080/api/v1/management/v3/catalog/request' \
--header 'Accept: */*' \
--header 'Content-Type: application/json' \
--data-raw '{
"@context": [
"https://w3id.org/edc/connector/management/v0.0.1"
],
"@type": "CatalogRequestMessage",
"protocol": "dataspace-protocol-http:2025-1",
"counterPartyId": "did:web:mp-operations.org",
"counterPartyAddress": "http://dcp-mp-operations.127.0.0.1.nip.io:8080/api/dsp/2025-1",
"querySpec": {
}
}' | jq .- Start the negotiation by selecting the offer:
curl -X POST \
'http://dsp-dcp-management.127.0.0.1.nip.io:8080/api/v1/management/v3/contractnegotiations' \
--header 'Accept: */*' \
--header 'Content-Type: application/json' \
--data-raw '{
"@context": [
"https://w3id.org/edc/connector/management/v0.0.1"
],
"@type": "ContractRequest",
"counterPartyAddress": "http://dcp-mp-operations.127.0.0.1.nip.io:8080/api/dsp/2025-1",
"counterPartyId": "did:web:mp-operations.org",
"protocol": "dataspace-protocol-http:2025-1",
"policy": {
"@context": "http://www.w3.org/ns/odrl.jsonld",
"@type": "Offer",
"@id": "OFFER-1:ASSET-1:123",
"assigner": "did:web:mp-operations.org",
"permission": [{
"action": "use",
"constraint": {
"leftOperand": "odrl:dayOfWeek",
"operator": "lt",
"rightOperand": 6
}
}],
"target": "ASSET-1"
}
}' | jq .- Check the negotiation state:
curl -X POST \
'http://dsp-dcp-management.127.0.0.1.nip.io:8080/api/v1/management/v3/contractnegotiations/request' \
--header 'Accept: */*' \
--header 'Content-Type: application/json' | jq .- When the state of the negotiation is "finalized", the agreement id can be retrieved:
export AGREEMENT_ID=$(curl -X POST \
'http://dsp-dcp-management.127.0.0.1.nip.io:8080/api/v1/management/v3/contractnegotiations/request' \
--header 'Accept: */*' \
--header 'Content-Type: application/json' | jq -r '.[0].contractAgreementId'); echo ${AGREEMENT_ID}- With the aggreement, the transfer can be started:
curl -X POST \
'http://dsp-dcp-management.127.0.0.1.nip.io:8080/api/v1/management/v3/transferprocesses' \
--header 'Accept: */*' \
--header 'Content-Type: application/json' \
--data-raw "{
\"@context\": [
\"https://w3id.org/edc/connector/management/v0.0.1\"
],
\"assetId\": \"ASSET-1\",
\"counterPartyId\": \"did:web:mp-operations.org\",
\"counterPartyAddress\": \"http://dcp-mp-operations.127.0.0.1.nip.io:8080/api/dsp/2025-1\",
\"connectorId\": \"did:web:mp-operations.org\",
\"contractId\": \"${AGREEMENT_ID}\",
\"protocol\": \"dataspace-protocol-http:2025-1\",
\"transferType\": \"HttpData-PULL\"
}" | jq .- Retrieve the transfer state:
curl -X POST \
'http://dsp-dcp-management.127.0.0.1.nip.io:8080/api/v1/management/v3/transferprocesses/request' \
--header 'Accept: */*' \
--header 'Content-Type: application/json' \
--data-raw '{
"@context": ["https://w3id.org/edc/connector/management/v0.0.1"],
"@type": "QuerySpec"
}' | jq .- Get the transfer Id and use it for retrieving endpoint-url and access-token:
export TRANSFER_ID=$(curl -s -X POST \
'http://dsp-dcp-management.127.0.0.1.nip.io:8080/api/v1/management/v3/transferprocesses/request' \
--header 'Accept: */*' \
--header 'Content-Type: application/json' \
--data-raw '{
"@context": ["https://w3id.org/edc/connector/management/v0.0.1"],
"@type": "QuerySpec"
}' | jq -r '.[]."@id"'); echo Transfer ID: ${TRANSFER_ID}
export ENDPOINT=$(curl -s -X GET "http://dsp-dcp-management.127.0.0.1.nip.io:8080/api/v1/management/v3/edrs/${TRANSFER_ID}/dataaddress" | jq -r .endpoint); echo Endpoint: ${ENDPOINT}
export ACCESS_TOKEN=$(curl -s -X GET "http://dsp-dcp-management.127.0.0.1.nip.io:8080/api/v1/management/v3/edrs/${TRANSFER_ID}/dataaddress" | jq -r .token); echo Access Token: ${ACCESS_TOKEN}- Access the service:
curl -s -x localhost:8888 -X GET ${ENDPOINT}/ngsi-ld/v1/entities/urn:ngsi-ld:UptimeReport:fms-1 \
--header 'Content-Type: application/json' \
--header "Authorization: Bearer ${ACCESS_TOKEN}" | jq .The exact same process can be done through connectors authenticating via OID4VC. In order to keep it simple, we will not run the full negotiation process again, since an agreement can be used independetly from the protocol it was negotiated under. However, the catalog will be read and the transfer process will be setup using OID4VC. In order to use OID4VC, the management API of the OID4VC instance of the controlplane has to be used.
- Read the catalog:
curl -s -X POST \
'http://dsp-oid4vc-management.127.0.0.1.nip.io:8080/api/v1/management/v3/catalog/request' \
--header 'Accept: */*' \
--header 'Content-Type: application/json' \
--data-raw '{
"@context": [
"https://w3id.org/edc/connector/management/v0.0.1"
],
"@type": "CatalogRequestMessage",
"protocol": "dataspace-protocol-http:2025-1",
"counterPartyId": "did:web:mp-operations.org",
"counterPartyAddress": "http://dsp-mp-operations.127.0.0.1.nip.io:8080/api/dsp/2025-1",
"querySpec": {
}
}' | jq .- Request the transfer with the Agreement created via DCP:
curl -X POST \
'http://dsp-oid4vc-management.127.0.0.1.nip.io:8080/api/v1/management/v3/transferprocesses' \
--header 'Accept: */*' \
--header 'Content-Type: application/json' \
--data-raw "{
\"@context\": [
\"https://w3id.org/edc/connector/management/v0.0.1\"
],
\"assetId\": \"ASSET-1\",
\"counterPartyId\": \"did:web:mp-operations.org\",
\"counterPartyAddress\": \"http://dsp-mp-operations.127.0.0.1.nip.io:8080/api/dsp/2025-1\",
\"connectorId\": \"did:web:mp-operations.org\",
\"contractId\": \"${AGREEMENT_ID}\",
\"dataDestination\": {
\"type\": \"HttpProxy\"
},
\"protocol\": \"dataspace-protocol-http:2025-1\",
\"transferType\": \"HttpData-PULL\"
}" | jq .- Get the transfer state:
curl -X POST \
'http://dsp-oid4vc-management.127.0.0.1.nip.io:8080/api/v1/management/v3/transferprocesses/request' \
--header 'Accept: */*' \
--header 'Content-Type: application/json' \
--data-raw '{
"@context": ["https://w3id.org/edc/connector/management/v0.0.1"],
"@type": "QuerySpec"
}' | jq .- Retrieve the endpoint:
export TRANSFER_ID=$(curl -X POST \
'http://dsp-oid4vc-management.127.0.0.1.nip.io:8080/api/v1/management/v3/transferprocesses/request' \
--header 'Accept: */*' \
--header 'Content-Type: application/json' \
--data-raw '{
"@context": ["https://w3id.org/edc/connector/management/v0.0.1"],
"@type": "QuerySpec"
}' | jq -r '.[]."@id"'); echo ${TRANSFER_ID}
export ENDPOINT=$(curl -X GET "http://dsp-oid4vc-management.127.0.0.1.nip.io:8080/api/v1/management/v3/edrs/${TRANSFER_ID}/dataaddress" | jq -r .endpoint); echo ${ENDPOINT}- Request without token fails:
curl -x localhost:8888 -X GET ${ENDPOINT}/ngsi-ld/v1/entities/urn:ngsi-ld:UptimeReport:fms-1- Request openid-configuration:
curl -x localhost:8888 -X GET ${ENDPOINT}/.well-known/openid-configuration | jq .- Access via OID4VP:
export MEMBERSHIP_CREDENTIAL=$(./doc/scripts/get_credential.sh https://keycloak-consumer.127.0.0.1.nip.io membership-credential employee)
export ACCESS_TOKEN=$(./doc/scripts/get_access_token_oid4vp.sh ${ENDPOINT} $MEMBERSHIP_CREDENTIAL openid); echo Access Token: $ACCESS_TOKEN
curl -x localhost:8888 -X GET ${ENDPOINT}/ngsi-ld/v1/entities/urn:ngsi-ld:UptimeReport:fms-1 \
--header 'Content-Type: application/json' \
--header "Authorization: Bearer ${ACCESS_TOKEN}" | jq .


