Base36: Obfuscating the numbers with the best of intentions

Early on as we were building the Twitter Ads platform, we had a MySQL database with straightforward auto-incrementing IDs. Advertisers, campaigns, and creatives all incremented by one. That worked perfectly internally, but it created an awkward external signal. Anyone looking at IDs in the UI or API could quickly infer how many objects existed.

So we did what seemed clever at the time. We converted those sequential integers into Base36 before exposing them externally. Internally, IDs were still:

142
12
923834
23413

Externally, they became:

3y
C
jsu2
i2d

These Base36 values showed up everywhere. URLs, the API, and some UI views and reports. The underlying IDs were still sequential, but the representation made them feel opaque.

There was another detail. These values were always returned as strings in the API.

Partly this was convenient, since they were no longer really numbers. But it also aligned with a broader pattern we were already using. Tweet IDs were 64-bit integers generated using Snowflake IDs, and JavaScript could not safely represent 64-bit integers. To avoid precision loss, the API returned both tweet_id as the 64-bit integer and tweet_id_str as the same value encoded as a string for JavaScript handling.

So now we had identifiers that: - were not sequential externally*
- were not numeric in representation
- and were not consistently encoded across endpoints

This worked for a while.

Eventually, more advanced partners started integrating deeply with the API. Not everything in reporting had been converted to Base36, and they needed a way to reconcile identifiers across systems. So we ended up providing a converter anyway. This essentially undid our own obfuscation layer.

At that point, the whole thing started to look a bit silly.

This little tool is the surviving artifact of that decision. It converts between Base10 and Base36 exactly the way we did it back then.

The bigger lesson was not about number bases. It was about designing APIs intentionally. Obscuring identifiers does not solve product or perception problems, and it often makes integrations harder later.

Still, it is a fun reminder of how fast decisions get made in early systems, and how those decisions tend to stick around longer than expected.

* Snowflake IDs are time-based and therefore roughly monotonic. Tweet IDs were close to sequential in practice, while the Base36-converted database IDs were derived from strictly sequential integers but intentionally obscured.

Update: I recently ported this small utility over from the deprecated base36.jaakkosf.io tool.