Skill: fortressquant/peerberry-sdk (version 1.0.0)

--- name: peerberry-sdk description: Use this skill when assisting with FortressQuant's peerberry-sdk for PeerBerry investor automation, P2P lending education, and alternative-investment onboarding. Apply it for authentication setup, portfolio and loan retrieval, filtering, purchase automation, risk-aware explanation, and SDK debugging. --- # PeerBerry SDK Skill ## TL;DR Quick Start - Start with read-only calls first (`get_profile`, `get_overview`, `get_loans`). - Use `Decimal` for money and rates, never `float`. - Treat `purchase_loan` as real-money action and gate it with `DRY_RUN` and `MAX_ORDERS`. - Use SDK filter arguments before local filtering (`min_interest_rate`, `countries`, `loan_types`). - Catch specific auth/funds errors, then fall back to `PeerberryException`. Read-only starter: ```python from peerberry_sdk import PeerberryClient with PeerberryClient(email="YOUR_EMAIL", [REDACTED_SECRET]") as api: profile = api.get_profile() overview = api.get_overview() loans = api.get_loans(quantity=5) print(profile.public_id) print(overview.data.get("availableMoney", overview.data.get("items", {}).get("availableMoney"))) print([loan.loan_id for loan in loans]) ``` Safe invest starter: ```python from decimal import Decimal from peerberry_sdk import PeerberryClient DRY_RUN = True MAX_ORDERS = 10 TICKET_SIZE = Decimal("10.00") with PeerberryClient(email="YOUR_EMAIL", [REDACTED_SECRET]") as api: loans = api.get_loans(quantity=50, min_interest_rate=Decimal("9.5"), exclude_invested_loans=True) for idx, loan in enumerate(loans): if idx >= MAX_ORDERS or loan.loan_id is None: break if DRY_RUN: print(f"[DRY_RUN] would invest {TICKET_SIZE} in loan {loan.loan_id}") continue api.purchase_loan(loan_id=loan.loan_id, amount=TICKET_SIZE) ``` ## Core Purpose `peerberry-sdk` is a Python wrapper around the PeerBerry investor API. In P2P lending, investors allocate capital across many loans (or loan fractions), receive principal and interest repayments over time, and manage risk through diversification and monitoring. PeerBerry provides marketplace access to these investor workflows, and this SDK converts them into programmable Python actions for analysis, automation, and operational control. ## Scope / Non-goals In scope: - Explain PeerBerry and P2P lending concepts in plain language. - Generate and debug Python code using the real SDK method surface. - Build read-only monitoring scripts and guarded investment automation. - Help with filtering, paging, exports, and auth/token lifecycle patterns. Out of scope: - Provide financial advice, suitability advice, or guaranteed-return claims. - Promise profitability, safety, or future performance. - Invent SDK methods that do not exist. ## Request Classifier Classify incoming requests and respond with the matching style: 1. `educational`: user is new to P2P/PeerBerry. - Explain concepts first, then provide read-only demo code. - Load: `references/p2p-primer.md`. 2. `read_only_coding`: user wants portfolio/loan analytics. - Provide runnable snippets with typed model handling. - Load: `references/api-quickref.md`. 3. `real_money_automation`: user wants buy/invest flows. - Add `DRY_RUN`, `MAX_ORDERS`, funds checks, and explicit risk labels. - Load: `references/api-quickref.md` and `references/task-recipes.md`. 4. `debugging`: user has errors/exceptions. - Triage auth, enum inputs, filter metadata, then payload shape. - Load: `references/api-quickref.md`. ## Prerequisites - Create and verify an investor account on the official PeerBerry website: <https://peerberry.com/>. - Use valid PeerBerry credentials (`email`, `password`). - If account uses TOTP 2FA, provide `tfa_secret` and install the `otp` extra. - Treat purchase actions as real-money operations. ## Key Concepts & Objects Primary entry point: - `PeerberryClient`: high-level client for authentication, retrieval, and purchase actions. Core model objects: - `Profile`, `Overview`, `Loan`, `LoanPage`, `InvestmentPage`, `Transaction`, `AccountSummary`, `PurchaseOrder`. Domain semantics: - `loan`: marketplace listing that can be invested into. - `investment`: already-owned position in a loan. - `purchase order`: accepted order result with `order_id` (not settlement confirmation). ## Installation & Authentication Install: ```bash pip install peerberry-sdk ``` Install with 2FA support: ```bash pip install "peerberry-sdk[otp]" ``` Authenticate: ```python from peerberry_sdk import PeerberryClient with PeerberryClient(email="YOUR_EMAIL", [REDACTED_SECRET]") as api: print(api.get_profile().public_id) ``` ## Core Functions & Common Workflows Use this method map: - Profile and portfolio: `get_profile`, `get_overview`, `get_loyalty_tier` - Loan discovery: `get_loans`, `get_loans_page`, `get_loan_details` - Purchase action: `purchase_loan` - Portfolio positions: `get_investments` - Cash flow and reporting: `get_transactions`, `get_account_summary` - Exports: `get_mass_investments`, `get_mass_transactions` - Metadata helpers: `get_countries`, `get_originators` For signatures, enums, and exception patterns, load `references/api-quickref.md`. For copy-paste user prompts and intent routing, load `references/task-recipes.md`. ## Safety Defaults (Real-Money Flows) Always apply unless the user explicitly overrides: - Default to read-only path first. - Add `DRY_RUN = True` for first run. - Set a hard cap with `MAX_ORDERS`. - Skip records missing `loan_id`. - Validate `available_to_invest >= ticket_size` when field is present. - Stop on `InsufficientFunds`. - Log each resulting `order_id`. ## Known SDK Quirks - `get_overview` payload can be flat or nested under `items`. - `get_loans` internally paginates with max page size 40. - `get_loans` defaults `group_guarantee=True`. - Country/originator filters require display names from metadata helpers. - Export methods return raw `bytes`, not typed rows. ## Reference Files (Progressive Loading) Load only what is needed: - `references/p2p-primer.md` - Use for beginner education, plain-language explanations, and trust-first communication rules. - `references/api-quickref.md` - Use for method signatures, accepted values, parameter semantics, exceptions, and debugging. - `references/task-recipes.md` - Use for copy-paste prompts mapped to common investor intents. ## Maintenance Contract When SDK changes, update this skill in this order: 1. Verify method signatures and accepted values against: - `src/peerberry_sdk/client.py` - `docs/api/client.md` 2. Update `references/api-quickref.md` first. 3. Update affected recipes in `references/task-recipes.md`. 4. Keep this root `SKILL.md` concise and routing-focused. 5. Re-check safety defaults for any new write action methods. ## Project Resources - Repository: <https://github.com/FortressQuant/peerberry-sdk> - Docs index: <https://github.com/FortressQuant/peerberry-sdk/tree/main/docs> - Client API reference: <https://github.com/FortressQuant/peerberry-sdk/blob/main/docs/api/client.md> - Issues: <https://github.com/FortressQuant/peerberry-sdk/issues> ## Skill Authoring References (March 2026) - OpenAI Academy skills guide: <https://academy.openai.com/public/clubs/work-users-ynjqu/resources/how-to-build-and-use-skills> - Anthropic memory guidance: <https://docs.anthropic.com/en/docs/claude-code/memory> - GitHub Copilot custom instructions: <https://docs.github.com/en/copilot/how-tos/custom-instructions/adding-custom-instructions-for-github-copilot?tool=vscode> - OpenAI AGENTS.md spec: <https://github.com/openai/agents.md>

path: LICENSE.md
content: MIT License

Copyright (c) 2026 FortressQuant

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.


path: pyproject.toml
content: [build-system]
requires = ["setuptools>=77.0.3", "wheel"]
build-backend = "setuptools.build_meta"

[project]
name = "peerberry-sdk"
version = "2.0.0"
authors = [
    { name = "FortressQuant" }
]
dependencies = [
    "cloudscraper>=1.2"
]
description = "Python SDK for the Peerberry investor API with typed models, authentication, retries, and export helpers."
readme = { file = "README.md", content-type = "text/markdown" }
requires-python = ">=3.8"
license = { text = "MIT" }
keywords = [
    "peerberry",
    "peerberry-api",
    "peerberry-sdk",
    "python-sdk",
    "api-client",
    "investing",
    "p2p-lending",
]
classifiers = [
    "Intended Audience :: Developers",
    "Operating System :: OS Independent",
    "Programming Language :: Python :: 3",
    "Programming Language :: Python :: 3 :: Only",
    "Programming Language :: Python :: 3.8",
    "Programming Language :: Python :: 3.9",
    "Programming Language :: Python :: 3.10",
    "Programming Language :: Python :: 3.11",
    "Programming Language :: Python :: 3.12",
    "Topic :: Software Development :: Libraries :: Python Modules",
    "Topic :: Office/Business :: Financial :: Investment",
    "Typing :: Typed",
]

[project.urls]
Homepage = "https://github.com/FortressQuant/peerberry-sdk"
Documentation = "https://github.com/FortressQuant/peerberry-sdk/tree/main/docs"
Repository = "https://github.com/FortressQuant/peerberry-sdk"
Issues = "https://github.com/FortressQuant/peerberry-sdk/issues"

[project.optional-dependencies]
otp = ['pyotp>=2.7.0,<3']
docs = [
    'zensical>=0.0,<1',
    'mkdocs-gen-files>=0.5,<1',
    'mkdocstrings-python>=1.11,<2',
    'ruff>=0.6,<1',
]

[tool.setuptools]
package-dir = {"" = "src"}
license-files = ["LICENSE.md"]

[tool.setuptools.packages.find]
where = ["src"]
include = ["peerberry_sdk", "peerberry_sdk.*"]

[tool.setuptools.package-data]
peerberry_sdk = ["py.typed"]


path: README.md
content: # Peerberry API Python SDK (Unofficial)

[![PyPI version](https://img.shields.io/pypi/v/peerberry-sdk.svg)](https://pypi.org/project/peerberry-sdk/)
[![Python versions](https://img.shields.io/pypi/pyversions/peerberry-sdk.svg)](https://pypi.org/project/peerberry-sdk/)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://github.com/FortressQuant/peerberry-sdk/blob/main/LICENSE.md)

`peerberry-sdk` is an unofficial Python SDK for the Peerberry investor API.  
It helps you build scripts and applications for Peerberry account data, loan discovery and purchases, investment tracking, and transaction exports.

Peerberry is a lending-focused investment platform where investors monitor portfolio performance, browse available loans, and manage investment activity.  
This SDK gives Python developers programmatic access to those investor workflows via an unofficial API client.

The package exposes a single high-level client (`peerberry_sdk.PeerberryClient`) with typed model outputs, configurable retries, token lifecycle behavior, and structured exceptions suitable for production integrations.

> Looking for a Peerberry Python SDK, Peerberry API wrapper, or Peerberry investor API client? This project is designed for that use case.

## Table of Contents

- [Feature Overview](#feature-overview)
- [Requirements](#requirements)
- [Installation](#installation)
- [Quick Start](#quick-start)
- [Documentation](#documentation)
- [Common Workflows](#common-workflows)
- [Authentication Modes](#authentication-modes)
- [Configuration](#configuration)
- [Header Profile Support](#header-profile-support)
- [Peerberry API Surface Covered](#peerberry-api-surface-covered)
- [Return Types](#return-types)
- [Error Handling](#error-handling)
- [Lifecycle](#lifecycle)
- [Contributing](#contributing)
- [Acknowledgements](#acknowledgements)
- [Disclaimer](#disclaimer)

## Feature Overview

- Authenticated client for Peerberry profile, loans, investments, and transactions
- Typed dataclass models for most read operations
- Token lifecycle handling (login, refresh, optional proactive refresh)
- Configurable retry policy for HTTP and network failures
- Optional observability hooks and structured event emission
- Context-manager and explicit-close lifecycle support

## Requirements

- Python `>=3.8`

## Installation

### Standard

```bash
pip install peerberry-sdk
```

### With 2FA Support

```bash
pip install "peerberry-sdk[otp]"
```

`[otp]` installs `pyotp`, required only when you use `tfa_secret`.

### For Local Development

```bash
pip install -e ".[otp,docs]"
```

## Quick Start

```python
from peerberry_sdk import PeerberryClient

with PeerberryClient(email="YOUR_EMAIL", [REDACTED_SECRET]") as api:
    profile = api.get_profile()
    overview = api.get_overview()
    loans = api.get_loans(quantity=10)

    print("public_id:", profile.public_id)
    print("available money:", overview.data.get("availableMoney"))
    print("loans:", len(loans))
```

## Documentation

- [Installation Guide](https://github.com/FortressQuant/peerberry-sdk/blob/main/docs/getting-started/installation.md)
- [Quickstart Guide](https://github.com/FortressQuant/peerberry-sdk/blob/main/docs/getting-started/quickstart.md)
- [Authentication Guide](https://github.com/FortressQuant/peerberry-sdk/blob/main/docs/guides/authentication.md)
- [Configuration Guide](https://github.com/FortressQuant/peerberry-sdk/blob/main/docs/guides/configuration.md)
- [Error Handling Guide](https://github.com/FortressQuant/peerberry-sdk/blob/main/docs/guides/error-handling.md)
- [Models Guide](https://github.com/FortressQuant/peerberry-sdk/blob/main/docs/guides/models.md)
- [Client API Reference (Generated)](https://github.com/FortressQuant/peerberry-sdk/blob/main/docs/api/client.md)
- [Module API Reference (Generated)](https://github.com/FortressQuant/peerberry-sdk/blob/main/docs/api/reference.md)

Repository docs are in `docs/`:

- [`docs/getting-started/`](https://github.com/FortressQuant/peerberry-sdk/tree/main/docs/getting-started/) for onboarding
- [`docs/guides/`](https://github.com/FortressQuant/peerberry-sdk/tree/main/docs/guides/) for deep task-oriented guides
- [`docs/api/client.md`](https://github.com/FortressQuant/peerberry-sdk/blob/main/docs/api/client.md) for full generated API contract docs
- [`docs/api/reference.md`](https://github.com/FortressQuant/peerberry-sdk/blob/main/docs/api/reference.md) for generated module reference

`docs/api/reference.md` is generated by `docs/gen_api.py` during docs build.
Documentation configuration lives in `zensical.toml`.

Build docs locally:

```bash
zensical build
```

## Common Workflows

### Manual Read-Only Smoke Test

Use `tests/manual_display_actions.py` for a manual account verification pass that only performs display/read operations.

- No investment purchase action is executed
- Intended for local/manual runs, not CI
- Named without `test_` so it is not auto-collected by `pytest`
- Reads dotenv from `tests/.env` only

Setup and run:

```bash
cp tests/.env.example tests/.env
python3 tests/manual_display_actions.py
```

Expected credentials in `tests/.env`:

- `PEERBERRY_USERNAME`
- `PEERBERRY_PASSWORD`
- `PEERBERRY_TFA_SECRET` (optional, base32 TOTP secret)

### Fetch Investments and Transactions

```python
from datetime import date, timedelta
from peerberry_sdk import PeerberryClient

with PeerberryClient(email="YOUR_EMAIL", [REDACTED_SECRET]") as api:
    investments = api.get_investments(quantity=50, current=True)

    end_date = date.today()
    start_date = end_date - timedelta(days=30)
    transactions = api.get_transactions(
        quantity=100,
        start_date=start_date,
        end_date=end_date,
        transaction_types=["deposit", "interest_payment", "principal_repayment"],
    )

    print(investments.total)
    print(len(transactions))
```

### Purchase a Loan

```python
from decimal import Decimal
from peerberry_sdk import PeerberryClient

with PeerberryClient(email="YOUR_EMAIL", [REDACTED_SECRET]") as api:
    order = api.purchase_loan(loan_id=123456, amount=Decimal("10.00"))
    print(order.order_id)
```

### Export Data

```python
from datetime import date, timedelta
from peerberry_sdk import PeerberryClient

with PeerberryClient(email="YOUR_EMAIL", [REDACTED_SECRET]") as api:
    inv_bytes = api.get_mass_investments(current=True)

    end = date.today()
    start = end - timedelta(days=365)
    tx_bytes = api.get_mass_transactions(start_date=start, end_date=end)

with open("investments.xlsx", "wb") as f:
    f.write(inv_bytes)

with open("transactions.xlsx", "wb") as f:
    f.write(tx_bytes)
```

## Authentication Modes

### 1. Credentials (Default)

```python
api = PeerberryClient(email="YOUR_EMAIL", [REDACTED_SECRET]")
```

### 2. Credentials + TOTP (2FA)

```python
api = PeerberryClient(
    email="YOUR_EMAIL",
    [REDACTED_SECRET]",
    tfa_secret="BASE32_TOTP_SECRET",
)
```

### 3. Existing Tokens

```python
api = PeerberryClient(
    access_token="ACCESS_TOKEN",
    refresh_token="REFRESH_TOKEN",
)
```

### Deferred Login

```python
api = PeerberryClient(email="YOUR_EMAIL", [REDACTED_SECRET]", auto_login=False)
api.login()
```

## Configuration

The client is configured through `SDKConfig`:

- `TransportConfig`: timeout, retries, header profiles
- `AuthConfig`: refresh strategy, token-store integration
- `ObservabilityConfig`: events/hook/logger/redaction
- `LifecycleConfig`: close/logout behavior

```python
import logging
from peerberry_sdk import (
    PeerberryClient,
    AuthConfig,
    InMemoryTokenStore,
    LifecycleConfig,
    ObservabilityConfig,
    RetryConfig,
    SDKConfig,
    TransportConfig,
)

config = SDKConfig(
    transport=TransportConfig(
        timeout=20.0,
        retry=RetryConfig(
            max_retries=3,
            backoff_factor=0.5,
            max_backoff=8.0,
            retry_status_codes=(429, 500, 502, 503, 504),
            retry_on_network_errors=True,
        ),
    ),
    auth=AuthConfig(
        auto_refresh_on_auth_error=True,
        max_refresh_attempts=1,
        proactive_refresh=True,
        proactive_refresh_skew_seconds=60,
        token_store=InMemoryTokenStore(),
        load_tokens_on_init=True,
    ),
    observability=ObservabilityConfig(
        event_hook=None,
        logger=logging.getLogger("peerberry-sdk"),
        verbose_events=False,
        redactor=None,
    ),
    lifecycle=LifecycleConfig(
        logout_on_close=False,
        swallow_logout_errors=True,
    ),
)

api = PeerberryClient(email="YOUR_EMAIL", [REDACTED_SECRET]", config=config)
```

## Header Profile Support

The transport layer allows custom static headers via:

- `request_opts={"header_profile": {...}}`
- `request_opts={"header_profile_path": "/path/to/profile.json"}`
- `peerberry_sdk_HEADER_PROFILE=/path/to/profile.json`

Accepted JSON formats:

```json
{ "x-app-system": "Linux x86_64", "x-app-user-agent": "Chrome 145.0.0.0" }
```

or:

```json
{ "headers": { "x-app-system": "Linux x86_64" } }
```

## Peerberry API Surface Covered

### Portfolio and Profile

- `get_profile()`
- `get_overview()`
- `get_loyalty_tier()`
- `get_profit_overview()`
- `get_investment_status()`
- `get_investment_originators_overview()`

### Loans

- `get_loans()`
- `get_loans_page()`
- `get_loan_details()`
- `get_agreement()`
- `purchase_loan()`

### Investments

- `get_investments()`
- `get_mass_investments()`

### Transactions and Summary

- `get_account_summary()`
- `get_transactions()`
- `get_mass_transactions()`

### Auth and Lifecycle

- `login()`
- `token()`
- `logout()`
- `close()`

### Metadata Helpers

- `get_countries()`
- `get_originators()`

## Return Types

Core methods return typed models:

- `Profile`, `Overview`, `LoyaltyTier`, `ProfitPoint`
- `Loan`, `LoanPage`, `LoanDetails`
- `Investment`, `InvestmentPage`
- `Transaction`, `AccountSummary`, `PurchaseOrder`

Export-oriented methods return `bytes`:

- `get_mass_investments()`
- `get_mass_transactions()`
- `get_agreement()`

## Error Handling

All SDK exceptions inherit from `PeerberryException`.

Common raised types:

- `AuthenticationError`, `InvalidCredentials`, `TokenRefreshError`
- `RateLimitError`, `ValidationError`, `InsufficientFunds`, `ServerError`
- `NetworkError`
- local validation errors such as `InvalidPeriodicity`, `InvalidSort`, `InvalidType`

Exception instances can include:

- `status_code`, `error_code`, `errors`, `payload`
- `url`, `method`, `retry_after`, `is_retryable`
- `request_id`, `original_exception`

```python
from peerberry_sdk import PeerberryClient, PeerberryException, RateLimitError, TokenRefreshError, ValidationError

try:
    with PeerberryClient(email="YOUR_EMAIL", [REDACTED_SECRET]") as api:
        api.get_loans(quantity=10)
except RateLimitError as exc:
    print("retry_after:", exc.retry_after)
except ValidationError as exc:
    print("validation:", exc)
except TokenRefreshError:
    print("re-authentication required")
except PeerberryException as exc:
    print("sdk error:", exc, "request_id:", exc.request_id)
```

## Lifecycle

Preferred pattern:

```python
with PeerberryClient(email="YOUR_EMAIL", [REDACTED_SECRET]") as api:
    ...
```

Explicit pattern:

```python
api = PeerberryClient(email="YOUR_EMAIL", [REDACTED_SECRET]")
try:
    ...
finally:
    api.close(logout=True)
```

## Contributing

Issues and pull requests are welcome. For documentation updates, edit files under `docs/` and validate locally with:

```bash
zensical build
```

## Acknowledgements

- This SDK builds on ideas from the archived API client project [`thicccat688/peerberrypy`](https://github.com/thicccat688/peerberrypy).
- Thanks to [`thicccat688`](https://github.com/thicccat688) for the original groundwork.

## Disclaimer

- This SDK is unofficial and not maintained by Peerberry
- Peerberry API behavior can change without prior notice


path: zensical.toml
content: [project]
site_name = "Peerberry SDK Documentation"
site_description = "Unofficial Python SDK for Peerberry"
repo_url = "https://github.com/FortressQuant/peerberry-sdk"
repo_name = "FortressQuant/peerberry-sdk"
edit_uri = "edit/main/docs/"

nav = [
    { Home = "index.md" },
    { "Getting Started" = [
        { Installation = "getting-started/installation.md" },
        { Quickstart = "getting-started/quickstart.md" },
    ] },
    { Guides = [
        { Authentication = "guides/authentication.md" },
        { Configuration = "guides/configuration.md" },
        { "Error Handling" = "guides/error-handling.md" },
        { Models = "guides/models.md" },
    ] },
    { API = [
        { "Client API (Generated)" = "api/client.md" },
        { "Module Reference (Generated)" = "api/reference.md" },
    ] },
]

[project.theme]
variant = "classic"
features = [
    "navigation.sections",
    "navigation.expand",
    "content.code.copy",
    "search.suggest",
    "search.highlight",
]

[project.plugins.search]

[project.plugins."gen-files"]
scripts = [
    "docs/gen_client_reference.py",
    "docs/gen_api.py",
]

[project.plugins.mkdocstrings.handlers.python]
paths = ["src"]

[project.plugins.mkdocstrings.handlers.python.options]
docstring_style = "sphinx"
show_source = false
members_order = "source"
filters = ["!^_"]
merge_init_into_class = true
separate_signature = true
show_root_heading = true
show_root_full_path = false

[project.markdown_extensions.admonition]

[project.markdown_extensions.attr_list]

[project.markdown_extensions.def_list]

[project.markdown_extensions.footnotes]

[project.markdown_extensions.tables]

[project.markdown_extensions.toc]
permalink = true

[project.markdown_extensions."pymdownx.highlight"]

[project.markdown_extensions."pymdownx.inlinehilite"]

[project.markdown_extensions."pymdownx.superfences"]

[project.markdown_extensions."pymdownx.snippets"]


path: references/api-quickref.md
content: # API Quick Reference

This file is the fast source of truth for method routing, accepted values, and error handling.

## Main Client

```python
PeerberryClient(
    email: Optional[str] = None,
    [REDACTED_SECRET] = None,
    tfa_secret: Optional[str] = None,
    access_token: Optional[str] = None,
    refresh_token: Optional[str] = None,
    request_opts: Optional[dict] = None,
    config: Optional[SDKConfig] = None,
    auto_login: bool = True,
)
```

Lifecycle methods:

- `login() -> str`
- `token() -> str`
- `logout() -> str`
- `close(logout: Optional[bool] = None) -> None`

## Method Map

Profile and portfolio:

- `get_profile() -> Profile`
- `get_overview() -> Overview`
- `get_loyalty_tier() -> LoyaltyTier`
- `get_profit_overview(start_date: date, end_date: date, periodicity: str = "day") -> List[ProfitPoint]`
- `get_investment_status() -> InvestmentStatusOverview`
- `get_investment_originators_overview() -> OriginatorsOverview`

Loans and purchases:

- `get_loans(quantity: int, ..., sort: str = "loan_amount", ...) -> List[Loan]`
- `get_loans_page(page_num: int, quantity: int = 40, ..., sort: str = "loan_id", ...) -> LoanPage`
- `get_secondary_loans(quantity: int, start_page: int = 0, ..., sort: Optional[str] = None, ...) -> SecondaryLoanPage`
- `get_loan_details(loan_id: int) -> LoanDetails`
- `get_agreement(loan_id: int, lang: str = "en") -> bytes`
- `purchase_loan(loan_id: int, amount: Decimal) -> PurchaseOrder`

Investments and cash flow:

- `get_investments(quantity: int, ..., current: bool = True) -> InvestmentPage`
- `get_mass_investments(countries: Optional[List[str]] = None, current: bool = True) -> bytes`
- `get_account_summary(start_date: date, end_date: date) -> AccountSummary`
- `get_transactions(quantity: Optional[int] = None, ..., period: Optional[str] = None, transaction_types: Optional[List[Union[str, int]]] = None, ...) -> List[Transaction]`
- `get_mass_transactions(start_date: Optional[date], end_date: Optional[date], transaction_types: Optional[List[Union[str, int]]] = None, period: Optional[str] = None, loan_id: Optional[int] = None) -> bytes`

Metadata helpers:

- `get_countries() -> dict`
- `get_originators() -> dict`

## Accepted Values

### Loan Sort (`get_loans`, `get_loans_page`)

- `loan_id`
- `term`
- `issued_date`
- `interest_rate`
- `loan_amount`

### Loan Types

- `short_term`
- `long_term`
- `real_estate`
- `leasing`
- `business`

### Profit Periodicity (`get_profit_overview`)

- `day`
- `month`
- `year`

### Transaction Period (`get_transactions`, `get_mass_transactions`)

- `today`
- `thisWeek`
- `thisMonth`

### Transaction Type Named Keys

- `deposit`
- `withdrawal`
- `principal_repayment`
- `interest_payment`
- `investment`
- `fees_and_bonuses`

### Secondary Market Sort (`get_secondary_loans`)

- `remaining_term`
- `loan_interest`
- `remaining_principal`
- `sale_amount`
- `final_payment_date`
- `sale_valid_until`

## Parameter Semantics and Units

- Use `Decimal` for money/rates.
- `purchase_loan(..., amount=...)` expects EUR-denominated amount.
- Interest filters are percentage-style decimals (`Decimal("9.5")` means 9.5%).
- `start_page` is zero-based.
- `quantity` is row count.
- `countries=[...]` expects PeerBerry display names, not raw ISO code.
- Resolve country/originator names dynamically from `get_countries()` and `get_originators()`.

## Model Notes

Most read methods return typed dataclasses with `raw` payload retained.

Common models:

- `Profile`
- `Overview`
- `Loan`
- `LoanPage`
- `InvestmentPage`
- `Transaction`
- `AccountSummary`
- `PurchaseOrder`

Payload caveat:

- `Overview.data` may be flat or nested under `items`.

## Exceptions To Handle

Authentication and lifecycle:

- `InvalidCredentials`
- `AuthenticationError`
- `TokenRefreshError`

Validation and input errors:

- `InvalidSort`
- `InvalidPeriodicity`
- `InvalidType`

Investment execution:

- `InsufficientFunds`

Catch-all SDK base:

- `PeerberryException`

## Minimal Error-Resilient Pattern

```python
from peerberry_sdk import (
    AuthenticationError,
    InsufficientFunds,
    InvalidCredentials,
    PeerberryClient,
    PeerberryException,
    TokenRefreshError,
)

try:
    with PeerberryClient(email="YOUR_EMAIL", [REDACTED_SECRET]") as api:
        print(api.get_overview().data)
except InvalidCredentials:
    print("Invalid credentials")
except AuthenticationError:
    print("2FA required or auth flow failed")
except TokenRefreshError:
    print("Refresh token flow failed")
except InsufficientFunds:
    print("Insufficient funds for purchase")
except PeerberryException as exc:
    print(f"SDK/API error: {exc}")
```

## Method Hallucination Guard

Do not invent these names:

- `invest(...)`
- `get_account_balance(...)`
- `list_available_loans(...)`
- `get_portfolio_overview(...)`

Use actual methods:

- `get_overview()`
- `get_loans(...)`
- `purchase_loan(...)`
- `get_investments(...)`

## Debugging Checklist

1. Auth failures:
   - confirm credentials
   - if account uses 2FA, install `peerberry-sdk[otp]` and pass `tfa_secret`
2. Filter errors:
   - verify sort/period/type enums
   - verify country/originator display names from metadata helpers
3. Unexpected field shape:
   - inspect model `.raw`
   - handle `Overview.data` flat vs `items` nesting
4. Long-running session issues:
   - call `token()` for manual refresh
   - handle `TokenRefreshError` fallback path


path: references/p2p-primer.md
content: # P2P Lending Primer (PeerBerry Context)

## What P2P Lending Is

Peer-to-peer lending lets investors fund borrower loans through a marketplace platform. Instead of earning bank deposit interest, investors allocate money into many loan positions and receive repayments over time.

## How It Works In Practice

1. Investor reviews available loans.
2. Investor places ticket-sized allocations across selected loans.
3. Borrowers repay principal and interest over time.
4. Investor tracks portfolio value, cash flow, and repayment status.
5. Investor rebalances by changing allocation rules or new purchases.

## Primary vs Secondary Market

- Primary market:
  - Investor funds newly listed loan opportunities.
  - In SDK terms, this is usually `get_loans(...)` + `purchase_loan(...)`.
- Secondary market:
  - Investor reviews existing loan positions offered for resale.
  - In SDK terms, this is `get_secondary_loans(...)`.

## Why Investors Use Automation

- Automate investments.
- Repeat filtering logic consistently.
- Reduce manual checking time.
- Standardize risk controls (caps, thresholds, dry-run passes).
- Keep records of actions and outcomes.

## Risk Concepts To Explain Clearly

- Credit/default risk:
  - Borrowers may repay late or not fully repay.
- Liquidity risk:
  - Capital may stay tied up until repayment or sale.
- Concentration risk:
  - Overexposure to one country/originator/loan type increases downside.
- Operational risk:
  - Mistakes in automation can place unintended orders.

## Beginner FAQ (LLM Ready)

### Is this risk-free?

No. P2P lending has credit, liquidity, and concentration risks. The SDK helps automate workflows but does not remove investment risk.

### Are returns guaranteed?

No. Rates shown in loan listings are not guarantees of realized portfolio return.

### What should I do first as a beginner?

Start read-only: authenticate, fetch profile/overview, sample loans, and understand fields before attempting purchase automation.

### Is this financial advice?

No. SDK guidance is technical and operational, not personal financial advice.

## PeerBerry-Specific Positioning

PeerBerry is a lending-focused investor marketplace where users track portfolio metrics, browse loan opportunities, and execute investments. `peerberry-sdk` is an unofficial integration layer that maps those actions into Python methods.

## Communication Rules For Newcomers

Use this wording style:

- Prefer:
  - "available rate", "historical performance", "risk-managed workflow", "read-only first"
- Avoid:
  - "guaranteed return", "risk-free income", "sure profit"

## Suggested Intro Paragraph Template

Use this template when user is new:

"PeerBerry is a marketplace for P2P lending where investors allocate across many loans and receive repayments over time. The Python SDK helps you automate monitoring, filtering, and controlled order placement. A good first step is read-only portfolio and loan analysis before any live purchase actions."


path: references/task-recipes.md
content: # Task Recipes (Copy-Paste Prompts)

Use these prompts directly for intent routing.

## Recipe 1: Portfolio Snapshot

- Tags: `read-only`, `beginner`
- Prompt:

```text
Using peerberry-sdk, authenticate with PeerberryClient and return my profile public_id, availableMoney, and estimated invested principal from current investments. Use Decimal-safe math and read-only calls only.
```

- Likely methods: `get_profile`, `get_overview`, `get_investments(current=True)`

## Recipe 2: Loan Discovery By Yield

- Tags: `read-only`, `screening`
- Prompt:

```text
Fetch the top 30 available loans with interest rate >= 10.0%, sorted descending by interest_rate. Print loan_id, country, originator, interest_rate, and available_to_invest.
```

- Likely methods: `get_loans(quantity=30, min_interest_rate=..., sort="interest_rate", ascending_sort=False)`

## Recipe 3: Country-Scoped Loan Search

- Tags: `read-only`, `screening`
- Prompt:

```text
Find available loans for Germany only, with interest > 9.5%, and show the first 20 results. Resolve country display name via get_countries() before filtering.
```

- Likely methods: `get_countries`, `get_loans(countries=[...], min_interest_rate=...)`

## Recipe 4: Safe Auto-Invest Loop

- Tags: `writes-money`, `guarded`
- Prompt:

```text
Build an auto-invest script that finds loans with interest >= 9.5% and invests EUR 10.00 in each. Include DRY_RUN mode, MAX_ORDERS cap, and stop on InsufficientFunds.
```

- Likely methods: `get_loans`, `purchase_loan`

## Recipe 5: Single-Loan Purchase

- Tags: `writes-money`, `manual`
- Prompt:

```text
Purchase a specific loan by ID with a EUR 10.00 ticket size and return the resulting order_id. Handle InsufficientFunds and PeerberryException.
```

- Likely methods: `purchase_loan`

## Recipe 6: Current vs Finished Investments

- Tags: `read-only`, `portfolio`
- Prompt:

```text
Show me the difference between my current and finished investments: fetch both sets, print totals, and display the first 5 entries from each.
```

- Likely methods: `get_investments(current=True)`, `get_investments(current=False)`

## Recipe 7: Account Summary Plus Transactions

- Tags: `read-only`, `reporting`
- Prompt:

```text
For the last 30 days, fetch account summary and transactions filtered to deposit, investment, principal_repayment, and interest_payment. Print totals and transaction count.
```

- Likely methods: `get_account_summary`, `get_transactions`

## Recipe 8: Export Workflow

- Tags: `read-only`, `reporting`, `exports`
- Prompt:

```text
Export current investments and last-year transactions to XLSX bytes and save them locally as investments.xlsx and transactions.xlsx.
```

- Likely methods: `get_mass_investments`, `get_mass_transactions`

## Recipe 9: Secondary Market Scan

- Tags: `read-only`, `secondary-market`
- Prompt:

```text
Scan secondary market listings with remaining term <= 24 months and interest >= 10%. Sort by loan_interest descending and print top 25 listings.
```

- Likely methods: `get_secondary_loans`

## Recipe 10: Authentication Diagnostics

- Tags: `debugging`, `auth`
- Prompt:

```text
Create a diagnostic script for PeerberryClient login with clear handling for InvalidCredentials, AuthenticationError (2FA), and TokenRefreshError, then run a read-only get_overview check.
```

- Likely methods: `PeerberryClient(...)`, `get_overview`

## Recipe 11: New To P2P (Explain Plus Demo)

- Tags: `educational`, `beginner`, `read-only`
- Prompt:

```text
I am new to P2P lending. Explain PeerBerry in simple terms, then show a read-only Python script using peerberry-sdk that fetches profile, overview, and 5 sample loans. Avoid any purchase call.
```

- Likely methods: `get_profile`, `get_overview`, `get_loans(quantity=5)`

## Recipe 12: Alternative Investment Exploration

- Tags: `educational`, `guarded`
- Prompt:

```text
I want to explore alternative investments safely. Build a two-phase script: phase 1 read-only loan analysis (interest, country, originator distribution), phase 2 optional DRY_RUN investment plan with EUR 10 ticket size and no real purchases.
```

- Likely methods: `get_loans`, `get_countries`, local aggregation

## Recipe 13: Loan Detail Due Diligence

- Tags: `read-only`, `analysis`
- Prompt:

```text
Given a shortlist of loan IDs, fetch detailed borrower/loan/schedule data and summarize key fields in a compact table for due diligence.
```

- Likely methods: `get_loan_details`

## Recipe 14: Cash-Flow Audit Trail

- Tags: `read-only`, `audit`
- Prompt:

```text
Generate a monthly cash-flow audit for the past 6 months using transactions and account summaries, grouped by transaction type and month.
```

- Likely methods: `get_transactions`, `get_account_summary`

## Recipe 15: Token Refresh Health Check

- Tags: `debugging`, `lifecycle`
- Prompt:

```text
Build a health-check script that validates login, forces token refresh with token(), and verifies a follow-up read-only call succeeds.
```

- Likely methods: `login`, `token`, `get_overview`

## Recipe 16: Country/Originator Distribution Snapshot

- Tags: `read-only`, `risk-monitoring`
- Prompt:

```text
Create a snapshot report of available loans grouped by country and originator to identify concentration risk before investing.
```

- Likely methods: `get_loans`, `get_countries`, `get_originators`


path: docs/index.md
content: # Peerberry SDK

This site contains documentation for the `peerberry_sdk` Python package.

## What This SDK Provides

- High-level API client for Peerberry investor operations
- Typed models for most response entities
- Retry and timeout control for transport reliability
- Authentication and token refresh lifecycle management
- Structured exceptions with request context metadata
- Optional observability events for retries/errors/refreshes

## Documentation Roadmap

Use this order for fastest onboarding:

1. [Installation](getting-started/installation.md)
2. [Quickstart](getting-started/quickstart.md)
3. [Authentication](guides/authentication.md)
4. [Configuration](guides/configuration.md)
5. [Error Handling](guides/error-handling.md)
6. [Models](guides/models.md)
7. [API Client Reference (Generated)](api/client.md)
8. [API Module Reference (Generated)](api/reference.md)

## Which API Doc Should I Use?

- Use [API Client Reference (Generated)](api/client.md) for practical usage: method contracts, accepted values, examples.
- Use [API Module Reference (Generated)](api/reference.md) for auto-generated symbol browsing.

## Notes

- This SDK is unofficial.
- Peerberry API behavior can change without public notice.
- `docs/api/reference.md` is generated during docs build from source modules.


path: docs/gen_client_reference.py
content: from __future__ import annotations

import inspect
import re
import sys
import types
from dataclasses import MISSING, fields
from pathlib import Path
from typing import Any, Dict, Iterable, Mapping, Optional, Tuple

try:
    import mkdocs_gen_files
except ModuleNotFoundError:
    mkdocs_gen_files = None


PROJECT_ROOT = Path(__file__).resolve().parents[1]
SOURCE_ROOT = PROJECT_ROOT / 'src'
OUTPUT_PATH = 'api/client.md'

if str(SOURCE_ROOT) not in sys.path:
    sys.path.insert(0, str(SOURCE_ROOT))

# Allow running this generator even when optional runtime deps are not installed.
if 'cloudscraper' not in sys.modules:
    try:
        import cloudscraper  # noqa: F401
    except ModuleNotFoundError:
        class _NoopSession:
            headers = {}

            def request(self, *args, **kwargs):
                raise RuntimeError('Noop session cannot perform requests.')

            def get(self, *args, **kwargs):
                raise RuntimeError('Noop session cannot perform requests.')

        sys.modules['cloudscraper'] = types.SimpleNamespace(
            create_scraper=lambda browser: _NoopSession(),
        )

from peerberry_sdk.client import PeerberryClient
from peerberry_sdk.config import AuthConfig, LifecycleConfig, ObservabilityConfig, RetryConfig, SDKConfig, TransportConfig
from peerberry_sdk.constants import CONSTANTS


METHOD_GROUPS = [
    ('Constructor and Lifecycle', ['__init__', 'login', 'token', 'logout', 'close']),
    ('Profile and Portfolio', [
        'get_profile',
        'get_overview',
        'get_loyalty_tier',
        'get_profit_overview',
        'get_investment_status',
        'get_investment_originators_overview',
    ]),
    ('Loans', ['get_loans', 'get_loans_page', 'get_secondary_loans', 'get_loan_details', 'get_agreement', 'purchase_loan']),
    ('Investments', ['get_investments', 'get_mass_investments']),
    ('Transactions and Summary', ['get_account_summary', 'get_transactions', 'get_mass_transactions']),
    ('Metadata Helpers', ['get_countries', 'get_originators']),
]

METHOD_SUMMARIES: Dict[str, str] = {
    '__init__': 'Creates a new client instance and optionally authenticates immediately.',
    'get_profile': 'Returns investor profile information as a typed model.',
    'get_loyalty_tier': 'Returns the highest unlocked loyalty tier as a typed model.',
    'get_overview': 'Returns portfolio overview data as a typed model wrapper.',
    'get_profit_overview': 'Returns profit chart points for a date interval.',
    'get_investment_status': 'Returns investment status breakdown data.',
    'get_investment_originators_overview': 'Returns originator allocation overview data.',
    'get_loans': 'Fetches multiple available loans using paginated internal requests.',
    'get_loans_page': 'Fetches a single page of available loans with filters.',
    'get_secondary_loans': 'Fetches a single page of secondary-market loan listings with filters.',
    'get_loan_details': 'Returns borrower, loan, schedule, and related details for one loan.',
    'get_agreement': 'Returns investment agreement bytes for a loan.',
    'purchase_loan': 'Creates a purchase order for a selected loan and amount.',
    'get_investments': 'Returns current or finished investments with paging metadata.',
    'get_mass_investments': 'Returns exported investments file bytes.',
    'get_account_summary': 'Returns normalized account summary for a date interval.',
    'get_transactions': 'Returns typed transactions list with optional filters.',
    'get_mass_transactions': 'Returns exported transactions file bytes.',
    'login': 'Authenticates session and returns a bearer token string.',
    'token': 'Forces access-token refresh and returns a bearer token string.',
    'logout': 'Logs out server-side and clears local authentication state.',
    'close': 'Closes underlying transport session, optionally logging out first.',
    'get_countries': 'Returns supported country definitions keyed by display name.',
    'get_originators': 'Returns supported originator definitions keyed by display name.',
}

COMMON_PARAM_DESCRIPTIONS: Dict[str, str] = {
    'email': 'Peerberry account email used in credential login flow.',
    'password': 'Peerberry account password used in credential login flow.',
    'tfa_secret': 'Base32 TOTP secret used in 2FA login flow.',
    'access_token': 'Existing access JWT token.',
    'refresh_token': 'Existing refresh token used for token rotation.',
    'request_opts': 'Optional transport overrides (timeout/headers/header profile options).',
    'config': 'Optional SDKConfig object controlling runtime behavior.',
    'auto_login': 'When true, attempts authentication during client initialization.',
    'quantity': 'Number of records to fetch.',
    'start_page': 'Zero-based page number offset.',
    'page_num': 'Zero-based page number to request.',
    'loan_id': 'Loan identifier.',
    'amount': 'Monetary amount for purchase operations.',
    'lang': 'Agreement language code (for example, "en").',
    'countries': 'List of country display names used as filter.',
    'originators': 'List of originator display names used as filter.',
    'loan_types': 'List of loan type keys used as filter.',
    'sort': 'Sort key; accepted values depend on method.',
    'ascending_sort': 'Sort direction; default false means descending.',
    'group_guarantee': 'When set, filters loans by group guarantee presence.',
    'exclude_invested_loans': 'When set, excludes loans already invested in.',
    'current': 'When true returns current investments; false returns finished.',
    'start_date': 'Date interval start.',
    'end_date': 'Date interval end.',
    'periodicity': 'Named date-range shortcut or aggregation period.',
    'period': 'Named period shortcut for transaction filters.',
    'transaction_types': 'List of transaction type keys used as filter.',
    'logout': 'Optional explicit override for logout-on-close behavior.',
    'max_remaining_term': 'Maximum remaining term filter for loans.',
    'min_remaining_term': 'Minimum remaining term filter for loans.',
    'max_interest_rate': 'Maximum interest rate filter.',
    'min_interest_rate': 'Minimum interest rate filter.',
    'max_available_amount': 'Maximum available amount filter for loans.',
    'min_available_amount': 'Minimum available amount filter for loans.',
    'max_selling_price': 'Maximum selling price filter for secondary-market listings.',
    'min_selling_price': 'Minimum selling price filter for secondary-market listings.',
    'max_date_of_purchase': 'Maximum purchase date filter for investments.',
    'min_date_of_purchase': 'Minimum purchase date filter for investments.',
    'max_invested_amount': 'Maximum invested amount filter for investments.',
    'min_invested_amount': 'Minimum invested amount filter for investments.',
    'only_my_sales': 'When set, returns only current investor sales.',
    'reduced_price_only': 'When set, returns only discounted listings.',
    'only_at_nominal_value': 'When set, returns only nominal-value listings.',
}

METHOD_PARAM_DESCRIPTIONS: Dict[str, Dict[str, str]] = {
    'get_profit_overview': {
        'periodicity': 'Aggregation step for points; must be one of day/month/year.',
    },
    'get_transactions': {
        'period': 'Optional named transaction range; today/thisWeek/thisMonth.',
    },
    'get_mass_transactions': {
        'period': 'Optional named transaction range; today/thisWeek/thisMonth.',
    },
    'get_loans': {
        'sort': 'Loan sort key; one of loan_id/term/issued_date/interest_rate/loan_amount.',
    },
    'get_loans_page': {
        'sort': 'Loan sort key; one of loan_id/term/issued_date/interest_rate/loan_amount.',
    },
    'get_secondary_loans': {
        'sort': 'Secondary-market sort key; one of remaining_term/loan_interest/remaining_principal/sale_amount/final_payment_date/sale_valid_until.',
    },
    'get_investments': {
        'sort': 'Investment sort key; accepted values depend on current flag.',
    },
    'purchase_loan': {
        'amount': 'EUR-denominated investment amount sent to API as string.',
    },
}

METHOD_RETURNS: Dict[str, str] = {
    '__init__': 'Initialized client instance.',
    'token': 'Bearer string in format "Bearer <access_token>".',
    'login': 'Bearer string in format "Bearer <access_token>".',
    'logout': 'Fixed success message string.',
    'get_agreement': 'Raw agreement bytes (file payload).',
    'get_mass_investments': 'Raw export file bytes.',
    'get_mass_transactions': 'Raw export file bytes.',
}

METHOD_EXCEPTIONS: Dict[str, Iterable[str]] = {
    '__init__': ['ValueError', 'PeerberryException'],
    'login': ['InvalidCredentials', 'PeerberryException'],
    'token': ['TokenRefreshError'],
    'close': ['PeerberryException (optional, when configured not to swallow logout errors)'],
    'get_profit_overview': ['InvalidPeriodicity', 'PeerberryException'],
    'get_loans': ['ValueError', 'TypeError', 'InvalidSort', 'PeerberryException'],
    'get_loans_page': ['ValueError', 'TypeError', 'InvalidSort', 'PeerberryException'],
    'get_secondary_loans': ['ValueError', 'TypeError', 'InvalidSort', 'PeerberryException'],
    'purchase_loan': ['InsufficientFunds', 'PeerberryException'],
    'get_investments': ['ValueError', 'TypeError', 'InvalidSort', 'PeerberryException'],
    'get_transactions': ['InvalidType', 'InvalidPeriodicity', 'PeerberryException'],
    'get_mass_transactions': ['InvalidType', 'InvalidPeriodicity', 'PeerberryException'],
    'get_mass_investments': ['TypeError', 'ValueError', 'PeerberryException'],
}

CONFIG_DESCRIPTIONS: Dict[str, Dict[str, str]] = {
    'RetryConfig': {
        'max_retries': 'Retry attempts after initial failure.',
        'backoff_factor': 'Exponential backoff multiplier.',
        'max_backoff': 'Maximum sleep duration between retries.',
        'retry_status_codes': 'HTTP status codes treated as retryable.',
        'retry_on_network_errors': 'Whether network/transport exceptions are retried.',
    },
    'TransportConfig': {
        'timeout': 'Default request timeout in seconds.',
        'retry': 'Retry policy configuration object.',
        'header_profile': 'Inline static headers merged into default request profile.',
        'header_profile_path': 'Path to JSON file containing static header profile.',
        'user_agent': 'Convenience override for x-app-user-agent.',
        'app_system': 'Convenience override for x-app-system.',
        'sec_ch_ua_platform': 'Convenience override for sec-ch-ua-platform.',
    },
    'AuthConfig': {
        'auto_refresh_on_auth_error': 'Refresh token automatically after auth failures.',
        'max_refresh_attempts': 'Refresh attempts for a failing request.',
        'proactive_refresh': 'Refresh access token before expiry when possible.',
        'proactive_refresh_skew_seconds': 'Seconds before expiry to trigger proactive refresh.',
        'token_store': 'Token store implementation used for load/save/clear.',
        'load_tokens_on_init': 'Load token pair from token store during client initialization.',
    },
    'ObservabilityConfig': {
        'event_hook': 'Callback receiving sanitized event dictionaries.',
        'logger': 'Logger receiving structured SDK event messages.',
        'verbose_events': 'Enable request_start/request_end verbose events.',
        'redactor': 'Optional custom event redaction hook.',
    },
    'LifecycleConfig': {
        'logout_on_close': 'Whether PeerberryClient.close() should call logout().',
        'swallow_logout_errors': 'Suppress logout errors during close when true.',
    },
    'SDKConfig': {
        'transport': 'Transport-related runtime configuration.',
        'auth': 'Authentication/token lifecycle configuration.',
        'observability': 'Event and logging configuration.',
        'lifecycle': 'Client-close and logout behavior configuration.',
    },
}


def _format_annotation(annotation: Any) -> str:
    if annotation is inspect.Signature.empty:
        return 'Any'

    if isinstance(annotation, str):
        return annotation

    text = inspect.formatannotation(annotation)
    text = text.replace('typing.', '')
    text = text.replace('NoneType', 'None')
    text = text.replace('peerberry_sdk.models.entities.', '')
    text = text.replace('peerberry_sdk.config.', '')
    text = text.replace('peerberry_sdk.token_store.', '')

    return text


def _format_default(value: Any) -> str:
    if value is inspect.Signature.empty:
        return 'Required'

    if isinstance(value, str):
        return f'"{value}"'

    return repr(value)


def _callable_signature(func: Any, *, strip_self: bool = True) -> inspect.Signature:
    signature = inspect.signature(func)

    if not strip_self:
        return signature

    filtered = [
        parameter
        for parameter in signature.parameters.values()
        if parameter.name != 'self'
    ]

    return signature.replace(parameters=filtered)


def _parse_sphinx_docstring(docstring: str) -> Tuple[str, Dict[str, str], Optional[str], Dict[str, str]]:
    if not docstring:
        return '', {}, None, {}

    lines = docstring.splitlines()
    summary_parts = []
    parameter_docs: Dict[str, str] = {}
    return_doc: Optional[str] = None
    raise_docs: Dict[str, str] = {}

    current_param = None
    in_return = False

    param_pattern = re.compile(r'^:param\s+(\w+)\s*:\s*(.*)$')
    return_pattern = re.compile(r'^:return\s*:\s*(.*)$')
    raise_pattern = re.compile(r'^:raises?\s+([\w\.]+)\s*:\s*(.*)$')

    for raw_line in lines:
        line = raw_line.rstrip()
        stripped = line.strip()

        if not stripped:
            current_param = None
            in_return = False
            continue

        param_match = param_pattern.match(stripped)
        if param_match:
            current_param = param_match.group(1)
            in_return = False
            parameter_docs[current_param] = param_match.group(2).strip()
            continue

        return_match = return_pattern.match(stripped)
        if return_match:
            current_param = None
            in_return = True
            return_doc = return_match.group(1).strip()
            continue

        raise_match = raise_pattern.match(stripped)
        if raise_match:
            current_param = None
            in_return = False
            raise_type = raise_match.group(1).strip()
            raise_desc = raise_match.group(2).strip()
            raise_docs[raise_type] = raise_desc
            continue

        if stripped.startswith(':'):
            current_param = None
            in_return = False
            continue

        if current_param is not None:
            parameter_docs[current_param] = f"{parameter_docs[current_param]} {stripped}".strip()
            continue

        if in_return and return_doc is not None:
            return_doc = f"{return_doc} {stripped}".strip()
            continue

        summary_parts.append(stripped)

    summary = ' '.join(summary_parts).strip()

    return summary, parameter_docs, return_doc, raise_docs


def _resolve_method_description(method_name: str, doc_summary: str) -> str:
    if method_name in METHOD_SUMMARIES:
        return METHOD_SUMMARIES[method_name]

    if doc_summary:
        return doc_summary

    return 'No summary available.'


def _resolve_parameter_description(method_name: str, parameter_name: str, doc_parameters: Mapping[str, str]) -> str:
    method_descriptions = METHOD_PARAM_DESCRIPTIONS.get(method_name, {})
    if parameter_name in method_descriptions:
        return method_descriptions[parameter_name]

    if parameter_name in doc_parameters and doc_parameters[parameter_name]:
        return doc_parameters[parameter_name]

    if parameter_name in COMMON_PARAM_DESCRIPTIONS:
        return COMMON_PARAM_DESCRIPTIONS[parameter_name]

    return 'No detailed description available.'


def _resolve_return_description(method_name: str, doc_return: Optional[str]) -> str:
    if method_name in METHOD_RETURNS:
        return METHOD_RETURNS[method_name]

    if doc_return:
        return doc_return

    return 'See return type annotation.'


def _resolve_method_exception_hints(method_name: str, doc_raises: Mapping[str, str]) -> Iterable[str]:
    configured = list(METHOD_EXCEPTIONS.get(method_name, []))
    from_docstrings = list(doc_raises.keys())
    combined = configured + from_docstrings

    if not combined:
        combined = ['PeerberryException']

    deduped = []
    for exception_name in combined:
        if exception_name not in deduped:
            deduped.append(exception_name)

    return deduped


def _format_signature_for_display(method_name: str, func: Any) -> str:
    signature = _callable_signature(func)
    rendered = _stringify_signature(signature)

    if method_name == '__init__':
        return f'PeerberryClient{rendered}'

    return f'api.{method_name}{rendered}'


def _format_method_anchor(method_name: str) -> str:
    if method_name == '__init__':
        return 'PeerberryClient(...)'

    return f'{method_name}(...)'


def _stringify_signature(signature: inspect.Signature) -> str:
    rendered_parameters = []
    seen_var_positional = False
    inserted_kw_only_separator = False

    for parameter in signature.parameters.values():
        prefix = ''
        if parameter.kind is inspect.Parameter.VAR_POSITIONAL:
            prefix = '*'
            seen_var_positional = True
        elif parameter.kind is inspect.Parameter.VAR_KEYWORD:
            prefix = '**'
        elif parameter.kind is inspect.Parameter.KEYWORD_ONLY and not seen_var_positional and not inserted_kw_only_separator:
            rendered_parameters.append('*')
            inserted_kw_only_separator = True

        part = f'{prefix}{parameter.name}'
        if parameter.annotation is not inspect.Signature.empty:
            part += f': {_format_annotation(parameter.annotation)}'

        if parameter.default is not inspect.Signature.empty:
            part += f' = {_format_default(parameter.default)}'

        rendered_parameters.append(part)

    rendered = f"({', '.join(rendered_parameters)})"

    if signature.return_annotation is not inspect.Signature.empty:
        rendered += f' -> {_format_annotation(signature.return_annotation)}'

    return rendered


def _render_accepted_values_for_method(method_name: str) -> Iterable[str]:
    lines = []

    if method_name in {'get_loans', 'get_loans_page'}:
        values = ', '.join(sorted(CONSTANTS.LOAN_SORT_TYPES.keys()))
        lines.append(f'`sort`: `{values}`')

    if method_name == 'get_secondary_loans':
        values = ', '.join(sorted(CONSTANTS.SECONDARY_LOAN_SORT_TYPES.keys()))
        lines.append(f'`sort`: `{values}`')

    if method_name == 'get_investments':
        current_values = ', '.join(sorted(CONSTANTS.CURRENT_INVESTMENT_SORT_TYPES.keys()))
        finished_values = ', '.join(sorted(CONSTANTS.FINISHED_INVESTMENT_SORT_TYPES.keys()))
        lines.append(f'`sort` when `current=True`: `{current_values}`')
        lines.append(f'`sort` when `current=False`: `{finished_values}`')

    if method_name in {'get_loans', 'get_loans_page', 'get_secondary_loans', 'get_investments'}:
        loan_types = ', '.join(sorted(CONSTANTS.LOAN_TYPES_ID.keys()))
        lines.append(f'`loan_types`: `{loan_types}`')

    if method_name == 'get_profit_overview':
        periodicities = ', '.join(sorted(CONSTANTS.PERIODICITIES))
        lines.append(f'`periodicity`: `{periodicities}`')

    if method_name in {'get_transactions', 'get_mass_transactions'}:
        periodicities = ', '.join(sorted(CONSTANTS.TRANSACTION_PERIODICITIES))
        types_ = ', '.join(sorted(CONSTANTS.TRANSACTION_TYPES.keys()))
        lines.append(f'`period`: `{periodicities}`')
        lines.append(f'`transaction_types` named keys: `{types_}`')
        lines.append('`transaction_types` numeric IDs: any positive integer (for example `14`)')

    return lines


def _iter_api_callables() -> Dict[str, Any]:
    callables: Dict[str, Any] = {}

    for name, attribute in PeerberryClient.__dict__.items():
        if name.startswith('__') and name != '__init__':
            continue

        if name.startswith('_') and name != '__init__':
            continue

        function = None
        if isinstance(attribute, staticmethod):
            function = attribute.__func__
        elif isinstance(attribute, classmethod):
            function = attribute.__func__
        elif inspect.isfunction(attribute):
            function = attribute

        if function is not None:
            callables[name] = function

    return callables


def _render_dataclass_table(config_class: Any) -> Iterable[str]:
    class_name = config_class.__name__
    lines = [f'### `{class_name}`', '', '| Field | Type | Default | Description |', '| --- | --- | --- | --- |']

    for field_info in fields(config_class):
        field_name = field_info.name
        annotation = _format_annotation(field_info.type)

        if field_info.default is not MISSING:
            default = _format_default(field_info.default)
        elif field_info.default_factory is not MISSING:  # type: ignore[attr-defined]
            default_factory = field_info.default_factory  # type: ignore[attr-defined]
            default_name = getattr(default_factory, '__name__', type(default_factory).__name__)
            default = f'{default_name}()'
        else:
            default = 'Required'

        description = CONFIG_DESCRIPTIONS.get(class_name, {}).get(field_name, 'No description available.')

        lines.append(f'| `{field_name}` | `{annotation}` | `{default}` | {description} |')

    lines.append('')

    return lines


def _render_method_section(method_name: str, func: Any) -> Iterable[str]:
    docstring = inspect.getdoc(func) or ''
    doc_summary, doc_parameters, doc_return, doc_raises = _parse_sphinx_docstring(docstring)
    method_description = _resolve_method_description(method_name, doc_summary)

    signature = _callable_signature(func)
    return_annotation = _format_annotation(signature.return_annotation)

    lines = [f'### `{_format_method_anchor(method_name)}`', '', method_description, '']

    lines.append('```python')
    lines.append(_format_signature_for_display(method_name, func))
    lines.append('```')
    lines.append('')

    parameters = list(signature.parameters.values())
    if parameters:
        lines.append('| Parameter | Type | Default | Description |')
        lines.append('| --- | --- | --- | --- |')

        for parameter in parameters:
            param_name = parameter.name
            param_type = _format_annotation(parameter.annotation)
            default_value = _format_default(parameter.default)
            description = _resolve_parameter_description(method_name, param_name, doc_parameters)
            lines.append(f'| `{param_name}` | `{param_type}` | `{default_value}` | {description} |')

        lines.append('')

    lines.append(f'- Returns: `{return_annotation}` - {_resolve_return_description(method_name, doc_return)}')

    accepted_values = list(_render_accepted_values_for_method(method_name))
    if accepted_values:
        lines.append('- Accepted values:')
        lines.extend(f'  - {entry}' for entry in accepted_values)

    exception_hints = list(_resolve_method_exception_hints(method_name, doc_raises))
    if exception_hints:
        formatted_exceptions = ', '.join(f'`{exception_name}`' for exception_name in exception_hints)
        lines.append(f'- Potential exceptions: {formatted_exceptions}')

    lines.append('')

    return lines


def _render_return_type_matrix(method_names: Iterable[str], callables: Mapping[str, Any]) -> Iterable[str]:
    lines = ['## Return Type Matrix', '', '| Method | Return Type |', '| --- | --- |']

    for method_name in method_names:
        if method_name not in callables:
            continue

        signature = _callable_signature(callables[method_name])
        return_annotation = _format_annotation(signature.return_annotation)

        display_name = 'PeerberryClient(...)' if method_name == '__init__' else f'{method_name}()'
        lines.append(f'| `{display_name}` | `{return_annotation}` |')

    lines.append('')

    return lines


def _render_docstring_coverage(callables: Mapping[str, Any]) -> Iterable[str]:
    documented = []
    missing = []

    for method_name, func in callables.items():
        if inspect.getdoc(func):
            documented.append(method_name)
        else:
            missing.append(method_name)

    lines = ['## Docstring Coverage Snapshot', '']

    total = len(callables)
    documented_count = len(documented)
    lines.append(f'- Public callable count: `{total}`')
    lines.append(f'- With docstrings: `{documented_count}`')
    lines.append(f'- Without docstrings: `{len(missing)}`')

    if missing:
        lines.append(f'- Missing docstrings: `{", ".join(missing)}`')

    lines.append('')

    return lines


def _render_content() -> str:
    api_callables = _iter_api_callables()
    ordered_methods = [name for _, methods in METHOD_GROUPS for name in methods if name in api_callables]

    lines = [
        '# API Client Reference (Generated)',
        '',
        'This page is generated from source code signatures, constants, and method docstrings.',
        'Do not edit this file manually; edit `docs/gen_client_reference.py` and source docstrings instead.',
        '',
        'For low-level symbol browsing, use [Generated Module Reference](reference.md).',
        '',
        '## SDK Configuration Schema',
        '',
        'These dataclass tables are generated directly from `peerberry_sdk.config`.',
        '',
    ]

    for config_class in (RetryConfig, TransportConfig, AuthConfig, ObservabilityConfig, LifecycleConfig, SDKConfig):
        lines.extend(_render_dataclass_table(config_class))

    lines.extend(['## API Methods', ''])

    for group_title, group_methods in METHOD_GROUPS:
        lines.append(f'## {group_title}')
        lines.append('')

        for method_name in group_methods:
            func = api_callables.get(method_name)
            if func is None:
                continue

            lines.extend(_render_method_section(method_name, func))

    lines.extend(_render_return_type_matrix(ordered_methods, api_callables))
    lines.extend(_render_docstring_coverage(api_callables))

    lines.extend([
        '## Source of Accepted Value Sets',
        '',
        '- Loan sort keys: `peerberry_sdk.constants.CONSTANTS.LOAN_SORT_TYPES`',
        '- Secondary-market sort keys: `peerberry_sdk.constants.CONSTANTS.SECONDARY_LOAN_SORT_TYPES`',
        '- Investment sort keys: `CONSTANTS.CURRENT_INVESTMENT_SORT_TYPES` / `CONSTANTS.FINISHED_INVESTMENT_SORT_TYPES`',
        '- Loan type keys: `CONSTANTS.LOAN_TYPES_ID`',
        '- Profit periodicities: `CONSTANTS.PERIODICITIES`',
        '- Transaction periodicities: `CONSTANTS.TRANSACTION_PERIODICITIES`',
        '- Transaction type keys: `CONSTANTS.TRANSACTION_TYPES`',
        '',
        '## Improving Generated Detail',
        '',
        'To enrich this page, improve method docstrings in source code with explicit parameter/return/raise details.',
    ])

    return '\n'.join(lines).rstrip() + '\n'


def _open_output(path: str):
    if mkdocs_gen_files is not None:
        return mkdocs_gen_files.open(path, 'w')

    target_path = PROJECT_ROOT / 'docs' / path
    target_path.parent.mkdir(parents=True, exist_ok=True)
    return target_path.open('w', encoding='utf-8')


def write_client_reference() -> None:
    content = _render_content()

    with _open_output(OUTPUT_PATH) as fd:
        fd.write(content)


write_client_reference()


path: docs/gen_api.py
content: from pathlib import Path

try:
    import mkdocs_gen_files
except ModuleNotFoundError:
    mkdocs_gen_files = None


PACKAGE_NAME = "peerberry_sdk"
PROJECT_ROOT = Path(__file__).resolve().parents[1]
SOURCE_ROOT = PROJECT_ROOT / "src" / PACKAGE_NAME
OUTPUT_PATH = "api/reference.md"


def iter_modules() -> list[str]:
    modules = []

    for path in sorted(SOURCE_ROOT.rglob("*.py")):
        if path.name == "__main__.py":
            continue

        rel = path.relative_to(PROJECT_ROOT / "src").with_suffix("")
        module = ".".join(rel.parts)

        if module.endswith(".__init__"):
            module = module[: -len(".__init__")]

        modules.append(module)

    return modules


def write_reference_page(modules: list[str]) -> None:
    with _open_output(OUTPUT_PATH) as fd:
        fd.write("# API Reference\n\n")
        fd.write("This page is generated automatically from source modules.\n\n")
        fd.write("For practical method usage and parameter guidance, use ")
        fd.write("[API Client Reference (Generated)](client.md).\n\n")

        for module in modules:
            fd.write(f"## `{module}`\n\n")
            fd.write(f"::: {module}\n")
            fd.write("    options:\n")
            fd.write("      show_root_heading: false\n\n")


def _open_output(path: str):
    if mkdocs_gen_files is not None:
        return mkdocs_gen_files.open(path, "w")

    target_path = PROJECT_ROOT / "docs" / path
    target_path.parent.mkdir(parents=True, exist_ok=True)
    return target_path.open("w", encoding="utf-8")


write_reference_page(iter_modules())


path: docs/getting-started/installation.md
content: # Installation

This page covers production installs, local development setup, and common environment issues.

## Requirements

- Python `>=3.8`
- `pip` (latest stable recommended)
- Network access to Peerberry API endpoints at runtime

## Choose Your Install Mode

## Standard Install (PyPI)

```bash
pip install peerberry-sdk
```

Use this for normal application usage when you do not need optional two-factor helpers.

## Install With OTP Support

```bash
pip install "peerberry-sdk[otp]"
```

This adds `pyotp`, required only when you pass `tfa_secret` to the client.

## Install With Documentation Tooling

```bash
pip install "peerberry-sdk[docs]"
```

Use this if you want to build the local documentation site with Zensical.

## Recommended Virtual Environment Setup

Create and activate an isolated environment before installation:

```bash
python -m venv .venv
source .venv/bin/activate
python -m pip install --upgrade pip
pip install peerberry-sdk
```

## Verify Installation

```python
from peerberry_sdk import PeerberryClient

print(PeerberryClient)
```

If the import succeeds, installation is complete.

## Local Development Install (Repository)

From repository root:

```bash
pip install -e ".[otp,docs]"
```

This enables editable mode for package development plus optional extras.

## Build Documentation Locally

After installing docs dependencies:

Zensical uses the native `zensical.toml` configuration in this repository.

```bash
zensical build
```

Useful alternatives:

```bash
zensical serve
```

## Troubleshooting

## `ModuleNotFoundError: No module named pyotp`

Cause: Using `tfa_secret` without OTP extras.

Fix:

```bash
pip install "peerberry-sdk[otp]"
```

## `zensical: command not found`

Cause: Docs extras not installed in current environment.

Fix:

```bash
pip install "peerberry-sdk[docs]"
```

## Importing wrong interpreter/environment

Cause: `pip` and `python` are from different environments.

Fix: use interpreter-coupled install commands:

```bash
python -m pip install peerberry-sdk
python -c "from peerberry_sdk import PeerberryClient; print(PeerberryClient)"
```

## Next Steps

1. [Quickstart](quickstart.md)
2. [Authentication Guide](../guides/authentication.md)
3. [Configuration Guide](../guides/configuration.md)


path: docs/getting-started/quickstart.md
content: # Quickstart

This guide gets you from zero to a working API session with safe defaults.

## Minimal Working Example

```python
from peerberry_sdk import PeerberryClient

with PeerberryClient(email="YOUR_EMAIL", [REDACTED_SECRET]") as api:
    profile = api.get_profile()
    overview = api.get_overview()
    loans = api.get_loans(quantity=10)

    print("public_id:", profile.public_id)
    print("available money:", overview.data.get("availableMoney"))
    print("loans fetched:", len(loans))
```

What this does:

- authenticates using credentials
- fetches profile and portfolio overview
- fetches first 10 marketplace loans
- closes session automatically at context exit

## First Portfolio Calls

```python
from peerberry_sdk import PeerberryClient

with PeerberryClient(email="YOUR_EMAIL", [REDACTED_SECRET]") as api:
    tier = api.get_loyalty_tier()
    status = api.get_investment_status()
    originators = api.get_investment_originators_overview()

    print("tier:", tier.tier)
    print("status keys:", list(status.data.keys())[:5])
    print("originators:", len(originators.data))
```

## Fetch Loans With Filters

```python
from decimal import Decimal
from peerberry_sdk import PeerberryClient

with PeerberryClient(email="YOUR_EMAIL", [REDACTED_SECRET]") as api:
    loans = api.get_loans(
        quantity=20,
        min_interest_rate=Decimal("10"),
        max_remaining_term=24,
        countries=["Poland", "Lithuania"],
        loan_types=["short_term", "business"],
        sort="interest_rate",
        ascending_sort=False,
        group_guarantee=True,
    )

    for loan in loans[:3]:
        print(loan.loan_id, loan.interest_rate, loan.country)
```

## Inspect Your Investments

```python
from peerberry_sdk import PeerberryClient

with PeerberryClient(email="YOUR_EMAIL", [REDACTED_SECRET]") as api:
    current = api.get_investments(quantity=25, current=True)
    finished = api.get_investments(quantity=25, current=False)

    print("current total:", current.total)
    print("finished total:", finished.total)
```

## Transactions And Account Summary

```python
from datetime import date, timedelta
from peerberry_sdk import PeerberryClient

with PeerberryClient(email="YOUR_EMAIL", [REDACTED_SECRET]") as api:
    end_date = date.today()
    start_date = end_date - timedelta(days=30)

    summary = api.get_account_summary(start_date=start_date, end_date=end_date)
    tx = api.get_transactions(
        quantity=50,
        start_date=start_date,
        end_date=end_date,
        transaction_types=["deposit", "interest_payment", "principal_repayment"],
    )

    print("currency:", summary.currency)
    print("transactions:", len(tx))
```

## Manual Login Flow (Advanced)

Use this when you need to defer authentication until later in your application lifecycle.

```python
from peerberry_sdk import PeerberryClient

api = PeerberryClient(
    email="YOUR_EMAIL",
    [REDACTED_SECRET]",
    auto_login=False,
)

# do other setup here
api.login()

try:
    print(api.get_profile())
finally:
    api.close(logout=True)
```

## 2FA Quick Example

```python
from peerberry_sdk import PeerberryClient

with PeerberryClient(
    email="YOUR_EMAIL",
    [REDACTED_SECRET]",
    tfa_secret="BASE32_SECRET",
) as api:
    print(api.get_overview())
```

Install extras first:

```bash
pip install "peerberry-sdk[otp]"
```

## Next Steps

- For token and login lifecycle details: [Authentication](../guides/authentication.md)
- For runtime tuning and retries: [Configuration](../guides/configuration.md)
- For exceptions and production patterns: [Error Handling](../guides/error-handling.md)
- For full method-level contract docs: [Client API Reference](../api/client.md)


path: docs/api/reference.md
content: # API Reference

This page is generated automatically from source modules.

For practical method usage and parameter guidance, use [API Client Reference (Generated)](client.md).

## `peerberry_sdk`

::: peerberry_sdk
    options:
      show_root_heading: false

## `peerberry_sdk.client`

::: peerberry_sdk.client
    options:
      show_root_heading: false

## `peerberry_sdk.config`

::: peerberry_sdk.config
    options:
      show_root_heading: false

## `peerberry_sdk.constants`

::: peerberry_sdk.constants
    options:
      show_root_heading: false

## `peerberry_sdk.endpoints`

::: peerberry_sdk.endpoints
    options:
      show_root_heading: false

## `peerberry_sdk.exceptions`

::: peerberry_sdk.exceptions
    options:
      show_root_heading: false

## `peerberry_sdk.models`

::: peerberry_sdk.models
    options:
      show_root_heading: false

## `peerberry_sdk.models.entities`

::: peerberry_sdk.models.entities
    options:
      show_root_heading: false

## `peerberry_sdk.observability`

::: peerberry_sdk.observability
    options:
      show_root_heading: false

## `peerberry_sdk.token_store`

::: peerberry_sdk.token_store
    options:
      show_root_heading: false

## `peerberry_sdk.transport`

::: peerberry_sdk.transport
    options:
      show_root_heading: false

## `peerberry_sdk.transport.client_headers`

::: peerberry_sdk.transport.client_headers
    options:
      show_root_heading: false

## `peerberry_sdk.transport.request_handler`

::: peerberry_sdk.transport.request_handler
    options:
      show_root_heading: false

## `peerberry_sdk.utils`

::: peerberry_sdk.utils
    options:
      show_root_heading: false



path: docs/api/client.md
content: # API Client Reference (Generated)

This page is generated from source code signatures, constants, and method docstrings.
Do not edit this file manually; edit `docs/gen_client_reference.py` and source docstrings instead.

For low-level symbol browsing, use [Generated Module Reference](reference.md).

## SDK Configuration Schema

These dataclass tables are generated directly from `peerberry_sdk.config`.

### `RetryConfig`

| Field | Type | Default | Description |
| --- | --- | --- | --- |
| `max_retries` | `int` | `2` | Retry attempts after initial failure. |
| `backoff_factor` | `float` | `0.5` | Exponential backoff multiplier. |
| `max_backoff` | `float` | `8.0` | Maximum sleep duration between retries. |
| `retry_status_codes` | `Tuple[int, ...]` | `(429, 500, 502, 503, 504)` | HTTP status codes treated as retryable. |
| `retry_on_network_errors` | `bool` | `True` | Whether network/transport exceptions are retried. |

### `TransportConfig`

| Field | Type | Default | Description |
| --- | --- | --- | --- |
| `timeout` | `Optional[float]` | `30.0` | Default request timeout in seconds. |
| `retry` | `RetryConfig` | `RetryConfig()` | Retry policy configuration object. |
| `header_profile` | `Optional[Dict[str, str]]` | `None` | Inline static headers merged into default request profile. |
| `header_profile_path` | `Optional[str]` | `None` | Path to JSON file containing static header profile. |
| `user_agent` | `Optional[str]` | `None` | Convenience override for x-app-user-agent. |
| `app_system` | `Optional[str]` | `None` | Convenience override for x-app-system. |
| `sec_ch_ua_platform` | `Optional[str]` | `None` | Convenience override for sec-ch-ua-platform. |

### `AuthConfig`

| Field | Type | Default | Description |
| --- | --- | --- | --- |
| `auto_refresh_on_auth_error` | `bool` | `True` | Refresh token automatically after auth failures. |
| `max_refresh_attempts` | `int` | `1` | Refresh attempts for a failing request. |
| `proactive_refresh` | `bool` | `True` | Refresh access token before expiry when possible. |
| `proactive_refresh_skew_seconds` | `int` | `60` | Seconds before expiry to trigger proactive refresh. |
| `token_store` | `TokenStore` | `InMemoryTokenStore()` | Token store implementation used for load/save/clear. |
| `load_tokens_on_init` | `bool` | `True` | Load token pair from token store during client initialization. |

### `ObservabilityConfig`

| Field | Type | Default | Description |
| --- | --- | --- | --- |
| `event_hook` | `Optional[Callable[[Dict[str, Any]], None]]` | `None` | Callback receiving sanitized event dictionaries. |
| `logger` | `Optional[logging.Logger]` | `None` | Logger receiving structured SDK event messages. |
| `verbose_events` | `bool` | `False` | Enable request_start/request_end verbose events. |
| `redactor` | `Optional[Callable[[Dict[str, Any]], Dict[str, Any]]]` | `None` | Optional custom event redaction hook. |

### `LifecycleConfig`

| Field | Type | Default | Description |
| --- | --- | --- | --- |
| `logout_on_close` | `bool` | `False` | Whether PeerberryClient.close() should call logout(). |
| `swallow_logout_errors` | `bool` | `True` | Suppress logout errors during close when true. |

### `SDKConfig`

| Field | Type | Default | Description |
| --- | --- | --- | --- |
| `transport` | `TransportConfig` | `TransportConfig()` | Transport-related runtime configuration. |
| `auth` | `AuthConfig` | `AuthConfig()` | Authentication/token lifecycle configuration. |
| `observability` | `ObservabilityConfig` | `ObservabilityConfig()` | Event and logging configuration. |
| `lifecycle` | `LifecycleConfig` | `LifecycleConfig()` | Client-close and logout behavior configuration. |

## API Methods

## Constructor and Lifecycle

### `PeerberryClient(...)`

Creates a new client instance and optionally authenticates immediately.

```python
PeerberryClient(email: Optional[str] = None, [REDACTED_SECRET] = None, tfa_secret: Optional[str] = None, access_token: Optional[str] = None, refresh_token: Optional[str] = None, request_opts: Optional[dict] = None, config: Optional[SDKConfig] = None, auto_login: bool = True)
```

| Parameter | Type | Default | Description |
| --- | --- | --- | --- |
| `email` | `Optional[str]` | `None` | Account's email |
| `password` | `Optional[str]` | `None` | Account's password |
| `tfa_secret` | `Optional[str]` | `None` | Base32 secret used for two-factor authentication |
| `access_token` | `Optional[str]` | `None` | Access token used to authenticate to the API (Optional; Only pass the JWT for it to work!) (Only mandatory if account has two-factor authentication enabled) |
| `refresh_token` | `Optional[str]` | `None` | Existing refresh token used for token rotation. |
| `request_opts` | `Optional[dict]` | `None` | Optional[dict] - Additional options for :any:`requests.sessions.Session.request()`. |
| `config` | `Optional[SDKConfig]` | `None` | Optional[SDKConfig] - Transport/auth behavior overrides. |
| `auto_login` | `bool` | `True` | Automatically authenticate during initialization (True by default). |

- Returns: `Any` - Initialized client instance.
- Potential exceptions: `ValueError`, `PeerberryException`

### `login(...)`

Authenticates session and returns a bearer token string.

```python
api.login() -> str
```

- Returns: `str` - Bearer string in format "Bearer <access_token>".
- Potential exceptions: `InvalidCredentials`, `PeerberryException`

### `token(...)`

Forces access-token refresh and returns a bearer token string.

```python
api.token() -> str
```

- Returns: `str` - Bearer string in format "Bearer <access_token>".
- Potential exceptions: `TokenRefreshError`

### `logout(...)`

Logs out server-side and clears local authentication state.

```python
api.logout() -> str
```

- Returns: `str` - Fixed success message string.
- Potential exceptions: `PeerberryException`

### `close(...)`

Closes underlying transport session, optionally logging out first.

```python
api.close(logout: Optional[bool] = None) -> None
```

| Parameter | Type | Default | Description |
| --- | --- | --- | --- |
| `logout` | `Optional[bool]` | `None` | Optional explicit logout override. If None, lifecycle config is used. |

- Returns: `None` - None
- Potential exceptions: `PeerberryException (optional, when configured not to swallow logout errors)`

## Profile and Portfolio

### `get_profile(...)`

Returns investor profile information as a typed model.

```python
api.get_profile() -> Profile
```

- Returns: `Profile` - Basic information, accounts & balance information
- Potential exceptions: `PeerberryException`

### `get_overview(...)`

Returns portfolio overview data as a typed model wrapper.

```python
api.get_overview() -> Overview
```

- Returns: `Overview` - Available balance, total invested, total profit, current investments, net annual return, etc.
- Potential exceptions: `PeerberryException`

### `get_loyalty_tier(...)`

Returns the highest unlocked loyalty tier as a typed model.

```python
api.get_loyalty_tier() -> LoyaltyTier
```

- Returns: `LoyaltyTier` - Tier, extra return in percentage, the max amount and the minimum amount to be in the tier
- Potential exceptions: `PeerberryException`

### `get_profit_overview(...)`

Returns profit chart points for a date interval.

```python
api.get_profit_overview(start_date: datetime.date, end_date: datetime.date, periodicity: str = "day") -> List[ProfitPoint]
```

| Parameter | Type | Default | Description |
| --- | --- | --- | --- |
| `start_date` | `datetime.date` | `Required` | Start date of profit data |
| `end_date` | `datetime.date` | `Required` | End date of profit data |
| `periodicity` | `str` | `"day"` | Aggregation step for points; must be one of day/month/year. |

- Returns: `List[ProfitPoint]` - Profit overview for portfolio on a daily, monthly or yearly basis
- Accepted values:
  - `periodicity`: `day, month, year`
- Potential exceptions: `InvalidPeriodicity`, `PeerberryException`

### `get_investment_status(...)`

Returns investment status breakdown data.

```python
api.get_investment_status() -> InvestmentStatusOverview
```

- Returns: `InvestmentStatusOverview` - Percentage of funds in current loans and late loans (In 1-15, 16-30, and 31-60 day intervals)
- Potential exceptions: `PeerberryException`

### `get_investment_originators_overview(...)`

Returns originator allocation overview data.

```python
api.get_investment_originators_overview() -> OriginatorsOverview
```

- Returns: `OriginatorsOverview` - Percentage of funds in current loan originators
- Potential exceptions: `PeerberryException`

## Loans

### `get_loans(...)`

Fetches multiple available loans using paginated internal requests.

```python
api.get_loans(quantity: int, start_page: int = 0, max_remaining_term: Optional[int] = None, min_remaining_term: Optional[int] = None, max_interest_rate: Optional[decimal.Decimal] = None, min_interest_rate: Optional[decimal.Decimal] = None, max_available_amount: Optional[decimal.Decimal] = None, min_available_amount: Optional[decimal.Decimal] = None, countries: Optional[List[str]] = None, originators: Optional[List[str]] = None, loan_types: Optional[List[str]] = None, sort: str = "loan_amount", ascending_sort: bool = False, group_guarantee: bool = True, exclude_invested_loans: Optional[bool] = None) -> List[Loan]
```

| Parameter | Type | Default | Description |
| --- | --- | --- | --- |
| `quantity` | `int` | `Required` | Amount of loans to fetch |
| `start_page` | `int` | `0` | Number of start page to start getting loans from |
| `max_remaining_term` | `Optional[int]` | `None` | Maximum remaining term to fetch loan |
| `min_remaining_term` | `Optional[int]` | `None` | Minimum remaining term to fetch loan |
| `max_interest_rate` | `Optional[decimal.Decimal]` | `None` | Maximum interest rate to fetch loan |
| `min_interest_rate` | `Optional[decimal.Decimal]` | `None` | Minimum interest rate to fetch loan |
| `max_available_amount` | `Optional[decimal.Decimal]` | `None` | Maximum available investment amount to fetch loan |
| `min_available_amount` | `Optional[decimal.Decimal]` | `None` | Minimum available investment amount to fetch loan |
| `countries` | `Optional[List[str]]` | `None` | Filter loans by country of origin (Gets loans from all countries by default) |
| `originators` | `Optional[List[str]]` | `None` | Filter loans by originator (Gets loans from all originators by default) |
| `loan_types` | `Optional[List[str]]` | `None` | Filter loans by type (Short-term, long-term, real estate, leasing, and business) |
| `sort` | `str` | `"loan_amount"` | Loan sort key; one of loan_id/term/issued_date/interest_rate/loan_amount. |
| `ascending_sort` | `bool` | `False` | Sort by ascending order when sort is provided (By default sorts in descending order) |
| `group_guarantee` | `bool` | `True` | Restrict loans to only those with a group guarantee |
| `exclude_invested_loans` | `Optional[bool]` | `None` | Exclude loans that have been invested in previously |

- Returns: `List[Loan]` - All available loans for investment according to specified parameters
- Accepted values:
  - `sort`: `interest_rate, issued_date, loan_amount, loan_id, term`
  - `loan_types`: `business, leasing, long_term, real_estate, short_term`
- Potential exceptions: `ValueError`, `TypeError`, `InvalidSort`, `PeerberryException`

### `get_loans_page(...)`

Fetches a single page of available loans with filters.

```python
api.get_loans_page(page_num: int, quantity: int = 40, max_remaining_term: Optional[int] = None, min_remaining_term: Optional[int] = None, max_interest_rate: Optional[decimal.Decimal] = None, min_interest_rate: Optional[decimal.Decimal] = None, max_available_amount: Optional[decimal.Decimal] = None, min_available_amount: Optional[decimal.Decimal] = None, countries: Optional[List[str]] = None, originators: Optional[List[str]] = None, loan_types: Optional[List[str]] = None, sort: str = "loan_id", ascending_sort: bool = False, group_guarantee: Optional[bool] = None, exclude_invested_loans: Optional[bool] = None) -> LoanPage
```

| Parameter | Type | Default | Description |
| --- | --- | --- | --- |
| `page_num` | `int` | `Required` | Number of start page to start getting loans from |
| `quantity` | `int` | `40` | Number of loans to fetch |
| `max_remaining_term` | `Optional[int]` | `None` | Maximum remaining term to fetch loan |
| `min_remaining_term` | `Optional[int]` | `None` | Minimum remaining term to fetch loan |
| `max_interest_rate` | `Optional[decimal.Decimal]` | `None` | Maximum interest rate to fetch loan |
| `min_interest_rate` | `Optional[decimal.Decimal]` | `None` | Minimum interest rate to fetch loan |
| `max_available_amount` | `Optional[decimal.Decimal]` | `None` | Maximum available investment amount to fetch loan |
| `min_available_amount` | `Optional[decimal.Decimal]` | `None` | Minimum available investment amount to fetch loan |
| `countries` | `Optional[List[str]]` | `None` | Filter loans by country of origin (Gets loans from all countries by default) |
| `originators` | `Optional[List[str]]` | `None` | Filter loans by originator (Gets loans from all originators by default) |
| `loan_types` | `Optional[List[str]]` | `None` | Filter loans by type (Short-term, long-term, real estate, leasing, and business) |
| `sort` | `str` | `"loan_id"` | Loan sort key; one of loan_id/term/issued_date/interest_rate/loan_amount. |
| `ascending_sort` | `bool` | `False` | Sort by ascending order (By default sorts in descending order) |
| `group_guarantee` | `Optional[bool]` | `None` | Restrict loans to only those with a group guarantee |
| `exclude_invested_loans` | `Optional[bool]` | `None` | Exclude loans that have been invested in previously |

- Returns: `LoanPage` - A single page of available loans for investment according to specified parameters
- Accepted values:
  - `sort`: `interest_rate, issued_date, loan_amount, loan_id, term`
  - `loan_types`: `business, leasing, long_term, real_estate, short_term`
- Potential exceptions: `ValueError`, `TypeError`, `InvalidSort`, `PeerberryException`

### `get_secondary_loans(...)`

Fetches a single page of secondary-market loan listings with filters.

```python
api.get_secondary_loans(quantity: int, start_page: int = 0, max_selling_price: Optional[decimal.Decimal] = None, min_selling_price: Optional[decimal.Decimal] = None, max_remaining_term: Optional[int] = None, min_remaining_term: Optional[int] = None, max_interest_rate: Optional[decimal.Decimal] = None, min_interest_rate: Optional[decimal.Decimal] = None, countries: Optional[List[str]] = None, originators: Optional[List[str]] = None, loan_types: Optional[List[str]] = None, sort: Optional[str] = None, ascending_sort: bool = False, group_guarantee: Optional[bool] = None, only_my_sales: Optional[bool] = None, reduced_price_only: Optional[bool] = None, only_at_nominal_value: Optional[bool] = None) -> SecondaryLoanPage
```

| Parameter | Type | Default | Description |
| --- | --- | --- | --- |
| `quantity` | `int` | `Required` | Number of secondary-market loan listings to fetch |
| `start_page` | `int` | `0` | Number of start page to start getting listings from |
| `max_selling_price` | `Optional[decimal.Decimal]` | `None` | Maximum selling price to fetch listings |
| `min_selling_price` | `Optional[decimal.Decimal]` | `None` | Minimum selling price to fetch listings |
| `max_remaining_term` | `Optional[int]` | `None` | Maximum remaining term to fetch listings |
| `min_remaining_term` | `Optional[int]` | `None` | Minimum remaining term to fetch listings |
| `max_interest_rate` | `Optional[decimal.Decimal]` | `None` | Maximum interest rate to fetch listings |
| `min_interest_rate` | `Optional[decimal.Decimal]` | `None` | Minimum interest rate to fetch listings |
| `countries` | `Optional[List[str]]` | `None` | Filter listings by country of origin |
| `originators` | `Optional[List[str]]` | `None` | Filter listings by originator |
| `loan_types` | `Optional[List[str]]` | `None` | Filter listings by loan type |
| `sort` | `Optional[str]` | `None` | Secondary-market sort key; one of remaining_term/loan_interest/remaining_principal/sale_amount/final_payment_date/sale_valid_until. |
| `ascending_sort` | `bool` | `False` | Sort by ascending order (By default sorts in descending order) |
| `group_guarantee` | `Optional[bool]` | `None` | Restrict listings to only those with a group guarantee |
| `only_my_sales` | `Optional[bool]` | `None` | Restrict listings to only user-owned sales |
| `reduced_price_only` | `Optional[bool]` | `None` | Restrict listings to reduced price sales only |
| `only_at_nominal_value` | `Optional[bool]` | `None` | Restrict listings to nominal value sales only |

- Returns: `SecondaryLoanPage` - A single page of secondary-market listings according to specified parameters
- Accepted values:
  - `sort`: `final_payment_date, loan_interest, remaining_principal, remaining_term, sale_amount, sale_valid_until`
  - `loan_types`: `business, leasing, long_term, real_estate, short_term`
- Potential exceptions: `ValueError`, `TypeError`, `InvalidSort`, `PeerberryException`

### `get_loan_details(...)`

Returns borrower, loan, schedule, and related details for one loan.

```python
api.get_loan_details(loan_id: int) -> LoanDetails
```

| Parameter | Type | Default | Description |
| --- | --- | --- | --- |
| `loan_id` | `int` | `Required` | ID of loan to get details from |

- Returns: `LoanDetails` - The borrower's data, the loan's data, and the repayment schedule
- Potential exceptions: `PeerberryException`

### `get_agreement(...)`

Returns investment agreement bytes for a loan.

```python
api.get_agreement(loan_id: int, lang: str = "en") -> bytes
```

| Parameter | Type | Default | Description |
| --- | --- | --- | --- |
| `loan_id` | `int` | `Required` | ID of investment to get agreement of |
| `lang` | `str` | `"en"` | Language to return agreement in (ISO code, by default "en" for english) |

- Returns: `bytes` - Raw agreement bytes (file payload).
- Potential exceptions: `PeerberryException`

### `purchase_loan(...)`

Creates a purchase order for a selected loan and amount.

```python
api.purchase_loan(loan_id: int, amount: decimal.Decimal) -> PurchaseOrder
```

| Parameter | Type | Default | Description |
| --- | --- | --- | --- |
| `loan_id` | `int` | `Required` | ID of loan to purchase |
| `amount` | `decimal.Decimal` | `Required` | EUR-denominated investment amount sent to API as string. |

- Returns: `PurchaseOrder` - Object containing an order (not transaction) id
- Potential exceptions: `InsufficientFunds`, `PeerberryException`

## Investments

### `get_investments(...)`

Returns current or finished investments with paging metadata.

```python
api.get_investments(quantity: int, start_page: int = 0, max_date_of_purchase: Optional[datetime.date] = None, min_date_of_purchase: Optional[datetime.date] = None, max_interest_rate: Optional[decimal.Decimal] = None, min_interest_rate: Optional[decimal.Decimal] = None, max_invested_amount: Optional[decimal.Decimal] = None, min_invested_amount: Optional[decimal.Decimal] = None, countries: Optional[List[str]] = None, loan_types: Optional[List[str]] = None, sort: Optional[str] = None, ascending_sort: bool = False, current: bool = True) -> InvestmentPage
```

| Parameter | Type | Default | Description |
| --- | --- | --- | --- |
| `quantity` | `int` | `Required` | Amount of investments to fetch |
| `start_page` | `int` | `0` | Number of start page to start getting loans from |
| `max_date_of_purchase` | `Optional[datetime.date]` | `None` | Set maximum date of purchase to fetch loan |
| `min_date_of_purchase` | `Optional[datetime.date]` | `None` | Set minimum date of purchase to fetch loan |
| `max_interest_rate` | `Optional[decimal.Decimal]` | `None` | Set maximum interest rate to fetch loan |
| `min_interest_rate` | `Optional[decimal.Decimal]` | `None` | Set minimum interest rate to fetch loan |
| `max_invested_amount` | `Optional[decimal.Decimal]` | `None` | Set maximum invested amount to fetch loan |
| `min_invested_amount` | `Optional[decimal.Decimal]` | `None` | Set minimum invested amount to fetch loan |
| `countries` | `Optional[List[str]]` | `None` | Filter investments by country of origin (Gets investments from all countries by default) |
| `loan_types` | `Optional[List[str]]` | `None` | Filter investments by type (Short-term, long-term, real estate, leasing, and business) |
| `sort` | `Optional[str]` | `None` | Investment sort key; accepted values depend on current flag. |
| `ascending_sort` | `bool` | `False` | Sort by ascending order (By default sorts in descending order) |
| `current` | `bool` | `True` | Fetch current or finished investments (Set to False to fetch finished investments) |

- Returns: `InvestmentPage` - All current or finished investments according to specified parameters
- Accepted values:
  - `sort` when `current=True`: `estimated_final_payment_date, interest_rate, loan_amount, purchase_date`
  - `sort` when `current=False`: `estimated_final_payment_date, final_payment_date, interest_rate, loan_amount, purchase_date`
  - `loan_types`: `business, leasing, long_term, real_estate, short_term`
- Potential exceptions: `ValueError`, `TypeError`, `InvalidSort`, `PeerberryException`

### `get_mass_investments(...)`

Returns exported investments file bytes.

```python
api.get_mass_investments(countries: Optional[List[str]] = None, current: bool = True) -> bytes
```

| Parameter | Type | Default | Description |
| --- | --- | --- | --- |
| `countries` | `Optional[List[str]]` | `None` | Filter investments by country of origin (Gets investments from all countries by default) |
| `current` | `bool` | `True` | Fetch current investments or finished investments (Gets current investments by default) |

- Returns: `bytes` - Raw export file bytes.
- Potential exceptions: `TypeError`, `ValueError`, `PeerberryException`

## Transactions and Summary

### `get_account_summary(...)`

Returns normalized account summary for a date interval.

```python
api.get_account_summary(start_date: datetime.date, end_date: datetime.date) -> AccountSummary
```

| Parameter | Type | Default | Description |
| --- | --- | --- | --- |
| `start_date` | `datetime.date` | `Required` | Start date of account summary |
| `end_date` | `datetime.date` | `Required` | End date of account summary |

- Returns: `AccountSummary` - Summary of transactions during the specified time period (Invested funds, interest payments, etc.)
- Potential exceptions: `PeerberryException`

### `get_transactions(...)`

Returns typed transactions list with optional filters.

```python
api.get_transactions(quantity: Optional[int] = None, start_page: int = 0, start_date: Optional[datetime.date] = None, end_date: Optional[datetime.date] = None, period: Optional[str] = None, transaction_types: Optional[List[Union[str, int]]] = None, loan_id: Optional[int] = None) -> List[Transaction]
```

| Parameter | Type | Default | Description |
| --- | --- | --- | --- |
| `quantity` | `Optional[int]` | `None` | Amount of investments to fetch (If quantity is not specified it will fetch all investments) |
| `start_page` | `int` | `0` | Number of start page to start getting loans from |
| `start_date` | `Optional[datetime.date]` | `None` | Start date of transaction data |
| `end_date` | `Optional[datetime.date]` | `None` | End date of transaction data |
| `period` | `Optional[str]` | `None` | Optional named transaction range; today/thisWeek/thisMonth. |
| `transaction_types` | `Optional[List[Union[str, int]]]` | `None` | Types of transactions to fetch (named keys or positive integer type IDs) |
| `loan_id` | `Optional[int]` | `None` | Restrict transactions to a specific loan ID |

- Returns: `List[Transaction]` - All transactions according to specified parameters
- Accepted values:
  - `period`: `thisMonth, thisWeek, today`
  - `transaction_types` named keys: `deposit, fees_and_bonuses, interest_payment, investment, principal_repayment, withdrawal`
  - `transaction_types` numeric IDs: any positive integer (for example `14`)
- Potential exceptions: `InvalidType`, `InvalidPeriodicity`, `PeerberryException`

### `get_mass_transactions(...)`

Returns exported transactions file bytes.

```python
api.get_mass_transactions(start_date: Optional[datetime.date], end_date: Optional[datetime.date], transaction_types: Optional[List[Union[str, int]]] = None, period: Optional[str] = None, loan_id: Optional[int] = None) -> bytes
```

| Parameter | Type | Default | Description |
| --- | --- | --- | --- |
| `start_date` | `Optional[datetime.date]` | `Required` | Start date of transaction data |
| `end_date` | `Optional[datetime.date]` | `Required` | End date of transaction data |
| `transaction_types` | `Optional[List[Union[str, int]]]` | `None` | Types of transactions to fetch (named keys or positive integer type IDs) |
| `period` | `Optional[str]` | `None` | Optional named transaction range; today/thisWeek/thisMonth. |
| `loan_id` | `Optional[int]` | `None` | Restrict transactions to a specific loan ID |

- Returns: `bytes` - Raw export file bytes.
- Accepted values:
  - `period`: `thisMonth, thisWeek, today`
  - `transaction_types` named keys: `deposit, fees_and_bonuses, interest_payment, investment, principal_repayment, withdrawal`
  - `transaction_types` numeric IDs: any positive integer (for example `14`)
- Potential exceptions: `InvalidType`, `InvalidPeriodicity`, `PeerberryException`

## Metadata Helpers

### `get_countries(...)`

Returns supported country definitions keyed by display name.

```python
api.get_countries() -> dict
```

- Returns: `dict` - Dictionary of available countries and corresponding metadata.
- Potential exceptions: `PeerberryException`

### `get_originators(...)`

Returns supported originator definitions keyed by display name.

```python
api.get_originators() -> dict
```

- Returns: `dict` - Dictionary of available originators and corresponding metadata.
- Potential exceptions: `PeerberryException`

## Return Type Matrix

| Method | Return Type |
| --- | --- |
| `PeerberryClient(...)` | `Any` |
| `login()` | `str` |
| `token()` | `str` |
| `logout()` | `str` |
| `close()` | `None` |
| `get_profile()` | `Profile` |
| `get_overview()` | `Overview` |
| `get_loyalty_tier()` | `LoyaltyTier` |
| `get_profit_overview()` | `List[ProfitPoint]` |
| `get_investment_status()` | `InvestmentStatusOverview` |
| `get_investment_originators_overview()` | `OriginatorsOverview` |
| `get_loans()` | `List[Loan]` |
| `get_loans_page()` | `LoanPage` |
| `get_secondary_loans()` | `SecondaryLoanPage` |
| `get_loan_details()` | `LoanDetails` |
| `get_agreement()` | `bytes` |
| `purchase_loan()` | `PurchaseOrder` |
| `get_investments()` | `InvestmentPage` |
| `get_mass_investments()` | `bytes` |
| `get_account_summary()` | `AccountSummary` |
| `get_transactions()` | `List[Transaction]` |
| `get_mass_transactions()` | `bytes` |
| `get_countries()` | `dict` |
| `get_originators()` | `dict` |

## Docstring Coverage Snapshot

- Public callable count: `24`
- With docstrings: `24`
- Without docstrings: `0`

## Source of Accepted Value Sets

- Loan sort keys: `peerberry_sdk.constants.CONSTANTS.LOAN_SORT_TYPES`
- Secondary-market sort keys: `peerberry_sdk.constants.CONSTANTS.SECONDARY_LOAN_SORT_TYPES`
- Investment sort keys: `CONSTANTS.CURRENT_INVESTMENT_SORT_TYPES` / `CONSTANTS.FINISHED_INVESTMENT_SORT_TYPES`
- Loan type keys: `CONSTANTS.LOAN_TYPES_ID`
- Profit periodicities: `CONSTANTS.PERIODICITIES`
- Transaction periodicities: `CONSTANTS.TRANSACTION_PERIODICITIES`
- Transaction type keys: `CONSTANTS.TRANSACTION_TYPES`

## Improving Generated Detail

To enrich this page, improve method docstrings in source code with explicit parameter/return/raise details.


path: docs/guides/configuration.md
content: # Configuration

`peerberry_sdk` runtime behavior is configured through `SDKConfig`, which groups transport, auth, observability, and lifecycle settings.

## Configuration Map

- `SDKConfig.transport` -> `TransportConfig`
- `SDKConfig.auth` -> `AuthConfig`
- `SDKConfig.observability` -> `ObservabilityConfig`
- `SDKConfig.lifecycle` -> `LifecycleConfig`

## Full Example

```python
import logging
from peerberry_sdk import (
    PeerberryClient,
    AuthConfig,
    InMemoryTokenStore,
    LifecycleConfig,
    ObservabilityConfig,
    RetryConfig,
    SDKConfig,
    TransportConfig,
)

config = SDKConfig(
    transport=TransportConfig(
        timeout=20.0,
        retry=RetryConfig(
            max_retries=3,
            backoff_factor=0.5,
            max_backoff=8.0,
            retry_status_codes=(429, 500, 502, 503, 504),
            retry_on_network_errors=True,
        ),
        header_profile={"x-app-system": "Linux x86_64"},
        user_agent="Chrome 145.0.0.0",
        app_system="Linux x86_64",
        sec_ch_ua_platform='"Linux"',
    ),
    auth=AuthConfig(
        auto_refresh_on_auth_error=True,
        max_refresh_attempts=1,
        proactive_refresh=True,
        proactive_refresh_skew_seconds=60,
        token_store=InMemoryTokenStore(),
        load_tokens_on_init=True,
    ),
    observability=ObservabilityConfig(
        event_hook=lambda event: print(event["event_type"]),
        logger=logging.getLogger("peerberry-sdk"),
        verbose_events=False,
        redactor=None,
    ),
    lifecycle=LifecycleConfig(
        logout_on_close=False,
        swallow_logout_errors=True,
    ),
)

api = PeerberryClient(email="YOUR_EMAIL", [REDACTED_SECRET]", config=config)
```

## `RetryConfig`

Controls retry behavior in HTTP transport.

| Field | Type | Default | Meaning |
| --- | --- | --- | --- |
| `max_retries` | `int` | `2` | Number of retry attempts after first failure. |
| `backoff_factor` | `float` | `0.5` | Exponential backoff base (`factor * 2**attempt`). |
| `max_backoff` | `float` | `8.0` | Upper bound for delay. |
| `retry_status_codes` | `tuple[int, ...]` | `(429, 500, 502, 503, 504)` | HTTP statuses treated as retryable. |
| `retry_on_network_errors` | `bool` | `True` | Retries transport/network exceptions. |

Notes:

- `Retry-After` header is honored when present (capped by `max_backoff`).
- Exhausted retries raise typed exceptions with context.

## `TransportConfig`

Controls request-level transport settings.

| Field | Type | Default | Meaning |
| --- | --- | --- | --- |
| `timeout` | `float | None` | `30.0` | Request timeout in seconds (if set). |
| `retry` | `RetryConfig` | defaults | Retry policy object. |
| `header_profile` | `dict[str, str] | None` | `None` | Inline static header overrides. |
| `header_profile_path` | `str | None` | `None` | Path to JSON header profile. |
| `user_agent` | `str | None` | `None` | Convenience override for `x-app-user-agent`. |
| `app_system` | `str | None` | `None` | Convenience override for `x-app-system`. |
| `sec_ch_ua_platform` | `str | None` | `None` | Convenience override for `sec-ch-ua-platform`. |

Header source precedence (lowest to highest):

1. SDK defaults
2. profile file (`header_profile_path` or env)
3. inline `header_profile`
4. convenience fields (`user_agent`, etc.) merged into request opts
5. per-request headers

## Header Profiles

Three ways to supply profile headers:

```python
# 1) Inline
PeerberryClient(..., request_opts={"header_profile": {"x-app-system": "Linux x86_64"}})

# 2) File path
PeerberryClient(..., request_opts={"header_profile_path": "/path/to/profile.json"})
```

```bash
# 3) Environment variable
export peerberry_sdk_HEADER_PROFILE=/path/to/profile.json
```

Accepted profile JSON formats:

```json
{ "x-app-system": "Linux x86_64", "x-app-user-agent": "Chrome 145.0.0.0" }
```

or:

```json
{ "headers": { "x-app-system": "Linux x86_64" } }
```

## `AuthConfig`

Controls login/refresh/token persistence behavior.

| Field | Type | Default | Meaning |
| --- | --- | --- | --- |
| `auto_refresh_on_auth_error` | `bool` | `True` | Retry request after auth failure by refreshing token. |
| `max_refresh_attempts` | `int` | `1` | Number of refresh attempts per failing request. |
| `proactive_refresh` | `bool` | `True` | Refresh before request if token near expiry. |
| `proactive_refresh_skew_seconds` | `int` | `60` | Expiry lead time window for proactive refresh. |
| `token_store` | `TokenStore` | `InMemoryTokenStore()` | Store for load/save/clear token pair. |
| `load_tokens_on_init` | `bool` | `True` | Load tokens from store when constructor tokens missing. |

## `ObservabilityConfig`

Controls event emission and logging hooks.

| Field | Type | Default | Meaning |
| --- | --- | --- | --- |
| `event_hook` | `Callable[[dict], None] | None` | `None` | Callback for each emitted event. |
| `logger` | `logging.Logger | None` | `None` | Logger integration for emitted events. |
| `verbose_events` | `bool` | `False` | Enables `request_start` and `request_end` events. |
| `redactor` | `Callable[[dict], dict] | None` | `None` | Custom redaction pass for event payloads. |

Always-emitted event families:

- `error`
- `retry`
- `token_refresh`

Verbose-only event families:

- `request_start`
- `request_end`

Sensitive keys are redacted by default (`authorization`, `access_token`, `refresh_token`, `password`, etc.).

## `LifecycleConfig`

Controls behavior at `PeerberryClient.close()`.

| Field | Type | Default | Meaning |
| --- | --- | --- | --- |
| `logout_on_close` | `bool` | `False` | Attempt remote logout during close. |
| `swallow_logout_errors` | `bool` | `True` | Suppress logout errors during close. |

## Common Configuration Profiles

## Conservative Production Profile

```python
from peerberry_sdk import SDKConfig, TransportConfig, RetryConfig, AuthConfig

config = SDKConfig(
    transport=TransportConfig(
        timeout=20.0,
        retry=RetryConfig(max_retries=2, backoff_factor=0.5),
    ),
    auth=AuthConfig(
        auto_refresh_on_auth_error=True,
        max_refresh_attempts=1,
        proactive_refresh=True,
    ),
)
```

## Low-Latency Profile

```python
from peerberry_sdk import SDKConfig, TransportConfig, RetryConfig

config = SDKConfig(
    transport=TransportConfig(
        timeout=10.0,
        retry=RetryConfig(max_retries=1, backoff_factor=0.2, max_backoff=1.0),
    )
)
```

## Debug/Trace Profile

```python
import logging
from peerberry_sdk import SDKConfig, ObservabilityConfig

config = SDKConfig(
    observability=ObservabilityConfig(
        logger=logging.getLogger("peerberry-sdk"),
        verbose_events=True,
    )
)
```

## Related Docs

- [Authentication](authentication.md)
- [Error Handling](error-handling.md)
- [Client API Reference](../api/client.md)


path: docs/guides/error-handling.md
content: # Error Handling

`peerberry_sdk` raises structured exceptions with context to support predictable production handling.

## Exception Hierarchy

All SDK exceptions derive from:

- `PeerberryException`

Main subclasses:

- `PeerberryAPIError`
  - `AuthenticationError`
    - `InvalidCredentials`
    - `TokenRefreshError`
  - `AuthorizationError`
  - `RateLimitError`
  - `ValidationError`
    - `InsufficientFunds`
  - `ServerError`
- `NetworkError`
- `InvalidPeriodicity`
- `InvalidSort`
- `InvalidType`

## Exception Context Fields

Most runtime exceptions include rich metadata:

| Field | Meaning |
| --- | --- |
| `status_code` | HTTP status from API response, when available |
| `error_code` | API-specific error code if present |
| `errors` | Parsed API `errors` payload |
| `payload` | Parsed API response payload |
| `url` | Request URL |
| `method` | HTTP method |
| `retry_after` | Parsed `Retry-After` seconds, if present |
| `is_retryable` | SDK hint based on retry policy/status |
| `request_id` | SDK-generated request correlation ID |
| `original_exception` | Underlying exception, when wrapping |

## Typical Failure Classes

## Network Failures

Raised as `NetworkError` when transport fails and retries are exhausted.

```python
from peerberry_sdk import NetworkError

try:
    api.get_overview()
except NetworkError as exc:
    print(exc.request_id, exc.is_retryable)
```

## Rate Limiting (`429`)

Raised as `RateLimitError`.

`retry_after` is populated when response includes a valid `Retry-After` header.

```python
from peerberry_sdk import RateLimitError

try:
    api.get_loans(quantity=10)
except RateLimitError as exc:
    wait_seconds = exc.retry_after or 5
    print("retry in", wait_seconds)
```

## Authentication Failures (`401`)

Raised as `AuthenticationError` (or `InvalidCredentials` for login-specific flows).

If auto-refresh is enabled, SDK may attempt token refresh first.

## Token Refresh Failures

Raised as `TokenRefreshError` when:

- refresh token is missing
- refresh request fails
- refresh response lacks `access_token`

## Validation Failures (`4xx`)

Raised as `ValidationError` for general client-side request problems.

Domain-specific local validation may also raise:

- `InvalidPeriodicity`
- `InvalidSort`
- `InvalidType`
- `ValueError` / `TypeError` for invalid argument shapes

## Server Failures (`5xx`)

Raised as `ServerError` after configured retries are exhausted.

## Recommended Catch Pattern

```python
from peerberry_sdk import (
    PeerberryException,
    RateLimitError,
    ValidationError,
    TokenRefreshError,
    NetworkError,
    ServerError,
)

try:
    result = api.get_transactions(quantity=50)
except RateLimitError as exc:
    # sleep + retry strategy
    raise
except ValidationError as exc:
    # fix caller inputs
    raise
except TokenRefreshError as exc:
    # trigger re-authentication path
    raise
except (NetworkError, ServerError) as exc:
    # transient upstream issue path
    raise
except PeerberryException as exc:
    # SDK fallback boundary
    raise
```

## Operational Logging Pattern

Use `request_id` for log correlation:

```python
try:
    api.get_overview()
except Exception as exc:
    request_id = getattr(exc, "request_id", None)
    print("request_id=", request_id, "error=", exc)
```

## Observability + Errors

Enable `ObservabilityConfig` to emit:

- `error`
- `retry`
- `token_refresh`
- `request_start`/`request_end` (if `verbose_events=True`)

This gives a useful trail without needing HTTP debug dumps.

## Retriable vs Non-Retriable Decisions

Use `is_retryable` as hint only.

Suggested approach:

- prefer explicit handling for `RateLimitError` (`retry_after`)
- treat `NetworkError` and `ServerError` as transient
- treat `ValidationError` and local argument validation errors as hard failures
- treat `TokenRefreshError` as auth-state recovery requirement

## Input Validation Errors Before HTTP Call

Several methods validate arguments locally and raise fast:

- invalid periodicity -> `InvalidPeriodicity`
- invalid sort -> `InvalidSort`
- invalid transaction type -> `InvalidType`
- non-list filters where lists are required -> `TypeError`
- impossible quantity bounds -> `ValueError`

This is intentional and helps catch bugs early.

## Related Docs

- [Authentication](authentication.md)
- [Configuration](configuration.md)
- [Client API Reference](../api/client.md)


path: docs/guides/authentication.md
content: # Authentication

This guide explains all authentication paths supported by `peerberry_sdk.PeerberryClient` and how token lifecycle behavior is controlled.

## Supported Authentication Inputs

The client accepts three input patterns:

1. `email` + `password`
2. `email` + `password` + `tfa_secret` (2FA)
3. `access_token` + optional `refresh_token`

## Default Flow (`auto_login=True`)

By default, authentication runs during `PeerberryClient(...)` initialization.

```python
from peerberry_sdk import PeerberryClient

api = PeerberryClient(email="YOUR_EMAIL", [REDACTED_SECRET]")
```

Behavior:

- if access token is present, SDK validates it first
- if token is valid, session starts immediately
- if token is stale and credentials are available, SDK falls back to credential login

## Two-Factor Authentication (TOTP)

If account login requires TOTP, pass `tfa_secret`:

```python
from peerberry_sdk import PeerberryClient

api = PeerberryClient(
    email="YOUR_EMAIL",
    [REDACTED_SECRET]",
    tfa_secret="BASE32_TOTP_SECRET",
)
```

Prerequisite:

```bash
pip install "peerberry-sdk[otp]"
```

Without `pyotp` installed, the 2FA login path cannot complete.

## Token-Based Startup

You can provide existing tokens directly:

```python
from peerberry_sdk import PeerberryClient

api = PeerberryClient(
    access_token="ACCESS_TOKEN",
    refresh_token="REFRESH_TOKEN",
)
```

If access token is stale:

- SDK can refresh automatically when configured to do so
- fallback to credentials is possible only if `email` and `password` are available

## Disabling Auto Login

Use deferred authentication when startup order matters:

```python
from peerberry_sdk import PeerberryClient

api = PeerberryClient(
    email="YOUR_EMAIL",
    [REDACTED_SECRET]",
    auto_login=False,
)

# authenticate later
api.login()
```

## Token Refresh Behavior

Refresh logic is controlled by `AuthConfig`:

- `auto_refresh_on_auth_error` (default `True`)
- `max_refresh_attempts` (default `1`)
- `proactive_refresh` (default `True`)
- `proactive_refresh_skew_seconds` (default `60`)

### Reactive Refresh

Triggered when a request gets an auth error and auto refresh is enabled.

### Proactive Refresh

Triggered before requests when token expiry is near (`exp - skew_seconds`).

## Forcing a Refresh

You can trigger refresh explicitly:

```python
bearer = api.token()
print(bearer)  # "Bearer ..."
```

If refresh token is missing or refresh fails, `TokenRefreshError` is raised.

## Token Persistence With `TokenStore`

`AuthConfig.token_store` defines where tokens are loaded/saved.

Default store:

- `InMemoryTokenStore` (process memory only)

Standard behavior:

- load on init when `load_tokens_on_init=True`
- save on login/refresh
- clear on logout

## Custom Token Store

Implement `TokenStore` to persist tokens in your own backend.

```python
from peerberry_sdk import TokenStore, TokenPair

class EnvTokenStore(TokenStore):
    def load(self) -> TokenPair:
        return TokenPair(
            access_token=None,
            refresh_token=None,
        )

    def save(self, access_token, refresh_token) -> None:
        # persist to your secure storage
        pass

    def clear(self) -> None:
        # clear persisted tokens
        pass
```

Then plug into `AuthConfig(token_store=...)`.

## Login Method Details

`login()` returns a bearer token string:

```python
bearer = api.login()
```

Return format:

- `"Bearer <access_token>"`

Common failures:

- `InvalidCredentials`
- other `PeerberryException` subclasses

## Logout and Close

Preferred shutdown:

```python
api.close(logout=True)
```

Equivalent explicit sequence:

```python
api.logout()
api.close()
```

`logout()` clears active auth headers and token store data.

## Lifecycle Modes

You can enforce logout during close via config:

```python
from peerberry_sdk import PeerberryClient, LifecycleConfig, SDKConfig

cfg = SDKConfig(lifecycle=LifecycleConfig(logout_on_close=True))
api = PeerberryClient(email="YOUR_EMAIL", [REDACTED_SECRET]", config=cfg)
```

Control logout error behavior:

- `swallow_logout_errors=True` -> ignore logout failures during close
- `swallow_logout_errors=False` -> re-raise logout failure after close

## Recommended Production Pattern

```python
from peerberry_sdk import PeerberryClient, SDKConfig, AuthConfig, TokenRefreshError

config = SDKConfig(
    auth=AuthConfig(
        auto_refresh_on_auth_error=True,
        max_refresh_attempts=1,
        proactive_refresh=True,
    )
)

with PeerberryClient(email="YOUR_EMAIL", [REDACTED_SECRET]", config=config) as api:
    try:
        print(api.get_overview())
    except TokenRefreshError:
        # signal re-authentication path in your app
        raise
```

## Related Docs

- [Configuration](configuration.md)
- [Error Handling](error-handling.md)
- [Client API Reference](../api/client.md)


path: docs/guides/models.md
content: # Typed Models

The SDK returns dataclass-based models for most read operations.

Each model is immutable (`frozen=True`) and retains the original payload in `raw`.

## Why Models Matter

- More predictable field access than raw dicts
- Decimal-aware numeric parsing for monetary data
- Stable shape at service boundaries
- Easy fallback to original payload with `.to_dict()`

## Common Model Conventions

- Missing or invalid source values become `None` when appropriate
- Numeric-like values are parsed to `Decimal`/`int` where defined by model
- Unknown extra fields are preserved in `raw`

## Profile and Portfolio Models

## `Profile`

Fields:

- `public_id: str | None`
- `email: str | None`
- `first_name: str | None`
- `last_name: str | None`
- `currency: str | None`

## `Overview`

Fields:

- `data: dict[str, Any]`

Notes:

- This model intentionally preserves overview payload shape as-is in `data`.

## `LoyaltyTier`

Fields:

- `tier: str | None`
- `extra_return: str | None`
- `max_amount: Decimal | None`
- `min_amount: Decimal | None`

## `ProfitPoint`

Fields:

- `period: str | None`
- `amount: Decimal | None`

## `InvestmentStatusOverview`

Fields:

- `data: dict[str, Any]`

## `OriginatorsOverview`

Fields:

- `data: dict[str, dict[str, Any]]`

## Loan and Investment Models

## `Loan`

Fields:

- `loan_id: int | None`
- `country: str | None`
- `originator: str | None`
- `status: str | None`
- `interest_rate: Decimal | None`
- `amount: Decimal | None`
- `available_to_invest: Decimal | None`
- `term: int | None`
- `currency: str | None`

## `LoanPage`

Fields:

- `data: list[Loan]`
- `total: int | None`
- `page_size: int | None`
- `offset: int | None`

## `SecondaryLoan`

Fields:

- `sale_id: str | None`
- `investment_id: int | None`
- `sale_amount: Decimal | None`
- `sale_status: str | None`
- `sale_valid_until: str | None`
- `originator_title: str | None`
- `loan_id: int | None`
- `loan_term_type: str | None`
- `loan_interest: Decimal | None`
- `remaining_principal: Decimal | None`
- `remaining_term: int | None`
- `seller: bool | None`
- `country_id: int | None`
- `country_iso: str | None`
- `investment_interest: Decimal | None`
- `final_payment_date: str | None`
- `investment_date: str | None`
- `loan_status: str | None`

## `SecondaryLoanPage`

Fields:

- `items: list[SecondaryLoan]`
- `total: int | None`
- `page_count: int | None`
- `current_page: int | None`
- `page_size: int | None`
- `offset: int | None`
- `available_sort_fields: dict[str, str]`

## `LoanDetails`

Fields:

- `borrower_data: dict[str, Any]`
- `loan: Loan | None`
- `originator_data: dict[str, Any]`
- `pledge_data: dict[str, Any]`
- `schedule_data: list[dict[str, Any]]`

## `Investment`

Fields:

- `investment_id: int | None`
- `loan_id: int | None`
- `country: str | None`
- `status: str | None`
- `amount: Decimal | None`
- `interest_rate: Decimal | None`
- `currency: str | None`
- `purchase_date: str | None`

## `InvestmentPage`

Fields:

- `data: list[Investment]`
- `total: int | None`
- `page_size: int | None`
- `offset: int | None`

## Transactions and Summary Models

## `Transaction`

Fields:

- `transaction_id: str | None`
- `type: str | None`
- `amount: Decimal | None`
- `currency: str | None`
- `occurred_at: str | None`
- `description: str | None`

## `AccountSummary`

Fields:

- `balance_data: dict[str, Any]`
- `cash_flow_data: dict[str, Any]`
- `currency: str | None`

## `PurchaseOrder`

Fields:

- `order_id: int | None`

## Model Usage Patterns

## Safe Field Access

```python
profile = api.get_profile()
print(profile.public_id)
```

## Fallback To Raw Payload

```python
loan = api.get_loans(quantity=1)[0]
print(loan.raw)
print(loan.to_dict())
```

## Decimal-Safe Processing

```python
from decimal import Decimal

investments = api.get_investments(quantity=50)
total = sum((inv.amount or Decimal("0")) for inv in investments.data)
print(total)
```

## Converting Lists To Models Manually

The helper `to_models(items, model_class)` can map iterable payloads to models.

```python
from peerberry_sdk.models import to_models, Transaction

transactions = to_models(raw_items, Transaction)
```

## Return Type Matrix

| API method | Return |
| --- | --- |
| `get_profile()` | `Profile` |
| `get_overview()` | `Overview` |
| `get_loyalty_tier()` | `LoyaltyTier` |
| `get_profit_overview()` | `list[ProfitPoint]` |
| `get_investment_status()` | `InvestmentStatusOverview` |
| `get_investment_originators_overview()` | `OriginatorsOverview` |
| `get_loans()` | `list[Loan]` |
| `get_loans_page()` | `LoanPage` |
| `get_secondary_loans()` | `SecondaryLoanPage` |
| `get_loan_details()` | `LoanDetails` |
| `purchase_loan()` | `PurchaseOrder` |
| `get_investments()` | `InvestmentPage` |
| `get_transactions()` | `list[Transaction]` |
| `get_account_summary()` | `AccountSummary` |
| `get_mass_investments()` | `bytes` |
| `get_mass_transactions()` | `bytes` |
| `get_agreement()` | `bytes` |

## Related Docs

- [Client API Reference](../api/client.md)
- [Error Handling](error-handling.md)


Security scan verdicts — ClawScan: suspicious | static analysis: clean | VirusTotal: clean | SkillSpector: suspicious (HIGH)