book_with_points
Search for Southwest Airlines flights and book using Rapid Rewards points. View points pricing across fare classes and complete bookings through your logged-in account.
Instructions
Search for flights and book using Rapid Rewards points. Shows points pricing for all fare classes. Requires logged-in account.
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| originAirport | Yes | Origin airport code | |
| destinationAirport | Yes | Destination airport code | |
| departureDate | Yes | Departure date YYYY-MM-DD | |
| returnDate | No | Return date YYYY-MM-DD (for round trip) | |
| passengers | No | ||
| tripType | No | ||
| username | No | Rapid Rewards account number (uses SW_USERNAME env var if not set) | |
| password | No | Account password (uses SW_PASSWORD env var if not set) |
Implementation Reference
- src/tools/book-with-points.ts:23-134 (handler)The main handler function 'bookWithPoints' that implements the tool logic. It authenticates the user, searches for Southwest Airlines flights with points pricing, and returns available flights with points fares for different fare classes (Wanna Get Away, Wanna Get Away Plus, Anytime, Business Select).
export async function bookWithPoints(input: BookWithPointsInput) { const page = await getPage(); const username = input.username || process.env.SW_USERNAME; const password = input.password || process.env.SW_PASSWORD; if (!username || !password) { return { success: false, message: "Login required for points booking. Set SW_USERNAME and SW_PASSWORD environment variables.", }; } // Log in const alreadyLoggedIn = await ensureLoggedIn(page); if (!alreadyLoggedIn) { await login(page, username, password); } // Search flights with points fare type const params = new URLSearchParams({ "originationAirportCode": input.originAirport.toUpperCase(), "destinationAirportCode": input.destinationAirport.toUpperCase(), "returnAirportCode": "", "departureDate": input.departureDate, "returnDate": input.returnDate || "", "adultsCount": input.passengers.toString(), "seniorCount": "0", "infantsInSeat": "0", "lapChildCount": "0", "tripType": input.tripType === "roundtrip" ? "roundtrip" : "oneWay", "fareType": "PTS", "passengerType": "ADULT", }); await page.goto( `https://www.southwest.com/air/booking/select.html?${params.toString()}`, { waitUntil: "networkidle", timeout: 30000 } ); await Promise.race([ page.waitForSelector('[data-qa="flight-card"]', { timeout: 20000 }), page.waitForSelector(".no-flights-found", { timeout: 20000 }), ]).catch(() => {}); // Extract available flights with points pricing const flights = await page.evaluate(() => { const cards = document.querySelectorAll('[data-qa="flight-card"]'); const results: Array<{ flightNumber: string; departureTime: string; arrivalTime: string; duration: string; stops: string; pointsFares: { wannaGetAway: string | null; wannaGetAwayPlus: string | null; anytime: string | null; businessSelect: string | null; }; }> = []; cards.forEach((card) => { results.push({ flightNumber: card.querySelector('[data-qa="flight-numbers-departure"]')?.textContent?.trim() || "", departureTime: card.querySelector('[data-qa="departure-time"]')?.textContent?.trim() || "", arrivalTime: card.querySelector('[data-qa="arrival-time"]')?.textContent?.trim() || "", duration: card.querySelector('[data-qa="duration"]')?.textContent?.trim() || "", stops: card.querySelector('[data-qa="stops"]')?.textContent?.trim() || "Nonstop", pointsFares: { wannaGetAway: card.querySelector('[data-qa="fare-button--wga"]')?.textContent?.trim() || null, wannaGetAwayPlus: card.querySelector('[data-qa="fare-button--wgaplus"]')?.textContent?.trim() || null, anytime: card.querySelector('[data-qa="fare-button--anytime"]')?.textContent?.trim() || null, businessSelect: card.querySelector('[data-qa="fare-button--business-select"]')?.textContent?.trim() || null, }, }); }); return results; }); // Get current points balance const pointsBalance = await page .locator('[data-qa="rapid-rewards-points"], .points-balance') .textContent() .catch(() => null); return { success: true, origin: input.originAirport.toUpperCase(), destination: input.destinationAirport.toUpperCase(), departureDate: input.departureDate, flightCount: flights.length, flights, currentPointsBalance: pointsBalance?.trim() || "Check your account", message: flights.length > 0 ? `Found ${flights.length} flights. Use select_flight with a fare index to book with points.` : "No flights found. Try different dates or airports.", note: "Points bookings also require taxes/fees (typically $5.60 one-way for domestic). Points bookings are fully refundable — points return to your account upon cancellation.", }; } - src/tools/book-with-points.ts:4-19 (schema)Zod schema 'bookWithPointsSchema' defining input validation for the tool: originAirport (3-char code), destinationAirport (3-char code), departureDate, returnDate (optional), passengers (1-8), tripType (roundtrip/oneway), and optional username/password for authentication.
export const bookWithPointsSchema = z.object({ originAirport: z.string().length(3).describe("Origin airport code"), destinationAirport: z.string().length(3).describe("Destination airport code"), departureDate: z.string().describe("Departure date YYYY-MM-DD"), returnDate: z.string().optional().describe("Return date YYYY-MM-DD (for round trip)"), passengers: z.number().int().min(1).max(8).default(1), tripType: z.enum(["roundtrip", "oneway"]).default("oneway"), username: z .string() .optional() .describe("Rapid Rewards account number (uses SW_USERNAME env var if not set)"), password: z .string() .optional() .describe("Account password (uses SW_PASSWORD env var if not set)"), }); - src/index.ts:163-168 (registration)Tool registration in the TOOLS array defining 'book_with_points' with its description and JSON schema derived from the Zod schema.
{ name: "book_with_points", description: "Search for flights and book using Rapid Rewards points. Shows points pricing for all fare classes. Requires logged-in account.", inputSchema: zodToJsonSchema(bookWithPointsSchema), }, - src/index.ts:232-234 (registration)Switch case handler that invokes bookWithPoints when the tool 'book_with_points' is called, parsing args with the schema.
case "book_with_points": result = await bookWithPoints(bookWithPointsSchema.parse(args)); break; - src/browser.ts:57-127 (helper)Browser automation helper functions used by bookWithPoints: getPage() for getting a Playwright page instance, ensureLoggedIn() for checking/establishing authentication, and login() for authenticating with Southwest Airlines credentials.
export async function getPage(): Promise<Page> { const ctx = await getContext(); const pages = ctx.pages(); if (pages.length > 0) { return pages[0]; } return ctx.newPage(); } export async function saveSession(): Promise<void> { if (!context) return; const dir = path.dirname(SESSION_FILE); if (!fs.existsSync(dir)) { fs.mkdirSync(dir, { recursive: true }); } const storageState = await context.storageState(); fs.writeFileSync(SESSION_FILE, JSON.stringify(storageState, null, 2)); } export async function closeBrowser(): Promise<void> { if (context) { await saveSession(); await context.close(); context = null; } if (browser) { await browser.close(); browser = null; } } export async function ensureLoggedIn(page: Page): Promise<boolean> { // Check if we're already logged in by looking for account indicators await page.goto("https://www.southwest.com/", { waitUntil: "networkidle" }); const loggedIn = await page .locator('[data-qa="header-account-button"]') .isVisible() .catch(() => false); if (!loggedIn) { const username = process.env.SW_USERNAME; const password = process.env.SW_PASSWORD; if (!username || !password) { return false; } await login(page, username, password); return true; } return true; } export async function login( page: Page, username: string, password: string ): Promise<void> { await page.goto("https://www.southwest.com/account/login", { waitUntil: "networkidle", }); await page.fill('[name="userNameOrAccountNumber"]', username); await page.fill('[name="password"]', password); await page.click('[data-qa="login-btn"]'); await page.waitForNavigation({ waitUntil: "networkidle" }).catch(() => {}); await saveSession(); }