Skip to main content

BurnLedger Python SDK

Python 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

pip install burnledger

Requirements: Python 3.10+

Quick Start

from burnledger import BurnLedger

with BurnLedger(api_key="dp_...") as dp:
    # 1. Attest — snapshot systems before deletion
    att = dp.attest("user@example.com", system_ids=["sys_abc", "sys_def"])

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

    # 3. Verify — confirm deletion and get certificate
    result = dp.verify(att.id, "user@example.com", timeout=60)

    # 4. Download certificate PDF
    dp.save_pdf(result.certificate.id, "./deletion-cert.pdf")

Async

from burnledger import AsyncBurnLedger

async with AsyncBurnLedger(api_key="dp_...") as dp:
    att = await dp.attest("user@example.com", system_ids=["sys_abc"])
    result = await dp.verify(att.id, "user@example.com", timeout=60)

Offline Verification

Verify certificates without network access using Ed25519 signatures:

from burnledger import verify_certificate, verify_transparency, PublicKeyInfo

key = PublicKeyInfo.from_hex("abcdef...", revoked=False)
keys = {key.key_id: key}

cert_result = verify_certificate(certificate, keys)
# VerificationResult.VALID or raises VerificationError

log_result = verify_transparency(certificate, keys)
# TransparencyResult.INCLUDED or raises VerificationError

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 — raises 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 raises its subclass UnsupportedFormatVersionError.

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:

from burnledger import AUTHORIZATION_POLICY_FAILED, AuthorizationPolicy

policy = AuthorizationPolicy(customer_key_ids={"cust_k_..."})  # every system, your key
verify_certificate(certificate, keys, require_authorization=policy)

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 raises VerificationError with code == AUTHORIZATION_POLICY_FAILED, naming the reason. A record older than 9.0 always fails: nothing signs its authorization field. 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.

from burnledger import BurnLedger, generate_key_group

# 1. A key group: n Ed25519 members, any t of which can act.
generated = generate_key_group(members=2, threshold=1)
seeds = [s.export_seed() for s in generated.signers]  # store these offline, now

with BurnLedger(api_key="dp_...") as 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.
    enclave = dp.attest_enclave_identity(expected_pcr0=PINNED_PCR0)

    # 3. Enrol the group for your team. Every member signs, over the latest the
    #    authorization may end (`not_after`, by default the enclave's 90-day maximum).
    dp.enroll_key_group(
        team_id=TEAM_ID, group=generated.group, signers=generated.signers, enclave=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.
    result = dp.create_registered_system(
        team_id=TEAM_ID,
        group=generated.group,
        signers=generated.signers[:1],
        enclave=enclave,
        name="users-db",
        connector_type="postgresql",
        dsn="postgres://...",
        subject_query="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.
    successor = generate_key_group(members=2, threshold=1)
    dp.rotate_key_group(
        team_id=TEAM_ID,
        previous_group=generated.group,
        previous_signers=generated.signers[:1],
        next_group=successor.group,
        next_signers=successor.signers,
        enclave=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 raises 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 not_after 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. verify_key_enrollment_statement, verify_team_authorization and verify_system_registration re-check a stored document against the enclave key. One thing the client cannot check is the registration's query_template_hash: the server computes it over the template after normalizing it, and the SDK has no normalizer. 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.

A signer is anything with a public_key and a sign(payload) -> bytes method (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 register_system_with_key, given the exact config bytes it was created with. AsyncBurnLedger has the same methods.

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.not_after 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 (export_seed()) as you would any credential that cannot be reissued.

Webhook Verification

Verify incoming webhook signatures (HMAC-SHA256):

from burnledger import verify_webhook_signature

valid = verify_webhook_signature(
    secret=webhook_secret,         # from dp.register_webhook()
    body=request.body,             # raw request body
    signature=request.headers["X-BurnLedger-Signature"],
)

API Reference

Client

BurnLedger(
    api_key: str,
    *,
    base_url: str = "https://api.burnledger.io",
    timeout: float = 30.0,
    max_retries: int = 2,
)

Systems

Method Returns
register_system(**opts) System
get_system(id) System
list_systems(limit=25) SyncPaginator[System]
deregister_system(id) None
health_check(id) System

Attestations

Method Returns
attest(subject, **opts) Attestation
batch_attest(subjects, **opts) BatchAttestationResponse
get_attestation(id) Attestation
wait_for(id, **opts) Attestation
verify(id, subject, **opts) VerifyResult

Certificates

Method Returns
get_certificate(id) CertificateResponse
list_certificates(limit=25) SyncPaginator[CertificateResponse]
get_certificate_stats() CertificateStats
export_certificates(**opts) bytes
download_pdf(id) bytes
save_pdf(id, path) None
get_revocation_status(id) RevocationStatus
revoke_certificate(id, reason=...) CertificateResponse
batch_revoke_certificates(ids, reason=...) BatchRevokeResponse

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

Webhooks

Method Returns
register_webhook(url=...) Webhook
list_webhooks(limit=25) SyncPaginator[Webhook]
delete_webhook(id) None
rotate_webhook_secret(id) WebhookRotateResponse
commit_webhook_rotation(id) None
list_failed_deliveries(limit=25) SyncPaginator[FailedDelivery]
retry_delivery(delivery_id) None
resolve_delivery(delivery_id) None

API Keys

Method Returns
list_api_keys() list[ApiKeyListItem]
create_api_key(role=..., team_id=None) ApiKeyResponse
revoke_api_key(id) None

Transparency Log

These methods do not require authentication.

Method Returns
get_log_head() SignedTreeHead
get_log_entry(index) LogEntry
get_log_entries(start, end) list[LogEntry]
get_inclusion_proof(index, tree_size) InclusionProof
get_consistency_proof(old_size, new_size) ConsistencyProof

Pagination

All list_* methods return a paginator that auto-fetches pages:

for cert in dp.list_certificates():
    print(cert.id)

# async
async for cert in dp.list_certificates():
    print(cert.id)

License

MIT

Metadata

Release files for burnledger 0.9.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for burnledger 0.9.0
File Size Uploaded Provenance
burnledger-0.9.0.tar.gz 173.8 kB Without provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for burnledger 0.9.0
File Interpreter ABI Platform
burnledger-0.9.0-py3-none-any.whl Python 3 none any Details

Total release size: 276.0 kB

Release files / burnledger-0.9.0.tar.gz

Download URL burnledger-0.9.0.tar.gz
Size 173.8 kB
Tags Source
SHA-256 checksum
How to use checksums
4acf29e5c2af56d360c6a82a58f9b5c72896f170dadbb4b77484e691627324d8
BLAKE2b-256 checksum
How to use checksums
1b17cc2e604593f872da37e63e2f16248b44ae892adb28ddf92c5c2b2ddb9bc4
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.12.14

Release files / burnledger-0.9.0-py3-none-any.whl

Download URL burnledger-0.9.0-py3-none-any.whl
Size 102.2 kB
Tags Python 3
SHA-256 checksum
How to use checksums
9f48697d7bf1fc88663e63adfe45b3629a65cac10562150c13fa6a1f1a9ebb0e
BLAKE2b-256 checksum
How to use checksums
2733059a6eb02230073026cf097865c25ab54f37df909c56a4760fe533acf4d1
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.12.14

Security for burnledger 0.9.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page