Someone forwards a supplier's API documentation and asks how long the integration will take. It is a fair question, asked in good faith, usually by someone who needs to answer it to somebody else. The uncomfortable part is that the documentation — however thorough — is close to the least informative artifact in the packet, and reading it faster does not make the answer arrive sooner.
I have integrated enough of these to know what the estimate is made of, and it is not made of endpoints. Roughly 150 supplier codes run on about 90 distinct integrations on the platform I work on; on the hotel side, 75 codes on 31 modules. The endpoint list is nearly the same document every time. The variance lives somewhere else.
What the first read is looking for
When I open a new supplier's documentation, I am not counting endpoints. I am trying to answer five questions, and none of them is usually answered in the document.
Which offer pattern is this? We have two, written down, because supplier models genuinely differ. In pattern A the supplier returns one token that books N rooms. In pattern B each room's rate is independent and an offer for a two-room search does not exist until we materialise it as the matrix of valid combinations. If a supplier fits one of the two, the module is a known shape and the estimate is mostly typing. If it fits neither — a third way of describing what a room is, or a rate that is only valid in combination with another supplier call — then the first piece of work is not code, it is a design conversation, and design conversations do not have durations, they have participants.
Is booking idempotent? Not "is there a reference field" — whether sending the same booking request twice produces one reservation or two, and whether the supplier will tell me which happened after a timeout. If the answer is no, the integration acquires a whole second subsystem: a reconciliation path that can ask, after a call that never returned, whether money moved. That path is usually as much work as the happy flow it protects, and it is invisible in a demo.
Where do errors live? Some suppliers use status codes. Some answer HTTP 200 with an error inside the body, which makes the transport layer's opinion of the call worthless and forces a second, supplier-specific reading of every response. That is a known cost — we have modules that do it — but it changes what "error handling is done" means, and it changes it before any code is written.
Does the sandbox resemble production? A sandbox that returns a handful of static hotels tells you the wire format and nothing about behaviour under a real catalogue: pagination that only appears past a threshold, rate limits, the latency distribution you have to fit under a 30-second search deadline. A sandbox can make an integration look finished a long time before it is.
Whose calendar does certification run on? Certification drives a per-provider testcase catalog through the whole flow and produces an evidence pack — every testcase's request and response plus the upstream call — which a partner reviews. The reviewing is theirs. The scheduling is theirs. That segment of the timeline is not an engineering estimate at all, and treating it as one is how a plan slips for reasons no engineer could have moved.
The two-phase answer
So I do not give a date. I give two things.
First, a bounded reading phase: a short, fixed slice of time whose deliverable is not code but a shape — which offer pattern this is, whether booking is idempotent, where errors live, what the sandbox is worth, and who owns the certification calendar. It is bounded on purpose, because the failure mode of "let me look into it" is that it expands until someone asks again.
Second, an estimate that only exists after that phase, delivered together with the named risks that would change it. Naming the risk is the part that makes the estimate usable by the person who has to plan around it: "if this turns out to need a reconciliation path, add that work" is something a planner can hold. A single number is not.
Why padding is worse than explaining
The tempting alternative is to take a guess and multiply it. Padding fails in a specific way: it hides which case you are in. A padded number that turns out generous looks like sandbagging, and a padded number that still overruns has spent its credibility for nothing. Neither outcome teaches anybody anything about the supplier, and the next estimate starts from the same blank.
Explaining the shape survives both outcomes. If the supplier turns out to fit pattern A cleanly, the reader can see why it landed early. If it needs a design conversation, they already know that was the branch, because it was named before it happened.
Why the normal case is genuinely repeatable
None of this means every integration is a mystery. The reason a two-phase answer works is that one branch of it is genuinely routine, and it is routine because of a structural decision: one module per supplier against a fixed contract, registered by code in a single registry. Supplier-specific behaviour has exactly one place to live, so the "normal" case is bounded by the contract rather than by how well anyone remembers the last integration.
That is what makes the estimate honest rather than defensive. It is not "this could take anything". It is "if it is the normal case, the shape is known and the work is known; here is what would make it not the normal case".
The number I do not have
I have never published a distribution of how long integrations actually took, and there is not one hiding in a spreadsheet somewhere. Start dates in a tracker are not when the work started, and "done" has several plausible definitions — merged, certified, live under a real switch — that would produce several different distributions. I could compute something. I would not trust it, and neither should anyone planning against it.
So the honest position is: I can tell you what the estimate depends on, I can bound the time it takes to find out, and I can name the things that would move it. What I cannot do is tell you the median, because nobody here measured it.