Roblox gamepersistence endpoints and usage of these

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?


  1. It will be helpful for external menus that require data from ROBLOX without having some proxy in between
  2. 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.
  3. 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:

  1. You can only POST to these endpoints.
  2. These endpoints require Authorization, but don’t have Cross Site Forgery protection.
  3. You will need a header called: Roblox-Place-Id, which, as it appears, is the PlaceId of the DataStore you want, it is required.
  4. The Content-Type must be application/x-www-form-urlencoded.
  5. With SetAsync, the valueLength query parameter has to be exact, and it is not optional.
  6. 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
20 Likes

You do realize this is potentially illegal if abused, right?

3 Likes

Yes, and that’s why I didn’t show usage of SetAsync

Literally doesn’t matter. Anyone with the know how to abuse this will know how to use the endpoint, which you should not have publicized as recommended in the thread you linked this to.

3 Likes

Also refer to this:

I also pointed that out about an hour ago, but this might be a new feature that Roblox is preparing to make public but hasn’t announced yet. Either way, it’s not a good idea to use these endpoints as they can break at any point.

3 Likes

If you go onto the https://gamepersistence.roblox.com:443/docs/json/v1, it in fact has a this, but no endpoints, so you might be right

Although this is a very intelligent contribution, I do think this can be exploited in the wrong ways.

2 Likes

Anyone with the technical know-how can already pull up Wireshark and work out how to use the endpoints for themselves. Releasing how these endpoints work is very interesting in my opinion and now opens the door for me to make external data management portals for my games, for example.

Abusing any API could be potentially illegal - that doesn’t mean that they shouldn’t be publicly documented or released. It is a double-edged sword.

2 Likes

Aforementioned disadvantages to this are invalid for a number of reasons:

  1. Abuse potential is limited by DoS protection on Roblox’s end (which applies to all endpoints afaik)

  2. Any form of computer misuse or abuse is illegal; using undocumented APIs is not illegal.

  3. He didn’t actually release precise details of how the set method works, which will prevent some dumb skid from doing something stupid with it.

    • with that said it would require minimal knowledge or experience to find it, using basic tools
    • see also what grilme99 said

However it’s not ideal to use this because:

  1. It doesn’t even have Swagger documentation, which is a massive red flag since implementation notes are not known.

  2. Behaviour could change at any time, which will break your code; meaning you shouldn’t use this for at all, but especially for mission critical applications.

  3. This is likely part of Roblox’s internals, which means that there is no gaurentee or warning about anything.

4 Likes

Not only did I not show precise details, but I didn’t even show any implementation.

That is true, but this is for the people who wish not to find it themselves

This is pretty interesting, thanks for sharing!

1 Like

I was curious to when someone was going to release this, it’s probably better to keep them hidden for now.

If Roblox wanted you to use the endpoints they’d be available to the public, also you can’t set async someone else’s data unless you own that game.