JOURNAL · SEPTEMBER 1, 2026

selling eSIMs and hotel rooms with zero inventory ops: pick the right partners, keep the facade

Travolp sells travel data and hotel rooms from the app, the AI chat, and inside Claude and ChatGPT, with no telco contract, no room inventory and no support rota. How eSIM Access and Nuitee's LiteAPI made that possible for a team of one.

Published
September 1, 2026
Author
Priorli
Tags
esim, hotel-booking, integrations, architecture

Travolp is our AI travel planner: it builds an itinerary, then travels with you. Itineraries are the hook, but two things a traveller actually pays for on the road are data and a bed. This year Travolp started selling both, from the app, from the AI chat, and from inside Claude and ChatGPT via MCP.

Here is the constraint that shaped every decision: Travolp’s engineering and operations team is one person. Any integration that adds a support queue, a settlement process, or an inventory to manage is dead on arrival. So the vendor question was never “who has the best API”. It was “whose business model lets us sell this without hiring anyone”. Two partners passed that test: eSIM Access for travel data, and Nuitee’s LiteAPI for hotels.

Architecture diagram: the Travolp app, in-app AI chat, and Claude/ChatGPT via MCP all sell through one API and shared payload builders, which talk to eSIM Access for travel data and Nuitee’s LiteAPI for hotel bookings.

travel data: eSIM Access

Selling connectivity means reselling wholesale eSIM packages. The first platform we evaluated wanted a four-figure minimum balance before the first test order. eSIM Access is pay as you go: fund a small wallet, order one profile, see if anyone buys. For a company that measures experiments in tens of dollars, that difference is not pricing, it is feasibility.

The API is uniform in a way that made the client trivial: every endpoint is a POST, every response arrives in the same envelope, and authentication is one access-code header plus per-request HMAC signing. The whole vendor client, ordering, top-ups, catalogue, balance and webhook registration included, fits in one file.

The purchase flow has one genuinely interesting property: provisioning is asynchronous, and the design leans into it instead of fighting it. Stripe confirms the payment, a worker places the order, and the vendor allocates the eSIM profile on its own schedule. The worker polls until the activation code exists, then the traveller gets a push and a one-tap install. Both mobile platforms now accept a universal link that opens the OS eSIM installer directly, so “install your eSIM” is a single tap rather than a QR scan ritual (the app keeps a QR fallback for Android devices that have not received the link handler yet).

Travolp’s My travel data screen: an eSIM ready to install with one tap, a live data meter on a 3 GB pack, and destination prices.

The retry story is the part worth copying. Our purchase id rides to the vendor as the order’s transactionId, and the vendor treats it as an idempotency key:

// purchase.id is the vendor-side idempotency key: a retried
// fulfilment job can never buy the same pack twice.
await provider.orderProfile({ packageCode, transactionId: purchase.id });

A crashed worker, a redelivered webhook, a double-fired job: all of them collapse into the same order. We never wrote a reconciliation script because there is nothing to reconcile.

After the sale, the vendor’s webhooks carry usage events, so the app shows a live data meter and nudges the traveller before a pack runs dry. That closes the loop that makes a top-up purchase natural, and top-ups go through the same order path with the same idempotency key.

The catalogue is deeper than we expected to be able to offer on day one: alongside single-country packs there are 301 multi-country packages across 35 region sets, so one purchase covers a three-country trip, plus roughly 1,300 daily-allowance plans for travellers who think in days rather than gigabytes. Packs start at $2.99, and the shop copy ships in every locale the app does.

One architectural note that has already paid for itself: nothing outside one adapter file knows the vendor’s wire format. Everything past the boundary speaks normalized units, cents, bytes, days. We know the seam works because we switched vendors once mid-build, and the swap took an afternoon.

// Everything past this interface speaks cents, bytes and days.
// The vendor's wire format never escapes its adapter.
interface EsimProvider {
  listPackages(opts?: CatalogueQuery): Promise<EsimPackage[]>;
  orderProfile(opts: OrderOpts): Promise<OrderRef>;
  // Null while the vendor is still provisioning; the worker re-polls.
  getOrderedProfile(ref: OrderRef): Promise<ProvisionedEsim | null>;
  topUp(opts: TopUpOpts): Promise<Usage>;
}

hotels: Nuitee’s LiteAPI

Hotels are the opposite problem. The money is bigger, and so is the operational blast radius: refunds, no-shows, date changes, “the front desk cannot find my booking” at 11pm. A one-person company cannot be the merchant of record for someone’s holiday.

Nuitee’s model removes exactly that. Bookings run through their whitelabel storefront on our domain, hotels.travolp.com. Nuitee is the merchant of record and provides first-line guest support after booking. We sell rooms at rates we control on top of their wholesale inventory, and settlement is theirs to run. The entire booking operation we had to build is: none.

The developer-facing half held up its end. Hotel content lookups are fast enough to sit inside an interactive flow: a city query returns 25 candidate hotels in under half a second, so Travolp has hotel cards on screen while the traveller is still describing the trip, and again on the trip’s stay card once the plan exists. Live prices come from a second, slower call that runs once the cards are already showing, so pricing latency never blocks the conversation. The content is licensed for display in our product, which for an EU company is not a footnote: it is the difference between building the feature and shelving it.

Bookable hotel cards with live prices inside Travolp’s trip planning flow, offering to pin one to the plan.

The handoff to the storefront is where the integration earns its keep. Tapping Book sends an HMAC-signed request to Nuitee’s guest-auth endpoint and gets back a single-use login link, so the traveller lands on the whitelabel already signed in and sees the price we quoted. The link is minted per tap and never stored, because a single-use credential that has been rendered twice has been spent once.

The whitelabel hotel storefront on hotels.travolp.com, signed in, showing recent searches and nearby hotels with rates.

Coming back the other way, Nuitee’s booking webhook tells us a room was actually booked, and our handler writes exactly one field:

// The webhook writes ONE field. Everything else derives from it:
// the trip rolls up to booked, the itinerary anchors to the real
// hotel, the Book button retires itself.
stay.bookingRef = event.bookingId;

Everything downstream is derivation, not stored state: the trip’s lodging status recomputes, the day plans re-anchor around the confirmed hotel’s location, and the booking CTA disappears because the condition that showed it is no longer true. Cancellation is the same one field cleared. When your webhook handler writes one field, idempotency stops being a discipline and becomes a property.

the shape both integrations share

We wrote previously about the rule behind Travolp’s MCP server: one API, no parallel backend, and most of the engineering is subtraction. These two integrations follow the same rule from the other side.

Each vendor lives behind one thin boundary module, and the product sells through one payload builder per good. The REST route, the in-app AI chat, and the MCP tools inside Claude and ChatGPT all call the same builder, so all three surfaces sell the identical catalogue at the identical price, and a pricing rule changed once changes everywhere.

Travolp’s data-pack widget rendering inside a Claude conversation: fixed packs and daily plans for Vietnam with Buy buttons.

The subtraction shows up at the AI boundary. An eSIM activation code is a credential: anyone holding it can install the profile. So it never enters a chat context; AI surfaces see that a pack exists and what remains of it, and installation happens only in the app. A hotel booking reference is the handle for managing the stay, so it never crosses either; the model sees bookingConfirmed: true, which is the only bit it needs to plan around.

what this buys

A traveller can ask Claude for a week in Vietnam, get a real itinerary, buy the data pack for it, and book the hotel, and behind that sentence there is no telco contract, no room inventory, no settlement job and no support rota. There is a wallet at eSIM Access, a whitelabel at Nuitee, two adapter files, and the same API that already ran the product.

Choosing partners whose business model absorbs your operational load is an architecture decision. It just happens to be one you make before writing any code.