Metadata-Version: 2.4
Name: blacksands-bursar
Version: 0.2.0
Summary: Python SDK for the Blacksands Bursar API
License: MIT
Requires-Python: >=3.9
Description-Content-Type: text/markdown
Requires-Dist: requests>=2.28.0
Provides-Extra: dev
Requires-Dist: pytest>=7.0; extra == "dev"
Requires-Dist: pytest-mock; extra == "dev"
Requires-Dist: responses>=0.23; extra == "dev"

# Blacksands Bursar Python SDK

Python client for the Blacksands Bursar API -- zero-trust network security provisioning, certificate management, compliance, and posture verification.

## Installation

```bash
pip install blacksands-shield
```

For development:

```bash
pip install -e ".[dev]"
```

## Quickstart

```python
from blacksands_shield import ShieldClient

client = ShieldClient(
    client_cert="/path/to/client.crt",
    client_key="/path/to/client.key",
    ca_cert="/path/to/blacksands-ca.crt",  # optional
)

# Create an organisation
org = client.orgs.create("Acme Corp", "admin@acme.com", tier="professional")
org_id = org["data"]["id"]

# Register an application
app = client.apps.register(org_id, "web-frontend", framework="react")
app_id = app["data"]["id"]

# Generate a certificate
cert = client.certs.generate(app_id)

# Provision an endpoint
ep = client.endpoints.provision(app_id, "ingress", "api.acme.com", 443)

# Run a verification scan and wait for results
scan = client.verify.scan(app_id)
result = client.operations.poll_until_complete(scan["data"]["operationId"])
```

## Authentication

The Shield SDK uses mTLS client certificates exclusively. API-key authentication is not supported. An mTLS certificate bundle is issued by a Blacksands administrator through Overwatch or SysAdmin and delivered to the caller out-of-band.

```python
client = ShieldClient(
    client_cert="/path/to/client.crt",
    client_key="/path/to/client.key",
)
```

The cert and key are attached to the underlying `requests` session via `session.cert`.

## Configuration

```python
client = ShieldClient(
    client_cert="/path/to/client.crt",
    client_key="/path/to/client.key",
    ca_cert="/path/to/blacksands-ca.crt",       # optional: pin the server cert
    base_url="https://shield.blacksands.io/v1",  # default
    timeout=30,       # seconds, default
    max_retries=3,    # retries on 5xx / connection errors, default
)
```

## API Reference

### Organisations -- `client.orgs`

| Method | Description |
|--------|-------------|
| `create(name, contact_email, tier="essentials")` | Create a new organisation |
| `get(org_id)` | Get organisation by ID |
| `list(limit=20, offset=0)` | List organisations |
| `update(org_id, **kwargs)` | Update organisation fields |

### Applications -- `client.apps`

| Method | Description |
|--------|-------------|
| `register(org_id, name, version=None, framework=None, runtime=None)` | Register a new app |
| `get(app_id)` | Get application by ID |
| `list(org_id, limit=20, offset=0)` | List apps for an org |
| `decommission(app_id)` | Decommission an app |

### Certificates -- `client.certs`

| Method | Description |
|--------|-------------|
| `generate(app_id, algorithm="RSA-2048")` | Generate a new certificate |
| `rotate(app_id, cert_id)` | Rotate a certificate |
| `revoke(app_id, cert_id)` | Revoke a certificate |
| `list(app_id, limit=20, offset=0)` | List certificates for an app |

### Endpoints -- `client.endpoints`

| Method | Description |
|--------|-------------|
| `provision(app_id, type, hostname, port, protocol="https")` | Provision an endpoint |
| `list(app_id, limit=20, offset=0)` | List endpoints |
| `verify(app_id, endpoint_id)` | Verify an endpoint |
| `deprovision(app_id, endpoint_id)` | Deprovision an endpoint |

### Policies -- `client.policies`

| Method | Description |
|--------|-------------|
| `create(app_id, type, name, rules, priority=1)` | Create a policy |
| `update(app_id, policy_id, **kwargs)` | Update a policy |
| `apply(app_id)` | Apply all policies for an app |
| `list(app_id, limit=20, offset=0)` | List policies |

### DNS -- `client.dns`

| Method | Description |
|--------|-------------|
| `allow(app_id, domain, reason=None)` | Allow a domain |
| `block(app_id, domain, reason=None)` | Block a domain |
| `scan(app_id)` | Trigger a DNS scan |
| `report(app_id)` | Get DNS report |

### Manifests -- `client.manifests`

| Method | Description |
|--------|-------------|
| `submit(org_id, manifest)` | Submit a deployment manifest |
| `get(manifest_id)` | Get manifest by ID |
| `provision(manifest_id)` | Provision from manifest |
| `diff(manifest_id, compare_version)` | Diff against a version |

### Verify -- `client.verify`

| Method | Description |
|--------|-------------|
| `scan(app_id)` | Trigger a posture scan |
| `get_result(operation_id)` | Get scan result |
| `get_latest(app_id)` | Get latest posture |

### Compliance -- `client.compliance`

| Method | Description |
|--------|-------------|
| `get_status(app_id)` | Get compliance status |
| `generate_report(app_id, framework="SOC2")` | Generate a report |
| `get_controls(framework)` | List controls for a framework |
| `get_history(app_id)` | Get compliance history |

### Operations -- `client.operations`

| Method | Description |
|--------|-------------|
| `get_status(operation_id)` | Get operation status |
| `poll_until_complete(operation_id, timeout=120, interval=3)` | Block until done |

### Sessions -- `client.sessions`

| Method | Description |
|--------|-------------|
| `list(app_id)` | List active sessions |
| `get(session_id)` | Get session details |
| `terminate(session_id)` | Terminate a session |

## Error Handling

```python
from blacksands_shield.exceptions import (
    ShieldAPIError,
    AuthenticationError,
    RateLimitError,
    NotFoundError,
    ValidationError,
)

try:
    client.orgs.get("nonexistent")
except NotFoundError as e:
    print(f"Not found: {e} (HTTP {e.status_code})")
except AuthenticationError:
    print("Check your API key")
except RateLimitError:
    print("Slow down -- rate limit hit")
except ShieldAPIError as e:
    print(f"API error: {e}")
```

All exceptions carry `status_code` and `response` attributes.

## License

MIT
