Metadata-Version: 2.4
Name: nxus-qbd
Version: 0.7.1
Summary: Official Python SDK for the Nxus QuickBooks Desktop API
Project-URL: Homepage, https://nxus.app
Project-URL: Repository, https://github.com/nxus-app/nxus-qbd-python
Project-URL: Documentation, https://nx-us.net/docs/
License-Expression: MIT
License-File: LICENSE
Keywords: accounting,api,nxus,qbd,quickbooks,quickbooks-desktop,sdk
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Requires-Python: >=3.12
Requires-Dist: httpx>=0.25.0
Requires-Dist: pydantic>=2.0
Provides-Extra: codegen
Requires-Dist: datamodel-code-generator[http]>=0.26.0; extra == 'codegen'
Provides-Extra: dev
Requires-Dist: pytest; extra == 'dev'
Requires-Dist: pytest-asyncio; extra == 'dev'
Requires-Dist: python-dotenv>=1.2.1; extra == 'dev'
Requires-Dist: respx; extra == 'dev'
Description-Content-Type: text/markdown

# nxus-qbd

Official Python SDK for the [Nxus](https://nx-us.net/docs) QuickBooks Desktop API.

Requires Python 3.12 or newer.

## Installation

```bash
pip install nxus-qbd
```

## Quick Start

```python
from nxus_qbd import NxusClient, NxusEnvironment

client = NxusClient(
    api_key="sk_live_…",
    environment=NxusEnvironment.PRODUCTION,  # default
)

# List vendors (with connection scoping)
vendors = client.vendors.list(connection_id="conn_abc123", limit=50)

# Retrieve a single customer
customer = client.customers.retrieve("cust_123", connection_id="conn_abc123")

# Create an invoice
invoice = client.invoices.create(
    customer_ref_list_id="cust_123",
    connection_id="conn_abc123",
)
```

## Async Support

```python
from nxus_qbd import AsyncNxusClient

async with AsyncNxusClient(api_key="sk_live_…") as client:
    vendors = await client.vendors.list(connection_id="conn_abc123")
```

The SDK defaults to a `100s` client timeout so normal callers can receive the
API's structured timeout responses for heavier QuickBooks operations. Advanced
callers can still override this globally or per request:

```python
client = NxusClient(api_key="sk_live_…", timeout=120)
vendors = client.vendors.list(connection_id="conn_abc123", timeout=30)
customer = client.customers.retrieve("cust_123", connection_id="conn_abc123", timeout=30)
```

For cursor-paginated list endpoints, the per-request `timeout` still controls
the local client timeout and is also sent to the API as the
`X-Nxus-Timeout-Seconds` request header so cursor continuation reuses the same
backend timeout hint automatically. CRUD-style requests keep treating
`timeout` as a client-side override only.

## Environments

Production is the default and uses `https://api.nx-us.net/`.

```python
from nxus_qbd import NxusClient

# Production (default)
client = NxusClient(api_key="sk_live_…")

# Local development convenience
client = NxusClient(api_key="sk_test_…", environment="development")

# Explicit override still wins
client = NxusClient(api_key="sk_test_…", base_url="https://staging.example.com/")
```

If you point `base_url` at `localhost` explicitly, the SDK also relaxes TLS
verification by default so local dev stays frictionless.

## Connection Scoping

Every request requires a `connection_id` to identify which QuickBooks Desktop company file to target:

```python
# Per-request
client.vendors.list(connection_id="conn_abc123")

# Global default via headers
client = NxusClient(
    api_key="sk_live_…",
    headers={"X-Connection-Id": "conn_abc123"},
)
```

## Pagination

All list methods return a paginated response that supports both manual page navigation and auto-iteration:

```python
# Auto-paginate through all records (sync)
for vendor in client.vendors.list(limit=100):
    print(vendor.name)

# Auto-paginate through all records (async)
async for vendor in await async_client.vendors.list(limit=100):
    print(vendor.name)
```

Manual continuation also replays the original filters, connection scoping, and
list timeout hint:

```python
page = client.vendors.list(connection_id="conn_abc123", limit=100, timeout=45)
page_2 = page.get_next_page()
```

> [!IMPORTANT]
> **Processing Constraints**: Each paginated request must either complete or be cancelled before the subsequent request for that connection can be processed by the backend.
>
> - **Async API (Primary)**: The Async API is the recommended way to handle these requests as it allows for better lifecycle management.
> - **Sync Wrappers**: While sync wrappers are provided for convenience, you may need to increase your client-side timeouts to ensure large paginated sets complete successfully.

## Examples

Runnable examples live in [`examples/`](examples/) and cover both sync and async usage:

| Example | Description |
|---|---|
| [`basic_crud.py`](examples/basic_crud.py) | Create, retrieve, update, list, and delete a vendor |
| [`auth_setup.py`](examples/auth_setup.py) | Create a connection, generate a hosted QWC auth flow URL, and check auth status |
| [`auto_pagination.py`](examples/auto_pagination.py) | Sync and async auto-iteration across pages |
| [`error_handling.py`](examples/error_handling.py) | Error categorization, retry with backoff, validation errors |
| [`connection_scoped.py`](examples/connection_scoped.py) | Multi-company isolation with `connection_id` |
| [`timeout_tuning.py`](examples/timeout_tuning.py) | Client timeout defaults, paginated list timeout hints, and CRUD timeout overrides |
| [`reports.py`](examples/reports.py) | Aging, general detail, and general summary reports |
| [`async_basic_crud.py`](examples/async_basic_crud.py) | Async CRUD lifecycle |
| [`async_error_handling.py`](examples/async_error_handling.py) | Async error handling and retry patterns |
| [`async_connection_scoped.py`](examples/async_connection_scoped.py) | Async multi-company data isolation |
| [`async_reports.py`](examples/async_reports.py) | Async report retrieval |

All examples auto-load a `.env` file from the project root. Copy `.env.example` to `.env` and fill in your values:

```bash
cp .env.example .env
```

```ini
NXUS_API_KEY=sk_test_your_key_here
NXUS_ENVIRONMENT=development
NXUS_CONNECTION_ID=your_connection_id_here
```

Optional overrides:

```ini
NXUS_BASE_URL=https://localhost:7242/
NXUS_DEV_MODE=true
```

Then run any example:

```bash
python examples/basic_crud.py
```

## Tests

The test suite includes focused unit coverage plus optional live integration tests:

| File | What it covers |
|---|---|
| [`tests/conftest.py`](tests/conftest.py) | Shared fixtures — loads `.env`, provides `client` and `async_client` fixtures, auto-skips when `NXUS_API_KEY` is not set |
| [`tests/unit/test_config.py`](tests/unit/test_config.py) | Environment/base URL resolution behavior |
| [`tests/unit/test_vendor_pydantic_wiring.py`](tests/unit/test_vendor_pydantic_wiring.py) | Typed response parsing and snake_case request serialization |
| [`tests/integration/test_smoke.py`](tests/integration/test_smoke.py) | Sync tests — list vendors/accounts, pagination with limits, cursor metadata, auto-pagination iteration, 404 error handling |
| [`tests/integration/test_async_smoke.py`](tests/integration/test_async_smoke.py) | Async tests — list vendors, async auto-pagination |

Run the full suite:

```bash
# Install dev dependencies
pip install nxus-qbd[dev]
# — or with uv —
uv sync

# Run tests (skipped automatically if NXUS_API_KEY is not set)
pytest

# Run only unit tests
pytest tests/unit -q

# Run with verbose output
pytest -v

# Run only async tests
pytest tests/integration/test_async_smoke.py -v
```

## Resources

All QuickBooks Desktop resources are available as properties:

| Category | Resources |
|---|---|
| **Transactions** | `invoices`, `bills`, `checks`, `deposits`, `estimates`, `credit_memos`, `purchase_orders`, `sales_receipts`, `journal_entries`, `receive_payments`, `vendor_credits`, `credit_card_charges`, `credit_card_bills`, `credit_card_credits`, `charges`, `build_assemblies`, `ar_refund_credit_cards`, `sales_tax_payment_checks`, `item_receipts`, `check_bills`, `time_trackings`, `transactions` |
| **Lists** | `accounts`, `customers`, `vendors`, `employees`, `other_names`, `currencies`, `terms`, `date_driven_terms`, `payment_methods`, `ship_methods`, `sales_tax_codes`, `price_levels`, `qbd_classes`, `customer_types`, `vendor_types`, `billing_rates`, `inventory_sites`, `bar_codes`, `account_tax_line_infos`, `bill_to_pay`, `unit_of_measure_sets`, `special_items` |
| **Items** | `items`, `inventory_items`, `item_discounts`, `item_fixed_assets`, `item_groups`, `item_inventory_assemblies`, `item_non_inventory`, `item_other_charges`, `item_payments`, `item_sales_tax`, `item_sales_tax_groups`, `service_items`, `item_subtotals` |
| **Payroll** | `payroll_item_non_wages`, `payroll_item_wages`, `workers_comp_codes` |
| **Reports** | `reports.retrieve_general_detail()`, `reports.retrieve_aging()`, etc. |
| **Core** | `auth_sessions`, `connections` |


## Custom fields and data extensions

QuickBooks Desktop uses the same underlying mechanism for both UI-visible custom fields and application-only integration data:

| QuickBooks concept | SDK/API name | Purpose |
|---|---|---|
| Data extension definition | `DataExtDef` / custom field definition | Describes a field's owner, name, data type, and supported object types. |
| Data extension value | `DataExt` / custom field value | Stores the field's value on one specific QuickBooks list object, transaction, or transaction line. |

A definition must exist before a value can be written. Definitions are identified by `ownerId + name`; values add the specific QuickBooks target to that composite identity. QuickBooks may omit `DataExtID` for private definitions, so SDK consumers must allow `DataExtDef.id` to be `null`.

The `ownerId` determines how a definition is used:

- **Public custom field:** use `"0"`. The field is visible in the QuickBooks UI and is limited to `STR255TYPE`. QuickBooks does not accept `AssignToObject` in the public definition request, so the backend omits it from the generated QBXML.
- **Private data extension:** use an application-owned GUID such as `"{C3AA84E0-D242-47AB-A12B-3EDA3A2590A2}"`. The field is available only to applications that know that GUID and can be assigned to supported list or transaction object types. The GUID does not require separate registration with QuickBooks.

The normal workflow is:

1. Create the `DataExtDef`.
2. Create a `DataExt` value using the same `ownerId` and field name, plus a target such as a Customer `ListID`, an Invoice `TxnID`, or a transaction-line `TxnLineID`.
3. Update or delete the value using that same composite identity.
4. Delete the definition only after its values are no longer needed.

Public fields are typically used for information users should see or edit in QuickBooks. Private extensions are commonly used for external-system identifiers, synchronization or verification markers, workflow state, migration metadata, and other integration data that should not appear in the QuickBooks UI.


## License

MIT
