python-sendparcel-dpdpl
DPD Poland API provider for the python-sendparcel shipping ecosystem.
Alpha (0.1.1) — API may change between minor releases. Pin your dependency if you use it in production.
Features
- Two providers —
DPDStandardProvider(door-to-door courier) andDPDPickupProvider(PUDO pickup point) as separateBaseProvidersubclasses. - Honest capability metadata — both providers declare
ConfirmationMethod.NONEbecause this package does not implement DPD status callbacks or polling. - Standalone DPD client —
DPDClientasync HTTP wrapper usable independently of the sendparcel framework. - Auto-discovery — both providers register via the
sendparcel.providersentry-point group; no manual registration needed. - Multiple courier services — standard, express, next-day, and same-day delivery options.
- Pickup point support — PUDO delivery with simplified receiver addressing (contact info only, no full address).
- Address conversion — automatic conversion between sendparcel
AddressInfoand DPD address format, with legacyname/line1fallback. - Structured error handling —
DPDAPIErrorhierarchy inheriting from coreCommunicationErrorwith status codes and validation details. - Async-first — fully asynchronous with
httpxandanyio.
Installation
uv add python-sendparcel-dpdpl
Or with pip:
pip install python-sendparcel-dpdpl
Both providers are auto-discovered via the sendparcel.providers entry-point group — no manual registration needed.
Quick Start
Using providers through sendparcel
The providers integrate with the sendparcel flow automatically:
from sendparcel.registry import PluginRegistry
# Providers are discovered via entry points
registry = PluginRegistry()
choices = registry.get_choices()
# [('dpd_standard', 'DPD Kurier'), ('dpd_pickup', 'DPD Pickup'), ...]
Creating a standard courier shipment
provider = DPDStandardProvider(shipment=shipment, config={
"login": "your-dpd-login",
"password": "your-dpd-password",
"master_fid": 1495,
"sandbox": True, # use demo environment for testing
})
result = await provider.create_shipment(
reference="ORDER-123", # optional: package reference
cod_amount=150.00, # optional: cash on delivery
cod_currency="PLN", # optional: default "PLN"
declared_value=500.00, # optional: declared value
)
# result["external_id"] = "12345678" (session ID)
# result["tracking_number"] = "0000123456789" (waybill number)
Creating a pickup point shipment
provider = DPDPickupProvider(shipment=shipment, config={
"login": "your-dpd-login",
"password": "your-dpd-password",
"master_fid": 1495,
"sandbox": True,
})
result = await provider.create_shipment(
pickup_point="PL14509", # required: DPD Pickup point ID
reference="ORDER-456", # optional
)
# result["external_id"] = "12345679" (session ID)
# result["tracking_number"] = "0000123456790" (waybill number)
Generating a label
After creating a shipment, generate the waybill label using the session ID stored in shipment.external_id:
label = await provider.create_label(
label_format="A4", # optional: "A4" or "LBL_PRINTER"
doc_format="PDF", # optional: "PDF", "ZPL", "EPL"
label_type="BIC3", # optional: "BIC3" or "EXTENDED"
)
# label["format"] = "PDF"
# label["content_base64"] = "JVBERi0xLjQ..." (base64-encoded label)
# Decode the label to raw bytes
from sendparcel_dpdpl import DPDClient
label_bytes = DPDClient.decode_label(label["content_base64"])
with open("label.pdf", "wb") as f:
f.write(label_bytes)
Using DPDClient standalone
The HTTP client can be used independently of the sendparcel framework:
from sendparcel_dpdpl import DPDClient
async with DPDClient(
login="your-login",
password="your-password",
master_fid=1495,
sandbox=True,
) as client:
# Create packages and get waybill numbers
result = await client.generate_packages_numbers(payload={
"generationPolicy": "ALL_OR_NOTHING",
"packages": [{
"sender": {
"name": "Nadawca Sp. z o.o.",
"address": "Krakowska 10",
"city": "Krakow",
"postalCode": "30-001",
"countryCode": "PL",
"phone": "500100200",
},
"receiver": {
"name": "Jan Kowalski",
"address": "Warszawska 5/3",
"city": "Warszawa",
"postalCode": "00-001",
"countryCode": "PL",
"phone": "600200300",
"email": "jan@example.com",
},
"payerFID": 1495,
"parcels": [{"weight": 2.5, "sizeX": 30, "sizeY": 20, "sizeZ": 15}],
}],
})
# Generate labels (advise parcels)
session_id = result["sessionId"]
labels = await client.generate_sped_labels(payload={
"labelSearchParams": {
"policy": "STOP_ON_FIRST_ERROR",
"session": {"sessionId": session_id, "type": "DOMESTIC"},
},
"outputDocFormat": "PDF",
"format": "A4",
"outputType": "BIC3",
})
# Decode base64 label to bytes
pdf_bytes = DPDClient.decode_label(labels["documentData"])
# All-in-one: create + label + advise
shipment = await client.generate_shipment(payload={...})
# Generate handover protocol
protocol = await client.generate_protocol(payload={...})
# Validate postal code
check = await client.check_postal_code("PL", "30-001")
# Check courier availability
avail = await client.get_courier_availability("PL", "30-001")
# Order courier pickup
pickup = await client.create_pickup_call(payload={...})
# Cancel courier pickup
await client.cancel_pickup_call(
order_number="12345",
check_sum="abc123",
)
Configuration
Provider configuration is passed as a dict either through the config constructor parameter or via your framework adapter's settings:
| Key | Type | Default | Description |
|---|---|---|---|
login |
str |
(required) | DPD API login |
password |
str |
(required) | DPD API password |
master_fid |
int |
(required) | DPD master FID number (payer identifier) |
sandbox |
bool |
False |
Use demo API endpoint |
base_url |
str |
None |
Override API base URL (takes precedence over sandbox) |
timeout |
float |
30.0 |
HTTP request timeout in seconds |
API endpoints
| Environment | Base URL |
|---|---|
| Production | https://dpdservices.dpd.com.pl |
| Sandbox | https://dpdservicesdemo.dpd.com.pl |
Authentication
DPD uses HTTP Basic Auth (login/password) plus a master FID header (x-dpd-fid). All three credentials (login, password, master_fid) are required.
Integration with framework adapters
Pass DPD configuration through your adapter's provider settings:
# Django settings.py
SENDPARCEL_PROVIDER_SETTINGS = {
"dpd_standard": {
"login": "your-dpd-login",
"password": "your-dpd-password",
"master_fid": 1495,
"sandbox": True,
},
"dpd_pickup": {
"login": "your-dpd-login",
"password": "your-dpd-password",
"master_fid": 1495,
"sandbox": True,
},
}
# FastAPI / Litestar
config = SendparcelConfig(
default_provider="dpd_standard",
providers={
"dpd_standard": {
"login": "your-dpd-login",
"password": "your-dpd-password",
"master_fid": 1495,
"sandbox": True,
},
},
)
Providers
DPDStandardProvider
Door-to-door courier delivery. Supports multiple service levels.
- Slug:
dpd_standard - Services:
dpd_standard,dpd_express,dpd_next_day,dpd_today - Confirmation method: NONE (no push or pull status updates)
- Supported countries: PL
create_shipment parameters:
| Parameter | Required | Description |
|---|---|---|
cod_amount |
no | Cash on delivery amount |
cod_currency |
no | COD currency (default "PLN") |
declared_value |
no | Declared value amount |
declared_currency |
no | Declared value currency (default "PLN") |
services |
no | Additional transport services (list of dicts or service code strings) |
reference |
no | Package reference |
generation_policy |
no | Error handling policy (default ALL_OR_NOTHING) |
DPDPickupProvider
PUDO (Pick Up Drop Off) pickup point delivery. The receiver picks up the parcel from a DPD Pickup point.
- Slug:
dpd_pickup - Service:
dpd_pickup - Confirmation method: NONE (no push or pull status updates)
- Supported countries: PL
create_shipment parameters:
| Parameter | Required | Description |
|---|---|---|
pickup_point |
yes | DPD Pickup point ID (e.g. "PL14509") |
cod_amount |
no | Cash on delivery amount |
cod_currency |
no | COD currency (default "PLN") |
services |
no | Additional transport services |
reference |
no | Package reference |
generation_policy |
no | Error handling policy (default ALL_OR_NOTHING) |
PUDO receivers only need contact information (name, phone, email, country code) — the pickup point provides the delivery address.
Common provider methods
Both providers implement a subset of the BaseProvider interface:
| Method | Purpose |
|---|---|
create_shipment(**kwargs) |
Create a shipment in DPD (returns session ID + waybill) |
create_label(**kwargs) |
Generate waybill label (base64-encoded, PDF/ZPL/EPL) |
create_label parameters (both providers):
| Parameter | Required | Description |
|---|---|---|
label_format |
no | Page format: "A4" or "LBL_PRINTER" (default "A4") |
doc_format |
no | Document format: "PDF", "ZPL", "EPL" (default "PDF") |
label_type |
no | Label type: "BIC3" or "EXTENDED" (default "BIC3") |
Note: DPD providers in this package currently support shipment creation and label generation only. They do not implement
fetch_shipment_status,cancel_shipment,verify_callback, orhandle_callback, so their confirmation method isNONE.
Shipment flow
DPD uses a two-step flow:
create_shipment()— callsgeneratePackagesNumbersto register packages and obtain a session ID + waybill numbers.create_label()— callsgenerateSpedLabelswith the session ID to generate and advise the label. The label is returned as a base64-encoded string.
The session ID is stored in shipment.external_id and used automatically by create_label(). This package does not currently expose shipment status updates after creation.
Address Handling
The providers accept sendparcel.types.AddressInfo and convert it to the DPD address format. Two addressing styles are supported:
Structured (preferred):
address: AddressInfo = {
"first_name": "Jan",
"last_name": "Kowalski",
"company": "Firma Sp. z o.o.",
"street": "Krakowska",
"building_number": "10",
"flat_number": "5",
"city": "Krakow",
"postal_code": "30-001",
"country_code": "PL",
"phone": "500100200",
"email": "jan@example.com",
}
# Converted to: {"name": "Jan Kowalski", "address": "Krakowska 10/5", ...}
Legacy style (fallback):
address: AddressInfo = {
"name": "Jan Kowalski",
"line1": "Krakowska 10/5",
"city": "Krakow",
"postal_code": "30-001",
"phone": "500100200",
}
PUDO receiver addressing: For DPDPickupProvider, receiver addresses are simplified — only contact information (name, phone, email, country_code) is sent. The pickup point provides the physical delivery address.
Error Handling
All DPD API errors inherit from sendparcel.exceptions.CommunicationError:
from sendparcel_dpdpl.exceptions import (
DPDAPIError, # base: any non-2xx response
DPDAuthenticationError, # 401 Unauthorized
DPDValidationError, # 400 Bad Request
)
try:
result = await client.generate_packages_numbers(payload=payload)
except DPDAuthenticationError:
# Invalid login/password or master FID
...
except DPDValidationError as exc:
# Payload validation failed
print(exc.detail) # human-readable message
print(exc.errors) # list of error dicts from DPD API
except DPDAPIError as exc:
# Other API error
print(exc.status_code, exc.detail)
DPDClient API Reference
The standalone DPDClient provides direct access to DPD Poland API endpoints:
| Method | HTTP | Endpoint | Description |
|---|---|---|---|
generate_packages_numbers(payload) |
POST | /public/shipment/v1/generatePackagesNumbers |
Create packages, get waybill numbers |
generate_sped_labels(payload) |
POST | /public/shipment/v1/generateSpedLabels |
Generate waybill labels (advise parcels) |
generate_shipment(payload) |
POST | /public/shipment/v1/generateShipment |
All-in-one: create + label + advise |
generate_protocol(payload) |
POST | /public/shipment/v1/generateProtocol |
Generate handover protocol |
check_postal_code(country_code, zip_code) |
GET | /public/routing/v1/postalCode |
Validate postal code for delivery |
get_courier_availability(country_code, zip_code) |
GET | /public/courierorder/v1/courierOrderAvailability |
Check courier availability |
create_pickup_call(payload) |
POST | /public/courierorder/v1/packagesPickupCall |
Order courier pickup |
cancel_pickup_call(order_number, check_sum) |
DELETE | /public/courierorder/v1/packagesPickupCall |
Cancel courier pickup |
decode_label(document_data) |
— | (static) | Decode base64 label to bytes |
Supported Versions
| Dependency | Version |
|---|---|
| Python | >= 3.12 |
| python-sendparcel | >= 0.1.1 |
| httpx | >= 0.27.0 |
| anyio | >= 4.0 |
Running Tests
The test suite uses pytest with pytest-asyncio (asyncio_mode = "auto")
and respx for HTTP mocking.
# Install dev dependencies
uv sync --extra dev
# Run the full test suite
uv run pytest
# With coverage
uv run pytest --cov=sendparcel_dpdpl --cov-report=term-missing
Credits
- Author: Dominik Kozaczko (dominik@kozaczko.info)
- Built on top of python-sendparcel core library
- Integrates with the DPD Poland API
License
Metadata
Release files for python-sendparcel-dpdpl 0.1.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| python_sendparcel_dpdpl-0.1.1.tar.gz | 19.0 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| python_sendparcel_dpdpl-0.1.1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 36.5 kB
Release files / python_sendparcel_dpdpl-0.1.1.tar.gz
| Download URL | python_sendparcel_dpdpl-0.1.1.tar.gz |
|---|---|
| Size | 19.0 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
936bb704e0389a9a7d05f08f961a61bce0d5d93d71baf3a6ec92f4f106567f70
|
|
BLAKE2b-256 checksum How to use checksums |
6c51eea6cc02638ab6aa624adb7ff01cee6eadee77d35c96d245149960125f18
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.10.3 {"installer":{"name":"uv","version":"0.10.3","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Manjaro Linux","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|
Release files / python_sendparcel_dpdpl-0.1.1-py3-none-any.whl
| Download URL | python_sendparcel_dpdpl-0.1.1-py3-none-any.whl |
|---|---|
| Size | 17.5 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
20ab1a5f324fe251e9e86bb991d51a72cf4084e23e0c0581040043b9a4ad9035
|
|
BLAKE2b-256 checksum How to use checksums |
2379e3564205c9c0bbba9ff9fb4cd9aeb1056196264a235d9cb2cfef01c9d79a
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.10.3 {"installer":{"name":"uv","version":"0.10.3","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Manjaro Linux","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|