Metadata-Version: 2.5
Name: ozon-delivery-business
Version: 0.1.0
Summary: Unofficial typed async client for the Ozon Delivery for Business API
Project-URL: Documentation, https://github.com/databear/ozon-delivery-business-python#readme
Project-URL: Issues, https://github.com/databear/ozon-delivery-business-python/issues
Project-URL: Repository, https://github.com/databear/ozon-delivery-business-python
Author: Databear contributors
License-Expression: MIT
License-File: LICENSE
Keywords: api,asyncio,delivery,logistics,ozon
Classifier: Development Status :: 3 - Alpha
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<1,>=0.27
Provides-Extra: dev
Requires-Dist: build>=1.2; extra == 'dev'
Requires-Dist: detect-secrets>=1.5; extra == 'dev'
Requires-Dist: mypy>=1.11; extra == 'dev'
Requires-Dist: pip-audit>=2.7; extra == 'dev'
Requires-Dist: pytest-asyncio>=0.24; extra == 'dev'
Requires-Dist: pytest>=8; extra == 'dev'
Requires-Dist: ruff>=0.9; extra == 'dev'
Requires-Dist: twine>=6; extra == 'dev'
Description-Content-Type: text/markdown

# ozon-delivery-business-python

Unofficial async Python client for the Ozon Delivery for Business API.

This is an unofficial community project. It is not affiliated with, endorsed by, or maintained
by Ozon. “Ozon” is used only to identify the service this client communicates with.

The package concentrates on the parts of this API that are easy to implement incorrectly:
OAuth client credentials, absolute token expiry, Ozon testcookie redirects, safe pagination, and
typed request/response models. It was developed from a real integration and verified against the
live Ozon Delivery for Business API in August 2026. CI uses synthetic HTTP transports and never
contacts Ozon.

Supported Python versions: 3.11, 3.12, and 3.13.

## Installation

```console
python -m pip install ozon-delivery-business
```

## Quickstart

```python
import os

from ozon_delivery_business import (
    Coordinates,
    LocationRequest,
    LocationShipmentMethod,
    OzonDeliveryClient,
)


async def estimate_location() -> None:
    async with OzonDeliveryClient(
        client_id=os.environ["OZON_DELIVERY_CLIENT_ID"],
        client_secret=os.environ["OZON_DELIVERY_CLIENT_SECRET"],
    ) as client:
        response = await client.delivery.location(
            LocationRequest(
                coordinates=Coordinates(latitude=55.75, longitude=37.61),
                shipment_methods=(
                    LocationShipmentMethod(
                        shipment_method_id=123,
                        cutoff_at="2030-01-02T12:00:00Z",
                    ),
                ),
            )
        )
        print(response.results)
```

`shipment_method_id`, delivery-point IDs, buyer data, parcel data, and `cutoff_at` are business
inputs. The library does not discover or invent them.

## API surface

The read/preflight groups are:

```python
await client.delivery.check_client("+7XXXXXXXXXX")
await client.delivery.location(request)

await client.delivery_points.list(cursor=None, limit=100)
async for page in client.delivery_points.iter_pages():
    ...
await client.delivery_points.info([point_id])
await client.delivery_points.check_availability(
    delivery_point_ids=[point_id],
    shipment_method_id=shipment_method_id,
    postings=postings,
)

await client.orders.checkout(request)
```

The checkout model serializes the documented structures
`recipient.phone_number` and `delivery.delivery_point.delivery_point_id`. The buyer phone is held
only by the request object supplied by the caller; it is not cached or logged by the client.

## Explicit mutation boundary

`client.orders.create(...)` creates a real order at Ozon. It is never called implicitly. The method
requires a caller-supplied UUID `idempotency_key`; the client does not generate a new key during a
retry. The only automatic replay is the same-origin testcookie redirect required by Ozon, and that
replay preserves both the request body and idempotency key.

```python
response = await client.orders.create(request, idempotency_key=stable_uuid)
```

Treat this call as a provider mutation and place an explicit business authorization boundary in
front of it. This release does not expose posting or return mutations.

## Errors

All package errors inherit from `OzonDeliveryError`:

- `OzonAuthenticationError` — authentication or authorization failure;
- `OzonValidationError` — HTTP 400 request validation failure;
- `OzonRateLimitError` — HTTP 429;
- `OzonAPIError` — other provider error response;
- `OzonTransportError` — timeout, network, or malformed response;
- `OzonRedirectError` — unsafe or excessive redirect;
- `OzonPaginationError` — pagination safety boundary.

API errors expose sanitized `status_code`, `provider_code`, `provider_message`, `provider_path`,
`trace_id`, and `details`. Credential and buyer fields are removed; callers should still avoid
logging complete request objects.

## Protocol behavior

The [official Ozon Delivery API documentation](https://docs.ozon.ru/api/ozon-delivery/) documents:

- OAuth `client_credentials` at `https://xapi.ozon.ru/oauth/token`;
- `scope` as a JSON array;
- API requests with a Bearer token;
- 302/307 testcookie responses whose cookies must be retained while the original request body is
  repeated;
- `Idempotency-Key` as a UUID for `POST /v1/order/create`.

Live behavior observed in August 2026 additionally showed that `expires_in` is an absolute Unix
timestamp and that the initial delivery-point page needs `pagination.cursor` serialized as JSON
`null` rather than an empty string. These observations are tested by the client but are not claims
of permanent API stability.

Redirects are not general retries. Only documented 302/307 testcookie responses are followed, only
on the configured HTTPS API origin, with a bounded loop guard. Timeouts and other transport errors
are surfaced to the caller.

## HTTP client ownership

The default internal `httpx.AsyncClient` is closed by `async with` or `aclose()`. An injected
`http_client` remains owned by the caller and is not closed by this package. Injection is useful for
custom transports and tests; do not disable TLS verification.

## Scope and stability

Version 0.1.0 supports the endpoints represented by the public methods above plus explicit
`order/create`. It is not a complete Ozon Delivery API SDK. Ozon may evolve this new API, so pin the
package, test upgrades, and consult the official documentation.

See [SECURITY.md](SECURITY.md) for private vulnerability reporting and
[CONTRIBUTING.md](CONTRIBUTING.md) for development instructions.
