Protocol Backward Compatibility Guide
The SDK supports two dataspace protocol generations — Jupiter ("dataspace-protocol-http") and Saturn ("dataspace-protocol-http:2025-1"). They are not wire-compatible: an EDC running Jupiter cannot talk to one running Saturn without a protocol bridge. Code that targets both generations must branch on the dataspace_version.
This guide summarises every breaking difference and the exact code pattern to handle each one.
Protocol Version at a Glance
| Jupiter | Saturn | |
|---|---|---|
| Target EDC | 0.8.x – 0.10.x | 0.11.x+ |
dataspace_version string |
"jupiter" |
"saturn" |
| DSP protocol string | "dataspace-protocol-http" |
"dataspace-protocol-http:2025-1" |
BPNL discovery (_with_bpnl methods) |
Not available | Built-in |
| DID discovery | Not available | Built-in |
connector_discovery controller |
Not present | Present |
| Policy JSON-LD context | Tractus-X v1.0.0 + W3C ODRL | Catena-X 2025-9 |
| Access policy left operand | tx:BusinessPartnerNumber |
BusinessPartnerNumber |
Negotiation @context |
Tractus-X + W3C ODRL URLs | Catena-X 2025-9 URLs |
Recommended Strategy for Backward Compatibility
Best Practice: Use _with_bpnl Methods
We strongly recommend using the _with_bpnl variants (get_catalog_by_dct_type_with_bpnl, do_get_with_bpnl, do_post_with_bpnl, do_dsp_with_bpnl) whenever possible. These methods provide automatic backward compatibility because:
- They work seamlessly on Saturn (EDC 0.11.x+) with built-in BPNL discovery
- They gracefully handle Jupiter (EDC 0.8–0.10.x) by requiring manual DSP URL configuration
- Your code remains version-agnostic and future-proof
Example:
Version-Specific Requirements
If you choose to use version-specific methods (without _with_bpnl), you must adhere to strict parameter requirements:
Jupiter (EDC 0.8–0.10.x):
- MUST use
counter_party_idwith a BPN value - MUST provide
counter_party_address(DSP URL) explicitly - No automatic discovery available
Saturn (EDC 0.11.x+):
- MUST use
counter_party_idwith a DID value (W3C Decentralized Identifier) counter_party_addressis optional (resolved via DID document if omitted)- Supports both DID-based and BPNL-based discovery
Example (version-specific):
1. Counter-Party Resolution — _with_bpnl Methods (Recommended)
Recommended Approach
Always use _with_bpnl methods for maximum backward compatibility. These methods work on both Jupiter and Saturn, eliminating the need for version-specific branching in your code.
Saturn introduces _with_bpnl variants of every catalog and DSP method. These methods automatically query the Connector Discovery Service to resolve the provider's DSP URL from a BPNL, so the caller only needs to know the partner's BPN.
Available _with_bpnl methods:
get_catalog_by_dct_type_with_bpnl()do_get_with_bpnl()do_post_with_bpnl()do_put_with_bpnl()do_patch_with_bpnl()do_delete_with_bpnl()do_dsp_with_bpnl()
Version-Specific Parameter Requirements
If using version-specific methods (not recommended)
When not using _with_bpnl methods, you must follow these strict rules:
- Jupiter:
counter_party_idMUST be a BPN (Business Partner Number) - Saturn:
counter_party_idMUST be a DID (W3C Decentralized Identifier)
# Works on both Jupiter and Saturn — no version branching needed
catalog = consumer.get_catalog_by_dct_type_with_bpnl(
bpnl="BPNL00000003AYRE",
dct_type="https://w3id.org/catenax/taxonomy#Submodel",
)
response = consumer.do_get_with_bpnl(
bpnl="BPNL00000003AYRE",
filter_expression=[consumer.get_filter_expression(
key="https://w3id.org/edc/v0.0.1/ns/id",
value="urn:uuid:my-asset",
)],
)
# Jupiter: counter_party_id MUST be a BPN
# No discovery — caller must know the DSP URL
catalog = consumer.get_catalog_by_dct_type(
counter_party_id="BPNL00000003AYRE", # BPN required
counter_party_address="http://provider.example.com/api/v1/dsp",
dct_type="https://w3id.org/catenax/taxonomy#Submodel",
)
response = consumer.do_get(
counter_party_id="BPNL00000003AYRE", # BPN required
counter_party_address="http://provider.example.com/api/v1/dsp",
filter_expression=[...],
protocol="dataspace-protocol-http",
)
# Saturn: counter_party_id MUST be a DID
# DSP URL resolved automatically from DID document
catalog = consumer.get_catalog_by_dct_type(
counter_party_id="did:web:provider.example.com", # DID required
counter_party_address="http://provider.example.com/api/v1/dsp/2025-1",
dct_type="https://w3id.org/catenax/taxonomy#Submodel",
)
response = consumer.do_get(
counter_party_id="did:web:provider.example.com", # DID required
counter_party_address="http://provider.example.com/api/v1/dsp/2025-1",
filter_expression=[...],
protocol="dataspace-protocol-http:2025-1",
)
2. Negotiation Protocol String
When calling start_edr_negotiation() directly (i.e. outside the high-level do_get / do_dsp helpers), you must pass the correct protocol string for the target EDC version. The helpers and runners handle this automatically.
3. Policy JSON-LD Context
The negotiation @context and the policy @context changed between versions. Using the wrong context causes the EDC to reject the negotiation request with a 400 error.
Negotiation context (context parameter of start_edr_negotiation / get_edr_negotiation_request)
NEGOTIATION_CONTEXT = {
"jupiter": [
"https://w3id.org/tractusx/policy/v1.0.0",
"http://www.w3.org/ns/odrl.jsonld",
{"@vocab": "https://w3id.org/edc/v0.0.1/ns/"},
],
"saturn": [
"https://w3id.org/catenax/2025/9/policy/odrl.jsonld",
"https://w3id.org/catenax/2025/9/policy/context.jsonld",
{"@vocab": "https://w3id.org/edc/v0.0.1/ns/"},
],
}
negotiation_id = consumer.start_edr_negotiation(
...
context=NEGOTIATION_CONTEXT[consumer.dataspace_version],
)
4. Access Policy Permissions — Left Operand
The BPNL constraint left operand changed between versions. Using the wrong operand causes the EDC to create the policy successfully but fail to evaluate it at negotiation time.
# Jupiter uses the Tractus-X namespace prefix: tx:BusinessPartnerNumber
access_policy_permissions = [{
"action": "use",
"constraint": {
"leftOperand": "tx:BusinessPartnerNumber", # Jupiter
"operator": "eq",
"rightOperand": "BPNL00000003AZQP", # consumer BPN
},
}]
# Policy @context must include the tx: namespace
policy_context = [
"https://w3id.org/tractusx/policy/v1.0.0",
"http://www.w3.org/ns/odrl.jsonld",
{
"tx": "https://w3id.org/tractusx/v0.0.1/ns/",
"@vocab": "https://w3id.org/edc/v0.0.1/ns/",
},
]
# Saturn uses the bare operand — no namespace prefix
access_policy_permissions = [{
"action": "access",
"constraint": {
"leftOperand": "BusinessPartnerNumber", # Saturn
"operator": "isAnyOf",
"rightOperand": "BPNL00000003AZQP",
},
}]
# Policy @context uses Catena-X 2025-9 URLs (context is often implicit)
policy_context = [
"https://w3id.org/catenax/2025/9/policy/odrl.jsonld",
"https://w3id.org/catenax/2025/9/policy/context.jsonld",
{"@vocab": "https://w3id.org/edc/v0.0.1/ns/"},
]
def build_access_policy(version: str, consumer_bpn: str) -> dict:
if version == "saturn":
return {
"permissions": [{
"action": "access",
"constraint": {
"leftOperand": "BusinessPartnerNumber",
"operator": "isAnyOf",
"rightOperand": consumer_bpn,
},
}],
"context": None, # Saturn: let provider.create_policy use its default
}
else:
return {
"permissions": [{
"action": "use",
"constraint": {
"leftOperand": "tx:BusinessPartnerNumber",
"operator": "eq",
"rightOperand": consumer_bpn,
},
}],
"context": [
"https://w3id.org/tractusx/policy/v1.0.0",
"http://www.w3.org/ns/odrl.jsonld",
{
"tx": "https://w3id.org/tractusx/v0.0.1/ns/",
"@vocab": "https://w3id.org/edc/v0.0.1/ns/",
},
],
}
5. High-Level do_get / do_dsp Flow — Recommended Pattern
Recommended: Use _with_bpnl for maximum compatibility
The _with_bpnl methods eliminate version branching and work seamlessly across both Jupiter and Saturn.
Recommended Approach (Version-Agnostic with _with_bpnl)
from tractusx_sdk.dataspace.services.connector import ServiceFactory
def access_data(
version: str,
consumer_url: str,
consumer_api_key: str,
provider_bpn: str,
asset_id: str,
):
consumer = ServiceFactory.get_connector_consumer_service(
dataspace_version=version,
base_url=consumer_url,
dma_path="/management",
headers={"X-Api-Key": consumer_api_key, "Content-Type": "application/json"},
)
filter_expression = [
consumer.get_filter_expression(
key="https://w3id.org/edc/v0.0.1/ns/id",
value=asset_id,
)
]
# Works on both Jupiter and Saturn - no version branching needed!
return consumer.do_get_with_bpnl(
bpnl=provider_bpn,
filter_expression=filter_expression,
)
Alternative Approach (Manual Version Branching)
If you cannot use _with_bpnl methods, you must implement version-specific branching and parameter handling:
from tractusx_sdk.dataspace.services.connector import ServiceFactory
def access_data(
version: str,
consumer_url: str,
consumer_api_key: str,
provider_bpn: str,
provider_dsp_url: str, # required for Jupiter; optional for Saturn
asset_id: str,
):
PROTOCOL = {
"jupiter": "dataspace-protocol-http",
"saturn": "dataspace-protocol-http:2025-1",
}
consumer = ServiceFactory.get_connector_consumer_service(
dataspace_version=version,
base_url=consumer_url,
dma_path="/management",
headers={"X-Api-Key": consumer_api_key, "Content-Type": "application/json"},
)
filter_expression = [
consumer.get_filter_expression(
key="https://w3id.org/edc/v0.0.1/ns/id",
value=asset_id,
)
]
if version == "saturn":
# Saturn: counter_party_id MUST be a DID
return consumer.do_get(
counter_party_id="did:web:provider.example.com", # DID required
counter_party_address=provider_dsp_url,
filter_expression=filter_expression,
protocol=PROTOCOL[version],
)
else:
# Jupiter: counter_party_id MUST be a BPN
return consumer.do_get(
counter_party_id=provider_bpn, # BPN required
counter_party_address=provider_dsp_url,
filter_expression=filter_expression,
protocol=PROTOCOL[version],
)
Summary Checklist — Migrating Jupiter → Saturn
When upgrading a connector from Jupiter to Saturn, work through this checklist:
- Recommended: Migrate to
_with_bpnlmethods for all catalog and data access operations - Change
dataspace_versionfrom"jupiter"to"saturn"inServiceFactorycalls - If using version-specific methods (not
_with_bpnl): Updatecounter_party_idfrom BPN to DID format - Update negotiation
contextfrom Tractus-X v1.0.0 + W3C ODRL to Catena-X 2025-9 URLs - Update access policy
leftOperandfromtx:BusinessPartnerNumber→BusinessPartnerNumber - Update access policy
actionfrom"use"→"access"(Saturn access policies use"access") - Update the DSP
protocolstring from"dataspace-protocol-http"→"dataspace-protocol-http:2025-1" - Update the provider DSP URL if it has a version suffix (e.g.
/api/v1/dsp/2025-1instead of/api/v1/dsp) - Remove explicit
tx:namespace entries from policy@context(no longer needed in Saturn) - Verify that usage policy constraints (Membership, FrameworkAgreement, UsagePurpose) match the Catena-X 2025-9 operand vocabulary
Use the TCK to validate
Run the Connector TCK against your connector after each migration step. The simple and detailed Saturn scripts confirm the full negotiate → transfer → data-access flow end-to-end.
DID-based discovery
Saturn also supports DID-based discovery (no BPNL lookup, uses a W3C DID document to resolve the counterparty DSP endpoint). See the Saturn API Reference for discover_connector_protocol() and get_discovery_info().
NOTICE
This work is licensed under the CC-BY-4.0.
- SPDX-License-Identifier: CC-BY-4.0
- SPDX-FileCopyrightText: 2025, 2026 Contributors to the Eclipse Foundation
- SPDX-FileCopyrightText: 2025, 2026 Catena-X Automotive Network e.V.
- SPDX-FileCopyrightText: 2025, 2026 LKS Next
- SPDX-FileCopyrightText: 2025, 2026 Mondragon Unibertsitatea
- Source URL: https://github.com/eclipse-tractusx/tractusx-sdk