npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

burnledger

v0.10.0

Published

TypeScript SDK for the BurnLedger API — cryptographic deletion certificates

Readme

BurnLedger TypeScript SDK

TypeScript/JavaScript client for the BurnLedger API — cryptographic deletion certificates for regulatory compliance.

Scope

The SDK covers the data plane: systems, attestations, certificates, webhooks, API keys, /v1/me, and the transparency log — everything involved in measuring a datastore and verifying what came back.

Account management (teams, TOTP enrolment, subscriptions, invoices, the audit log, billing) is deliberately not part of the SDK. Those are administrative actions people take once, in the dashboard at https://dashboard.burnledger.io, and they are available on the raw /v1 API for anyone who needs to automate them.

Install

npm install burnledger

Requirements: Node.js 18+

Quick Start

import { BurnLedger } from "burnledger";

const dp = new BurnLedger({ apiKey: "dp_..." });

// 1. Attest — snapshot systems before deletion
const att = await dp.attest("[email protected]", {
  systemIds: ["sys_abc", "sys_def"],
});

// 2. Delete data (your code, your tools)

// 3. Verify — confirm deletion and get certificate
const result = await dp.verify(att.id, "[email protected]", { timeout: 60 });

// 4. Download certificate PDF
await dp.savePdf(result.certificate!.id, "./deletion-cert.pdf");

Browser Support

For browser environments (verification only, no API client):

import { verifyCertificate, publicKeyFromHex } from "burnledger/browser";

Uses WebCrypto (crypto.subtle) instead of node:crypto. Requires Chrome 113+, Firefox 128+, or Safari 17+.

Offline Verification

Verify certificates without network access using Ed25519 signatures:

import {
  verifyCertificate,
  verifyTransparency,
  publicKeyFromHex,
} from "burnledger";

const key = await publicKeyFromHex("abcdef...", { revoked: false });
const keys = new Map([[key.keyId, key]]);

const certResult = await verifyCertificate(certificate, keys);
// "VALID" or a soft key verdict; throws VerificationError on failure

const logResult = await verifyTransparency(certificate, keys);
// "INCLUDED" | "NOT_AVAILABLE"; throws VerificationError on an invalid proof

Malformed input — a missing field, a value of the wrong JSON type, bytes that are neither hex nor base64, a hash or signature of the wrong length — throws VerificationError too, with a message naming the field (systems[0].query_hash is 31 bytes, expected 32). A record in a format this version cannot read throws it with code === UNSUPPORTED_FORMAT_VERSION.

Requiring a registration

From format 9.0 every system in a record says whether the enclave checked it against a registration (authorization: certified, with the customer_key_id it was registered under) or not (authorization: none). A none record is still genuine, and verifies by default: whoever relays requests to the enclave can always leave a registration out, and the enclave cannot refuse that. What nothing but you can do is refuse the record, so ask for it, naming your key id:

import { AUTHORIZATION_POLICY_FAILED, verifyCertificate } from "burnledger";

await verifyCertificate(certificate, keys, {
  requireAuthorization: { customerKeyIds: ["cust_k_..."] }, // every system, your key
});

What a pass means: every required system is certified under a key id you named, in a record from an enclave image of wire protocol 12 or later — the first that registers a system only with the signatures of the key group that id names. So that key group signed the registration. Earlier images, which include every image that has issued format 9.0 so far, could register a system with no customer signature, so a certified record from one of them fails the policy, and so does a record that names no image. The key id is required for the same reason: certified under a key you did not name could be under a key group the relay enrolled itself.

systems narrows the requirement to the system ids you name. A record that does not meet the policy throws VerificationError with code === AUTHORIZATION_POLICY_FAILED, naming the reason. A record older than 9.0 always fails: nothing signs its authorization field. verifyCertificateWithStatus takes the same options as its last argument, and the CLI takes --require-certified or --require-certified-system <system_id>, each with --customer-key-id <id>.

Registering a system with your own key

ADR-025 lets a system's Verification Records say authorization: certified with your customer_key_id: before measuring it, the enclave checked that the system's config and query are the ones a key group you hold registered. That holds for records from an enclave image of protocol 12 or later; the verifiers say when a record comes from an earlier one, whose certified is not evidence. All of it happens on your machine, and the SDK never holds or sends a private key. Node only: ADR-025 makes a client you run locally the only place enrolment can happen, so these calls are not part of the browser entry point.

import { BurnLedger, generateKeyGroup } from "burnledger";

// 1. A key group: n Ed25519 members, any t of which can act.
const { group, signers } = await generateKeyGroup({ members: 2, threshold: 1 });
const seeds = signers.map((s) => s.exportSeed()); // store these offline, now

const dp = new BurnLedger({ apiKey: "dp_..." });

// 2. Verify the enclave against the PCR0 you pinned out of band. Configs are
//    sealed to it, and everything below must come back signed by its key.
const enclave = await dp.attestEnclaveIdentity({ expectedPcr0: PINNED_PCR0 });

// 3. Enrol the group for your team. Every member signs, over the latest the
//    authorization may end (`notAfter`, by default the enclave's 90-day maximum).
await dp.enrollKeyGroup({ teamId: TEAM_ID, group, signers, enclave });

// 4. Seal the config to the enclave, create the system with only the sealed
//    bytes, and register it, signed at the group's threshold.
const { system, registration } = await dp.createRegisteredSystem({
  teamId: TEAM_ID,
  group,
  signers: signers.slice(0, 1),
  enclave,
  name: "users-db",
  connectorType: "postgresql",
  dsn: "postgres://...",
  subjectQuery: "SELECT id FROM users WHERE email = $1",
});

// 5. Rotate when you must. The OUTGOING group signs at its threshold and every
//    incoming member signs; nothing is re-registered.
const next = await generateKeyGroup({ members: 2, threshold: 1 });
await dp.rotateKeyGroup({
  teamId: TEAM_ID,
  previousGroup: group,
  previousSigners: signers.slice(0, 1),
  nextGroup: next.group,
  nextSigners: next.signers,
  enclave,
});

What each call checks. The API relays every document, so each one that comes back — the enrolment statement, the authorization, the registration — must verify under the signing key of the enclave you attested, or the call throws VerificationError. A relay that forged an enrolment would otherwise have you believe you were registered. The key comes from your attestation, not from /.well-known/burnledger-keys, which the same API serves. Then the enrolment must name the group you hold and the team you asked for, its authorization must end exactly at the notAfter you signed and not start in the future, so an older genuine answer or another team's is refused, and the registration must name the system, config and connector you signed. verifyKeyEnrollmentStatement, verifyTeamAuthorization and verifySystemRegistration re-check a stored document against the enclave key. Two of the registration's signed fields are deliberately not compared. Its query_template_hash is HashQuery over the template after the server normalizes it, while the request signs sha256 of the raw template, so the two are not byte-equal even for an honest registration and the SDK has no normalizer to reconcile them. registered_at is not freshness-checked either, as there is no signed bound to hold it to. So a relay could hand back an older genuine registration of this system, under this key and config, over a template you signed for it earlier; the customer path calls the check right after its own POST, which narrows that window but does not close it.

A signer is anything with a publicKey and an async sign(payload) (KeyGroupSigner), so a member can live in an HSM, a cloud KMS or another process; InMemorySigner is the convenience for keys on this machine. A system that already exists is registered with registerSystemWithKey, given the exact config bytes it was created with.

What the records then say. Every Verification Record of a registered system carries authorization: certified and your customer_key_id for it, and the CLI prints that key id on the system's Registration line, with what the record's enclave image makes of it. A system never registered says authorization: none. A record naming a key id that is not yours means someone else authorized that verification. The authorization behind your key expires; enrol the same group again before authorization.notAfter to renew it.

Custody. The private keys are yours alone: BurnLedger never sees them and cannot reissue them. Lose enough members that the group can no longer reach its threshold and it can neither register a system nor authorize its own rotation, and there is no recovery, because an operator who could re-enrol on your behalf is an operator who could mint (ADR-025 §3). The way back is a new group and re-registering every system, and records before and after will name different key ids, visibly. So enrol more members than the threshold and keep one offline — two members, threshold one, is the simplest. A recovery-phrase format is not defined yet; until it is, back up each member's 32-byte seed (exportSeed()) as you would any credential that cannot be reissued.

Webhook Verification

Verify incoming webhook signatures (HMAC-SHA256):

import { verifyWebhookSignature } from "burnledger";

const valid = verifyWebhookSignature(
  webhookSecret,       // from dp.registerWebhook()
  request.body,        // raw request body
  request.headers["x-burnledger-signature"],
);

API Reference

Client

new BurnLedger({
  apiKey: string,
  baseUrl?: string,    // default: "https://api.burnledger.io"
  timeout?: number,    // seconds, default: 30
  maxRetries?: number, // default: 2
})

Systems

| Method | Returns | |--------|---------| | registerSystem(opts) | Promise<System> | | getSystem(id) | Promise<System> | | listSystems(opts?) | Paginator<System> | | deregisterSystem(id) | Promise<void> | | healthCheck(id) | Promise<System> |

Attestations

| Method | Returns | |--------|---------| | attest(subject, opts?) | Promise<Attestation> | | batchAttest(subjects, opts?) | Promise<BatchAttestationResponse> | | getAttestation(id) | Promise<Attestation> | | waitFor(id, opts?) | Promise<Attestation> | | verify(id, subject, opts?) | Promise<VerifyResult> |

Certificates

| Method | Returns | |--------|---------| | getCertificate(id) | Promise<CertificateResponse> | | listCertificates(opts?) | Paginator<CertificateResponse> | | getCertificateStats() | Promise<CertificateStats> | | exportCertificates(opts?) | Promise<Uint8Array> | | downloadPdf(id) | Promise<Uint8Array> | | savePdf(id, path) | Promise<void> | | getRevocationStatus(id) | Promise<RevocationStatus> | | revokeCertificate(id, opts) | Promise<CertificateResponse> | | batchRevokeCertificates(ids, opts) | Promise<BatchRevokeResponse> |

batchRevokeCertificates takes up to MAX_BATCH_REVOKE (100) ids under one reason and resolves whether all, some or none were revoked: the failures are in errors, each naming the request index and certificateId, so a partially failed batch is inspected, not caught. More than 100 ids throws before any request is made; chunk larger sets by MAX_BATCH_REVOKE.

Webhooks

| Method | Returns | |--------|---------| | registerWebhook(opts) | Promise<Webhook> | | listWebhooks(opts?) | Paginator<Webhook> | | deleteWebhook(id) | Promise<void> | | rotateWebhookSecret(id) | Promise<WebhookRotateResponse> | | commitWebhookRotation(id) | Promise<void> | | listFailedDeliveries(opts?) | Paginator<WebhookDelivery> | | retryDelivery(deliveryId) | Promise<void> | | resolveDelivery(deliveryId) | Promise<void> |

API Keys

| Method | Returns | |--------|---------| | listApiKeys() | Promise<ApiKeyListItem[]> | | createApiKey({ teamId? }) | Promise<ApiKeyResponse> | | revokeApiKey(id) | Promise<void> |

Transparency Log

These methods do not require authentication.

| Method | Returns | |--------|---------| | getLogHead() | Promise<SignedTreeHead> | | getLogEntry(index) | Promise<LogEntry> | | getLogEntries(opts) | Promise<LogEntry[]> | | getInclusionProof(opts) | Promise<InclusionProof> | | getConsistencyProof(opts) | Promise<ConsistencyProof> |

Pagination

All list* methods return a Paginator that auto-fetches pages:

for await (const cert of dp.listCertificates()) {
  console.log(cert.id);
}

Dual ESM/CJS

The package ships both ESM and CommonJS builds:

// ESM
import { BurnLedger } from "burnledger";

// CommonJS
const { BurnLedger } = require("burnledger");

License

MIT