Metadata-Version: 2.4
Name: burnledger
Version: 0.6.0
Summary: Python SDK for the BurnLedger API
License-Expression: MIT
Project-URL: Homepage, https://burnledger.io/docs/
Project-URL: Documentation, https://burnledger.io/docs/
Project-URL: Verifier, https://burnledger.io/verify/
Project-URL: Support, https://burnledger.io/contact/
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: httpx>=0.25.0
Requires-Dist: cryptography>=41.0.0
Provides-Extra: dev
Requires-Dist: pytest>=7.0; extra == "dev"
Requires-Dist: pytest-asyncio>=0.21; extra == "dev"
Requires-Dist: respx>=0.21; extra == "dev"
Requires-Dist: mypy==2.3.1; extra == "dev"
Dynamic: license-file

# BurnLedger Python SDK

Python client for the [BurnLedger](https://burnledger.io) 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

```bash
pip install burnledger
```

**Requirements:** Python 3.10+

## Quick Start

```python
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

```python
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:

```python
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
```

## Webhook Verification

Verify incoming webhook signatures (HMAC-SHA256):

```python
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

```python
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:

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

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

## License

MIT
