> ## Documentation Index
> Fetch the complete documentation index at: https://docs.truthlocks.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Python SDK

> The official Python SDK for the Truthlocks verification platform. Type-safe, with automatic retries and comprehensive error handling.

<Info>
  **Requirements:** Python 3.9 or later. Uses `httpx` for HTTP requests.
</Info>

## Installation

```bash theme={null}
pip install truthlock
```

## Quick Start

```python quickstart.py theme={null}
from truthlock import TruthlockClient, Algorithm, Verdict

client = TruthlockClient(api_key="your-api-key")

# Create an issuer
issuer = client.issuers.create(
    name="My Organization",
    legal_name="My Organization Inc.",
)

# Register a key
key = client.keys.register(
    issuer_id=issuer.id,
    kid="key-1",
    alg=Algorithm.ED25519.value,
    public_key_b64url="your-base64url-public-key",
)

# Mint an attestation
attestation = client.attestations.mint(
    issuer_id=issuer.id,
    kid="key-1",
    alg=Algorithm.ED25519.value,
    payload_b64url="base64url-encoded-payload",
)
print(f"Attestation: {attestation.attestation_id}")

# Verify
result = client.verify.verify_online(
    attestation_id=attestation.attestation_id,
)
if result.verdict == Verdict.VALID:
    print("Verified successfully")
```

## Client Configuration

```python theme={null}
client = TruthlockClient(
    api_key="your-api-key",
    base_url="https://api.truthlocks.com",  # default
    environment="production",                 # or "sandbox"
    timeout=30.0,                             # seconds
    max_retries=3,                            # auto-retry on failures
    idempotency_prefix="my-app",             # prefix for auto-generated idempotency keys
)
```

## Idempotency keys

The SDK generates an `Idempotency-Key` header automatically on every write operation (mint, revoke, supersede), making retries safe by default. If a request fails and is retried, the server returns the original response instead of performing the action twice.

When multiple applications share the same tenant, set `idempotency_prefix` to namespace the auto-generated keys and prevent collisions:

```python theme={null}
# Service A
billing_client = TruthlockClient(
    api_key="tl_live_...",
    idempotency_prefix="billing-svc",
)

# Service B
onboarding_client = TruthlockClient(
    api_key="tl_live_...",
    idempotency_prefix="onboarding-svc",
)
```

With these prefixes, a key generated by the billing service looks like `billing-svc_<uuid>`, while onboarding produces `onboarding-svc_<uuid>` — so concurrent requests from different services never conflict.

<Info>
  Idempotency keys expire after 24 hours. If you omit `idempotency_prefix`, keys are generated without a prefix and are still unique per request.
</Info>

## Retry behavior

When `max_retries` is set (default: 3), the SDK automatically retries failed requests using exponential backoff with jitter.

### What gets retried

The SDK retries when **all** of the following are true:

* The HTTP status code is retryable: `408`, `429`, `500`, `502`, `503`, or `504`
* The retry count has not exceeded `max_retries`

Responses with `400`, `401`, `403`, `404`, or `409` are **not** retried.

### Backoff schedule

| Attempt   | Base delay | With jitter (approx.) |
| :-------- | :--------- | :-------------------- |
| 1st retry | 100 ms     | 80–120 ms             |
| 2nd retry | 200 ms     | 160–240 ms            |
| 3rd retry | 400 ms     | 320–480 ms            |

The delay doubles on each attempt, capped at 2 seconds. If the API returns a `429` with a `Retry-After` header, the SDK waits the server-specified duration instead.

### Disabling retries

```python theme={null}
client = TruthlockClient(
    api_key="your-api-key",
    max_retries=0,  # No automatic retries
)
```

<Info>
  The SDK generates idempotency keys automatically for write operations, so retries for mint and revoke calls are safe. See [idempotency keys](#idempotency-keys) to configure a prefix for multi-service environments.
</Info>

## Error handling

```python theme={null}
from truthlock import TruthlockClient, TruthlockError, NotFoundError

client = TruthlockClient(api_key="your-key")

try:
    att = client.attestations.get("nonexistent-id")
except NotFoundError:
    print("Attestation not found")
except TruthlockError as e:
    print(f"API error {e.status_code}: {e.error_code} - {e}")
```

## Revoke an attestation

Permanently invalidate an attestation. Once revoked, any verification check returns `REVOKED`. This action cannot be undone — if you need to issue an updated credential instead, use [supersede](#supersede-an-attestation).

```python revoke.py theme={null}
from truthlock import TruthlockClient, Verdict

client = TruthlockClient(api_key="tl_live_...")

revoked = client.attestations.revoke(
    "660e8400-e29b-41d4-a716-446655440001",
    reason="Certificate holder no longer employed",
)

print(f"Status: {revoked['status']}")        # "REVOKED"
print(f"Revoked at: {revoked['revoked_at']}")

# Subsequent verifications reflect the revocation
check = client.verify.verify_online(
    attestation_id="660e8400-e29b-41d4-a716-446655440001",
)
print(f"Verdict: {check.verdict}")           # "REVOKED"
```

<Warning>
  Revocation is permanent and recorded in the transparency log. You cannot undo it. Use **supersede** if you need to replace a credential with an updated version.
</Warning>

## Supersede an attestation

Replace an existing attestation with an updated version. The original is marked as `SUPERSEDED` and linked to the new one, creating an auditable version chain.

```python supersede.py theme={null}
import base64
import json
from truthlock import TruthlockClient

client = TruthlockClient(api_key="tl_live_...")

# Supersede an attestation with updated claims
updated_payload = base64.urlsafe_b64encode(
    json.dumps({
        "student_name": "Jane Doe",
        "degree_type": "Master of Science",  # Updated from Bachelor to Master
    }).encode()
).decode().rstrip("=")

result = client.attestations.supersede(
    attestation_id="660e8400-e29b-41d4-a716-446655440001",  # Original attestation ID
    payload_b64url=updated_payload,
)

print(f"Old attestation status: {result['old']['status']}")  # "SUPERSEDED"
print(f"New attestation ID: {result['new']['attestation_id']}")
```

<Info>
  Both the original and new attestation remain in the transparency log. Verifiers can trace the full chain using the `superseded_by_attestation_id` field on the original.
</Info>

## Context Manager

Use the client as a context manager to ensure proper cleanup of HTTP connections.

```python theme={null}
with TruthlockClient(api_key="your-key") as client:
    issuers = client.issuers.list()
    for issuer in issuers:
        print(f"{issuer.name} ({issuer.id})")
```

## API Reference

### Issuers

* `client.issuers.create(name, ...)` - Create a new issuer
* `client.issuers.list(limit, offset)` - List issuers
* `client.issuers.get(issuer_id)` - Get issuer by ID
* `client.issuers.trust(issuer_id)` - Trust an issuer

### Attestations

* `client.attestations.mint(...)` - Mint a new attestation
* `client.attestations.get(id)` - Get attestation details
* `client.attestations.list(limit, offset)` - List attestations
* `client.attestations.revoke(id, reason)` - Revoke an attestation
* `client.attestations.supersede(attestation_id, payload_b64url)` - [Supersede an attestation](/api-reference/attestations/supersede)
* `client.attestations.proof_bundle(id)` - Get proof bundle

### Receipts

* `client.receipts.mint(req)` - Mint a signed receipt
* `client.receipts.get(receipt_id)` - Get receipt by ID
* `client.receipts.list(filter?)` - List receipts with optional filters
* `client.receipts.revoke(receipt_id, reason?)` - Revoke a receipt
* `client.receipts.list_types()` - List available receipt types
* `client.receipts.get_type(name)` - Get a receipt type by name
* `client.receipts.get_proof_bundle(receipt_id)` - [Get proof bundle](/api-reference/receipts/proof-bundle)
* `client.receipts.verify(receipt_id)` - [Verify a receipt](/api-reference/receipts/verify)
* `client.receipts.search(**kwargs)` - [Search receipts](/api-reference/receipts/search)
* `client.receipts.export(format, filters?)` - [Queue bulk export](/api-reference/receipts/export)
* `client.receipts.get_export(export_id)` - Get export job status
* `client.receipts.redact(receipt_id)` - [Redact receipt payload](/api-reference/receipts/redact)

### Verify

* `client.verify.verify_online(attestation_id, payload_b64url)` - Verify attestation online

### Audit

* `client.audit.query(params)` - Query audit events with filters
* `client.audit.export(data)` - Start an async audit log export

### Governance

* `client.governance.list_requests()` - List governance requests
* `client.governance.create_request(data)` - Create a governance request
* `client.governance.approve_request(id)` - Approve a pending request
* `client.governance.execute_request(id)` - Execute an approved request

## Querying audit logs

Retrieve audit events to track API activity, monitor security events, and generate compliance reports. Filter by action, actor, resource, or time range.

```python audit.py theme={null}
from truthlock import TruthlockClient

client = TruthlockClient(api_key="tl_live_...")

# Query recent attestation events
events = client.audit.query(
    action="attestation.mint",
    from_date="2026-01-01T00:00:00Z",
    to_date="2026-01-31T23:59:59Z",
    limit=100,
)

for event in events:
    print(f"[{event['timestamp']}] {event['action']} by {event['actor_id']}")

# Export audit logs for compliance
export_job = client.audit.export(
    start_date="2026-01-01",
    end_date="2026-01-31",
    format="csv",
)
print(f"Export started: {export_job['id']} (status: {export_job['status']})")
```

See the [audit API reference](/api-reference/audit/query) for the full list of filter parameters.

## Governance workflows

Manage formal issuer actions — suspend, revoke, reinstate, and change trust tier — through a multi-party approval workflow. Create a request, collect approvals from authorized reviewers, then execute.

```python governance.py theme={null}
from truthlock import TruthlockClient

client = TruthlockClient(api_key="tl_live_...")

# Create a governance request to suspend an issuer
req = client.governance.create_request(
    action="suspend",
    issuer_id="issuer-uuid",
    reason="Compliance review pending",
)
print(f"Request created: {req['id']} (status: {req['status']})")

# Another authorized user approves the request
client.governance.approve_request(req["id"])

# Execute the approved request
result = client.governance.execute_request(req["id"])
print(f"Executed: {result['id']} — issuer is now {result['issuer_status']}")

# List all governance requests
requests = client.governance.list_requests()
for r in requests:
    print(f"[{r['status']}] {r['action']} {r['issuer_id']} — {r['reason']}")
```

See the [governance API reference](/api-reference/governance/list-requests) for the full request and response schemas.

## Audit queries and exports

Query audit events with filters and export logs for compliance reporting. Use `client.audit.query()` to search events and `client.audit.export()` to start an asynchronous export job.

```python audit.py theme={null}
from truthlock import TruthlockClient

client = TruthlockClient(api_key="tl_live_...")

# Query recent attestation events
events = client.audit.query(
    action="attestation.mint",
    from_date="2026-06-01T00:00:00Z",
    to_date="2026-06-30T23:59:59Z",
    limit=100,
)
for evt in events["data"]:
    print(f"[{evt['timestamp']}] {evt['action']} by {evt['actor_id']}")

# Start an async export for compliance
export_job = client.audit.export(
    from_date="2026-01-01T00:00:00Z",
    to_date="2026-06-30T23:59:59Z",
    report_type="soc2",  # "soc2", "gdpr", "hipaa", or omit for raw
    format="json",
)
print(f"Export started: {export_job['id']} (status: {export_job['status']})")
```

See [audit logs](/security/audit) for the full event structure, filter parameters, and retention policies.

## Receipt operations

Mint, verify, search, export, and redact cryptographically signed receipts. See the [receipts guide](/guides/receipts) for an overview of receipt types and the full lifecycle.

```python receipts.py theme={null}
from truthlock import TruthlockClient, MintReceiptRequest

client = TruthlockClient(api_key="tl_live_...")

# Mint a payment receipt
receipt = client.receipts.mint(MintReceiptRequest(
    issuer_id="your-issuer-id",
    kid="ed-key-2026",
    alg="Ed25519",
    receipt_type="payment_receipt",
    subject="customer@example.com",
    payload={
        "amount": 5000,
        "currency": "USD",
        "provider": "stripe",
        "provider_reference": "ch_3Px...",
    },
))

# Verify a receipt
result = client.receipts.verify(receipt.receipt_id)
print(f"Verdict: {result['verdict']}")  # "VALID"

# Download a proof bundle for offline verification
bundle = client.receipts.get_proof_bundle(receipt.receipt_id)

# Search receipts
results = client.receipts.search(
    q="stripe",
    receipt_type="payment_receipt",
    from_date="2026-01-01T00:00:00Z",
)

# Export receipts as JSON
export_job = client.receipts.export(
    format="json",
    filters={"receipt_type": "payment_receipt"},
)
# Poll for completion
completed = client.receipts.get_export(export_job["id"])
if completed["status"] == "complete":
    print(f"Download: {completed['download_url']}")

# Redact PII from a receipt (permanent, for GDPR erasure requests)
client.receipts.redact(receipt.receipt_id)
```

See the [receipts API reference](/api-reference/receipts/mint) for the full request and response schemas.
