Metadata-Version: 2.4
Name: smartbroker-plus-sdk
Version: 0.3.0
Summary: Python SDK for the Smartbroker+ B2B API
Author-email: Steven England <github@steven-england.info>
License: MIT License
        
        Copyright (c) 2026 Steven England
        
        Permission is hereby granted, free of charge, to any person obtaining a copy
        of this software and associated documentation files (the "Software"), to deal
        in the Software without restriction, including without limitation the rights
        to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
        copies of the Software, and to permit persons to whom the Software is
        furnished to do so, subject to the following conditions:
        
        The above copyright notice and this permission notice shall be included in all
        copies or substantial portions of the Software.
        
        THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
        IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
        FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
        AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
        LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
        OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
        SOFTWARE.
        
Project-URL: Homepage, https://github.com/stevenengland/smartbroker-plus-sdk
Project-URL: Issues, https://github.com/stevenengland/smartbroker-plus-sdk/issues
Project-URL: Documentation, https://github.com/stevenengland/smartbroker-plus-sdk#readme
Project-URL: Changelog, https://github.com/stevenengland/smartbroker-plus-sdk/blob/main/CHANGELOG.md
Keywords: smartbroker,smartbroker-plus,trading,brokerage
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: Intended Audience :: Financial and Insurance Industry
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: Implementation :: CPython
Classifier: Topic :: Office/Business :: Financial :: Investment
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Typing :: Typed
Requires-Python: >=3.11
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: httpx
Requires-Dist: pydantic>=2.0
Provides-Extra: cli
Requires-Dist: typer>=0.12; extra == "cli"
Requires-Dist: rich>=13; extra == "cli"
Dynamic: license-file

# smartbroker-plus-sdk

[![Build](https://github.com/stevenengland/smartbroker-plus-sdk/actions/workflows/code_testing.yml/badge.svg)](https://github.com/stevenengland/smartbroker-plus-sdk/actions/workflows/code_testing.yml)
[![Python Versions](https://img.shields.io/badge/python-3.11%20%7C%203.12%20%7C%203.13-blue)](https://www.python.org)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
[![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](https://github.com/stevenengland/smartbroker-plus-sdk/pulls)

> *Typed Python SDK and CLI for the for the Smartbroker+ B2B API.*

> [!WARNING]
> **Alpha** — the public surface may still change. Unofficial project, not
> affiliated with Smartbroker+.

## Features

- **OAuth2 Authorization Code + PKCE helpers** — full RFC 7636 implementation; the SDK handles token exchange, refresh, and revocation.
- **Sync and async parity** — `SmartbrokerClient` and `AsyncSmartbrokerClient` expose identical resource surfaces.
- **Full B2B resource coverage** — orders, order modifications, portfolios, positions, account transactions, market state, tradeability, cost report, and profile.
- **2FA token header support** — automatic `X-2fa-Token` injection for order-modifying endpoints.
- **Typed end-to-end** — Pydantic v2 models throughout, ships `py.typed`.
- **RFC 9457 error mapping** — HTTP problem details mapped to a typed exception hierarchy.
- **Python 3.11+**

## Quick Start (sync)

```python
from smartbroker_plus_sdk import SmartbrokerClient, generate_code_verifier, generate_code_challenge

# 1. Generate PKCE pair before starting the authorization flow
verifier = generate_code_verifier()
challenge = generate_code_challenge(verifier)
# → Send `challenge` as `code_challenge` in the authorization URL

# 2. Create a client — it handles token exchange and refresh internally
client = SmartbrokerClient(
    base_url="https://api.smartbroker.de",
    token_url="https://auth.smartbroker.de/.../token",
    api_key="your_api_key",
    client_id="your_client_id",
    client_secret="your_client_secret",
)

# 3. Exchange the authorization code for tokens
tokens = client.exchange_authorization_code(
    code="authorization_code_from_callback",
    redirect_uri="https://your-app.com/callback",
    code_verifier=verifier,
)

# 4. Use resources
profile = client.profile.get_account_numbers()
print(profile.account_number)
```

## Quick Start (async)

```python
import asyncio
from smartbroker_plus_sdk import AsyncSmartbrokerClient, generate_code_verifier, generate_code_challenge

async def main() -> None:
    verifier = generate_code_verifier()
    challenge = generate_code_challenge(verifier)

    client = AsyncSmartbrokerClient(
        base_url="https://api.smartbroker.de",
        token_url="https://auth.smartbroker.de/.../token",
        api_key="your_api_key",
        client_id="your_client_id",
        client_secret="your_client_secret",
    )

    await client.exchange_authorization_code(
        code="authorization_code_from_callback",
        redirect_uri="https://your-app.com/callback",
        code_verifier=verifier,
    )

    profile = await client.profile.get_account_numbers()
    print(profile.account_number)

asyncio.run(main())
```

## Error Handling

All SDK errors extend `SmartbrokerApiError`:

```
SmartbrokerApiError
├── AuthenticationError       # HTTP 401 — invalid/expired credentials
├── AuthorizationError        # HTTP 403 — insufficient permissions
├── NotFoundError             # HTTP 404 — resource does not exist
├── ValidationError           # HTTP 400/412 — request validation failed
│   ├── OrderRejectedError    # messageKey: ORDER_REJECTED
│   ├── InsufficientBalanceError   # messageKey: INSUFFICIENT_BALANCE
│   └── BuyingPowerExceededError   # messageKey: BUYINGPOWER_EXCEEDED
├── ServerError               # HTTP 5xx — vendor server-side failure
├── TwoFactorError            # 2FA token flow failed (timeout, rejection)
└── RetryExhaustedError       # all retry attempts exhausted
```

```python
from smartbroker_plus_sdk.exceptions import SmartbrokerApiError, ValidationError

try:
    client.orders.create(...)
except ValidationError as e:
    print(f"Validation failed: {e}")
except SmartbrokerApiError as e:
    print(f"API error: {e}")
```

## CLI Quick Start

Install the package and run the CLI with `sbplus`:

```bash
# 1. Set credentials (or use env vars — see table below)
export SMARTBROKER_CLIENT_ID=my-client-id
export SMARTBROKER_CLIENT_SECRET=my-client-secret  # or enter at the prompt

# 2. Log in — prompts for environment (sand/prod), then opens browser for PKCE flow
sbplus auth login
# > Environment [sand/prod] (prod): prod
# > Credentials will be stored at ~/.cache/smartbroker-plus-sdk/tokens.json. Continue? [Y/n]:

# 3. Check status and browse data
sbplus auth status
sbplus portfolios list
sbplus orders list --portfolio <portfolioId>

# 4. Log out
sbplus auth logout
```

Use `--output` (or `-o`) to change the format: `table` (default), `wide`, `detail`, `json`.

For terminal demo recordings see [docs/cast/](docs/cast/).

### Environment Variables

| Variable | Flag | Description |
|---|---|---|
| `SMARTBROKER_BASE_URL` | `--base-url` | API base URL |
| `SMARTBROKER_ENV` | `--env` | Deployment environment (`sand` or `prod`) |
| `SMARTBROKER_AUTH_BASE_URL` | `--auth-base-url` | Auth/IdP base URL (defaults to `--base-url`) |
| `SMARTBROKER_API_KEY` | `--api-key` | API key |
| `SMARTBROKER_CLIENT_ID` | `--client-id` | OAuth2 client ID |
| `SMARTBROKER_CLIENT_SECRET` | `--client-secret` | OAuth2 client secret |
| `SMARTBROKER_REDIRECT_URI` | `--redirect-uri` | OAuth2 redirect URI (default: `http://127.0.0.1:8765/callback`) |
| `SMARTBROKER_CACHE_DIR` | `--cache-dir` | Token cache directory |
| `SMARTBROKER_OUTPUT` | `--output` / `-o` | Default output format (`table`, `wide`, `detail`, `json`) |
| `SMARTBROKER_TIMEOUT` | `--timeout` | HTTP request timeout in seconds (default: 30) |

### Exit Codes

| Code | Meaning |
|------|---------|
| `0` | Success |
| `1` | General error / unexpected exception |
| `2` | Authentication or authorization failure |
| `3` | Resource not found |
| `4` | Validation error |
| `5` | Server error |
| `6` | 2FA required |
| `7` | Retry limit exhausted |
| `130` | Interrupted (Ctrl-C) |

## Migration from pre-0.2 (`#41` rename)

The `smartbroker_plus_sdk.auth` sub-package was removed in 0.2.0. Update your imports:

```python
# Before (≤0.1.x)
from smartbroker_plus_sdk.auth import generate_code_verifier, generate_code_challenge
from smartbroker_plus_sdk.auth.exceptions import AuthenticationError

# After (≥0.2.0)
from smartbroker_plus_sdk import generate_code_verifier, generate_code_challenge
from smartbroker_plus_sdk.exceptions import AuthenticationError
```

## Troubleshooting

See [docs/troubleshooting.md](docs/troubleshooting.md) for common gotchas.

## Changelog

See [CHANGELOG.md](CHANGELOG.md).

## Development

See [docs/development.md](docs/development.md) for the full development guide.

