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 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** | 6090 s | 2–5 s (discover) · 6090 s (full) |
14
+ | **Speed** | 810 s to first results; longer on a split | 2–5 s (discover) · 810 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 = 1e4;
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 60-90s call.
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
- await new Promise((r) => setTimeout(r, PFS_POLL_INTERVAL_MS));
1633
- const result = await this.getNoAuth(
1634
- `/api/results/${search_id}`
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
- throw new LetsFGError("Search timed out after 120s. Try polling /api/results/<id> directly.", 504);
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
- * Cost: 1% of ticket price, min $3. Developer API only — there is no unlock
1661
- * endpoint on a PFS Bearer token, so PFS callers use book() directly.
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 = 1e4;
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 60-90s call.
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
- await new Promise((r) => setTimeout(r, PFS_POLL_INTERVAL_MS));
529
- const result = await this.getNoAuth(
530
- `/api/results/${search_id}`
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
- throw new LetsFGError("Search timed out after 120s. Try polling /api/results/<id> directly.", 504);
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
- * Cost: 1% of ticket price, min $3. Developer API only — there is no unlock
557
- * endpoint on a PFS Bearer token, so PFS callers use book() directly.
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 \u2014 1% of ticket (min $3)
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-SYNHNDEL.mjs";
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 \u2014 1% of ticket (min $3)
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: 'trav_...' });
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 60-90s call.
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
- * Cost: 1% of ticket price, min $3. Developer API only — there is no unlock
500
- * endpoint on a PFS Bearer token, so PFS callers use book() directly.
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: 'trav_...' });
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 60-90s call.
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
- * Cost: 1% of ticket price, min $3. Developer API only — there is no unlock
500
- * endpoint on a PFS Bearer token, so PFS callers use book() directly.
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 = 1e4;
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 60-90s call.
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
- await new Promise((r) => setTimeout(r, PFS_POLL_INTERVAL_MS));
1681
- const result = await this.getNoAuth(
1682
- `/api/results/${search_id}`
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
- throw new LetsFGError("Search timed out after 120s. Try polling /api/results/<id> directly.", 504);
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
- * Cost: 1% of ticket price, min $3. Developer API only — there is no unlock
1709
- * endpoint on a PFS Bearer token, so PFS callers use book() directly.
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
@@ -22,7 +22,7 @@ import {
22
22
  offerSummary,
23
23
  rankOffers,
24
24
  selectDiverseTop
25
- } from "./chunk-SYNHNDEL.mjs";
25
+ } from "./chunk-GO3FXQXC.mjs";
26
26
  export {
27
27
  AuthenticationError,
28
28
  BoostedTravel,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "letsfg",
3
- "version": "2026.5.70",
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",