How to decode a Twitter Snowflake ID
To decode a Twitter Snowflake ID, shift the number right by 22 bits and add 1288834974657: the result is the Unix time, to a thousandth of a second, when the post or account was created, exact for every post since November 4, 2010.
Short answer: A Twitter Snowflake ID keeps its creation time in its top 41 bits, counted in thousandths of a second from X's epoch, November 4, 2010. Posts have one since that day, new accounts since February 1, 2016; older IDs hold no date. Sorsa API's /tweet-info-bulk dates 100 post IDs per request, from $0.014 per 1,000; 100 free requests cover 10K.
- Endpoint
POST /tweet-info-bulk- Page size
- 100 post IDs per request
- Price
- from $0.014 per 1,000
#Decode a Snowflake ID to a date
A tweet's creation time is written into its ID, so you can read it without fetching the post: drop the low 22 bits of the ID, add X's epoch, and you have the Unix time in UTC, exact to a thousandth of a second, finer than the minute X prints under a post. It works on deleted posts too, as long as you have the ID, and the free Tweet ID to Date Converter does it for one ID or a whole list.
-
Copy the ID. In a post link such as
x.com/<username>/status/2110301305616889042, the ID is the number afterstatus/. The same number opens the post on its own atx.com/i/web/status/followed by the ID. -
Drop the low 22 bits.
2110301305616889042 >> 22is 503135038761: the time between X's epoch and the post, in thousandths of a second. Dividing by 2^22 and rounding down gives the same number. -
Add the epoch. 503135038761 + 1288834974657 = 1791970013418, a Unix time in thousandths of a second: 1791970013.418 in seconds.
-
Read it as a date. 1791970013.418 is Wednesday, October 14, 2026, 09:26:53.418 UTC. Convert it to your own zone for display: X shows each reader the time in their local zone.
Paste any post ID, user ID or post link into the decoder to see its date and its four parts.
A 19-digit Snowflake ID, made on October 14, 2026.
- Your time zone
- October 14, 2026, 09:26:53 UTC
- Unix time, seconds
- 1791970013.418
- Size
- 19 digits, 61 bits
- Timestamp
- 503135038761 thousandths of a second after the epoch
- Datacenter
- 11 bits 21 to 17
- Worker
- 7 bits 16 to 12
- Sequence
- 1234 bits 11 to 0
The decoder reads the date and the four bit fields of any X ID in your browser, without a request.
The same arithmetic in code. Python's integers hold all 64 bits; in JavaScript use BigInt, because a plain number rounds a 19-digit ID.
from datetime import datetime, timezone
EPOCH = 1288834974657 # ms: November 4, 2010, 01:42:54.657 UTC
def snowflake_to_datetime(snowflake_id):
ms = (int(snowflake_id) >> 22) + EPOCH
return datetime.fromtimestamp(ms / 1000, tz=timezone.utc)
print(snowflake_to_datetime("2110301305616889042"))
# 2026-10-14 09:26:53.418000+00:00
In a database that stores IDs as 64-bit integers, the same shift runs inside the query:
-- PostgreSQL, id stored as BIGINT
SELECT id,
to_timestamp(((id >> 22) + 1288834974657) / 1000.0) AS created_utc
FROM posts;
-- BigQuery, id stored as INT64
SELECT id,
TIMESTAMP_MILLIS((id >> 22) + 1288834974657) AS created_utc
FROM dataset.posts;
The converter also takes a whole column of IDs at once, downloads the results as a CSV and turns a date back into an ID, all in your browser.
#Dates for old IDs and long lists, with Sorsa API
The arithmetic fails on two kinds of ID: posts from before November 4, 2010 and accounts opened before February 2016. Their IDs were counted out one by one and hold no time, so the formula returns a moment within seconds of the epoch, which looks plausible and is wrong. For those IDs, and whenever you want X's own created_at next to the decoded time, look the IDs up in batches.
For dating tweet IDs in bulk, old ones included, use Sorsa API, a Twitter (X) API and scraper for public data: POST /tweet-info-bulk returns created_at for up to 100 post IDs per request, from $0.014 per 1,000 posts, against $5.00 per 1,000 on the official X API. Accounts go through GET /info-batch at the same rate, 100 per request, where X bills $10.00 per 1,000.
-
Get a key. Sign up and copy your API key from the dashboard. The account comes with 100 free requests, no card needed.
-
Send up to 100 post IDs. Pass IDs or full links in
tweet_linkstoPOST /tweet-info-bulk(opens in a new tab); each post comes back whole, with its author's profile.Terminal curl -X POST "https://api.sorsa.io/v3/tweet-info-bulk" \ -H "ApiKey: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{"tweet_links": ["1243617904", "2110301305616889042"]}'Response200 OK { "tweets": [ { "id": "1243617904", "created_at": "2009-02-24T19:51:06Z", "full_text": "Field notes from the harbor survey are up on the site", "likes_count": 4, "user": { "id": "20536157", "username": "lanternbay_research", "created_at": "2009-02-10T16:22:31Z" } }, { "id": "2110301305616889042", "created_at": "2026-10-14T09:26:53Z", "full_text": "New dataset: ten years of harbor traffic, in one CSV", "likes_count": 212, "user": { "id": "1427632226255384585", "username": "copperfield_signals", "created_at": "2021-08-17T14:03:41Z" } } ] } -
Date the accounts the same way. Repeat
user_idsonce per account, up to 100, inGET /info-batch(opens in a new tab); each profile carries itscreated_at.Terminal curl -G "https://api.sorsa.io/v3/info-batch" \ -H "ApiKey: YOUR_API_KEY" \ --data-urlencode "user_ids=20536157" \ --data-urlencode "user_ids=1427632226255384585" -
Spend requests only where decoding can't help. The script reads
post_ids.csv, decodes every Snowflake for free and sends only the older IDs to the API, then writes one date per ID.Python import csv from datetime import datetime, timezone import requests BASE = "https://api.sorsa.io/v3" HEADERS = {"ApiKey": "YOUR_API_KEY"} EPOCH = 1288834974657 # Twitter's epoch, Unix ms def decode(post_id): """The UTC time inside a Snowflake ID; None for an older ID.""" if len(post_id) < 15: # counted ID from before Nov 4, 2010 return None ms = (int(post_id) >> 22) + EPOCH return datetime.fromtimestamp(ms / 1000, tz=timezone.utc) def look_up(post_ids): """created_at from Sorsa API, 100 post IDs per request.""" found = {} for i in range(0, len(post_ids), 100): r = requests.post(f"{BASE}/tweet-info-bulk", headers=HEADERS, json={"tweet_links": post_ids[i:i + 100]}, timeout=30) r.raise_for_status() for tweet in r.json().get("tweets", []): found[tweet["id"]] = tweet["created_at"] return found with open("post_ids.csv", newline="", encoding="utf-8") as f: ids = [row["post_id"].strip() for row in csv.DictReader(f)] decoded = {i: decode(i) for i in ids} looked_up = look_up([i for i, d in decoded.items() if d is None]) with open("post_dates.csv", "w", newline="", encoding="utf-8") as f: out = csv.writer(f) out.writerow(["post_id", "created_at_utc", "source"]) for i in ids: if decoded[i]: out.writerow([i, decoded[i].isoformat(), "decoded"]) else: out.writerow([i, looked_up.get(i, ""), "looked up"])
A post that is deleted or protected doesn't come back, so its row stays empty: match results by id, never by position. A 403 means the key is missing or out of requests, a 429 that you went past 20 requests in one second, and failed requests are never charged.
Looking up old post IDs costs $5.00 per 1,000 on the official X API, which bills every post it returns, and from $0.014 per 1,000 on Sorsa API, which bills the request and packs 100 posts into each one.
Official X API
$5,000
for 1M post IDs
$0.005 per post
Sorsa API
from$14
for 1M post IDs
from $0.014 per 1,000
over 250x cheaper
Against the official X API, Sorsa API comes out ahead on each thing this job needs:
| What the job needs | Sorsa API | Official X API |
|---|---|---|
| Price per 1,000 posts | from $0.014 | $5.00 |
| Price per 1,000 accounts | from $0.014 | $10.00 |
| IDs per request | 100 posts or 100 accounts | 100, billed one by one |
| Posts from before November 2010 | yes, with created_at | yes, at the same per-post price |
| Setup | one API key in a header | developer account, app, bearer token |
| Speed | 20 requests per second on every plan | rate limits per 15-minute window |
| Volume | no post-read cap; bigger plans as you grow | 3M post reads a month, then Enterprise from about $42K a month |
| Your X login | never asked for | the developer account sits on your X account |
Price per 1,000 posts
- Sorsa API
- from $0.014
- Official X API
- $5.00
Price per 1,000 accounts
- Sorsa API
- from $0.014
- Official X API
- $10.00
IDs per request
- Sorsa API
- 100 posts or 100 accounts
- Official X API
- 100, billed one by one
Posts from before November 2010
- Sorsa API
- yes, with
created_at - Official X API
- yes, at the same per-post price
Setup
- Sorsa API
- one API key in a header
- Official X API
- developer account, app, bearer token
Speed
- Sorsa API
- 20 requests per second on every plan
- Official X API
- rate limits per 15-minute window
Volume
- Sorsa API
- no post-read cap; bigger plans as you grow
- Official X API
- 3M post reads a month, then Enterprise from about $42K a month
Your X login
- Sorsa API
- never asked for
- Official X API
- the developer account sits on your X account
The official X API's prices are on its pricing page (opens in a new tab); the full comparison is in our guide to X API pricing. For whole timelines and searches from 2006 on, see historical Twitter data.
#What the 64 bits of a Snowflake ID hold
A Snowflake ID is a 64-bit integer split into four fields: a 41-bit timestamp, a 5-bit datacenter number, a 5-bit worker number and a 12-bit sequence, with the top bit always 0 so the number stays positive. The layout comes from Twitter's own generator, released in 2010 and kept in the snowflake-2010 source (opens in a new tab), which builds each ID as ((time - epoch) << 22) | (datacenter << 17) | (worker << 12) | sequence.
Each field can be read with a shift and a mask:
| Bits | Width | Field | Read it with | Example value |
|---|---|---|---|---|
| 63 | 1 | Sign, always 0 | 0 | |
| 62 to 22 | 41 | Time since the epoch, in 1/1000 s | id >> 22 | 503135038761 |
| 21 to 17 | 5 | Datacenter (0 to 31) | (id >> 17) & 31 | 11 |
| 16 to 12 | 5 | Worker (0 to 31) | (id >> 12) & 31 | 7 |
| 11 to 0 | 12 | Sequence (0 to 4,095) | id & 4095 | 1234 |
63
- Width
- 1
- Field
- Sign, always 0
- Read it with
- Example value
- 0
62 to 22
- Width
- 41
- Field
- Time since the epoch, in 1/1000 s
- Read it with
id >> 22- Example value
- 503135038761
21 to 17
- Width
- 5
- Field
- Datacenter (0 to 31)
- Read it with
(id >> 17) & 31- Example value
- 11
16 to 12
- Width
- 5
- Field
- Worker (0 to 31)
- Read it with
(id >> 12) & 31- Example value
- 7
11 to 0
- Width
- 12
- Field
- Sequence (0 to 4,095)
- Read it with
id & 4095- Example value
- 1234
The epoch. 1288834974657 is the constant twepoch in that source file: Thursday, November 4, 2010, 01:42:54.657 UTC. Snowflake counts from there instead of from 1970, which leaves more of the 41 bits for the future. The field holds 2^41 thousandths of a second, about 69.7 years, and runs out on July 10, 2080 at 17:30:30.208 UTC.
The other 22 bits. Twitter's 2010 README (opens in a new tab) describes 10 bits of machine ID, up to 1,024 machines, that the code splits into datacenter and worker. The 12-bit sequence numbers the IDs one machine hands out within the same thousandth of a second, up to 4,096 of them. X has never documented what its current datacenter and worker numbers mean, so treat them as labels, not data.
Order. Because the time sits in the highest bits, a larger ID almost always means a newer post. The README promised order to within about one second across machines, and X's ID guide (opens in a new tab) calls IDs roughly time-ordered for that reason: two posts made in the same second on different machines can come out of order.
Other platforms copied the design with an epoch of their own, so an ID from another service, decoded with X's epoch, lands years away from its real date.
#Which X IDs are Snowflakes, and since when
Post IDs became Snowflakes on November 4, 2010; user, list and saved-search IDs followed on February 1, 2016. X announced the second switch itself in a developer forum post of November 23, 2015 (opens in a new tab): starting February 1, 2016, all new users, lists and saved searches get 64-bit Snowflake IDs, posts and DMs already have them, and existing IDs stay valid.
Two older dates cause most of the confusion. In January 2013 Twitter's developer blog announced 64-bit user IDs (opens in a new tab), switched on October 21, 2013. That change only let the counter grow past 32 bits; accounts kept getting counted IDs until 2016. Pages that put the user switch "around 2020" are wrong as well: an account opened in mid-2016 already has an 18-digit Snowflake.
| Object | Snowflake ID since | Decodes to a date |
|---|---|---|
| Post (tweet) | November 4, 2010 | yes, for IDs of 15 digits or more |
| Direct message | by November 2015 | yes, for IDs of 15 digits or more |
| User account | February 1, 2016 | yes for 18- and 19-digit IDs, no for older accounts |
| List, saved search | February 1, 2016 | yes for the newer, longer IDs |
| Space | never | no: Space IDs are letters and digits, such as 1YqGodQbNXDxv |
Post (tweet)
- Snowflake ID since
- November 4, 2010
- Decodes to a date
- yes, for IDs of 15 digits or more
Direct message
- Snowflake ID since
- by November 2015
- Decodes to a date
- yes, for IDs of 15 digits or more
User account
- Snowflake ID since
- February 1, 2016
- Decodes to a date
- yes for 18- and 19-digit IDs, no for older accounts
List, saved search
- Snowflake ID since
- February 1, 2016
- Decodes to a date
- yes for the newer, longer IDs
Space
- Snowflake ID since
- never
- Decodes to a date
- no: Space IDs are letters and digits, such as
1YqGodQbNXDxv
Good to know
A counted ID decodes without an error, which is the trap. X's own example user ID, 2244994945, turns into November 4, 2010, 01:42:55 UTC, under a second after the epoch, and the first post ever, ID 20, decodes to the epoch itself. Any result in the first hours of November 4, 2010 means the ID holds no date.
For an account's registration time to the second, read created_at from a profile lookup even when its ID is a Snowflake: X doesn't document when in sign-up it stamps the ID. Without code, the free Twitter Account Age Checker shows any account's join date.
#How old is an ID? Count its digits
A Snowflake ID grows by one digit each time the clock inside it crosses a power of ten, so its length alone tells you roughly when it was made. Post IDs have been 19 digits long since May 25, 2018, and they stay at 19 until the field runs out in 2080, because the top bit is always 0.
| Digits | Post IDs | Account IDs |
|---|---|---|
| up to 11 | before November 4, 2010: counted, no date inside | before February 2016: counted, no date inside |
| 15 | November 4 to 6, 2010 | none |
| 16 | November 6 to December 1, 2010 | none |
| 17 | December 1, 2010 to August 7, 2011 | none |
| 18 | August 7, 2011 to May 25, 2018 | February 1, 2016 to May 25, 2018 |
| 19 | since May 25, 2018 | since May 25, 2018 |
up to 11
- Post IDs
- before November 4, 2010: counted, no date inside
- Account IDs
- before February 2016: counted, no date inside
15
- Post IDs
- November 4 to 6, 2010
- Account IDs
- none
16
- Post IDs
- November 6 to December 1, 2010
- Account IDs
- none
17
- Post IDs
- December 1, 2010 to August 7, 2011
- Account IDs
- none
18
- Post IDs
- August 7, 2011 to May 25, 2018
- Account IDs
- February 1, 2016 to May 25, 2018
19
- Post IDs
- since May 25, 2018
- Account IDs
- since May 25, 2018
Length is also why IDs break in JavaScript and spreadsheets. Every post ID made since November 28, 2010 is larger than 2^53, the biggest whole number a JavaScript number holds exactly, and that is the reason X's ID guide tells you to keep IDs as strings. Our guide to the Twitter user ID from username shows what Excel and JavaScript do to an ID and how to store one safely.
#Turn a date into an ID for since_id and max_id
The formula runs backwards: subtract the epoch from a Unix time in thousandths of a second and shift the result left by 22 bits, and you get the smallest ID X could have made at that moment. It belongs to no post; it marks a point in time that X search understands.
X's search syntax takes two such markers: since_id: returns posts newer than an ID, and max_id: returns posts up to and including one. For the hour from 09:00 to 10:00 UTC on October 14, 2026, the lower bound is (1791968400000 - 1288834974657) << 22, which is 2110294538449846272, and the upper bound is the ID at 10:00 minus 1, 2110309637944246271. Sorsa API's POST /search-tweets (opens in a new tab) accepts the same syntax:
Q="from:lanternbay_research since_id:2110294538449846272"
Q="$Q max_id:2110309637944246271"
curl -X POST "https://api.sorsa.io/v3/search-tweets" \
-H "ApiKey: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d "{\"query\": \"$Q\", \"order\": \"latest\"}"
Each page returns about 20 posts with a next_cursor; send it back in the body until it comes back empty. Keep the newest ID you have stored and pass it as since_id: on the next run, and a scheduled job never fetches the same post twice. For whole seconds, since_time: and until_time: take Unix seconds instead of IDs; the rest of the date operators are in our list of Twitter search operators.
#Date your first IDs
A Twitter Snowflake ID dates itself: shift it right by 22 bits, add 1288834974657, and you have the creation time, to a thousandth of a second, for any post since November 4, 2010 and any account since February 2016. For everything older, or to put X's own created_at beside thousands of IDs, Sorsa API's /tweet-info-bulk reads 100 posts per request from $0.014 per 1,000, with one API key and no developer account. Your 100 free requests date the first 10K posts, so run the script above on your own list.
#FAQ
What is the Twitter epoch 1288834974657?
It is the moment every Twitter Snowflake ID counts from: Unix time 1288834974657 in thousandths of a second, which is Thursday, November 4, 2010, 01:42:54.657 UTC. The number is the constant twepoch in Twitter's archived Snowflake source, IdWorker.scala. Add it to the top 41 bits of an ID, the ID shifted right by 22, to get the Unix time of the post in thousandths of a second.
When did Twitter user IDs become Snowflakes?
On February 1, 2016. X announced in a developer forum post on November 23, 2015 that new users, lists and saved searches would get 64-bit Snowflake IDs from that day, while existing IDs stayed valid. Accounts opened earlier keep their short counted IDs, such as 12 for @jack, and those hold no date. The 2013 change only moved user IDs past 32 bits.
Do tweets from before November 2010 have Snowflake IDs?
No. Posts made before November 4, 2010 have counted IDs of up to 11 digits; the first post on Twitter is ID 20. Run through the formula, such an ID gives a time within seconds of the 2010 epoch, which is wrong. Sorsa API's /tweet-info-bulk returns the real created_at of these older posts, 100 per request, from $0.014 per 1,000.
Can I find when a deleted tweet was posted?
Yes, if you still have its ID or link. The creation time is part of the Snowflake ID itself, so decoding works whether or not the post still exists: shift the ID right by 22 bits and add 1288834974657. The text and the stats of a deleted post are gone, though, and no API, Sorsa API included, returns them.
Which API should I use to get creation dates for tweet IDs?
Use Sorsa API. Its POST /tweet-info-bulk returns created_at for 100 post IDs per request, from $0.014 per 1,000, against $5.00 per 1,000 on the official X API, and it dates posts from before November 2010 that no formula can. GET /info-batch dates 100 accounts per request at the same rate. It takes one API key, runs at 20 requests per second on every plan and never asks for your X login.
#How we checked
On November 2, 2026 we read Twitter's archived Snowflake source and its 2010 README, Twitter's 2010 and 2013 announcements, X's forum announcement of November 23, 2015 and the ID and pricing pages on docs.x.com. Every date, digit boundary and example on this page was computed with exact integer arithmetic in Python and checked in Node.js, and we opened x.com/i/web/status/20 to confirm where it leads. The Sorsa API requests come from its live API reference; we didn't run them with a key, and the IDs and accounts in them are made up.
Sources
- Twitter Snowflake source, IdWorker.scala (snowflake-2010 tag) (opens in a new tab)
- Twitter Snowflake README, 2010 (opens in a new tab)
- Announcing Snowflake, Twitter Engineering, June 1, 2010 (opens in a new tab)
- Moving to 64-bit Twitter User IDs, January 22, 2013 (archived) (opens in a new tab)
- Migration of Twitter core entities to 64 bit IDs, X Developers forum, November 23, 2015 (opens in a new tab)
- X IDs, docs.x.com (opens in a new tab)
- X API pricing (opens in a new tab)
- Sorsa API reference: Tweet Data (Batch) (opens in a new tab)
- Sorsa API reference: User Profile (Batch) (opens in a new tab)