intermediate 3 min answer

A marketplace of eBay's kind exposes 64-bit listing ids over a JSON API. A partner's browser-based integration reports that about one listing in a thousand cannot be fetched - the id it sends differs from the one it received in the last two or three digits. The server logs show a valid request for a listing that does not exist. What is happening?

ebayjsonieee-754identifiersinteroperability
Show the full answer Hide the answer

The trigger

JSON has one number type and no precision rules of its own. RFC 8259 (2017) notes that implementations commonly use IEEE 754 double-precision floating point, and that good interoperability is achieved when numbers stay within the range a double represents exactly: ±(2^53 − 1), which is 9,007,199,254,740,991.

A 64-bit identifier can exceed that by three orders of magnitude. A JavaScript client parsing 9007199254740993 gets 9007199254740992. No error is raised at any point - the parse succeeds, the value is wrong, and the client sends back a plausible id for a listing that does not exist.

The one-in-a-thousand pattern is the signature: only ids whose low bits matter are affected, so the failure rate depends on how many of your ids have crossed the threshold.

Why it propagated silently

Every component behaved correctly by its own specification. The server serialised a valid integer. The transport delivered it intact. The client's parser did what IEEE 754 requires. There is no layer at which anything can detect the loss, because the information is gone before any validation sees it - and the resulting id is well-formed, so schema validation passes.

This is also why it survives testing. Test fixtures use small ids. The bug appears only once the identifier space grows past 2^53, which can be years after launch.

The fix

Serialise large identifiers as strings. The documented precedent is Twitter's API, which added an id_str field alongside the numeric id precisely because browser clients were truncating its identifiers. The pattern that follows:

  • New APIs: identifiers are strings, always. An id is an opaque token, not a quantity - you do not add two of them, and nothing is lost by quoting it.
  • Existing APIs: add the string field and keep the numeric one, deprecate the numeric field on a published timetable, and state the precision risk in the documentation rather than assuming partners know.
  • Where a number must stay a number - a monetary amount, a count - either keep it within the safe range or send it as a string with an explicit scale. Money as a floating-point number has the same class of problem for a different reason.
  • Detect it in a contract test, by including an id above 2^53 in the test fixtures. One fixture closes this permanently and almost no API has it.

Where else the same mistake appears

Any boundary where a 64-bit value crosses into a double: a spreadsheet import, a BI tool that types columns by sampling, a logging pipeline that parses JSON into typed fields, a NoSQL store whose default number type is a double. The API is the place it is noticed and rarely the only place it happens.

When this is the wrong answer

If the differing digits are in the middle of the identifier rather than at the end, this is not precision loss - look for a truncating column type, a bad regex or a URL-encoding problem. And if the partner's integration is server-side in a language with native 64-bit integers, the same symptom usually comes from a logging or serialisation layer in between rather than from the client's own parser. The diagnostic is that the error is always in the least significant digits, and always for large values.