An unofficial Python client library for the Duffel travel booking API
Project description
duffelbag
An unofficial Python client library for the Duffel travel booking API.
Duffel provides a single API for searching and booking flights across hundreds of airlines, hotel stays, and managing payments. Duffel discontinued support for their own Python library in 2023. While there are other Python libraries that wrap parts of their API, none appear to be complete.
duffelbag aims to provide a comprehensive, typed, Pythonic interface to the API with both synchronous and async client support.
Features
- Sync and async clients with identical API surfaces
- Pydantic v2 models for all request and response types
- Automatic pagination with lazy iterators
- Built-in rate limit handling (auto-retry on 429)
- Typed exceptions mapped to HTTP status codes
- Covers flights, stays, payments, webhooks, and identity APIs
Installation
Install using pip (or better still, uv pip) with:
pip install duffelbag
You can install the very latest version from source directly from GitHub with:
pip install git+https://github.com/nickovs/duffelbag.git
duffelbag is designed to run on Python 3.12 or later, but may run on earlier versions of Python.
Quick start
Sign up at duffel.com and grab your API token from the dashboard. Test tokens start with duffel_test_ and let you make bookings against Duffel's simulated airlines at no cost.
Search for flights
from duffelbag import Duffel
client = Duffel(token="duffel_test_...")
offer_request = client.offer_requests.create(
slices=[{
"origin": "LHR",
"destination": "JFK",
"departure_date": "2026-06-15",
}],
passengers=[{"type": "adult"}],
cabin_class="economy",
return_offers=True,
)
for offer in sorted(offer_request.offers, key=lambda o: float(o.total_amount))[:5]:
print(f"{offer.owner.name:20s} {offer.total_amount} {offer.total_currency}")
client.close()
Book a flight
from duffelbag import Duffel
client = Duffel(token="duffel_test_...")
# 1. Search
offer_request = client.offer_requests.create(
slices=[{"origin": "LHR", "destination": "JFK", "departure_date": "2026-06-15"}],
passengers=[{"type": "adult"}],
return_offers=True,
)
# 2. Pick an offer
offer = sorted(offer_request.offers, key=lambda o: float(o.total_amount))[0]
# 3. Create an order
order = client.orders.create(
selected_offers=[offer.id],
passengers=[{
"id": offer.passengers[0].id,
"title": "mr",
"gender": "m",
"given_name": "Test",
"family_name": "Traveller",
"born_on": "1990-01-01",
"email": "test@example.com",
"phone_number": "+442080000000",
}],
type="instant",
payments=[{
"type": "balance",
"amount": offer.total_amount,
"currency": offer.total_currency,
}],
)
print(f"Booked! Reference: {order.booking_reference}")
client.close()
Async usage
import asyncio
from duffelbag import AsyncDuffel
async def main():
async with AsyncDuffel(token="duffel_test_...") as client:
offer_request = await client.offer_requests.create(
slices=[{"origin": "LHR", "destination": "CDG", "departure_date": "2026-07-01"}],
passengers=[{"type": "adult"}],
return_offers=True,
)
async for offer in client.offers.list(offer_request.id, sort="total_amount"):
print(f"{offer.owner.name} {offer.total_amount} {offer.total_currency}")
asyncio.run(main())
Browse reference data
from duffelbag import Duffel
client = Duffel(token="duffel_test_...")
# Airlines, airports, aircraft, cities — .page() fetches one page
for airline in client.airlines.list(limit=10).page():
print(f"[{airline.iata_code}] {airline.name}")
# Place search
for place in client.places.suggest("Tokyo"):
print(f"{place.type}: {place.name} ({place.iata_code})")
client.close()
API coverage
| Section | Resources |
|---|---|
| Flights | Offer requests, offers, orders, order changes, order cancellations, seat maps, payments, partial offer requests, batch offer requests, airline-initiated changes, airline credits |
| Stays | Search, quotes, bookings, accommodation, brands, negotiated rates, loyalty programmes |
| Payments | Cards (PCI-compliant endpoint), 3D Secure sessions |
| Notifications | Webhooks, webhook events, webhook deliveries |
| Identity | Customer users, customer user groups, component client keys |
| Reference data | Airlines, airports, aircraft, cities, places, loyalty programmes |
Every resource is available through both the sync Duffel client and the async AsyncDuffel client.
Pagination
List endpoints return lazy iterators that fetch pages on demand:
# Iterate through all results automatically
for airline in client.airlines.list():
print(airline.name)
# Or fetch one page at a time
iterator = client.airports.list(limit=50, iata_country_code="US")
first_page = iterator.page()
second_page = iterator.page()
Async pagination works with async for:
async for offer in client.offers.list(offer_request_id):
print(offer.total_amount)
Error handling
API errors are raised as typed exceptions:
from duffelbag import Duffel, NotFoundError, ValidationError, AuthenticationError
client = Duffel(token="duffel_test_...")
try:
client.orders.get("ord_nonexistent")
except NotFoundError as e:
print(f"Not found: {e}")
except ValidationError as e:
print(f"Validation error: {e}")
for error in e.errors:
print(f" {error.code}: {error.message}")
except AuthenticationError:
print("Check your API token")
Examples
See the examples/ directory for runnable scripts:
- search_flights.py -- Search and display flight offers
- book_and_cancel_flight.py -- Full booking lifecycle
- async_search.py -- Async round-trip flight search
- explore_reference_data.py -- Browse airlines, airports, and places
- seat_maps.py -- Fetch seat map data for an offer
Set your token via environment variable or a test_api_key file:
export DUFFEL_TOKEN="duffel_test_..."
python examples/search_flights.py
Duffel API documentation
Development
# Clone and install
git clone https://github.com/nickovs/duffelbag.git
cd duffelbag
uv pip install -e ".[dev]"
# Run tests (mocked, no API key needed)
.venv/bin/pytest tests/ --ignore=tests/test_live.py -v
# Run live integration tests (requires test_api_key file)
.venv/bin/pytest tests/test_live.py -v
# Lint
.venv/bin/ruff check src/ tests/
License
MIT
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file duffelbag-0.1.3.tar.gz.
File metadata
- Download URL: duffelbag-0.1.3.tar.gz
- Upload date:
- Size: 51.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.6.1
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
663fe79292408470dd811607c04279e2dbac15e213d2166645698bcb99647791
|
|
| MD5 |
d3f925b6819daf24336149d9b4863a12
|
|
| BLAKE2b-256 |
55d4e206586376a53d83cfa27bc022a8018407d1acd6cd3bc8b4da2131bc0229
|
File details
Details for the file duffelbag-0.1.3-py3-none-any.whl.
File metadata
- Download URL: duffelbag-0.1.3-py3-none-any.whl
- Upload date:
- Size: 33.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.6.1
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
12a3214103ed4ff794ed6c0b6ddfa57a79efeb0056e47f03c568ea0d58daa0b4
|
|
| MD5 |
a501a4fe5aa73a6536915147d3edc701
|
|
| BLAKE2b-256 |
ae891ad6d54235cb334c90b4e896a07992a869c505120d3262a75f9aa1764b63
|