Agent skill
duffel
Real-time GDS flight search via Duffel API. Accurate per-fare-class pricing, cabin selection, multi-city, time preferences. Primary cash price source. Does not include Southwest.
Install this agent skill to your Project
npx add-skill https://github.com/borski/travel-hacking-toolkit/tree/main/skills/duffel
SKILL.md
Duffel Flights
Search for real-time flight offers across airlines via the Duffel API. Returns live pricing, cabin details, baggage info, and booking links. Supports one-way, round-trip, and multi-city searches.
Source: duffel.com
Prerequisites
DUFFEL_API_KEY_LIVEenvironment variable set with a live API token- Token needs
air.offer_requests.createpermission
API Basics
- Base URL:
https://api.duffel.com - Version header:
Duffel-Version: v2(REQUIRED, v1 is deprecated) - Auth:
Authorization: Bearer $DUFFEL_API_KEY_LIVE - Content-Type:
application/json - Rate limit: 60 requests per 60 seconds
Search Flights (One-Way)
curl -s -X POST "https://api.duffel.com/air/offer_requests?return_offers=true&supplier_timeout=15000" \
-H "Accept: application/json" \
-H "Duffel-Version: v2" \
-H "Authorization: Bearer $DUFFEL_API_KEY_LIVE" \
-H "Content-Type: application/json" \
-d '{
"data": {
"slices": [{
"origin": "SFO",
"destination": "NRT",
"departure_date": "2026-08-15"
}],
"passengers": [{"type": "adult"}],
"cabin_class": "economy"
}
}'
Search Flights (Round-Trip)
Add a second slice with origin/destination reversed:
curl -s -X POST "https://api.duffel.com/air/offer_requests?return_offers=true&supplier_timeout=15000" \
-H "Accept: application/json" \
-H "Duffel-Version: v2" \
-H "Authorization: Bearer $DUFFEL_API_KEY_LIVE" \
-H "Content-Type: application/json" \
-d '{
"data": {
"slices": [
{
"origin": "SFO",
"destination": "NRT",
"departure_date": "2026-08-15"
},
{
"origin": "NRT",
"destination": "SFO",
"departure_date": "2026-08-22"
}
],
"passengers": [{"type": "adult"}],
"cabin_class": "business"
}
}'
Search Flights (Multi-City)
Add as many slices as needed:
curl -s -X POST "https://api.duffel.com/air/offer_requests?return_offers=true&supplier_timeout=15000" \
-H "Accept: application/json" \
-H "Duffel-Version: v2" \
-H "Authorization: Bearer $DUFFEL_API_KEY_LIVE" \
-H "Content-Type: application/json" \
-d '{
"data": {
"slices": [
{"origin": "SFO", "destination": "NRT", "departure_date": "2026-08-15"},
{"origin": "NRT", "destination": "ICN", "departure_date": "2026-08-20"},
{"origin": "ICN", "destination": "SFO", "departure_date": "2026-08-25"}
],
"passengers": [{"type": "adult"}],
"cabin_class": "economy"
}
}'
Nonstop Only
Set max_connections to 0:
curl -s -X POST "https://api.duffel.com/air/offer_requests?return_offers=true&supplier_timeout=15000" \
-H "Accept: application/json" \
-H "Duffel-Version: v2" \
-H "Authorization: Bearer $DUFFEL_API_KEY_LIVE" \
-H "Content-Type: application/json" \
-d '{
"data": {
"slices": [{
"origin": "SFO",
"destination": "NRT",
"departure_date": "2026-08-15"
}],
"passengers": [{"type": "adult"}],
"cabin_class": "business",
"max_connections": 0
}
}'
Multiple Passengers
"passengers": [
{"type": "adult"},
{"type": "adult"},
{"age": 10},
{"type": "infant_without_seat"}
]
Use age instead of type for children to avoid passenger type mismatches between search and booking.
Time Preferences
Constrain departure or arrival times:
"slices": [{
"origin": "SFO",
"destination": "NRT",
"departure_date": "2026-08-15",
"departure_time": {"from": "08:00", "to": "14:00"},
"arrival_time": {"from": "06:00", "to": "18:00"}
}]
Query Parameters
| Parameter | Default | Description |
|---|---|---|
return_offers |
true |
Set to false to get just the request ID, then fetch offers separately |
supplier_timeout |
20000 |
Max ms to wait for airline responses (2000 to 60000) |
Reading the Response
The response is nested under data. Key fields:
data.id -> offer request ID
data.offers[] -> array of flight offers
.id -> offer ID (use to get details or book)
.total_amount / .total_currency -> total price
.base_amount / .base_currency -> base fare (before tax)
.tax_amount / .tax_currency -> taxes
.owner.name -> airline selling this
.expires_at -> when offer expires
.slices[] -> journey legs
.origin.iata_code -> departure airport
.destination.iata_code -> arrival airport
.duration -> e.g. "PT11H30M"
.segments[] -> individual flights
.marketing_carrier.name -> airline name
.marketing_carrier_flight_number
.operating_carrier.name -> actual operating airline
.departing_at / .arriving_at -> datetime
.duration -> segment duration
.origin.iata_code / .destination.iata_code
.passengers[].cabin_class -> economy/business/first
.passengers[].cabin.amenities -> wifi, power, seat info
.passengers[].baggages[] -> checked/carry_on allowances
.conditions -> refund/change policies
.refund_before_departure.allowed
.change_before_departure.allowed
.change_before_departure.penalty_amount
Get Offer Details
Retrieve full details for a specific offer:
curl -s "https://api.duffel.com/air/offers/$OFFER_ID" \
-H "Accept: application/json" \
-H "Duffel-Version: v2" \
-H "Authorization: Bearer $DUFFEL_API_KEY_LIVE"
Parsing Tips
Extract the 5 cheapest offers with jq:
| jq '[.data.offers | sort_by(.total_amount | tonumber) | .[:5][] | {
price: (.total_amount + " " + .total_currency),
airline: .owner.name,
route: [.slices[] | (.origin.iata_code + " -> " + .destination.iata_code)],
segments: [.slices[].segments[] | {
flight: (.marketing_carrier.iata_code + .marketing_carrier_flight_number),
carrier: .operating_carrier.name,
depart: .departing_at,
arrive: .arriving_at,
cabin: .passengers[0].cabin_class,
duration: .duration
}],
stops: ([.slices[].segments | length] | map(. - 1)),
expires: .expires_at
}]'
Cabin Classes
| Value | Description |
|---|---|
economy |
Standard economy |
premium_economy |
Premium economy |
business |
Business class |
first |
First class |
Important Notes
- Offers expire quickly (usually 15 to 30 minutes). Check
expires_at. - Always show the operating carrier name (US DOT regulation).
- Set
supplier_timeoutlower than your HTTP client timeout. - Use
agefor child passengers instead oftypeto avoid airline mismatch errors. - The API returns real GDS prices. These are bookable, not estimates.
- Duffel aggregates across multiple airlines in a single search.
Recommended Agent Skills
Expand your agent's capabilities with these related and highly-rated skills.
chase-travel
atlas-obscura
Search Atlas Obscura for weird, wonderful, and hidden gem places near any destination. Find the interesting stuff, not boring plaques. Search by coordinates, get full details with descriptions and images.
rapidapi
Search Google Flights Live and Booking.com Live via RapidAPI. Use as a secondary source for flight cash prices and hotel availability/pricing. Triggers on "Booking.com", "booking prices", "RapidAPI", "secondary flight search", "hotel prices on Booking", or when SerpAPI results need a second opinion. Also useful for vacation rental pricing.
premium-hotels
Search Amex Fine Hotels & Resorts (FHR), The Hotel Collection (THC), and Chase Sapphire Reserve Hotel Collection (Chase Edit) properties by city. Compare credits, benefits, and stacking opportunities across programs.
google-flights
Search Google Flights for flight prices and schedules using browser automation. Use when user asks to search flights, find airfare, compare prices, check flight availability, or look up routes. Covers ALL airlines including Southwest (which no API can reach). Triggers include "search flights", "find flights", "how much is a flight", "flights from X to Y", "cheapest flight", "flight prices", "airfare", "flight schedule", "nonstop flights", "when should I fly".
ignav
Search for flights using the Ignav API. Fast REST API for flight prices and booking links. Use alongside other flight sources for price comparison. Always include in every flight search. Triggers on any flight-related search request.
Didn't find tool you were looking for?