Skip to content

Authentication ​

Sign in ​

Make sure you're passing all Security headers or you might stumble upon {"code":28,"message":"ACCOUNT_BANNED","profileId":null,"type":1,"reason":null,"isBanAutomated":true,"thirdPartyUserIdToShow":null,"banSubReason":null} — but don't fret — it's a fake error, your account isn't banned and the API simply blocked your request, not the account. Endpoint selection depends on the recaptcha_first_party assignment: when disabled (default) sign-in uses /v8/sessions; when enabled the client uses POST /v9/sessions with an extra captchaToken, sharing the same body otherwise.

POST /v8/sessions

Body:

  • email — string with email
  • password — string with password, don't specify if using authToken
  • authToken — string obtained from login+password flow or null
  • token — FCM (push service) string or null
  • geohash — geohash string or null

Response:

  • profileId — string with numbers, account's ID
  • sessionId — Session ID
  • authToken — string, Auth token for session refresh

Errors:

  • ACCOUNT_BANNED — could be malformed request
  • INVALID_INPUT_PARAMETERS — incorrect credentials

Sign in via reCAPTCHA ​

reCAPTCHA variant of POST /v8/sessions. The client uses this endpoint instead of /v8/sessions only when the recaptcha_first_party assignment is enabled. That assignment is sent by the server and resolved before login. The body is identical to SessionCreateRequest plus a captchaToken (a reCAPTCHA Enterprise token for the login action). The official client always populates captchaToken here and aborts locally if it can't obtain one, so a null/absent token never reaches this endpoint from the app.

POST /v9/sessions

Body:

SessionCreateRequestV9

Response:

  • profileId — string with numbers, account's ID
  • sessionId — Session ID
  • authToken — string, Auth token for session refresh

Errors:

  • ACCOUNT_BANNED — could be malformed request
  • INVALID_INPUT_PARAMETERS — incorrect credentials; on this endpoint a rejected captchaToken (e.g. expired or low-score) is also a plausible cause — server behavior, not confirmed from the client

Login via third party, WIP ​

NOTE

This endpoint hasn't been researched yet

Single endpoint used for both initial third-party sign-in and third-party session refresh, distinguished by the request body shape (oneOf).

Facebook (vendor 1): the Facebook user access token (scopes public_profile and email, app id 1273378622718674) is sent here verbatim as thirdPartyToken (ThirdPartyRequest). There is no exchange step, and nothing else from the Facebook session — user id, signed_request, id_token, granted scopes, expiry — is forwarded. See Facebook login dialog for how that token is obtained.

Google (vendor 2): a Google server auth code obtained on-device is exchanged for a Google access_token via Exchange Google auth code (POST /v3/users/thirdparty/exchange), and that token is sent here as thirdPartyToken.

When registered is true, authenticationResponse carries the session. When registered is false, the identity has no account yet: the server resolves the provider identity from the token on its own and returns it as thirdPartyUserInfo, and sign-up continues at Create third party account.

A third outcome is possible on this call and on the account-creation calls: HTTP 403 with ERR_VERIFICATION_REQUIRED (Grindr error code 30) and a profile id in the error body. That profile must pass phone verification (POST /v4/sms/verification/{profileId}/sendcode then /verifycode, see SMS verification) before a session can be obtained.

Session refresh reuses this same URL with ThirdPartySessionRequest ({ thirdPartyUserId, authToken, geohash }): the provider token is not re-sent — only the stable thirdPartyUserId and the rolling Grindr authToken. The response is the same ThirdPartyAuthResponse, yielding a fresh sessionId (JWT) and a rotated authToken.

There is no reCAPTCHA variant of this endpoint and no captchaToken field: the recaptcha_first_party assignment only switches Sign in between /v8/sessions and /v9/sessions, and recaptcha_third_party only affects account creation.

POST /v8/sessions/thirdparty

Body:

One of:

Response:

  • registered — boolean, true if a Grindr account is already linked to this provider identity.
  • profileId — string with numbers, the linked account's ID, or null when registered is false
  • thirdPartyUserInfo — ThirdPartyUserInfo, provider identity used to pre-fill account creation when registered is false, or null
  • authenticationResponse — ThirdPartyAuthenticationResponse, session payload, present when registered is true, otherwise null

Exchange Google auth code, WIP ​

NOTE

This endpoint hasn't been researched yet

First step of the Google sign-in flow. A Google server auth code, requested on-device with requestOfflineAccess against server client id 1036042917246-68g7siev1clho9lhqevahao9hlbpvssf.apps.googleusercontent.com, is sent here. The backend holds the matching web-client secret, performs the authorization-code → token exchange with Google, and returns the Google OAuth tokens. The access_token then goes to Login via third party as thirdPartyToken. Session refreshes do not repeat this step.

Google only. Facebook sign-in has no counterpart to this endpoint — the Facebook SDK yields a user access token directly on-device, and it goes straight to Login via third party with thirdPartyVendor: 1.

POST /v3/users/thirdparty/exchange

Body:

  • code — string, Google OAuth 2.0 server auth code (serverAuthCode from the Google Sign-In result).

Response:

  • access_token — string, Google OAuth access token. Used as thirdPartyToken in POST /v8/sessions/thirdparty.
  • token_type — string, OAuth token type, e.g. "Bearer".
  • id_token — Google OpenID Connect ID token (JWT).
  • expires_in — long integer, Access token lifetime in seconds.

SessionCreateRequest ​

  • email — string with email
  • password — string with password, don't specify if using authToken
  • authToken — string obtained from login+password flow or null
  • token — FCM (push service) string or null
  • geohash — geohash string or null

SessionCreateRequestV9 ​

Request body for Sign in via reCAPTCHA (POST /v9/sessions). Identical to SessionCreateRequest plus one extra field, captchaToken. The client uses this shape only when the recaptcha_first_party assignment is enabled for the device.

  • everything from SessionCreateRequest
  • captchaToken — string, reCAPTCHA Enterprise token for the FirstPartyLogin action. Non-null for the email + password sign-in; null for the authToken session-refresh flow. The token is only fetched by the device when the recaptcha_first_party assignment is enabled.

AccountBannedError ​

Error body from sign-in / session refresh when the account, device, or network is restricted. code 28 (ACCOUNT_BANNED) is often a false positive from missing/invalid security headers, not a real ban.

  • code — integer
  • message — string
  • profileId — string or null, optional
  • type — integer or null, optional
  • reason — BanReason or null, optional
  • banSubReason — BanSubReason or null, optional
  • isBanAutomated — boolean or null, optional
  • thirdPartyUserIdToShow — string or null, optional
  • mail — string or null, optional
  • phoneNumber — string or null, optional
  • dialCode — string or null, optional

BanReason ​

Account ban reason (timed-ban classification), from the JWT banDetails.reason claim. Unknown values map to Safety & Privacy on the client.

  • "Harassment"
  • "Impersonation"
  • "Underage User"
  • "Seeking Underage / Child Endangerment"
  • "Underage Impersonation / Vigilante Account"
  • "Commercial Use / Advertising / Promotion"
  • "Sexual Solicitation"
  • "Illegal Drug Related Content"
  • "Drug Selling"
  • "Hate Speech / Discrimination / Hateful Content"
  • "Serious Offline Incident"
  • "Scam / Spam"
  • "Underage - 12 and under"
  • "Safety & Privacy"
  • "TRUST_VENDOR_REJECTED"

BanSubReason ​

Account ban sub-reason, from the JWT banDetails.subReason claim and the sign-in ban error banSubReason. Unknown values map to Other on the client.

  • "UNDERAGE_ACCESS"
  • "UNDERAGE_IMPERSONATION"
  • "CHILD_ENDANGERMENT"
  • "UNWANTED_MESSAGING"
  • "PROFILE_SPAM"
  • "PHISHING_AND_FRAUD"
  • "BOT_ACTIVITY"
  • "DRUG_SALES"
  • "FREQUENT_DRUG_SOLICITATION"
  • "OCCASIONAL_DRUG_SOLICITATION"
  • "DRUG_PROMOTION"
  • "NON_ILLICIT_SUBSTANCES"
  • "PAID_SEXUAL_SERVICES"
  • "MILD_SOLICITATION"
  • "SALE_OF_SEXUAL_CONTENT"
  • "MILD_INSULTS"
  • "BULLYING"
  • "HATE_SPEECH"
  • "BLACKMAIL_AND_THREATS"
  • "PRIVACY_VIOLATIONS"
  • "IMPERSONATION"
  • "CONSENT_VIOLATIONS"
  • "OFFLINE_INCIDENTS"
  • "GENERAL_ADVERTISING"
  • "BUSINESS_SOLICITATION"
  • "TRANSACTIONAL_SERVICES"
  • "SCAMS_AND_PROMOTIONS"
  • "DRUG_SEEKING"
  • "DRUG_FACILITATION"
  • "NON_CONSENSUAL_INTIMATE_IMAGERY"
  • "SEXTORTION"
  • "BLOCK_DECISION"
  • "Other"

AuthSessionResponse ​

  • profileId — string with numbers, account's ID
  • sessionId — Session ID
  • authToken — string, Auth token for session refresh

GoogleAccessTokenRequest ​

Request body for Exchange Google auth code (POST /v3/users/thirdparty/exchange). Wraps the Google server auth code obtained on-device from native Google Sign-In.

  • code — string, Google OAuth 2.0 server auth code (serverAuthCode from the Google Sign-In result).

GoogleAccessTokenResponse ​

Response from Exchange Google auth code. Standard Google OAuth 2.0 token payload returned after the backend exchanges the server auth code. Only access_token is needed; it goes to Login via third party as thirdPartyToken.

  • access_token — string, Google OAuth access token. Used as thirdPartyToken in POST /v8/sessions/thirdparty.
  • token_type — string, OAuth token type, e.g. "Bearer".
  • id_token — Google OpenID Connect ID token (JWT).
  • expires_in — long integer, Access token lifetime in seconds.

ThirdPartyRequest ​

Request body for the initial Login via third party (POST /v8/sessions/thirdparty). For Google, thirdPartyToken is the access_token from Exchange Google auth code; for Facebook it is the user access token the Facebook SDK produced on-device. A null geohash is omitted from the body rather than sent as null.

  • thirdPartyVendor — integer, 1 — Facebook, 2 — Google
  • thirdPartyToken — string, provider access token: a Facebook user access token for vendor 1, a Google access_token for vendor 2. Sent verbatim
  • geohash — geohash string or null, optional

ThirdPartySessionRequest ​

Request body for session refresh via third party, sent to the same POST /v8/sessions/thirdparty endpoint as the initial login. Used once a thirdPartyUserId is stored (account created/logged in via Google or Facebook). The provider token is not re-sent — only the stable thirdPartyUserId plus the stored Grindr authToken.

  • thirdPartyUserId — string, Stable provider user id from the previous login (authenticationResponse.thirdPartyUserId).
  • authToken — string, Grindr authToken from the previous session (rotated on every refresh).
  • geohash — geohash string or null, optional

ThirdPartyAuthenticationResponse ​

Session payload nested in ThirdPartyAuthResponse.authenticationResponse; present when registered is true. sessionId is the same JWT as in AuthSessionResponse.

  • profileId — string, Account ID.
  • sessionId — Session ID
  • authToken — string, Auth token used for session refresh.
  • thirdPartyUserId — string, Stable provider user id; persisted and re-sent as thirdPartyUserId on refresh.
  • thirdPartyUserIdToShow — Display variant of the provider user id (e.g. email/handle).

ThirdPartyAuthResponse ​

Response from Login via third party and from third-party session refresh. When registered is true, authenticationResponse carries the session. When registered is false, no account is linked yet and sign-up continues at Create third party account using thirdPartyUserInfo.

  • registered — boolean, true if a Grindr account is already linked to this provider identity.
  • profileId — string with numbers, the linked account's ID, or null when registered is false, optional
  • thirdPartyUserInfo — ThirdPartyUserInfo, provider identity used to pre-fill account creation when registered is false, or null, optional
  • authenticationResponse — ThirdPartyAuthenticationResponse, session payload, present when registered is true, otherwise null, optional

ThirdPartyUserInfo ​

Provider identity carried by Login via third party when registered is false. Grindr's backend resolves it from the provider token server-side, which is why a bare Facebook access token is enough to reach the sign-up flow. Every field except id may come back null.

  • id — string with the provider user id, prefixed with the vendor name, e.g. facebook122104356681457161. It is this prefixed form that is echoed back as thirdPartyUserId on account creation, persisted, and re-sent on every session refresh, optional
  • name — string with the provider display name, or null. Not echoed on account creation; it pre-fills the displayName submitted on the profile update that follows sign-up, optional
  • email — string with email from the provider, or null. Pre-fills the sign-up form and is sent back as email on account creation, optional
  • age — integer, age in whole years, or null. Overwritten during sign-up by an age computed from the birthday collected there, optional
  • identity — object with provider-supplied identity details, or null. Shape unverified; the matching client model holds pronounsCategory (integer) and pronounsDisplay (string), optional
  • photoUrl — string with the provider profile picture, or null, optional
  • accessToken — string, the provider access token echoed back, or null, optional

Session ID ​

JWT obtained from authentication flow. Decoded JWT content:

Headers claims structure:

  • kid — key ID
  • alg — "RS256"
  • typ — "JWT"

Payload claims:

  • exp — number, unix timestamp in seconds defining token expiration date
  • profileId — string with numbers, account's ID
  • roles — unknown array, appears to be empty
  • features — array of strings, e.g. "HidePremiumStore", "CreateVideoCall", "VideoCallDailyFree"
  • featureFlags — array of strings, e.g. "profile-insights", "online-until-updates", "enable-account-filters-bulk-exposure", "a-list-v3", "discover-v2", "boost_purchase_fixes", "discover-studfinder-upsell"
  • experiments — object with experiment name → variant string, known keys:
    • explore-paywall-profiles — string, e.g. "test"
    • limiting_chat_credits_from_unlimited_to_5_for_xtra_users_in_explore — string, e.g. "treatment"
    • validation-aa-backend-profileid — string, e.g. "test"
    • grindr-core-day-pass — string, e.g. "control"
    • llm-age-verification-methods — string, e.g. "variant_b"
    • trans_tribe_filtering_changes — string, e.g. "Test"
    • green_dot_v2 — string, e.g. "treatment-1"
    • sponsored-profiles-cascade-selection-mode — string, e.g. "treatment"
    • read-receipt-ad-reward — string, e.g. "variant_2"
    • taps-paywall — string, e.g. "treatment-1"
    • explore-insertables-v1 — string, e.g. "treatment-insertables-below-mpu"
    • reduce_number_of_free_profiles_from_99_to_90 — string, e.g. "test"
    • cascade-mpu-explore-studfinder-2026-03-12 — string, e.g. "control"
    • reduce_number_of_results_with_age_filter_for_free_users — string, e.g. "test"
    • mpu-heuristic-algorithm-optimizations-q125 — string, e.g. "control"
    • ships_in_the_night_v3 — string, e.g. "treatment"
    • cascade-mpu-disable-poc-heuristic-2026-03-02 — string, e.g. "treatment"
    • for-you-recsys-v1_1 — string, e.g. "control"
    • cascade-mpu-studfinder-profile-limit-2026-03-12 — string, e.g. "treatment"
    • reduce_free_user_results_with_any_filter — string, e.g. "control"
    • cascade-mpu-studfinder-unlimited-2026-02-02 — string, e.g. "treatment"
    • mpu-rest-of-world — string, e.g. "treatment"
    • top_18_mpu_profiles_are_online_in_extended_cascade_and_explore — string, e.g. "control"
    • for-you-v2 — string, e.g. "Test"
    • rewarded-ads-viewed-me-v2 — string, e.g. "variant_1"
    • mega-boost-v1 — string, e.g. "mega-boost-low"
  • systemTime — unix timestamp in milliseconds
  • upsells — unknown object, appears to be empty
  • restriction — string or null; account restriction type, one of "AGE_RESTRICTED", "TIMED_BAN", "TRUST_VENDOR_REJECTED"; absent/null when unrestricted
  • restrictionReason — string or null; for AGE_RESTRICTED one of "UK_VERIFICATION_REQUIRED", "BR_VERIFICATION_REQUIRED", "AU_VERIFICATION_REQUIRED", "US_VERIFICATION_REQUIRED" (calls for an app store age signal, see Submit store age verification result), "BANNED_USER", "VERIFICATION_REQUESTED"; for TRUST_VENDOR_REJECTED a free-form reason
  • banDetails — object, present when restriction is "TIMED_BAN": expiryTime (unix seconds), reason (see BanReason), subReason (see BanSubReason), isAutomated, isAgreementCompleted, systemTime (unix ms)
  • grit — unknown UUIDv4 string

Open Grind is not affiliated with Grindr in any way.