Metadata-Version: 2.5
Name: smartbills
Version: 2.0.0
Summary: Official Python SDK for the Smartbills API — digital receipts, expense management, invoicing, payments, and more.
Project-URL: Homepage, https://smartbills.io
Project-URL: Documentation, https://docs.smartbills.io
Project-URL: Repository, https://github.com/smartbills/python-sdk
Project-URL: Changelog, https://github.com/smartbills/python-sdk/blob/main/CHANGELOG.md
Project-URL: Bug Tracker, https://github.com/smartbills/python-sdk/issues
Author-email: Smartbills <dev@smartbills.io>
License-Expression: MIT
Keywords: accounting,api,async,expenses,finance,fintech,invoicing,payments,sdk,smartbills
Classifier: Development Status :: 3 - Alpha
Classifier: Framework :: AsyncIO
Classifier: Intended Audience :: Developers
Classifier: Intended Audience :: Financial and Insurance Industry
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Office/Business :: Financial
Classifier: Topic :: Office/Business :: Financial :: Accounting
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Typing :: Typed
Requires-Python: >=3.10
Requires-Dist: httpx>=0.27.0
Requires-Dist: pydantic>=2.0.0
Provides-Extra: dev
Requires-Dist: mypy>=1.10.0; extra == 'dev'
Requires-Dist: pytest-asyncio>=0.23.0; extra == 'dev'
Requires-Dist: pytest-httpx>=0.30.0; extra == 'dev'
Requires-Dist: pytest>=8.0.0; extra == 'dev'
Requires-Dist: ruff>=0.4.0; extra == 'dev'
Description-Content-Type: text/markdown

<p align="center">
  <a href="https://smartbills.io/?utm_source=github&utm_medium=logo" target="_blank">
    <!-- <img src="https://smartbills-brand.storage.googleapis.com/smartbills-wordmark-dark-280x84.png" alt="Smartbills" width="280" height="84"> -->
  </a>
</p>

Smartbills is on a mission to help developers build the best financial tools. If you want to join us,
[<kbd>**Check out our open positions**</kbd>](https://smartbills.io/careers/)

[![PyPI version](https://img.shields.io/pypi/v/smartbills.svg)](https://pypi.org/project/smartbills/)
[![Python versions](https://img.shields.io/pypi/pyversions/smartbills.svg)](https://pypi.org/project/smartbills/)
[![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)](https://opensource.org/licenses/MIT)
[![CI](https://github.com/smartbills/python-sdk/actions/workflows/build-and-release.yml/badge.svg)](https://github.com/smartbills/python-sdk/actions/workflows/build-and-release.yml)

# Official Smartbills SDK for Python

The `smartbills` package is the official Python SDK for the Smartbills API. It gives you a fully
typed, async-first interface to expense management, invoicing, payments, vendor management, banking,
and more — with built-in retry logic, pagination helpers, and Pydantic v2 models throughout.

## Links

- [![Documentation](https://img.shields.io/badge/documentation-docs.smartbills.io-green.svg)](https://docs.smartbills.io/)
- [![Stack Overflow](https://img.shields.io/badge/stack%20overflow-smartbills-green.svg)](http://stackoverflow.com/questions/tagged/smartbills)
- [![Twitter Follow](https://img.shields.io/twitter/follow/SmartbillsApp?label=SmartbillsApp&style=social)](https://twitter.com/intent/follow?screen_name=SmartbillsApp)

## Contents

- [Requirements](#requirements)
- [Installation](#installation)
- [Quick Start](#quick-start)
- [Configuration](#configuration)
- [Per-request Options](#per-request-options)
- [Available Services](#available-services)
- [Complete API coverage](#complete-api-coverage-clientapi)
- [Error Handling](#error-handling)
- [Pagination](#pagination)
- [Contributing](https://github.com/smartbills/python-sdk/blob/dev/CONTRIBUTING.md)

## Requirements

- Python 3.10+
- `httpx >= 0.27`
- `pydantic >= 2.0`

## Installation

```bash
pip install smartbills
```

```bash
uv add smartbills
```

## Quick Start

```python
import asyncio
from smartbills import SmartbillsClient, SmartbillsClientOptions


async def main():
    client = SmartbillsClient(
        SmartbillsClientOptions(
            access_token="your-api-token",
            business_id=123,
        )
    )

    async with client:
        invoices = await client.invoices.list()
        for invoice in invoices.data:
            print(f"Invoice #{invoice.invoice_number}: {invoice.total} {invoice.currency}")


asyncio.run(main())
```

## Configuration

`SmartbillsClientOptions` accepts the following parameters:

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `access_token` | `str \| None` | `None` | API access token |
| `business_id` | `int \| None` | `None` | Active business / tenant ID |
| `base_url` | `str` | `https://api.smartbills.io` | API base URL |
| `locale` | `str` | `en-CA` | Response locale (`en-CA`, `en-US`, `fr-CA`) |
| `timeout` | `float` | `30.0` | Request timeout in seconds |
| `max_retries` | `int` | `3` | Max retries on 5xx / 429 responses |
| `retry_delay` | `float` | `1.0` | Base retry delay in seconds (exponential backoff) |

You can also update the token or business context at runtime without creating a new client:

```python
client.set_access_token("new-token")
client.set_business_id(456)
client.set_locale("fr-CA")
```

## Per-request Options

Override token, business, or locale for a single call using `RequestOptions`:

```python
from smartbills import RequestOptions

invoices = await client.invoices.list(
    options=RequestOptions(access_token="other-token", business_id=999)
)
```

## Available Services

| Service | Description |
|---------|-------------|
| `client.expenses` | Expense management |
| `client.expense_reports` | Expense report lifecycle |
| `client.bills` | Accounts payable bills |
| `client.invoices` | Accounts receivable invoices |
| `client.connect` | Connect onboarding |
| `client.products` | Product catalog |
| `client.taxes` | Tax configuration |
| `client.customers` | Customer management |
| `client.employees` | Employee management |
| `client.business_users` | Team member management |
| `client.vendors` | Vendor directory |
| `client.vendor_connections` | Vendor integrations |
| `client.categories` | Expense categories |
| `client.departments` | Department management |
| `client.banks` | Banking connections |
| `client.accounting` | Accounting ledger |
| `client.reporting` | Financial reports |
| `client.workflows` | Workflow automation |
| `client.notifications` | Notification management |
| `client.billing` | Subscription billing |
| `client.approbations` | Approval workflows |
| `client.attachments` | File attachments |
| `client.storage_folders` | Vault folders |
| `client.storage_files` | Vault files: upload, download, rename, move |
| `client.storage_bulk` | Vault bulk move, rename and delete |

## Complete API coverage: `client.api`

The curated services above are ergonomic wrappers over the endpoints used most
often. Everything the API documents — all 999 operations across 151 tags — is
reachable through `client.api`, which is generated straight from the OpenAPI
document and never drifts from it.

```python
async with client:
    # one attribute per OpenAPI tag, one method per operation
    bill = await client.api.business_bills.get_businesses_by_business_id_bills_by_bill_id(123, 7)
    chart = await client.api.business_ledger_accounts.get_businesses_by_business_id_accounting_accounts_chart(123)
```

Method names are `<verb>_<path segments>`, with `by_<name>` standing in for a
path parameter, so they are stable across regenerations. Models for every schema
live in `smartbills.generated.models`.

## Regenerating from the API

`spec/openapi.json` is a committed snapshot of the API's OpenAPI document, and
everything under `smartbills/generated/` is produced from it. To refresh:

```bash
# 1. point at a running API (Aspire serves it on :8001; needs `aws sso login --profile dev-admin`)
python scripts/fetch_spec.py --url http://localhost:8001/swagger/v1/swagger.json

#    ...or generate the document offline from the built assembly, in smartbills-api:
#    dotnet swagger tofile --output openapi.json #        Smartbills.API/Smartbills.API/bin/Debug/net9.0/Smartbills.API.dll v1

# 2. regenerate the generated layer
python scripts/generate.py

# 3. check the whole SDK, curated services included, against the snapshot
python scripts/spec_coverage.py
```

`spec_coverage.py` reports both directions: spec operations with no SDK method,
and SDK methods pointing at endpoints the API does not have. Both lists should
be empty; `tests/test_contract.py` asserts exactly that in CI.

## Testing

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

# contract + transport tests, no server needed
pytest tests -q

# also exercise a live API
SMARTBILLS_API_URL=http://localhost:8001 SMARTBILLS_ACCESS_TOKEN=... SMARTBILLS_BUSINESS_ID=123 pytest tests/integration -q

# opt in to the tests that create and delete real records
SMARTBILLS_ALLOW_WRITES=1 pytest tests/integration -q
```

The integration suite skips cleanly when those variables are unset.

## Usage Examples

### Expenses

```python
# List expenses
expenses = await client.expenses.list()

# Create an expense report
from smartbills.models.expense_reports import ExpenseReportCreateRequest

report = await client.expense_reports.create(
    ExpenseReportCreateRequest(title="Q2 Travel", employee_id=7)
)

# Approve and reimburse
await client.expense_reports.approve(report.id)
await client.expense_reports.reimburse(report.id)
```

### Invoices

```python
# List invoices
invoices = await client.invoices.list()

# Get a specific invoice
invoice = await client.invoices.get_by_id(1326)
```

### Customers & Vendors

```python
# List customers
customers = await client.customers.list()

# Get a vendor
vendor = await client.vendors.get_by_id(42)
```

## Error Handling

All errors inherit from `SmartbillsError`. Import specific types to handle different failure modes:

```python
from smartbills import (
    SmartbillsError,
    AuthenticationError,     # 401 — invalid or expired token
    AuthorizationError,      # 403 — insufficient permissions
    NotFoundError,           # 404 — resource not found
    ValidationError,         # 422 — request validation failed
    RateLimitError,          # 429 — too many requests
    ConflictError,           # 409 — resource conflict
    SmartbillsAPIError,      # other 4xx/5xx errors
)

try:
    invoice = await client.invoices.get_by_id(9999)
except NotFoundError:
    print("Invoice not found")
except RateLimitError:
    print("Rate limited — retry after a moment")
except SmartbillsError as e:
    print(f"API error: {e}")
```

## Pagination

List responses include a `pagination` object. Use `page` and `limit` in your request to paginate:

```python
from smartbills.models.invoices import InvoiceListRequest

page = 1
while True:
    result = await client.invoices.list(InvoiceListRequest(page=page, limit=50))
    for invoice in result.data:
        process(invoice)

    if page >= result.pagination.page_count:
        break
    page += 1
```

## Contributors

Thanks to everyone who contributed to the Smartbills Python SDK!

<a href="https://github.com/smartbills/python-sdk/graphs/contributors">
  <img src="https://contributors-img.web.app/image?repo=smartbills/python-sdk" />
</a>
