Human-in-the-loop for travel and expense agents

Most trips are in policy. The exceptions are what need a person.

Let the agent book what fits policy on its own, and route the exception to whoever owns the budget.

How do I let an AI travel agent auto approve inside policy and ask a human for exceptions?

Pushary gives an AI travel or expense agent one call that reaches a real person and waits for their answer. Book inside policy automatically, then wrap the out-of-policy fare, the upgrade, or the over-limit claim in decisions.ask() so the agent holds until the budget owner approves, denies, or types a cheaper instruction. If nobody answers before your window closes it fails closed, so an unattended booking never becomes a charge nobody authorised.

Where it stops

The calls that should not be automatic

bookFlight(itinerary, fareClass)
A fare above the policy ceiling is the classic exception, and it is time-boxed: the price is only good for a few minutes. That is exactly the case where an approval sitting in an inbox costs real money, because the fare is gone before anyone reads the email.
upgradeCabin(bookingRef, cabin)
Cabin rules usually depend on facts the agent does not hold: flight length, who is travelling, whether a client is paying. A person can settle that in one tap, and the rule is often unwritten anyway.
approveExpense(reportId, amount)
Reimbursing an over-limit claim is money leaving the company with no clean undo, and it is the line item an auditor asks about later. The record of who approved it matters as much as the decision.
rebookDisruption(bookingRef, newItinerary)
A cancelled flight at 6am is where an agent is most useful and least supervised. Rebooking into a higher fare to get someone home is usually right, but it should still be somebody's call rather than a default.
issueTravelAdvance(employeeId, amount)
Advances move cash before any receipt exists, so the usual after-the-fact expense controls do not apply. The approval is the only control there is.

The integration

Two calls inside the step you already have

Wrap the action, not the whole agent. Everything the agent does that is safe keeps running without asking anyone.

npm i @pushary/servertwo calls
import { createPusharyServer } from "@pushary/server"

const px = createPusharyServer({ apiKey: process.env.PUSHARY_API_KEY! })

async function book(trip: TripRequest, fare: Fare) {
  // In policy and under the ceiling: the agent just books it.
  if (fare.total <= POLICY.ceilingFor(trip.route) && fare.cabin === "economy") {
    return bookFlight(trip.itinerary, fare.cabin)
  }

  const decision = await px.decisions.ask({
    externalId: trip.budgetOwnerId,
    type: "confirm",
    question: `Approve ${fare.currency}${fare.total} for ${trip.traveller}? Policy ceiling is ${POLICY.ceilingFor(trip.route)}.`,
    timeoutMs: 9 * 60 * 1000,
  })

  if (!decision.approved) return holdForCheaperFare(trip)
  return bookFlight(trip.itinerary, fare.cabin)
}

Who answers

The approver is usually not an engineer

The person whose budget the trip lands on, which is usually a line manager or a cost centre owner rather than anyone in travel operations. That is the point: travel teams administer the policy, they do not own the money, so routing every exception to a shared travel inbox puts the decision with people who cannot actually say yes. Pushary sends it to the budget owner's phone and comes back with a decision attached to their name.

We already have an approval step in our booking tool. Why add another one?

Most booking tools do have an approval step, and it is usually an email with a link into a portal the approver has not logged into for a month. That is fine for a trip planned three weeks out and useless for a fare that expires in nine minutes or a disruption at six in the morning. The gap is not that approval is missing, it is that the approval arrives on a channel nobody is watching at the moment the decision matters. Pushary does not replace your travel policy or your booking tool. It moves the exception onto the phone the approver already has in their hand, gives it a deadline, fails closed when the deadline passes, and writes who decided what into a record you can export. If your current step already gets answered inside the fare window, you do not need this.

Worth reading: IRS Publication 463 on travel expense substantiation, GSA per diem rates, the ceiling most US travel policies inherit.

$99/ month, Partner plan

Embed it in your product. Your users never see a Pushary login or bill.

FAQ

Questions, answered

Can an AI travel agent auto approve inside policy and only ask about exceptions?
Yes, and that is the split worth building. Compare the fare against your own ceiling in code and book it when it fits, so the common case needs nobody. Call decisions.ask() only on the exception, and pass the policy number in the question text so the approver sees what rule is being broken and by how much. Where the exception rate is low, almost every trip still books untouched.
What happens if the approver does not answer before the fare expires?
Set the timeout shorter than the fare hold and the decision comes back denied when it lapses, so the agent falls through to whatever you do next: hold for a cheaper fare, re-search, or escalate to a second approver. The request stays in the ledger either way, so an expired exception is visible afterwards rather than silently dropped. Failing closed matters more here than in most verticals because the alternative is a charge on a company card that nobody authorised.
Who should approve an out-of-policy trip, travel operations or the manager?
The budget owner. Travel operations administer the policy but the spend lands on a cost centre somebody else is accountable for, so an approval from the travel desk is an administrative acknowledgement rather than a financial decision. Address the question to the manager by your own externalId and the answer comes back attributed to them, which is also what an auditor is looking for later.
Does this work for expense reports as well as bookings?
Yes, and the shape is the same. Auto approve claims under your limit with a matching receipt, and ask a person about the ones over it, the ones with no receipt, and the categories your policy calls out. The difference is timing: a booking is urgent and an expense report is not, so expense approvals can carry a much longer window without anything being lost.

Put a person on the step that needs one.

Connect a user with one tap, ask a human with one call, and act on a fail-closed decision. Every decision is saved to a durable ledger you can audit.