letsfg 2026.5.72 → 2026.5.74
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 +89 -49
- package/dist/{chunk-GO3FXQXC.mjs → chunk-GVKYPTY3.mjs} +214 -62
- package/dist/cli.js +237 -101
- package/dist/cli.mjs +25 -40
- package/dist/index.d.mts +172 -46
- package/dist/index.d.ts +172 -46
- package/dist/index.js +215 -62
- package/dist/index.mjs +3 -1
- package/package.json +56 -56
package/dist/index.js
CHANGED
|
@@ -25,6 +25,7 @@ __export(index_exports, {
|
|
|
25
25
|
BoostedTravelError: () => BoostedTravelError,
|
|
26
26
|
ErrorCategory: () => ErrorCategory,
|
|
27
27
|
ErrorCode: () => ErrorCode,
|
|
28
|
+
HOTEL_BOOKING_FINAL_STATUSES: () => HOTEL_BOOKING_FINAL_STATUSES,
|
|
28
29
|
LetsFG: () => LetsFG,
|
|
29
30
|
LetsFGError: () => LetsFGError,
|
|
30
31
|
OfferExpiredError: () => OfferExpiredError,
|
|
@@ -1612,6 +1613,7 @@ var LATE_MERGE_POLL_MS = 3e3;
|
|
|
1612
1613
|
var LATE_MERGE_GRACE_MS = 9e4;
|
|
1613
1614
|
var WAIT_FOR_SPLIT = (process.env.LETSFG_WAIT_FOR_SPLIT || "").trim() !== "0";
|
|
1614
1615
|
var NON_TERMINAL = ["pending", "searching"];
|
|
1616
|
+
var HOTEL_BOOKING_FINAL_STATUSES = ["succeeded", "failed", "attention"];
|
|
1615
1617
|
var LetsFG = class {
|
|
1616
1618
|
bearerToken;
|
|
1617
1619
|
apiKey;
|
|
@@ -1719,13 +1721,23 @@ var LetsFG = class {
|
|
|
1719
1721
|
return Array.isArray(data) ? data : data.locations || [];
|
|
1720
1722
|
}
|
|
1721
1723
|
/**
|
|
1722
|
-
*
|
|
1723
|
-
*
|
|
1724
|
-
*
|
|
1724
|
+
* RETIRED 2026-09-08. Throws instead of calling the server.
|
|
1725
|
+
*
|
|
1726
|
+
* There is no unlock step on any lane. Unlock existed to confirm a live price
|
|
1727
|
+
* before charging; booking now HOLDS the fare on the connected payment method
|
|
1728
|
+
* and captures only once a real airline PNR exists, so a fare that moved
|
|
1729
|
+
* cannot become a charge for a ticket you did not get. If it moves at
|
|
1730
|
+
* checkout you get a `price_change` question to accept or decline instead.
|
|
1731
|
+
*
|
|
1732
|
+
* Kept as a method, and throwing locally rather than making the request, so an
|
|
1733
|
+
* older caller gets one clear sentence at the line that is actually wrong —
|
|
1734
|
+
* not a 410 body to decode, and not a TypeError somewhere else.
|
|
1725
1735
|
*/
|
|
1726
|
-
async unlock(
|
|
1727
|
-
|
|
1728
|
-
|
|
1736
|
+
async unlock(_offerId) {
|
|
1737
|
+
throw new LetsFGError(
|
|
1738
|
+
"unlock() was retired on 2026-09-08 and the endpoint answers 410 Gone. There is no unlock step: call book() directly. The fare is held on the connected payment method and captured only against a real airline PNR. See https://letsfg.co/developers/api/docs",
|
|
1739
|
+
410
|
|
1740
|
+
);
|
|
1729
1741
|
}
|
|
1730
1742
|
/**
|
|
1731
1743
|
* Book a flight.
|
|
@@ -1736,9 +1748,14 @@ var LetsFG = class {
|
|
|
1736
1748
|
* complete, { ok, booked: false, booking_url } — hand the link to the user,
|
|
1737
1749
|
* nothing was charged.
|
|
1738
1750
|
*
|
|
1739
|
-
* Developer API (X-API-Key):
|
|
1740
|
-
*
|
|
1741
|
-
*
|
|
1751
|
+
* Developer API (X-API-Key): POST /flights/book. NO unlock step. searchId is
|
|
1752
|
+
* REQUIRED — an offer is bookable only inside the search that produced it.
|
|
1753
|
+
* The connected Revolut method is HELD, not charged; a LetsFG booking agent
|
|
1754
|
+
* buys the ticket and the hold is captured only against a real airline PNR.
|
|
1755
|
+
* Returns the 202 { ok, booking_id, state, held, charged: 0, poll_url } —
|
|
1756
|
+
* poll getBooking(bookingId) until `terminal`, or use bookAndWait().
|
|
1757
|
+
* Always provide idempotencyKey: a retry with the same key returns the
|
|
1758
|
+
* existing booking instead of opening a second hold on the card.
|
|
1742
1759
|
*/
|
|
1743
1760
|
async book(offerId, passengers, contactEmail, contactPhone = "", idempotencyKey = "", searchId = "") {
|
|
1744
1761
|
this.requireAuth();
|
|
@@ -1759,28 +1776,107 @@ var LetsFG = class {
|
|
|
1759
1776
|
});
|
|
1760
1777
|
}
|
|
1761
1778
|
this.requireApiKey();
|
|
1779
|
+
if (!searchId) {
|
|
1780
|
+
throw new LetsFGError(
|
|
1781
|
+
"searchId is required to book on the Developer API \u2014 pass the search_id from search()'s result. An offer can only be booked inside the search that produced it. (Before 2026-09-08 this argument was ignored on this path.)",
|
|
1782
|
+
400
|
|
1783
|
+
);
|
|
1784
|
+
}
|
|
1785
|
+
const pax = passengers.map((p) => ({ ...p }));
|
|
1786
|
+
if (contactPhone && pax.length && !pax[0].phone_number) pax[0].phone_number = contactPhone;
|
|
1762
1787
|
const body = {
|
|
1788
|
+
search_id: searchId,
|
|
1763
1789
|
offer_id: offerId,
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
contact_email: contactEmail,
|
|
1767
|
-
contact_phone: contactPhone
|
|
1790
|
+
passengers: pax,
|
|
1791
|
+
contact_email: contactEmail
|
|
1768
1792
|
};
|
|
1769
1793
|
if (idempotencyKey) body.idempotency_key = idempotencyKey;
|
|
1770
|
-
return this.post("/developers/api/v1/
|
|
1794
|
+
return this.post("/developers/api/v1/flights/book", body);
|
|
1795
|
+
}
|
|
1796
|
+
/**
|
|
1797
|
+
* Poll a Developer API flight booking.
|
|
1798
|
+
*
|
|
1799
|
+
* Poll every few seconds until `terminal` is true. The poll is ALSO how LetsFG
|
|
1800
|
+
* knows you are still there, which is what keeps a booking paused on a
|
|
1801
|
+
* question alive — so do not back off to minutes.
|
|
1802
|
+
*
|
|
1803
|
+
* States: authorised, card_issued, booking_in_progress, awaiting_settlement,
|
|
1804
|
+
* then completed (with `pnr` and `charged_amount`), failed (hold released,
|
|
1805
|
+
* nothing charged) or needs_attention (a human at LetsFG is on it — do not
|
|
1806
|
+
* book again).
|
|
1807
|
+
*/
|
|
1808
|
+
async getBooking(bookingId) {
|
|
1809
|
+
this.requireApiKey();
|
|
1810
|
+
return this.getWithAuth(
|
|
1811
|
+
`/developers/api/v1/flights/bookings/${encodeURIComponent(bookingId)}`
|
|
1812
|
+
);
|
|
1813
|
+
}
|
|
1814
|
+
/**
|
|
1815
|
+
* Answer the open `question` on a booking.
|
|
1816
|
+
*
|
|
1817
|
+
* Echo the question's `round`. A stale round is refused with 409 rather than
|
|
1818
|
+
* guessed at, so an answer to an old question can never be applied to a new
|
|
1819
|
+
* one. Seat: { seats: [...] } or { skip: true }. Price change or paid extra:
|
|
1820
|
+
* { confirm: true } or { skip: true } — declining an extra still completes
|
|
1821
|
+
* the booking, without it.
|
|
1822
|
+
*/
|
|
1823
|
+
async answerBooking(bookingId, round, answer = {}) {
|
|
1824
|
+
this.requireApiKey();
|
|
1825
|
+
return this.post(
|
|
1826
|
+
`/developers/api/v1/flights/bookings/${encodeURIComponent(bookingId)}/answer`,
|
|
1827
|
+
{ round, ...answer }
|
|
1828
|
+
);
|
|
1829
|
+
}
|
|
1830
|
+
/**
|
|
1831
|
+
* Book and poll to a terminal state. Mirrors bookHotelAndWait().
|
|
1832
|
+
*
|
|
1833
|
+
* Blocks for as long as the booking takes (4–11 minutes typically), so use
|
|
1834
|
+
* book() + getBooking() instead if your caller has a request timeout.
|
|
1835
|
+
*
|
|
1836
|
+
* `onQuestion` returns the answer for answerBooking(). Without it, a fare
|
|
1837
|
+
* increase is ACCEPTED and a paid extra is DECLINED — the conservative
|
|
1838
|
+
* reading of "the traveller asked for this flight".
|
|
1839
|
+
*/
|
|
1840
|
+
async bookAndWait(offerId, passengers, contactEmail, searchId, opts = {}) {
|
|
1841
|
+
const { contactPhone = "", idempotencyKey = "", pollMs = 5e3, timeoutMs = 9e5, onQuestion } = opts;
|
|
1842
|
+
const started = await this.book(
|
|
1843
|
+
offerId,
|
|
1844
|
+
passengers,
|
|
1845
|
+
contactEmail,
|
|
1846
|
+
contactPhone,
|
|
1847
|
+
idempotencyKey,
|
|
1848
|
+
searchId
|
|
1849
|
+
);
|
|
1850
|
+
if (!started || started.ok !== true) return started;
|
|
1851
|
+
const bookingId = String(started.booking_id);
|
|
1852
|
+
const deadline = Date.now() + timeoutMs;
|
|
1853
|
+
while (Date.now() < deadline) {
|
|
1854
|
+
await new Promise((r) => setTimeout(r, pollMs));
|
|
1855
|
+
const state = await this.getBooking(bookingId);
|
|
1856
|
+
const question = state.question;
|
|
1857
|
+
if (question) {
|
|
1858
|
+
const answer = onQuestion ? onQuestion(question) : question.kind === "extra" ? { skip: true } : { confirm: true };
|
|
1859
|
+
await this.answerBooking(bookingId, Number(question.round), answer);
|
|
1860
|
+
continue;
|
|
1861
|
+
}
|
|
1862
|
+
if (state.terminal) return state;
|
|
1863
|
+
}
|
|
1864
|
+
return this.getBooking(bookingId);
|
|
1771
1865
|
}
|
|
1772
1866
|
// ── Hotels ──────────────────────────────────────────────────────────
|
|
1773
1867
|
//
|
|
1774
|
-
// A
|
|
1775
|
-
// deliberate: a hotel search opens a real session at the
|
|
1776
|
-
// blocks a real rate, so a caller is never allowed to
|
|
1777
|
-
// commitment only to discover it cannot pay. The same
|
|
1778
|
-
// flight booking authorises hotels
|
|
1868
|
+
// A connected payment method is required for EVERY hotel call, search
|
|
1869
|
+
// included. That is deliberate: a hotel search opens a real session at the
|
|
1870
|
+
// supplier and booking blocks a real rate, so a caller is never allowed to
|
|
1871
|
+
// reach the point of commitment only to discover it cannot pay. The same
|
|
1872
|
+
// method that authorises flight booking authorises hotels.
|
|
1779
1873
|
//
|
|
1780
|
-
//
|
|
1781
|
-
//
|
|
1782
|
-
//
|
|
1783
|
-
//
|
|
1874
|
+
// How a hotel is paid (since 2026-09-11): the full `price` is HELD on the
|
|
1875
|
+
// connected Revolut method, LetsFG books and pays the supplier itself, and the
|
|
1876
|
+
// hold is captured only once the supplier has confirmed. A booking that fails
|
|
1877
|
+
// releases the hold. There is no reservation fee, no deposit and no pay link —
|
|
1878
|
+
// those belonged to the process retired on 2026-09-11. Every rate type is
|
|
1879
|
+
// sold, refundable and non-refundable.
|
|
1784
1880
|
/**
|
|
1785
1881
|
* Resolve a place name to the city id that searchHotels() needs.
|
|
1786
1882
|
*
|
|
@@ -1801,12 +1897,17 @@ var LetsFG = class {
|
|
|
1801
1897
|
* Slow by nature — the supplier streams a whole city and every rate is priced
|
|
1802
1898
|
* — so this gets its own generous timeout rather than the client default.
|
|
1803
1899
|
*
|
|
1804
|
-
*
|
|
1805
|
-
*
|
|
1806
|
-
* `
|
|
1900
|
+
* The response carries `session_id`, `currency`, `supplier_currency`,
|
|
1901
|
+
* `markup_rate`, `fx_rate`, `fx_as_of`, `count`, `hotels`, `terms` and
|
|
1902
|
+
* `caveats`. Each offer carries `price` (what the guest pays, in `currency`),
|
|
1903
|
+
* `currency`, `fx_rate`, `expected_cost` (the supplier's cost, in PLN),
|
|
1904
|
+
* `refundable`, `free_cancellation_until` (refundable rates only),
|
|
1905
|
+
* `cancellation_policy` and its own `session_id`.
|
|
1807
1906
|
*
|
|
1808
|
-
*
|
|
1809
|
-
*
|
|
1907
|
+
* `price` is the supplier's cost plus `markup_rate` (6.4% for Revolut Pay or an
|
|
1908
|
+
* EEA-issued card, 8.3% for a card issued outside the EEA); nothing is added at
|
|
1909
|
+
* booking. Keep the chosen offer whole: bookHotel() needs its `session_id`,
|
|
1910
|
+
* `combination_id_v2`, `price`, `expected_cost`, `currency` and `fx_rate`.
|
|
1810
1911
|
*/
|
|
1811
1912
|
async searchHotels(params) {
|
|
1812
1913
|
this.requireApiKey();
|
|
@@ -1819,7 +1920,8 @@ var LetsFG = class {
|
|
|
1819
1920
|
children: params.children ?? 0,
|
|
1820
1921
|
nationality: params.nationality ?? "PL",
|
|
1821
1922
|
limit: params.limit ?? 40,
|
|
1822
|
-
with_images: params.withImages ?? true
|
|
1923
|
+
with_images: params.withImages ?? true,
|
|
1924
|
+
currency: params.currency ?? "USD"
|
|
1823
1925
|
};
|
|
1824
1926
|
if (params.childAges?.length) body.child_ages = params.childAges;
|
|
1825
1927
|
return this.post("/developers/api/v1/hotels/search", body, 24e4);
|
|
@@ -1827,30 +1929,42 @@ var LetsFG = class {
|
|
|
1827
1929
|
/**
|
|
1828
1930
|
* Start a booking. Returns a job immediately — it does NOT book inline.
|
|
1829
1931
|
*
|
|
1830
|
-
*
|
|
1831
|
-
*
|
|
1832
|
-
*
|
|
1833
|
-
*
|
|
1834
|
-
*
|
|
1932
|
+
* What happens, in order: the offer's full `price` is HELD on the Revolut
|
|
1933
|
+
* payment method connected to this account (authorised, not taken); LetsFG
|
|
1934
|
+
* books the room with the supplier and pays the supplier itself; the hold is
|
|
1935
|
+
* captured only once the supplier has confirmed. If the booking fails for any
|
|
1936
|
+
* reason, the hold is released and nothing is charged. There is no
|
|
1937
|
+
* reservation fee, no deposit and no pay link.
|
|
1835
1938
|
*
|
|
1836
|
-
*
|
|
1837
|
-
*
|
|
1939
|
+
* A booking takes minutes and no proxy holds a connection that long, so this
|
|
1940
|
+
* returns at once and you poll hotelBooking() for the outcome. Use
|
|
1941
|
+
* bookHotelAndWait() if you would rather block.
|
|
1838
1942
|
*
|
|
1839
|
-
* Send `expectedPrice`
|
|
1840
|
-
* them
|
|
1841
|
-
*
|
|
1943
|
+
* Send `expectedPrice` (the offer's `price`), `expectedCost`, `currency` and
|
|
1944
|
+
* `fxRate` exactly as search returned them. The booking is refused if the
|
|
1945
|
+
* supplier's live cost is above `expectedCost`, and a USD offer sent without
|
|
1946
|
+
* its `currency` is refused with 400 price_mismatch (the API assumes PLN).
|
|
1947
|
+
* Guest names, phone and e-mail are checked before anything is held; a problem
|
|
1948
|
+
* returns 400 invalid_details naming the fields.
|
|
1842
1949
|
*
|
|
1843
|
-
* Do NOT call this again for
|
|
1844
|
-
* the
|
|
1950
|
+
* Do NOT call this again for a booking whose job is still running: poll it.
|
|
1951
|
+
* A retry of the same booking returns the job already under way
|
|
1952
|
+
* (`duplicate: true`) rather than holding the money twice.
|
|
1845
1953
|
*/
|
|
1846
1954
|
async bookHotel(params) {
|
|
1847
1955
|
this.requireApiKey();
|
|
1956
|
+
if (typeof params.expectedCost !== "number") {
|
|
1957
|
+
throw new LetsFGError(
|
|
1958
|
+
"bookHotel() needs expectedCost: copy the offer's expected_cost, currency and fx_rate. " + ("expectedBalance" in params ? "expectedBalance belonged to the reservation-fee process retired on 2026-09-11 and is not sent. " : "") + "See https://letsfg.co/developers/api/docs",
|
|
1959
|
+
400
|
|
1960
|
+
);
|
|
1961
|
+
}
|
|
1848
1962
|
const body = {
|
|
1849
1963
|
session_id: params.sessionId,
|
|
1850
1964
|
hotel_code: params.hotelCode,
|
|
1851
1965
|
combination_id_v2: params.combinationIdV2,
|
|
1852
1966
|
expected_price: params.expectedPrice,
|
|
1853
|
-
|
|
1967
|
+
expected_cost: params.expectedCost,
|
|
1854
1968
|
city_id: params.cityId,
|
|
1855
1969
|
city_name: params.cityName,
|
|
1856
1970
|
check_in: params.checkIn,
|
|
@@ -1862,16 +1976,30 @@ var LetsFG = class {
|
|
|
1862
1976
|
phone_country_code: params.phoneCountryCode ?? "48",
|
|
1863
1977
|
special_requests: params.specialRequests ?? []
|
|
1864
1978
|
};
|
|
1979
|
+
if (params.currency) body.currency = params.currency;
|
|
1980
|
+
if (params.fxRate != null) body.fx_rate = params.fxRate;
|
|
1865
1981
|
if (params.combinationId != null) body.combination_id = params.combinationId;
|
|
1866
1982
|
if (params.hotelName) body.hotel_name = params.hotelName;
|
|
1983
|
+
if (params.idempotencyKey) body.idempotency_key = params.idempotencyKey;
|
|
1867
1984
|
return this.post("/developers/api/v1/hotels/book", body, 9e4);
|
|
1868
1985
|
}
|
|
1869
1986
|
/**
|
|
1870
1987
|
* Collect the result of a booking started with bookHotel().
|
|
1871
1988
|
*
|
|
1872
|
-
* `status` is 'in_progress', 'succeeded'
|
|
1873
|
-
*
|
|
1874
|
-
*
|
|
1989
|
+
* `status` is 'in_progress', 'succeeded', 'failed' or 'attention'; the last
|
|
1990
|
+
* three are final (HOTEL_BOOKING_FINAL_STATUSES).
|
|
1991
|
+
*
|
|
1992
|
+
* - succeeded: `confirmation`, `booking_id`, `hotel`, `room`, `total_price` +
|
|
1993
|
+
* `currency` (what the guest is charged), `supplier_paid` +
|
|
1994
|
+
* `supplier_currency` (what the supplier was paid), `payment_status`,
|
|
1995
|
+
* `refundable`, `free_cancellation_until`, `cancellation_ladder`, `terms`.
|
|
1996
|
+
* - failed: `error`, written for the guest. The hold has been released and
|
|
1997
|
+
* nothing was charged.
|
|
1998
|
+
* - attention: `error` (and `confirmation` when known). The outcome could not
|
|
1999
|
+
* be settled automatically; the hold is kept — nothing is charged — while a
|
|
2000
|
+
* person checks with the supplier. Do not book again.
|
|
2001
|
+
*
|
|
2002
|
+
* The guest is e-mailed in every case.
|
|
1875
2003
|
*/
|
|
1876
2004
|
async hotelBooking(bookingJobId) {
|
|
1877
2005
|
this.requireApiKey();
|
|
@@ -1883,13 +2011,15 @@ var LetsFG = class {
|
|
|
1883
2011
|
/**
|
|
1884
2012
|
* bookHotel(), then poll until the booking settles. Convenience only.
|
|
1885
2013
|
*
|
|
1886
|
-
*
|
|
1887
|
-
*
|
|
1888
|
-
*
|
|
2014
|
+
* Stops at 'succeeded', 'failed' or 'attention' and never re-books. Giving up
|
|
2015
|
+
* after `maxWaitMs` (default 30 minutes: a booking usually takes 5-10, and
|
|
2016
|
+
* hotel bookings run one at a time) does NOT cancel anything — the booking may
|
|
2017
|
+
* still complete. The returned object carries `booking_job_id` so you can keep
|
|
2018
|
+
* polling, and the guest is e-mailed the outcome regardless.
|
|
1889
2019
|
*/
|
|
1890
2020
|
async bookHotelAndWait(params) {
|
|
1891
2021
|
const pollIntervalMs = params.pollIntervalMs ?? 2e4;
|
|
1892
|
-
const maxWaitMs = params.maxWaitMs ??
|
|
2022
|
+
const maxWaitMs = params.maxWaitMs ?? 18e5;
|
|
1893
2023
|
const job = await this.bookHotel(params);
|
|
1894
2024
|
const jobId = job.booking_job_id;
|
|
1895
2025
|
if (!jobId) return job;
|
|
@@ -1899,19 +2029,19 @@ var LetsFG = class {
|
|
|
1899
2029
|
await new Promise((r) => setTimeout(r, pollIntervalMs));
|
|
1900
2030
|
waited += pollIntervalMs;
|
|
1901
2031
|
result = await this.hotelBooking(jobId);
|
|
1902
|
-
|
|
1903
|
-
if (st === "succeeded" || st === "failed") return result;
|
|
2032
|
+
if (HOTEL_BOOKING_FINAL_STATUSES.includes(result.status)) return result;
|
|
1904
2033
|
}
|
|
1905
2034
|
if (result.booking_job_id == null) result.booking_job_id = jobId;
|
|
1906
2035
|
return result;
|
|
1907
2036
|
}
|
|
1908
2037
|
/**
|
|
1909
|
-
* Release a reservation at the supplier.
|
|
2038
|
+
* Release a reservation at the supplier and refund the guest.
|
|
1910
2039
|
*
|
|
1911
|
-
*
|
|
1912
|
-
*
|
|
1913
|
-
*
|
|
1914
|
-
*
|
|
2040
|
+
* Only this account's own bookings can be cancelled (anything else is 404). A
|
|
2041
|
+
* zero-charge cancellation — a refundable rate before its
|
|
2042
|
+
* `free_cancellation_until` — refunds the charge in full, or releases a hold
|
|
2043
|
+
* not yet captured. A cancellation that would cost money is refused with 409;
|
|
2044
|
+
* the hotel's own ladder is in the booking's `terms`.
|
|
1915
2045
|
*
|
|
1916
2046
|
* This drives a browser at the supplier and takes over a minute. If it times
|
|
1917
2047
|
* out, do not assume it failed — re-check before retrying.
|
|
@@ -1925,15 +2055,37 @@ var LetsFG = class {
|
|
|
1925
2055
|
);
|
|
1926
2056
|
}
|
|
1927
2057
|
/**
|
|
1928
|
-
* [Developer API
|
|
2058
|
+
* [Developer API] Mint a one-time link for connecting a Revolut payment method.
|
|
2059
|
+
*
|
|
2060
|
+
* This replaced setupPayment() on 2026-09-08. Nothing is charged to connect, and
|
|
2061
|
+
* card details never touch LetsFG: the returned `connect_url` opens a hosted page
|
|
2062
|
+
* where the developer saves a card, Revolut Pay or Google Pay. A PERSON must open
|
|
2063
|
+
* it in a browser — there is no endpoint that takes card details, so do not ask a
|
|
2064
|
+
* user for a card number and do not try to automate this step.
|
|
1929
2065
|
*
|
|
1930
|
-
* Most agents should NOT
|
|
1931
|
-
* search and booking
|
|
1932
|
-
* through a zero-amount setup and creates no billing account.
|
|
2066
|
+
* Most agents should NOT need a Developer API account at all. To authenticate for
|
|
2067
|
+
* search and booking, run `letsfg auth`, which creates no billing account.
|
|
1933
2068
|
*/
|
|
1934
|
-
async
|
|
2069
|
+
async connectPayment() {
|
|
1935
2070
|
this.requireApiKey();
|
|
1936
|
-
return this.post("/developers/api/v1/agents/
|
|
2071
|
+
return this.post("/developers/api/v1/agents/connect-payment", {});
|
|
2072
|
+
}
|
|
2073
|
+
/**
|
|
2074
|
+
* RETIRED 2026-09-08 with Stripe. Throws instead of calling the server.
|
|
2075
|
+
*
|
|
2076
|
+
* `/agents/setup-payment` answers 410 Gone. Payment enrolment moved onto the same
|
|
2077
|
+
* Revolut rail as the rest of the product: call connectPayment() and open the
|
|
2078
|
+
* `connect_url` it returns.
|
|
2079
|
+
*
|
|
2080
|
+
* Kept as a method, and throwing locally rather than making the request, for the
|
|
2081
|
+
* same reason as unlock() — an older caller gets one clear sentence at the line that
|
|
2082
|
+
* is actually wrong, not a 410 body to decode and not a TypeError somewhere else.
|
|
2083
|
+
*/
|
|
2084
|
+
async setupPayment(_token) {
|
|
2085
|
+
throw new LetsFGError(
|
|
2086
|
+
"setupPayment() was retired on 2026-09-08 with Stripe and the endpoint answers 410 Gone. Call connectPayment() instead and open the connect_url it returns; nothing is charged to connect. See https://letsfg.co/developers/api/docs",
|
|
2087
|
+
410
|
|
2088
|
+
);
|
|
1937
2089
|
}
|
|
1938
2090
|
/**
|
|
1939
2091
|
* Get current agent profile and usage stats.
|
|
@@ -2028,6 +2180,7 @@ var BoostedTravelError = LetsFGError;
|
|
|
2028
2180
|
BoostedTravelError,
|
|
2029
2181
|
ErrorCategory,
|
|
2030
2182
|
ErrorCode,
|
|
2183
|
+
HOTEL_BOOKING_FINAL_STATUSES,
|
|
2031
2184
|
LetsFG,
|
|
2032
2185
|
LetsFGError,
|
|
2033
2186
|
OfferExpiredError,
|
package/dist/index.mjs
CHANGED
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
BoostedTravelError,
|
|
5
5
|
ErrorCategory,
|
|
6
6
|
ErrorCode,
|
|
7
|
+
HOTEL_BOOKING_FINAL_STATUSES,
|
|
7
8
|
LetsFG,
|
|
8
9
|
LetsFGError,
|
|
9
10
|
OfferExpiredError,
|
|
@@ -22,13 +23,14 @@ import {
|
|
|
22
23
|
offerSummary,
|
|
23
24
|
rankOffers,
|
|
24
25
|
selectDiverseTop
|
|
25
|
-
} from "./chunk-
|
|
26
|
+
} from "./chunk-GVKYPTY3.mjs";
|
|
26
27
|
export {
|
|
27
28
|
AuthenticationError,
|
|
28
29
|
BoostedTravel,
|
|
29
30
|
BoostedTravelError,
|
|
30
31
|
ErrorCategory,
|
|
31
32
|
ErrorCode,
|
|
33
|
+
HOTEL_BOOKING_FINAL_STATUSES,
|
|
32
34
|
LetsFG,
|
|
33
35
|
LetsFGError,
|
|
34
36
|
OfferExpiredError,
|
package/package.json
CHANGED
|
@@ -1,56 +1,56 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "letsfg",
|
|
3
|
-
"version": "2026.5.
|
|
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
|
-
"main": "dist/index.js",
|
|
6
|
-
"module": "dist/index.mjs",
|
|
7
|
-
"types": "dist/index.d.ts",
|
|
8
|
-
"bin": {
|
|
9
|
-
"letsfg": "dist/cli.js"
|
|
10
|
-
},
|
|
11
|
-
"files": [
|
|
12
|
-
"dist/",
|
|
13
|
-
"README.md",
|
|
14
|
-
"LICENSE"
|
|
15
|
-
],
|
|
16
|
-
"scripts": {
|
|
17
|
-
"build": "tsup src/index.ts src/cli.ts --format cjs,esm --dts --clean",
|
|
18
|
-
"test": "tsx --test src/index.test.ts src/auth.test.ts",
|
|
19
|
-
"prepublishOnly": "npm run build"
|
|
20
|
-
},
|
|
21
|
-
"keywords": [
|
|
22
|
-
"flights",
|
|
23
|
-
"travel",
|
|
24
|
-
"booking",
|
|
25
|
-
"agent",
|
|
26
|
-
"ai",
|
|
27
|
-
"cli",
|
|
28
|
-
"autonomous",
|
|
29
|
-
"mcp",
|
|
30
|
-
"openai",
|
|
31
|
-
"airline",
|
|
32
|
-
"ndc",
|
|
33
|
-
"search",
|
|
34
|
-
"ndc",
|
|
35
|
-
"hotels",
|
|
36
|
-
"hotel-booking",
|
|
37
|
-
"hotel-search",
|
|
38
|
-
"accommodation"
|
|
39
|
-
],
|
|
40
|
-
"author": "LetsFG",
|
|
41
|
-
"license": "MIT",
|
|
42
|
-
"repository": {
|
|
43
|
-
"type": "git",
|
|
44
|
-
"url": "https://github.com/LetsFG/LetsFG.git"
|
|
45
|
-
},
|
|
46
|
-
"homepage": "https://letsfg.co",
|
|
47
|
-
"devDependencies": {
|
|
48
|
-
"@types/node": "^25.3.3",
|
|
49
|
-
"tsup": "^8.0.0",
|
|
50
|
-
"tsx": "^4.19.0",
|
|
51
|
-
"typescript": "^5.3.0"
|
|
52
|
-
},
|
|
53
|
-
"engines": {
|
|
54
|
-
"node": ">=18.0.0"
|
|
55
|
-
}
|
|
56
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "letsfg",
|
|
3
|
+
"version": "2026.5.74",
|
|
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
|
+
"main": "dist/index.js",
|
|
6
|
+
"module": "dist/index.mjs",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"bin": {
|
|
9
|
+
"letsfg": "dist/cli.js"
|
|
10
|
+
},
|
|
11
|
+
"files": [
|
|
12
|
+
"dist/",
|
|
13
|
+
"README.md",
|
|
14
|
+
"LICENSE"
|
|
15
|
+
],
|
|
16
|
+
"scripts": {
|
|
17
|
+
"build": "tsup src/index.ts src/cli.ts --format cjs,esm --dts --clean",
|
|
18
|
+
"test": "tsx --test src/index.test.ts src/auth.test.ts",
|
|
19
|
+
"prepublishOnly": "npm run build"
|
|
20
|
+
},
|
|
21
|
+
"keywords": [
|
|
22
|
+
"flights",
|
|
23
|
+
"travel",
|
|
24
|
+
"booking",
|
|
25
|
+
"agent",
|
|
26
|
+
"ai",
|
|
27
|
+
"cli",
|
|
28
|
+
"autonomous",
|
|
29
|
+
"mcp",
|
|
30
|
+
"openai",
|
|
31
|
+
"airline",
|
|
32
|
+
"ndc",
|
|
33
|
+
"search",
|
|
34
|
+
"ndc",
|
|
35
|
+
"hotels",
|
|
36
|
+
"hotel-booking",
|
|
37
|
+
"hotel-search",
|
|
38
|
+
"accommodation"
|
|
39
|
+
],
|
|
40
|
+
"author": "LetsFG",
|
|
41
|
+
"license": "MIT",
|
|
42
|
+
"repository": {
|
|
43
|
+
"type": "git",
|
|
44
|
+
"url": "https://github.com/LetsFG/LetsFG.git"
|
|
45
|
+
},
|
|
46
|
+
"homepage": "https://letsfg.co",
|
|
47
|
+
"devDependencies": {
|
|
48
|
+
"@types/node": "^25.3.3",
|
|
49
|
+
"tsup": "^8.0.0",
|
|
50
|
+
"tsx": "^4.19.0",
|
|
51
|
+
"typescript": "^5.3.0"
|
|
52
|
+
},
|
|
53
|
+
"engines": {
|
|
54
|
+
"node": ">=18.0.0"
|
|
55
|
+
}
|
|
56
|
+
}
|