EDC Connector API
Concepts

Contracts

Understanding Contract Definitions, Negotiations, and Agreements in the sovity EDC

Contracts

Contracts are the foundation of data exchange in the EDC. They define the terms under which data can be accessed and used. The contract system consists of three key components: Contract Definitions, Contract Negotiations, and Contract Agreements.

Contract Lifecycle

┌───────────────────┐     ┌───────────────────┐     ┌───────────────────┐
│ Contract          │     │ Contract          │     │ Contract          │
│ Definition        │ ──▶ │ Negotiation       │ ──▶ │ Agreement         │
│ (Provider creates)│     │ (Both parties)    │     │ (Binding contract)│
└───────────────────┘     └───────────────────┘     └───────────────────┘
        │                         │                         │
        │                         │                         │
        ▼                         ▼                         ▼
  Makes asset visible       Consumer initiates       Enables data
  in the catalog            from catalog offer       transfer

Contract Definitions

A Contract Definition links an Asset to policies, making it available in the catalog for consumers to discover.

Structure

FieldRequiredDescription
contractDefinitionIdYesUnique identifier
accessPolicyIdYesPolicy that determines who can see the offer
contractPolicyIdYesPolicy that governs data usage after agreement
assetSelectorYesCriteria to select which assets this definition applies to

Asset Selectors

Asset selectors use UiCriterion objects to filter which assets are included:

interface UiCriterion {
  operandLeft: string;   // Property to match
  operator: 'EQ' | 'IN' | 'LIKE';  // Comparison operator
  operandRight: UiCriterionLiteral;  // Value(s) to match
}

interface UiCriterionLiteral {
  type: 'VALUE' | 'VALUE_LIST';
  value?: string;        // For single value
  valueList?: string[];  // For multiple values (with IN operator)
}

Selector Examples

Select a specific asset by ID:

const selector = [{
  operandLeft: 'https://w3id.org/edc/v0.0.1/ns/id',
  operator: 'EQ',
  operandRight: { type: 'VALUE', value: 'museum-collection-2024' }
}];

Select multiple assets:

const selector = [{
  operandLeft: 'https://w3id.org/edc/v0.0.1/ns/id',
  operator: 'IN',
  operandRight: { 
    type: 'VALUE_LIST', 
    valueList: ['asset-1', 'asset-2', 'asset-3'] 
  }
}];

Select by custom property pattern:

const selector = [{
  operandLeft: 'dataCategory',
  operator: 'LIKE',
  operandRight: { type: 'VALUE', value: 'Cultural%' }
}];

Creating a Contract Definition

Endpoint: POST /wrapper/ui/pages/contract-definition-page/contract-definitions

cURL Example

curl -X POST "https://api.your-connector-instance.prod.truzztbox.eu/api/management/wrapper/ui/pages/contract-definition-page/contract-definitions" \
  -H "Content-Type: application/json" \
  -H "X-Api-Key: your-api-key" \
  -d '{
    "contractDefinitionId": "museum-data-contract",
    "accessPolicyId": "public-access-policy",
    "contractPolicyId": "research-usage-policy",
    "assetSelector": [
      {
        "operandLeft": "https://w3id.org/edc/v0.0.1/ns/id",
        "operator": "EQ",
        "operandRight": {
          "type": "VALUE",
          "value": "museum-collection-2024"
        }
      }
    ]
  }'

TypeScript Example

interface ContractDefinitionRequest {
  contractDefinitionId: string;
  accessPolicyId: string;
  contractPolicyId: string;
  assetSelector: UiCriterion[];
}

const contractDefinition: ContractDefinitionRequest = {
  contractDefinitionId: 'museum-data-contract',
  accessPolicyId: 'public-access-policy',
  contractPolicyId: 'research-usage-policy',
  assetSelector: [
    {
      operandLeft: 'https://w3id.org/edc/v0.0.1/ns/id',
      operator: 'EQ',
      operandRight: { type: 'VALUE', value: 'museum-collection-2024' },
    },
  ],
};

const response = await fetch(
  'https://api.your-connector-instance.prod.truzztbox.eu/api/management/wrapper/ui/pages/contract-definition-page/contract-definitions',
  {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'X-Api-Key': 'your-api-key',
    },
    body: JSON.stringify(contractDefinition),
  }
);

const result = await response.json();
console.log('Created contract definition:', result.id);

Listing Contract Definitions

Endpoint: GET /wrapper/ui/pages/contract-definition-page

interface ContractDefinitionPage {
  contractDefinitions: ContractDefinitionEntry[];
}

interface ContractDefinitionEntry {
  contractDefinitionId: string;
  accessPolicyId: string;
  contractPolicyId: string;
  assetSelector: UiCriterion[];
}

const response = await fetch(
  'https://api.your-connector-instance.prod.truzztbox.eu/api/management/wrapper/ui/pages/contract-definition-page',
  { headers: { 'X-Api-Key': 'your-api-key' } }
);

const page: ContractDefinitionPage = await response.json();
console.log(`Found ${page.contractDefinitions.length} contract definitions`);

Deleting a Contract Definition

Endpoint: DELETE /wrapper/ui/pages/contract-definition-page/contract-definitions/{contractDefinitionId}

curl -X DELETE "https://api.your-connector-instance.prod.truzztbox.eu/api/management/wrapper/ui/pages/contract-definition-page/contract-definitions/museum-data-contract" \
  -H "X-Api-Key: your-api-key"

Contract Negotiations

A Contract Negotiation is the process where a consumer and provider agree on the terms for data access. Negotiations are initiated by the consumer after discovering an offer in the catalog.

Negotiation States

The sovity EDC provides simplified states for easier handling:

Simplified StateDescription
IN_PROGRESSNegotiation is ongoing
AGREEDBoth parties have agreed, contract is established
TERMINATEDNegotiation was terminated (rejected or failed)

Initiating a Negotiation

Endpoint: POST /wrapper/ui/pages/catalog-page/contract-negotiations

After fetching data offers from the catalog, you can initiate a negotiation:

cURL Example

curl -X POST "https://api.your-connector-instance.prod.truzztbox.eu/api/management/wrapper/ui/pages/catalog-page/contract-negotiations" \
  -H "Content-Type: application/json" \
  -H "X-Api-Key: your-api-key" \
  -d '{
    "counterPartyId": "provider-participant-id",
    "counterPartyAddress": "https://provider-connector.example.com/api/dsp",
    "contractOfferId": "contract-offer-id-from-catalog",
    "assetId": "museum-collection-2024",
    "policyJsonLd": "{\"@type\": \"Set\", ...}"
  }'

TypeScript Example

interface ContractNegotiationRequest {
  counterPartyId: string;
  counterPartyAddress: string;
  contractOfferId: string;
  assetId: string;
  policyJsonLd: string;
}

// Values typically come from a catalog data offer
const negotiationRequest: ContractNegotiationRequest = {
  counterPartyId: dataOffer.participantId,
  counterPartyAddress: dataOffer.endpoint,
  contractOfferId: dataOffer.contractOffers[0].contractOfferId,
  assetId: dataOffer.asset.assetId,
  policyJsonLd: dataOffer.contractOffers[0].policy.policyJsonLd,
};

const response = await fetch(
  'https://api.your-connector-instance.prod.truzztbox.eu/api/management/wrapper/ui/pages/catalog-page/contract-negotiations',
  {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'X-Api-Key': 'your-api-key',
    },
    body: JSON.stringify(negotiationRequest),
  }
);

const negotiation: UiContractNegotiation = await response.json();
console.log('Negotiation started:', negotiation.contractNegotiationId);
console.log('State:', negotiation.state.simplifiedState);

Checking Negotiation Status

Endpoint: GET /wrapper/ui/pages/catalog-page/contract-negotiations/{contractNegotiationId}

interface UiContractNegotiation {
  contractNegotiationId: string;
  createdAt: string;
  contractAgreementId?: string;  // Only set when AGREED
  state: {
    name: string;
    code: number;
    simplifiedState: 'IN_PROGRESS' | 'AGREED' | 'TERMINATED';
  };
}

async function waitForAgreement(negotiationId: string): Promise<string> {
  while (true) {
    const response = await fetch(
      `https://api.your-connector-instance.prod.truzztbox.eu/api/management/wrapper/ui/pages/catalog-page/contract-negotiations/${negotiationId}`,
      { headers: { 'X-Api-Key': 'your-api-key' } }
    );
    
    const negotiation: UiContractNegotiation = await response.json();
    
    if (negotiation.state.simplifiedState === 'AGREED') {
      return negotiation.contractAgreementId!;
    }
    
    if (negotiation.state.simplifiedState === 'TERMINATED') {
      throw new Error('Negotiation was terminated');
    }
    
    // Wait before polling again
    await new Promise(resolve => setTimeout(resolve, 1000));
  }
}

Contract Agreements

A Contract Agreement is the finalized, binding contract between provider and consumer. Once established, it enables data transfers.

Agreement Structure

interface ContractAgreementCard {
  contractAgreementId: string;
  contractNegotiationId: string;
  direction: 'CONSUMING' | 'PROVIDING';
  counterPartyAddress: string;
  counterPartyId: string;
  contractSigningDate: string;
  asset: UiAsset;
  contractPolicy: UiPolicy;
  transferProcesses: ContractAgreementTransferProcess[];
  terminationStatus: 'ONGOING' | 'TERMINATED';
  terminationInformation?: ContractAgreementTerminationInfo;
}

Listing Contract Agreements

Endpoint: POST /wrapper/ui/pages/contract-agreement-page

You can optionally filter by termination status:

cURL Example (All Agreements)

curl -X POST "https://api.your-connector-instance.prod.truzztbox.eu/api/management/wrapper/ui/pages/contract-agreement-page" \
  -H "Content-Type: application/json" \
  -H "X-Api-Key: your-api-key" \
  -d '{}'

cURL Example (Only Active)

curl -X POST "https://api.your-connector-instance.prod.truzztbox.eu/api/management/wrapper/ui/pages/contract-agreement-page" \
  -H "Content-Type: application/json" \
  -H "X-Api-Key: your-api-key" \
  -d '{"terminationStatus": "ONGOING"}'

TypeScript Example

interface ContractAgreementPageQuery {
  terminationStatus?: 'ONGOING' | 'TERMINATED';
}

interface ContractAgreementPage {
  contractAgreements: ContractAgreementCard[];
}

// Get all active agreements
const response = await fetch(
  'https://api.your-connector-instance.prod.truzztbox.eu/api/management/wrapper/ui/pages/contract-agreement-page',
  {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'X-Api-Key': 'your-api-key',
    },
    body: JSON.stringify({ terminationStatus: 'ONGOING' }),
  }
);

const page: ContractAgreementPage = await response.json();

// Separate by direction
const consuming = page.contractAgreements.filter(a => a.direction === 'CONSUMING');
const providing = page.contractAgreements.filter(a => a.direction === 'PROVIDING');

console.log(`Consuming ${consuming.length} data offers`);
console.log(`Providing ${providing.length} data offers`);

Getting a Single Agreement

Endpoint: GET /wrapper/ui/pages/contract-agreement-page/{contractAgreementId}

const response = await fetch(
  'https://api.your-connector-instance.prod.truzztbox.eu/api/management/wrapper/ui/pages/contract-agreement-page/agreement-123',
  { headers: { 'X-Api-Key': 'your-api-key' } }
);

const agreement: ContractAgreementCard = await response.json();
console.log('Asset:', agreement.asset.title);
console.log('Counter Party:', agreement.counterPartyId);
console.log('Signed:', agreement.contractSigningDate);
console.log('Transfers:', agreement.transferProcesses.length);

Terminating a Contract Agreement

Endpoint: POST /wrapper/ui/pages/content-agreement-page/{contractAgreementId}/terminate

Terminating a contract agreement is irreversible. All ongoing transfers will be stopped and no new transfers can be initiated.

cURL Example

curl -X POST "https://api.your-connector-instance.prod.truzztbox.eu/api/management/wrapper/ui/pages/content-agreement-page/agreement-123/terminate" \
  -H "Content-Type: application/json" \
  -H "X-Api-Key: your-api-key" \
  -d '{
    "reason": "Contract period ended",
    "detail": "The agreed contract period of 12 months has ended. Please contact us to renew the agreement if you wish to continue accessing the data."
  }'

TypeScript Example

interface ContractTerminationRequest {
  reason: string;  // Max 100 characters
  detail: string;  // Max 1000 characters
}

const termination: ContractTerminationRequest = {
  reason: 'Contract period ended',
  detail: 'The agreed contract period of 12 months has ended.',
};

await fetch(
  'https://api.your-connector-instance.prod.truzztbox.eu/api/management/wrapper/ui/pages/content-agreement-page/agreement-123/terminate',
  {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'X-Api-Key': 'your-api-key',
    },
    body: JSON.stringify(termination),
  }
);

Termination Information

When an agreement is terminated, the terminationInformation field contains details:

interface ContractAgreementTerminationInfo {
  terminatedAt: string;
  reason: string;
  detail: string;
  terminatedBy: 'SELF' | 'COUNTERPARTY';
}

// Check who terminated
if (agreement.terminationStatus === 'TERMINATED') {
  const info = agreement.terminationInformation!;
  if (info.terminatedBy === 'COUNTERPARTY') {
    console.log(`Terminated by provider: ${info.reason}`);
  } else {
    console.log(`Terminated by us: ${info.reason}`);
  }
}

Complete Workflow

Provider Workflow

// 1. Create a policy (see Policies documentation)
const policyId = await createPolicy();

// 2. Create an asset (see Assets documentation)
const assetId = await createAsset();

// 3. Create a contract definition linking asset and policy
const contractDef = await fetch(
  'https://api.your-connector-instance.prod.truzztbox.eu/api/management/wrapper/ui/pages/contract-definition-page/contract-definitions',
  {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'X-Api-Key': 'your-api-key',
    },
    body: JSON.stringify({
      contractDefinitionId: `${assetId}-contract`,
      accessPolicyId: policyId,
      contractPolicyId: policyId,
      assetSelector: [{
        operandLeft: 'https://w3id.org/edc/v0.0.1/ns/id',
        operator: 'EQ',
        operandRight: { type: 'VALUE', value: assetId },
      }],
    }),
  }
);

// Asset is now visible in the catalog!

Consumer Workflow

// 1. Fetch data offers from provider (see Catalog documentation)
const offers = await fetchCatalogOffers(providerEndpoint);

// 2. Select an offer and initiate negotiation
const selectedOffer = offers[0];
const negotiation = await initiateNegotiation(selectedOffer);

// 3. Wait for agreement
const agreementId = await waitForAgreement(negotiation.contractNegotiationId);

// 4. Initiate transfer (see Transfer Process documentation)
await initiateTransfer(agreementId);

Best Practices

  1. Use descriptive IDs: Make contract definition IDs meaningful (e.g., museum-research-access-2024)
  2. Separate policies: Use different access and contract policies for flexibility
  3. Monitor negotiations: Poll negotiation status or implement webhooks
  4. Track agreements: Keep records of all contract agreements for compliance
  5. Handle terminations: Implement proper cleanup when agreements are terminated
  6. Validate before creation: Use the validation endpoints to check ID availability

Next Steps

On this page