Metadata-Version: 2.4
Name: smartbroker-plus-sdk
Version: 0.5.1
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`.

Scope `orders list` to a single account with `--account` (env: `SMARTBROKER_ACCOUNT`), and add `--with-partial-executions` to receive the richer V2 listing whose entries carry partial-execution summaries:

```bash
sbplus orders list --portfolio <portfolioId> --account <accountId> --with-partial-executions
```

Increase log verbosity with `-v` (info), `-vv` (debug — full HTTP wire dump), or `-vvv` (trace). Setting `SBP_LOG=info|debug|trace` has the same effect without flags. See [docs/logging.md](docs/logging.md).

### Placing an Order

The `orders calculate` → `orders place` pipeline previews fees before committing:

```bash
# 1. Approve a 2FA token (cached until expiry)
sbplus auth 2fa create --wait

# 2. Preview the order and capture the calc response
sbplus orders calculate \
  --portfolio <portfolioId> \
  --isin US0378331005 \
  --exchange EDG \
  --direction buy \
  --units 1 \
  --output json > calc.json

# 3. Review fees, then place (prompts for confirmation)
sbplus orders place --portfolio <portfolioId> --order-data-file calc.json
# About to place: BUY 1.0 US0378331005 @ EDG MARKET. Proceed? [y/N]:

# One-liner via stdin (--yes skips the prompt, --2fa-token overrides cache)
sbplus orders calculate ... --output json \
  | sbplus orders place --portfolio <portfolioId> --order-data-file - --yes
```

### 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) |
| `SMARTBROKER_PORTFOLIO` | `--portfolio` | Default portfolio identifier |
| `SMARTBROKER_ACCOUNT` | `--account` | Default account number |
| `SMARTBROKER_EXCHANGE` | `--exchange` | Default exchange (Coperitus code) |
| `SMARTBROKER_PAGE_SIZE` | `--page-size` | Default page size for list commands |

**Naming convention.** Environment variable names mirror their flag: drop
the leading dashes, uppercase, replace `-` with `_`, and prefix with
`SMARTBROKER_` (for example `--page-size` → `SMARTBROKER_PAGE_SIZE`).

**Resolution precedence.** An explicit flag overrides the environment
variable. When neither is provided and the option is required, the CLI
emits the standard missing-parameter error.

**Env-sourced confirmation prompts.** When the portfolio used in an
order confirmation prompt was sourced from `SMARTBROKER_PORTFOLIO`, the
prompt annotates it with the variable name so the source is unambiguous:

```text
Portfolio: <portfolioId>  [env: SMARTBROKER_PORTFOLIO]
Account:   <accountNumber>
```

Values supplied via `--portfolio` are rendered without annotation. The
account line is sourced from the calculate-response payload, never from
`SMARTBROKER_ACCOUNT`, and therefore carries no `[env: ...]` annotation
in the place/modify flow.
### 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) |

## 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.

