Docs

How it works

What I found out about Rockstar's services behind the Social Club and GTA Online while building the app. It's the technical side, mostly for whoever is curious or builds tools too.

About these notes

A note for Rockstar Games

SocialClub Plus is a free fan project, not affiliated with or endorsed by Rockstar Games or Take-Two Interactive. It exists to make the Social Club easier to use for GTA Online players, where the official website offers little, and it's not meant to harm Rockstar's services or anyone who uses them. The app uses no exploits and no vulnerabilities. It sends the same requests the Social Club website, the Rockstar Games Launcher and the game send, on the player's own account, and keeps its requests few. Problems I found in Rockstar's services along the way are reported to Rockstar privately and are not published here. If Rockstar would like anything on this page changed or removed, you can reach me through the contact page.

None of this is documented by Rockstar. I read it from the Social Club website's scripts, the overlay's scripts and the game's own code, and checked it against the live services. Any of it can change without notice.

MeasuredSeen in answers from Rockstar's servers.
ReadRead in code: the website's scripts, the game's, or the app's own.
AssumedAn inference, not verified yet.

The game's encryption keys and a few problems still pending with Rockstar are left out on purpose.

Services

scapi.rockstargames.com

Website API

Job, playlist, crew, player and photo search, job details, bookmarks, profiles, crew emblems.

Website sign-in, some calls work signed out
*.ros.rockstargames.com

Rockstar Online Services (ROS)

The game's back end: user content (playlists, photos), matchmaking, presence, crews.

Launcher sign-in and a ticket
scui.rockstargames.com

Overlay API

What the in-game Social Club overlay calls: profile, avatars, friends, privacy.

A ROS ticket
prod.cloud.rockstargames.com

Content server

Job pictures, job creator files, crew emblems.

Public
socialclub.rockstargames.com/games/gtav/…

Old website

The pre-2019 Social Club, still online: career pages, the playlist browser, the emblem editor.

Website sign-in, browser cookies

Notes

  • The website session and the ROS session are separate, and tokens from one are not accepted by the other. One sign-in can give both, though (below).
  • The website API doesn't reject unknown query parameters. A wrong parameter name has no effect and gives no error, so every filter has to be checked by comparing results.
  • Some limits are enforced without an error: invalid jobs are dropped from a playlist, job search results past the 10,000th can't be paged to, and a lobby closed by its host is still advertised.
  • The website API allows about 30 searches back to back. The 31st answers 429, with no header saying how long to wait, and it clears after about 30 seconds. The limit is per address, not per account: right after a 429 the same search signed out got a 429 too. Measured

Sign-in

Website

ItemDetails
Sessionrsso-<suffix> cookie on signin.rockstargames.com, HttpOnly, 365 days. The suffix differs per account. Measured
TokenBearerToken cookie, a JWT valid for about 5 minutes. Sent as Authorization: Bearer <token>.
MintingSet by the sign-in page's JavaScript while the rsso session is valid, not by an HTTP redirect.

There are two authorize entry points, and only one of them gives a token that can be renewed: Measured

Entry pointToken set onRenewable
/connect/authorize/socialclub?lang=en-US&returnUrl=…socialclub.rockstargames.comYes
/connect/authorize/rsg?lang=en-us&returnUrl=…www.rockstargames.comNo (401)

Renewal:

POST https://socialclub.rockstargames.com/connect/refreshaccess
X-Requested-With: XMLHttpRequest
Content-Type: application/x-www-form-urlencoded; charset=utf-8
Cookie: <the cookies left by the sign-in>

accessToken=<current token>

Sent with the token alone, without the sign-in cookies, it answers 401. Measured

Game (ROS)

ROS only accepts sessions obtained the way the Rockstar Games Launcher obtains them. Tickets from an email and password (CreateTicketSc3) are refused with NotAllowed/Password. Measured

StepRequestResult
1GET https://signin.rockstargames.com/signin/user-form?cid=launcher. The page runs in launcher mode and expects the launcher's native bridge.An authCode, valid for about a minute
2GET https://rgl.rockstargames.com/api/connect/gateway?code=<authCode>&fingerprint=<state>, where the state is {"fp": <device fingerprint>}A ScAuthToken, a launcher ticket, and a remembered-machine token that may be empty
3POST https://gta5-prod.ros.rockstargames.com/gta5/11/gameservices/auth.asmx/CreateTicketScAuthToken2A ticket

Step 1: the launcher bridge

Inside the launcher, the sign-in page talks to the launcher through two functions on window, rgscQuery and rgscAddSubscription. Each takes an object with method, params and an onSuccess callback. In a normal browser they're missing and the page can't finish. These are the answers the page needs: Read

CallAnswer
rgscQuery GetWindowConfig{"serviceInfo": {"rosPlatform": "pc"}, "platformInfo": {}}
rgscQuery GetDeviceId{"fingerprintKvp": <device fingerprint>}
rgscQuery GetVersionInfo{"isDebugSdk": false}
rgscQuery GetProfileList{"profiles": []}. With no saved profile, the page shows the email and password form.
rgscQuery CallAuthResultNot a question: the page hands over the result here, in params.authCode.
rgscAddSubscription OnStartAuth{"launchPlatform": 0, "titleLaunchInfo": {"activeTitleName": "gta5", "externalPlatformInfo": {}}, "profile": null, "errorMessage": null}

The device fingerprint is an object of five strings, in this order: device_name, machine_name, os_version, volume_serial, cpu_info. The launcher fills them in from the machine. SocialClub Plus sends its own name as the device and machine, and a random id kept per install for the serial and CPU, so the sign-in shows up as SocialClub Plus and not as the launcher. The page runs its own bot checks, and the password only goes into Rockstar's form.

Step 2: the gateway

GET https://rgl.rockstargames.com/api/connect/gateway?code=<authCode>&fingerprint=<state, URL-encoded>
X-Requested-With: XMLHttpRequest
Origin: https://rgl.rockstargames.com
Referer: https://rgl.rockstargames.com/
Cookie: <the sign-in window's cookies for rgl.rockstargames.com, if any>

The state is the fingerprint wrapped as {"fp": {...}}, with one space after the colon, as the launcher writes it. The same string goes with every ticket request and has to match. The JSON answer holds ScAuthToken (164 characters) and RememberedMachineToken. A ScAuthToken keeps minting tickets for days, one about 8 days old still worked. Measured

Step 3: the ticket

POST https://gta5-prod.ros.rockstargames.com/gta5/11/gameservices/auth.asmx/CreateTicketScAuthToken2
User-Agent: ros <encoded version string>
SCS-MFAMachineFingerPrint: <the state from step 2>
Content-Type: application/x-www-form-urlencoded; charset=utf-8

<the form, encrypted>
FieldValue
ticketEmpty
platformNamepcros (Legacy) or pcrosalt (Enhanced)
scAuthTokenFrom step 2
rememberedMachineTokenFrom step 2, may be empty

The version string is e=1,t=gta5,p=<platformName>,v=11. The User-Agent sends it as ros followed by the Base64 of four bytes 0xCD and then each byte of the string XORed with 0xCD. A GET, or platformName=pc, doesn't work. Measured

The platform name, the key and the platform in the User-Agent must agree, otherwise the answer is DoesNotMatch/TitleInfo. The XML answer holds Ticket, SessionKey, SessionTicket, CloudKey, PlayerAccountId, RockstarId, Privileges, Services (a host per service) and SecsUntilExpiration (86,399, so one day). Measured

One ROS session per account. A new ticket invalidates the previous one (AuthenticationFailed/DupLogin), and a game running on that account is signed out of GTA Online. Measured

ROS errorMeaning
AuthenticationFailed/DupLoginAnother session replaced this one
NotAllowed/PrivilegeThe ticket has no write privilege on this build
NotAllowed/ProfaneText refused by the word filter
InvalidData/MissionCountToo many jobs in a playlist
RateLimitExceededToo many writes in a short time
DoesNotMatch/TitleInfoPlatform name, key and User-Agent disagree

Renewing it from a ticket

A live ticket can make a new ScAuthToken with socialclub.asmx/CreateScAuthToken on prod.ros, fields ticket, audience, ttlminutes and userdata (may be empty). The token comes back in <Result>. The old Social Club SDK does exactly this after signing in, with audience Game and 30 days, and signs in with that token the next time. Read A token made this way with 30 days minted a working Enhanced ticket. Measured Note that GetScAuthToken looks similar but gives a web-linking token that the ticket calls refuse.

So the launcher sign-in can be kept alive without the password, but only by minting a ticket, which ends the account's GTA Online session anyway.

One sign-in for both

The launcher's sign-in leaves the website's session behind too. Right after it, the browser profile holds an rsso-<suffix> cookie, and opening /connect/authorize/socialclub in that profile gives a BearerToken for the same account in about 2 seconds, with no form. Measured

It only works that way round. After the website's own sign-in, the launcher's page still asks for the password, and the ScAuthToken inside the website token's claims (scAuth.ScAuthToken, 160 characters) is refused for a ticket with AuthenticationFailed/ScAuthToken. Measured That's why the app's one sign-in is the launcher's.

The launcher sign-in itself mints no ROS ticket, so signing in doesn't end a GTA Online session. Only the first ticket does.

Game requests

Every ROS call, the ticket request included, sends its form encrypted and gets its answer encrypted. The scheme is the one in FiveM's open source code (ros-patches-five), and the app follows it line by line. Each build of the game has its own platform key, built into the game. The keys are not given here. Read

The platform key

A Base64 string. Decoded, byte 0 isn't used, bytes 1 to 32 are an RC4 key, and bytes 33 to 48 and 49 to 64 are two 16-byte keys, here called the mask key and the hash key. Both are stored encrypted with that RC4 key, each with a fresh RC4 state.

The form

Fields sorted by name in byte order, written as name=value and joined with &. Values are encoded as UTF-8: letters, digits and - _ . ~ stay as they are, a space becomes +, and every other byte becomes %xx in lowercase hex. Calls made with a ticket carry it in the ticket field.

Encrypting a request

  1. Pick 16 random bytes: the request key.
  2. Write the request key XORed with the mask key. These are the first 16 bytes of the body.
  3. With a ticket, XOR the request key with the first 16 bytes of the ticket's SessionKey (Base64). The ticket request itself has none, and the request key stays as it is.
  4. Encrypt the form with RC4 under the request key, and append it.
  5. Append a 20-byte trailer, computed over everything written so far followed by the hash key: SHA-1 without a ticket, HMAC-SHA1 keyed with the request key with one.

Reading an answer

The first 16 bytes, XORed with the mask key (and with the session key, for a call made with a ticket), are an RC4 key. One RC4 stream then runs over the rest. Bytes 16 to 19 give a block size, big-endian. From byte 20 on, the answer comes in pieces of the block size plus 20 bytes: the block, then a 20-byte trailer that is skipped. The last piece can be shorter. Inside there's XML. An error raised by the server itself, and not by the service, comes back as plain text.

Calls made with a ticket

They carry four more headers. Measured

HeaderValue
ros-SecurityFlags239
ros-SessionTicketThe ticket's SessionTicket
ros-Challenge8 random bytes, Base64
ros-HeadersHmacBase64 of an HMAC-SHA1 keyed with the session key XORed with the mask key (16 bytes), over the method, the path, the security flags, the session ticket and the challenge as sent, each followed by a zero byte, and then the hash key

Every ROS host answers over https with a valid certificate. A wrong key gives an answer that can't be decrypted, while an error like NotAllowed/… or InvalidArgument/<field> means the server did read the request. Measured

Website API

Host https://scapi.rockstargames.com. Every call carries X-Requested-With: XMLHttpRequest, and the bearer token when signed in.

Access without a token

Signed out, the website sends X-AMC: true (allow mature content) after its age gate. Signed in, it leaves it out. Read

CallWithout a token
/search/mission200 with X-Requested-With and X-AMC: true, 403 otherwise
/ugc/mission/details200 with both headers
/search/playlist200 with both headers
/search/crew200
/search/user401
/profile/*401

Measured

Job search

GET /search/mission?title=gtav&platform=pcalt&missionType=race&subType=landrace&sort=likes&searchTerm=&pageIndex=0&pageSize=30
ParameterValues
titlegtav
platformpc (Legacy), pcalt (Enhanced), ps4, ps5, xboxone, xboxsx
missionTypeThe job type
subTypeThe website's lowercase values: landrace, stuntrace, targetrace, airrace, waterrace, creator, adversary, arenadeathmatch, … Ignored without its missionType.
sortlikes, plays, createdDate (first published), updatedDate (last update), name, relevance. Anything else falls back to last update.
filterrockstar, rockstarVerified, community (Rockstar's Community Series, about 35 jobs, not all player jobs), scMembers (all player jobs), trending (recent jobs being played a lot right now), myBookmarks, or crew with crewId
areaA map zone code (Search by area)
playersA player count: only jobs whose minimum is at most that and whose maximum is at least that
creatorRockstarIdA creator's Rockstar ID
dateRangeCreatedtoday, last7, lastmonth, lastyear, any, or custom with startDate and endDate
startDate, endDateBoth accept a time of day, in UTC (2020-03-10T13:00:00). endDate is exclusive.
searchTermFree text over name, description and tags. #tag searches the tags. The API accepts it empty, it's only the website's form that doesn't.
pageIndex, pageSizeUp to 30 per page

Measured

  • The dates are on two different axes. The presets (today, last7 and so on) filter on rootCreatedDate, when the job was first published. A custom range filters on createdDate, the date of the current version, which for an updated job is its last update. I didn't find a custom range on the first publication. Measured
  • The website writes a crew filter as crew_<id> in its own address, but the API only filters with the split form, filter=crew&crewId=<id>.
  • community and rockstarVerified ignore the platform and return jobs of every build. Measured
  • No result past the 10,000th can be fetched: at 30 per page, page 332 answers and 333 returns 500.
  • The answer has three collections, items, users and crews, linked by id. Signed in, each item carries bookmarked and played for the account.
  • There's no sort direction, no player range, no rank, no vehicle class and no language filter. Each was tried with names that would change the result, and all were ignored. POST answers 405. Measured
  • A job's page on the website is /job/gtav/<id>. The id is the same in playlists and on the content server.

Job details

GET /ugc/mission/details?title=gtav&contentId=<id>

Returns what the website's job page shows, plus content.data.mission, a trimmed copy of the creator file. Signed in, it adds liked, played, bookmarked, and lastPlayed for a job the account has played. Measured

FieldMeaning
gen.min, gen.numMinimum and maximum players
gen.rankMinimum rank
race.chp, race.lap, race.rdisCheckpoints, laps, length in metres
race.chlCheckpoint coordinates
rule.*, race.aveh, race.adlcNot known yet (time of day, traffic, vehicle classes as bit masks)

Crews, players, photos

CallNotes
GET /search/crewFinds only open crews unless crewtype=all. Other values: opencrews, rockstar, chatterboxes, thrillseekers, rebels, soldiers, allstars. sort: relevance or membercount. Text in searchTerm.
GET /search/userText in searchTerm. service searches the name on another service: sc, np, xbl, xblpc, steam, twitch. total is capped at the page size.
GET /profile/getbasicprofile?rockstarId=<id>Name and short profile for any Rockstar ID, one per call. An id it can't read returns the viewer's own profile, not an error.
GET /profile/playedplatforms?game=gtavThe signed-in account's platforms, for example {"platforms":["pc","pcalt"]}. Always the viewer's own.
GET /search/photoNeeds platform, and creatorRockstarId for one player. A photo's page is /photo/gtav/<id>.

Measured

Search by area

The job search has a map zone filter, area=<zone code>, which the website's script accepts but its jobs page has no control for. It returns the jobs whose meta.loc holds that code exactly. meta.loc is a list of zones the game writes into the creator file when the job is published (Job files). Measured

  • Races: the zones of the checkpoints, minus the zone the race starts in, even when later checkpoints are in it. So a race that only starts in an area isn't found by that area, and a race entirely inside its start zone has none. Measured
  • Other modes: usually the zone they start in. Measured
  • The codes are the game's zone names in upper case, cut to seven characters (BANHAMC, not BANHAMCA). One per call, case-sensitive, and an unknown code gives 0. 93 codes find jobs. Measured
  • The golf course is spelled two ways: older jobs say GOLF, newer ones golf, and they're different jobs. A search for the golf course takes both calls.
  • Outside every zone (high in the sky, the Arena War arena, Cayo Perico) a job gets SanAnd, written just like that.

To place a point in a zone the game takes the smallest box by volume among the boxes around it. Read The app reads the boxes from the game's files and draws its map with the same rule (without the height, since the map is flat), and it matched the meta.loc of 29 real races checkpoint by checkpoint. Measured That's why a tunnel or a creek can be a zone of its own.

Bookmarks

Each platform has its own list. The newer generation's list includes the older one's: pcalt includes pc, ps5 includes ps4, xboxsx includes xboxone. Measured

ActionRequest
ListGET /search/mission?filter=myBookmarks&platform=<p>. Without a platform it answers status:false. Every job comes with bookmarked: true and played.
SetPOST /ugc/mission/bookmark, JSON {"title":"gtav","contentId":"<id>","platformType":"<p>","bookmark":true|false}. It sets the state, it doesn't toggle. Answer {"status":true}.

platformType: Measured

  • A PC job takes its own platform, pc or pcalt.
  • A console job, from an account that plays on PC: any platform name returns 500, while an empty value is accepted and lands on that console's list. A player reported the empty value failing on their account too.
  • The website sends the job's own platform for every job. Read An account that plays on that console probably accepts it. Assumed

The list is a search, so it counts against the same limit of about 30 searches (Notes). A player with about 1,000 bookmarks hit the 429 reading all six lists. The app now skips an older generation's list when the newer one already has all of it, and spaces its reads. Measured

Job files

A job's full creator file, the data the game loads to run it, is public:

GET https://prod.cloud.rockstargames.com/ugc/gta5mission/{id}/0_{version}_{language}.json
PartValue
{id}The job id. Measured
{language}The language the job was created in, and only that one. It's not written anywhere, so the app tries them in turn. Rockstar's own jobs answered in every language tried. Measured
{version}The second number in the job picture's file name (…/2_3.jpg → 3). Assumed

mission holds sections under short internal names: gen, race, prop, dprop, veh, obj, weap, ene, zone, rule, … The fields that are settled:

PathMeaning
mission.gen.nmName
mission.gen.decDescription, in chunks
mission.gen.owneridCreator's nickname
mission.gen.typeFamily: 0 mission, 1 deathmatch, 2 race, 3 survival
mission.gen.min, numMinimum and maximum players
mission.gen.startWhere the job starts
mission.race.chp, lap, rdis, chlCheckpoints, laps, length in metres, checkpoint coordinates
meta.locThe map zones the job is filed under (Search by area)

Legacy and Enhanced

LegacyEnhanced
Website platformpcpcalt
ROS platformpcrospcrosalt
Jobs a playlist can holdLegacyEnhanced and Legacy
Bookmark listLegacyEnhanced, including Legacy's

Write privilege follows the build played last. A ticket's Privileges list decides whether it can write. An account that had never played GTA Online on Enhanced got an Enhanced ticket with no privileges: reads worked, writes returned NotAllowed/Privilege. After one session on Enhanced, the Enhanced ticket was complete and the Legacy one empty. The same goes for lobby searches and photo uploads. Measured

Playlists

The website can read playlists but can't create them. They're created through ROS user content, ugc-gta5-prod.ros.rockstargames.com/gta5/11/gameservices/ugc.asmx, content type gta5playlist, with a ticket that has write privilege on the build. Measured

Playlist data:

{ "h2h": true, "list": [ { "cid": "<job id>", "name": "<job name>", "type": 2 } ] }
  • list is the jobs, in play order. The game only reads cid. Read
  • type is the family: 0 mission, 1 deathmatch, 2 race, 3 survival.
  • h2h and ch: meaning not known.
  • A playlist is published or a draft. A draft stays on the account and can't be played.
  • Reads lag behind writes by a minute or two. A running game needs a restart to list a new playlist.
  • The game plays jobs in list order unless Shuffle is on in the menu the playlist is started from, and that option starts off every time. Read
LimitValue
Jobs16. 17 returns InvalidData/MissionCount.
Unknown job idsDropped without a word. The count is checked before the drop.
Jobs from the other buildAn Enhanced job in a Legacy playlist is dropped the same way
Name100 characters (101 returns NotAllowed/Profane). The game only keeps the first 63 bytes, since its scripts copy the name into 64-byte buffers.
Names containing "SCP"Always refused as profane
Playlists per accountMore than 22. I didn't look for the real cap.
Create rateAbout 9 or 10 creates, then RateLimitExceeded for 2 to 3 minutes

Measured

Lobbies

Open job lobbies and free roam sessions aren't exposed by the website. They're advertised in ROS matchmaking, mm-gta5-prod.ros.rockstargames.com, and a search needs a ticket with privileges on the build.

Advertised attributes

AttributeMeaning
OwnerThe host's Rockstar ID
KindJob lobby or free roam
Activity type0 mission, 1 deathmatch, 2 race, 3 survival. Special modes have their own values.
Activity idjoaat of the job id, as a signed 32-bit integer. Not the name and not the id itself.
Activity playersFree places, not players
Region1 South America, 2 US East, 3 Europe, 4 Asia, 5 Australia, 6 US West, 7 Japan
Language0 en, 1 fr, 2 de, 3 it, 4 es, 5 pt-BR, 6 pl, 7 ru, 8 ko, 9 zh-TW, 10 ja, 11 es-MX, 12 zh-CN
Discriminator32-bit value that must match exactly for players to meet

Measured

  • Players in a job lobby are the job's maximum (gen.num in its details) minus the free places. In free roam another attribute looks like the player count (up to 32), not checked against a real session. Assumed
  • No call turns an activity id into a job. To name a lobby's job you hash known job ids until one matches. An index of every Enhanced job (about 3.5 million) named every open race lobby in a sample. Measured

Discriminator bits

BitsMeaning
0 to 7Port
8 to 11Version
12 to 13Build
14Bad sport pool
15Matchmaking open. Off when the host closed the lobby, which stays advertised anyway.
16In progress: the job has already started
17Free aim. Free and assisted aim players never match.
18 to 31Hash of runtime values of the game (file checksums, player pool)

Read bit 15: Measured

Joining

Joining is done by the game client, peer to peer, and GTA V has no launch option that targets a job or a session. Read When a player starts a job, the game looks for an open lobby with the same activity id for about 60 seconds before hosting one. Read So starting a job from the bookmarks while a lobby of it is open should join that lobby, but I haven't seen it happen yet. Assumed

Profile picture

The profile picture is set through the in-game overlay's API, on scui.rockstargames.com. The overlay is a web page packed in scui.pak in the Social Club folder, and every call it makes is in its script. Read The calls are authorised with the ROS ticket itself, as Authorization: SCAUTH val="<ticket>", plus X-Title-Name: gta5, so like every ticket they end the account's GTA Online session. Measured

CallWhat it does
POST /api/profile/GetCustomAvatarsThe account's photos it may use as a picture (selfies and mugshots), each with i, cid (the photo's content id), src and t
POST /api/profile/GetAvatarsRockstar's stock avatars by game
POST /api/profile/UpdateAvatarSets it: {"AvatarUrl":"","AvatarId":<i>,"AvatarContextId":"<cid>","CustomAvatar":true} for a photo, the stock avatar's i and CustomAvatar false otherwise

Measured

The new picture was live within two minutes, at prod-avatars.akamaized.net/custom-avatars/n/<cid>.png (128 by 128, and /l/ for 256). The avatar is a fixed square cut from the photo, not its centre. Measured

A photo is uploaded the way the game does it, as ROS user content of type gta5photo: a create that answers with a CDN address and a short-lived upload token, the JPEG sent there, then a publish. Once it's on the account it shows up in GetCustomAvatars and can be set like any selfie. This needs write privilege on the build, like a playlist. Measured

Crew emblems

Emblems are made in the old website's editor (/emblems/new). An emblem is a stack of layers, each one of Rockstar's shapes placed with a position, scale, rotation, colour and so on, on a 512 by 512 canvas. These calls use the website's session, not a ticket, so they don't end a GTA Online session. Read

CallWhat it does
POST /emblems/saveSaves an emblem to the account's library (up to 10). The answer lists the crews the account may publish to.
POST /emblems/publish{emblemId, crewId}: puts it in the crew's gallery. It does not make it the active emblem.
GET /crew/emblems?crewId=<id> (scapi)The crew's gallery, with who made each emblem
POST /crew/activateEmblem?crewId=<id>&emblemId=<id> (scapi)Makes a gallery emblem the active one. The parameters go in the query string, with a JSON body it answers 404.

Measured

The old website's calls carry the page's anti-forgery token like its other calls (Old website). The active emblem's files on the content server change within seconds of the activation, and a new gallery emblem's pictures appear over about a minute. Measured

Every crew has a public folder on the content server, prod.cloud.rockstargames.com/crews/sc/<bucket>/<crew id>/publish/. The bucket is a djb2 hash of the crew id written in decimal, modulo 10,000, padded to four digits. emblem/emblem_<size>.png is the active emblem in 32, 64, 128, 256 and 512, and emblem_128.dds is the texture the game asks for. Measured

Old website

  • Pages under socialclub.rockstargames.com/games/gtav/<platform>/. Calls are the site's own routes, with the browser's cookies and, for most of them, the anti-forgery token each page carries (__RequestVerificationToken, sent back as a header).
  • The platform is session state: calls take no platform parameter and answer for the platform of the last page loaded.
  • Career pages: a player's GTA Online statistics per platform, depending on the player's privacy settings.

Playlist search, the one the website still uses: Measured

GET /games/gtav/playlists/ajax/search?SearchOptSort=Played&SearchOptPublisher=&SearchOptDate=&TextQuery=stunt&page=1
ParameterValues
SearchOptSortPlayed, CreatedDate, Name
SearchOptPublisherEmpty (any), me, friends, rockstar, members, crew<id>
SearchOptDateEmpty, today, last7, lastMonth
TextQueryFree text
Bookmarkedtrue
page1, 2, … (20 per page)

Each job in a result carries the same mission block as the job details, and meta.loc. The new API's playlist search does more: one player, any date range, and no account needed.

Glossary

scapi
scapi.rockstargames.com, the API behind the Social Club website
ROS
Rockstar Online Services, the game's back end (*.ros.rockstargames.com)
scui
The in-game Social Club overlay and its API
rsso
The website sign-in's session cookie
BearerToken
The website API's five-minute token
ScAuthToken
The launcher sign-in's token, which mints ROS tickets
Ticket
A ROS session: one day, one per account
UGC
User-generated content: jobs, playlists, photos
Content id
A job's id, shared by the website, playlists and the content server
Rockstar ID
An account's number
pc / pcalt
Website names of Legacy and Enhanced
pcros / pcrosalt
ROS names of Legacy and Enhanced
meta.loc
The map zones a job is filed under, written by the game when it's published
joaat
The game's string hash (Jenkins one-at-a-time, lower-cased input)
Discriminator
Session value that must match for players to meet
X-AMC
"Allow mature content" header, sent by the website when signed out

If you find something wrong here, tell me on the Discord server.