Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/getting-started/quickstart.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ client = FinWise()

```python
accounts = client.accounts.list()
for account in accounts.data:
for account in accounts:
print(f"{account.name}: {account.currency} {account.balance}")
```

Expand Down
4 changes: 2 additions & 2 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ For the official API documentation, see [finwiseapp.io/docs/api](https://finwise

- **Type-safe**: Full type hints and Pydantic models
- **Automatic retries**: Exponential backoff for transient errors
- **Pagination support**: Easy iteration through paginated results
- **Simple API**: Returns plain Python lists for easy iteration
- **Context manager**: Automatic resource cleanup

## Quick Example
Expand All @@ -25,7 +25,7 @@ client = FinWise(api_key="your-api-key")

# List all accounts
accounts = client.accounts.list()
for account in accounts.data:
for account in accounts:
print(f"{account.name}: {account.currency} {account.balance}")
```

Expand Down
82 changes: 30 additions & 52 deletions docs/reference/pagination.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# Pagination

All list methods return a `PaginatedResponse` object for easy iteration through results.
!!! note "API Behavior"
The FinWise API does not currently support pagination for list endpoints. All `list()` methods return all available items as a simple Python list.

## Basic Usage

Expand All @@ -9,67 +10,44 @@ from finwise import FinWise

client = FinWise(api_key="your-api-key")

# Get first page
accounts = client.accounts.list(page_number=1, page_size=50)
# Get all accounts
accounts = client.accounts.list()
print(f"Found {len(accounts)} accounts")

print(f"Page {accounts.page_number} of {accounts.total_pages}")
print(f"Showing {len(accounts)} of {accounts.total_count} accounts")
```

## PaginatedResponse Properties

| Property | Type | Description |
|----------|------|-------------|
| `data` | `list` | Items on the current page |
| `page_number` | `int` | Current page number (1-indexed) |
| `page_size` | `int` | Items per page |
| `total_count` | `int` | Total items across all pages |
| `total_pages` | `int` | Total number of pages |
| `has_next` | `bool` | Whether there's a next page |
| `has_previous` | `bool` | Whether there's a previous page |

## Iterating Through Items

```python
# Iterate through items on this page
for account in accounts.data:
print(account.name)

# Or iterate directly on the response
# Iterate through items
for account in accounts:
print(account.name)

# Access by index
first_account = accounts[0] # or accounts.data[0]
first_account = accounts[0]
```

## Fetching Multiple Pages

```python
# Check for more pages
if accounts.has_next:
next_page = client.accounts.list(
page_number=accounts.page_number + 1,
page_size=50,
)
```
## List Methods

## Iterating Through All Pages
All list methods return a `list` of model objects:

```python
page_number = 1
all_accounts = []
| Method | Return Type |
|--------|-------------|
| `accounts.list()` | `list[Account]` |
| `transactions.list()` | `list[Transaction]` |
| `transaction_categories.list()` | `list[TransactionCategory]` |
| `account_balances.list()` | `list[AccountBalance]` |

while True:
accounts = client.accounts.list(
page_number=page_number,
page_size=100
)
all_accounts.extend(accounts.data)
## Filtering

if not accounts.has_next:
break
page_number += 1
While pagination is not supported, you can still filter results using available parameters:

print(f"Fetched {len(all_accounts)} accounts")
```python
# Filter transactions by date range
transactions = client.transactions.list(
start_date=date(2024, 1, 1),
end_date=date(2024, 1, 31),
type="expense",
)

# Filter by account
transactions = client.transactions.list(account_id="acc_123")

# Filter account balances
balances = client.account_balances.list(account_id="acc_123")
```
10 changes: 7 additions & 3 deletions docs/usage/account-balances.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,16 +30,16 @@ balance = client.account_balances.create(
```python
balances = client.account_balances.list(account_id="acc_123")

for balance in balances.data:
for balance in balances:
print(f"{balance.balance_date}: {balance.balance}")
```

## Get Aggregated Balance

Get the total balance across all accounts:
Get the total balance across all accounts for a specific currency:

```python
summary = client.account_balances.aggregated()
summary = client.account_balances.aggregated(currency="USD")
print(f"Total: {summary.currency} {summary.total_balance}")
print(f"Across {summary.account_count} accounts")
```
Expand All @@ -48,10 +48,14 @@ You can also get the balance as of a specific date:

```python
summary = client.account_balances.aggregated(
currency="USD",
as_of_date=date(2024, 1, 31)
)
```

!!! note "Currency Parameter"
The `currency` parameter specifies which currency to aggregate balances for (e.g., "USD", "EUR", "ZAR").

## Archive a Balance Record

```python
Expand Down
10 changes: 1 addition & 9 deletions docs/usage/accounts.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,17 +50,9 @@ account = client.accounts.update(
## List Accounts

```python
# Basic listing
accounts = client.accounts.list()
for account in accounts.data:
for account in accounts:
print(f" - {account.name}")

# With pagination
accounts = client.accounts.list(page_number=1, page_size=50)
print(f"Total accounts: {accounts.total_count}")

if accounts.has_next:
next_page = client.accounts.list(page_number=2, page_size=50)
```

## Archive an Account
Expand Down
2 changes: 1 addition & 1 deletion docs/usage/transaction-categories.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ subcategory = client.transaction_categories.create(
```python
categories = client.transaction_categories.list()

for cat in categories.data:
for cat in categories:
prefix = " " if cat.is_subcategory else ""
print(f"{prefix}{cat.name}")
```
Expand Down
16 changes: 1 addition & 15 deletions docs/usage/transactions.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,24 +46,10 @@ transactions = client.transactions.list(
type="expense",
)

for txn in transactions.data:
for txn in transactions:
print(f"{txn.transaction_date}: {txn.description} ({txn.amount})")
```

## Get Aggregated Summary

Get totals for a date range:

```python
summary = client.transactions.aggregated(
start_date=date(2024, 1, 1),
end_date=date(2024, 1, 31),
)
print(f"Income: {summary.total_income}")
print(f"Expenses: {summary.total_expenses}")
print(f"Net: {summary.net_amount}")
```

## Archive a Transaction

```python
Expand Down
10 changes: 1 addition & 9 deletions src/finwise/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
>>>
>>> # List accounts
>>> accounts = client.accounts.list()
>>> for account in accounts.data:
>>> for account in accounts:
... print(account.name)
>>>
>>> # Create a transaction
Expand Down Expand Up @@ -74,16 +74,12 @@
AccountCreateRequest,
AccountUpdateRequest,
AggregatedBalance,
AggregatedTransactions,
Transaction,
TransactionCategory,
TransactionCategoryCreateRequest,
TransactionCreateRequest,
)

# Types
from finwise.types import PaginatedResponse, PaginationParams

__all__ = [
# Version
"__version__",
Expand Down Expand Up @@ -112,11 +108,7 @@
# Models - Transaction
"Transaction",
"TransactionCreateRequest",
"AggregatedTransactions",
# Models - Transaction Category
"TransactionCategory",
"TransactionCategoryCreateRequest",
# Types
"PaginatedResponse",
"PaginationParams",
]
38 changes: 37 additions & 1 deletion src/finwise/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,42 @@ class FinWiseTimeoutError(FinWiseError):
pass


def _extract_error_message(response_body: dict[str, Any]) -> str:
"""
Extract error message from API response formats.

The FinWise API returns errors in the format:
{"error": {"errors": [...], "name": "...", "message": "..."}}

This function handles multiple formats for robustness.

Args:
response_body: Parsed JSON response body.

Returns:
Extracted error message or a fallback message.
"""
# Try top-level message first
if "message" in response_body and isinstance(response_body["message"], str):
return response_body["message"]

# Try nested error.message (actual API format)
if "error" in response_body and isinstance(response_body["error"], dict):
error_obj = response_body["error"]
if "message" in error_obj and isinstance(error_obj["message"], str):
return error_obj["message"]

# Try detail (FastAPI style)
if "detail" in response_body and isinstance(response_body["detail"], str):
return response_body["detail"]

# Fallback: include response body for debugging
if response_body:
return f"API error: {response_body}"

return "Unknown error (empty response body)"


def raise_for_status(
status_code: int,
response_body: dict[str, Any],
Expand All @@ -190,7 +226,7 @@ def raise_for_status(
Raises:
FinWiseAPIError: Appropriate subclass based on status code.
"""
message = response_body.get("message", "Unknown error")
message = _extract_error_message(response_body)
error_code = response_body.get("code")

kwargs: dict[str, Any] = {
Expand Down
2 changes: 0 additions & 2 deletions src/finwise/models/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
BalanceType,
)
from finwise.models.transaction import (
AggregatedTransactions,
Transaction,
TransactionCreateRequest,
)
Expand All @@ -40,7 +39,6 @@
# Transaction
"Transaction",
"TransactionCreateRequest",
"AggregatedTransactions",
# Transaction Category
"TransactionCategory",
"TransactionCategoryCreateRequest",
Expand Down
2 changes: 1 addition & 1 deletion src/finwise/resources/account_balances.py
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ def aggregated(
if as_of_date:
params["asOfDate"] = as_of_date.isoformat()
if currency:
params["currency"] = currency
params["currencyCode"] = currency

response = self._transport.get(
f"{self._path}/aggregated", params=params or None
Expand Down
Loading