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:
| Type | Purpose |
|---|---|
| Access Policy | Determines who can see an offer in the catalog |
| Contract Policy | Governs 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 operationsExpression Types
| Type | Description | Example |
|---|---|---|
EMPTY | Always evaluates to true (no constraints) | Open/unrestricted access |
CONSTRAINT | Single condition (a = b) | region EQ "EU" |
AND | All sub-expressions must be true | Region EU AND purpose research |
OR | At least one sub-expression must be true | Region EU OR region US |
XONE | Exactly one sub-expression must be true | Exclusive 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
V2 API (Recommended)
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:
- Catalog Query: Access policies determine visibility of offers
- Contract Negotiation: Contract policies must be satisfied for agreement
Policy Evaluation Flow
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ Access Policy │ │ Contract Policy │ │ Agreement │
│ (Visibility) │ ─▶ │ (Negotiation) │ ─▶ │ (Usage) │
└─────────────────┘ └─────────────────┘ └─────────────────┘Best Practices
- Use V2 API: Always use the V2 endpoint for new policy creation
- Start Simple: Begin with
EMPTYor single-constraint policies - Test Thoroughly: Verify policies don't accidentally block legitimate access
- Use Descriptive IDs: Make policy IDs self-documenting (e.g.,
eu-research-only) - Document Constraints: Keep records of what each policy requires
- Don't Modify: Create new policies instead of modifying existing ones
- 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);