When I describe my job, the reply I get most often is some polite version of "so you call other people's APIs". It is technically true the way "a compiler reads text files" is technically true. I want to write down the actual shape of the work, because the shape is the argument.
The numbers first
The platform I work on aggregates travel suppliers across five product lines — hotel, flight, tour, transfer, car hire. Roughly 150 supplier codes run on about 90 distinct integrations; on the hotel side alone, 75 codes sit on 31 modules. The gap between those two numbers is white-labelling: one integration resold under many brand codes, with the differences held in configuration rather than forked code.
If integration work were "calling an API", those would be counts of API calls. They are not. They are counts of opinions — 90 different opinions about what a room is, what a price is, what an error is, and what "yes" means — that all have to answer one question through one contract.
One contract, ninety opinions
The first structural decision: every supplier gets exactly one module, written against a fixed contract, and the module is registered by its code in a single registry. The point is not tidiness. The point is that when a supplier is wrong — and one of ninety is always wrong — the difference between that supplier and everyone else lives in one file. You do not go hunting for where supplier-specific behaviour leaked into shared code, because the contract made that leak a compile-time-shaped problem instead of an archaeology problem.
Two ways to say "a room"
You might expect the room-and-rate model to be the easy part. It is where the deepest fork lives. There are two documented offer patterns, because the suppliers' models genuinely differ and no amount of mapping makes them the same thing:
In pattern A, the supplier returns a single token that books N rooms — one offer, one confirmation, done. In pattern B, each room's rate is independent, and an "offer" for a two-room search does not exist until we materialise it: the offer list is the computed matrix of valid combinations. Same search, same screen, and underneath, one supplier is selling you an object while another is selling you the parts of one.
Pretending these are one pattern produces bookings where room two silently gets room one's rate. Documenting them as two, and forcing every new integration to declare which one it is, is a design decision — the kind that never shows up in a demo.
Two execution models behind one door
Protocols range from JSON over HTTP to SOAP/XML; the XML side is mapped with camaro and xmlbuilder rather than a DOM walk, because at search volume the parser is a cost centre. But the bigger split is temporal.
Hotel, flight and car search are asynchronous: a queue, one job per supplier, each guarded by a Redis lock, workers writing results into a shared cache hash plus a meta hash that marks completion. The HTTP layer aggregates whatever has landed and reports a progress fraction, under an absolute 30-second deadline — after that, whoever has not answered is not in the results. Tour and transfer search are synchronous: a single server process, no worker, no polling.
The client sees neither. Both models sit behind the same public API contract. That sentence is short and cost a great deal, because "the same contract" means the async side has to answer questions the sync side never faces — what does "half done" look like, what happens to the supplier that answers at second 31 — and the answers must be invisible from outside.
The part nobody advertises
Here is the honest centre of the job: most of the work is deciding what to do when the other side is wrong, slow, or silent.
Suppliers answer 200 with an error body — a success status wrapping a failure, which means the transport layer's opinion of the call is worthless and every response needs a second, supplier-specific reading. Suppliers time out at exactly the moment money is in flight, leaving you to decide whether a booking exists. Suppliers return prices that disagree with the prices they returned one search earlier. None of this is an edge case; at 90 integrations it is the weather. I have not counted what fraction of integration code handles the unhappy paths versus the happy one — so I will not put a number on it — but the happy path is the part you finish first and think about least.
One more structural choice belongs here: cross-call state rides inside a composite versioned offer identifier — source, contract, hotel, uuid — decoded at prebook. No server-side session. Every fact the next step needs travels inside the ID the client already holds, which means a prebook can land on any instance, survive a deploy in the middle of a customer's checkout, and be debugged from the identifier alone.
Why this is architecture
Look back at the list: a fixed contract with one module per opinion; two offer patterns instead of one false one; two execution models behind one public face; error semantics rebuilt on top of a transport that lies; state packed into an identifier instead of a session. Not one of these is "calling an API". Every one of them is a boundary — a line where somebody decided what the rest of the system is allowed to assume, and accepted responsibility for the cases where that assumption breaks.
Plumbing moves water that behaves like water. This job federates ninety parties who each behave differently, and makes them answer one question, within 30 seconds, in one shape. The pipes are the easy part.