Preface

Tinder has no public, documented developer API. Even so, for more than a decade hundreds of developers have studied how the app talks to its own servers, taken notes and written libraries. That knowledge is now scattered across dozens of GitHub repositories, old gists and issue threads, and much of it no longer holds.

This handbook brings that knowledge together. It is more than a list: every endpoint was compared against at least one piece of source code, each fact is marked with the period it belongs to, and contradictions are called out explicitly.

Who this book is for

  • Developers curious about Tinder's client–server architecture
  • Students who want to see how a reverse-engineered API evolves over time
  • People who want to run small experiments on their own account data
  • Security and privacy researchers

There is nothing here for anyone who wants to create accounts in bulk, unlock paid features for free or collect other people's data. The reasons are in Responsible use and the law.

How to read it

Your goalReading order
See the big picture1 → 2 → 10 → 11
Write a client1 → 2 → 3 → 4–9 → 12
Pick an existing library11 → Appendix A
Look up an endpoint quickly10 (reference matrix)

Markers

Throughout the book, the freshness of each fact is marked like this:

MarkerMeaning
🟢 CurrentUsed in at least one client maintained in 2025–2026
🟡 UncertainOnly seen in 2020–2024 material; it may still work
🔴 HistoricalPre-2020 and very likely broken
💳Requires a paid subscription (Plus, Gold, Platinum)

Important: None of the requests in this book were tried against a real Tinder account. The material is based on the code and documentation of open-source projects. It was last verified on 12 November 2026.

Not affiliated with Tinder

Tinder is a trademark of Match Group, LLC. This book is not approved, endorsed or supported by Match Group and has no connection to it. The name is used only to identify the system being described.

— Bahri Yilmaz

Understanding an unofficial API

What "unofficial API" means

Tinder's iOS, Android and web apps all get their data from the same server: api.gotinder.com. The contract between the apps and that server is an API, but it was never opened up to outsiders:

  • There is no documentation. Everything known comes from developers who watched the traffic and took notes.
  • There is no versioning guarantee. An endpoint can be removed or change shape without a day's notice.
  • There is no access program. There are no API keys, OAuth apps or developer portal. The only credential is a normal user session.

These three facts frame the rest of the book. Every statement here means "this was observed at a given time in a given client".

A short history

PeriodWhat happenedEffect
2014A widely shared gist published the first comprehensive endpoint list.It became the common ancestor of almost every later project.
2014–2017The first libraries appeared (pynder, tinderjs, tinder_pro). Login used a Facebook token.🔴 Most libraries from this era no longer work.
2018An updated community reference documented the /v2/... endpoints and SMS login.It became the most cited endpoint table.
2019–2020Facebook Account Kit shut down. Tinder moved to its own phone OTP flow (/v2/auth/sms/...) and added CAPTCHA (Arkose Labs).Automated login got harder. The community moved to copying the token from the browser.
2021–2023An async Python client, a Java wrapper and the first OpenAPI schema appeared.The API got its first machine-readable description.
2024–2026Login moved to protobuf over /v3/auth/login. The first MCP server was published.The app API stayed largely JSON. The login layer is what changed.

Architecture overview

┌────────────────────┐   HTTPS + JSON     ┌─────────────────────────┐
│  Client            │ ─────────────────▶ │ api.gotinder.com        │
│  (app / web /      │  X-Auth-Token      │  /v2/profile, /v2/recs, │
│   library)         │ ◀───────────────── │  /like, /v2/matches …   │
│                    │                    └─────────────────────────┘
│                    │   HTTPS + protobuf ┌─────────────────────────┐
│                    │ ─────────────────▶ │ /v3/auth/login          │
│                    │  (login flow)      │  SMS, email, CAPTCHA    │
│                    │                    └─────────────────────────┘
│                    │   WebSocket        ┌─────────────────────────┐
│                    │ ◀════════════════▶ │ keepalive.gotinder.com  │
└────────────────────┘  (new messages…)   └─────────────────────────┘

There are three separate layers:

  1. The app API (REST + JSON). Profile, recommendations, likes, matches and messages. Most of this book is about this layer.
  2. The authentication gateway (protobuf). Phone login, OTP, email verification and CAPTCHA. Covered in chapter 2.
  3. The real-time channel (WebSocket). Notifications for new messages and matches. Covered in chapter 7.

Versioning

Tinder's paths follow no consistent versioning scheme:

PrefixExampleStatus
(none)/like/{id}, /pass/{id}, /user/matches/{id}, /boost🟢 They look old but are still used by 2026 clients
/v1//v1/activity/feed🔴 Rare
/v2//v2/profile, /v2/recs/core, /v2/matches, /v2/meta🟢 The main app API
/v3//v3/auth/login🟢 Login only

Rule of thumb: if a /v2 version exists, use it. Unprefixed paths are still the primary route only for single-purpose actions such as liking, passing, sending a message and boosting.

How each fact is checked

For every endpoint this book asks three questions:

  1. Who documented it?
  2. Which client code actually calls it?
  3. When was it last seen?

An endpoint that only appears in documentation, and in no current client code, is marked 🟡 or 🔴.

Authentication

Almost every request to the app API is authenticated with a single header:

X-Auth-Token: 3f9c2a1e-xxxx-xxxx-xxxx-xxxxxxxxxxxx

The token is a UUID and gives full access to your account: profile, matches, messages and location. This chapter explains how the token is obtained and how to protect it.

Warning: Never put your token on a website, in an issue, in a screenshot or in a git repository. Anyone who has it can read and send messages as you.

How login has evolved

PeriodMethodEndpointStatus
2014–2019Exchange a Facebook access tokenPOST /auth, later POST /v2/auth/login/facebook🔴
2018–2020SMS via Facebook Account KitPOST /auth/login/accountkit🔴 Account Kit shut down
2019–2023Tinder's own SMS OTP flow (JSON)POST /v2/auth/sms/send, /v2/auth/sms/validate, /v2/auth/login/sms🟡
2024–Protobuf authentication gatewayPOST /v3/auth/login🟢
AlwaysCopy your own token from the browser–🟢 The most robust option

For personal experiments this is the simplest and least fragile route, and it never runs into a CAPTCHA. Current clients recommend it too.

  1. Sign in to tinder.com with your own account as usual.
  2. Open the developer tools (F12 or Cmd+Option+I).
  3. Go to Application → Local Storage → https://tinder.com.
  4. Find these keys:
KeyContentsUsed for
TinderWeb/APITokenThe X-Auth-Token valueEvery app API request
TinderWeb/uuidPersistent device IDThe persistent-device-id header
Refresh token (key name may vary)Refresh tokenGetting a new token when the old one expires

Put the token in an environment variable. Never hard-code it:

# .env (remember to add this file to .gitignore)
TINDER_AUTH_TOKEN=...
TINDER_DEVICE_ID=...

Method 2: The protobuf gateway (/v3/auth/login) 🟢

Since 2024 the mobile app logs in through a single endpoint that speaks Protocol Buffers instead of JSON. The only current open-source implementation is the LoginSession class in the tinder-api-ts package.

Flow

Client                                    /v3/auth/login
  │  1. PhoneRequest(phone)                      │
  │ ───────────────────────────────────────────▶ │
  │ ◀──────────────── SmsSent(masked phone, OTP length)
  │                                              │
  │  2. OtpRequest(phone, sms_code)              │
  │ ───────────────────────────────────────────▶ │
  │ ◀──── a) Success(auth_token, refresh_token, user_id)
  │ ◀──── b) EmailRequired(masked email, jwt)  ──┐
  │ ◀──── c) CaptchaRequired(Arkose)  → STOP     │
  │                                              │
  │  3. EmailCode(email_code, jwt)  ◀────────────┘
  │ ───────────────────────────────────────────▶ │
  │ ◀──────────── Success(auth_token, refresh_token, user_id)

Request details

PropertyValue (as of July 2026)
Method and pathPOST /v3/auth/login?locale=en
Content-Type / Acceptapplication/x-protobuf
Client identityplatform: ios, tinder-version, app-version, an iOS User-Agent
Session headerspersistent-device-id, app-session-id, funnel-session-id, app-session-time-elapsed
BodyAn AuthGatewayRequest protobuf message (with a phone, OTP or email-code sub-message)
ResponseAn AuthGatewayResponse message containing one of sms_sent, success, email_required or captcha

The .proto schema has never been published, so the messages are built with a small hand-written protobuf encoder. If the field numbers change, the flow can break silently. In your own code, throw an explicit error when the field you expect is missing from the response.

The three possible outcomes

ResponseMeaningWhat to do
Successauth_token and refresh_token were returnedStore both securely
EmailRequiredAn extra code was sent to the account's email; the response includes a jwtAsk the user for the email code and send it together with the jwt
CaptchaRequiredThe server found the traffic suspicious (Arkose Labs)Stop. Log in normally in the browser and switch to Method 1. Trying to solve the CAPTCHA automatically is a clear violation of the Terms of Use.

Method 3: The JSON SMS flow (/v2/auth/...) 🟡

The older flow, documented in 2019–2023 material. Don't use it in new projects, but it helps when you read older code:

StepRequestBodyTaken from the response
1POST /v2/auth/sms/send?auth_type=sms{"phone_number": "1555…"}–
2POST /v2/auth/sms/validate?auth_type=sms{"otp_code": "123456", "phone_number": "1555…"}refresh_token
3POST /v2/auth/login/sms{"refresh_token": "…"}api_token (= X-Auth-Token)

Token lifecycle

  • An X-Auth-Token is valid for a limited time. When it expires, requests return 401 Unauthorized.
  • Getting a new token with a refresh token also rotates the refresh token. Store the new pair every time and never reuse the old refresh token.
  • Logging out on the web (POST /v2/auth/logout) invalidates the token.
         ┌─────────── 401 ───────────┐
         ▼                           │
 [refresh_token] ──▶ new pair ──▶ [auth_token] ──▶ requests
         ▲                │
         └── store ◀──────┘

Secure storage checklist

  • The token lives in an environment variable or the OS keychain (macOS Keychain, Windows Credential Manager)
  • The .env file is in .gitignore
  • Tokens are masked in logs (3f9c…xxxx)
  • Request headers are redacted in error reports
  • The token is only ever sent to https://api.gotinder.com

Anatomy of a request

The basics

Base URLhttps://api.gotinder.com
TransportHTTPS only
Body formatJSON (except login, which uses protobuf)
CredentialThe X-Auth-Token header
LanguageThe ?locale=en query parameter

Example request

GET /v2/recs/core?locale=en&duos=0 HTTP/1.1
Host: api.gotinder.com
X-Auth-Token: 3f9c2a1e-…
Content-Type: application/json
Accept: application/json
platform: web
tinder-version: <Tinder Web version>
persistent-device-id: 7b1d…

Take the version values from your own browser's Network tab. For example, in 2023 a web client sent tinder-version: 2.54.0, which is long outdated.

Headers

HeaderRequired?Notes
X-Auth-Token✅The session token. Every current client sends it.
Content-Type: application/jsonMostlyOlder material says it should not be sent with the legacy GET requests to /like/{id} and /pass/{id}. Current clients send it on every request.
platformRecommendedweb, ios or android. Real clients always send it.
tinder-version, app-versionRecommendedThe app version. A very old version may be rejected or flagged.
persistent-device-idRecommendedThe device ID. On the web it is the TinderWeb/uuid value.
User-AgentRecommendedShould match the platform.
x-supported-image-formatsOptionalwebp,jpeg. Affects the format of photo URLs.

Why it matters: Requests with missing or inconsistent headers can be flagged as automation. That can lead to a CAPTCHA, a 403 or a ban. Define your headers in one place and keep them close to what the browser actually sends.

Query parameters

ParameterWhereNotes
localeAlmost everywhereThe language of text in the response (en, tr, es-ES)
includeGET /v2/profileWhich profile sections to return (see chapter 4)
count/v2/matches, /v2/matches/{id}/messagesPage size. Up to 100 for matches.
message/v2/matches1: matches you have messaged, 0: new matches
s_number/like, /passThe s_number from the recommendation card (see chapter 6)

Pagination: None of the clients examined use a page token. They all fetch a single list capped by count. For longer lists, check the extra fields in the response's data object in your browser's Network tab.

Response envelope

/v2/... endpoints usually share the same envelope:

{
  "meta": { "status": 200 },
  "data": { "...": "..." }
}

Unprefixed legacy endpoints (such as /like/{id}) return a flat object with no envelope:

{ "match": false, "likes_remaining": 99, "status": 200 }

Tip: Normalize both shapes in your client with a single unwrap() function: return data if present, otherwise the body itself.

HTTP status codes

CodeMeaningRecommended behavior
200Success–
400Missing or invalid parameterFix the request; don't retry
401Token invalid or expiredRefresh it; if that fails, log in again
403Not allowed: premium feature, suspicious traffic or CAPTCHAStop. If it is a premium error, tell the user. Otherwise turn the automation off.
404Endpoint or resource not foundThe API may have changed; compare with a current client
429Rate limitedExponential backoff (2 s, 4 s, 8 s…) with a capped number of retries
5xxServer errorAt most 1–2 retries after a short wait

A good model is to split errors into types:

Error typeWhen it happens
HttpErrorGeneric HTTP error carrying status and body
PremiumRequiredErrorA 403 on a preference update: a subscription is required
NoBoostAvailableErrorNo Boost left
CaptchaRequiredErrorLogin asked for a CAPTCHA
EmailRequiredErrorLogin asked for email verification

Pace and politeness

Tinder does not publish its rate limits. Safe assumptions based on community experience:

  • Wait at least 1–3 seconds between requests, with a little random jitter.
  • Send one request at a time.
  • Cache responses such as GET /v2/profile and GET /v2/meta. They rarely change.
  • Free accounts have a daily like quota. Track what is left through the likes_remaining field in every /like response.
  • When the recommendation pool runs out (an empty results), Tinder may apply a temporary "soft block" that reportedly lasts about 30 minutes.

Profile and account

Reading your own profile 🟢

GET /v2/profile?locale=en&include=user,account,likes,boost,super_likes

The include parameter decides which sections the response contains. Without it the response is very thin. Known scopes:

ScopeContents
userName, bio, photos, birth date, gender, discovery preferences
accountPhone, email, account status
likesRemaining likes and when they reset
super_likesRemaining Super Likes
boostBoost allowance and any active boost
travelPassport (location change) status 💳
plus_controlPaid privacy settings (hide age and distance) 💳
readreceiptsRead receipt allowance 💳
spotify, instagramConnected accounts
notifications, email_settingsNotification preferences
available_descriptorsDescriptors you can add to a profile (star sign, education, …)
profile_meterProfile completeness meter
feature_access, paywalls, offerings, purchase, misc_merchandisingSubscription and sales screens
onboarding, tutorialsFirst-run steps
tinder_uTinder U (university) status
contact_cards, bouncerbypass, all_in_genderUndocumented internal scopes

Tip: Only request the scopes you need. The response is smaller and you avoid processing data you don't use. user,likes,boost covers most cases.

Updating your profile and discovery settings 🟢

Updates are split across two endpoints with two different body shapes:

EndpointBodyWhat it updates
POST /v2/profile{"user": {…}}Your profile and basic discovery settings: age range, distance, gender filter, bio, visibility, auto-expansion, Global mode
POST /v2/profile/user{"preference_filters": {…}}Advanced filters that narrow recommendations: interests, descriptors (star sign, education…), "has a bio", number of photos 💳

Example: basic discovery settings

{
  "user": {
    "age_filter_min": 25,
    "age_filter_max": 35,
    "distance_filter": 30,
    "gender_filter": 1,
    "bio": "Coffee, code and long walks.",
    "discoverable": true
  }
}
FieldValues
age_filter_min, age_filter_maxAges 18 and up
distance_filterIn miles. The API stores miles even when the app shows kilometres (36 mi ≈ 58 km). Divide km by 1.609344 and round. The upper limit is roughly 100 miles (≈160 km).
gender_filter0 men, 1 women, -1 everyone
bioProfile text
discoverableIf false, you don't appear in anyone's deck
Auto-expansionWiden age and distance when recommendations run low
Global modeRecommendations from around the world, plus the languages you want to match in

💳 Premium limit: Preference updates (especially preference_filters) return 403 on a free account. Treat that 403 as "not allowed" and don't retry.

Legacy /profile options 🔴

In 2018-era material, POST /profile could also do the following:

BodyEffect
{"hide_age": true}Hide your age 💳
{"hide_distance": true}Hide your distance 💳
{"hide_ads": true}Hide ads 💳
{"blend": "recency"} / {"blend": "optimal"}Recommendation ordering mode 💳
{"discoverable_party": "liked"}Only show yourself to people you liked 💳

These are probably managed today under the plus_control scope via /v2/profile. This has not been confirmed in any current client.

Other profile endpoints

RequestPurposeStatus
PUT /profile/job · DELETE /profile/jobAdd or remove your job🔴
PUT /profile/school · DELETE /profile/schoolAdd or remove your school🔴
POST /v2/profile/job · POST /v2/profile/schoolv2 equivalents🟡
GET /v2/profile/spotify · PUT /v2/profile/spotify/themeSpotify settings and profile anthem🔴
PUT /profile/username · DELETE /profile/usernameWeb profile username🔴
GET /profile/photos · DELETE /mediaList photos, delete a photo🟡
POST /v2/profile/email_settingsEmail notification settings🟡
POST /v2/account/account-email/confirmConfirm your email🟡
POST /v2/account/recovery/sendAccount recovery email🟡
DELETE /v2/profilePermanently deletes the account🟡 ⚠️

⚠️ DELETE /v2/profile: This request deletes the account. Don't define this route in your client at all, or put it behind an explicit confirmation step. Deleting and re-creating an account repeatedly is reported to get the phone number permanently banned.

Another user's profile 🟡

GET /user/{user_id}?locale=en

The response is a results object similar to a recommendation card: name, age, photos, bio, distance. This data belongs to someone else. Only use it to display what you could already see in the app. Don't store or collect it (see chapter 13).

Discovery and recommendations

The deck of cards you swipe through comes from a single endpoint.

The recommendation deck 🟢

GET /v2/recs/core?locale=en&duos=0
ParameterNotes
localeResponse language
duos0 normal recommendations, 1 "Double Date / Duos" mode

Response

{
  "meta": { "status": 200 },
  "data": {
    "results": [
      {
        "type": "user",
        "user": {
          "_id": "5f1c…",
          "name": "…",
          "birth_date": "…",
          "bio": "…",
          "photos": [ { "id": "…", "url": "…", "processedFiles": [ … ] } ],
          "gender": 1,
          "badges": [ … ]
        },
        "distance_mi": 4,
        "s_number": 123456789,
        "content_hash": "…",
        "teasers": [ … ],
        "experiment_info": { "user_interests": { "selected_interests": [ … ] } },
        "spotify": { … },
        "facebook": { "common_connections": [], "common_interests": [] }
      }
    ]
  }
}

Key fields

FieldWhy it matters
user._idThe ID used in like and pass requests
s_numberA per-session card number. It is sent back in like and pass requests (see chapter 6).
content_hashA hash of the card content. Some older clients added it to likes.
user.photos[].idSent as the liked content (liked_content_id) in current clients
distance_miApproximate distance in miles. Rounded, not an exact location.
teasersShort facts shown on the card (job, school, interest)
experiment_info.user_interestsInterests, with an is_common flag for shared ones
typeUsually user. Ads or special cards can use other types.

An empty deck

If results comes back empty:

  1. Your distance and age range may be too narrow.
  2. You may have seen everyone nearby.
  3. You may have sent too many requests in a short time, triggering a temporary soft block. Wait; don't repeat the request.

The legacy recommendations endpoint 🔴

GET /user/recs

Used by early clients. Historical since /v2/recs/core arrived. Don't use it in new code.

Explore 🟡

GET /v2/explore?locale=en

Returns the themed cards from the app's "Explore" tab (for example "New friends" or "Long-term partner"). The response schema is undocumented.

Filtered search: the temporary preference pattern

One current client implements filtered search with an interesting pattern:

1. Read current preferences     GET  /v2/profile?include=user
2. Apply filters temporarily    POST /v2/profile, /v2/profile/user
3. Fetch the deck               GET  /v2/recs/core
4. Restore old preferences      POST /v2/profile, /v2/profile/user
   (even if step 2 or 3 fails)

The lesson: wrap every state-changing operation in try/finally. If your code stops halfway, your account shouldn't be left with unexpected settings.

Interactions: like, pass, Super Like, Boost

Methods differ across eras

The HTTP method for these actions has changed over time, which explains why older code breaks:

ActionPath2018 docs2021 schema2023 client2026 client
Like/like/{user_id}GETGETPOSTPOST + body
Pass/pass/{user_id}GETGETPOSTGET + s_number
Super Like/like/{user_id}/superPOSTPOST–POST + body
Boost/boost–POST–POST

Follow the most recent column. The others are there for historical context.

Like 🟢

POST /like/{user_id}?locale=en
Content-Type: application/json

{
  "s_number": 123456789,
  "liked_content_id": "<photo id>",
  "liked_content_type": "photo"
}
Body fieldWhere it comes from
s_numberThe recommendation card's s_number
liked_content_idThe id of the photo you liked (usually the first)
liked_content_typeCurrently only photo

Response:

{ "match": false, "likes_remaining": 87, "status": 200 }
FieldMeaning
matchIf true, the like was mutual and a new match was created
likes_remainingLikes left today. Stop at 0.
X-PaddingMeaningless padding; ignore it

Current clients type match as a boolean. Older clients suggest it could be an object when a match happens. Check it as a truthy value: if (res.match).

Pass (swipe left) 🟢

GET /pass/{user_id}?locale=en&s_number=123456789

No body. s_number goes in the query string.

Super Like 🟢 💳

POST /like/{user_id}/super?locale=en

Same body as a like. Free accounts get very few. Check what is left with GET /v2/profile?include=super_likes.

Boost 🟢 💳

POST /boost?locale=en

The body is empty ({}). Boost puts your profile in front for a limited time. Key response fields:

FieldMeaning
durationBoost duration (unit not documented)
expires_atEnd time (Unix ms)
remaining, allotment_remaining, purchased_remainingBoosts left
multiplierVisibility multiplier
resets_atWhen the allowance resets
boost_type, is_super_boostType of boost

With no Boost left, the server returns an error. Model it as its own error type.

Liking a message 🔴

POST /message/{message_id}/like

Appears in early clients. The current equivalent is unconfirmed.

Why auto-swiping is a bad idea

Calling /like in a loop is technically easy. But:

  • Tinder's Terms of Use prohibit automated interaction. Your account and phone number can be banned permanently.
  • Tinder has repeatedly told users not to like everyone. Community experience suggests indiscriminate liking doesn't improve visibility.
  • The people on the other side are real. Indiscriminate likes make their experience worse too.

Use the endpoints in this chapter for single, deliberate actions, such as clicking a card in your own interface.

Matches, messages and the real-time channel

The match list 🟢

GET /v2/matches?locale=en&count=60&message=1
ParameterNotes
countHow many matches to return, between 1 and 100
message1: matches you have messaged, 0: new matches with no messages yet
is_tinder_uTinder U matches (usually false)

The app's "New matches" strip and its "Messages" list are separate requests. To combine them, call once with message=0 and once with message=1.

The match object (selected fields)

FieldMeaning
_id / idThe match ID, used as match_id in message endpoints. Usually the two user IDs concatenated.
personA short profile of the other person (_id, name, photos, bio)
created_dateWhen the match happened
last_activity_dateLast message or activity
message_count, messages[]Message count and the latest messages
participantsParticipant user IDs
seenWhether the match has been seen
closed, deadThe match was closed or has ended
pendingA match that isn't complete yet
is_super_like, is_boost_match, is_fast_match, is_super_boost_match, is_primetime_boost_matchHow the match came about
is_opener, has_shown_initial_interestWho wrote first
readreceiptRead receipts 💳
liked_contentWhat of theirs you liked
common_friend_count, common_like_countShared connections

Messages in a match 🟢

GET /v2/matches/{match_id}/messages?locale=en&count=100

Returns a data.messages[] array:

{
  "_id": "6a3f…",
  "match_id": "5f1c…5f2d…",
  "from": "5f1c…",
  "to": "5f2d…",
  "message": "Hi!",
  "sent_date": "2026-11-12T09:41:00.000Z",
  "created_date": "2026-11-12T09:41:00.000Z",
  "timestamp": 1794476460000,
  "is_liked": false
}

Sending a message 🟢

POST /user/matches/{match_id}?locale=en
Content-Type: application/json

{ "message": "Hi! Where was the coffee photo on your profile taken?" }

The response is the created message object (_id, from, to, match_id, sent_date, message).

🐛 A bug found in the wild: One popular async Python client builds the body as {"message", message}. In Python that is a set, not a dictionary. The correct form is {"message": message}. If a Python client can't send messages, check for this.

Unmatching 🟢

DELETE /user/matches/{match_id}

Deletes the match and the conversation irreversibly. Put it behind a confirmation step in your client.

Single lookups 🟡

RequestPurpose
GET /matches/{match_id}A single match
GET /message/{message_id}A single message
POST /updates with {"last_activity_date": "<ISO date>"}Everything that changed since a date 🔴 (the old method, before WebSocket)

The real-time channel (WebSocket)

Instead of polling, Tinder Web receives new messages over a WebSocket:

1. GET /ws/generate?locale=en   → { "token": "…" }   (with X-Auth-Token)
2. WebSocket: wss://keepalive.gotinder.com/ws?token=<token>
3. The server pushes an event   → fetch the data from the matching endpoint
StepDetails
TokenGET /ws/generate is called with web headers (platform: web)
Connectionkeepalive.gotinder.com, using a short-lived token
Keep-aliveClients send a small binary ping frame every 30 seconds
EventsBinary messages with an undocumented schema

Design tip: Don't try to decode event payloads. Treat an event as a "something changed" signal and call GET /v2/matches. That is more robust and survives schema changes.

Location, meta and Passport

Updating your location

The location endpoint is one of the areas that has changed most:

PeriodRequestBodyStatus
2014–2020POST /user/ping{"lat": 40.71, "lon": -74.00}🔴
2021–POST /v2/meta{"lat": 40.71, "lon": -74.00}🟢

Current clients call POST /v2/meta?locale=en, so updating your location and fetching your account meta happen in the same request.

Location is sensitive. Changing it often and inconsistently (different cities within minutes) can be flagged as suspicious. It has been reported that this can be used as a free "location change". That would bypass the paid Passport feature, which this book's rules exclude.

The meta response 🟢

The POST /v2/meta response is a broad summary of your account state:

SectionContents
readreceiptsWhether read receipts are on
profileEditing limits: can_edit_jobs, can_edit_email, school_name_max_length, job_title_max_length…
super_likeWhether Super Like is enabled, alc_mode
location_prechecksLocation pre-check codes
age_verificationAge verification status
biometric_consentConsent for biometric data (e.g. photo verification)
voter_registrationUS-only voter registration prompt

The legacy GET /meta endpoint (🔴) returned things like remaining swipes and the number of people seen. GET /v2/meta (🟡) added extras such as "Top Picks".

Passport (travel mode) 💳 🔴

RequestBodyPurpose
POST /passport/user/travel{"lat": …, "lon": …}Move your swiping location to another city
POST /passport/user/reset{}Return to your real location

Requires a paid subscription. Documented in 2018.

GET /location/popular

Returns the list of popular cities shown on the Passport screen.

Other endpoints

This chapter collects endpoints that sit outside the main flow but have been observed in clients and documentation.

"Likes You" 💳

RequestPurposeStatus
POST /v2/fast-match/count · GET /v2/fast-match/countHow many people liked you🟡 (method varies)
GET /v2/fast-match/teaser?type=recently-activeA single blurred preview🟡
GET /v2/fast-match/teasersA list of previews🟡
GET /v2/fast-match/previewPreview thumbnail🔴
GET /v2/my-likesSummary of your likes🟡

Limit: On a free account these endpoints only return blurred previews. In the past some projects exposed unblurred images through API flaws. Tinder closed those flaws, and such tools are deliberately left out of this book (chapter 13).

GIFs in chat 🔴

RequestPurpose
GET /giphy/trending?limit={n}Trending GIFs
GET /giphy/search?limit={n}&query={q}GIF search

Tinder uses Giphy for GIFs in chat and proxies these requests through its own server.

Connected accounts

RequestPurposeStatus
GET /instagram/authorizeStart an Instagram connection🔴
POST /instagram/authenticate · POST /instagram/deauthorizeConnect / disconnect Instagram🟡
GET /v2/profile/spotify · PUT /v2/profile/spotify/themeSpotify settings and profile anthem🔴

Notifications

RequestPurposeStatus
PUT /v2/push/notificationsNotification preferences (exact behavior undocumented)🟡
POST /v2/push/devices/web/{device_code} · DELETE …Web push device registration🟡

Reporting

RequestBodyStatus
POST /report/{user_id}{"cause": <number>, "text": "<explanation>"}🔴
POST /report/user/{user_id}Similar🟡

Report causes are a small set of numbers. To report a real problem, the app's own report screen is always the better choice. Automated reporting is very easy to abuse.

Sign-up (onboarding)

The sign-up steps (/v2/onboarding/fields, /v2/onboarding/photo, /v2/onboarding/complete, /v2/onboarding/user) have been documented. This book deliberately doesn't go into them. Creating accounts programmatically is almost only ever used for fake or bulk accounts. Create your account in the app.

Everything else

RequestPurposeStatus
GET /user/{user_id}/common_connectionsShared connections🔴
POST /user/{user_id}/shareProfile share link🔴
GET /group/friendsFacebook friends (the old "Social" feature)🔴
GET /v1/activity/feed?direction=past&eventTypes=1023Activity feed🔴
POST /v2/incentives/eligibilityPromo code eligibility🟡
POST /v2/auth/verify-captchaCAPTCHA verification (don't use it; see chapter 2)🟡
POST /v2/auth/logoutLog out🟡

Endpoint reference matrix

Every endpoint in this book in one place, with its status and the chapter that covers it.

MarkerMeaning
🟢Used by a client maintained in 2025–2026
🟡Only seen in 2020–2024 material
🔴Pre-2020, very likely broken
💳Paid subscription
⚠️Irreversible

Authentication

MethodPathStatusChapter
POST/v3/auth/login (protobuf)🟢2
POST/v2/auth/sms/send🟡2
POST/v2/auth/sms/validate🟡2
POST/v2/auth/login/sms🟡2
POST/v2/auth/login/facebook🔴2
POST/auth (Facebook)🔴2
POST/v2/auth/verify-captcha🟡9
POST/v2/auth/logout🟡9

Profile and account

MethodPathStatusChapter
GET/v2/profile?include=…🟢4
POST/v2/profile🟢4
POST/v2/profile/user🟢 💳4
DELETE/v2/profile ⚠️🟡4
GET / POST/profile🔴4
PUT / DELETE/profile/job, /profile/school🔴4
POST/v2/profile/job, /v2/profile/school🟡4
PUT / DELETE/profile/username🔴4
GET/profile/photos🟡4
DELETE/media🟡4
POST/v2/profile/email_settings🟡4
POST/v2/account/account-email/confirm🟡4
POST/v2/account/recovery/send🟡4
GET/user/{id}🟡4

Discovery and interactions

MethodPathStatusChapter
GET/v2/recs/core🟢5
GET/user/recs🔴5
GET/v2/explore🟡5
POST (current) · GET (legacy)/like/{id}🟢6
GET (current) · POST (2023 client)/pass/{id}🟢6
POST/like/{id}/super🟢 💳6
POST/boost🟢 💳6
POST/message/{id}/like🔴6

Matches and messages

MethodPathStatusChapter
GET/v2/matches🟢7
GET/v2/matches/{id}/messages🟢7
POST/user/matches/{id} (send)🟢7
DELETE/user/matches/{id} (unmatch)🟢7
GET/matches/{id}🟡7
GET/message/{id}🟡7
POST / GET/updates🔴7
GET/ws/generate + wss://keepalive.gotinder.com/ws🟡7

Location and meta

MethodPathStatusChapter
POST/v2/meta (location + meta)🟢8
GET/v2/meta, /meta🔴8
POST/user/ping🔴8
POST/passport/user/travel, /passport/user/reset🔴 💳8
GET/location/popular🟡8

Other

MethodPathStatusChapter
POST / GET/v2/fast-match/count🟡 💳9
GET/v2/fast-match/teaser(s)🟡9
GET/v2/fast-match/preview🔴9
GET/v2/my-likes🟡9
PUT/v2/push/notifications🟡9
POST / DELETE/v2/push/devices/web/{code}🟡9
GET/giphy/trending, /giphy/search🔴9
GET / POST/instagram/…🔴9
GET / PUT/v2/profile/spotify…🔴9
POST/report/{id}, /report/user/{id}🔴9
GET/user/{id}/common_connections🔴9
POST/user/{id}/share🔴9
GET/group/friends🔴9
GET/v1/activity/feed🔴9
POST/v2/incentives/eligibility🟡9
–/v2/onboarding/… (sign-up)Out of scope9

Library reviews

This chapter reviews the seven most important projects in depth. The reviews are based on source code, not READMEs. None were run against the live API. The full list of projects is in Appendix A.

At a glance

tinder-api-tstinder.pypyndertinder-mcp-serverOpenAPI schema2018 referenceTinderSharp
LanguageTypeScriptPythonPythonTypeScriptOpenAPIMarkdown + PythonC#
Last push2026-072023-112021-062025-052025-032023-012022-12
Current login flow✅ /v3➖ token only❌ Facebook❌ /v2➖ /v2❌❌
Types / models✅✅✅✅ Zod✅ schema❌✅
Error types✅✅➖✅––➖
Rate limiting / retries❌➖ 3 tries on 5xx❌✅––❌
Tests✅✅❌✅––❌
LicenseMITGPL-3.0CustomUnclearNoneMITUnlicense
GradeABC (historical)CB (reference)B (historical)C

1. tinder-api-ts

What: A TypeScript client for Node.js and Deno, published on npm as tinder-api-ts and on JSR.

Why it matters: Of all the projects examined, it is the only open code that implements the post-2024 protobuf login flow.

FeatureStatus
SMS login + email verification (LoginSession)✅ /v3/auth/login
CAPTCHA detection✅ Throws TinderCaptchaRequiredError; never tries to solve it
Reading the profile (with scopes)✅
Preference updates (both endpoints)✅ With km/mile conversion
Recommendations, like, pass, Super Like✅
Matches, reading and sending messages✅
Location (/v2/meta), Boost✅
Proxy support✅ Deno HttpClient / undici ProxyAgent
Rate limiting, retries❌ Left to the caller

Code quality: All endpoints live in a single TINDER_ROUTER constant. There is one request() function, and HTTP errors are split into types. Filtered search uses a try/finally-style pattern to restore preferences. The protobuf encoder is small and hand-written (85 lines), so a schema change could break it.

Watch out: The default locale is es-ES. For English responses, pass locale: "en" on each call or set baseOptions.defaultLocale.

import { TinderAPI } from "tinder-api-ts";

const tinder = new TinderAPI({
  xAuthToken: process.env.TINDER_AUTH_TOKEN!,
  baseOptions: { defaultLocale: "en" },
});

const { data } = await tinder.getMatches({ count: 20, message: 1 });
console.log(data.data.matches.length, "matches");

Verdict: The best starting point for a new JS/TS project. It is MIT licensed, so you can read it and adapt it into your own client.


2. tinder.py

What: An asyncio Python client inspired by discord.py, published on PyPI as tinder.py, with documentation on Read the Docs.

FeatureStatus
LoginWith a token copied from the browser
Recommendations (/v2/recs/core), like, pass✅
Matches, unmatch✅
Sending messages⚠️ Broken (see below)
Like previews (fast-match), my-likes, explore✅
WebSocket➖ Connects and pings; doesn't decode events
Location (/v2/meta)✅
Rate limiting➖ No 429 handling; 3 tries with growing waits on 500/502 only. 403 and 404 have their own error types.

The bug: send_message in http.py:

payload = {"message", message}   # ❌ This is a set, not a dict
payload = {"message": message}   # ✅ Correct

The source also has a TODO comment listing unsupported endpoints, such as /v2/matches/{id}/messages and /like/{id}/super.

Verdict: Good async architecture and models, but it hasn't been updated since 2023. Because it is GPL-3.0, copying its code would make your project GPL too. Borrow ideas from its design rather than its code.


3. pynder

What: The best-known Python client (600+ stars).

Status: Login depends on a Facebook token (POST /auth). Most endpoints are unprefixed legacy paths (/user/recs, /updates, /user/ping). It has not been updated since 2021. Don't expect it to work today.

Value: Its issue tracker is the richest record of API changes from 2015 to 2021. It is the first place to look when you want to know when and how an endpoint changed.

Verdict: Historical reference. Don't use it in a new project.


4. tinder-mcp-server

What: According to its README, a Model Context Protocol (MCP) server for the Tinder API.

What the code shows:

ClaimReality
"MCP server"It does not use the official MCP SDK (@modelcontextprotocol/sdk). It is an Express HTTP server with /mcp/... routes. An MCP client such as Claude can't connect to it directly without a bridge.
AuthenticationThe old /v2/auth/sms/... and /v2/auth/login/facebook flows. No /v3 flow.
CAPTCHACalls /v2/auth/verify-captcha, the opposite of the "stop" approach this book recommends.
Licensepackage.json says MIT, but the repository has no LICENSE file
SetupThe clone URL in the README still contains a yourusername placeholder
QualityZod schemas, a rate limiter, a cache and an extensive test suite

Verdict: Worth reading for its architecture (schema validation, caching, rate limiting). Not a product you can use as is. For a real MCP integration, write a small server on the official SDK that only exposes read-only tools (chapter 12).


5. The OpenAPI schema

What: tinder-api-swagger.yaml, the only OpenAPI schema written for the Tinder API. It can also be browsed as a web page.

Strengths: 39 path and method combinations, including endpoints found nowhere else (account management, push, Instagram), plus practical notes: OTP behavior, refresh token rotation, soft-block duration and the penalty for repeated account deletion.

Weaknesses: Many endpoints have summary: TODO. Login is stuck in the /v2 era. There is no license file.

Tip: You can feed the schema to an OpenAPI generator (for example openapi-generator or NSwag) to scaffold a typed client. Compare the result with a current client, though: the like and pass methods differ.


6. The 2018 community reference

What: The most-starred (860+) endpoint document, from 2018–2019, with a small Python wrapper.

Value: It documents things no other material covers: paid features (hide_age, blend, Passport), Spotify, job and school, and usernames. Much of the community's shared vocabulary comes from it.

Watch out: The content stops in 2018–2019. Its GET /like/{id} is now POST, and /user/ping is now /v2/meta.


7. TinderSharp

What: A C# wrapper for .NET Standard 2.0 (Unlicense, i.e. public domain).

Status: Not updated since 2022. Its login models belong to the Facebook and /v2 SMS era (FacebookAuthentication, SMSAuthentication). There is no test project, only a SandBox console app. It includes a custom JSON converter for match responses (MatchResponseConverter).

Value: The only serious example in the .NET ecosystem. Because it is Unlicense, you can freely take its model classes into a modern .NET 8+ client built on System.Text.Json and HttpClient. Chapter 12 gives you a skeleton for exactly that.


Choosing

Language?
├── TypeScript / Node / Deno ──▶ tinder-api-ts
├── Python ──────────────────▶ tinder.py (patch the message bug)
│                              or generate a client from the OpenAPI schema
├── C# / .NET ───────────────▶ Your own client (chapter 12)
│                              + TinderSharp models
└── Just understanding it ───▶ This book

Designing your own client

Existing libraries may be outdated or not quite fit your needs. This chapter gives you the principles for writing a small, safe and maintainable client for your own account, plus starter skeletons in two languages.

Design principles

PrincipleWhyHow
Read firstReads don't change the account, so the risk is lowStart the client with readOnly: true. Write methods must be enabled explicitly.
One entry pointHeaders, errors and rate limits are handled in one placeRoute every request through a single request() function
Slow and serialAvoids automation detection and rate limits1.5–3 s between requests plus random jitter, no concurrent requests
Respect stop signalsA 403 or CAPTCHA means elevated riskTrip a circuit breaker on these errors and require a human to reset it
Don't trust the schemaThe API changes without noticeValidate responses at runtime (Zod, System.Text.Json + null checks)
Protect secretsThe token is the key to your accountEnvironment variable or keychain; mask it in logs
Lock irreversible actionsDeleting the account or unmatching can't be undoneDon't define these routes, or require explicit confirmation

Architecture

┌──────────────┐    ┌──────────────┐    ┌───────────────┐    ┌───────────┐
│ Your code    │──▶ │ TinderClient │──▶ │ RequestGate   │──▶ │  fetch /  │
│ (UI, CLI…)   │    │ (typed API)  │    │ • rate limit  │    │ HttpClient│
└──────────────┘    └──────────────┘    │ • breaker     │    └───────────┘
                           │            │ • retries     │
                           ▼            └───────────────┘
                    ┌──────────────┐
                    │ Validate     │
                    └──────────────┘

TypeScript skeleton

Node.js 20+ or Deno. The only external dependency is the optional zod.

// tinder-client.ts
const BASE = "https://api.gotinder.com";

export class TinderBlockedError extends Error {}
export class TinderAuthError extends Error {}

type Options = {
  token: string;
  locale?: string;
  readOnly?: boolean;
  minDelayMs?: number;
};

export class TinderClient {
  private last = 0;
  private blocked = false;
  private queue: Promise<unknown> = Promise.resolve();

  constructor(private readonly opts: Options) {
    if (!opts.token) throw new Error("TINDER_AUTH_TOKEN is missing");
  }

  // --- Reads ---
  profile(include = ["user", "likes", "boost"]) {
    return this.request("GET", "/v2/profile", { include: include.join(",") });
  }
  recommendations() {
    return this.request("GET", "/v2/recs/core", { duos: "0" });
  }
  matches(count = 60, withMessages = true) {
    return this.request("GET", "/v2/matches", {
      count: String(count),
      message: withMessages ? "1" : "0",
    });
  }
  messages(matchId: string, count = 100) {
    return this.request("GET", `/v2/matches/${encodeURIComponent(matchId)}/messages`, {
      count: String(count),
    });
  }

  // --- Writes (require readOnly: false) ---
  sendMessage(matchId: string, message: string) {
    this.assertWritable();
    return this.request("POST", `/user/matches/${encodeURIComponent(matchId)}`, {}, { message });
  }

  // --- Plumbing ---
  private assertWritable() {
    if (this.opts.readOnly !== false) {
      throw new Error("The client is read-only. Pass readOnly: false to enable writes.");
    }
  }

  private request(
    method: "GET" | "POST" | "DELETE",
    path: string,
    query: Record<string, string> = {},
    body?: unknown,
  ): Promise<unknown> {
    // Queue requests: one at a time.
    const run = async () => {
      if (this.blocked) throw new TinderBlockedError("Circuit open: a previous 403/CAPTCHA");
      await this.pace();

      const url = new URL(path, BASE);
      url.search = new URLSearchParams({ locale: this.opts.locale ?? "en", ...query }).toString();

      const res = await fetch(url, {
        method,
        headers: {
          "X-Auth-Token": this.opts.token,
          "Content-Type": "application/json",
          Accept: "application/json",
          platform: "web",
        },
        body: body === undefined ? undefined : JSON.stringify(body),
      });

      if (res.status === 401) throw new TinderAuthError("Token invalid or expired");
      if (res.status === 403 || res.status === 429) {
        this.blocked = true; // Stop until a human looks.
        throw new TinderBlockedError(`HTTP ${res.status}: automation stopped`);
      }
      if (!res.ok) throw new Error(`HTTP ${res.status}: ${await res.text()}`);

      const json = (await res.json()) as { data?: unknown };
      return json.data ?? json; // v2 envelope or flat object
    };
    const next = this.queue.then(run, run);
    this.queue = next.catch(() => undefined);
    return next;
  }

  private async pace() {
    const min = this.opts.minDelayMs ?? 2000;
    const jitter = Math.random() * 1000;
    const wait = this.last + min + jitter - Date.now();
    if (wait > 0) await new Promise((r) => setTimeout(r, wait));
    this.last = Date.now();
  }
}

Usage:

const client = new TinderClient({ token: process.env.TINDER_AUTH_TOKEN! });
const matches = await client.matches(20);

Note: The circuit also trips on 429. That is a deliberately cautious choice: for personal use, hitting the rate limit means you are already going too fast.

C# / .NET 8 skeleton

// TinderClient.cs
using System.Net;
using System.Net.Http.Json;
using System.Text.Json;

public sealed class TinderBlockedException(string message) : Exception(message);

public sealed class TinderClient : IDisposable
{
    private readonly HttpClient _http;
    private readonly SemaphoreSlim _gate = new(1, 1);
    private readonly TimeSpan _minDelay;
    private DateTimeOffset _last = DateTimeOffset.MinValue;
    private bool _blocked;
    private readonly string _locale;

    public bool ReadOnly { get; init; } = true;

    public TinderClient(string token, string locale = "en", TimeSpan? minDelay = null)
    {
        ArgumentException.ThrowIfNullOrWhiteSpace(token);
        _locale = locale;
        _minDelay = minDelay ?? TimeSpan.FromSeconds(2);
        _http = new HttpClient { BaseAddress = new Uri("https://api.gotinder.com") };
        _http.DefaultRequestHeaders.Add("X-Auth-Token", token);
        _http.DefaultRequestHeaders.Add("platform", "web");
    }

    public Task<JsonElement> GetMatchesAsync(int count = 60, bool withMessages = true, CancellationToken ct = default) =>
        SendAsync(HttpMethod.Get, $"/v2/matches?count={count}&message={(withMessages ? 1 : 0)}", null, ct);

    public Task<JsonElement> GetMessagesAsync(string matchId, int count = 100, CancellationToken ct = default) =>
        SendAsync(HttpMethod.Get, $"/v2/matches/{Uri.EscapeDataString(matchId)}/messages?count={count}", null, ct);

    public Task<JsonElement> SendMessageAsync(string matchId, string message, CancellationToken ct = default)
    {
        if (ReadOnly) throw new InvalidOperationException("The client is read-only. Create it with ReadOnly = false.");
        return SendAsync(HttpMethod.Post, $"/user/matches/{Uri.EscapeDataString(matchId)}", new { message }, ct);
    }

    private async Task<JsonElement> SendAsync(HttpMethod method, string pathAndQuery, object? body, CancellationToken ct)
    {
        await _gate.WaitAsync(ct);
        try
        {
            if (_blocked) throw new TinderBlockedException("Circuit open: a previous 403/429");

            var wait = _last + _minDelay + TimeSpan.FromMilliseconds(Random.Shared.Next(0, 1000)) - DateTimeOffset.UtcNow;
            if (wait > TimeSpan.Zero) await Task.Delay(wait, ct);
            _last = DateTimeOffset.UtcNow;

            var separator = pathAndQuery.Contains('?') ? '&' : '?';
            using var request = new HttpRequestMessage(method, $"{pathAndQuery}{separator}locale={_locale}");
            if (body is not null) request.Content = JsonContent.Create(body);

            using var response = await _http.SendAsync(request, ct);
            if (response.StatusCode is HttpStatusCode.Forbidden or HttpStatusCode.TooManyRequests)
            {
                _blocked = true;
                throw new TinderBlockedException($"HTTP {(int)response.StatusCode}: automation stopped");
            }
            response.EnsureSuccessStatusCode();

            var json = await response.Content.ReadFromJsonAsync<JsonElement>(ct);
            return json.TryGetProperty("data", out var data) ? data : json;
        }
        finally
        {
            _gate.Release();
        }
    }

    public void Dispose()
    {
        _http.Dispose();
        _gate.Dispose();
    }
}

Usage:

var token = Environment.GetEnvironmentVariable("TINDER_AUTH_TOKEN")
    ?? throw new InvalidOperationException("TINDER_AUTH_TOKEN is missing");
using var tinder = new TinderClient(token);
var matches = await tinder.GetMatchesAsync(count: 20);
Console.WriteLine(matches.GetProperty("matches").GetArrayLength());

Validating responses

When the API schema changes, you want an immediate error rather than silently processing the wrong data. In TypeScript, with Zod:

import { z } from "zod";

const Match = z.object({
  _id: z.string(),
  created_date: z.string(),
  person: z.object({ _id: z.string(), name: z.string() }).passthrough(),
}).passthrough();

const MatchesData = z.object({ matches: z.array(Match) });

const data = MatchesData.parse(await client.matches(20));

.passthrough() allows unknown fields. Only require the fields you actually use.

Testing strategy

Don't write automated tests against the live API. It puts your account at risk and the tests will be flaky. Instead:

  1. Save your own responses from the browser's Network tab.
  2. Replace personal data (names, photo URLs, messages, IDs) with fake values.
  3. Test parsing and business logic against these fixture files.
  4. Mock the HTTP layer (msw, nock, or an HttpMessageHandler in .NET).

Notes for AI agents and MCP

If you want to give an AI agent access to Tinder:

  • Use the official MCP SDK (@modelcontextprotocol/sdk, or the ModelContextProtocol NuGet package).
  • Expose read-only tools only: list_matches, read_messages, get_profile.
  • If you expose writes such as sending messages, require human approval on every call. Let the agent only draft.
  • Never expose like, pass or bulk actions.
  • Think about the privacy implications of putting the other person's messages into the agent's context (chapter 13).

Responsible use and the law

Tinder's API is private. Using it puts you in a grey area at best. This chapter sums up the rules this book is built on and the legal framework to keep in mind. This is not legal advice.

Five rules

  1. Only your own account. Use your own session and act on your own data. Never use someone else's token, and never create accounts for automation.
  2. Respect other people's privacy. Profiles, photos and messages belong to real people who never agreed to be in your dataset. Don't scrape, store, publish or train models on them.
  3. Don't get around protections. Don't try to beat CAPTCHAs, rate limits, phone verification or paywalls. When the API says stop (403, 429, CAPTCHA), stop.
  4. No spam, no deception. Don't send automated messages that pretend to be a person. Don't run mass-like or mass-message bots.
  5. Keep it small. Personal experiments, learning and security research with responsible disclosure. Those are the only uses this book supports.

Terms of Use

Tinder's Terms of Use restrict automated access to the service outside the official apps. Accounts used with unofficial clients can be suspended or permanently banned, and bans are often tied to the phone number. Read the current terms yourself.

Data protection

If you process personal data obtained through the API, data protection law may apply to you too:

RegionLawKey point
EU / EEAGDPRSexual orientation and biometric data are special categories (Art. 9). The household exemption is read narrowly.
United KingdomUK GDPR + Data Protection Act 2018Same special-category rules as the GDPR
CaliforniaCCPA / CPRACategories of sensitive personal information and consumer rights
TürkiyeKVKK (Law No. 6698)Data about sex life and biometric data are special categories (Art. 6). Processing them without explicit consent is prohibited as a rule.

Every profile on a dating app indirectly reveals something about the person's sexual orientation. That makes not storing other people's data a legal requirement in most cases, not just an ethical choice.

What this book deliberately leaves out

TopicWhy
Account creation, onboarding detailsCreating accounts programmatically is almost only used for fake or bulk accounts
CAPTCHA solvingA direct bypass of a protection
Unblurring "Likes You"Bypasses a paid feature and reveals other people's photos without consent
Free Passport (location tricks)Bypasses a paid feature
Mass-like and mass-message botsSpam that degrades other people's experience
Profile scraping and building datasetsPrivacy violations and GDPR / KVKK exposure

Reporting vulnerabilities

If you find a security vulnerability in Tinder, report it privately through Tinder's security page or Match Group's bug bounty program. Don't publish working exploit code that exposes other users.

Some projects call themselves "research" or a "proof of concept" while their code turns out to bypass paid features. Judge a project by what it does, not by what it calls itself.

Appendix A: Project catalog

The full list of 40 projects reviewed for this book. Metadata comes from the GitHub REST API and was last refreshed on 12 November 2026 (scripts/refresh-stats.sh).

MarkerMeaning
🟢Pushed within the last 12 months
🟡Last push 1–3 years ago
🔴Last push more than 3 years ago
🗄️Archived
⚖️No license file: you may read the code but not reuse it

TypeScript / JavaScript clients

ProjectLanguageStatus⭐Last pushLicenseNotes
Miguelo981/tinder-apiTypeScript🟢92026-07-23MITThe most current client; /v3 protobuf login
hfreire/tinder-wrapperJavaScript🟢312026-01-28MITRate limiting, retries and a circuit breaker; Facebook login
tinderjs/tinderjsJavaScript🟡762024-03-02⚖️Community continuation of alkawryk/tinderjs
jaebradley/tinder-clientJavaScript🔴672022-12-07MITPromise-based, good examples
rednit-team/tinder.jsTypeScript🔴52022-01-24⚖️
alkawryk/tinderjsJavaScript🔴1952016-06-13⚖️
tranhungt/tinder_proJavaScript🔴462014-08-29MIT

Python clients

ProjectLanguageStatus⭐Last pushLicenseNotes
tiagovla/tinder.pyPython🔴142023-11-12GPL-3.0Async; has a message-sending bug (chapter 11)
rednit-team/tinder.pyPython🔴222021-12-31Apache-2.0Models, caching, rate limiting
charliewolf/pynderPython🔴6322021-06-23CustomBest-known Python client; its issues are a history of the API
Xisurthros/pyTinderPython🔴32022-09-20MIT
MMcintire96/python_TinderAPIPython🗄️762022-07-08⚖️
yoimnik/tinder-api-wrapperPython🔴242017-02-17⚖️
faruken/aio-tinderPython🗄️32016-10-19MIT
Coriou/py-tinderPython🔴172014-05-14MIT

Java clients

ProjectLanguageStatus⭐Last pushLicenseNotes
rednit-team/tinder4jJava🔴92022-09-18Apache-2.0The most complete Java wrapper
s1l2/tinder-apiJava🔴02017-05-28Custom

C# / .NET clients

ProjectLanguageStatus⭐Last pushLicenseNotes
ldellisola/TinderSharpC#🔴42022-12-08Unlicense.NET Standard 2.0; models free to reuse
zachary-kaelan/TinderAPIC#🔴72021-08-15⚖️

Go clients

ProjectLanguageStatus⭐Last pushLicenseNotes
d6o/TinderGoGo🔴122017-04-06MIT
zachlatta/tinderGo🔴92016-10-20⚖️

Ruby clients

ProjectLanguageStatus⭐Last pushLicenseNotes
patrickclery/tinder_clientRuby🔴132019-11-13MIT
jvenezia/tinderbotRuby🔴682019-07-26MIT

PHP clients

ProjectLanguageStatus⭐Last pushLicenseNotes
skipperbent/tinder-php-sdkPHP🔴182017-08-10⚖️Its README advises against production use
Adewra/TinderPHP🗄️82015-04-14GPL-3.0

Other languages

ProjectLanguageStatus⭐Last pushLicenseNotes
jcrodriguez1989/RtinderR🔴152019-06-03⚖️
imryan/swifty-tinderSwift🔴22016-08-04MIT
graves/extinderElixir🔴22016-05-08⚖️

API documentation

ProjectLanguageStatus⭐Last pushLicenseNotes
NastyDevs/tinder-api-documentation-🟡342025-03-16⚖️The only OpenAPI schema
fbessez/TinderPython🔴8662023-01-29MITThe classic endpoint table (2018–2019)
ultralytics/tinderPython🟢752026-08-28AGPL-3.0Recent push, but the content describes the 2018 API
rednit-team/tinder-api-docs-🔴102021-12-03MIT

Authentication helpers

ProjectLanguageStatus⭐Last pushLicenseNotes
meister245/tinder-tokenPython🔴82023-02-15MIT
jaebradley/tinder-access-token-generatorJavaScript🔴112023-01-03MIT
tinderjs/tinderauthJavaScript🔴272020-12-22⚖️
shuheiktgw/tinder_auth_fetcherRuby🔴72019-07-19MIT

AI and MCP

ProjectLanguageStatus⭐Last pushLicenseNotes
glassBead-tc/tinder-mcp-serverTypeScript🟡262025-05-24⚖️No real MCP SDK; an Express server

Unofficial apps

ProjectLanguageStatus⭐Last pushLicenseNotes
DylanVanAssche/harbour-sailfinderC++🗄️182021-08-23GPL-3.0
Dainius14/twinderC#🗄️182017-04-09MIT

Research and experiments

ProjectLanguageStatus⭐Last pushLicenseNotes
joelbarmettlerUZH/auto-tinderPython🟡5692025-02-11MITEnd-to-end ML experiment with a write-up. Automates swiping, so it carries Terms of Use risk.

Not listed

These kinds of results from a "tinder api" search were left out on purpose:

  • Tools that create fake or bulk accounts or get around phone verification
  • Tools that unlock paid features for free or expose other people's data
  • Selenium/Appium swipe bots with no research value
  • "Tinder-like" app clones and unrelated namesakes (such as the old 37signals Campfire library collectiveidea/tinder)

Appendix B: Glossary

TermMeaning
X-Auth-TokenThe session token (a UUID) used to access the app API
Refresh tokenA long-lived token used to get a new X-Auth-Token when the old one expires. It rotates every time it is used.
OTPOne-Time Password: the single-use code sent by SMS
ProtobufGoogle's Protocol Buffers binary format, used by /v3/auth/login
Arkose LabsThe CAPTCHA and bot-detection provider Tinder uses
recsRecommendations: the cards in the swipe deck
s_numberA per-card number that is sent back in like and pass requests
content_hashA hash of a card's content
teaserA short preview shown on a card or on the "Likes You" screen
fast-matchThe API name for the "Likes You" feature
BoostPuts your profile in front for a limited time 💳
Super LikeA like the other person is explicitly notified about
PassportMoves your swiping location to another city 💳
Duos"Double Date" mode, where two people share a profile
Global modeRecommendations from around the world, with language preferences
Tinder UA discovery mode for university students
Soft blockA short-lived restriction: an empty deck or errors
Circuit breakerA design pattern that automatically stops requests after an error
JitterRandom variation added to the delay between requests
MCPModel Context Protocol: a standard for connecting AI agents to external tools
OpenAPI / SwaggerA standard for describing REST APIs in a machine-readable way
GDPRThe EU General Data Protection Regulation
KVKKTürkiye's Personal Data Protection Law (No. 6698)

Appendix C: Methodology

How this book was made

1. Discovery (12 November 2026). GitHub was searched with the GitHub CLI, sorted by stars, for tinder api, tinder-api, tinder client, tinder bot, pynder, tinder python and tinder sdk. Links in the documentation projects were then followed.

2. Filtering. Forks without meaningful changes, copies, unrelated projects and anything in the categories listed in chapter 13 were removed.

3. Metadata. For the remaining 40 projects, stars, last push date, archive status and license were read from the GitHub REST API (gh api repos/{owner}/{repo}). scripts/refresh-stats.sh repeats this step.

4. Code review. Six primary projects were cloned. Their endpoints, headers, request bodies and error handling were read directly in the code.

5. Cross-checking. Every endpoint was checked against at least one source to build the reference matrix. Where sources disagree, the most recent code wins and the difference is spelled out.

6. Code samples. The TypeScript samples in chapter 12 were type-checked with deno check. The C# sample was compiled against .NET 8 with TreatWarningsAsErrors. None were run against the live API.

Limitations

  • No request was tried against a real Tinder account.
  • Star counts favor older projects. A 🟢 project with few stars may be the better choice.
  • pushed_at can be bumped by trivial commits (formatting bots and the like).
  • Response schemas come from type definitions in client code. Real responses may contain more fields.

Appendix D: Changelog

The format follows Keep a Changelog and versions follow Semantic Versioning.

[2.0.0] - 2026-11-12

Changed

  • The whole book is now in English.
  • Per-chapter source lists were removed. The book stands on its own; the project catalog stays in Appendix A.
  • The reference matrix now shows method, path, status and chapter.
  • The repository was renamed to tinder-api-docs, and search metadata was added (page descriptions, Open Graph tags, a sitemap).

[1.0.0] - 2026-11-12

Added

  • 13 chapters: foundations, authentication (including the /v3 protobuf flow), request anatomy, profile, discovery, interactions, matches and WebSocket, location and meta, other endpoints, the reference matrix, library reviews, writing your own client, and responsible use
  • A 59-row endpoint reference matrix
  • In-depth reviews of 7 projects, based on their source code
  • TypeScript and C# (.NET 8) client skeletons that compile
  • A verified catalog of 40 projects, a glossary and a methodology appendix
  • scripts/refresh-stats.sh for repeatable metadata checks