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.
- letsfg-1.0.0/.gitignore +73 -0
- letsfg-1.0.0/LICENSE +21 -0
- letsfg-1.0.0/PKG-INFO +471 -0
- letsfg-1.0.0/README.md +427 -0
- letsfg-1.0.0/letsfg/CONNECTOR_STATUS.md +216 -0
- letsfg-1.0.0/letsfg/__init__.py +76 -0
- letsfg-1.0.0/letsfg/__main__.py +4 -0
- letsfg-1.0.0/letsfg/cli.py +588 -0
- letsfg-1.0.0/letsfg/client.py +740 -0
- letsfg-1.0.0/letsfg/connectors/__init__.py +15 -0
- letsfg-1.0.0/letsfg/connectors/_connector_template.py +61 -0
- letsfg-1.0.0/letsfg/connectors/airarabia.py +344 -0
- letsfg-1.0.0/letsfg/connectors/airasia.py +447 -0
- letsfg-1.0.0/letsfg/connectors/airbaltic.py +225 -0
- letsfg-1.0.0/letsfg/connectors/airindiaexpress.py +223 -0
- letsfg-1.0.0/letsfg/connectors/airline_routes.py +631 -0
- letsfg-1.0.0/letsfg/connectors/airpeace.py +301 -0
- letsfg-1.0.0/letsfg/connectors/akasa.py +392 -0
- letsfg-1.0.0/letsfg/connectors/alaska.py +321 -0
- letsfg-1.0.0/letsfg/connectors/allegiant.py +562 -0
- letsfg-1.0.0/letsfg/connectors/american.py +807 -0
- letsfg-1.0.0/letsfg/connectors/avelo.py +356 -0
- letsfg-1.0.0/letsfg/connectors/avianca.py +591 -0
- letsfg-1.0.0/letsfg/connectors/azul.py +463 -0
- letsfg-1.0.0/letsfg/connectors/batikair.py +819 -0
- letsfg-1.0.0/letsfg/connectors/biman.py +393 -0
- letsfg-1.0.0/letsfg/connectors/booking_base.py +382 -0
- letsfg-1.0.0/letsfg/connectors/breeze.py +378 -0
- letsfg-1.0.0/letsfg/connectors/browser.py +490 -0
- letsfg-1.0.0/letsfg/connectors/cathay.py +257 -0
- letsfg-1.0.0/letsfg/connectors/cebupacific.py +420 -0
- letsfg-1.0.0/letsfg/connectors/checkout_engine.py +1738 -0
- letsfg-1.0.0/letsfg/connectors/combo_engine.py +175 -0
- letsfg-1.0.0/letsfg/connectors/condor.py +860 -0
- letsfg-1.0.0/letsfg/connectors/copa.py +751 -0
- letsfg-1.0.0/letsfg/connectors/currency.py +88 -0
- letsfg-1.0.0/letsfg/connectors/delta.py +978 -0
- letsfg-1.0.0/letsfg/connectors/easyjet.py +1027 -0
- letsfg-1.0.0/letsfg/connectors/emirates.py +816 -0
- letsfg-1.0.0/letsfg/connectors/engine.py +961 -0
- letsfg-1.0.0/letsfg/connectors/etihad.py +552 -0
- letsfg-1.0.0/letsfg/connectors/eurowings.py +974 -0
- letsfg-1.0.0/letsfg/connectors/flair.py +270 -0
- letsfg-1.0.0/letsfg/connectors/flybondi.py +476 -0
- letsfg-1.0.0/letsfg/connectors/flydubai.py +810 -0
- letsfg-1.0.0/letsfg/connectors/flynas.py +607 -0
- letsfg-1.0.0/letsfg/connectors/flysafair.py +345 -0
- letsfg-1.0.0/letsfg/connectors/frontier.py +404 -0
- letsfg-1.0.0/letsfg/connectors/gol.py +451 -0
- letsfg-1.0.0/letsfg/connectors/hawaiian.py +324 -0
- letsfg-1.0.0/letsfg/connectors/indigo.py +611 -0
- letsfg-1.0.0/letsfg/connectors/jazeera.py +482 -0
- letsfg-1.0.0/letsfg/connectors/jejuair.py +244 -0
- letsfg-1.0.0/letsfg/connectors/jet2.py +1096 -0
- letsfg-1.0.0/letsfg/connectors/jetblue.py +485 -0
- letsfg-1.0.0/letsfg/connectors/jetsmart.py +875 -0
- letsfg-1.0.0/letsfg/connectors/jetstar.py +952 -0
- letsfg-1.0.0/letsfg/connectors/kiwi.py +458 -0
- letsfg-1.0.0/letsfg/connectors/korean.py +446 -0
- letsfg-1.0.0/letsfg/connectors/latam.py +637 -0
- letsfg-1.0.0/letsfg/connectors/lot.py +549 -0
- letsfg-1.0.0/letsfg/connectors/luckyair.py +470 -0
- letsfg-1.0.0/letsfg/connectors/malaysia.py +288 -0
- letsfg-1.0.0/letsfg/connectors/nh.py +520 -0
- letsfg-1.0.0/letsfg/connectors/nineair.py +508 -0
- letsfg-1.0.0/letsfg/connectors/nokair.py +343 -0
- letsfg-1.0.0/letsfg/connectors/norwegian.py +669 -0
- letsfg-1.0.0/letsfg/connectors/peach.py +414 -0
- letsfg-1.0.0/letsfg/connectors/pegasus.py +587 -0
- letsfg-1.0.0/letsfg/connectors/play.py +39 -0
- letsfg-1.0.0/letsfg/connectors/porter.py +372 -0
- letsfg-1.0.0/letsfg/connectors/qatar.py +479 -0
- letsfg-1.0.0/letsfg/connectors/ryanair.py +659 -0
- letsfg-1.0.0/letsfg/connectors/salamair.py +356 -0
- letsfg-1.0.0/letsfg/connectors/scoot.py +1112 -0
- letsfg-1.0.0/letsfg/connectors/singapore.py +759 -0
- letsfg-1.0.0/letsfg/connectors/smartwings.py +584 -0
- letsfg-1.0.0/letsfg/connectors/southwest.py +971 -0
- letsfg-1.0.0/letsfg/connectors/spicejet.py +420 -0
- letsfg-1.0.0/letsfg/connectors/spirit.py +813 -0
- letsfg-1.0.0/letsfg/connectors/spring.py +294 -0
- letsfg-1.0.0/letsfg/connectors/suncountry.py +286 -0
- letsfg-1.0.0/letsfg/connectors/sunexpress.py +602 -0
- letsfg-1.0.0/letsfg/connectors/thai.py +309 -0
- letsfg-1.0.0/letsfg/connectors/transavia.py +1077 -0
- letsfg-1.0.0/letsfg/connectors/turkish.py +571 -0
- letsfg-1.0.0/letsfg/connectors/twayair.py +593 -0
- letsfg-1.0.0/letsfg/connectors/united.py +415 -0
- letsfg-1.0.0/letsfg/connectors/usbangla.py +461 -0
- letsfg-1.0.0/letsfg/connectors/vietjet.py +852 -0
- letsfg-1.0.0/letsfg/connectors/vivaaerobus.py +231 -0
- letsfg-1.0.0/letsfg/connectors/volaris.py +651 -0
- letsfg-1.0.0/letsfg/connectors/volotea.py +1048 -0
- letsfg-1.0.0/letsfg/connectors/vueling.py +719 -0
- letsfg-1.0.0/letsfg/connectors/westjet.py +445 -0
- letsfg-1.0.0/letsfg/connectors/wizzair.py +961 -0
- letsfg-1.0.0/letsfg/connectors/zipair.py +390 -0
- letsfg-1.0.0/letsfg/local.py +151 -0
- letsfg-1.0.0/letsfg/models/__init__.py +342 -0
- letsfg-1.0.0/letsfg/models/flights.py +153 -0
- letsfg-1.0.0/letsfg/models.py +328 -0
- letsfg-1.0.0/letsfg/system_info.py +181 -0
- letsfg-1.0.0/pyproject.toml +69 -0
letsfg-1.0.0/.gitignore
ADDED
|
@@ -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
|