EDC Connector API
Concepts

Policies

Understanding Policy Definitions in the EDC Connector

Policies

Policies in the EDC define the rules and constraints for data access and usage. They are based on the ODRL (Open Digital Rights Language) standard and provide a flexible way to express permissions, prohibitions, and obligations.

The sovity EDC UI API provides a type-safe, simplified interface for creating and managing policies.

Policy Types

The EDC uses two types of policies in Contract Definitions:

TypePurpose
Access PolicyDetermines who can see an offer in the catalog
Contract PolicyGoverns the actual usage of the data after agreement

UiPolicyExpression

The sovity UI API uses UiPolicyExpression to represent policies in a type-safe, hierarchical structure:

interface UiPolicyExpression {
  type: 'EMPTY' | 'CONSTRAINT' | 'AND' | 'OR' | 'XONE';
  expressions?: UiPolicyExpression[];  // For AND, OR, XONE
  constraint?: UiPolicyConstraint;      // For CONSTRAINT
}

interface UiPolicyConstraint {
  left: string;                         // Left operand (e.g., "region")
  operator: OperatorDto;                // Comparison operator
  right: UiPolicyLiteral;               // Right operand value
}

interface UiPolicyLiteral {
  type: 'STRING' | 'STRING_LIST' | 'JSON';
  value?: string;                       // For STRING and JSON
  valueList?: string[];                 // For STRING_LIST
}

type OperatorDto = 
  | 'EQ' | 'NEQ'           // Equality
  | 'GT' | 'GEQ'           // Greater than
  | 'LT' | 'LEQ'           // Less than
  | 'IN'                   // In list
  | 'HAS_PART' | 'IS_A'    // Containment
  | 'IS_ALL_OF' | 'IS_ANY_OF' | 'IS_NONE_OF';  // Set operations

Expression Types

TypeDescriptionExample
EMPTYAlways evaluates to true (no constraints)Open/unrestricted access
CONSTRAINTSingle condition (a = b)region EQ "EU"
ANDAll sub-expressions must be trueRegion EU AND purpose research
ORAt least one sub-expression must be trueRegion EU OR region US
XONEExactly one sub-expression must be trueExclusive choice

Policy Definition Page

Retrieve all policy definitions:

curl -X GET "https://api.your-connector-instance.prod.truzztbox.eu/api/management/wrapper/ui/pages/policy-page" \
  -H "Content-Type: application/json" \
  -H "X-Api-Key: your-api-key"
const BASE_URL = 'https://api.your-connector-instance.prod.truzztbox.eu/api/management';

interface UiPolicy {
  policyJsonLd: string;
  expression?: UiPolicyExpression;
  errors: string[];
}

interface PolicyDefinitionDto {
  policyDefinitionId: string;
  policy: UiPolicy;
}

interface PolicyDefinitionPage {
  policies: PolicyDefinitionDto[];
}

async function getPolicyDefinitions(): Promise<PolicyDefinitionPage> {
  const response = await fetch(`${BASE_URL}/wrapper/ui/pages/policy-page`, {
    headers: {
      'Content-Type': 'application/json',
      'X-Api-Key': 'your-api-key',
    },
  });

  if (!response.ok) {
    throw new Error(`Failed to get policies: ${response.statusText}`);
  }

  return response.json();
}

const page = await getPolicyDefinitions();
console.log('Total policies:', page.policies.length);
page.policies.forEach((p) => {
  console.log(`- ${p.policyDefinitionId}: ${p.policy.expression?.type}`);
});

Creating Policy Definitions

The v2 API uses UiPolicyExpression for full flexibility:

# Create policy with AND expression
curl -X POST "https://api.your-connector-instance.prod.truzztbox.eu/api/management/wrapper/ui/v2/pages/policy-page/policy-definitions" \
  -H "Content-Type: application/json" \
  -H "X-Api-Key: your-api-key" \
  -d '{
    "policyDefinitionId": "cultural-research-policy",
    "expression": {
      "type": "AND",
      "expressions": [
        {
          "type": "CONSTRAINT",
          "constraint": {
            "left": "purpose",
            "operator": "EQ",
            "right": {
              "type": "STRING",
              "value": "research"
            }
          }
        },
        {
          "type": "CONSTRAINT",
          "constraint": {
            "left": "participantType",
            "operator": "IN",
            "right": {
              "type": "STRING_LIST",
              "valueList": ["museum", "university", "archive"]
            }
          }
        }
      ]
    }
  }'
interface PolicyDefinitionCreateDto {
  policyDefinitionId: string;
  expression: UiPolicyExpression;
}

async function createPolicyV2(
  policyDefinitionId: string,
  expression: UiPolicyExpression
): Promise<{ id: string; lastUpdatedDate: string }> {
  const request: PolicyDefinitionCreateDto = {
    policyDefinitionId,
    expression,
  };

  const response = await fetch(
    `${BASE_URL}/wrapper/ui/v2/pages/policy-page/policy-definitions`,
    {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'X-Api-Key': 'your-api-key',
      },
      body: JSON.stringify(request),
    }
  );

  if (!response.ok) {
    throw new Error(`Failed to create policy: ${response.statusText}`);
  }

  return response.json();
}

// Example: Create an unrestricted (always-true) policy
const unrestrictedPolicy = await createPolicyV2('open-access-policy', {
  type: 'EMPTY',
});

// Example: Create a simple constraint policy
const regionPolicy = await createPolicyV2('eu-only-policy', {
  type: 'CONSTRAINT',
  constraint: {
    left: 'region',
    operator: 'EQ',
    right: { type: 'STRING', value: 'EU' },
  },
});

// Example: Create an AND policy with multiple constraints
const researchPolicy = await createPolicyV2('academic-research-policy', {
  type: 'AND',
  expressions: [
    {
      type: 'CONSTRAINT',
      constraint: {
        left: 'purpose',
        operator: 'EQ',
        right: { type: 'STRING', value: 'research' },
      },
    },
    {
      type: 'CONSTRAINT',
      constraint: {
        left: 'participantType',
        operator: 'IN',
        right: {
          type: 'STRING_LIST',
          valueList: ['university', 'research-institute'],
        },
      },
    },
  ],
});

// Example: Create an OR policy for multiple regions
const multiRegionPolicy = await createPolicyV2('eu-us-policy', {
  type: 'OR',
  expressions: [
    {
      type: 'CONSTRAINT',
      constraint: {
        left: 'region',
        operator: 'EQ',
        right: { type: 'STRING', value: 'EU' },
      },
    },
    {
      type: 'CONSTRAINT',
      constraint: {
        left: 'region',
        operator: 'EQ',
        right: { type: 'STRING', value: 'US' },
      },
    },
  ],
});

V1 API (Deprecated)

The V1 API is deprecated. Use V2 for new implementations. V1 only supports a conjunction (AND) of constraints.

curl -X POST "https://api.your-connector-instance.prod.truzztbox.eu/api/management/wrapper/ui/pages/policy-page/policy-definitions" \
  -H "Content-Type: application/json" \
  -H "X-Api-Key: your-api-key" \
  -d '{
    "policyDefinitionId": "legacy-policy",
    "policy": {
      "constraints": [
        {
          "left": "region",
          "operator": "EQ",
          "right": {
            "type": "STRING",
            "value": "EU"
          }
        }
      ]
    }
  }'
// Deprecated V1 API - only supports AND of constraints
interface PolicyDefinitionCreateRequest {
  policyDefinitionId: string;
  policy: {
    constraints: UiPolicyConstraint[];
  };
}

async function createPolicyV1(
  policyDefinitionId: string,
  constraints: UiPolicyConstraint[]
): Promise<{ id: string; lastUpdatedDate: string }> {
  const request: PolicyDefinitionCreateRequest = {
    policyDefinitionId,
    policy: { constraints },
  };

  const response = await fetch(
    `${BASE_URL}/wrapper/ui/pages/policy-page/policy-definitions`,
    {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'X-Api-Key': 'your-api-key',
      },
      body: JSON.stringify(request),
    }
  );

  if (!response.ok) {
    throw new Error(`Failed to create policy: ${response.statusText}`);
  }

  return response.json();
}

// V1 example (deprecated)
const legacyPolicy = await createPolicyV1('legacy-region-policy', [
  {
    left: 'region',
    operator: 'EQ',
    right: { type: 'STRING', value: 'EU' },
  },
]);

Validate Policy ID Availability

Before creating a policy, check if the ID is available:

curl -X GET "https://api.your-connector-instance.prod.truzztbox.eu/api/management/wrapper/ui/pages/data-offer-page/validate-policy-id/my-new-policy" \
  -H "Content-Type: application/json" \
  -H "X-Api-Key: your-api-key"
interface IdAvailabilityResponse {
  id: string;
  available: boolean;
}

async function isPolicyIdAvailable(policyId: string): Promise<boolean> {
  const response = await fetch(
    `${BASE_URL}/wrapper/ui/pages/data-offer-page/validate-policy-id/${policyId}`,
    {
      headers: {
        'Content-Type': 'application/json',
        'X-Api-Key': 'your-api-key',
      },
    }
  );

  if (!response.ok) {
    throw new Error(`Failed to validate policy ID: ${response.statusText}`);
  }

  const result: IdAvailabilityResponse = await response.json();
  return result.available;
}

const available = await isPolicyIdAvailable('my-new-policy');
if (available) {
  console.log('Policy ID is available');
} else {
  console.log('Policy ID is already taken');
}

Delete Policy Definition

curl -X DELETE "https://api.your-connector-instance.prod.truzztbox.eu/api/management/wrapper/ui/pages/policy-page/policy-definitions/my-policy-id" \
  -H "Content-Type: application/json" \
  -H "X-Api-Key: your-api-key"
async function deletePolicy(
  policyId: string
): Promise<{ id: string; lastUpdatedDate: string }> {
  const response = await fetch(
    `${BASE_URL}/wrapper/ui/pages/policy-page/policy-definitions/${policyId}`,
    {
      method: 'DELETE',
      headers: {
        'Content-Type': 'application/json',
        'X-Api-Key': 'your-api-key',
      },
    }
  );

  if (!response.ok) {
    throw new Error(`Failed to delete policy: ${response.statusText}`);
  }

  return response.json();
}

const deleted = await deletePolicy('old-policy-id');
console.log('Deleted policy:', deleted.id);

You cannot delete a policy that is referenced by a Contract Definition. Remove the Contract Definition first.

Common Policy Patterns

Unrestricted Access

Allow anyone to access the data:

const unrestrictedPolicy: UiPolicyExpression = {
  type: 'EMPTY',
};

await createPolicyV2('open-access', unrestrictedPolicy);

Region-Based Access

Restrict access to specific regions:

const euOnlyPolicy: UiPolicyExpression = {
  type: 'CONSTRAINT',
  constraint: {
    left: 'region',
    operator: 'EQ',
    right: { type: 'STRING', value: 'EU' },
  },
};

await createPolicyV2('eu-only', euOnlyPolicy);

Purpose-Based Access

Restrict by usage purpose:

const researchOnlyPolicy: UiPolicyExpression = {
  type: 'CONSTRAINT',
  constraint: {
    left: 'purpose',
    operator: 'IN',
    right: {
      type: 'STRING_LIST',
      valueList: ['research', 'education', 'preservation'],
    },
  },
};

await createPolicyV2('research-purpose', researchOnlyPolicy);

Time-Limited Access

Restrict access to a time window:

const timeLimitedPolicy: UiPolicyExpression = {
  type: 'AND',
  expressions: [
    {
      type: 'CONSTRAINT',
      constraint: {
        left: 'currentTime',
        operator: 'GEQ',
        right: { type: 'STRING', value: '2024-01-01T00:00:00Z' },
      },
    },
    {
      type: 'CONSTRAINT',
      constraint: {
        left: 'currentTime',
        operator: 'LEQ',
        right: { type: 'STRING', value: '2025-12-31T23:59:59Z' },
      },
    },
  ],
};

await createPolicyV2('time-limited-2024-2025', timeLimitedPolicy);

Complex Multi-Condition Policy

Combine multiple conditions:

const complexPolicy: UiPolicyExpression = {
  type: 'AND',
  expressions: [
    // Must be in EU or US
    {
      type: 'OR',
      expressions: [
        {
          type: 'CONSTRAINT',
          constraint: {
            left: 'region',
            operator: 'EQ',
            right: { type: 'STRING', value: 'EU' },
          },
        },
        {
          type: 'CONSTRAINT',
          constraint: {
            left: 'region',
            operator: 'EQ',
            right: { type: 'STRING', value: 'US' },
          },
        },
      ],
    },
    // Must be for research purpose
    {
      type: 'CONSTRAINT',
      constraint: {
        left: 'purpose',
        operator: 'EQ',
        right: { type: 'STRING', value: 'research' },
      },
    },
    // Must be a verified institution
    {
      type: 'CONSTRAINT',
      constraint: {
        left: 'verified',
        operator: 'EQ',
        right: { type: 'STRING', value: 'true' },
      },
    },
  ],
};

await createPolicyV2('complex-research-policy', complexPolicy);

Using Policies with Quick Data Offer

When creating a data offer with the Quick Data Offer endpoint, you can specify policy constraints inline. The payload uses a flat structure where all fields are at the top level:

// DataOfferCreateRequest uses a flat structure (no nested 'asset' object)
interface DataOfferCreateRequest {
  // Required
  id: string;
  dataSource: UiDataSource;
  
  // Optional metadata
  title?: string;
  description?: string;
  language?: string;  // IDS URI, e.g., "https://w3id.org/idsa/code/DE"
  // ... other metadata fields
  
  // Policy
  publishType?: 'DONT_PUBLISH' | 'PUBLISH_UNRESTRICTED' | 'PUBLISH_RESTRICTED';
  policyExpression?: UiPolicyExpression;  // Only for PUBLISH_RESTRICTED
}

// Create a data offer with restricted access
const dataOfferRequest = {
  // Required fields at top level
  id: 'cultural-dataset-2024',
  dataSource: {
    type: 'HTTP_DATA',
    httpData: {
      baseUrl: 'https://api.museum.example.com/data',
    },
  },
  
  // Metadata at top level (not nested)
  title: 'Cultural Heritage Dataset 2024',
  description: 'Digitized collection metadata from the National Museum',
  language: 'https://w3id.org/idsa/code/DE',
  keywords: ['museum', 'cultural-heritage', 'metadata'],
  
  // DRK-specific fields (optional)
  drkAssetType: 'MUSEUM',
  drkMuseumName: 'Deutsches Nationalmuseum',
  
  // Policy at top level
  publishType: 'PUBLISH_RESTRICTED',
  policyExpression: {
    type: 'AND',
    expressions: [
      {
        type: 'CONSTRAINT',
        constraint: {
          left: 'purpose',
          operator: 'EQ',
          right: { type: 'STRING', value: 'research' },
        },
      },
      {
        type: 'CONSTRAINT',
        constraint: {
          left: 'participantType',
          operator: 'EQ',
          right: { type: 'STRING', value: 'cultural-institution' },
        },
      },
    ],
  },
};

const response = await fetch(
  `${BASE_URL}/wrapper/ui/pages/create-data-offer`,
  {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'X-Api-Key': 'your-api-key',
    },
    body: JSON.stringify(dataOfferRequest),
  }
);

const result = await response.json();
// The same ID is used for asset, policy, and contract definition
console.log('Created with ID:', result.id);

The create-data-offer endpoint uses a flat payload structure where all fields (id, title, dataSource, publishType, etc.) are at the top level. There is no nested asset object.

Understanding UiPolicy in Responses

When you retrieve policies, they include the original JSON-LD and the parsed expression:

interface UiPolicy {
  policyJsonLd: string;          // Original ODRL JSON-LD
  expression?: UiPolicyExpression;  // Parsed sovity expression
  errors: string[];              // Any parsing errors
}

// Example: Check for policy parsing errors
const page = await getPolicyDefinitions();
page.policies.forEach((p) => {
  if (p.policy.errors.length > 0) {
    console.warn(`Policy ${p.policyDefinitionId} has parsing errors:`);
    p.policy.errors.forEach((err) => console.warn(`  - ${err}`));
  }
});

The errors array contains warnings when the ODRL policy contains features not fully supported by the sovity UI API. The policy still works, but some constraints may not be visible in the UI representation.

Policy Evaluation

Policies are evaluated at two points:

  1. Catalog Query: Access policies determine visibility of offers
  2. Contract Negotiation: Contract policies must be satisfied for agreement

Policy Evaluation Flow

┌─────────────────┐    ┌─────────────────┐    ┌─────────────────┐
│  Access Policy  │    │ Contract Policy │    │   Agreement     │
│  (Visibility)   │ ─▶ │  (Negotiation)  │ ─▶ │   (Usage)       │
└─────────────────┘    └─────────────────┘    └─────────────────┘

Best Practices

  1. Use V2 API: Always use the V2 endpoint for new policy creation
  2. Start Simple: Begin with EMPTY or single-constraint policies
  3. Test Thoroughly: Verify policies don't accidentally block legitimate access
  4. Use Descriptive IDs: Make policy IDs self-documenting (e.g., eu-research-only)
  5. Document Constraints: Keep records of what each policy requires
  6. Don't Modify: Create new policies instead of modifying existing ones
  7. Check Availability: Validate policy IDs before creation

Cultural Sector Examples

Museum Research Access

const museumResearchPolicy: UiPolicyExpression = {
  type: 'AND',
  expressions: [
    {
      type: 'CONSTRAINT',
      constraint: {
        left: 'purpose',
        operator: 'IN',
        right: {
          type: 'STRING_LIST',
          valueList: ['academic-research', 'preservation', 'education'],
        },
      },
    },
    {
      type: 'CONSTRAINT',
      constraint: {
        left: 'participantType',
        operator: 'IN',
        right: {
          type: 'STRING_LIST',
          valueList: ['museum', 'university', 'archive', 'library'],
        },
      },
    },
  ],
};

await createPolicyV2('museum-research-access', museumResearchPolicy);

Archive Public Domain

const publicDomainPolicy: UiPolicyExpression = {
  type: 'EMPTY',  // No restrictions - public domain
};

await createPolicyV2('public-domain-access', publicDomainPolicy);

Theatre Performance Rights

const performanceRightsPolicy: UiPolicyExpression = {
  type: 'AND',
  expressions: [
    {
      type: 'CONSTRAINT',
      constraint: {
        left: 'usage',
        operator: 'EQ',
        right: { type: 'STRING', value: 'non-commercial' },
      },
    },
    {
      type: 'CONSTRAINT',
      constraint: {
        left: 'attribution',
        operator: 'EQ',
        right: { type: 'STRING', value: 'required' },
      },
    },
  ],
};

await createPolicyV2('theatre-performance-rights', performanceRightsPolicy);

Next Steps

On this page