Getting Started
Configuration
Configure your environment for the sovity EDC API Wrapper
Configuration
This guide covers how to configure your development environment for working with the sovity EDC API Wrapper.
Environment Setup
Required Configuration
Create an environment configuration file to store your connector settings:
# .env.local
EDC_MANAGEMENT_URL=https://api.your-connector-instance.prod.truzztbox.eu/api/management
EDC_API_KEY=your-api-keyTypeScript Configuration
Create a configuration module for type-safe access to your settings:
// config/edc.ts
export const edcConfig = {
managementUrl:
process.env.EDC_MANAGEMENT_URL ||
'https://api.your-connector-instance.prod.truzztbox.eu/api/management',
apiKey: process.env.EDC_API_KEY || '',
};
// Validate configuration
if (!edcConfig.apiKey) {
throw new Error('EDC_API_KEY environment variable is required');
}TypeScript API Client
Create a complete, type-safe API client for the sovity UI API:
// lib/edc-client.ts
import { edcConfig } from '@/config/edc';
// ============ Types ============
export interface UiAsset {
assetId: string;
title: string;
description?: string;
dataSourceAvailability: 'LIVE' | 'ON_REQUEST';
connectorEndpoint: string;
participantId: string;
creatorOrganizationName: string;
keywords?: string[];
mediaType?: string;
language?: string;
version?: string;
licenseUrl?: string;
publisherHomepage?: string;
isOwnConnector: boolean;
}
export interface UiDataSource {
type: 'HTTP_DATA' | 'ON_REQUEST' | 'CUSTOM';
httpData?: {
baseUrl: string;
method?: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
queryString?: string;
authHeaderName?: string;
authHeaderValue?: { secretName?: string; rawValue?: string };
headers?: Record<string, string>;
enableMethodParameterization?: boolean;
enablePathParameterization?: boolean;
enableQueryParameterization?: boolean;
enableBodyParameterization?: boolean;
};
onRequest?: {
contactEmail: string;
contactPreferredEmailSubject: string;
};
customProperties?: Record<string, string>;
}
export interface UiAssetCreateRequest {
id: string;
title?: string;
description?: string;
language?: string;
keywords?: string[];
mediaType?: string;
version?: string;
licenseUrl?: string;
publisherHomepage?: string;
dataSource: UiDataSource;
}
export interface UiPolicyLiteral {
type: 'STRING' | 'STRING_LIST' | 'JSON';
value?: string;
valueList?: string[];
}
export type OperatorDto =
| 'EQ' | 'NEQ' | 'GT' | 'GEQ' | 'LT' | 'LEQ'
| 'IN' | 'HAS_PART' | 'IS_A'
| 'IS_ALL_OF' | 'IS_ANY_OF' | 'IS_NONE_OF';
export interface UiPolicyConstraint {
left: string;
operator: OperatorDto;
right: UiPolicyLiteral;
}
export interface UiPolicyExpression {
type: 'EMPTY' | 'CONSTRAINT' | 'AND' | 'OR' | 'XONE';
expressions?: UiPolicyExpression[];
constraint?: UiPolicyConstraint;
}
export interface UiPolicy {
policyJsonLd: string;
expression?: UiPolicyExpression;
errors: string[];
}
export interface UiContractOffer {
contractOfferId: string;
policy: UiPolicy;
}
export interface UiDataOffer {
endpoint: string;
participantId: string;
asset: UiAsset;
contractOffers: UiContractOffer[];
}
export interface TransferProcessState {
name: string;
code: number;
simplifiedState: 'RUNNING' | 'OK' | 'ERROR';
}
export interface TransferHistoryEntry {
transferProcessId: string;
createdDate: string;
lastUpdatedDate: string;
state: TransferProcessState;
contractAgreementId: string;
direction: 'CONSUMING' | 'PROVIDING';
counterPartyConnectorEndpoint: string;
counterPartyParticipantId: string;
assetName: string;
assetId: string;
errorMessage?: string;
}
export interface DashboardTransferAmounts {
numTotal: number;
numRunning: number;
numOk: number;
numError: number;
}
export interface DashboardPage {
numAssets: number;
numPolicies: number;
numContractDefinitions: number;
numContractAgreementsConsuming: number;
numContractAgreementsProviding: number;
transferProcessesConsuming: DashboardTransferAmounts;
transferProcessesProviding: DashboardTransferAmounts;
managementApiUrlShownInDashboard: string;
connectorEndpoint: string;
connectorParticipantId: string;
connectorTitle: string;
connectorDescription: string;
connectorCuratorUrl: string;
connectorCuratorName: string;
}
export interface IdResponse {
id: string;
lastUpdatedDate: string;
}
// ============ API Client ============
class EdcApiError extends Error {
constructor(
public statusCode: number,
message: string
) {
super(message);
this.name = 'EdcApiError';
}
}
async function edcFetch<T>(
endpoint: string,
options: RequestInit = {}
): Promise<T> {
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), 30000);
try {
const response = await fetch(`${edcConfig.managementUrl}${endpoint}`, {
...options,
signal: controller.signal,
headers: {
'Content-Type': 'application/json',
'X-Api-Key': edcConfig.apiKey,
...options.headers,
},
});
if (!response.ok) {
let errorMessage = response.statusText;
try {
const error = await response.json();
errorMessage = error.message || error.detail || errorMessage;
} catch {
// Ignore JSON parse errors
}
throw new EdcApiError(response.status, errorMessage);
}
return response.json();
} finally {
clearTimeout(timeout);
}
}
// ============ Dashboard ============
export async function getDashboard(): Promise<DashboardPage> {
return edcFetch('/wrapper/ui/pages/dashboard-page');
}
// ============ Assets ============
export async function getAssets(): Promise<{ assets: UiAsset[] }> {
return edcFetch('/wrapper/ui/pages/asset-page');
}
export async function createAsset(
asset: UiAssetCreateRequest
): Promise<IdResponse> {
return edcFetch('/wrapper/ui/pages/asset-page/assets', {
method: 'POST',
body: JSON.stringify(asset),
});
}
export async function deleteAsset(assetId: string): Promise<IdResponse> {
return edcFetch(`/wrapper/ui/pages/asset-page/assets/${assetId}`, {
method: 'DELETE',
});
}
export async function isAssetIdAvailable(
assetId: string
): Promise<{ id: string; available: boolean }> {
return edcFetch(
`/wrapper/ui/pages/data-offer-page/validate-asset-id/${assetId}`
);
}
// ============ Data Offers ============
export type DrkAssetType = 'GENERAL' | 'MUSEUM' | 'THEATRE_PLAN' | 'MUSICSCHOOL';
export 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"
version?: string;
keywords?: string[];
mediaType?: string;
publisherHomepage?: string;
licenseUrl?: string;
landingPageUrl?: string; // Link to asset documentation
// DRK-specific (all optional, default drkAssetType = 'GENERAL')
drkAssetType?: DrkAssetType;
drkMuseumName?: string | null; // When drkAssetType = 'MUSEUM'
drkTheatreName?: string | null; // When drkAssetType = 'THEATRE_PLAN'
drkTheatreStreetaddress?: string | null;
drkTheatrePostalCode?: string | null;
drkTheatreLocality?: string | null;
drkTheatreCountry?: string | null;
drkMusicSchoolName?: string | null; // When drkAssetType = 'MUSICSCHOOL'
// Policy (optional)
publishType?: 'DONT_PUBLISH' | 'PUBLISH_UNRESTRICTED' | 'PUBLISH_RESTRICTED';
policyExpression?: UiPolicyExpression;
}
export async function createDataOffer(
request: DataOfferCreateRequest
): Promise<IdResponse> {
return edcFetch('/wrapper/ui/pages/create-data-offer', {
method: 'POST',
body: JSON.stringify(request),
});
}
// ============ Catalog ============
export interface CatalogQuery {
connectorEndpoint?: string;
participantId?: string;
}
export async function getCatalogDataOffers(
query: CatalogQuery
): Promise<UiDataOffer[]> {
const params = new URLSearchParams();
if (query.connectorEndpoint) {
params.set('connectorEndpoint', query.connectorEndpoint);
}
if (query.participantId) {
params.set('participantId', query.participantId);
}
return edcFetch(`/wrapper/ui/pages/catalog-page/data-offers?${params}`);
}
// ============ Contract Negotiations ============
export interface ContractNegotiationRequest {
counterPartyId: string;
counterPartyAddress: string;
contractOfferId: string;
policyJsonLd: string;
assetId: string;
}
export interface UiContractNegotiation {
contractNegotiationId: string;
createdAt: string;
contractAgreementId?: string;
state: {
name: string;
code: number;
simplifiedState: 'IN_PROGRESS' | 'AGREED' | 'TERMINATED';
};
}
export async function initiateNegotiation(
request: ContractNegotiationRequest
): Promise<UiContractNegotiation> {
return edcFetch('/wrapper/ui/pages/catalog-page/contract-negotiations', {
method: 'POST',
body: JSON.stringify(request),
});
}
export async function getNegotiation(
negotiationId: string
): Promise<UiContractNegotiation> {
return edcFetch(
`/wrapper/ui/pages/catalog-page/contract-negotiations/${negotiationId}`
);
}
// ============ Contract Agreements ============
export interface ContractAgreementCard {
contractAgreementId: string;
contractNegotiationId: string;
direction: 'CONSUMING' | 'PROVIDING';
counterPartyAddress: string;
counterPartyId: string;
contractSigningDate: string;
asset: UiAsset;
contractPolicy: UiPolicy;
transferProcesses: Array<{
transferProcessId: string;
lastUpdatedDate: string;
state: TransferProcessState;
errorMessage?: string;
}>;
terminationStatus: 'ONGOING' | 'TERMINATED';
}
export interface ContractAgreementPageQuery {
terminationStatus?: 'ONGOING' | 'TERMINATED';
}
export async function getContractAgreements(
query?: ContractAgreementPageQuery
): Promise<{ contractAgreements: ContractAgreementCard[] }> {
return edcFetch('/wrapper/ui/pages/contract-agreement-page', {
method: 'POST',
body: JSON.stringify(query || {}),
});
}
export async function getContractAgreement(
agreementId: string
): Promise<ContractAgreementCard> {
return edcFetch(`/wrapper/ui/pages/contract-agreement-page/${agreementId}`);
}
export async function terminateContractAgreement(
agreementId: string,
reason: string,
detail: string
): Promise<IdResponse> {
return edcFetch(
`/wrapper/ui/pages/content-agreement-page/${agreementId}/terminate`,
{
method: 'POST',
body: JSON.stringify({ reason, detail }),
}
);
}
// ============ Transfers ============
export interface InitiateTransferRequest {
contractAgreementId: string;
transferType: string;
dataSinkProperties: Record<string, string>;
transferProcessProperties: Record<string, string>;
}
export async function initiateTransfer(
request: InitiateTransferRequest
): Promise<IdResponse> {
return edcFetch('/wrapper/ui/pages/contract-agreement-page/transfers', {
method: 'POST',
body: JSON.stringify(request),
});
}
export async function getTransferHistory(): Promise<{
transferEntries: TransferHistoryEntry[];
}> {
return edcFetch('/wrapper/ui/pages/transfer-history-page');
}
export async function getTransferAsset(
transferProcessId: string
): Promise<UiAsset> {
return edcFetch(
`/wrapper/ui/pages/transfer-history-page/transfer-processes/${transferProcessId}/asset`
);
}
// ============ Policies ============
export interface PolicyDefinitionDto {
policyDefinitionId: string;
policy: UiPolicy;
}
export async function getPolicyDefinitions(): Promise<{
policies: PolicyDefinitionDto[];
}> {
return edcFetch('/wrapper/ui/pages/policy-page');
}
export async function createPolicyDefinition(
policyDefinitionId: string,
expression: UiPolicyExpression
): Promise<IdResponse> {
return edcFetch('/wrapper/ui/v2/pages/policy-page/policy-definitions', {
method: 'POST',
body: JSON.stringify({ policyDefinitionId, expression }),
});
}
export async function deletePolicyDefinition(
policyId: string
): Promise<IdResponse> {
return edcFetch(`/wrapper/ui/pages/policy-page/policy-definitions/${policyId}`, {
method: 'DELETE',
});
}
// ============ Build Info ============
export interface BuildInfo {
buildDate: string;
buildVersion: string;
}
export async function getBuildInfo(): Promise<BuildInfo> {
return edcFetch('/wrapper/ui/build-info');
}Usage Examples
Basic Usage
import {
getDashboard,
getAssets,
createDataOffer,
getCatalogDataOffers,
} from '@/lib/edc-client';
// Get connector overview
const dashboard = await getDashboard();
console.log(`Connected to: ${dashboard.connectorTitle}`);
console.log(`Assets: ${dashboard.numAssets}`);
// List all assets
const { assets } = await getAssets();
assets.forEach((asset) => {
console.log(`- ${asset.assetId}: ${asset.title}`);
});
// Create a quick data offer (flat structure)
const result = await createDataOffer({
id: 'my-dataset',
title: 'My Dataset',
description: 'Example dataset for demonstration',
language: 'https://w3id.org/idsa/code/EN',
dataSource: {
type: 'HTTP_DATA',
httpData: { baseUrl: 'https://api.example.com/data' },
},
publishType: 'PUBLISH_UNRESTRICTED',
});
console.log(`Created: ${result.id}`);
// Browse another connector's catalog
const offers = await getCatalogDataOffers({
connectorEndpoint: 'https://partner.truzztbox.eu/protocol',
});
offers.forEach((offer) => {
console.log(`- ${offer.asset.title}`);
});Complete Data Exchange Flow
import {
getCatalogDataOffers,
initiateNegotiation,
getNegotiation,
getContractAgreements,
initiateTransfer,
getTransferHistory,
} from '@/lib/edc-client';
// 1. Discover data offers
const offers = await getCatalogDataOffers({
connectorEndpoint: 'https://provider.truzztbox.eu/protocol',
});
const selectedOffer = offers[0];
const contractOffer = selectedOffer.contractOffers[0];
// 2. Initiate negotiation
const negotiation = await initiateNegotiation({
counterPartyId: selectedOffer.participantId,
counterPartyAddress: selectedOffer.endpoint,
contractOfferId: contractOffer.contractOfferId,
policyJsonLd: contractOffer.policy.policyJsonLd,
assetId: selectedOffer.asset.assetId,
});
// 3. Wait for agreement
let status = negotiation;
while (status.state.simplifiedState === 'IN_PROGRESS') {
await new Promise((r) => setTimeout(r, 2000));
status = await getNegotiation(negotiation.contractNegotiationId);
}
if (status.state.simplifiedState !== 'AGREED') {
throw new Error('Negotiation failed');
}
// 4. Initiate transfer
const transfer = await initiateTransfer({
contractAgreementId: status.contractAgreementId!,
transferType: 'HttpData-PULL',
dataSinkProperties: { type: 'HttpProxy' },
transferProcessProperties: {},
});
console.log(`Transfer started: ${transfer.id}`);
// 5. Monitor transfer
const history = await getTransferHistory();
const myTransfer = history.transferEntries.find(
(t) => t.transferProcessId === transfer.id
);
console.log(`Transfer state: ${myTransfer?.state.simplifiedState}`);React/Next.js Integration
API Route Handler
// app/api/edc/dashboard/route.ts
import { getDashboard } from '@/lib/edc-client';
import { NextResponse } from 'next/server';
export async function GET() {
try {
const dashboard = await getDashboard();
return NextResponse.json(dashboard);
} catch (error) {
return NextResponse.json(
{ error: 'Failed to fetch dashboard' },
{ status: 500 }
);
}
}React Hook
// hooks/use-edc.ts
import useSWR from 'swr';
import { getDashboard, getAssets, getTransferHistory } from '@/lib/edc-client';
export function useDashboard() {
return useSWR('edc-dashboard', getDashboard, {
refreshInterval: 30000, // Refresh every 30 seconds
});
}
export function useAssets() {
return useSWR('edc-assets', getAssets);
}
export function useTransferHistory() {
return useSWR('edc-transfers', getTransferHistory, {
refreshInterval: 5000, // Refresh every 5 seconds
});
}Component Example
// components/dashboard-stats.tsx
'use client';
import { useDashboard } from '@/hooks/use-edc';
export function DashboardStats() {
const { data, error, isLoading } = useDashboard();
if (isLoading) return <div>Loading...</div>;
if (error) return <div>Error loading dashboard</div>;
if (!data) return null;
return (
<div className="grid grid-cols-4 gap-4">
<StatCard title="Assets" value={data.numAssets} />
<StatCard title="Policies" value={data.numPolicies} />
<StatCard title="Contract Definitions" value={data.numContractDefinitions} />
<StatCard
title="Agreements (Consuming)"
value={data.numContractAgreementsConsuming}
/>
</div>
);
}
function StatCard({ title, value }: { title: string; value: number }) {
return (
<div className="rounded-lg border p-4">
<h3 className="text-sm text-gray-500">{title}</h3>
<p className="text-2xl font-bold">{value}</p>
</div>
);
}Error Handling
import { EdcApiError } from '@/lib/edc-client';
try {
const dashboard = await getDashboard();
} catch (error) {
if (error instanceof EdcApiError) {
switch (error.statusCode) {
case 401:
console.error('Invalid API key');
break;
case 403:
console.error('Access denied');
break;
case 404:
console.error('Resource not found');
break;
default:
console.error(`API error: ${error.message}`);
}
} else {
console.error('Network error:', error);
}
}Multiple Environments
// config/environments.ts
type Environment = 'development' | 'staging' | 'production';
const configs: Record<Environment, { url: string }> = {
development: {
url: 'https://dev-api.your-connector.truzztbox.eu/api/management',
},
staging: {
url: 'https://staging-api.your-connector.truzztbox.eu/api/management',
},
production: {
url: 'https://api.your-connector-instance.prod.truzztbox.eu/api/management',
},
};
const env = (process.env.NODE_ENV as Environment) || 'development';
export const envConfig = configs[env];