AgentStack
Browse Sign in
Browse Why AgentStack Sell Docs
Sign in
SKILL verified MIT Self-run

Duffel

skill-borski-travel-hacking-toolkit-duffel · by borski

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.

No reviews yet
0 installs
0 views
view→install

Install

$ agentstack add skill-borski-travel-hacking-toolkit-duffel

✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.

Security review

✓ Passed

No issues found. Passed automated security review. · v0.1.0 How review works →

  • Prompt-injection patterns
  • Secret / credential exfiltration
  • Dangerous shell & filesystem operations
  • Untrusted network calls
  • Known-malicious package signatures

What it can access

  • Network access Used
  • Filesystem access No
  • Shell / process execution No
  • Environment & secrets No
  • Dynamic code execution No

From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.

View the full security report →

Verified badge

Passed review? Show it. Paste this badge into your README, it links to the public security report.

AgentStack Verified badge Links to your public security report.
[![AgentStack Verified](https://agentstack.voostack.com/badges/verified.svg)](https://agentstack.voostack.com/security/report/skill-borski-travel-hacking-toolkit-duffel)

Reliability & compatibility

Security review passed
0 installs to date
no reviews yet
3mo ago

Declared compatibility

Claude CodeClaude Desktop

Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.

Preview Execution monitoring

We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.

How agent discovery & health will work →
Are you the author of Duffel? Claim this listing to set pricing, connect Stripe payouts, and keep 70% of every sale.
Sign up to claim

About

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_LIVE environment variable set with a live API token
  • Token needs air.offer_requests.create permission

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_timeout lower than your HTTP client timeout.
  • Use age for child passengers instead of type to avoid airline mismatch errors.
  • The API returns real GDS prices. These are bookable, not estimates.
  • Duffel aggregates across multiple airlines in a single search.

Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

Install and usage instructions live in the source repository linked above.

Reviews

No reviews yet, be the first.

Versions

  • v0.1.0 Imported from the upstream source.