You can build this today. An agent watches a route on a schedule, re-quotes the fare against a live booking API, and when the price crosses your target it stops and asks you. You approve with one tap, and a deterministic API creates the order. The only part you should not automate in 2026 is the yes.
Last verified 21 July 2026.
The pattern in one paragraph
The agent is a scheduler plus a state machine, not a chatbot. Every few hours it searches a route through a flight API, normalizes the offers, and compares the cheapest acceptable one against your target. When something lands at or below target, it re-quotes to confirm the price is real, then sends you the exact offer with a one-time approval token. You reply yes, the agent re-quotes once more, and creates the order with an idempotency key. The LLM plans, filters, and summarizes. The API prices and books. Money never moves on a model’s word. The reference implementation that ships with this guide (TypeScript, in the same repository as this site) implements exactly this loop. It runs fully offline in mock mode, and against Duffel’s test environment when you want real API shapes.
Why full autonomy is the wrong default
Honesty first: almost nobody wants an agent that spends unsupervised, and the agents are not good enough to deserve it anyway.
Consumers have been clear. Across 2026 surveys, only about 2 to 7 percent would let an agent complete a purchase fully on its own. Most travelers want AI to find options while they keep the final call.
The biggest player in AI agreed, with its wallet. OpenAI launched Instant Checkout in September 2025 and pulled it back on 4 March 2026, with travel called out as too complex for in-chat transactions: live pricing, cancellation policies, currencies, and post-booking service. Expedia and Booking.com now run as apps inside ChatGPT and handle the transaction themselves.
And the accuracy problem is measurable. In 2026 head-to-head tests, assistants quoted Frontier fares of $121 to $196 when the actual fare was $262, and steered users toward $1,500 sponsored itineraries over $500 ones. A language model predicting a fare is guessing. Sometimes confidently, sometimes 30 percent low.
The industry’s fix is the same one this guide uses: put a deterministic system under the model. On 1 July 2026, Travelport connected more than 400 European agencies to its TripServices booking API through MCP on exactly this thesis: an LLM cannot reliably confirm a ticket, so it should hand execution to an authoritative API. An agent that watches, plus a human who approves, plus an API that books, is the design that matches reality.
Is the watching worth doing at all? Yes. Airfares genuinely move: carriers reprice continuously and advance-purchase windows create real steps. A patient watcher catches drops a human checking twice a week misses. Just be suspicious of anyone quoting a specific average savings number without a source. We are not going to invent one here.
The state machine
Four states cover the whole product.
WATCHING -> PENDING_APPROVAL fresh re-quote at or below target
PENDING_APPROVAL -> BOOKED human approves and final re-quote is within bounds
PENDING_APPROVAL -> EXPIRED offer expiry or approval window passes with no yes
PENDING_APPROVAL -> WATCHING final re-quote came back above the ceiling
EXPIRED -> WATCHING next poll tick, with a note about the miss
- WATCHING is the resting state. Poll, search, compare, log, sleep.
- PENDING_APPROVAL is entered only after a fresh quote, never from cached search results. The clock is ticking from the moment it is entered.
- BOOKED is terminal for the run. One booking per run, by design.
- EXPIRED is normal, not an error. Offers die every day. The agent records what it missed and goes back to watching.
Everything else in this guide is detail on the transitions.
What you need
An agent runtime. Any LLM harness that can run on a schedule works (Claude Code, a small SDK script, even plain cron calling a TypeScript file). The intelligence is optional garnish for summarizing tradeoffs. The loop, the state machine, and the guardrails are deterministic code.
A supply API. Duffel is the practical choice for a personal build: modern REST, 300 plus airlines, and it can act as merchant of record through Managed Content, so you need no IATA or ARC accreditation. Its test mode (keys prefixed duffel_test) lets you run the whole flow without ticketing anything. Amadeus Self-Service has a free test tier that is useful for search experiments (thousands of free requests per month). Google Flights tracks prices and emails you, which is a fine zero-code baseline, but it exposes no booking API to third parties, so it can only tell you, not act.
Somewhere to run it. Anything always-on: a $5 VPS, a home server, a Raspberry Pi. Polls are minutes to hours apart. This is not high-frequency trading.
The watch loop and re-quoting
The single most important thing to internalize: an offer is only real at order time. Search responses lean on cached fares. Live availability differs. The number that made your agent excited at 03:00 may not exist at 03:04.
So the loop re-quotes twice. Once before asking for approval, so you are approving a price that was real seconds ago. And once immediately before creating the order, so the booking call uses a live offer. Duffel makes the deadline explicit: every offer carries an expires_at timestamp, typically around 30 minutes out, and you should validate it before creating an order.
The reference implementation is driven by one config file. Annotated:
# watch.yaml, the shape the reference implementation reads
route: { origin: AUS, destination: LHR }
dates: { depart: 2026-10-09, return: 2026-10-18 }
cabin: economy
target: 620 # asks for approval at or below this total, USD
ceiling: 660 # hard spend bound, never books above this
refundable_only: true # default on, cancellation is your undo button
poll_minutes: 180 # fares step daily, not secondly, be polite
mode: mock # mock | duffel_test, live requires explicit override
passenger:
name: "SAMPLE/ALEX JORDAN" # exactly as on the passport
approval:
channel: telegram # where the yes lands
ttl_minutes: 25 # kept under the offer's expires_at
Two details earn their lines. ceiling above target gives the final re-quote a little headroom, so a $6 drift does not kill an approved booking, while a real jump does. And passenger.name is in config, verified at approval time, because ticket names must match the passport exactly and corrections after ticketing are expensive or impossible.
The approval step is the product
Do not treat approval as a checkbox. It is a signature. The approval message should contain everything a careful human needs to say yes in ten seconds: exact flights and times, full all-in price, cabin, refundability, the passenger name that will go on the ticket, and how long the offer has left. The reply carries a one-time token bound to that specific offer. A yes for offer A can never book offer B.
This is also where the human catches what the machine cannot: you know your calendar changed, that the 06:10 departure is miserable, that this trip might move. The agent removes the tedium of watching. It should never remove the judgment.
Guardrails the reference implementation enforces
These are not suggestions in the shipped code. They are checks that fail closed.
- Approval token required to book. No token, no order, no exceptions.
- One booking per run. A crashed and restarted loop cannot double-book; state is persisted and
BOOKEDis terminal. - Test mode by default. The code refuses a live key unless you pass an explicit override flag. Nobody live-tickets by accident.
- Refundable fares by default. While you are trusting new automation, keep the undo button.
- Idempotency key on order creation. A timeout and retry produces one order, not two.
- Spend bounds.
targetgates the ask,ceilinggates the buy. The agent cannot approve its way past either.
A note on payment
For a personal build, keep payment boring: the human taps pay, or the booking runs on a merchant-of-record API in test mode. Agent-initiated card payments do now exist in production: Visa ran live agent purchases with European merchants and more than 30 issuing banks on 2 July 2026 using its Trusted Agent Protocol, and Stripe’s Shared Payment Token is generally available. They are real, they are interesting, and they are unnecessary for this pattern. They also drag in liability questions (agent transactions are card-not-present with disputes landing on the merchant) that a fare watcher simply does not need.
What still breaks
Being honest about failure modes is most of the engineering.
- The vanishing offer. Great fare at search, gone at re-quote. Expect it. The state machine treats it as routine, not as a bug.
- The approval race. You say yes at minute 24 of a 25 minute window and the offer dies mid-booking. The agent must surface a clean “missed it, still watching” rather than retrying into a different, pricier offer.
- Model transcription errors. If you let the LLM write the approval message freehand, it will eventually mangle a price. The reference implementation templates the message from structured API fields; the model never touches the numbers.
- Name mismatches. One typo in config becomes a ticket in the wrong name. Verify at approval, not after.
- Post-booking service. Schedule changes, cancellations, and refunds remain human work. This agent buys the ticket; it does not yet advocate for you when the airline reshuffles it.
None of these are reasons not to build. They are reasons the human stays in the loop. We publish this at Levelfare because the pattern is our own promise applied to software: the fare is the fare, whether a human or an agent is the one looking at it.