For Developers
There's no single standard every API follows, which is exactly the problem. Here's what you'll actually run into, and how to design timestamps into an API you're building yourself.
Ask ten different APIs what time it is and you'll get answers in at least three different shapes: a Unix timestamp in seconds, a Unix timestamp in milliseconds, or an RFC 3339 string — sometimes all three from the same provider, depending on which endpoint you're hitting. None of this is documented consistently, which is why "the timestamp field was off by exactly 1000x" is such a common bug report.
Stripe and many financial APIs use Unix seconds. Most JavaScript-originated APIs default to Unix milliseconds, because that's what Date.now() returns natively. Twitter/X's older API used a custom string format that isn't RFC 3339 or Unix time at all. Most modern REST APIs designed since roughly 2015 default to RFC 3339 strings, partly because they're human-readable in logs and partly because JSON has no native date type, forcing every API to pick some string or number convention on its own.
JSON itself has no timestamp or date type — only strings, numbers, booleans, objects, and arrays. Every API has to encode a moment in time as one of those, which is precisely why there's no single universal convention: a number could be seconds or milliseconds, and a string could be any of several date formats unless the API explicitly documents which one it uses. When you're consuming an unfamiliar API, always check the docs for the exact format before assuming — guessing based on the field name alone (created_at, timestamp, updated) is a common source of the seconds-vs-milliseconds bug.
A Unix timestamp is inherently timezone-free — it represents an exact instant regardless of where you are. A string timestamp is not automatically timezone-free unless it explicitly includes an offset or Z for UTC. APIs that return local, offset-less time strings force every consumer to somehow know what timezone that string is in from context, which is fragile and a frequent source of off-by-several-hours bugs when data crosses between systems in different regions.
Store everything in UTC internally, and return timestamps as RFC 3339 strings with an explicit Z or offset — it's unambiguous, sorts correctly as plain text, and doesn't force consumers to guess a unit. If clients specifically need a raw number for fast comparison or storage, offer it as an additional field (and document the unit explicitly, seconds or milliseconds, right in the field name — created_at_ms rather than just created_at) rather than replacing the string entirely. Consistency across every endpoint matters more than which specific format you pick — an API that's RFC 3339 everywhere is far easier to work with than one that's RFC 3339 in some endpoints and Unix milliseconds in others.
Related
A closer look at what actually separates these formats.
The same underlying problem, on the storage side instead of the API side.
Practical timezone handling for a common API backend.