letsfg 2026.5.79
pip install letsfg
Released:
Flight search & booking for AI agents. 200+ airline connectors run locally, free. Direct booking URLs via letsfg.co concierge or Developer API.
Navigation
Verified details
These details have been verified by PyPIMaintainers
๐ Avatar for amjaworsky from gravatar.comamjaworsky
Unverified details
These details have not been verified by PyPIProject links
Meta
- License: MIT License (MIT)
- Author: LetsFG
- Tags agent , ai , airline , autonomous , booking , cli , easyjet , flights , low-cost , mcp , ndc , openai , ryanair , scraper , travel , wizzair
- Requires: Python >=3.10
-
Provides-Extra:
all,dev
Classifiers
- Development Status
- Intended Audience
- License
- Programming Language
- Topic
Project description
LetsFG โ Your AI agent just learned to book flights.
200+ airline connectors. Real prices. One function call. Search 400+ airlines at raw airline prices โ $20โ$50 cheaper than Booking.com, Kayak, and other OTAs.
Three ways to use LetsFG
| Local (this SDK) | PFS (Programmatic Flight Search via letsfg.co) | Developer API | |
|---|---|---|---|
| Search cost | Free | Free (Twitter/X token, one-time setup) | Prepaid credits |
| Booking URL | 1% concierge fee (min $3) via letsfg.co | 1% concierge fee (min $3) via letsfg.co | Direct airline URL, no fee |
| Speed | 20โ40 s (fast mode) ยท 1โ15 min (full) | 60โ90 s | 2โ5 s (discover) ยท 60โ90 s (full) |
| Setup | pip install letsfg |
Twitter/X challenge โ letsfg.co/for-agents | letsfg.co/developers |
Want direct airline URLs without any per-booking fee? Use the Developer API โ prepaid credits, results in seconds, no checkout step.
Install
pipinstallletsfg
Search flights immediately โ no account, no API key:
letsfgsearch-localLHRBCN2026-06-15
That fires 200+ airline connectors on your machine. Search is free. Booking links are delivered via the letsfg.co concierge flow (1% fee, min $3) โ see Unlocking offer results below.
# Search using only Brazilian sites letsfgsearchBSBSDU2026-05-24--countryBR # Or by region letsfgsearchGRULIM2026-06-15--regionlatin-america
Short on time? Use --mode fast to search only OTAs + key airlines (~25 connectors, 20-40s instead of 6+ min):
letsfgsearch-localLHRBCN2026-06-15--modefast
Authentication
fromletsfgimport LetsFG # Register (one-time, no auth needed) creds = LetsFG.register("my-agent", "agent@example.com") print(creds["api_key"]) # "trav_xxxxx..." โ save this # Option A: Pass API key directly bt = LetsFG(api_key="trav_...") # Option B: Set LETSFG_API_KEY env var, then: bt = LetsFG() # Setup payment (required before unlock) โ two options: # Option 1: Stripe test token (for development) bt.setup_payment(token="tok_visa") # Option 2: Stripe PaymentMethod ID (from Stripe.js or Elements) bt.setup_payment(payment_method_id="pm_1234567890")
The API accepts only Stripe-generated tokens or
payment_method_idvalues โ raw card numbers are not accepted.
The API key is sent as X-API-Key header on every request. The SDK handles this automatically.
Verify Your Credentials
# Check that auth + payment are working profile = bt.me() print(f"Agent: {profile['agent_name']}") print(f"Payment: {profile.get('payment_status','not set up')}") print(f"Searches: {profile.get('search_count',0)}")
Auth Failure Recovery
fromletsfgimport LetsFG, AuthenticationError try: bt = LetsFG(api_key="trav_...") flights = bt.search("LHR", "JFK", "2026-04-15") except AuthenticationError: # Key invalid or expired โ re-register to get a new one creds = LetsFG.register("my-agent", "agent@example.com") bt = LetsFG(api_key=creds["api_key"]) bt.setup_payment(token="tok_visa") # Re-attach payment on new key flights = bt.search("LHR", "JFK", "2026-04-15")
Quick Start (Python)
fromletsfgimport LetsFG bt = LetsFG(api_key="trav_...") # Search flights โ FREE flights = bt.search("GDN", "BER", "2026-03-03") print(f"{flights.total_results} offers, cheapest: {flights.cheapest.summary()}") # Unlock booking link (1% fee, min $3, charged via letsfg.co) unlock = bt.unlock(flights.cheapest.id) print(f"Confirmed price: {unlock.confirmed_currency}{unlock.confirmed_price}") # Book โ ticket price charged via Stripe (zero markup) booking = bt.book( offer_id=flights.cheapest.id, passengers=[{ "id": flights.passenger_ids[0], "given_name": "John", "family_name": "Doe", "born_on": "1990-01-15", "gender": "m", "title": "mr", "email": "john@example.com", }], contact_email="john@example.com" ) print(f"PNR: {booking.booking_reference}")
Multi-Passenger Search
# 2 adults + 1 child, round-trip, premium economy flights = bt.search( "LHR", "JFK", "2026-06-01", return_date="2026-06-15", adults=2, children=1, cabin_class="W", # W=premium, M=economy, C=business, F=first sort="price", ) # passenger_ids will be ["pas_0", "pas_1", "pas_2"] print(f"Passenger IDs: {flights.passenger_ids}") # Book with details for EACH passenger booking = bt.book( offer_id=unlocked.offer_id, passengers=[ {"id": "pas_0", "given_name": "John", "family_name": "Doe", "born_on": "1990-01-15", "gender": "m", "title": "mr"}, {"id": "pas_1", "given_name": "Jane", "family_name": "Doe", "born_on": "1992-03-20", "gender": "f", "title": "ms"}, {"id": "pas_2", "given_name": "Tom", "family_name": "Doe", "born_on": "2018-05-10", "gender": "m", "title": "mr"}, ], contact_email="john@example.com", )
Resolve Locations
Always resolve city names to IATA codes before searching:
locations = bt.resolve_location("New York") # [{"iata_code": "JFK", "name": "John F. Kennedy", "type": "airport", "city": "New York"}, ...] # Use in search flights = bt.search(locations[0]["iata_code"], "LAX", "2026-04-15")
Working with Search Results
flights = bt.search("LON", "BCN", "2026-04-01", return_date="2026-04-08", limit=50) # Iterate all offers for offer in flights.offers: print(f"{offer.owner_airline}: {offer.currency}{offer.price}") print(f" Route: {offer.outbound.route_str}") print(f" Duration: {offer.outbound.total_duration_seconds//3600}h") print(f" Stops: {offer.outbound.stopovers}") print(f" Refundable: {offer.conditions.get('refund_before_departure','unknown')}") print(f" Changeable: {offer.conditions.get('change_before_departure','unknown')}") # Filter: direct flights only direct = [o for o in flights.offers if o.outbound.stopovers == 0] # Filter: specific airline ba = [o for o in flights.offers if "British Airways" in o.airlines] # Filter: refundable only refundable = [o for o in flights.offers if o.conditions.get("refund_before_departure") == "allowed"] # Sort by duration by_duration = sorted(flights.offers, key=lambda o: o.outbound.total_duration_seconds) # Cheapest offer print(f"Best: {flights.cheapest.price}{flights.cheapest.currency}")
Error Handling
fromletsfgimport ( LetsFG, LetsFGError, AuthenticationError, PaymentRequiredError, OfferExpiredError, ) bt = LetsFG(api_key="trav_...") # Handle invalid locations try: flights = bt.search("INVALID", "JFK", "2026-04-15") except LetsFGError as e: if e.status_code == 422: # Resolve the location first locations = bt.resolve_location("London") flights = bt.search(locations[0]["iata_code"], "JFK", "2026-04-15") # Handle payment and expiry try: unlocked = bt.unlock(offer_id) except PaymentRequiredError: print("Run bt.setup_payment() first") except OfferExpiredError: print("Offer expired โ search again for fresh results") # Handle booking failures try: booking = bt.book(offer_id=unlocked.offer_id, passengers=[...], contact_email="...") except OfferExpiredError: print("30-minute window expired โ search and unlock again") except AuthenticationError: print("Invalid API key") except LetsFGError as e: print(f"API error ({e.status_code}): {e.message}")
| Exception | HTTP Code | Cause |
|---|---|---|
AuthenticationError |
401 | Missing or invalid API key |
PaymentRequiredError |
402 | No payment method (call setup_payment()) |
OfferExpiredError |
410 | Offer no longer available |
LetsFGError |
any | Base class for all API errors |
Timeout and Retry Pattern
Airline APIs can be slow (2โ15s for search). Use retry with backoff for production:
importtime fromletsfgimport LetsFG, LetsFGError bt = LetsFG() defsearch_with_retry(origin, dest, date, max_retries=3): """Retry with exponential backoff on rate limit or timeout.""" for attempt in range(max_retries): try: return bt.search(origin, dest, date) except LetsFGError as e: if "429" in str(e) or "rate limit" in str(e).lower(): wait = 2 ** attempt # 1s, 2s, 4s print(f"Rate limited, waiting {wait}s...") time.sleep(wait) elif "timeout" in str(e).lower() or "504" in str(e): print(f"Timeout, retrying ({attempt+1}/{max_retries})...") time.sleep(1) else: raise raise LetsFGError("Max retries exceeded")
Rate Limits
| Endpoint | Rate Limit | Typical Latency |
|---|---|---|
| Search | No hard limit (billing is the natural governor) | 60โ90 s |
| Resolve location | 120 req/min | < 1 s |
| Unlock | 20 req/min | 2โ5 s |
| Book | 10 req/min | 3โ10 s |
Minimizing Unlock Costs
Searching is free and unlimited. Unlock via the Developer API is also free; via the local/website path the concierge fee (1% of ticket price, min $3) applies once per offer. Strategy:
# Search multiple dates (free) โ compare before unlocking dates = ["2026-04-01", "2026-04-02", "2026-04-03"] best = None for date in dates: result = bt.search("LON", "BCN", date) if result.offers and (best is None or result.cheapest.price < best[1].price): best = (date, result.cheapest) # Unlock only the winner (1% fee, min $3) if best: unlocked = bt.unlock(best[1].id) # Book within 30 minutes (ticket price only) booking = bt.book(offer_id=unlocked.offer_id, passengers=[...], contact_email="...")
Local Search (No API Key)
Note: Local search results return masked booking links by default. Each offer includes
offer_refandpayment_tokenfields. To get a direct airline booking URL, you have two options: use the concierge unlock flow (1% fee, min $3 โ no API key needed) or sign up for the Developer API at letsfg.co/developers for fee-free direct links.
The SDK includes 200 connectors for airlines that run directly on your machine. No API key, no backend, completely free:
fromletsfg.localimport search_local # Fires all relevant airline connectors โ Ryanair, Wizz Air, EasyJet, etc. result = await search_local("GDN", "BCN", "2026-06-15") print(f"{result['total_results']} offers from local connectors") # Limit browser concurrency for constrained environments result = await search_local("GDN", "BCN", "2026-06-15", max_browsers=4)
The full search (bt.search()) runs both local connectors and cloud providers simultaneously and merges results.
Unlocking offer results
Local search results include offer_ref and payment_token on each offer. Use these to get the direct airline booking URL via the concierge flow (no API key required):
importhttpx importtime # 1. Pick an offer from local search results offer = result["offers"][0] offer_id = offer["id"] offer_ref = offer["offer_ref"] payment_token = offer["payment_token"] price = offer["price"] # ticket price as string, e.g. "312.50" currency = offer["currency"] # e.g. "USD" # 2. Initiate checkout โ returns a Stripe checkout URL # Fee = max(price ร 1%, $3.00). No API key needed. resp = httpx.post( "https://letsfg.co/api/developers/checkout", json={ "offer_id": offer_id, "offer_ref": offer_ref, "payment_token": payment_token, "currency": currency, "price": price, }, ) resp.raise_for_status() checkout = resp.json() checkout_url = checkout["checkout_url"] # 3. Present the checkout URL to the user (or open it in a browser) print(f"Pay here: {checkout_url}") # 4. Poll until payment is confirmed booking_url = None while not booking_url: time.sleep(3) verify = httpx.get( "https://letsfg.co/api/developers/payment-verify", params={"token": payment_token}, ) data = verify.json() if data.get("verified"): booking_url = data["booking_url"] # 5. booking_url is the direct airline link โ no further fees print(f"Book here: {booking_url}")
To skip the per-booking fee entirely, use the Developer API โ it returns direct airline booking URLs on every search result.
Quick Start (CLI)
exportLETSFG_API_KEY=trav_... # Search (1 adult, one-way, economy โ defaults) letsfgsearchGDNBER2026-03-03--sortprice # Multi-passenger round trip letsfgsearchLONBCN2026-04-01--return2026-04-08--adults2--children1--cabinM # Business class, direct flights only letsfgsearchJFKLHR2026-05-01--adults3--cabinC--max-stops0 # Machine-readable output (for agents) letsfgsearchLONBCN2026-04-01--json # Unlock letsfgunlockoff_xxx # Book letsfgbookoff_xxx\ --passenger'{"id":"pas_xxx","given_name":"John","family_name":"Doe","born_on":"1990-01-15","gender":"m","title":"mr","email":"john@example.com"}'\ --emailjohn@example.com # Resolve location letsfglocations"Berlin"
Search Flags
| Flag | Short | Default | Description |
|---|---|---|---|
--return |
-r |
(one-way) | Return date YYYY-MM-DD |
--adults |
-a |
1 |
Adults (1โ9) |
--children |
0 |
Children 2โ11 years | |
--cabin |
-c |
(any) | M economy, W premium, C business, F first |
--max-stops |
-s |
2 |
Max stopovers (0โ4) |
--currency |
EUR |
Currency code | |
--limit |
-l |
20 |
Max results (1โ100) |
--sort |
price |
price or duration |
|
--json |
-j |
Raw JSON output |
All CLI Commands
| Command | Description | Cost |
|---|---|---|
search |
Search flights between any two airports | FREE |
locations |
Resolve city name to IATA codes | FREE |
unlock |
Unlock offer (confirms price, reserves 30min) | FREE |
book |
Book flight (creates real airline PNR) | Ticket price |
search-local |
Search 200 local airline connectors | FREE |
system-info |
Show system resources & concurrency tier | FREE |
register |
Register new agent, get API key | FREE |
setup-payment |
Attach payment card (payment token) | FREE |
me |
Show agent profile and usage stats | FREE |
Every command supports --json for machine-readable output.
Environment Variables
| Variable | Description |
|---|---|
LETSFG_API_KEY |
Your agent API key |
LETSFG_BASE_URL |
API URL (default: https://letsfg.co/developers) |
LETSFG_MAX_BROWSERS |
Max concurrent browser instances (1โ32). Auto-detected from RAM if not set. |
Performance Tuning
LetsFG auto-detects your system's available RAM and scales browser concurrency:
| Available RAM | Tier | Max Browsers |
|---|---|---|
| < 2 GB | Minimal | 2 |
| 2โ4 GB | Low | 3 |
| 4โ8 GB | Moderate | 5 |
| 8โ16 GB | Standard | 8 |
| 16โ32 GB | High | 12 |
| 32+ GB | Maximum | 16 |
fromletsfgimport get_system_profile, configure_max_browsers # Check system resources and recommended concurrency profile = get_system_profile() print(f"RAM: {profile['ram_available_gb']:.1f} GB available") print(f"Tier: {profile['tier']} โ {profile['recommended_max_browsers']} browsers") # Override auto-detection configure_max_browsers(4) # clamps to 1โ32
# Via CLI letsfgsystem-info letsfgsystem-info--json# machine-readable # Override via env var exportLETSFG_MAX_BROWSERS=4 letsfgsearch-localLHRBCN2026-04-15 # Override via CLI flag letsfgsearch-localLHRBCN2026-04-15--max-browsers4
Priority: env var > explicit config/flag > auto-detect.
How It Works
- Search โ Free. 200+ connectors fire on your machine and return real-time offers.
- Unlock booking URL โ Pay the concierge fee (1% of ticket price, min $3) via letsfg.co to receive the direct airline booking link. Or use the Developer API (prepaid credits) for fee-free direct links.
- Book โ Open the direct airline URL and complete the booking on the airline's own site.
Prices are cheaper because we connect directly to airlines โ no OTA markup.
Also Available As
- MCP Server:
npx letsfg-mcpโ npm - JS/TS SDK:
npm install letsfgโ npm - Try without installing: letsfg.co โ search instantly in your browser
- GitHub: LetsFG/LetsFG
โญ Star the repo โ we appreciate the support.
License
MIT
Project details
Verified details
These details have been verified by PyPIMaintainers
๐ Avatar for amjaworsky from gravatar.comamjaworsky
Unverified details
These details have not been verified by PyPIProject links
Meta
- License: MIT License (MIT)
- Author: LetsFG
- Tags agent , ai , airline , autonomous , booking , cli , easyjet , flights , low-cost , mcp , ndc , openai , ryanair , scraper , travel , wizzair
- Requires: Python >=3.10
-
Provides-Extra:
all,dev
Classifiers
- Development Status
- Intended Audience
- License
- Programming Language
- Topic
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 letsfg-2026.5.79.tar.gz.
File metadata
- Download URL: letsfg-2026.5.79.tar.gz
- Upload date:
- Size: 1.6 MB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.5
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
62be0016e3af0922ec1ecc97e12fdb114d71af9f474b7edda00f0df0fd99c9ff
|
|
| MD5 |
db866f35553993b6c5f5276b8944d552
|
|
| BLAKE2b-256 |
3474b10bcf6f960a792559df7f1a408a9d62a5ae90b50169bd2de7cc7a79e09a
|
File details
Details for the file letsfg-2026.5.79-py3-none-any.whl.
File metadata
- Download URL: letsfg-2026.5.79-py3-none-any.whl
- Upload date:
- Size: 1.9 MB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.5
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
d5b0f5ba3b795a86c9c54238bc1e77710ffef8653d0b5d75c0f4a411d1b478e5
|
|
| MD5 |
b657769fc766c0efd5c0eaf9f93e5cd5
|
|
| BLAKE2b-256 |
71f5c177b95a5e1534fbcfd9552fa97d056b16a9fab9ae40dc2c5f07cfd4b4f7
|
