Metadata-Version: 2.4
Name: zippendo
Version: 1.2.1
Summary: Zippendo Public API
Home-page: 
Author: Zippendo Support
Author-email: Zippendo Support <support@zippendo.com>
License: MIT
Project-URL: Repository, https://github.com/zippendo/zippendo-python
Keywords: OpenAPI,OpenAPI-Generator,Zippendo Public API
Requires-Python: >=3.9
Description-Content-Type: text/markdown
License-File: LICENSE.md
Requires-Dist: urllib3<3.0.0,>=2.6.3
Requires-Dist: python-dateutil>=2.8.2
Requires-Dist: pydantic>=2.11
Requires-Dist: typing-extensions>=4.7.1
Dynamic: author
Dynamic: license-file

# Zippendo Python SDK

Official Python client for the [Zippendo](https://zippendo.com) shipping & logistics API. Models are
pydantic v2, so requests and responses are both type-hinted and validated at runtime. Requires
Python 3.8+.

## Install

```sh
pip install zippendo
```

## Authentication

Create an API token in your Zippendo dashboard (**Settings → API tokens**) — a Bearer token prefixed
with `zipp_`. Pass it as `access_token`:

```python
import os
import zippendo

config = zippendo.Configuration(access_token=os.environ["ZIPPENDO_API_TOKEN"])
```

The base URL defaults to `https://api.zippendo.com`.

## Resources & clients

The API is split into resource clients — `ShipmentsApi`, `OrdersApi`, `CarriersApi`, `AddressesApi`,
`RulesApi`, `WebhooksApi`, `TokensApi`, and more. Create an `ApiClient` from your config, then the
resource clients you need:

```python
with zippendo.ApiClient(config) as client:
    shipments = zippendo.ShipmentsApi(client)
    orders = zippendo.OrdersApi(client)
```

## The `org_id` parameter

Every call takes an `org_id` (your organization ID, found in the dashboard). It is explicit on each
call by design: one API token can be granted access to multiple organizations, and `org_id` selects
which one the request acts on.

## Brands

A brand is a sub-account inside your organization — one company running several consumer-facing labels
(say Acme and Globex) out of one Zippendo org, with each brand's orders and shipments kept separate.
Scope a request to a single brand with the `X-Zippendo-Brand` header, which takes the brand's ID or slug.

The header applies uniformly to every operation, so it is not a method parameter — set it once on the
`ApiClient` and every call made through that client inherits it:

```python
with zippendo.ApiClient(config) as client:
    client.set_default_header("X-Zippendo-Brand", "acme")  # brand ID or slug

    shipments = zippendo.ShipmentsApi(client)
    result = shipments.list_shipments("org_8f3kd92ld0", limit=50)  # Acme's shipments only
```

Omit the header and you get the organization-wide view across every brand. To work with both views in
one program, build a second `ApiClient` from the same `Configuration` and leave its default header unset.

An API token can instead be bound to a brand at creation time (`CreateApiTokenRequest(brand_id=...)`).
Such a token is permanently confined to that brand, so the header is unnecessary — and sending
`X-Zippendo-Brand` naming a *different* brand is refused with `BRAND_ACCESS_DENIED` (403); the binding is
never widened. A header naming a brand that is not in the organization raises `BRAND_NOT_FOUND` (404).

List operations also take a `brand_scope` query parameter (`"own"` / `"shared"` / `"both"`) to narrow
further within whichever brand context already applies: `"own"` returns only that brand's rows and
needs a brand context (otherwise `400`); `"shared"` returns only the unassigned rows (equivalent to
`brand_id="none"`). Set `X-Zippendo-Brand-Scope` as a default header the same way to cover every call:

```python
client.set_default_header("X-Zippendo-Brand-Scope", "own")
```

An explicit `brand_scope` argument passed to a call still wins over the header.

### Managing brands

Brands are managed with `BrandsApi`. Use an organization-wide client for this — you are
administering brands, not acting inside one:

```python
with zippendo.ApiClient(config) as client:
    brands = zippendo.BrandsApi(client)

    created = brands.create_org_brand(
        "org_8f3kd92ld0",
        zippendo.CreateOrgBrandRequest(name="Acme", company_name="Acme ApS"),
    )

    page = brands.list_org_brands("org_8f3kd92ld0")
    brands.update_org_brand(
        "org_8f3kd92ld0",
        created.id,
        zippendo.UpdateOrgBrandRequest(vat_number="DK12345678"),
    )
    brands.archive_org_brand("org_8f3kd92ld0", created.id)
```

Retire a brand with `archive_org_brand` — archived brands keep their slug and can be restored with
`unarchive_org_brand`. Permanent deletion is dashboard-only: it is refused while any order, shipment,
member or token still references the brand. Brands require a plan that includes them; creating one
past your plan's limit returns `403`.

## Listing & pagination

List endpoints accept `page` (1-based) and `limit`, and return a page with `data` plus `total`,
`page`, `limit`, and `total_pages`:

```python
with zippendo.ApiClient(config) as client:
    shipments = zippendo.ShipmentsApi(client)
    result = shipments.list_shipments("org_8f3kd92ld0", page=1, limit=50)
    print(result.data)                       # list[Shipment]
    print(result.total, result.total_pages)  # pagination metadata
```

## Creating resources

```python
with zippendo.ApiClient(config) as client:
    orders = zippendo.OrdersApi(client)
    order = orders.create_order(
        "org_8f3kd92ld0",
        zippendo.CreateOrderRequest(
            order_number="1001",
            order_channel_id="chan_7d2k1",
            order_lines=[zippendo.CreateOrderRequestOrderLinesInner(name="T-shirt", quantity=2)],
        ),
    )
    print(order.id)
```

See [`./docs`](./docs) for the full request/response shape of every operation.

## Error handling

Non-2xx responses raise `ApiException`. The body is Zippendo's canonical `{ code, error, message }` —
branch on the machine-readable `code`:

```python
from zippendo.rest import ApiException

try:
    shipments.get_shipment("org_8f3kd92ld0", "shp_missing")
except ApiException as e:
    print(e.status, e.body)   # e.body contains the JSON, e.g. {"code": "SHIPMENT_NOT_FOUND", ...}
```

## Configuration

Point the client at a different environment by overriding `host`:

```python
config = zippendo.Configuration(
    access_token=os.environ["ZIPPENDO_API_TOKEN"],
    host="https://staging.api.zippendo.com",
)
```

## Reference

Full per-endpoint and per-model documentation is in [`./docs`](./docs). Hosted reference:
<https://www.zippendo.com/docs/api-reference/overview>.

## License

[MIT](./LICENSE.md)
