# Optropic Python SDK

Official Python SDK for the [Optropic Trust API](https://docs.optropic.com). Register, verify, and manage digital assets with cryptographic signatures and an immutable audit trail.

Verify any asset at [verify.optropic.com](https://verify.optropic.com).

## Installation

```bash
pip install optropic
```

Requires Python 3.9 or later.

## Quick Start

```python
from optropic import Optropic, CreateAssetParams

client = Optropic(api_key="optr_live_...")

# Create a new asset
result = client.assets.create(
    CreateAssetParams(product_name="Luxury Handbag", sku="LH-001", vertical="fashion")
)

asset = result["asset"]
seal  = result["seal"]

print(f"Asset ID: {asset['id']}")
print(f"Seal signature: {seal['signature']}")
```

## Authentication

Every request requires an API key passed via the `X-API-Key` header. The SDK handles this automatically:

```python
# Production
client = Optropic(api_key="optr_live_...")

# Sandbox / testing
client = Optropic(api_key="optr_test_...")
```

## Resources

### Assets

```python
from optropic import CreateAssetParams, UpdateAssetParams, ListAssetsParams

# Create
result = client.assets.create(CreateAssetParams(product_name="Widget"))

# List (with optional filters)
result = client.assets.list(ListAssetsParams(vertical="fashion", page=1, per_page=25))

# Get by ID
result = client.assets.get("asset_id")

# Update
result = client.assets.update("asset_id", UpdateAssetParams(sku="NEW-SKU"))

# Revoke
result = client.assets.revoke("asset_id", reason="Counterfeit detected")
```

### Audit Log

```python
from optropic import ListAuditParams

# List events
result = client.audit.list(ListAuditParams(event_type="asset.created", limit=50))

# Get a single event
event = client.audit.get("event_id")

# Create a custom audit event
result = client.audit.create(
    event_type="custom.action",
    resource_id="res_123",
    resource_type="order",
    details={"note": "Manual review completed"},
)
```

### Compliance

```python
from optropic import ExportParams

# Verify the full audit chain
result = client.compliance.verify_chain()
print(f"Chain valid: {result.chain_valid}")

# List Merkle roots
roots = client.compliance.list_merkle_roots()

# Get a Merkle inclusion proof
proof = client.compliance.get_merkle_proof("event_id")

# Export audit data as a signed CSV
export = client.compliance.export_audit(ExportParams(from_date="2025-01-01"))

# Get / update compliance config
config = client.compliance.get_config()
config = client.compliance.update_config(mode="strict")
```

### Webhooks

```python
from optropic import CreateWebhookParams, verify_webhook_signature

# Register an endpoint
result = client.webhooks.create(
    CreateWebhookParams(url="https://example.com/hook", events=["asset.created"])
)
secret = result["secret"]

# List endpoints
endpoints = client.webhooks.list()

# Delete an endpoint
client.webhooks.delete("endpoint_id")

# List deliveries for an endpoint
deliveries = client.webhooks.list_deliveries("endpoint_id")
```

### API Keys

```python
from optropic import CreateKeyParams

# Create a new key
result = client.keys.create(
    CreateKeyParams(environment="live", tenant_id="tenant_123", label="CI deploys")
)
print(result["key"])  # shown only once

# List keys
keys = client.keys.list()

# Update a key label
client.keys.update("key_id", label="Renamed key")

# Revoke a key
client.keys.revoke("key_id")
```

## Webhook Signature Verification

When you receive a webhook, verify its cryptographic signature before processing:

```python
from optropic import verify_webhook_signature

payload   = request.body.decode("utf-8")
signature = request.headers["X-Optropic-Signature"]
secret    = "whsec_..."

if verify_webhook_signature(payload, signature, secret):
    # Signature is valid -- process the event
    ...
else:
    # Reject the request
    ...
```

## Error Handling

The SDK raises typed exceptions for API errors:

```python
from optropic import (
    OptropicError,
    AuthenticationError,
    ForbiddenError,
    NotFoundError,
    ValidationError,
    RateLimitError,
)

try:
    client.assets.get("nonexistent_id")
except NotFoundError as e:
    print(f"Not found: {e.message}")
except RateLimitError:
    print("Slow down -- too many requests")
except OptropicError as e:
    print(f"API error {e.status_code}: {e.message}")
```

## Verification

Any asset created through the API can be independently verified at
[verify.optropic.com](https://verify.optropic.com).

## License

MIT
