letsfg 2026.5.70 → 2026.5.71
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 +1 -1
- package/dist/{chunk-SYNHNDEL.mjs → chunk-GO3FXQXC.mjs} +26 -11
- package/dist/cli.js +27 -12
- package/dist/cli.mjs +2 -2
- package/dist/index.d.mts +4 -4
- package/dist/index.d.ts +4 -4
- package/dist/index.js +26 -11
- package/dist/index.mjs +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
|---|---|---|
|
|
12
12
|
| **Search cost** | Free (Bearer token via `letsfg auth` — zero-amount card setup) | Prepaid credits |
|
|
13
13
|
| **Booking** | `POST /api/agent-book` — confirmed order or a booking link, no LetsFG fee | Direct airline URL (unlock required first) |
|
|
14
|
-
| **Speed** |
|
|
14
|
+
| **Speed** | 8–10 s to first results; longer on a split | 2–5 s (discover) · 8–10 s to first results (full) |
|
|
15
15
|
| **Setup** | `npm install letsfg` then `letsfg auth` | [letsfg.co/developers](https://letsfg.co/developers) |
|
|
16
16
|
|
|
17
17
|
> **Want direct airline URLs without any per-booking fee?** Use the [Developer API](https://letsfg.co/developers) — prepaid credits, results in seconds, no checkout step.
|
|
@@ -1558,8 +1558,12 @@ function cheapestOffer(result) {
|
|
|
1558
1558
|
return result.offers.reduce((min, o) => o.price < min.price ? o : min, result.offers[0]);
|
|
1559
1559
|
}
|
|
1560
1560
|
var DEFAULT_BASE_URL = "https://letsfg.co";
|
|
1561
|
-
var PFS_POLL_INTERVAL_MS =
|
|
1561
|
+
var PFS_POLL_INTERVAL_MS = 2e3;
|
|
1562
1562
|
var PFS_POLL_TIMEOUT_MS = 12e4;
|
|
1563
|
+
var LATE_MERGE_POLL_MS = 3e3;
|
|
1564
|
+
var LATE_MERGE_GRACE_MS = 9e4;
|
|
1565
|
+
var WAIT_FOR_SPLIT = (process.env.LETSFG_WAIT_FOR_SPLIT || "").trim() !== "0";
|
|
1566
|
+
var NON_TERMINAL = ["pending", "searching"];
|
|
1563
1567
|
var LetsFG = class {
|
|
1564
1568
|
bearerToken;
|
|
1565
1569
|
apiKey;
|
|
@@ -1595,7 +1599,7 @@ var LetsFG = class {
|
|
|
1595
1599
|
*
|
|
1596
1600
|
* Uses PFS (Bearer token) or Developer API (X-API-Key) depending on config.
|
|
1597
1601
|
* PFS: async polling (POST /api/search -> poll /api/results/<id> every 10s).
|
|
1598
|
-
* Developer API: synchronous
|
|
1602
|
+
* Developer API: synchronous call.
|
|
1599
1603
|
*
|
|
1600
1604
|
* @param origin - IATA code (e.g., "GDN", "LON")
|
|
1601
1605
|
* @param destination - IATA code (e.g., "BER", "BCN")
|
|
@@ -1627,17 +1631,28 @@ var LetsFG = class {
|
|
|
1627
1631
|
/** PFS path: POST /api/search -> poll /api/results/<id> */
|
|
1628
1632
|
async searchPFS(body) {
|
|
1629
1633
|
const { search_id } = await this.postWithBearer("/api/search", body);
|
|
1634
|
+
const poll = () => this.getNoAuth(`/api/results/${search_id}`);
|
|
1635
|
+
const inbound = (r) => Boolean(r.split_ticket_pending || r.gf_enrich_pending);
|
|
1630
1636
|
const deadline = Date.now() + PFS_POLL_TIMEOUT_MS;
|
|
1637
|
+
let terminal = null;
|
|
1631
1638
|
while (Date.now() < deadline) {
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
if (!["pending", "searching"].includes(result.status)) {
|
|
1637
|
-
return result;
|
|
1639
|
+
const result = await poll();
|
|
1640
|
+
if (!NON_TERMINAL.includes(result.status)) {
|
|
1641
|
+
terminal = result;
|
|
1642
|
+
break;
|
|
1638
1643
|
}
|
|
1644
|
+
await new Promise((r) => setTimeout(r, PFS_POLL_INTERVAL_MS));
|
|
1645
|
+
}
|
|
1646
|
+
if (!terminal) {
|
|
1647
|
+
throw new LetsFGError("Search timed out after 120s. Try polling /api/results/<id> directly.", 504);
|
|
1648
|
+
}
|
|
1649
|
+
const lateDeadline = Date.now() + LATE_MERGE_GRACE_MS;
|
|
1650
|
+
while (WAIT_FOR_SPLIT && inbound(terminal) && Date.now() < lateDeadline) {
|
|
1651
|
+
await new Promise((r) => setTimeout(r, LATE_MERGE_POLL_MS));
|
|
1652
|
+
const merged = await poll();
|
|
1653
|
+
if (!NON_TERMINAL.includes(merged.status)) terminal = merged;
|
|
1639
1654
|
}
|
|
1640
|
-
|
|
1655
|
+
return terminal;
|
|
1641
1656
|
}
|
|
1642
1657
|
/**
|
|
1643
1658
|
* Resolve a city/airport name to IATA codes.
|
|
@@ -1657,8 +1672,8 @@ var LetsFG = class {
|
|
|
1657
1672
|
}
|
|
1658
1673
|
/**
|
|
1659
1674
|
* Unlock a flight offer — confirms live price, reveals direct airline booking URL.
|
|
1660
|
-
*
|
|
1661
|
-
*
|
|
1675
|
+
* Developer API only, legacy — there is no unlock endpoint on a PFS Bearer
|
|
1676
|
+
* token, so PFS callers use book() directly.
|
|
1662
1677
|
*/
|
|
1663
1678
|
async unlock(offerId) {
|
|
1664
1679
|
this.requireApiKey();
|
package/dist/cli.js
CHANGED
|
@@ -454,8 +454,12 @@ function offerSummary(offer) {
|
|
|
454
454
|
return `${offer.currency} ${offer.price.toFixed(2)} | ${airline} | ${route} | ${dur} | ${offer.outbound.stopovers} stop(s)`;
|
|
455
455
|
}
|
|
456
456
|
var DEFAULT_BASE_URL = "https://letsfg.co";
|
|
457
|
-
var PFS_POLL_INTERVAL_MS =
|
|
457
|
+
var PFS_POLL_INTERVAL_MS = 2e3;
|
|
458
458
|
var PFS_POLL_TIMEOUT_MS = 12e4;
|
|
459
|
+
var LATE_MERGE_POLL_MS = 3e3;
|
|
460
|
+
var LATE_MERGE_GRACE_MS = 9e4;
|
|
461
|
+
var WAIT_FOR_SPLIT = (process.env.LETSFG_WAIT_FOR_SPLIT || "").trim() !== "0";
|
|
462
|
+
var NON_TERMINAL = ["pending", "searching"];
|
|
459
463
|
var LetsFG = class {
|
|
460
464
|
bearerToken;
|
|
461
465
|
apiKey;
|
|
@@ -491,7 +495,7 @@ var LetsFG = class {
|
|
|
491
495
|
*
|
|
492
496
|
* Uses PFS (Bearer token) or Developer API (X-API-Key) depending on config.
|
|
493
497
|
* PFS: async polling (POST /api/search -> poll /api/results/<id> every 10s).
|
|
494
|
-
* Developer API: synchronous
|
|
498
|
+
* Developer API: synchronous call.
|
|
495
499
|
*
|
|
496
500
|
* @param origin - IATA code (e.g., "GDN", "LON")
|
|
497
501
|
* @param destination - IATA code (e.g., "BER", "BCN")
|
|
@@ -523,17 +527,28 @@ var LetsFG = class {
|
|
|
523
527
|
/** PFS path: POST /api/search -> poll /api/results/<id> */
|
|
524
528
|
async searchPFS(body) {
|
|
525
529
|
const { search_id } = await this.postWithBearer("/api/search", body);
|
|
530
|
+
const poll = () => this.getNoAuth(`/api/results/${search_id}`);
|
|
531
|
+
const inbound = (r) => Boolean(r.split_ticket_pending || r.gf_enrich_pending);
|
|
526
532
|
const deadline = Date.now() + PFS_POLL_TIMEOUT_MS;
|
|
533
|
+
let terminal = null;
|
|
527
534
|
while (Date.now() < deadline) {
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
if (!["pending", "searching"].includes(result.status)) {
|
|
533
|
-
return result;
|
|
535
|
+
const result = await poll();
|
|
536
|
+
if (!NON_TERMINAL.includes(result.status)) {
|
|
537
|
+
terminal = result;
|
|
538
|
+
break;
|
|
534
539
|
}
|
|
540
|
+
await new Promise((r) => setTimeout(r, PFS_POLL_INTERVAL_MS));
|
|
541
|
+
}
|
|
542
|
+
if (!terminal) {
|
|
543
|
+
throw new LetsFGError("Search timed out after 120s. Try polling /api/results/<id> directly.", 504);
|
|
544
|
+
}
|
|
545
|
+
const lateDeadline = Date.now() + LATE_MERGE_GRACE_MS;
|
|
546
|
+
while (WAIT_FOR_SPLIT && inbound(terminal) && Date.now() < lateDeadline) {
|
|
547
|
+
await new Promise((r) => setTimeout(r, LATE_MERGE_POLL_MS));
|
|
548
|
+
const merged = await poll();
|
|
549
|
+
if (!NON_TERMINAL.includes(merged.status)) terminal = merged;
|
|
535
550
|
}
|
|
536
|
-
|
|
551
|
+
return terminal;
|
|
537
552
|
}
|
|
538
553
|
/**
|
|
539
554
|
* Resolve a city/airport name to IATA codes.
|
|
@@ -553,8 +568,8 @@ var LetsFG = class {
|
|
|
553
568
|
}
|
|
554
569
|
/**
|
|
555
570
|
* Unlock a flight offer — confirms live price, reveals direct airline booking URL.
|
|
556
|
-
*
|
|
557
|
-
*
|
|
571
|
+
* Developer API only, legacy — there is no unlock endpoint on a PFS Bearer
|
|
572
|
+
* token, so PFS callers use book() directly.
|
|
558
573
|
*/
|
|
559
574
|
async unlock(offerId) {
|
|
560
575
|
this.requireApiKey();
|
|
@@ -1191,7 +1206,7 @@ Developer API only (a SEPARATE paid product \u2014 most agents should not use th
|
|
|
1191
1206
|
they create a billing account. Use auth above instead):
|
|
1192
1207
|
register --name ... --email ... Create a paid Developer API account
|
|
1193
1208
|
setup-payment Attach a card to that paid account
|
|
1194
|
-
unlock <offer_id> [Developer API only] Unlock offer
|
|
1209
|
+
unlock <offer_id> [Developer API only] Unlock offer (legacy)
|
|
1195
1210
|
|
|
1196
1211
|
Options:
|
|
1197
1212
|
--json, -j Output raw JSON
|
package/dist/cli.mjs
CHANGED
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
LetsFG,
|
|
4
4
|
LetsFGError,
|
|
5
5
|
offerSummary
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-GO3FXQXC.mjs";
|
|
7
7
|
import {
|
|
8
8
|
BearerTokenError,
|
|
9
9
|
getBearerToken,
|
|
@@ -348,7 +348,7 @@ Developer API only (a SEPARATE paid product \u2014 most agents should not use th
|
|
|
348
348
|
they create a billing account. Use auth above instead):
|
|
349
349
|
register --name ... --email ... Create a paid Developer API account
|
|
350
350
|
setup-payment Attach a card to that paid account
|
|
351
|
-
unlock <offer_id> [Developer API only] Unlock offer
|
|
351
|
+
unlock <offer_id> [Developer API only] Unlock offer (legacy)
|
|
352
352
|
|
|
353
353
|
Options:
|
|
354
354
|
--json, -j Output raw JSON
|
package/dist/index.d.mts
CHANGED
|
@@ -283,7 +283,7 @@ declare function getOfferDetailPromptNotes(offer: OfferDetailLike): string[];
|
|
|
283
283
|
* const flights = await bt.search('GDN', 'BER', '2026-03-03');
|
|
284
284
|
*
|
|
285
285
|
* // Developer API (prepaid credits)
|
|
286
|
-
* const bt2 = new LetsFG({ apiKey: '
|
|
286
|
+
* const bt2 = new LetsFG({ apiKey: 'letsfg_...' });
|
|
287
287
|
* const flights2 = await bt2.search('LHR', 'JFK', '2026-04-15');
|
|
288
288
|
* ```
|
|
289
289
|
*/
|
|
@@ -473,7 +473,7 @@ declare class LetsFG {
|
|
|
473
473
|
*
|
|
474
474
|
* Uses PFS (Bearer token) or Developer API (X-API-Key) depending on config.
|
|
475
475
|
* PFS: async polling (POST /api/search -> poll /api/results/<id> every 10s).
|
|
476
|
-
* Developer API: synchronous
|
|
476
|
+
* Developer API: synchronous call.
|
|
477
477
|
*
|
|
478
478
|
* @param origin - IATA code (e.g., "GDN", "LON")
|
|
479
479
|
* @param destination - IATA code (e.g., "BER", "BCN")
|
|
@@ -496,8 +496,8 @@ declare class LetsFG {
|
|
|
496
496
|
resolveLocation(query: string): Promise<Array<Record<string, unknown>>>;
|
|
497
497
|
/**
|
|
498
498
|
* Unlock a flight offer — confirms live price, reveals direct airline booking URL.
|
|
499
|
-
*
|
|
500
|
-
*
|
|
499
|
+
* Developer API only, legacy — there is no unlock endpoint on a PFS Bearer
|
|
500
|
+
* token, so PFS callers use book() directly.
|
|
501
501
|
*/
|
|
502
502
|
unlock(offerId: string): Promise<UnlockResult>;
|
|
503
503
|
/**
|
package/dist/index.d.ts
CHANGED
|
@@ -283,7 +283,7 @@ declare function getOfferDetailPromptNotes(offer: OfferDetailLike): string[];
|
|
|
283
283
|
* const flights = await bt.search('GDN', 'BER', '2026-03-03');
|
|
284
284
|
*
|
|
285
285
|
* // Developer API (prepaid credits)
|
|
286
|
-
* const bt2 = new LetsFG({ apiKey: '
|
|
286
|
+
* const bt2 = new LetsFG({ apiKey: 'letsfg_...' });
|
|
287
287
|
* const flights2 = await bt2.search('LHR', 'JFK', '2026-04-15');
|
|
288
288
|
* ```
|
|
289
289
|
*/
|
|
@@ -473,7 +473,7 @@ declare class LetsFG {
|
|
|
473
473
|
*
|
|
474
474
|
* Uses PFS (Bearer token) or Developer API (X-API-Key) depending on config.
|
|
475
475
|
* PFS: async polling (POST /api/search -> poll /api/results/<id> every 10s).
|
|
476
|
-
* Developer API: synchronous
|
|
476
|
+
* Developer API: synchronous call.
|
|
477
477
|
*
|
|
478
478
|
* @param origin - IATA code (e.g., "GDN", "LON")
|
|
479
479
|
* @param destination - IATA code (e.g., "BER", "BCN")
|
|
@@ -496,8 +496,8 @@ declare class LetsFG {
|
|
|
496
496
|
resolveLocation(query: string): Promise<Array<Record<string, unknown>>>;
|
|
497
497
|
/**
|
|
498
498
|
* Unlock a flight offer — confirms live price, reveals direct airline booking URL.
|
|
499
|
-
*
|
|
500
|
-
*
|
|
499
|
+
* Developer API only, legacy — there is no unlock endpoint on a PFS Bearer
|
|
500
|
+
* token, so PFS callers use book() directly.
|
|
501
501
|
*/
|
|
502
502
|
unlock(offerId: string): Promise<UnlockResult>;
|
|
503
503
|
/**
|
package/dist/index.js
CHANGED
|
@@ -1606,8 +1606,12 @@ function cheapestOffer(result) {
|
|
|
1606
1606
|
return result.offers.reduce((min, o) => o.price < min.price ? o : min, result.offers[0]);
|
|
1607
1607
|
}
|
|
1608
1608
|
var DEFAULT_BASE_URL = "https://letsfg.co";
|
|
1609
|
-
var PFS_POLL_INTERVAL_MS =
|
|
1609
|
+
var PFS_POLL_INTERVAL_MS = 2e3;
|
|
1610
1610
|
var PFS_POLL_TIMEOUT_MS = 12e4;
|
|
1611
|
+
var LATE_MERGE_POLL_MS = 3e3;
|
|
1612
|
+
var LATE_MERGE_GRACE_MS = 9e4;
|
|
1613
|
+
var WAIT_FOR_SPLIT = (process.env.LETSFG_WAIT_FOR_SPLIT || "").trim() !== "0";
|
|
1614
|
+
var NON_TERMINAL = ["pending", "searching"];
|
|
1611
1615
|
var LetsFG = class {
|
|
1612
1616
|
bearerToken;
|
|
1613
1617
|
apiKey;
|
|
@@ -1643,7 +1647,7 @@ var LetsFG = class {
|
|
|
1643
1647
|
*
|
|
1644
1648
|
* Uses PFS (Bearer token) or Developer API (X-API-Key) depending on config.
|
|
1645
1649
|
* PFS: async polling (POST /api/search -> poll /api/results/<id> every 10s).
|
|
1646
|
-
* Developer API: synchronous
|
|
1650
|
+
* Developer API: synchronous call.
|
|
1647
1651
|
*
|
|
1648
1652
|
* @param origin - IATA code (e.g., "GDN", "LON")
|
|
1649
1653
|
* @param destination - IATA code (e.g., "BER", "BCN")
|
|
@@ -1675,17 +1679,28 @@ var LetsFG = class {
|
|
|
1675
1679
|
/** PFS path: POST /api/search -> poll /api/results/<id> */
|
|
1676
1680
|
async searchPFS(body) {
|
|
1677
1681
|
const { search_id } = await this.postWithBearer("/api/search", body);
|
|
1682
|
+
const poll = () => this.getNoAuth(`/api/results/${search_id}`);
|
|
1683
|
+
const inbound = (r) => Boolean(r.split_ticket_pending || r.gf_enrich_pending);
|
|
1678
1684
|
const deadline = Date.now() + PFS_POLL_TIMEOUT_MS;
|
|
1685
|
+
let terminal = null;
|
|
1679
1686
|
while (Date.now() < deadline) {
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
if (!["pending", "searching"].includes(result.status)) {
|
|
1685
|
-
return result;
|
|
1687
|
+
const result = await poll();
|
|
1688
|
+
if (!NON_TERMINAL.includes(result.status)) {
|
|
1689
|
+
terminal = result;
|
|
1690
|
+
break;
|
|
1686
1691
|
}
|
|
1692
|
+
await new Promise((r) => setTimeout(r, PFS_POLL_INTERVAL_MS));
|
|
1693
|
+
}
|
|
1694
|
+
if (!terminal) {
|
|
1695
|
+
throw new LetsFGError("Search timed out after 120s. Try polling /api/results/<id> directly.", 504);
|
|
1696
|
+
}
|
|
1697
|
+
const lateDeadline = Date.now() + LATE_MERGE_GRACE_MS;
|
|
1698
|
+
while (WAIT_FOR_SPLIT && inbound(terminal) && Date.now() < lateDeadline) {
|
|
1699
|
+
await new Promise((r) => setTimeout(r, LATE_MERGE_POLL_MS));
|
|
1700
|
+
const merged = await poll();
|
|
1701
|
+
if (!NON_TERMINAL.includes(merged.status)) terminal = merged;
|
|
1687
1702
|
}
|
|
1688
|
-
|
|
1703
|
+
return terminal;
|
|
1689
1704
|
}
|
|
1690
1705
|
/**
|
|
1691
1706
|
* Resolve a city/airport name to IATA codes.
|
|
@@ -1705,8 +1720,8 @@ var LetsFG = class {
|
|
|
1705
1720
|
}
|
|
1706
1721
|
/**
|
|
1707
1722
|
* Unlock a flight offer — confirms live price, reveals direct airline booking URL.
|
|
1708
|
-
*
|
|
1709
|
-
*
|
|
1723
|
+
* Developer API only, legacy — there is no unlock endpoint on a PFS Bearer
|
|
1724
|
+
* token, so PFS callers use book() directly.
|
|
1710
1725
|
*/
|
|
1711
1726
|
async unlock(offerId) {
|
|
1712
1727
|
this.requireApiKey();
|
package/dist/index.mjs
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "letsfg",
|
|
3
|
-
"version": "2026.5.
|
|
3
|
+
"version": "2026.5.71",
|
|
4
4
|
"description": "Flights and hotels for AI agents. Server-side engine covers hundreds of airlines; hotels are real bookable inventory with free cancellation and pay-later terms. Includes open-source ranking engine.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/index.mjs",
|