Files
jlinc-sdk-node/README.md
T
2026-10-23 18:49:11 +00:00

20 KiB

JLINC SDK

npm version License: MIT

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