Developer Quickstart
Get up and running with PDTF 2.0 in five minutes. This guide covers the three operations every implementer needs: verifying a credential, looking up an issuer’s OpenID Federation Trust Marks, and checking revocation status.
Install the packages
Section titled “Install the packages”npm install @pdtf/corecargo add pdtf-corepip install pdtf-coredotnet add package Pdtf.Core1. Verify a credential
Section titled “1. Verify a credential”Given a W3C Verifiable Credential containing PDTF entity data, verify its signature, issuer authorisation, and revocation status in one call:
import { VcValidator, DidResolver, FederationRegistryResolver } from '@pdtf/core';
const credential = { "@context": [ "https://www.w3.org/ns/credentials/v2", "https://propdata.org.uk/ns/v4" ], "type": ["VerifiableCredential", "PropertyCredential"], "issuer": "did:web:adapter.propdata.org.uk", "credentialSubject": { "id": "urn:pdtf:uprn:100023336956", "type": "Property", "energyPerformance": { "currentRating": "C", "certificateNumber": "0000-0000-0000-0000-0000" } }, "proof": { /* Ed25519 signature */ }};
const validator = new VcValidator();const didResolver = new DidResolver();const trustResolver = new FederationRegistryResolver({ registryUrl: 'https://trust.pdtf.org/.well-known/openid-federation'});
const result = await validator.validate(credential, { didResolver, trustResolver});
if (result.valid) { console.log('Credential is valid'); console.log('Issuer trust level:', result.stages.trust.details?.trustLevel); // → 'root-issuer' | 'accredited-issuer' | 'trusted-proxy'} else { console.error('Verification failed:', result.warnings);}use pdtf_core::{VcValidator, DidResolver, FederationRegistryResolver};use serde_json::json;
#[tokio::main]async fn main() -> Result<(), Box<dyn std::error::Error>> { let credential = json!({ "@context": [ "https://www.w3.org/ns/credentials/v2", "https://propdata.org.uk/ns/v4" ], "type": ["VerifiableCredential", "PropertyCredential"], "issuer": "did:web:adapter.propdata.org.uk", "credentialSubject": { "id": "urn:pdtf:uprn:100023336956", "type": "Property", "energyPerformance": { "currentRating": "C", "certificateNumber": "0000-0000-0000-0000-0000" } } });
let validator = VcValidator::new(); let did_resolver = DidResolver::new(); let trust_resolver = FederationRegistryResolver::new( "https://trust.pdtf.org/.well-known/openid-federation" );
let result = validator.validate(&credential, &did_resolver, &trust_resolver).await?;
if result.valid { println!("Credential is valid"); if let Some(level) = result.stages.trust.details.trust_level { println!("Issuer trust level: {}", level); } } else { eprintln!("Verification failed: {:?}", result.warnings); }
Ok(())}from pdtf_core import VcValidator, DidResolver, FederationRegistryResolver
credential = { "@context": [ "https://www.w3.org/ns/credentials/v2", "https://propdata.org.uk/ns/v4", ], "type": ["VerifiableCredential", "PropertyCredential"], "issuer": "did:web:adapter.propdata.org.uk", "credentialSubject": { "id": "urn:pdtf:uprn:100023336956", "type": "Property", "energyPerformance": { "currentRating": "C", "certificateNumber": "0000-0000-0000-0000-0000", }, }, # proof omitted for brevity}
validator = VcValidator()did_resolver = DidResolver()trust_resolver = FederationRegistryResolver( registry_url="https://trust.pdtf.org/.well-known/openid-federation")
result = validator.validate( credential, did_resolver=did_resolver, trust_resolver=trust_resolver,)
if result.valid: print("Credential is valid") print("Issuer trust level:", result.stages.trust.details.trust_level) # → 'root-issuer' | 'accredited-issuer' | 'trusted-proxy'else: print("Verification failed:", result.warnings)using Pdtf.Core;using System.Text.Json;
var credential = JsonDocument.Parse("""{ "@context": [ "https://www.w3.org/ns/credentials/v2", "https://propdata.org.uk/ns/v4" ], "type": ["VerifiableCredential", "PropertyCredential"], "issuer": "did:web:adapter.propdata.org.uk", "credentialSubject": { "id": "urn:pdtf:uprn:100023336956", "type": "Property", "energyPerformance": { "currentRating": "C", "certificateNumber": "0000-0000-0000-0000-0000" } }}""");
var validator = new VcValidator();var didResolver = new DidResolver();var trustResolver = new FederationRegistryResolver( registryUrl: "https://trust.pdtf.org/.well-known/openid-federation");
var result = await validator.ValidateAsync(credential, new ValidateOptions{ DidResolver = didResolver, TrustResolver = trustResolver,});
if (result.Valid){ Console.WriteLine("Credential is valid"); Console.WriteLine($"Issuer trust level: {result.Stages.Trust.Details?.TrustLevel}");}else{ Console.Error.WriteLine($"Verification failed: {string.Join(", ", result.Warnings)}");}What validate checks
Section titled “What validate checks”- Structure — validates the
credentialSubjectagainst the PDTF v4 JSON schema. - Signature — resolves the issuer’s DID document, extracts the verification method, and validates the
eddsa-jcs-2022proof. - Trust — evaluates the issuer’s OpenID Federation Trust Marks and confirms they are authorised for this entity type and data path.
- Status — fetches the Bitstring Status List and checks the credential’s revocation bit.
2. Verify OpenID Federation Trust Marks
Section titled “2. Verify OpenID Federation Trust Marks”The OpenID Federation Trust Anchor dictates who is authorised to issue which types of credentials:
import { FederationRegistryResolver } from '@pdtf/core';
const trustResolver = new FederationRegistryResolver({ registryUrl: 'https://trust.pdtf.org/.well-known/openid-federation'});
// Check if an issuer has a Trust Mark authorising them for a specific pathconst isTrusted = await trustResolver.isIssuerTrusted( 'did:web:adapter.propdata.org.uk', ['Property:/energyPerformance']);
if (isTrusted) { console.log('Issuer is authorised via OpenID Federation Trust Marks');} else { console.log('Issuer lacks Trust Marks for this entity:path');}use pdtf_core::FederationRegistryResolver;
#[tokio::main]async fn main() -> Result<(), Box<dyn std::error::Error>> { let trust_resolver = FederationRegistryResolver::new( "https://trust.pdtf.org/.well-known/openid-federation" );
let is_trusted = trust_resolver .is_issuer_trusted( "did:web:adapter.propdata.org.uk", &["Property:/energyPerformance"], ) .await?;
if is_trusted { println!("Issuer is authorised via OpenID Federation Trust Marks"); } else { println!("Issuer lacks Trust Marks for this entity:path"); }
Ok(())}from pdtf_core import FederationRegistryResolver
trust_resolver = FederationRegistryResolver( registry_url="https://trust.pdtf.org/.well-known/openid-federation")
is_trusted = trust_resolver.is_issuer_trusted( "did:web:adapter.propdata.org.uk", ["Property:/energyPerformance"],)
if is_trusted: print("Issuer is authorised via OpenID Federation Trust Marks")else: print("Issuer lacks Trust Marks for this entity:path")using Pdtf.Core;
var trustResolver = new FederationRegistryResolver( registryUrl: "https://trust.pdtf.org/.well-known/openid-federation");
var isTrusted = await trustResolver.IsIssuerTrustedAsync( "did:web:adapter.propdata.org.uk", new[] { "Property:/energyPerformance" });
if (isTrusted){ Console.WriteLine("Issuer is authorised via OpenID Federation Trust Marks");}else{ Console.WriteLine("Issuer lacks Trust Marks for this entity:path");}Fetch an Entity Statement
Section titled “Fetch an Entity Statement”// Fetch the raw entity statement for an issuer within the federationconst statement = await trustResolver.fetchEntityStatement('did:web:adapter.propdata.org.uk');
console.log(`Issuer: ${statement.sub}`);console.log(`Trust Marks:`, statement.trust_marks);let statement = trust_resolver .fetch_entity_statement("did:web:adapter.propdata.org.uk") .await?;
println!("Issuer: {}", statement.sub);println!("Trust Marks: {:?}", statement.trust_marks);statement = trust_resolver.fetch_entity_statement("did:web:adapter.propdata.org.uk")
print(f"Issuer: {statement.sub}")print(f"Trust Marks: {statement.trust_marks}")var statement = await trustResolver.FetchEntityStatementAsync( "did:web:adapter.propdata.org.uk");
Console.WriteLine($"Issuer: {statement.Sub}");Console.WriteLine($"Trust Marks: {string.Join(", ", statement.TrustMarks)}");3. Check revocation status
Section titled “3. Check revocation status”Every PDTF credential includes a credentialStatus field pointing to a Bitstring Status List. Check it directly:
import { StatusListResolver } from '@pdtf/core';
const statusResolver = new StatusListResolver();const status = await statusResolver.checkRevocation(credential);
if (status.revoked) { console.log('Credential has been revoked'); console.log('Revoked at:', status.revokedAt);} else { console.log('Credential is active');}use pdtf_core::StatusListResolver;
let status_resolver = StatusListResolver::new();let status = status_resolver.check_revocation(&credential).await?;
if status.revoked { println!("Credential has been revoked"); if let Some(revoked_at) = status.revoked_at { println!("Revoked at: {}", revoked_at); }} else { println!("Credential is active");}from pdtf_core import StatusListResolver
status_resolver = StatusListResolver()status = status_resolver.check_revocation(credential)
if status.revoked: print("Credential has been revoked") print("Revoked at:", status.revoked_at)else: print("Credential is active")using Pdtf.Core;
var statusResolver = new StatusListResolver();var status = await statusResolver.CheckRevocationAsync(credential);
if (status.Revoked){ Console.WriteLine("Credential has been revoked"); Console.WriteLine($"Revoked at: {status.RevokedAt}");}else{ Console.WriteLine("Credential is active");}The status list is a compact bitstring where each credential is assigned an index. Issuers publish the status list at a public URL and flip individual bits to revoke credentials — no need to contact the issuer directly.
4. Resolve a DID
Section titled “4. Resolve a DID”Look up a DID document for any PDTF entity that uses did:web:
import { DidResolver } from '@pdtf/core';
const resolver = new DidResolver();
// Resolve a transaction's DID documentconst doc = await resolver.resolve('did:web:platform.example.com:transactions:abc123');
console.log('Service endpoints:', doc.service);// → [{ type: 'PDTFv4API', serviceEndpoint: 'https://...' }]
console.log('Verification methods:', doc.verificationMethod);// → [{ type: 'Ed25519VerificationKey2020', publicKeyMultibase: 'z6Mkh...' }]use pdtf_core::DidResolver;
let resolver = DidResolver::new();
let doc = resolver .resolve("did:web:platform.example.com:transactions:abc123") .await?;
println!("Service endpoints: {:?}", doc.service);println!("Verification methods: {:?}", doc.verification_method);from pdtf_core import DidResolver
resolver = DidResolver()
doc = resolver.resolve("did:web:platform.example.com:transactions:abc123")
print("Service endpoints:", doc.service)print("Verification methods:", doc.verification_method)using Pdtf.Core;
var resolver = new DidResolver();
var doc = await resolver.ResolveAsync( "did:web:platform.example.com:transactions:abc123");
Console.WriteLine($"Service endpoints: {JsonSerializer.Serialize(doc.Service)}");Console.WriteLine($"Verification methods: {JsonSerializer.Serialize(doc.VerificationMethod)}");CLI usage
Section titled “CLI usage”The @pdtf/cli package provides command-line equivalents:
# Verify a credential from a JSON filepdtf verify credential.json
# Look up an issuer's Trust Markspdtf trust resolve did:web:adapter.propdata.org.uk
# Check revocationpdtf revocation check credential.json
# Resolve a DIDpdtf did resolve did:web:platform.example.com:transactions:abc123Next steps
Section titled “Next steps”- Understand the entity graph — how property data decomposes into ten entity types
- Read the VC data model spec — full credential format reference
- Explore the OpenID Federation spec — registry schema and trust levels
- Browse the packages — package documentation