JLINC SDK
A TypeScript/JavaScript SDK for the JLINC API enabling verifiable data-sharing agreements, cryptographically signed data exchange, and zero-knowledge auditing.
Overview
The JLINC SDK provides a unified interface to the JLINC Server API, covering:
- Identity management - Create and resolve decentralized identifiers (DIDs) with federation-formatted short names.
- Verifiable Contractual Agreements (VCAs) - Produce and cross-sign agreements that govern data-sharing permissions.
- Cryptographic event records - Produce, retrieve, and process data exchange events under agreement governance.
- Zero-knowledge auditing - Retrieve and verify tamper-proof audit records from third-party archives.
- Fine-grained access control - Check AuthZEN-formatted permissions before executing sensitive operations.
Installation
npm install @jlinc/sdk
yarn add @jlinc/sdk
pnpm add @jlinc/sdk
Quick Start
import { init, createEntity, produceAgreement, processAgreement, produceEvent, getAudits, verifyAudit, auth } from "@jlinc/sdk";
// 1. Initialize the SDK with your JLINC Server configuration
init({
apiKey: process.env.JLINC_API_KEY!,
apiUrl: process.env.JLINC_API_URL!,
archiveUrl: process.env.JLINC_ARCHIVE_URL!,
archiveKey: process.env.JLINC_ARCHIVE_KEY!,
fedidUrl: process.env.JLINC_FEDID_URL!,
}, {
// Optional: set a default agreement ID fallback
agreementId: "00000000-0000-0000-0000-000000000000",
});
// 2. Create an identity
const entity = await createEntity({
shortName: `my-app@${(await getDomains({ auth: { subject: { type: "user", id: "admin" }, action: { name: "read" }, resource: { type: "data", id: "demo" } } }))[0]}`,
});
// 3. Produce and process an agreement
const agreement = await produceAgreement({
data: {
references: ["https://sisa.jlinc.org/v1/34020c5fb59ebc6507ebca4eb38090aec1097c6aec8d2ae2250ddfed4b4aa63c"],
permitted: ["data-sharing"],
prohibited: ["non-delegable"],
shortNames: [entity.didDoc.shortName],
validRoles: ["provider", "user", "third-party"],
},
shortName: entity.didDoc.shortName,
role: "provider",
});
await processAgreement({
agreementId: agreement.created.agreementId,
shortName: entity.didDoc.shortName,
role: "user",
});
// 4. Exchange data under the agreement
const event = await produceEvent({
type: "data",
senderShortName: "sender@domain.tld",
recipientShortName: "recipient@domain.tld",
agreementId: agreement.created.agreementId,
meta: { myCustomId: "unique-id-123" },
data: { query: "Hello, world!" },
});
// 5. Audit and verify
const audits = await getAudits({ agreementId: agreement.created.agreementId });
const result = await verifyAudit({
audits: audits.auditRecords || [],
shortNames: ["sender@domain.tld", "recipient@domain.tld"],
});
Configuration
init(config, defaults?)
Initialize the SDK with your server credentials and optional default parameters. All subsequent API calls inherit these settings.
init(
{
apiKey: string; // Bearer token for the Core API
apiUrl: string; // JLINC Core API base URL (e.g. "https://api.jlinc.io")
archiveUrl: string; // JLINC Archiver base URL for audit records
archiveKey: string; // Bearer token for the Archiver API
fedidUrl: string; // FedID service URL for identity resolution
},
defaults?: {
agreementId?: string; // Default agreement ID (fallback for all calls that accept one)
}
): void
| Parameter | Description |
|---|---|
apiKey |
Bearer token for authenticating requests to the Core API |
apiUrl |
Base URL of the JLINC Core API endpoint |
archiveUrl |
Base URL of the JLINC Archiver (audit) endpoint |
archiveKey |
Bearer token for authenticating requests to the Archiver API |
fedidUrl |
URL of the FedID service for domain resolution and identity operations |
API Reference
All methods return the parsed response data from the JLINC API. Where responses include a created object, it contains the newly generated identifiers (e.g., created.agreementId, created.eventId).
auth(params)
Perform a fine-grained authorization check using an AuthZEN-formatted request. Returns { decision: true } if the subject is permitted, or { decision: false } if denied.
type AuthSubject = {
type: string; // e.g. "user", "system", "ai-agent"
id: string; // Unique identifier for the subject
};
type AuthAction = {
name: string; // e.g. "read", "write", "delete"
};
type AuthResource = {
type: string; // e.g. "data", "agreement", "event"
id: string; // Resource identifier
properties?: {
ownerID?: string; // Optional data owner ID
};
};
type AuthParams = {
subject: AuthSubject;
action: AuthAction;
resource: AuthResource;
};
auth(params: AuthParams): Promise<{ decision: boolean }>;
Example:
const result = await auth({
subject: { type: "user", id: "myuser" },
action: { name: "read" },
resource: {
type: "data",
id: "1234",
properties: { ownerID: "owner@test.com" },
},
});
if (result.decision) {
// Proceed with the operation
} else {
// Deny access
}
getDomains(params)
Retrieve the list of available federation domains configured on your JLINC Server.
type GetDomainsParams = {
fedidUrl?: string; // Optional: override the configured fedidUrl
auth: AuthParams; // AuthZEN-formatted authorization payload
};
getDomains(params: GetDomainsParams): Promise<string[]>;
Example:
const domains = await getDomains({
auth: {
subject: { type: "user", id: "admin" },
action: { name: "read" },
resource: { type: "data", id: "domains" },
},
});
console.log(domains); // ["test.jlinc.io", "example.com", ...]
createEntity(params)
Create a new identity (entity) with a federation-formatted short name. Returns the entity's DID document.
type CreateEntityParams = {
fedidUrl?: string; // Optional: override the configured fedidUrl
shortName: string; // Federation-format name: "name@domain.tld"
};
createEntity(params: CreateEntityParams): Promise<{ didDoc: { shortName: string; did: string; ... } }>;
Example:
const domain = (await getDomains({ auth: /* ... */ }))[0];
const system = await createEntity({
fedidUrl: "http://localhost:8881",
shortName: `system@${domain}`,
});
console.log(system.didDoc.shortName); // "system@test.jlinc.io"
console.log(system.didDoc.did); // "did:jlinc:..."
getEntity(params)
Retrieve an existing entity's DID document by its short name.
type GetEntityParams = {
shortName: string; // The entity's federation-format short name
};
getEntity(params: GetEntityParams): Promise<{ data: { didDoc: { shortName: string; did: string; ... } } }>;
Example:
const result = await getEntity({ shortName: "system@test.jlinc.io" });
console.log(result.data.didDoc.shortName);
produceAgreement(params)
Create a Verifiable Contractual Agreement (VCA). The agreement is cryptographically signed, audited, and delivered to the archive service.
type ProduceAgreementParams = {
data: {
references?: string[]; // External references (e.g. SISA policy URIs)
permitted?: string[]; // Allowed operations
prohibited?: string[]; // Disallowed operations
shortNames?: string[]; // Entities that must sign before the agreement is valid
validRoles?: string[]; // Permitted roles (e.g. "provider", "user", "third-party")
public?: boolean; // If true, the agreement is publicly resolvable by any API key
};
shortName: string; // Entity that produces (initially signs) the agreement
role: string; // Role for the initial signature
archive?: {
url: string; // Optional: override the configured archiveUrl
key: string; // Optional: override the configured archiveKey
};
};
produceAgreement(params: ProduceAgreementParams): Promise<{ created: { agreementId: string }, ... }>;
Example:
const agreement = await produceAgreement({
data: {
references: ["https://sisa.jlinc.org/v1/34020c5fb59ebc6507ebca4eb38090aec1097c6aec8d2ae2250ddfed4b4aa63c"],
permitted: ["data-sharing"],
prohibited: ["non-delegable"],
shortNames: [provider.didDoc.shortName],
validRoles: ["provider", "user", "third-party"],
},
shortName: provider.didDoc.shortName,
role: "provider",
});
console.log(agreement.created.agreementId); // "58810f16-b976-41f0-..."
processAgreement(params)
Cross-sign an agreement as a second entity, finalizing its validity. This is how the "user" party agrees to the terms set by the "provider".
type ProcessAgreementParams = {
agreementId?: string; // Optional: falls back to init() defaults.agreementId
shortName: string; // Entity that processes (cross-signs) the agreement
role: string; // Role for this entity's signature
archive?: {
url: string;
key: string;
};
};
processAgreement(params: ProcessAgreementParams): Promise<...>;
Example:
await processAgreement({
agreementId: agreement.created.agreementId,
shortName: user.didDoc.shortName,
role: "user",
});
getAgreement(params)
Retrieve an agreement, optionally including its signatures. Falls back to the default agreementId from init() if not specified.
type GetAgreementParams = {
agreementId?: string; // Optional: falls back to init() defaults.agreementId
includeSignatures?: boolean; // Whether to include signature data
};
getAgreement(params: GetAgreementParams): Promise<...>;
Example:
const agreement = await getAgreement({
agreementId: "58810f16-b976-41f0-9566-2c26f174b592",
includeSignatures: true,
});
produceEvent(params)
Produce a cryptographically signed event record - used when two entities exchange data (e.g., user input, agent handoff, API call).
type ProduceEventParams = {
type: string; // Event type (e.g. "data", "command", "result")
senderShortName: string; // Sending entity's short name
recipientShortName: string; // Receiving entity's short name
agreementId?: string; // Optional: falls back to init() defaults.agreementId (use "nil" ID for audits only)
meta?: Record<string, unknown>; // Custom metadata for indexing and lookups
data: unknown; // The actual payload being exchanged
archive?: {
url: string;
key: string;
};
};
produceEvent(params: ProduceEventParams): Promise<{ created: { eventId: string }, ... }>;
Example:
const event = await produceEvent({
type: "data",
senderShortName: "alice@test.jlinc.io",
recipientShortName: "bob@test.jlinc.io",
agreementId: agreement.created.agreementId,
meta: { myCustomId: "unique-identifier-456" },
data: { query: "Why am I making this?" },
});
getEvent(params)
Retrieve an event by its unique ID or by custom metadata keys.
type GetEventParams = {
eventId?: string; // Optional: lookup by event ID
meta?: Record<string, unknown>; // Optional: lookup by meta keys (AND logic for multiple keys)
};
getEvent(params: GetEventParams): Promise<...>;
Example - by ID:
const event = await getEvent({ eventId: "evt-12345" });
Example - by meta ID:
const event = await getEvent({
meta: { myCustomId: "unique-identifier-456" },
});
processEvent(params)
Cross-sign an event to indicate the recipient has received and will use the data. This finalizes the event record.
type ProcessEventParams = {
eventId: string; // The event to process
shortName: string; // Receiving entity that processes the event
archive?: {
url: string;
key: string;
};
};
processEvent(params: ProcessEventParams): Promise<...>;
Example:
await processEvent({
eventId: event.created.eventId,
shortName: "bob@test.jlinc.io",
});
getAudits(params)
Retrieve audit records from the zero-knowledge archive. Uses the archiveKey for authentication.
type GetAuditsParams = {
agreementId?: string; // Optional: falls back to init() defaults.agreementId
eventId?: string; // Optional: lookup audits for a specific event
meta?: Record<string, unknown>; // Optional: lookup audits by meta keys (AND logic)
};
getAudits(params: GetAuditsParams): Promise<{ auditRecords: AuditRecord[], ... }>;
Example:
// Get all audits for an agreement
const audits = await getAudits({ agreementId: "58810f16-b976-41f0-9566-2c26f174b592" });
// Get audits for a specific event
const eventAudits = await getAudits({ eventId: "evt-12345" });
// Get audits by custom meta ID
const metaAudits = await getAudits({ meta: { myCustomId: "unique-456" } });
verifyAudit(params)
Verify the integrity of audit records against the local data store. Returns arrays of valid and invalid records with detailed validation results.
type VerifyAuditParams = {
audits: AuditRecord[]; // Audit records to verify
shortNames: string[]; // Entity short names that signed the records
};
type AuditResult = {
audit: AuditRecord;
results: {
validId: boolean; // Event ID matches local data store
validSignature: boolean; // Cryptographic signature is valid
validAuditHash: boolean; // Audit hash matches stored data
validAuditSignature: boolean; // Audit record signature is valid
validMatchingDids: boolean; // Signing DIDs match target object
validEventAgreement: boolean; // Event signing DIDs signed the governing agreement
validEventAgreementSignature: boolean; // Agreement signatures are valid
};
};
verifyAudit(params: VerifyAuditParams): Promise<{ valid: AuditResult[], invalid: AuditResult[] }>;
Example:
const audits = await getAudits({ agreementId: agreement.created.agreementId });
const result = await verifyAudit({
audits: audits.auditRecords || [],
shortNames: [
"alice@test.jlinc.io",
"bob@test.jlinc.io",
],
});
console.log(result.valid.length); // Number of valid records
console.log(result.invalid.length); // Number of tampered/invalid records
Validation Result Fields
When verifyAudit returns results, each boolean field means:
| Field | Description |
|---|---|
validId |
The event ID in the audit record matches the local data store. |
validSignature |
The cryptographic signature on the stored data is valid. |
validAuditHash |
The hash in the audit record is a valid representation of stored data. |
validAuditSignature |
The cryptographic signature on the audit record is valid. |
validMatchingDids |
The DIDs that signed the audit also signed the target object. |
validEventAgreement |
The DIDs that signed the audit signed the governing agreement. |
validEventAgreementSignature |
The signatures on the governing agreement are valid. |
Any record with a single false field is placed in the invalid array.
Complete Workflow Example
import {
init,
getDomains,
createEntity,
getEntity,
produceAgreement,
processAgreement,
getAgreement,
produceEvent,
getEvent,
processEvent,
getAudits,
verifyAudit,
auth,
} from "@jlinc/sdk";
// Step 0: Initialize
init({
apiKey: "your-api-key",
apiUrl: "https://api.jlinc.io",
archiveUrl: "https://api.jlinc.io",
archiveKey: "your-archive-key",
fedidUrl: "https://fedid.jlinc.io",
});
// Step 1: Create identities
const domains = await getDomains({
auth: {
subject: { type: "user", id: "admin" },
action: { name: "read" },
resource: { type: "data", id: "demo" },
},
});
const provider = await createEntity({
fedidUrl: "https://fedid.jlinc.io",
shortName: `provider@${domains[0]}`,
});
const user = await createEntity({
fedidUrl: "https://fedid.jlinc.io",
shortName: `user@${domains[0]}`,
});
// Step 2: Sign agreements
const agreement = await produceAgreement({
data: {
references: ["https://sisa.jlinc.org/v1/34020c5fb59ebc6507ebca4eb38090aec1097c6aec8d2ae2250ddfed4b4aa63c"],
permitted: ["data-sharing"],
prohibited: ["non-delegable"],
shortNames: [provider.didDoc.shortName],
validRoles: ["provider", "user", "third-party"],
},
shortName: provider.didDoc.shortName,
role: "provider",
});
await processAgreement({
agreementId: agreement.created.agreementId,
shortName: user.didDoc.shortName,
role: "user",
});
// Step 3: Exchange data
const event = await produceEvent({
type: "data",
senderShortName: user.didDoc.shortName,
recipientShortName: provider.didDoc.shortName,
agreementId: agreement.created.agreementId,
meta: { myCustomId: "session-abc" },
data: { query: "What is the weather?" },
});
await processEvent({
eventId: event.created.eventId,
shortName: provider.didDoc.shortName,
});
// Step 4: Audit and verify
const audits = await getAudits({ agreementId: agreement.created.agreementId });
const verification = await verifyAudit({
audits: audits.auditRecords || [],
shortNames: [user.didDoc.shortName, provider.didDoc.shortName],
});
Nil Agreement
If JLINC is being used for zero-knowledge auditing only (no formal agreement), the nil agreement ID can be used:
const event = await produceEvent({
type: "data",
senderShortName: "alice@test.jlinc.io",
recipientShortName: "bob@test.jlinc.io",
agreementId: "00000000-0000-0000-0000-000000000000", // Nil agreement
meta: { myCustomId: "audit-only-1" },
data: { query: "test" },
});
All subsequent operations (getEvent, getAudits, verifyAudit) work normally under the nil agreement.
Fine-Grained Access Control with AuthZEN
Every API call can optionally include an auth parameter for AuthZEN-formatted authorization checks. If the subject lacks permission, the server returns { decision: false }.
// Check authorization before proceeding
const check = await auth({
subject: { type: "user", id: "myuser" },
action: { name: "read" },
resource: { type: "data", id: "1234", properties: { ownerID: "owner@test.com" } },
});
if (check.decision) {
// Authorized - proceed with the operation
}
Environment Variables
The SDK does not load environment variables automatically. Use a library like dotenv in your application:
import dotenv from "dotenv";
dotenv.config();
init({
apiKey: process.env.JLINC_API_KEY!,
apiUrl: process.env.JLINC_API_URL!,
archiveUrl: process.env.JLINC_ARCHIVE_URL!,
archiveKey: process.env.JLINC_ARCHIVE_KEY!,
fedidUrl: process.env.JLINC_FEDID_URL!,
});
Type Exports
export type {
AuthSubject,
AuthAction,
AuthResource,
AuthParams,
GetDomainsParams,
CreateEntityParams,
GetEntityParams,
ProduceAgreementParams,
ProcessAgreementParams,
GetAgreementParams,
ProduceEventParams,
GetEventParams,
ProcessEventParams,
GetAuditsParams,
VerifyAuditParams,
AuditResult,
};
export {
init,
auth,
getDomains,
createEntity,
getEntity,
produceAgreement,
processAgreement,
getAgreement,
produceEvent,
getEvent,
processEvent,
getAudits,
verifyAudit,
};
Error Handling
The SDK uses JavaScript Promise rejection for network errors and non-2xx responses. Wrap calls in try/catch blocks:
try {
const agreement = await produceAgreement({
data: { permitted: ["data-sharing"] },
shortName: "my-entity@domain.tld",
role: "provider",
});
} catch (error) {
if (error.response) {
// Server responded with an error status
console.error(error.response.status, error.response.data);
} else if (error.request) {
// Request was made but no response received
console.error("No response from server");
} else {
// Something else went wrong
console.error(error.message);
}
}
Documentation
Full usage guide: docs.jlinc.io/usage-guide