What is Game Persistence?
Game Persistence is ROBLOX’s DataStore system, it means Data To Persist in a game or Data that Stays.
What are the benefits to knowing these endpoints?
- It will be helpful for external menus that require data from ROBLOX without having some proxy in between
- No rate limiting on SetAsync anymore, this rate limit of 6000ms will be abolished, as it turns out this rate limit is done internally on the engine, and not the server.
- Better Data management
What are the endpoints?
The base URL for this API is https://gamepersistence.roblox.com:443 if you use HTTP over TLS/SSL, it is http://gamepersistence.roblox.com:80 if using HTTP.
The endpoints we have found consist of:
GetAsync:
https://gamepersistence.roblox.com:443/persistence/getV2 TLS/SSL ||| http://gamepersistence.roblox.com:80/persistence/getV2 HTTP
SetAsync
https://gamepersistence.roblox.com:443/persistence/set TLS/SSL ||| http://gamepersistence.roblox.com:80/persistence/set HTTP
The usage of these follows:
- You can only POST to these endpoints.
- These endpoints require Authorization, but don’t have Cross Site Forgery protection.
- You will need a header called:
Roblox-Place-Id
, which, as it appears, is the PlaceId of the DataStore you want, it is required. - The Content-Type must be
application/x-www-form-urlencoded
. - With SetAsync, the
valueLength
query parameter has to be exact, and it is not optional. - All values sent have to be valid UTF-8 Encoded characters.
Let us start with the Examples:
/persistence/getV2
You can use HTTP/1.0, HTTP/1.1 and HTTP/2 with this.
# You can use HTTP also, you don't actually need to use http-over-tls
# The query parameter that you need for this is the `type`, which can just equal to `standard`
POST /persistence/getV2?type=standard HTTP/1.1
Host: gamepersistence.roblox.com
Port: 443
Scheme: https
Accept: application/json; charset=utf-8
Accept-Encoding: gzip, deflate, br
Accept-Language: ru,en;q=0.9,en-US;q=0.8
Cache-Control: nocache
Connection: close
Content-Type: application/x-www-form-urlencoded; charset=utf-8
Cookie: .ROBLOSECURITY=_||_...
DNT: 1
Pragma: nocache
Roblox-Session-Id: {"SessionId":"...","GameId":"...","PlaceId":...,"ClientIpAddress":"...","PlatformTypeId":...,"SessionStarted":"...","BrowserTrackerId":...,"PartyId":...,"Age":...,"Latitude":...,"Longitude":..,"CountryId":...,"PolicyCountryId":null,"LanguageId":..,"BlockedPlayerIds":[...],"JoinType":"...","PlaySessionFlags":...,"MatchmakingDecisionId":"..."}
Roblox-Game-Id: ...
Roblox-Place-Id: ...
Requester: Client
Sec-Fetch-Dest: document
Sec-Fetch-Mode: navigate
Sec-Fetch-Site: none
Sec-Fetch-User: ?1
Upgrade-Insecure-Requests: 1
User-Agent: Roblox/WinInet
&qkeys[0].scope=DATASTORESCOPE
&qkeys[0].target=KEY
&qkeys[0].key=DATASTORE
# Scope is the second argument to
# GlobalDataStore DataStoreService::GetDataStore(std::string Key, std::string Scope),
# and it should be `global` if you don't want a scope.
# Target is the first Argument to
# std::variant GlobalDataStore::GetAsync(std::string Key),
# void GlobalDataStore::SetAsync(std::string Key, std::variant Value)
# void GlobalDataStore::UpdateAsync(std::string Key, std::Function<void(std::variant)> Replacer)
# Key is the first argument of
# GlobalDataStore DataStoreService::GetDataStore(std::string Key, std::string Scope)
If this request doesn’t fail, it should respond with:
HTTP/1.1 200 OK
cache-control: no-cache
pragma: no-cache
content-type: application/json; charset=utf-8
content-encoding: gzip
expires: -1
vary: Accept-Encoding
x-frame-options: SAMEORIGIN
roblox-machine-id: CHI1-WEB7589
x-powered-by: ASP.NET
p3p: CP="CAO DSP COR CURa ADMa DEVa OUR IND PHY ONL UNI COM NAV INT DEM PRE"
date: ...
content-length: ...
x-rblx-pop: lhr2
{"data": []}
Because the Key doesn’t exist, it will return nothing, but if it did, and the value was \"true\"
, then it would return this:
HTTP/1.1 200 OK
cache-control: no-cache
pragma: no-cache
content-type: application/json; charset=utf-8
content-encoding: gzip
expires: -1
vary: Accept-Encoding
x-frame-options: SAMEORIGIN
roblox-machine-id: CHI1-WEB7589
x-powered-by: ASP.NET
p3p: CP="CAO DSP COR CURa ADMa DEVa OUR IND PHY ONL UNI COM NAV INT DEM PRE"
date: ...
content-length: ...
x-rblx-pop: lhr2
{"data": [{"Key": {"Scope": "DATASTORESCOPE", "Target": "KEY", "Key": "DATASTORE"}, "Value": "\"true\""}]}
I won’t release the usage of SetAsync, as it can be abused.
But if you were to monitor the traffic on HTTP between these requests:
EXP | SRC | DEST | PRO | SRCPORT | DESTPORT | FLAG | INFO | ADDPRO |
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
CHAT | 172.0.0.1 | 128.116.119.3 | TCP | 150 -> http(80) [SYN] Seq=0 Win=64240 Len=0 MSS=1460 WS=256 SACK_PERM=1
CHAT | 128.116.119.3 | 172.0.0.1 | TCP | http(80) -> 150 [SYN, ACK] Seq=0 Ack=1 Win=26880 Len=0 MSS=1400 SACK_PERM=1 WS=512
| 172.0.0.1 | 128.116.119.3 | TCP | 150 -> http(80) [ACK] Seq=1 Ack=1 Win=64240 Len=0
-> | 172.0.0.1 | 128.116.119.3 | HTTP| 150 -> http(80) [PSH, ACK] Seq=1 Ack=1 Win=64240 Len=1050 >> HyperTextTransferProtocol: FormData Len=77
| 128.116.119.3 | 172.0.0.1 | TCP | http(80) -> 150 [ACK] Seq=1 Ack=1051 Win=26880 Len=0
<- | 128.116.119.3 | 172.0.0.1 | HTTP| http(80) -> 150 [PSH, ACK] Seq=1 Ack=1051 Win=26880 Len=501 >> HyperTextTransferProtocol: JavascriptObjectNotation Len=106
| 172.0.0.1 | 128.116.119.3 | TCP | 150 -> http(80) [ACK] Seq=1051 Ack=502 Win=64240 Len=0
CHAT | 128.116.119.3 | 172.0.0.1 | HTTP| http(80) -> 150 [FIN, ACK] Seq=502 Ack=1051 Win=26880 Len=0
| 172.0.0.1 | 128.116.119.3 | TCP | 150 -> http(80) [ACK] Seq=1051 Ack=503 Win=64240 Len=0
CHAT | 172.0.0.1 | 128.116.119.3 | HTTP| 150 -> http(80) [FIN, ACK] Seq=1052 Ack=503 Win=64240 Len=0
| 128.116.119.3 | 172.0.0.1 | TCP | http(80) -> 150 [ACK] Seq=504 Ack=1052 Win=26880 Len=0