Notes
Teardown 3 min read

The audit that was out by 13×

An ad-account review read one page of an API response and reported ₹41,000 of lifetime spend. The true figure was ₹5.4 lakh. Pagination is a finding, not a footnote.


A morning audit of a travel client’s ad account produced a clean summary: lifetime spend ₹41,336, cost per conversation ₹33. Confident, internally consistent, plausible.

Both numbers were wrong by roughly 13×.

The campaigns endpoint returns 25 rows by default. The account held 142. The audit read one page, treated it as the population, and derived everything downstream from a sixth of the data. The true figures: ₹5,40,461 of lifetime spend, 8,445 conversations, ₹64 each. Every conclusion built on the first version — which campaigns mattered, what “normal” cost was, how the account had trended — was fiction.

Why it slipped through

Because nothing failed. No error, no warning, no truncated: true banner. An API returning a page of a collection looks identical to an API returning the whole collection, and the partial numbers were plausible enough that nothing tripped the smell test. The most dangerous wrong number is the one that looks reasonable.

The same audit made a second, related error in the other direction: it reported regional breakdowns as “unavailable” when they were available all along — under a different action-type name than the one it queried. Two mistakes, one root cause: the API answered the question that was asked, not the question that was meant.

The three defences

  1. Reconcile to a control total the client already knows. Lifetime spend is on the billing screen. If your computed total doesn’t match a number the owner can see in their own interface, stop. This one check would have caught the whole thing in a minute.
  2. Follow the pagination cursor until it is absent. Not “until the data looks complete” — until the API says there is no next page. Every time, including the times the first page looks like plenty.
  3. Count rows before analysing them. A result set of exactly 25, 50, or 100 is a smell — round numbers at common API defaults are how truncation announces itself, quietly.

The audit framing

Accountancy has a name for this: a completeness assertion. Before an auditor signs anything, the question isn’t whether the records in front of them are accurate — it’s whether they are all the records. Existence and accuracy get all the attention; completeness is the one that fails silently.

Every API response deserves the same scepticism as a client-provided ledger. Accurate rows, honestly reported, one page of six.