letsfg 2026.5.71 → 2026.5.73
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.
- package/README.md +283 -221
- package/dist/{chunk-GO3FXQXC.mjs → chunk-F5BBI6XX.mjs} +134 -20
- package/dist/cli.js +381 -216
- package/dist/cli.mjs +248 -61
- package/dist/index.d.mts +92 -14
- package/dist/index.d.ts +92 -14
- package/dist/index.js +134 -20
- package/dist/index.mjs +1 -1
- package/package.json +56 -56
- package/dist/auth-SYZ5AYZ6.mjs +0 -16
- package/dist/chunk-XNO2W77S.mjs +0 -126
package/README.md
CHANGED
|
@@ -1,221 +1,283 @@
|
|
|
1
|
-
# LetsFG — Your AI agent just learned to book flights. (Node.js)
|
|
2
|
-
|
|
3
|
-
**Server-side search engine. Real prices. One function call.** Search hundreds of airlines at raw airline prices — **$20–$50 cheaper** than Booking.com, Kayak, and other OTAs. Zero dependencies. Built for AI agents.
|
|
4
|
-
|
|
5
|
-
[](https://github.com/LetsFG/LetsFG)
|
|
6
|
-
[](https://www.npmjs.com/package/letsfg)
|
|
7
|
-
|
|
8
|
-
## Two ways to use LetsFG
|
|
9
|
-
|
|
10
|
-
| | **CLI / SDK** (this package) | **Developer API** |
|
|
11
|
-
|---|---|---|
|
|
12
|
-
| **Search cost** | Free (
|
|
13
|
-
| **Booking** | `POST /api/agent-book` —
|
|
14
|
-
| **Speed** | 8–10 s to first results; longer on a split | 2–5 s (discover) · 8–10 s to first results (full) |
|
|
15
|
-
| **Setup** | `npm install letsfg
|
|
16
|
-
|
|
17
|
-
> **
|
|
18
|
-
|
|
19
|
-
## Install
|
|
20
|
-
|
|
21
|
-
```bash
|
|
22
|
-
npm install letsfg
|
|
23
|
-
```
|
|
24
|
-
|
|
25
|
-
##
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
)
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
)
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
**
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
`
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
**
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
`
|
|
221
|
-
|
|
1
|
+
# LetsFG — Your AI agent just learned to book flights. (Node.js)
|
|
2
|
+
|
|
3
|
+
**Server-side search engine. Real prices. One function call.** Search hundreds of airlines at raw airline prices — **$20–$50 cheaper** than Booking.com, Kayak, and other OTAs. Zero dependencies. Built for AI agents.
|
|
4
|
+
|
|
5
|
+
[](https://github.com/LetsFG/LetsFG)
|
|
6
|
+
[](https://www.npmjs.com/package/letsfg)
|
|
7
|
+
|
|
8
|
+
## Two ways to use LetsFG
|
|
9
|
+
|
|
10
|
+
| | **CLI / SDK** (this package) | **Developer API** |
|
|
11
|
+
|---|---|---|
|
|
12
|
+
| **Search cost** | Free (card-backed token from [letsfg.co/connect](https://letsfg.co/connect), nothing charged) | Prepaid credits |
|
|
13
|
+
| **Booking** | `POST /api/agent-book` — fare held on your card, a LetsFG agent buys the ticket, captured only on a real PNR. Every offer. | Direct airline URL (unlock required first) |
|
|
14
|
+
| **Speed** | 8–10 s to first results; longer on a split | 2–5 s (discover) · 8–10 s to first results (full) |
|
|
15
|
+
| **Setup** | `npm install letsfg`, then connect at [letsfg.co/developers/api/mcp](https://letsfg.co/developers/api/mcp) | [letsfg.co/developers](https://letsfg.co/developers) |
|
|
16
|
+
|
|
17
|
+
> **Building a product, or need hotels?** Use the [Developer API](https://letsfg.co/developers) — look-to-book search (200 free after every booking, then $0.01), booking through `POST /flights/book`, no booking fee and no transaction fee.
|
|
18
|
+
|
|
19
|
+
## Install
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
npm install letsfg
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Getting a token
|
|
26
|
+
|
|
27
|
+
Connect LetsFG as an MCP server at `https://letsfg.co/developers/api/mcp` and
|
|
28
|
+
approve the connection — in Claude, ChatGPT, Cursor, Windsurf, or Claude Code
|
|
29
|
+
(`claude mcp add --transport http letsfg https://letsfg.co/developers/api/mcp`).
|
|
30
|
+
The consent step opens [letsfg.co/connect](https://letsfg.co/connect), where you
|
|
31
|
+
add a card (any card, or Revolut Pay / Google Pay) in a 0.00 Revolut setup.
|
|
32
|
+
Nothing is charged, no Revolut account is needed, and the card details go to
|
|
33
|
+
Revolut, never to LetsFG. The token you get back is card-backed: it searches
|
|
34
|
+
and it books. One card = one account; quotas are per card (10 searches per
|
|
35
|
+
10 min, 30 per hour, 100 per day — polling never counts).
|
|
36
|
+
|
|
37
|
+
Pass it as `bearerToken`, or set `LETSFG_BEARER_TOKEN` for the CLI.
|
|
38
|
+
|
|
39
|
+
> `letsfg auth` still runs the Stripe card setup that was retired on
|
|
40
|
+
> 2026-09-02 and cannot get a token today; every token issued that way was
|
|
41
|
+
> revoked (401 `TOKEN_REVOKED`). A connect-flow login for the CLI and SDKs is
|
|
42
|
+
> coming — until then, connect through the MCP.
|
|
43
|
+
|
|
44
|
+
## Quick Start (SDK)
|
|
45
|
+
|
|
46
|
+
```typescript
|
|
47
|
+
import { LetsFG, cheapestOffer, offerSummary } from 'letsfg';
|
|
48
|
+
|
|
49
|
+
// PFS — free. The card-backed token from the connect flow (see above).
|
|
50
|
+
const bt = new LetsFG({ bearerToken: 'eyJ...' });
|
|
51
|
+
|
|
52
|
+
// Search — FREE
|
|
53
|
+
const flights = await bt.search('GDN', 'BER', '2026-03-03');
|
|
54
|
+
const best = cheapestOffer(flights);
|
|
55
|
+
console.log(offerSummary(best));
|
|
56
|
+
|
|
57
|
+
// Book — no booking fee, no transaction fee — our margin is already in the price you saw; no unlock step. Starts the booking:
|
|
58
|
+
// the fare is HELD on your card and a LetsFG agent buys the ticket (4-11 min).
|
|
59
|
+
const result = await bt.book(
|
|
60
|
+
best.id,
|
|
61
|
+
[{
|
|
62
|
+
given_name: 'John', family_name: 'Doe', born_on: '1990-01-15', gender: 'm',
|
|
63
|
+
nationality: 'GB', phone_number: '+447700900123', phone_country: 'GB',
|
|
64
|
+
address_line1: '1 Analytical Way', address_city: 'London',
|
|
65
|
+
address_postal: 'N1 9GU', address_country: 'GB',
|
|
66
|
+
}],
|
|
67
|
+
'john@example.com',
|
|
68
|
+
'',
|
|
69
|
+
'',
|
|
70
|
+
flights.search_id,
|
|
71
|
+
);
|
|
72
|
+
const bookingRef = result.booking_ref as string;
|
|
73
|
+
|
|
74
|
+
// Poll until it lands (every 20-30 s): completed | failed | needs_attention
|
|
75
|
+
let status: Record<string, unknown>;
|
|
76
|
+
do {
|
|
77
|
+
await new Promise(r => setTimeout(r, 25_000));
|
|
78
|
+
status = await (await fetch('https://letsfg.co/api/agent-book/status', {
|
|
79
|
+
method: 'POST',
|
|
80
|
+
headers: { Authorization: 'Bearer eyJ...', 'Content-Type': 'application/json' },
|
|
81
|
+
body: JSON.stringify({ booking_ref: bookingRef }),
|
|
82
|
+
})).json();
|
|
83
|
+
} while (status.state === 'booking_in_progress');
|
|
84
|
+
console.log(status); // { state: 'completed', pnr: 'ABC123', charged_amount: 93, currency: 'EUR' }
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### How booking works
|
|
88
|
+
|
|
89
|
+
`bt.book()` posts to `POST /api/agent-book` and does exactly what the website
|
|
90
|
+
checkout does: the fare plus LetsFG's markup is **held** on the connected card
|
|
91
|
+
(not taken), a LetsFG booking agent buys the ticket from the seller, and the
|
|
92
|
+
hold is captured only once a real airline PNR exists. If the booking fails the
|
|
93
|
+
hold is released and nothing is charged. Every offer can be booked this way —
|
|
94
|
+
no unlock step, no booking-link fallback, no separate LetsFG fee.
|
|
95
|
+
|
|
96
|
+
The call returns within seconds with a `booking_ref`; the booking itself takes
|
|
97
|
+
4–11 minutes. Poll `POST /api/agent-book/status` with `{"booking_ref": ...}`
|
|
98
|
+
every 20–30 s (the SDK has no helper for this yet):
|
|
99
|
+
|
|
100
|
+
| `state` | Meaning |
|
|
101
|
+
|---|---|
|
|
102
|
+
| `booking_in_progress` | the agent is at the seller's checkout — keep waiting |
|
|
103
|
+
| `completed` | booked — `pnr`, `charged_amount`, `currency` are in the answer |
|
|
104
|
+
| `failed` | not booked — the hold was released, nothing charged; see `failure_reason` |
|
|
105
|
+
| `needs_attention` | a human at LetsFG is checking it — do **not** book again |
|
|
106
|
+
|
|
107
|
+
One traveller per call, with the details an airline checkout asks for: name,
|
|
108
|
+
date of birth, gender, nationality, email, phone with its country, residence
|
|
109
|
+
address (passport optional). A missing detail returns `missing_details` with
|
|
110
|
+
`missing_fields` and charges nothing. Never start a second booking for the
|
|
111
|
+
same trip while one is in progress — that would place a second hold.
|
|
112
|
+
|
|
113
|
+
Prefer the paid Developer API instead? Pass `apiKey` instead of `bearerToken` —
|
|
114
|
+
`search()`/`book()` dispatch automatically. That path needs a `searchId` (an offer
|
|
115
|
+
is bookable only inside the search that produced it) and has no unlock step:
|
|
116
|
+
`unlock()` was retired 2026-09-08, the route answers `410 Gone`, and there is no fee.
|
|
117
|
+
|
|
118
|
+
## Quick Start (CLI)
|
|
119
|
+
|
|
120
|
+
```bash
|
|
121
|
+
export LETSFG_BEARER_TOKEN=<your-bearer-token> # card-backed, from the connect flow
|
|
122
|
+
|
|
123
|
+
letsfg search GDN BER 2026-03-03 --sort price
|
|
124
|
+
letsfg search LON BCN 2026-04-01 --json # Machine-readable
|
|
125
|
+
letsfg book off_xxx --search-id srch_xxx -p '{"given_name":"John","family_name":"Doe","born_on":"1990-01-15","gender":"m","nationality":"GB","phone_number":"+447700900123","phone_country":"GB","address_line1":"1 Analytical Way","address_city":"London","address_postal":"N1 9GU","address_country":"GB"}' -e john@example.com
|
|
126
|
+
# prints the booking_ref — poll POST /api/agent-book/status until completed
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
## API
|
|
130
|
+
|
|
131
|
+
### `new LetsFG({ bearerToken?, apiKey?, baseUrl?, timeout? })`
|
|
132
|
+
|
|
133
|
+
### `bt.search(origin, destination, dateFrom, options?)`
|
|
134
|
+
### `bt.resolveLocation(query)`
|
|
135
|
+
### `bt.book(offerId, passengers, contactEmail, contactPhone?, idempotencyKey?, searchId?)`
|
|
136
|
+
Dispatches on which credential is set: `bearerToken` → PFS booking via
|
|
137
|
+
`POST /api/agent-book` (pass `searchId`, one passenger with full details;
|
|
138
|
+
returns `booking_ref` — poll `POST /api/agent-book/status`). `apiKey` → paid
|
|
139
|
+
Developer API `book` (requires `searchId`, no unlock step, supports multiple
|
|
140
|
+
passengers and `idempotencyKey`; returns a `booking_id` to poll).
|
|
141
|
+
### `bt.getBooking(bookingId)` / `bt.answerBooking(...)` / `bt.bookAndWait(...)` — Developer API only
|
|
142
|
+
### `bt.connectPayment()` — Developer API only. Returns `connect_url`; nothing is charged
|
|
143
|
+
### `bt.setupPayment()` / `bt.unlock()` — **retired 2026-09-08, both throw locally**
|
|
144
|
+
### `bt.me()`
|
|
145
|
+
### `LetsFG.register(agentName, email, baseUrl?, ownerName?, description?)` — Developer API only, most agents don't need this
|
|
146
|
+
|
|
147
|
+
### Helpers
|
|
148
|
+
- `offerSummary(offer)` — One-line string summary
|
|
149
|
+
- `cheapestOffer(result)` — Get cheapest offer from search
|
|
150
|
+
|
|
151
|
+
## Starlink Wi-Fi
|
|
152
|
+
|
|
153
|
+
Offers may carry `starlink`: `confirmed_all` / `confirmed_some` mean the carrier
|
|
154
|
+
has **fully** fitted that aircraft type; `likely_all` / `likely_some` mean the
|
|
155
|
+
rollout on that type is underway but incomplete. Segments carry `confirmed` or
|
|
156
|
+
`likely`.
|
|
157
|
+
|
|
158
|
+
Only `confirmed_*` is safe to state as fact — `likely_*` is a signal, not a
|
|
159
|
+
promise. Anything ending `_some` has at least one leg without it. An **absent**
|
|
160
|
+
field means no information, **not** an absence of Wi-Fi.
|
|
161
|
+
|
|
162
|
+
Full semantics: [docs/api-search.md](https://github.com/LetsFG/LetsFG/blob/main/docs/api-search.md#starlink-wi-fi).
|
|
163
|
+
|
|
164
|
+
## Zero Dependencies
|
|
165
|
+
|
|
166
|
+
Uses native `fetch` (Node 18+). No `axios`, no `node-fetch`, nothing. Safe for sandboxed environments.
|
|
167
|
+
|
|
168
|
+
## Also Available As
|
|
169
|
+
|
|
170
|
+
- **MCP Server**: `npx letsfg-mcp` — [npm](https://www.npmjs.com/package/letsfg-mcp)
|
|
171
|
+
- **Python SDK + CLI**: `pip install letsfg` — [PyPI](https://pypi.org/project/letsfg/)
|
|
172
|
+
- **Try without installing**: [letsfg.co](https://letsfg.co) — search instantly in your browser
|
|
173
|
+
- **GitHub**: [LetsFG/LetsFG](https://github.com/LetsFG/LetsFG)
|
|
174
|
+
|
|
175
|
+
> ⭐ **[Star the repo](https://github.com/LetsFG/LetsFG)** — we appreciate the support.
|
|
176
|
+
|
|
177
|
+
## License
|
|
178
|
+
|
|
179
|
+
MIT
|
|
180
|
+
|
|
181
|
+
## 🏨 Hotels — new, and live
|
|
182
|
+
|
|
183
|
+
Your agent can book hotels as well as flights. Same card-backed token or API key, same card on file.
|
|
184
|
+
|
|
185
|
+
```python
|
|
186
|
+
from letsfg import LetsFG
|
|
187
|
+
lfg = LetsFG()
|
|
188
|
+
|
|
189
|
+
city = lfg.hotel_destinations("Warsaw")[0]
|
|
190
|
+
stays = lfg.search_hotels(
|
|
191
|
+
city_id=city["Id"], city_name=city["Name"],
|
|
192
|
+
check_in="2026-11-10", check_out="2026-11-12", adults=2,
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
hotel = stays["hotels"][0]
|
|
196
|
+
offer = hotel["offers"][0]
|
|
197
|
+
print(hotel["name"], offer["price"], stays["currency"])
|
|
198
|
+
# Hotel Gromada Warszawa Centrum 669.86 PLN
|
|
199
|
+
|
|
200
|
+
booking = lfg.book_hotel_and_wait(
|
|
201
|
+
session_id=stays["session_id"],
|
|
202
|
+
hotel_code=hotel["hotel_code"],
|
|
203
|
+
combination_id_v2=offer["combination_id_v2"],
|
|
204
|
+
expected_price=offer["price"],
|
|
205
|
+
expected_balance=offer["balance_to_supplier"],
|
|
206
|
+
city_id=city["Id"], city_name=city["Name"],
|
|
207
|
+
check_in="2026-11-10", check_out="2026-11-12",
|
|
208
|
+
guests=[{"title": "Mr", "first_name": "Jan", "last_name": "Kowalski"}],
|
|
209
|
+
email="guest@example.com", phone="512345678",
|
|
210
|
+
)
|
|
211
|
+
print(booking["confirmation"], booking["pay_link"])
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
### How you pay
|
|
215
|
+
|
|
216
|
+
**5% now, the rest to the hotel later.** At booking we charge 5% of the price
|
|
217
|
+
to your card as a reservation fee. The remaining balance is paid **directly to
|
|
218
|
+
the supplier** through a `pay_link` we return — we never hold it.
|
|
219
|
+
|
|
220
|
+
`balance_due_by` is the supplier's own auto-cancellation date, not a date we
|
|
221
|
+
invent. Miss it and the room is released.
|
|
222
|
+
|
|
223
|
+
The 5% is **non-refundable**. Cancelling before `balance_due_by` costs nothing
|
|
224
|
+
else; after it, the hotel's own cancellation ladder applies and can reach 100%.
|
|
225
|
+
That ladder ships in the booking's `terms`, so you can always see the cost before
|
|
226
|
+
you cancel.
|
|
227
|
+
|
|
228
|
+
### What search costs
|
|
229
|
+
|
|
230
|
+
Search is metered separately from booking, on **either** auth path (free PFS
|
|
231
|
+
Bearer token or Developer API key — both count against the same agent):
|
|
232
|
+
**the first 1,000 `search_hotels` calls since your last hotel booking are
|
|
233
|
+
free.** Past that, searches are billed in blocks of 1,000 for **$5**
|
|
234
|
+
(~$0.005/search) from your prepaid balance — refused with a 402 if the
|
|
235
|
+
balance can't cover the next block, never silently allowed. Book a hotel
|
|
236
|
+
and the count resets to zero. Resolving a city name (`hotel_destinations`)
|
|
237
|
+
is not metered, only the search call itself.
|
|
238
|
+
|
|
239
|
+
### Things worth knowing before you build
|
|
240
|
+
|
|
241
|
+
- **A card on file is required for every hotel call, including search.** That is
|
|
242
|
+
unusual and it is deliberate: a hotel search opens a real session at the
|
|
243
|
+
supplier, and booking blocks a real rate. We would rather refuse up front than
|
|
244
|
+
let you reach the point of commitment and discover you cannot pay. The same
|
|
245
|
+
card that authorises flight booking authorises hotels — there is no separate
|
|
246
|
+
hotel signup.
|
|
247
|
+
- **Only free-cancellation, pay-later rates are sold.** Those are the rates where
|
|
248
|
+
the balance can safely be settled with the supplier after booking, which is
|
|
249
|
+
what makes 5%-now/rest-later work at all. You will see fewer results than a
|
|
250
|
+
metasearch shows you. Every one of them can actually be booked.
|
|
251
|
+
- **Booking is asynchronous.** `book_hotel` returns a `booking_job_id`, not a
|
|
252
|
+
booking — the real thing takes minutes. Poll `hotel_booking(job_id)` until
|
|
253
|
+
`status` is `succeeded` or `failed`, or call `book_hotel_and_wait` and let the
|
|
254
|
+
SDK do it. This is not ceremony: it is what makes it impossible to charge a
|
|
255
|
+
card and then lose the confirmation to a timeout.
|
|
256
|
+
- **The fee is charged before the room is committed.** A declined card therefore
|
|
257
|
+
costs nothing to unwind — no reservation exists and nothing is charged.
|
|
258
|
+
- **Do not retry a booking blindly.** Calling `book_hotel` twice for the same
|
|
259
|
+
rate books the room twice and charges two reservation fees.
|
|
260
|
+
- `price` is what the guest pays. There is no wholesale figure in the response to
|
|
261
|
+
quote by mistake.
|
|
262
|
+
|
|
263
|
+
### JavaScript
|
|
264
|
+
|
|
265
|
+
```javascript
|
|
266
|
+
import { LetsFG } from 'letsfg';
|
|
267
|
+
const lfg = new LetsFG({ apiKey: process.env.LETSFG_API_KEY });
|
|
268
|
+
|
|
269
|
+
const [city] = await lfg.hotelDestinations('Warsaw');
|
|
270
|
+
const stays = await lfg.searchHotels({
|
|
271
|
+
cityId: city.Id, cityName: city.Name,
|
|
272
|
+
checkIn: '2026-11-10', checkOut: '2026-11-12', adults: 2,
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
const booking = await lfg.bookHotelAndWait({ /* ...offer + guest details... */ });
|
|
276
|
+
console.log(booking.confirmation, booking.pay_link);
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
### MCP
|
|
280
|
+
|
|
281
|
+
Five new tools, in the order you call them: `resolve_hotel_city` →
|
|
282
|
+
`search_hotels` → `book_hotel` → `get_hotel_booking` → `cancel_hotel_booking`.
|
|
283
|
+
|