Metadata-Version: 2.4
Name: bodo-api
Version: 1.0.0
Summary: Python SDK for the Bodo REST API (generated from the frozen OpenAPI spec, thin hand-written client).
License-Expression: MIT
License-File: LICENSE
Classifier: Programming Language :: Python :: 3
Classifier: Typing :: Typed
Requires-Python: >=3.11
Requires-Dist: attrs>=24.2.0
Requires-Dist: httpx<1,>=0.28.1
Description-Content-Type: text/markdown

# bodo-api

The Python SDK for the Bodo public API (version `2026-11-01`). It needs Python 3.11 or newer and
uses `httpx`. The models are generated from the published OpenAPI document (`attrs` classes,
fully typed, `py.typed`). The client is synchronous.

## Install

The package is not on PyPI yet. Download `bodo_api-<version>-py3-none-any.whl` and its `.sha256`
from the developer portal, check the wheel and install it:

```sh
sha256sum --check bodo_api-1.0.0-py3-none-any.whl.sha256
pip install ./bodo_api-1.0.0-py3-none-any.whl
```

## Quickstart

```python
import os

from bodo_api import Bodo, BodoApiError

with Bodo(api_key=os.environ["BODO_API_KEY"], preview=True) as bodo:
    me = bodo.me()
    contact = bodo.contacts.create(first_name="Erika", last_name="Mustermann")
    try:
        bodo.contacts.get("con_0000000000000000")
    except BodoApiError as err:
        print(err.code, err.status, err.request_id)
```

`Bodo(...)` takes `api_key` or `oauth` (exactly one), `base_url` (default
`https://api.bodo-app.com/v1`), `version`, `preview`, `locale`, `max_retries`,
`max_retry_delay`, `timeout`, `http_client` and `on_deprecation`. POST and DELETE requests get an
`Idempotency-Key` that stays the same across retries after 429/503.

## Resources

`contacts`, `companies`, `tasks` (with `complete`), `documents` (`content_url`, `download`),
`uploads` (`upload_document`), `batch`, `operations` (`get`, `wait`), `webhook_endpoints` and
`events`.

## Errors

Every problem response raises `BodoApiError` with `status`, `code`, `title`, `detail`,
`request_id`, `errors` and `retryable`. Branch on `code`, never on the text.

## OAuth

`bodo_api.oauth` has PKCE (`create_pkce_pair`, `build_authorize_url`, `exchange_code`) and
`OAuthSession`. Refresh tokens rotate: concurrent threads share one refresh behind a lock, and
`on_tokens` hands you the new pair to store. A spent or revoked grant raises `BodoOAuthError`
with `error == "invalid_grant"`.

## Webhooks

```python
from bodo_api.webhooks import Webhook

event = Webhook(secret).verify(raw_body, headers)
```

## Examples

`examples/` holds the code the developer portal shows. CI compiles them and checks them with
`mypy --strict`.

## Development

```sh
scripts/generate.sh      # bodo_api/_generated from the frozen spec (openapi-python-client 0.29.1)
uv run pytest tests/test_client.py
uv run mypy
scripts/build.sh         # dist/bodo_api-<version>-py3-none-any.whl + .sha256 (reproducible)
BODO_SDK_LIVE=1 uv run pytest tests/test_live_smoke.py   # against this worktree's deployment
```

The spec hash in `bodo_api/_spec_hash.py` is written by `packages/sdk-typescript`
(`bun run generate`). The workflow `.github/workflows/sdk-publish.yml` builds the wheel, verifies
the checksum and only runs `twine check`. Nothing is uploaded to PyPI.

## Licence

MIT.
