Metadata-Version: 2.5
Name: veribl
Version: 0.1.0
Summary: Official Veribl SDK: digital product passports (EN 18222), products, battery units and webhooks
Project-URL: Homepage, https://docs.veribl.com
License-Expression: MIT
License-File: LICENSE
Keywords: battery passport,digital product passport,dpp,en 18222,espr,veribl
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Typing :: Typed
Requires-Python: >=3.9
Requires-Dist: httpx<1,>=0.25
Description-Content-Type: text/markdown

# Veribl Python SDK

The official Python library for the [Veribl API](https://docs.veribl.com): digital product passports (EN 18222), products, battery units, manuals, articles, registrations, contacts and webhooks.

Requires Python 3.9 or later. The only dependency is [httpx](https://www.python-httpx.org/).

## Install

```bash
pip install veribl
```

## Quickstart

Make an API key in **Settings › API keys**, then set it as `VERIBL_API_KEY` (or pass it to `Veribl("vdpp_...")`).

```python
from veribl import Veribl

client = Veribl()  # reads VERIBL_API_KEY

# List products (one page)
page = client.products.list(status="live", limit=20)
for product in page.data:
    print(product["id"], product["name"])

# Read a product's passport through the Passport API (EN 18222), by its GS1 Digital Link
passport = client.dpps.retrieve_by_product_id("https://go.veribl.com/01/04012345678901")
print(passport["dppHeader"])

# Create a product
product = client.products.create({"name": "Trail E-Bike 500", "gtin": "04012345678901"})
print(product["id"])
```

Responses are plain dicts and lists, as the API sends them. Use the client as a context manager, or call `client.close()`, to release connections:

```python
with Veribl() as client:
    ...
```

For an endpoint this SDK version doesn't name yet, use `client.request("GET", "/v1/languages")`.

## Pagination

List calls return a `Page` with `.data` and `.next_cursor`. To walk every item across pages:

```python
for product in client.products.list(status="live").auto_paging_iter():
    print(product["name"])
```

## Errors

Every failed call raises a `VeriblError` (or a subclass) with the HTTP status, the error code and the request id to quote to support@veribl.com.

```python
from veribl import VeriblError, VeriblInvalidRequestError

try:
    client.products.retrieve("missing")
except VeriblInvalidRequestError as e:
    print(e.status, e.code, e.request_id, e.message)  # 404 not_found req_... No product with this id.
except VeriblError as e:
    print("Veribl error", e.status, e.request_id)
```

| Class | When |
| --- | --- |
| `VeriblAuthenticationError` | 401: key missing, invalid, revoked or expired |
| `VeriblPermissionError` | 403: the key or plan doesn't allow this |
| `VeriblInvalidRequestError` | 400, 404, 409, 422: something about the request is wrong |
| `VeriblRateLimitError` | 429: too many requests |
| `VeriblAPIError` | 5xx: a failure on Veribl's side |
| `VeriblConnectionError` | no answer: network failure or timeout |
| `VeriblSignatureVerificationError` | a webhook signature doesn't match |

Network errors, 408, 429 and 5xx are retried automatically (2 times by default, honoring `Retry-After`, otherwise with exponential backoff). Change it with `Veribl(max_retries=5)` or per call.

## Idempotency

Every write (anything other than GET) is sent with an `Idempotency-Key`, generated for you and identical across retries, so a retried request is never applied twice. To use your own key, for example one tied to a row in your database:

```python
client.products.create({"name": "Trail E-Bike 500"}, idempotency_key="import-row-8841")
```

## Per-call options

Every method accepts these keyword arguments: `idempotency_key`, `api_version`, `max_retries`, `timeout` (seconds) and `extra_headers`.

## Test mode

Keys that start with `vdpp_test_` reach test data only; `vdpp_` keys reach live data.

```python
client = Veribl("vdpp_test_...")
client.test_mode  # True
```

## Webhooks

Verify every delivery with the endpoint secret, using the raw request body exactly as received (before any JSON parsing). A Flask example:

```python
import os
from flask import Flask, request
from veribl import Veribl, VeriblSignatureVerificationError

app = Flask(__name__)

@app.post("/webhooks/veribl")
def veribl_webhook():
    try:
        event = Veribl.webhooks.construct_event(
            request.get_data(),  # raw bytes, not request.json
            request.headers.get("Veribl-Signature"),
            os.environ["VERIBL_WEBHOOK_SECRET"],
        )
    except VeriblSignatureVerificationError:
        return "invalid signature", 400

    if event["event"] == "passport.published":
        print("Published", event["data"])
    return "", 204
```

Deliveries older than 5 minutes are rejected (`tolerance=300`). The event `id` is the same on every retry, so deduplicate on it. In tests, `Veribl.webhooks.generate_test_header(body, secret)` makes a valid header.

## API versions

Pin a dated API version, sent as `Veribl-Version`, for the whole client or for one call:

```python
client = Veribl(api_version="2026-10-21")
client.products.list(api_version="2026-10-21")
```

Without it, you get the version your key was made with.

## Documentation

Full API reference and guides: https://docs.veribl.com

## License

MIT
