Snowflakes¶
A snowflake is a 64-bit identifier for a TEBHOS resource such as a user, guild, channel, message, role, emoji or attachment. It is the type written as snowflake in every field table of this reference, and the HTTP API and the Gateway share the same identifier space.
A snowflake is unique within TEBHOS. The value is stable, and it is not reused after the resource is deleted.
Never parse a snowflake as a JSON number
Snowflake values exceed the exact integer range of a JSON double, so parsing one as a number can silently change it. Parse the string into a 64-bit integer (BigInt in JavaScript, int in Python), and compare snowflakes as integers, because decimal strings of different lengths do not sort correctly as text.
Some identifiers are not snowflakes. Invite codes, vanity URL codes, webhook tokens and upload keys are opaque strings, and each resource page says so where it applies.
Format¶
A snowflake packs a timestamp, a worker ID and a sequence into 64 bits. The worker and sequence fields distinguish identifiers issued during the same millisecond. Bit 63 stays zero until the year 2084, so every snowflake issued today fits a signed 64-bit integer.
| Field | Bits | Description |
|---|---|---|
| Timestamp | 63 to 22 | Milliseconds since 2015-01-01T00:00:00.000Z |
| Worker ID | 21 to 12 | The ID generator worker, from 0 through 1023 |
| Sequence | 11 to 0 | The per-worker sequence within one millisecond, from 0 through 4095 |
The epoch is 1420070400000 milliseconds after the Unix epoch. The lower 22 bits describe how the identifier was allocated. Do not use them for routing or for any resource meaning.
Identifiers are issued by a dedicated generator service, so they increase monotonically per worker. Treat the embedded timestamp as an approximate creation time, not as proof that the resource exists.
Extracting a timestamp¶
To obtain the creation time in Unix milliseconds, shift the snowflake right by 22 bits and add the epoch.
The inverse produces the smallest possible snowflake for a Unix millisecond timestamp. It is useful as a pagination boundary.
from datetime import datetime, timezone
TEBHOS_EPOCH = 1420070400000
def snowflake_to_datetime(snowflake: str) -> datetime:
ms = (int(snowflake) >> 22) + TEBHOS_EPOCH
return datetime.fromtimestamp(ms / 1000, tz=timezone.utc)
def datetime_to_snowflake(dt: datetime) -> str:
return str((int(dt.timestamp() * 1000) - TEBHOS_EPOCH) << 22)
Wire representation¶
TEBHOS emits a snowflake as an unsigned decimal string in every JSON body and Gateway payload, for example "id": "1489002177550843904". Path parameters and query string parameters are always text.
Send snowflakes the same way:
- In a path, use the decimal digits, as in
GET /channels/1489002177550843904. - In a JSON body or query string, send a decimal string.
A path or body value that does not parse as a 64-bit integer is rejected, usually with 400 BAD_REQUEST. A well-formed ID that names no visible resource returns the ordinary not-found result for that resource, usually 404 NOT_FOUND.
Pagination¶
A collection that pages over a snowflake-ordered resource accepts a cursor such as before, after or around. Each operation defines which cursors it supports, whether they can be combined, the result order and how limit applies.
| Cursor | Selects |
|---|---|
before |
Identifiers lower than the cursor value, excluding it |
after |
Identifiers higher than the cursor value, excluding it |
around |
A window centred on the cursor value |
List channel messages accepts all three. The bulk message fetch in Messages accepts them on each entry. Some cursors are not snowflakes: for example the pinned message list pages by pin time. Each operation states the type of its own cursors.
Because the timestamp occupies the high bits, numeric snowflake order is creation order at millisecond resolution.
A cursor is a numeric boundary
A cursor does not need to identify an existing resource. A snowflake computed from a timestamp can delimit a time range, for example "messages after midnight yesterday".