Southwest Airlines Flight Search

Site southwest.comTask search-flights-jl329mVersion v1Updated Jul 13, 2026Category travel

Search southwest.com for available flights between two airports on given dates and return matching itineraries with four-bucket fares (Wanna Get Away / Plus / Anytime / Business Select) in dollars or Rapid Rewards points. Read-only — never books. Southwest refuses syndication to OTAs, so southwest.com is the only source of truth for these fares. This skill was captured from a live agent session on southwest.com and publishes here verbatim, exactly as an agent receives it.

NoteSelectors and URL schemes drift as sites change. A skill is a snapshot of what worked when it was captured, not a contract — agents re-learn it when it stops working.

Purpose

Search southwest.com for available flights between two airports on given dates and return matching itineraries — flight numbers, routing, segments, durations, stop counts, connection airports, layovers, on-time-performance (when shown), and a four-bucket fares object (wanna_get_away / wanna_get_away_plus / anytime / business_select) priced in both USD and Rapid Rewards points + tax. Supports round-trip and one-way (multi-city is modeled as a sequence of one-ways — Southwest does not sell true multi-city), Dollars or Points fare mode, full passenger mix (adult / child / lap-infant / senior), promo code, time-of-day / stops / sort filters, and the Low Fare Calendar month-grid sweep. Read-only — never click Continue, Select, Book, or any seat-selection control. Southwest deliberately refuses syndication to third-party search engines (Kayak, Google Flights, Expedia), so southwest.com is the only source of truth for Southwest fares.

When to Use

  • Compare Southwest's four fare tiers for a specific O-D pair on specific dates.
  • Sweep a month-grid for the cheapest-day fare (Low Fare Calendar).
  • Price the same itinerary in dollars vs. Rapid Rewards points to inform a points-vs-cash decision.
  • Confirm whether a Southwest route exists at all (Southwest's network is heavily point-to-point — many city pairs require connections, and some "expected" pairs like JFK don't exist because Southwest doesn't serve JFK).
  • Any flow that needs Southwest fares — they cannot be obtained from any OTA, meta-search, or GDS aggregator.

Workflow

The only reliable surface is the scripted browser flow (browserless_agent) with the /air/booking/select.html?… deep-link URL. The internal JSON endpoint /api/air-booking/v1/air-booking/page/air/booking/shopping (and all sibling /api/air-booking/v1/* and /api/content/v1/* routes) returns Akamai 403 Access Denied on every cookieless HTTP call — verified across direct out-of-band fetches and a residential-proxy fetch (Reference #18.1071ca17). Don't waste cycles trying to hit those endpoints; lead with the deep-link + scripted browsing path described below.

1. Residential-proxy session (mandatory)

Every browserless_agent call must carry a residential proxy — pass the top-level proxy arg on every call:

{ "proxy": { "proxy": "residential", "proxyCountry": "us" } }

The residential proxy is non-negotiable. Southwest is fronted by Akamai Bot Manager (bazadebezolkohpepadr token, /akam/13/8333552 sensor, ak_bmsc + bm_mi + bm_sz cookies). A datacenter or proxy-less session gets either the generic "There was a problem" error page or an outright Akamai-Access-Denied HTML.

A browserless_agent session persists across separate calls — it is keyed by the call's proxy config, so a later call carrying the same proxy reconnects to the same warmed browser with its Akamai cookies (ak_bmsc, bm_sz, …) intact; there is still no session-release step to make. For convenience, keep the whole deep-link → hydrate → snapshot → extract flow inside ONE call's commands array — it saves round-trips and avoids accidentally dropping the proxy config on a follow-up call. If you do split it across calls, repeat the same proxy on every call; a call that drops or changes it lands in a different, cookieless session that Akamai rejects.

Southwest accepts the entire search payload as URL query parameters on /air/booking/select.html, which 301-redirects to /air/booking/select-depart.html and triggers the same React shopping flow the form would. This saves ~6 turns of form filling, autocomplete waiting, and date-picker clicking per search.

https://www.southwest.com/air/booking/select.html
    ?originationAirportCode=DAL
    &destinationAirportCode=LAS
    &departureDate=2026-06-15
    &returnDate=2026-06-18
    &tripType=roundtrip                  // or "oneway" (no &returnDate)
    &adultPassengersCount=1              // 1-8
    &seniorPassengersCount=0             // 65+; counts toward total (max 8)
    &passengerType=ADULT                 // primary passenger pricing class
    &fareType=USD                        // "USD" (dollars) or "POINTS"
    &promoCode=                          // optional, leave empty for none
    &int=HOMEQBOMAIR                     // internal tracking; safe to omit

Open it — put the goto and the hydration wait in the same commands array (see §4/§5 for the full single-call shape):

{
  "proxy": { "proxy": "residential", "proxyCountry": "us" },
  "commands": [
    {
      "method": "goto",
      "params": {
        "url": "https://www.southwest.com/air/booking/select.html?originationAirportCode=DAL&destinationAirportCode=LAS&departureDate=2026-06-15&returnDate=2026-06-18&tripType=roundtrip&adultPassengersCount=1&fareType=USD&passengerType=ADULT",
        "waitUntil": "load",
        "timeout": 45000
      }
    },
    { "method": "waitForTimeout", "params": { "time": 4000 } }
  ]
}

Use waitUntil: "load" (never networkidle — the SPA polls and never idles). The 4 s pause lets the fare grid hydrate asynchronously; §4 gives the more robust text-based wait signal.

URL-param contract (verified from 301-redirect echo behavior and bootstrap config):

ParamValuesNotes
originationAirportCode / destinationAirportCode3-letter IATAMust be a Southwest-served airport (see §3).
departureDate / returnDateYYYY-MM-DDLocal-date at the origin airport. Omit returnDate for tripType=oneway.
tripTyperoundtrip | onewaySouthwest does not offer true multi-city — model as a sequence of one-ways.
adultPassengersCount18
seniorPassengersCount0765+; reduces the adult bucket. Combined adult + senior ≤ 8.
passengerTypeADULTThe lookup pricing class. Children (2–11) and lap-infants are added on the in-page passenger drawer — they are NOT URL-deeplinkable; if you need them, fall through to the form-fill path in §6.
fareTypeUSD | POINTSURL form sends USD/POINTS; the page's own internal state object may show DOLLARS/POINTS — don't conflate.
promoCodestringOptional.

3. Resolve airport codes (when input is a city name, not IATA)

The full Southwest airport list (122 stations, including Caribbean / Mexico / Central America) is published as a base64-encoded JS bundle at:

https://www.southwest.com/swa-ui/bootstrap/air-booking-v2/1/data.js

Fetch it with browserless_function. The bundle lives on the www.southwest.com origin, so a same-origin in-page fetch works once the page has navigated there — page.goto('https://www.southwest.com/') first (a bare fetch in the function runtime has no network egress until the page is on the origin), then page.evaluate the fetch, base64-decode, and parse/project the airport records inside the eval — return only the matched records, never the ~650 KB decoded blob (the function text return is capped). Grep the decoded text for "emailDisplayName":"…","cityServed":"…","stationName":"…","id":"XYZ" records:

export default async function ({ page }) {
  await page.goto("https://www.southwest.com/", {
    waitUntil: "load",
    timeout: 45000,
  });
  const records = await page.evaluate(async () => {
    const b64 = await fetch("/swa-ui/bootstrap/air-booking-v2/1/data.js").then((r) =>
      r.text(),
    );
    const src = atob(b64);
    const out = [];
    const re =
      /"emailDisplayName":"([^"]*)","cityServed":"([^"]*)","stationName":"([^"]*)","id":"([A-Z]{3})"/g;
    let m;
    while ((m = re.exec(src)))
      out.push({
        emailDisplayName: m[1],
        cityServed: m[2],
        stationName: m[3],
        id: m[4],
      });
    return out;
  });
  return { data: records, type: "application/json" };
}

Run this behind the same residential proxy (pass proxy on the browserless_function call too). This is faster, cheaper, and more reliable than driving Southwest's airport autocomplete UI for the lookup. Cache the returned list — it is the full 122-station table.

Example records:

"emailDisplayName":"Dallas (Love)","cityServed":"Dallas","stationName":"Dallas (Love Field)","id":"DAL"
"emailDisplayName":"Chicago (Midway)","cityServed":"Chicago","stationName":"Chicago (Midway)","id":"MDW"
"emailDisplayName":"Chicago (O'Hare-Terminal 5)","cityServed":"Chicago","stationName":"Chicago (O'Hare-Terminal 5)","id":"ORD"

Multi-airport cities surface multiple records — use cityServed for "any Dallas airport" and pick DAL (Love) vs. DFW (Southwest does NOT serve DFW; only DAL).

4. Wait for the fare grid to hydrate

The HTML shell that comes back from /air/booking/select-depart.html is ~5 KB of <div id="root"> + script tags — the fare cells are rendered client-side after the React app calls /api/air-booking/v1/air-booking/page/air/booking/shopping from within the page context (where Akamai accepts the session). The grid takes 2–5 s wall-time to render after load fires.

Wait signal: at least one outbound row contains the text "Wanna Get Away" in a button. Don't snapshot before that — early snapshots get a skeleton-loader DOM. Gate on the text with a waitForFunction, then take the a11y snapshot — all in the same commands array as the goto from §2:

{
  "proxy": { "proxy": "residential", "proxyCountry": "us" },
  "commands": [
    {
      "method": "goto",
      "params": {
        "url": "https://www.southwest.com/air/booking/select.html?originationAirportCode=DAL&destinationAirportCode=LAS&departureDate=2026-06-15&returnDate=2026-06-18&tripType=roundtrip&adultPassengersCount=1&fareType=USD&passengerType=ADULT",
        "waitUntil": "load",
        "timeout": 45000
      }
    },
    {
      "method": "waitForFunction",
      "params": {
        "fn": "document.body.innerText.includes('Wanna Get Away')",
        "timeout": 15000
      }
    },
    { "method": "snapshot" }
  ]
}

For a large results grid prefer parsing in-page with an evaluate command (return a compact JSON projection of the cards) over a raw snapshot, which can exceed the result-size limit.

5. Extract per-flight data from the snapshot

Each outbound itinerary card on /air/booking/select-depart.html exposes:

  • Flight number(s) — 4-digit Southwest numerics (e.g., 1234, 2789); a connecting itinerary shows multiple flight numbers joined by / (e.g., 1234 / 5678).
  • Routing — origin → connection(s) → destination IATAs, visible as "DAL → LAS" or "DAL → HOU → LAS".
  • Depart / Arrive times — local times per segment, displayed as 6:00 AM etc. Combine with the URL's departureDate to build local ISO datetimes.
  • Aircraft type — when surfaced, appears as "Boeing 737-700", "Boeing 737-800", or "Boeing 737 MAX 8" in the segment detail row. Southwest operates an all-737 fleet.
  • Total duration — formatted Xh Ym.
  • Stop count + connection airport(s) — "Nonstop", "1 stop in HOU", "2 stops in HOU, MCO".
  • Layover duration — shown per connection as Xh Ym layover in <CITY>.
  • On-time-performance % — when shown (Southwest surfaces this inconsistently, mostly on mainline routes), labeled "On-time performance: 78%".
  • Four-bucket faresWanna Get Away / Wanna Get Away Plus / Anytime / Business Select. In Points mode the cell shows points + + $5.60 cash for taxes/fees. Sold-out buckets render as "Unavailable" or are missing entirely — flag as sold_out: true.

Internal fare-class enum (from the bootstrap config — useful when parsing DOM attributes): WANNA_GET_AWAY_FARE, WANNA_GET_AWAY_PLUS_FARE, ANYTIME_FARE, BUSINESS_SELECT_FARE.

For round-trips, the page renders outbound first; after extraction, click the "Return" tab (read-only — selecting a tab is not a booking action) and take a second snapshot/evaluate to extract the return leg using the same shape. Append these as further entries in the same commands array so the warmed Akamai session carries through.

The deep-link URL covers adults + seniors, dates, O-D, fare type, promo. It does not cover:

  • Children (ages 2–11) — must be added on the in-page passenger drawer.
  • Lap infants (< 2) — same.
  • Multi-city sequences — drive two one-way searches in sequence.

When the search requires children or lap-infants, goto https://www.southwest.com/air/booking/ (the form, not the deep-link) and drive the form as a sequence of commands in one browserless_agent call (confirm any label via a snapshot if a selector misses):

  1. click radio: Round Trip or radio: One Way.
  2. click combobox: Depart (the origin field), type the 3-letter IATA, waitForTimeout 1500 ms for the autocomplete dropdown, then click the matching option: <City>, <ST> - <IATA>.
  3. Repeat for combobox: Return.
  4. click the depart-date input → date picker → click the target date cell. Repeat for return date.
  5. click button: Passengers → adjust adult / child / lap-infant counts via the +/- steppers → click button: Confirm. Senior toggles also live here.
  6. click button: Search and continue from §4.

Do NOT drive the airport combobox with a single type that auto-submits — a type carrying a trailing Enter (or a press Enter) fires before the dropdown surfaces. Use a click on the field, then a type of the plain IATA, then a waitForTimeout 1500 ms, then click the option.

7. Low Fare Calendar (month-grid sweep)

The Low Fare Calendar shows the cheapest fare across a 30-day window. Deep-link:

https://www.southwest.com/air/low-fare-calendar/
    ?originationAirportCode=DAL
    &destinationAirportCode=LAS
    &tripType=roundtrip
    &adultPassengersCount=1
    &fareType=USD
    &passengerType=ADULT

(No departureDate / returnDate — the calendar picks its own anchor month.) Each day-cell renders the lowest available fare for an outbound departing that date (round-trip pricing assumes a 3-night return; one-way mode shows one-way price). Capture per-day cheapest fare and the matching day-cell data- attribute or aria-label. Calendar hydration is the same — wait for the first price cell to render before snapshotting.

/air/low-fare-calendar.html (note the .html suffix) returns 404 — only the trailing-slash form works.

8. Filters (results page only)

Once on /air/booking/select-depart.html, the results page exposes a filter drawer (button: Filter):

  • Stops — Nonstop / 1 stop / 2+ stops.
  • Departure time window — Early morning (12am–5am), Morning (5am–noon), Afternoon (noon–6pm), Evening (6pm–midnight).
  • Sort — Departure / Arrival / Duration / Price (per fare bucket).

Apply via UI clicks; there are no URL-param forms of these filters that survive a 301 redirect. Re-snapshot after each filter click.

9. Session teardown

No session-release step — there is nothing to release. The session persists across calls, keyed by the proxy config: a later call with the same proxy reconnects to the same warmed browser with its Akamai cookies intact. As a convenience, keep the whole flow (warm-up → hydrate → extract → filter → return-tab) inside ONE call's commands array to save round-trips and avoid accidentally dropping the proxy config; if you split it, repeat the same proxy on every call so you stay in the same session.

Site-Specific Gotchas

  • READ-ONLY. Never click button: Continue, the per-fare-cell button: Select, or any seat-map control. Selecting a fare advances to a passenger-info page and starts a booking flow. Stop at the results / calendar grid.
  • Akamai is the protection layer. Bot-manager fingerprint script at /akam/13/8333552, sensor token bazadebezolkohpepadr, edge cookies ak_bmsc + bm_mi + bm_sz. Verified Akamai routing via Akamai-Request-Bc response header. a stealth + residential-proxy session is non-negotiable.
  • The internal JSON shopping API is confirmed-blocked from cookieless callers. /api/air-booking/v1/air-booking/page/air/booking/shopping, /api/air-booking/v1/air-booking/page/air/booking/price, /api/air-booking/v1/air-booking/page/air/booking/purchase-secure, /api/air-booking/v1/air-booking/page/air/booking/confirmation-secure, and /api/content/v1/* all return Akamai 403 (Reference #18.1071ca17.…) when called out-of-band even behind a residential proxy — a fresh residential-proxy IP and no other anti-bot diff didn't help. Don't try to replay them out-of-band; the page-context fetch the React app makes (from inside a warmed browserless_agent session) is the only way Akamai accepts them. Don't waste time on direct API calls.
  • /v2/ path is Disallow: in robots.txt. Southwest's robots.txt forbids spidering /v2/* (which is where the React static bundles live: /v2/air/booking/static/9.0.1/...). This is a politeness signal — the booking flow is on /air/booking/* which is allowed.
  • select.htmlselect-depart.html 301 redirect is part of the contract. The form-submit endpoint is /air/booking/select.html; that URL 301s to /air/booking/select-depart.html carrying all query params unchanged. Build your deep-link against select.html and let the redirect resolve — both URLs work, but constructing against select.html matches what the form does.
  • fareType value mismatch. The URL param value is USD (or POINTS); the page's internal state object uses DOLLARS/POINTS. Don't send fareType=DOLLARS in the URL — verified the form sends USD.
  • Children and lap-infants are NOT URL-deeplinkable. Only adultPassengersCount and seniorPassengersCount are read from the URL; children and lap-infants must be added in the in-page passenger drawer. If you need them, take the form-fill path (§6).
  • Southwest does NOT serve DFW or JFK. Common pre-flight check: confirm both origin and destination are in the 122-airport list at §3. Don't fabricate a route — the SPA renders "We couldn't find any flights" silently if the route doesn't exist.
  • All-737 fleet. Aircraft type is always Boeing 737-700, Boeing 737-800, or Boeing 737 MAX 8 when shown. Treat any other value as a parse error.
  • The fare grid hydrates 2–5 s after load. Snapshot too early and you get the skeleton DOM with no price cells. Wait until at least one row contains "Wanna Get Away" text before extracting.
  • "There was a problem" generic error page. When Akamai bot-detection trips mid-session or the hydration XHR fails, Southwest renders a generic error card. Capture the page text and retry once as a distinct browserless_agent session (residential proxy — because a call reusing the same config would reconnect to the same tripped session, start a fresh session, e.g. by not reusing the prior config, so it lands on a clean IP/fingerprint). If it recurs, return success: false, reason: "site_error" rather than re-attempting indefinitely.
  • Geolocation is Akamai-inferred from the proxy IP. The HTML shell includes swa.geolocation = "georegion=…,country_code=US,region_code=NY,…". This doesn't affect fare results but may cause US dollar/points denomination defaults. For non-US searches, no URL param is needed — Southwest only sells in USD/Rapid Rewards.
  • The bootstrap data bundle is base64-encoded. https://www.southwest.com/swa-ui/bootstrap/air-booking-v2/1/data.js returns a base64 string that decodes to a ~650 KB JS module containing the airport list, fare-class enums, family-trip destination lists, and other UI metadata. The accompanying …/content/en.js is similarly base64-encoded but contains CMS strings (homepage copy), not fare/airport data — only data.js is useful for the airport-resolution shortcut in §3.
  • Sold-out buckets ≠ missing fare class. A flight that shows three of four prices with the fourth replaced by "Unavailable" is sold out on that bucket. A flight that shows fewer than four buckets total may be on a fare-class-restricted route (e.g., some short-haul routes don't sell Business Select). Distinguish in output: sold_out: true vs not_offered: true.
  • Senior fares (seniorPassengersCount) are a separate pricing class but render in the same four-bucket grid; you can verify by re-running the same itinerary with seniorPassengersCount=1 and confirming a different price (typically only Anytime and Wanna Get Away change).
  • No-change-fees + two-free-bags are static perks. Surface them as static metadata in the response (perks: ["Two free checked bags", "No change fees"]), not as a per-flight field — they apply to all Southwest itineraries equally and the bootstrap config confirms fareTypes.WGA.features.NO_CHANGE_FEE = true for every fare bucket.
  • The Low Fare Calendar URL is /air/low-fare-calendar/ (trailing slash, no .html). /air/low-fare-calendar.html returns 404. Easy footgun.
  • Filter state is not URL-encoded. Stops, time-of-day, and sort filters live in client-side React state only — they do not survive a page reload. Apply filters via UI clicks after the results render; don't try to URL-encode them.

Expected Output

{
  "success": true,
  "trip_type": "round_trip",
  "origin": "DAL",
  "destination": "LAS",
  "depart_date": "2026-06-15",
  "return_date": "2026-06-18",
  "pay_with": "dollars",
  "passengers": {
    "adults": 1,
    "seniors": 0,
    "children": 0,
    "lap_infants": 0
  },
  "promo_code": null,
  "outbound_flights": [
    {
      "flight_numbers": ["1234"],
      "routing": ["DAL", "LAS"],
      "segments": [
        {
          "flight_number": "1234",
          "origin": "DAL",
          "destination": "LAS",
          "depart_local": "2026-06-15T06:00:00",
          "arrive_local": "2026-06-15T07:25:00",
          "aircraft": "Boeing 737-800"
        }
      ],
      "duration_minutes": 205,
      "stops": 0,
      "connections": [],
      "on_time_performance_pct": 78,
      "fares": {
        "wanna_get_away": {
          "price_usd": 79,
          "points": 4567,
          "points_plus_dollars": 5.6,
          "sold_out": false
        },
        "wanna_get_away_plus": {
          "price_usd": 109,
          "points": 6800,
          "points_plus_dollars": 5.6,
          "sold_out": false
        },
        "anytime": {
          "price_usd": 280,
          "points": 17500,
          "points_plus_dollars": 5.6,
          "sold_out": false
        },
        "business_select": {
          "price_usd": 330,
          "points": 20700,
          "points_plus_dollars": 5.6,
          "sold_out": false
        }
      }
    },
    {
      "flight_numbers": ["2789", "3411"],
      "routing": ["DAL", "HOU", "LAS"],
      "segments": [
        {
          "flight_number": "2789",
          "origin": "DAL",
          "destination": "HOU",
          "depart_local": "2026-06-15T08:15:00",
          "arrive_local": "2026-06-15T09:20:00",
          "aircraft": "Boeing 737 MAX 8"
        },
        {
          "flight_number": "3411",
          "origin": "HOU",
          "destination": "LAS",
          "depart_local": "2026-06-15T10:55:00",
          "arrive_local": "2026-06-15T12:10:00",
          "aircraft": "Boeing 737-700"
        }
      ],
      "duration_minutes": 295,
      "stops": 1,
      "connections": [{ "airport": "HOU", "layover_minutes": 95 }],
      "on_time_performance_pct": null,
      "fares": {
        "wanna_get_away": {
          "price_usd": 119,
          "points": 7200,
          "points_plus_dollars": 5.6,
          "sold_out": false
        },
        "wanna_get_away_plus": {
          "price_usd": 149,
          "points": 9400,
          "points_plus_dollars": 5.6,
          "sold_out": false
        },
        "anytime": {
          "price_usd": 350,
          "points": 21900,
          "points_plus_dollars": 5.6,
          "sold_out": true
        },
        "business_select": {
          "price_usd": null,
          "points": null,
          "points_plus_dollars": null,
          "not_offered": true
        }
      }
    }
  ],
  "return_flights": [
    {
      "flight_numbers": ["4501"],
      "routing": ["LAS", "DAL"],
      "segments": [
        {
          "flight_number": "4501",
          "origin": "LAS",
          "destination": "DAL",
          "depart_local": "2026-06-18T17:30:00",
          "arrive_local": "2026-06-18T22:35:00",
          "aircraft": "Boeing 737-800"
        }
      ],
      "duration_minutes": 185,
      "stops": 0,
      "connections": [],
      "on_time_performance_pct": 82,
      "fares": {
        "wanna_get_away": {
          "price_usd": 89,
          "points": 5300,
          "points_plus_dollars": 5.6,
          "sold_out": false
        },
        "wanna_get_away_plus": {
          "price_usd": 119,
          "points": 7400,
          "points_plus_dollars": 5.6,
          "sold_out": false
        },
        "anytime": {
          "price_usd": 295,
          "points": 18400,
          "points_plus_dollars": 5.6,
          "sold_out": false
        },
        "business_select": {
          "price_usd": 345,
          "points": 21600,
          "points_plus_dollars": 5.6,
          "sold_out": false
        }
      }
    }
  ],
  "perks": ["Two free checked bags", "No change fees"],
  "error_reasoning": null
}

Distinct outcome shapes:

// Site-level error / Akamai wall
{ "success": false, "reason": "site_error", "error_reasoning": "Generic 'There was a problem' page rendered after hydration. Recreate session with fresh Verified flags and retry once.", ... }

// Anti-bot block (page-load 403 Access Denied)
{ "success": false, "reason": "anti_bot_block", "error_reasoning": "Akamai Access Denied at page load. Reference #18.xxxxxxxx.…", ... }

// Route not served by Southwest
{ "success": false, "reason": "route_not_served", "error_reasoning": "Southwest does not operate between <ORIG> and <DEST>. Both airports must appear in the 122-station network list.", ... }

// No availability on the requested date (route exists but zero flights)
{ "success": true, "outbound_flights": [], "return_flights": [], "no_availability": true }

// Low Fare Calendar month-grid sweep
{
  "success": true,
  "mode": "low_fare_calendar",
  "origin": "DAL", "destination": "LAS", "trip_type": "round_trip",
  "calendar": [
    {"date": "2026-06-01", "cheapest_usd": 59,  "fare_class": "wanna_get_away"},
    {"date": "2026-06-02", "cheapest_usd": 69,  "fare_class": "wanna_get_away"},
    {"date": "2026-06-03", "cheapest_usd": null, "sold_out": true}
  ],
  "perks": ["Two free checked bags", "No change fees"]
}

Call it

GET https://production-sfo.browserless.io/skills?token=TOKEN-HERE&domain=southwest.com&task=search-flights-jl329m