letsfg 1.0.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (103) hide show
  1. letsfg-1.0.0/.gitignore +73 -0
  2. letsfg-1.0.0/LICENSE +21 -0
  3. letsfg-1.0.0/PKG-INFO +471 -0
  4. letsfg-1.0.0/README.md +427 -0
  5. letsfg-1.0.0/letsfg/CONNECTOR_STATUS.md +216 -0
  6. letsfg-1.0.0/letsfg/__init__.py +76 -0
  7. letsfg-1.0.0/letsfg/__main__.py +4 -0
  8. letsfg-1.0.0/letsfg/cli.py +588 -0
  9. letsfg-1.0.0/letsfg/client.py +740 -0
  10. letsfg-1.0.0/letsfg/connectors/__init__.py +15 -0
  11. letsfg-1.0.0/letsfg/connectors/_connector_template.py +61 -0
  12. letsfg-1.0.0/letsfg/connectors/airarabia.py +344 -0
  13. letsfg-1.0.0/letsfg/connectors/airasia.py +447 -0
  14. letsfg-1.0.0/letsfg/connectors/airbaltic.py +225 -0
  15. letsfg-1.0.0/letsfg/connectors/airindiaexpress.py +223 -0
  16. letsfg-1.0.0/letsfg/connectors/airline_routes.py +631 -0
  17. letsfg-1.0.0/letsfg/connectors/airpeace.py +301 -0
  18. letsfg-1.0.0/letsfg/connectors/akasa.py +392 -0
  19. letsfg-1.0.0/letsfg/connectors/alaska.py +321 -0
  20. letsfg-1.0.0/letsfg/connectors/allegiant.py +562 -0
  21. letsfg-1.0.0/letsfg/connectors/american.py +807 -0
  22. letsfg-1.0.0/letsfg/connectors/avelo.py +356 -0
  23. letsfg-1.0.0/letsfg/connectors/avianca.py +591 -0
  24. letsfg-1.0.0/letsfg/connectors/azul.py +463 -0
  25. letsfg-1.0.0/letsfg/connectors/batikair.py +819 -0
  26. letsfg-1.0.0/letsfg/connectors/biman.py +393 -0
  27. letsfg-1.0.0/letsfg/connectors/booking_base.py +382 -0
  28. letsfg-1.0.0/letsfg/connectors/breeze.py +378 -0
  29. letsfg-1.0.0/letsfg/connectors/browser.py +490 -0
  30. letsfg-1.0.0/letsfg/connectors/cathay.py +257 -0
  31. letsfg-1.0.0/letsfg/connectors/cebupacific.py +420 -0
  32. letsfg-1.0.0/letsfg/connectors/checkout_engine.py +1738 -0
  33. letsfg-1.0.0/letsfg/connectors/combo_engine.py +175 -0
  34. letsfg-1.0.0/letsfg/connectors/condor.py +860 -0
  35. letsfg-1.0.0/letsfg/connectors/copa.py +751 -0
  36. letsfg-1.0.0/letsfg/connectors/currency.py +88 -0
  37. letsfg-1.0.0/letsfg/connectors/delta.py +978 -0
  38. letsfg-1.0.0/letsfg/connectors/easyjet.py +1027 -0
  39. letsfg-1.0.0/letsfg/connectors/emirates.py +816 -0
  40. letsfg-1.0.0/letsfg/connectors/engine.py +961 -0
  41. letsfg-1.0.0/letsfg/connectors/etihad.py +552 -0
  42. letsfg-1.0.0/letsfg/connectors/eurowings.py +974 -0
  43. letsfg-1.0.0/letsfg/connectors/flair.py +270 -0
  44. letsfg-1.0.0/letsfg/connectors/flybondi.py +476 -0
  45. letsfg-1.0.0/letsfg/connectors/flydubai.py +810 -0
  46. letsfg-1.0.0/letsfg/connectors/flynas.py +607 -0
  47. letsfg-1.0.0/letsfg/connectors/flysafair.py +345 -0
  48. letsfg-1.0.0/letsfg/connectors/frontier.py +404 -0
  49. letsfg-1.0.0/letsfg/connectors/gol.py +451 -0
  50. letsfg-1.0.0/letsfg/connectors/hawaiian.py +324 -0
  51. letsfg-1.0.0/letsfg/connectors/indigo.py +611 -0
  52. letsfg-1.0.0/letsfg/connectors/jazeera.py +482 -0
  53. letsfg-1.0.0/letsfg/connectors/jejuair.py +244 -0
  54. letsfg-1.0.0/letsfg/connectors/jet2.py +1096 -0
  55. letsfg-1.0.0/letsfg/connectors/jetblue.py +485 -0
  56. letsfg-1.0.0/letsfg/connectors/jetsmart.py +875 -0
  57. letsfg-1.0.0/letsfg/connectors/jetstar.py +952 -0
  58. letsfg-1.0.0/letsfg/connectors/kiwi.py +458 -0
  59. letsfg-1.0.0/letsfg/connectors/korean.py +446 -0
  60. letsfg-1.0.0/letsfg/connectors/latam.py +637 -0
  61. letsfg-1.0.0/letsfg/connectors/lot.py +549 -0
  62. letsfg-1.0.0/letsfg/connectors/luckyair.py +470 -0
  63. letsfg-1.0.0/letsfg/connectors/malaysia.py +288 -0
  64. letsfg-1.0.0/letsfg/connectors/nh.py +520 -0
  65. letsfg-1.0.0/letsfg/connectors/nineair.py +508 -0
  66. letsfg-1.0.0/letsfg/connectors/nokair.py +343 -0
  67. letsfg-1.0.0/letsfg/connectors/norwegian.py +669 -0
  68. letsfg-1.0.0/letsfg/connectors/peach.py +414 -0
  69. letsfg-1.0.0/letsfg/connectors/pegasus.py +587 -0
  70. letsfg-1.0.0/letsfg/connectors/play.py +39 -0
  71. letsfg-1.0.0/letsfg/connectors/porter.py +372 -0
  72. letsfg-1.0.0/letsfg/connectors/qatar.py +479 -0
  73. letsfg-1.0.0/letsfg/connectors/ryanair.py +659 -0
  74. letsfg-1.0.0/letsfg/connectors/salamair.py +356 -0
  75. letsfg-1.0.0/letsfg/connectors/scoot.py +1112 -0
  76. letsfg-1.0.0/letsfg/connectors/singapore.py +759 -0
  77. letsfg-1.0.0/letsfg/connectors/smartwings.py +584 -0
  78. letsfg-1.0.0/letsfg/connectors/southwest.py +971 -0
  79. letsfg-1.0.0/letsfg/connectors/spicejet.py +420 -0
  80. letsfg-1.0.0/letsfg/connectors/spirit.py +813 -0
  81. letsfg-1.0.0/letsfg/connectors/spring.py +294 -0
  82. letsfg-1.0.0/letsfg/connectors/suncountry.py +286 -0
  83. letsfg-1.0.0/letsfg/connectors/sunexpress.py +602 -0
  84. letsfg-1.0.0/letsfg/connectors/thai.py +309 -0
  85. letsfg-1.0.0/letsfg/connectors/transavia.py +1077 -0
  86. letsfg-1.0.0/letsfg/connectors/turkish.py +571 -0
  87. letsfg-1.0.0/letsfg/connectors/twayair.py +593 -0
  88. letsfg-1.0.0/letsfg/connectors/united.py +415 -0
  89. letsfg-1.0.0/letsfg/connectors/usbangla.py +461 -0
  90. letsfg-1.0.0/letsfg/connectors/vietjet.py +852 -0
  91. letsfg-1.0.0/letsfg/connectors/vivaaerobus.py +231 -0
  92. letsfg-1.0.0/letsfg/connectors/volaris.py +651 -0
  93. letsfg-1.0.0/letsfg/connectors/volotea.py +1048 -0
  94. letsfg-1.0.0/letsfg/connectors/vueling.py +719 -0
  95. letsfg-1.0.0/letsfg/connectors/westjet.py +445 -0
  96. letsfg-1.0.0/letsfg/connectors/wizzair.py +961 -0
  97. letsfg-1.0.0/letsfg/connectors/zipair.py +390 -0
  98. letsfg-1.0.0/letsfg/local.py +151 -0
  99. letsfg-1.0.0/letsfg/models/__init__.py +342 -0
  100. letsfg-1.0.0/letsfg/models/flights.py +153 -0
  101. letsfg-1.0.0/letsfg/models.py +328 -0
  102. letsfg-1.0.0/letsfg/system_info.py +181 -0
  103. letsfg-1.0.0/pyproject.toml +69 -0
@@ -0,0 +1,73 @@
1
+ node_modules/
2
+ dist/
3
+ __pycache__/
4
+ *.pyc
5
+ *.egg-info/
6
+ build/
7
+ .env
8
+ .env.local
9
+ .DS_Store
10
+ Thumbs.db
11
+ .vscode/
12
+ .idea/
13
+ site/
14
+
15
+ # Chrome profile directories (probe/test artifacts)
16
+ *_chrome_data/
17
+ *_chrome_probe*/
18
+ *_chrome_profile/
19
+ *_chrome_headed/
20
+ *_chrome_clean/
21
+ *_chrome_dom/
22
+ *_chrome_fresh/
23
+ *_chrome_js/
24
+ *_chrome_cdp*/
25
+ .chrome-*/
26
+ .azul_*/
27
+ .indigo_*/
28
+ .gol_*/
29
+ .jetstar_*/
30
+ .norwegian_*/
31
+ .peach_*/
32
+ .smartwings_*/
33
+ .transavia_*/
34
+ .volotea_*/
35
+ *_persistent_ctx/
36
+ *_cdp_test/
37
+ *_subprocess_chrome/
38
+ *_default_page_chrome/
39
+
40
+ # Probe/test/debug scripts (prefixed with _)
41
+ _*_probe*.py
42
+ _*_test*.py
43
+ _*_debug*.py
44
+ _probe_*.py
45
+ _test_*.py
46
+
47
+ # Probe output artifacts
48
+ _*_output.txt
49
+ _*_result.png
50
+ _*_state.png
51
+ _*_form.png
52
+ _*_home.png
53
+ _*_before.png
54
+ _*_debug*.png
55
+ _*_approach*.png
56
+ _*_date*.png
57
+ _*_success*.json
58
+ _*_data*.json
59
+ _*_captured.json
60
+ _*_api.json
61
+ _*_response.json
62
+ _*_format.json
63
+ _*_dump.json
64
+ _*_full.txt
65
+ _*_sample.json
66
+ *.log
67
+
68
+ # Backup files
69
+ *.bak
70
+
71
+ # Agent communication (runtime files)
72
+ .github/agent-registry.json
73
+ .github/agent-pings.json
letsfg-1.0.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 BoostedTravel
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
letsfg-1.0.0/PKG-INFO ADDED
@@ -0,0 +1,471 @@
1
+ Metadata-Version: 2.4
2
+ Name: letsfg
3
+ Version: 1.0.0
4
+ Summary: Agent-native flight search & booking. 400+ airlines, 75 direct airline connectors run locally. Built for autonomous AI agents.
5
+ Project-URL: Homepage, https://letsfg.co
6
+ Project-URL: Documentation, https://api.letsfg.co/docs
7
+ Project-URL: Repository, https://github.com/LetsFG/LetsFG
8
+ Project-URL: Issues, https://github.com/LetsFG/LetsFG/issues
9
+ Project-URL: API Reference, https://api.letsfg.co/redoc
10
+ Author: LetsFG
11
+ License: MIT
12
+ License-File: LICENSE
13
+ Keywords: agent,ai,airline,autonomous,booking,cli,easyjet,flights,low-cost,mcp,ndc,openai,ryanair,scraper,travel,wizzair
14
+ Classifier: Development Status :: 4 - Beta
15
+ Classifier: Intended Audience :: Developers
16
+ Classifier: License :: OSI Approved :: MIT License
17
+ Classifier: Programming Language :: Python :: 3
18
+ Classifier: Programming Language :: Python :: 3.10
19
+ Classifier: Programming Language :: Python :: 3.11
20
+ Classifier: Programming Language :: Python :: 3.12
21
+ Classifier: Programming Language :: Python :: 3.13
22
+ Classifier: Topic :: Internet :: WWW/HTTP
23
+ Classifier: Topic :: Software Development :: Libraries
24
+ Requires-Python: >=3.10
25
+ Requires-Dist: aiohttp>=3.9.0
26
+ Requires-Dist: beautifulsoup4>=4.12.0
27
+ Requires-Dist: curl-cffi>=0.7.0
28
+ Requires-Dist: httpx>=0.25.0
29
+ Requires-Dist: lxml>=5.0.0
30
+ Requires-Dist: playwright-stealth>=1.0.6
31
+ Requires-Dist: playwright>=1.40.0
32
+ Requires-Dist: pydantic>=2.0
33
+ Provides-Extra: all
34
+ Requires-Dist: click<8.2,>=8.0; extra == 'all'
35
+ Requires-Dist: nodriver>=0.38; extra == 'all'
36
+ Requires-Dist: patchright>=1.0; extra == 'all'
37
+ Requires-Dist: rich>=13.0.0; extra == 'all'
38
+ Requires-Dist: typer>=0.9.0; extra == 'all'
39
+ Provides-Extra: cli
40
+ Requires-Dist: click<8.2,>=8.0; extra == 'cli'
41
+ Requires-Dist: rich>=13.0.0; extra == 'cli'
42
+ Requires-Dist: typer>=0.9.0; extra == 'cli'
43
+ Description-Content-Type: text/markdown
44
+
45
+ # LetsFG — Agent-Native Flight Search & Booking
46
+
47
+ <!-- mcp-name: io.github.Efistoffeles/letsfg -->
48
+
49
+ Search 400+ airlines at raw airline prices — **$20-50 cheaper** than Booking.com, Kayak, and other OTAs. 75 direct airline connectors run locally, plus GDS/NDC providers via cloud API. Built for autonomous AI agents — works with OpenClaw, Perplexity Computer, Claude, Cursor, Windsurf, and any MCP-compatible client.
50
+
51
+ > 🎥 **[Watch the demo](https://github.com/LetsFG/LetsFG#demo-boostedtravel-vs-default-agent-search)** — side-by-side comparison of default agent search vs LetsFG CLI.
52
+
53
+ ## Install
54
+
55
+ ```bash
56
+ pip install letsfg # SDK + 75 airline connectors
57
+ pip install letsfg[cli] # SDK + CLI (adds typer, rich)
58
+ ```
59
+
60
+ **Dependencies:** `pydantic`, `httpx`, `playwright`, `beautifulsoup4`, `lxml`. The SDK client itself uses stdlib `urllib` for API calls (zero deps), while the local connectors need the above for browser automation.
61
+
62
+ ## Authentication
63
+
64
+ ```python
65
+ from letsfg import BoostedTravel
66
+
67
+ # Register (one-time, no auth needed)
68
+ creds = LetsFG.register("my-agent", "agent@example.com")
69
+ print(creds["api_key"]) # "trav_xxxxx..." — save this
70
+
71
+ # Option A: Pass API key directly
72
+ bt = LetsFG(api_key="trav_...")
73
+
74
+ # Option B: Set LETSFG_API_KEY env var, then:
75
+ bt = LetsFG()
76
+
77
+ # Setup payment (required before unlock) — three options:
78
+
79
+ # Option 1: Stripe test token (for development)
80
+ bt.setup_payment(token="tok_visa")
81
+
82
+ # Option 2: Stripe PaymentMethod ID (from Stripe.js or Elements)
83
+ bt.setup_payment(payment_method_id="pm_1234567890")
84
+
85
+ # Option 3: Raw card details (requires PCI-compliant Stripe account)
86
+ bt.setup_payment(card_number="4242424242424242", exp_month=12, exp_year=2027, cvc="123")
87
+ ```
88
+
89
+ The API key is sent as `X-API-Key` header on every request. The SDK handles this automatically.
90
+
91
+ ### Verify Your Credentials
92
+
93
+ ```python
94
+ # Check that auth + payment are working
95
+ profile = bt.me()
96
+ print(f"Agent: {profile['agent_name']}")
97
+ print(f"Payment: {profile.get('payment_status', 'not set up')}")
98
+ print(f"Searches: {profile.get('search_count', 0)}")
99
+ ```
100
+
101
+ ### Auth Failure Recovery
102
+
103
+ ```python
104
+ from letsfg import LetsFG, AuthenticationError
105
+
106
+ try:
107
+ bt = LetsFG(api_key="trav_...")
108
+ flights = bt.search("LHR", "JFK", "2026-04-15")
109
+ except AuthenticationError:
110
+ # Key invalid or expired — re-register to get a new one
111
+ creds = LetsFG.register("my-agent", "agent@example.com")
112
+ bt = LetsFG(api_key=creds["api_key"])
113
+ bt.setup_payment(token="tok_visa") # Re-attach payment on new key
114
+ flights = bt.search("LHR", "JFK", "2026-04-15")
115
+ ```
116
+
117
+ ## Quick Start (Python)
118
+
119
+ ```python
120
+ from letsfg import BoostedTravel
121
+
122
+ bt = LetsFG(api_key="trav_...")
123
+
124
+ # Search flights — FREE
125
+ flights = bt.search("GDN", "BER", "2026-03-03")
126
+ print(f"{flights.total_results} offers, cheapest: {flights.cheapest.summary()}")
127
+
128
+ # Unlock — $1
129
+ unlock = bt.unlock(flights.cheapest.id)
130
+ print(f"Confirmed price: {unlock.confirmed_currency} {unlock.confirmed_price}")
131
+
132
+ # Book — FREE after unlock
133
+ booking = bt.book(
134
+ offer_id=flights.cheapest.id,
135
+ passengers=[{
136
+ "id": flights.passenger_ids[0],
137
+ "given_name": "John",
138
+ "family_name": "Doe",
139
+ "born_on": "1990-01-15",
140
+ "gender": "m",
141
+ "title": "mr",
142
+ "email": "john@example.com",
143
+ }],
144
+ contact_email="john@example.com"
145
+ )
146
+ print(f"PNR: {booking.booking_reference}")
147
+ ```
148
+
149
+ ## Multi-Passenger Search
150
+
151
+ ```python
152
+ # 2 adults + 1 child, round-trip, premium economy
153
+ flights = bt.search(
154
+ "LHR", "JFK", "2026-06-01",
155
+ return_date="2026-06-15",
156
+ adults=2,
157
+ children=1,
158
+ cabin_class="W", # W=premium, M=economy, C=business, F=first
159
+ sort="price",
160
+ )
161
+
162
+ # passenger_ids will be ["pas_0", "pas_1", "pas_2"]
163
+ print(f"Passenger IDs: {flights.passenger_ids}")
164
+
165
+ # Book with details for EACH passenger
166
+ booking = bt.book(
167
+ offer_id=unlocked.offer_id,
168
+ passengers=[
169
+ {"id": "pas_0", "given_name": "John", "family_name": "Doe", "born_on": "1990-01-15", "gender": "m", "title": "mr"},
170
+ {"id": "pas_1", "given_name": "Jane", "family_name": "Doe", "born_on": "1992-03-20", "gender": "f", "title": "ms"},
171
+ {"id": "pas_2", "given_name": "Tom", "family_name": "Doe", "born_on": "2018-05-10", "gender": "m", "title": "mr"},
172
+ ],
173
+ contact_email="john@example.com",
174
+ )
175
+ ```
176
+
177
+ ## Resolve Locations
178
+
179
+ Always resolve city names to IATA codes before searching:
180
+
181
+ ```python
182
+ locations = bt.resolve_location("New York")
183
+ # [{"iata_code": "JFK", "name": "John F. Kennedy", "type": "airport", "city": "New York"}, ...]
184
+
185
+ # Use in search
186
+ flights = bt.search(locations[0]["iata_code"], "LAX", "2026-04-15")
187
+ ```
188
+
189
+ ## Working with Search Results
190
+
191
+ ```python
192
+ flights = bt.search("LON", "BCN", "2026-04-01", return_date="2026-04-08", limit=50)
193
+
194
+ # Iterate all offers
195
+ for offer in flights.offers:
196
+ print(f"{offer.owner_airline}: {offer.currency} {offer.price}")
197
+ print(f" Route: {offer.outbound.route_str}")
198
+ print(f" Duration: {offer.outbound.total_duration_seconds // 3600}h")
199
+ print(f" Stops: {offer.outbound.stopovers}")
200
+ print(f" Refundable: {offer.conditions.get('refund_before_departure', 'unknown')}")
201
+ print(f" Changeable: {offer.conditions.get('change_before_departure', 'unknown')}")
202
+
203
+ # Filter: direct flights only
204
+ direct = [o for o in flights.offers if o.outbound.stopovers == 0]
205
+
206
+ # Filter: specific airline
207
+ ba = [o for o in flights.offers if "British Airways" in o.airlines]
208
+
209
+ # Filter: refundable only
210
+ refundable = [o for o in flights.offers if o.conditions.get("refund_before_departure") == "allowed"]
211
+
212
+ # Sort by duration
213
+ by_duration = sorted(flights.offers, key=lambda o: o.outbound.total_duration_seconds)
214
+
215
+ # Cheapest offer
216
+ print(f"Best: {flights.cheapest.price} {flights.cheapest.currency}")
217
+ ```
218
+
219
+ ## Error Handling
220
+
221
+ ```python
222
+ from letsfg import (
223
+ BoostedTravel, LetsFGError,
224
+ AuthenticationError, PaymentRequiredError, OfferExpiredError,
225
+ )
226
+
227
+ bt = LetsFG(api_key="trav_...")
228
+
229
+ # Handle invalid locations
230
+ try:
231
+ flights = bt.search("INVALID", "JFK", "2026-04-15")
232
+ except LetsFGError as e:
233
+ if e.status_code == 422:
234
+ # Resolve the location first
235
+ locations = bt.resolve_location("London")
236
+ flights = bt.search(locations[0]["iata_code"], "JFK", "2026-04-15")
237
+
238
+ # Handle payment and expiry
239
+ try:
240
+ unlocked = bt.unlock(offer_id)
241
+ except PaymentRequiredError:
242
+ print("Run bt.setup_payment() first")
243
+ except OfferExpiredError:
244
+ print("Offer expired — search again for fresh results")
245
+
246
+ # Handle booking failures
247
+ try:
248
+ booking = bt.book(offer_id=unlocked.offer_id, passengers=[...], contact_email="...")
249
+ except OfferExpiredError:
250
+ print("30-minute window expired — search and unlock again")
251
+ except AuthenticationError:
252
+ print("Invalid API key")
253
+ except LetsFGError as e:
254
+ print(f"API error ({e.status_code}): {e.message}")
255
+ ```
256
+
257
+ | Exception | HTTP Code | Cause |
258
+ |-----------|-----------|-------|
259
+ | `AuthenticationError` | 401 | Missing or invalid API key |
260
+ | `PaymentRequiredError` | 402 | No payment method (call `setup_payment()`) |
261
+ | `OfferExpiredError` | 410 | Offer no longer available |
262
+ | `LetsFGError` | any | Base class for all API errors |
263
+
264
+ ### Timeout and Retry Pattern
265
+
266
+ Airline APIs can be slow (2–15s for search). Use retry with backoff for production:
267
+
268
+ ```python
269
+ import time
270
+ from letsfg import LetsFG, LetsFGError
271
+
272
+ bt = LetsFG()
273
+
274
+ def search_with_retry(origin, dest, date, max_retries=3):
275
+ """Retry with exponential backoff on rate limit or timeout."""
276
+ for attempt in range(max_retries):
277
+ try:
278
+ return bt.search(origin, dest, date)
279
+ except LetsFGError as e:
280
+ if "429" in str(e) or "rate limit" in str(e).lower():
281
+ wait = 2 ** attempt # 1s, 2s, 4s
282
+ print(f"Rate limited, waiting {wait}s...")
283
+ time.sleep(wait)
284
+ elif "timeout" in str(e).lower() or "504" in str(e):
285
+ print(f"Timeout, retrying ({attempt + 1}/{max_retries})...")
286
+ time.sleep(1)
287
+ else:
288
+ raise
289
+ raise LetsFGError("Max retries exceeded")
290
+ ```
291
+
292
+ ### Rate Limits
293
+
294
+ | Endpoint | Rate Limit | Typical Latency |
295
+ |----------|-----------|------------------|
296
+ | Search | 60 req/min | 2-15s |
297
+ | Resolve location | 120 req/min | <1s |
298
+ | Unlock | 20 req/min | 2-5s |
299
+ | Book | 10 req/min | 3-10s |
300
+
301
+ ## Minimizing Unlock Costs
302
+
303
+ Searching is **free and unlimited**. Only unlock ($1) costs money. Strategy:
304
+
305
+ ```python
306
+ # Search multiple dates (free) — compare before unlocking
307
+ dates = ["2026-04-01", "2026-04-02", "2026-04-03"]
308
+ best = None
309
+ for date in dates:
310
+ result = bt.search("LON", "BCN", date)
311
+ if result.offers and (best is None or result.cheapest.price < best[1].price):
312
+ best = (date, result.cheapest)
313
+
314
+ # Unlock only the winner ($1)
315
+ if best:
316
+ unlocked = bt.unlock(best[1].id)
317
+ # Book within 30 minutes (free)
318
+ booking = bt.book(offer_id=unlocked.offer_id, passengers=[...], contact_email="...")
319
+ ```
320
+
321
+ ## Local LCC Search (No API Key)
322
+
323
+ The SDK includes 75 connectors for airlines that run directly on your machine. No API key, no backend, completely free:
324
+
325
+ ```python
326
+ from letsfg.local import search_local
327
+
328
+ # Fires all relevant airline connectors — Ryanair, Wizz Air, EasyJet, etc.
329
+ result = await search_local("GDN", "BCN", "2026-06-15")
330
+ print(f"{result['total_results']} offers from local connectors")
331
+
332
+ # Limit browser concurrency for constrained environments
333
+ result = await search_local("GDN", "BCN", "2026-06-15", max_browsers=4)
334
+ ```
335
+
336
+ The full search (`bt.search()`) runs both local connectors and cloud providers simultaneously and merges results.
337
+
338
+ ### Supported Airlines (75)
339
+
340
+ Ryanair, Wizz Air, EasyJet, Norwegian, Vueling, Eurowings, Transavia, Pegasus, Turkish Airlines, Southwest, AirAsia, IndiGo, SpiceJet, Akasa Air, Air India Express, VietJet, Cebu Pacific, Scoot, Jetstar, Peach, Spring Airlines, Lucky Air, 9 Air, flydubai, Air Arabia, flynas, Salam Air, Emirates, Etihad, Qatar Airways, Condor, SunExpress, Volotea, Smartwings, Jet2, LOT Polish Airlines, Frontier, Volaris, VivaAerobus, Allegiant, JetBlue, Flair, GOL, Azul, JetSmart, Flybondi, Porter, WestJet, LATAM, Copa, Avianca, Nok Air, Batik Air, Jeju Air, T'way Air, ZIPAIR, Air Peace, FlySafair, Avelo, Breeze, Sun Country, Alaska Airlines, Hawaiian Airlines, American Airlines, United Airlines, Delta Air Lines, Singapore Airlines, Cathay Pacific, Malaysian Airlines, Thai Airways, Korean Air, ANA, US-Bangla, Biman Bangladesh, Kiwi.com
341
+
342
+ ## Quick Start (CLI)
343
+
344
+ ```bash
345
+ export LETSFG_API_KEY=trav_...
346
+
347
+ # Search (1 adult, one-way, economy — defaults)
348
+ letsfg search GDN BER 2026-03-03 --sort price
349
+
350
+ # Multi-passenger round trip
351
+ letsfg search LON BCN 2026-04-01 --return 2026-04-08 --adults 2 --children 1 --cabin M
352
+
353
+ # Business class, direct flights only
354
+ letsfg search JFK LHR 2026-05-01 --adults 3 --cabin C --max-stops 0
355
+
356
+ # Machine-readable output (for agents)
357
+ letsfg search LON BCN 2026-04-01 --json
358
+
359
+ # Unlock
360
+ letsfg unlock off_xxx
361
+
362
+ # Book
363
+ letsfg book off_xxx \
364
+ --passenger '{"id":"pas_xxx","given_name":"John","family_name":"Doe","born_on":"1990-01-15","gender":"m","title":"mr","email":"john@example.com"}' \
365
+ --email john@example.com
366
+
367
+ # Resolve location
368
+ letsfg locations "Berlin"
369
+ ```
370
+
371
+ ### Search Flags
372
+
373
+ | Flag | Short | Default | Description |
374
+ |------|-------|---------|-------------|
375
+ | `--return` | `-r` | _(one-way)_ | Return date YYYY-MM-DD |
376
+ | `--adults` | `-a` | `1` | Adults (1–9) |
377
+ | `--children` | | `0` | Children 2–11 years |
378
+ | `--cabin` | `-c` | _(any)_ | `M` economy, `W` premium, `C` business, `F` first |
379
+ | `--max-stops` | `-s` | `2` | Max stopovers (0–4) |
380
+ | `--currency` | | `EUR` | Currency code |
381
+ | `--limit` | `-l` | `20` | Max results (1–100) |
382
+ | `--sort` | | `price` | `price` or `duration` |
383
+ | `--json` | `-j` | | Raw JSON output |
384
+
385
+ ## All CLI Commands
386
+
387
+ | Command | Description | Cost |
388
+ |---------|-------------|------|
389
+ | `search` | Search flights between any two airports | FREE |
390
+ | `locations` | Resolve city name to IATA codes | FREE |
391
+ | `unlock` | Unlock offer (confirms price, reserves 30min) | $1 |
392
+ | `book` | Book flight (creates real airline PNR) | FREE |
393
+ | `search-local` | Search 73 local airline connectors | FREE |
394
+ | `system-info` | Show system resources & concurrency tier | FREE |
395
+ | `register` | Register new agent, get API key | FREE |
396
+ | `setup-payment` | Attach payment card (payment token) | FREE |
397
+ | `me` | Show agent profile and usage stats | FREE |
398
+
399
+ Every command supports `--json` for machine-readable output.
400
+
401
+ ## Environment Variables
402
+
403
+ | Variable | Description |
404
+ |----------|-------------|
405
+ | `LETSFG_API_KEY` | Your agent API key |
406
+ | `LETSFG_BASE_URL` | API URL (default: `https://api.letsfg.co`) |
407
+ | `LETSFG_MAX_BROWSERS` | Max concurrent browser instances (1–32). Auto-detected from RAM if not set. |
408
+
409
+ ## Performance Tuning
410
+
411
+ LetsFG auto-detects your system's available RAM and scales browser concurrency:
412
+
413
+ | Available RAM | Tier | Max Browsers |
414
+ |-------------|------|-------------|
415
+ | < 2 GB | Minimal | 2 |
416
+ | 2–4 GB | Low | 3 |
417
+ | 4–8 GB | Moderate | 5 |
418
+ | 8–16 GB | Standard | 8 |
419
+ | 16–32 GB | High | 12 |
420
+ | 32+ GB | Maximum | 16 |
421
+
422
+ ```python
423
+ from letsfg import get_system_profile, configure_max_browsers
424
+
425
+ # Check system resources and recommended concurrency
426
+ profile = get_system_profile()
427
+ print(f"RAM: {profile['ram_available_gb']:.1f} GB available")
428
+ print(f"Tier: {profile['tier']} → {profile['recommended_max_browsers']} browsers")
429
+
430
+ # Override auto-detection
431
+ configure_max_browsers(4) # clamps to 1–32
432
+ ```
433
+
434
+ ```bash
435
+ # Via CLI
436
+ letsfg system-info
437
+ letsfg system-info --json # machine-readable
438
+
439
+ # Override via env var
440
+ export LETSFG_MAX_BROWSERS=4
441
+ letsfg search-local LHR BCN 2026-04-15
442
+
443
+ # Override via CLI flag
444
+ letsfg search-local LHR BCN 2026-04-15 --max-browsers 4
445
+ ```
446
+
447
+ Priority: env var > explicit config/flag > auto-detect.
448
+
449
+ ## How It Works
450
+
451
+ 1. **Search** — Free, unlimited. Returns real-time offers from 400+ airlines via NDC/GDS.
452
+ 2. **Unlock** — $1 proof-of-intent. Confirms latest price with airline, reserves offer for 30 minutes.
453
+ 3. **Book** — FREE after unlock. Creates real airline reservation with PNR code.
454
+
455
+ Prices are cheaper because we connect directly to airlines — no OTA markup.
456
+
457
+ ### City-Wide Airport Expansion
458
+
459
+ Search a city code and all airports in that city are searched automatically. `LON` → LHR, LGW, STN, LTN, SEN, LCY. `NYC` → JFK, EWR, LGA. Works for 25+ major cities.
460
+
461
+ ## For Agents
462
+
463
+ The SDK client uses **only Python stdlib** (`urllib`) for API calls — safe for sandboxed environments. The local LCC connectors additionally require `playwright`, `httpx`, and `beautifulsoup4` for browser automation.
464
+
465
+ The `--json` flag on every CLI command outputs structured JSON for easy parsing by agents.
466
+ ### Virtual Interlining
467
+
468
+ The combo engine builds cross-airline round-trips by combining one-way fares from different carriers. A Ryanair outbound + Wizz Air return can save 30-50% vs booking a round-trip on either airline alone.
469
+ ## License
470
+
471
+ MIT