Metadata-Version: 2.4
Name: qodev-qonto-api
Version: 0.1.0
Summary: Async Python client for Qonto Business API
Project-URL: Homepage, https://github.com/qodevai/qonto-api
Project-URL: Repository, https://github.com/qodevai/qonto-api
Project-URL: Issues, https://github.com/qodevai/qonto-api/issues
Author-email: Jan Scheffler <jan.scheffler@qodev.ai>
License: MIT
License-File: LICENSE
Keywords: api,async,banking,client,fintech,invoicing,qonto,qonto.com,sepa
Classifier: Development Status :: 4 - Beta
Classifier: Framework :: AsyncIO
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Typing :: Typed
Requires-Python: >=3.11
Requires-Dist: httpx>=0.27.0
Requires-Dist: pydantic>=2.0.0
Provides-Extra: dev
Requires-Dist: pyright>=1.1.0; extra == 'dev'
Requires-Dist: pytest-asyncio>=0.24.0; extra == 'dev'
Requires-Dist: pytest-cov>=4.1.0; extra == 'dev'
Requires-Dist: pytest-mock>=3.12.0; extra == 'dev'
Requires-Dist: pytest>=8.0.0; extra == 'dev'
Requires-Dist: python-dotenv>=1.0.0; extra == 'dev'
Requires-Dist: ruff>=0.8.0; extra == 'dev'
Description-Content-Type: text/markdown

# qodev-qonto-api

[![CI](https://github.com/qodevai/qonto-api/actions/workflows/ci.yml/badge.svg)](https://github.com/qodevai/qonto-api/actions/workflows/ci.yml)
[![PyPI](https://img.shields.io/pypi/v/qodev-qonto-api)](https://pypi.org/project/qodev-qonto-api/)
[![Python](https://img.shields.io/pypi/pyversions/qodev-qonto-api)](https://pypi.org/project/qodev-qonto-api/)

Async Python client for Qonto Business API with full type safety.

## Features

- **Async-first design** built on httpx
- **Full Pydantic v2 models** with `extra="allow"` for forward compatibility
- **Context manager support** for clean resource management
- **Sandbox support** via `QONTO_BASE_URL` environment variable or `base_url=` kwarg
- **~40 API methods** across 15+ resource types (organizations, memberships, labels, bank accounts, transactions, attachments, clients, invoices, quotes, credit notes, supplier invoices, products, SEPA transfers, bulk transfers, internal transfers, beneficiaries)
- **Multipart attachment upload** helper for receipts and documents
- **Typed exceptions** — `AuthenticationError`, `NotFoundError`, `ValidationError`, `RateLimitError`, `APIError`
- **Generic `PaginatedResponse[T]`** with Qonto meta-envelope flattening
- **Rate-limit header tracking** via `client.rate_limit_status`
- **py.typed marker** for downstream type checking

## Installation

```bash
pip install qodev-qonto-api
```

Or with uv:
```bash
uv add qodev-qonto-api
```

## Quick Start

```python
import asyncio
from qodev_qonto_api import QontoClient


async def main():
    async with QontoClient() as client:  # reads QONTO_LOGIN + QONTO_SECRET_KEY
        org = await client.get_organization()
        print(org.legal_name)

        accounts = await client.list_bank_accounts()
        for account in accounts:
            txns = await client.list_transactions(
                bank_account_id=account.id, per_page=10,
            )
            print(f"{account.iban}: {len(txns.items)} recent")


asyncio.run(main())
```

## Configuration

### Credentials

Set the two required environment variables:

```bash
export QONTO_LOGIN="your-org-login"
export QONTO_SECRET_KEY="your-secret-key"
```

Or pass directly:

```python
async with QontoClient(login="your-org-login", secret_key="your-secret-key") as client:
    ...
```

> **Important**: The Qonto `Authorization` header is the literal string `{login}:{secret}` with a real colon. This is **not** HTTP Basic Authentication — there is no Base64 encoding.

### Sandbox

Switch to the staging sandbox via env var:

```bash
export QONTO_BASE_URL="https://thirdparty-sandbox.staging.qonto.co/v2"
```

Or via kwarg:

```python
async with QontoClient(base_url="https://thirdparty-sandbox.staging.qonto.co/v2") as client:
    ...
```

### Timeout

Customize request timeout (default 30 seconds):

```python
async with QontoClient(timeout=60.0) as client:
    ...
```

## Rate Limiting

Qonto enforces these limits (per IP):
- **1,000 requests / 10 seconds**
- **10,000 requests / 10 minutes**

Rate-limit headers are captured defensively (Qonto does not officially document header names — the client captures any `ratelimit-*` or `x-ratelimit-*` header) and exposed as a raw dict:

```python
async with QontoClient() as client:
    await client.list_bank_accounts()
    print(client.rate_limit_status)  # {"x-ratelimit-remaining": "998", ...}
```

## API Methods

### Organization

```python
org = await client.get_organization()
```

### Memberships & Labels

```python
members = await client.list_memberships()
labels = await client.list_labels()
```

### Bank Accounts

```python
accounts = await client.list_bank_accounts()
```

### Transactions

`GET /transactions` requires either `bank_account_id` or `iban`:

```python
txns = await client.list_transactions(bank_account_id="acc_123", per_page=50)
txn = await client.get_transaction("txn_456")
```

### Attachments

```python
attachment = await client.upload_attachment(
    file_path="invoice.pdf",
    idempotency_key="unique-key-123",
)
await client.attach_to_transaction(transaction_id="txn_456", attachment_ids=[attachment.id])
```

### Clients

```python
clients = await client.list_clients()
client_obj = await client.create_client(type="company", name="Acme Corp", email="billing@acme.com")
```

### Client Invoices

```python
invoices = await client.list_client_invoices()
invoice = await client.create_client_invoice(client_id="cl_1", items=[...])
```

### Quotes & Credit Notes

```python
quotes = await client.list_quotes()
credit_notes = await client.list_credit_notes()
```

### Supplier Invoices

```python
supplier_invoices = await client.list_supplier_invoices()
si = await client.get_supplier_invoice("si_123")
```

### Products

```python
products = await client.list_products()
```

### SEPA Transfers

```python
# VoP (Verification of Payee) must be called manually — pass the resulting
# vop_proof_token via extra_fields. See roadmap.
transfer = await client.create_sepa_transfer(
    debit_account_id="acc_1",
    beneficiary_id="ben_1",
    amount="100.00",
    currency="EUR",
    reference="Invoice INV-001",
    vop_proof_token="token-from-verify-payee",
)
```

### Bulk Transfers & Internal Transfers

```python
bulk = await client.create_bulk_transfer(debit_account_id="acc_1", transfers=[...])
internal = await client.create_internal_transfer(
    debit_account_id="acc_1",
    credit_account_id="acc_2",
    amount="50.00",
)
```

### Beneficiaries

```python
beneficiaries = await client.list_beneficiaries()
```

## Error Handling

```python
from qodev_qonto_api import (
    QontoClient,
    QontoError,
    AuthenticationError,
    NotFoundError,
    ValidationError,
    RateLimitError,
    APIError,
)

try:
    async with QontoClient() as client:
        await client.get_transaction("does-not-exist")
except AuthenticationError:
    print("Invalid login / secret key")
except NotFoundError:
    print("Resource not found")
except ValidationError as e:
    for err in e.errors:
        print(f"{err.get('source', {}).get('pointer')}: {err.get('detail')}")
except RateLimitError as e:
    print(f"Rate limited. Retry after {e.retry_after} seconds")
except APIError as e:
    print(f"API error: {e} (status: {e.status_code})")
except QontoError as e:
    print(f"Qonto client error: {e}")
```

## Development

```bash
make install          # Install dependencies
make check            # Lint, format, typecheck, typos
make test             # Run tests with coverage
```

## Roadmap (Out of Scope for v0.1.0)

- **OAuth2 / Qonto Connect** — partner app integrations
- **PSD2 QSeal certificates** — regulatory signing
- **Cards API** — virtual/physical card management
- **Webhooks** — event subscriptions
- **Onboarding API** — account creation flows
- **VoP integration** — Verification of Payee for SEPA transfers (currently manual via `extra_fields`)
- **Payment links**
- **Embed iframe** features

## License

MIT
