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 goal | Reading order |
|---|---|
| See the big picture | 1 → 2 → 10 → 11 |
| Write a client | 1 → 2 → 3 → 4–9 → 12 |
| Pick an existing library | 11 → Appendix A |
| Look up an endpoint quickly | 10 (reference matrix) |
Markers
Throughout the book, the freshness of each fact is marked like this:
| Marker | Meaning |
|---|---|
| 🟢 Current | Used in at least one client maintained in 2025–2026 |
| 🟡 Uncertain | Only seen in 2020–2024 material; it may still work |
| 🔴 Historical | Pre-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
| Period | What happened | Effect |
|---|---|---|
| 2014 | A widely shared gist published the first comprehensive endpoint list. | It became the common ancestor of almost every later project. |
| 2014–2017 | The first libraries appeared (pynder, tinderjs, tinder_pro). Login used a Facebook token. | 🔴 Most libraries from this era no longer work. |
| 2018 | An updated community reference documented the /v2/... endpoints and SMS login. | It became the most cited endpoint table. |
| 2019–2020 | Facebook 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–2023 | An async Python client, a Java wrapper and the first OpenAPI schema appeared. | The API got its first machine-readable description. |
| 2024–2026 | Login 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:
- The app API (REST + JSON). Profile, recommendations, likes, matches and messages. Most of this book is about this layer.
- The authentication gateway (protobuf). Phone login, OTP, email verification and CAPTCHA. Covered in chapter 2.
- The real-time channel (WebSocket). Notifications for new messages and matches. Covered in chapter 7.
Versioning
Tinder's paths follow no consistent versioning scheme:
| Prefix | Example | Status |
|---|---|---|
| (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:
- Who documented it?
- Which client code actually calls it?
- 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
| Period | Method | Endpoint | Status |
|---|---|---|---|
| 2014–2019 | Exchange a Facebook access token | POST /auth, later POST /v2/auth/login/facebook | 🔴 |
| 2018–2020 | SMS via Facebook Account Kit | POST /auth/login/accountkit | 🔴 Account Kit shut down |
| 2019–2023 | Tinder's own SMS OTP flow (JSON) | POST /v2/auth/sms/send, /v2/auth/sms/validate, /v2/auth/login/sms | 🟡 |
| 2024– | Protobuf authentication gateway | POST /v3/auth/login | 🟢 |
| Always | Copy your own token from the browser | – | 🟢 The most robust option |
Method 1: Copy your own token from the browser (recommended)
For personal experiments this is the simplest and least fragile route, and it never runs into a CAPTCHA. Current clients recommend it too.
- Sign in to tinder.com with your own account as usual.
- Open the developer tools (
F12orCmd+Option+I). - Go to Application → Local Storage →
https://tinder.com. - Find these keys:
| Key | Contents | Used for |
|---|---|---|
TinderWeb/APIToken | The X-Auth-Token value | Every app API request |
TinderWeb/uuid | Persistent device ID | The persistent-device-id header |
| Refresh token (key name may vary) | Refresh token | Getting 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
| Property | Value (as of July 2026) |
|---|---|
| Method and path | POST /v3/auth/login?locale=en |
Content-Type / Accept | application/x-protobuf |
| Client identity | platform: ios, tinder-version, app-version, an iOS User-Agent |
| Session headers | persistent-device-id, app-session-id, funnel-session-id, app-session-time-elapsed |
| Body | An AuthGatewayRequest protobuf message (with a phone, OTP or email-code sub-message) |
| Response | An 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
| Response | Meaning | What to do |
|---|---|---|
| Success | auth_token and refresh_token were returned | Store both securely |
| EmailRequired | An extra code was sent to the account's email; the response includes a jwt | Ask the user for the email code and send it together with the jwt |
| CaptchaRequired | The 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:
| Step | Request | Body | Taken from the response |
|---|---|---|---|
| 1 | POST /v2/auth/sms/send?auth_type=sms | {"phone_number": "1555…"} | – |
| 2 | POST /v2/auth/sms/validate?auth_type=sms | {"otp_code": "123456", "phone_number": "1555…"} | refresh_token |
| 3 | POST /v2/auth/login/sms | {"refresh_token": "…"} | api_token (= X-Auth-Token) |
Token lifecycle
- An
X-Auth-Tokenis valid for a limited time. When it expires, requests return401 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
.envfile 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 URL | https://api.gotinder.com |
| Transport | HTTPS only |
| Body format | JSON (except login, which uses protobuf) |
| Credential | The X-Auth-Token header |
| Language | The ?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
| Header | Required? | Notes |
|---|---|---|
X-Auth-Token | ✅ | The session token. Every current client sends it. |
Content-Type: application/json | Mostly | Older 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. |
platform | Recommended | web, ios or android. Real clients always send it. |
tinder-version, app-version | Recommended | The app version. A very old version may be rejected or flagged. |
persistent-device-id | Recommended | The device ID. On the web it is the TinderWeb/uuid value. |
User-Agent | Recommended | Should match the platform. |
x-supported-image-formats | Optional | webp,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
403or a ban. Define your headers in one place and keep them close to what the browser actually sends.
Query parameters
| Parameter | Where | Notes |
|---|---|---|
locale | Almost everywhere | The language of text in the response (en, tr, es-ES) |
include | GET /v2/profile | Which profile sections to return (see chapter 4) |
count | /v2/matches, /v2/matches/{id}/messages | Page size. Up to 100 for matches. |
message | /v2/matches | 1: matches you have messaged, 0: new matches |
s_number | /like, /pass | The 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'sdataobject 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: returndataif present, otherwise the body itself.
HTTP status codes
| Code | Meaning | Recommended behavior |
|---|---|---|
200 | Success | – |
400 | Missing or invalid parameter | Fix the request; don't retry |
401 | Token invalid or expired | Refresh it; if that fails, log in again |
403 | Not allowed: premium feature, suspicious traffic or CAPTCHA | Stop. If it is a premium error, tell the user. Otherwise turn the automation off. |
404 | Endpoint or resource not found | The API may have changed; compare with a current client |
429 | Rate limited | Exponential backoff (2 s, 4 s, 8 s…) with a capped number of retries |
5xx | Server error | At most 1–2 retries after a short wait |
A good model is to split errors into types:
| Error type | When it happens |
|---|---|
HttpError | Generic HTTP error carrying status and body |
PremiumRequiredError | A 403 on a preference update: a subscription is required |
NoBoostAvailableError | No Boost left |
CaptchaRequiredError | Login asked for a CAPTCHA |
EmailRequiredError | Login 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/profileandGET /v2/meta. They rarely change. - Free accounts have a daily like quota. Track what is left through the
likes_remainingfield in every/likeresponse. - 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:
| Scope | Contents |
|---|---|
user | Name, bio, photos, birth date, gender, discovery preferences |
account | Phone, email, account status |
likes | Remaining likes and when they reset |
super_likes | Remaining Super Likes |
boost | Boost allowance and any active boost |
travel | Passport (location change) status 💳 |
plus_control | Paid privacy settings (hide age and distance) 💳 |
readreceipts | Read receipt allowance 💳 |
spotify, instagram | Connected accounts |
notifications, email_settings | Notification preferences |
available_descriptors | Descriptors you can add to a profile (star sign, education, …) |
profile_meter | Profile completeness meter |
feature_access, paywalls, offerings, purchase, misc_merchandising | Subscription and sales screens |
onboarding, tutorials | First-run steps |
tinder_u | Tinder U (university) status |
contact_cards, bouncerbypass, all_in_gender | Undocumented internal scopes |
Tip: Only request the scopes you need. The response is smaller and you avoid processing data you don't use.
user,likes,boostcovers most cases.
Updating your profile and discovery settings 🟢
Updates are split across two endpoints with two different body shapes:
| Endpoint | Body | What 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
}
}
| Field | Values |
|---|---|
age_filter_min, age_filter_max | Ages 18 and up |
distance_filter | In 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_filter | 0 men, 1 women, -1 everyone |
bio | Profile text |
discoverable | If false, you don't appear in anyone's deck |
| Auto-expansion | Widen age and distance when recommendations run low |
| Global mode | Recommendations from around the world, plus the languages you want to match in |
💳 Premium limit: Preference updates (especially
preference_filters) return403on a free account. Treat that403as "not allowed" and don't retry.
Legacy /profile options 🔴
In 2018-era material, POST /profile could also do the following:
| Body | Effect |
|---|---|
{"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
| Request | Purpose | Status |
|---|---|---|
PUT /profile/job · DELETE /profile/job | Add or remove your job | 🔴 |
PUT /profile/school · DELETE /profile/school | Add or remove your school | 🔴 |
POST /v2/profile/job · POST /v2/profile/school | v2 equivalents | 🟡 |
GET /v2/profile/spotify · PUT /v2/profile/spotify/theme | Spotify settings and profile anthem | 🔴 |
PUT /profile/username · DELETE /profile/username | Web profile username | 🔴 |
GET /profile/photos · DELETE /media | List photos, delete a photo | 🟡 |
POST /v2/profile/email_settings | Email notification settings | 🟡 |
POST /v2/account/account-email/confirm | Confirm your email | 🟡 |
POST /v2/account/recovery/send | Account recovery email | 🟡 |
DELETE /v2/profile | Permanently 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
| Parameter | Notes |
|---|---|
locale | Response language |
duos | 0 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
| Field | Why it matters |
|---|---|
user._id | The ID used in like and pass requests |
s_number | A per-session card number. It is sent back in like and pass requests (see chapter 6). |
content_hash | A hash of the card content. Some older clients added it to likes. |
user.photos[].id | Sent as the liked content (liked_content_id) in current clients |
distance_mi | Approximate distance in miles. Rounded, not an exact location. |
teasers | Short facts shown on the card (job, school, interest) |
experiment_info.user_interests | Interests, with an is_common flag for shared ones |
type | Usually user. Ads or special cards can use other types. |
An empty deck
If results comes back empty:
- Your distance and age range may be too narrow.
- You may have seen everyone nearby.
- 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:
| Action | Path | 2018 docs | 2021 schema | 2023 client | 2026 client |
|---|---|---|---|---|---|
| Like | /like/{user_id} | GET | GET | POST | POST + body |
| Pass | /pass/{user_id} | GET | GET | POST | GET + s_number |
| Super Like | /like/{user_id}/super | POST | POST | – | 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 field | Where it comes from |
|---|---|
s_number | The recommendation card's s_number |
liked_content_id | The id of the photo you liked (usually the first) |
liked_content_type | Currently only photo |
Response:
{ "match": false, "likes_remaining": 87, "status": 200 }
| Field | Meaning |
|---|---|
match | If true, the like was mutual and a new match was created |
likes_remaining | Likes left today. Stop at 0. |
X-Padding | Meaningless 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:
| Field | Meaning |
|---|---|
duration | Boost duration (unit not documented) |
expires_at | End time (Unix ms) |
remaining, allotment_remaining, purchased_remaining | Boosts left |
multiplier | Visibility multiplier |
resets_at | When the allowance resets |
boost_type, is_super_boost | Type 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
| Parameter | Notes |
|---|---|
count | How many matches to return, between 1 and 100 |
message | 1: matches you have messaged, 0: new matches with no messages yet |
is_tinder_u | Tinder 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)
| Field | Meaning |
|---|---|
_id / id | The match ID, used as match_id in message endpoints. Usually the two user IDs concatenated. |
person | A short profile of the other person (_id, name, photos, bio) |
created_date | When the match happened |
last_activity_date | Last message or activity |
message_count, messages[] | Message count and the latest messages |
participants | Participant user IDs |
seen | Whether the match has been seen |
closed, dead | The match was closed or has ended |
pending | A match that isn't complete yet |
is_super_like, is_boost_match, is_fast_match, is_super_boost_match, is_primetime_boost_match | How the match came about |
is_opener, has_shown_initial_interest | Who wrote first |
readreceipt | Read receipts 💳 |
liked_content | What of theirs you liked |
common_friend_count, common_like_count | Shared 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 🟡
| Request | Purpose |
|---|---|
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
| Step | Details |
|---|---|
| Token | GET /ws/generate is called with web headers (platform: web) |
| Connection | keepalive.gotinder.com, using a short-lived token |
| Keep-alive | Clients send a small binary ping frame every 30 seconds |
| Events | Binary 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:
| Period | Request | Body | Status |
|---|---|---|---|
| 2014–2020 | POST /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:
| Section | Contents |
|---|---|
readreceipts | Whether read receipts are on |
profile | Editing limits: can_edit_jobs, can_edit_email, school_name_max_length, job_title_max_length… |
super_like | Whether Super Like is enabled, alc_mode |
location_prechecks | Location pre-check codes |
age_verification | Age verification status |
biometric_consent | Consent for biometric data (e.g. photo verification) |
voter_registration | US-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) 💳 🔴
| Request | Body | Purpose |
|---|---|---|
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.
Popular places 🟡
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" 💳
| Request | Purpose | Status |
|---|---|---|
POST /v2/fast-match/count · GET /v2/fast-match/count | How many people liked you | 🟡 (method varies) |
GET /v2/fast-match/teaser?type=recently-active | A single blurred preview | 🟡 |
GET /v2/fast-match/teasers | A list of previews | 🟡 |
GET /v2/fast-match/preview | Preview thumbnail | 🔴 |
GET /v2/my-likes | Summary 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 🔴
| Request | Purpose |
|---|---|
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
| Request | Purpose | Status |
|---|---|---|
GET /instagram/authorize | Start an Instagram connection | 🔴 |
POST /instagram/authenticate · POST /instagram/deauthorize | Connect / disconnect Instagram | 🟡 |
GET /v2/profile/spotify · PUT /v2/profile/spotify/theme | Spotify settings and profile anthem | 🔴 |
Notifications
| Request | Purpose | Status |
|---|---|---|
PUT /v2/push/notifications | Notification preferences (exact behavior undocumented) | 🟡 |
POST /v2/push/devices/web/{device_code} · DELETE … | Web push device registration | 🟡 |
Reporting
| Request | Body | Status |
|---|---|---|
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
| Request | Purpose | Status |
|---|---|---|
GET /user/{user_id}/common_connections | Shared connections | 🔴 |
POST /user/{user_id}/share | Profile share link | 🔴 |
GET /group/friends | Facebook friends (the old "Social" feature) | 🔴 |
GET /v1/activity/feed?direction=past&eventTypes=1023 | Activity feed | 🔴 |
POST /v2/incentives/eligibility | Promo code eligibility | 🟡 |
POST /v2/auth/verify-captcha | CAPTCHA verification (don't use it; see chapter 2) | 🟡 |
POST /v2/auth/logout | Log out | 🟡 |
Endpoint reference matrix
Every endpoint in this book in one place, with its status and the chapter that covers it.
| Marker | Meaning |
|---|---|
| 🟢 | Used by a client maintained in 2025–2026 |
| 🟡 | Only seen in 2020–2024 material |
| 🔴 | Pre-2020, very likely broken |
| 💳 | Paid subscription |
| ⚠️ | Irreversible |
Authentication
| Method | Path | Status | Chapter |
|---|---|---|---|
| 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
| Method | Path | Status | Chapter |
|---|---|---|---|
| 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
| Method | Path | Status | Chapter |
|---|---|---|---|
| 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
| Method | Path | Status | Chapter |
|---|---|---|---|
| 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
| Method | Path | Status | Chapter |
|---|---|---|---|
| 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
| Method | Path | Status | Chapter |
|---|---|---|---|
| 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 scope | 9 |
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-ts | tinder.py | pynder | tinder-mcp-server | OpenAPI schema | 2018 reference | TinderSharp | |
|---|---|---|---|---|---|---|---|
| Language | TypeScript | Python | Python | TypeScript | OpenAPI | Markdown + Python | C# |
| Last push | 2026-07 | 2023-11 | 2021-06 | 2025-05 | 2025-03 | 2023-01 | 2022-12 |
| Current login flow | ✅ /v3 | ➖ token only | ❌ /v2 | ➖ /v2 | ❌ | ❌ | |
| Types / models | ✅ | ✅ | ✅ | ✅ Zod | ✅ schema | ❌ | ✅ |
| Error types | ✅ | ✅ | ➖ | ✅ | – | – | ➖ |
| Rate limiting / retries | ❌ | ➖ 3 tries on 5xx | ❌ | ✅ | – | – | ❌ |
| Tests | ✅ | ✅ | ❌ | ✅ | – | – | ❌ |
| License | MIT | GPL-3.0 | Custom | Unclear | None | MIT | Unlicense |
| Grade | A | B | C (historical) | C | B (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.
| Feature | Status |
|---|---|
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.
| Feature | Status |
|---|---|
| Login | With 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:
| Claim | Reality |
|---|---|
| "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. |
| Authentication | The old /v2/auth/sms/... and /v2/auth/login/facebook flows. No /v3 flow. |
| CAPTCHA | Calls /v2/auth/verify-captcha, the opposite of the "stop" approach this book recommends. |
| License | package.json says MIT, but the repository has no LICENSE file |
| Setup | The clone URL in the README still contains a yourusername placeholder |
| Quality | Zod 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
| Principle | Why | How |
|---|---|---|
| Read first | Reads don't change the account, so the risk is low | Start the client with readOnly: true. Write methods must be enabled explicitly. |
| One entry point | Headers, errors and rate limits are handled in one place | Route every request through a single request() function |
| Slow and serial | Avoids automation detection and rate limits | 1.5–3 s between requests plus random jitter, no concurrent requests |
| Respect stop signals | A 403 or CAPTCHA means elevated risk | Trip a circuit breaker on these errors and require a human to reset it |
| Don't trust the schema | The API changes without notice | Validate responses at runtime (Zod, System.Text.Json + null checks) |
| Protect secrets | The token is the key to your account | Environment variable or keychain; mask it in logs |
| Lock irreversible actions | Deleting the account or unmatching can't be undone | Don'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:
- Save your own responses from the browser's Network tab.
- Replace personal data (names, photo URLs, messages, IDs) with fake values.
- Test parsing and business logic against these fixture files.
- Mock the HTTP layer (
msw,nock, or anHttpMessageHandlerin .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 theModelContextProtocolNuGet 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
- 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.
- 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.
- 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. - No spam, no deception. Don't send automated messages that pretend to be a person. Don't run mass-like or mass-message bots.
- 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:
| Region | Law | Key point |
|---|---|---|
| EU / EEA | GDPR | Sexual orientation and biometric data are special categories (Art. 9). The household exemption is read narrowly. |
| United Kingdom | UK GDPR + Data Protection Act 2018 | Same special-category rules as the GDPR |
| California | CCPA / CPRA | Categories of sensitive personal information and consumer rights |
| Türkiye | KVKK (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
| Topic | Why |
|---|---|
Account creation, onboarding details | Creating accounts programmatically is almost only used for fake or bulk accounts |
| CAPTCHA solving | A 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 bots | Spam that degrades other people's experience |
| Profile scraping and building datasets | Privacy 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).
| Marker | Meaning |
|---|---|
| 🟢 | 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
| Project | Language | Status | ⭐ | Last push | License | Notes |
|---|---|---|---|---|---|---|
| Miguelo981/tinder-api | TypeScript | 🟢 | 9 | 2026-07-23 | MIT | The most current client; /v3 protobuf login |
| hfreire/tinder-wrapper | JavaScript | 🟢 | 31 | 2026-01-28 | MIT | Rate limiting, retries and a circuit breaker; Facebook login |
| tinderjs/tinderjs | JavaScript | 🟡 | 76 | 2024-03-02 | ⚖️ | Community continuation of alkawryk/tinderjs |
| jaebradley/tinder-client | JavaScript | 🔴 | 67 | 2022-12-07 | MIT | Promise-based, good examples |
| rednit-team/tinder.js | TypeScript | 🔴 | 5 | 2022-01-24 | ⚖️ | |
| alkawryk/tinderjs | JavaScript | 🔴 | 195 | 2016-06-13 | ⚖️ | |
| tranhungt/tinder_pro | JavaScript | 🔴 | 46 | 2014-08-29 | MIT |
Python clients
| Project | Language | Status | ⭐ | Last push | License | Notes |
|---|---|---|---|---|---|---|
| tiagovla/tinder.py | Python | 🔴 | 14 | 2023-11-12 | GPL-3.0 | Async; has a message-sending bug (chapter 11) |
| rednit-team/tinder.py | Python | 🔴 | 22 | 2021-12-31 | Apache-2.0 | Models, caching, rate limiting |
| charliewolf/pynder | Python | 🔴 | 632 | 2021-06-23 | Custom | Best-known Python client; its issues are a history of the API |
| Xisurthros/pyTinder | Python | 🔴 | 3 | 2022-09-20 | MIT | |
| MMcintire96/python_TinderAPI | Python | 🗄️ | 76 | 2022-07-08 | ⚖️ | |
| yoimnik/tinder-api-wrapper | Python | 🔴 | 24 | 2017-02-17 | ⚖️ | |
| faruken/aio-tinder | Python | 🗄️ | 3 | 2016-10-19 | MIT | |
| Coriou/py-tinder | Python | 🔴 | 17 | 2014-05-14 | MIT |
Java clients
| Project | Language | Status | ⭐ | Last push | License | Notes |
|---|---|---|---|---|---|---|
| rednit-team/tinder4j | Java | 🔴 | 9 | 2022-09-18 | Apache-2.0 | The most complete Java wrapper |
| s1l2/tinder-api | Java | 🔴 | 0 | 2017-05-28 | Custom |
C# / .NET clients
| Project | Language | Status | ⭐ | Last push | License | Notes |
|---|---|---|---|---|---|---|
| ldellisola/TinderSharp | C# | 🔴 | 4 | 2022-12-08 | Unlicense | .NET Standard 2.0; models free to reuse |
| zachary-kaelan/TinderAPI | C# | 🔴 | 7 | 2021-08-15 | ⚖️ |
Go clients
| Project | Language | Status | ⭐ | Last push | License | Notes |
|---|---|---|---|---|---|---|
| d6o/TinderGo | Go | 🔴 | 12 | 2017-04-06 | MIT | |
| zachlatta/tinder | Go | 🔴 | 9 | 2016-10-20 | ⚖️ |
Ruby clients
| Project | Language | Status | ⭐ | Last push | License | Notes |
|---|---|---|---|---|---|---|
| patrickclery/tinder_client | Ruby | 🔴 | 13 | 2019-11-13 | MIT | |
| jvenezia/tinderbot | Ruby | 🔴 | 68 | 2019-07-26 | MIT |
PHP clients
| Project | Language | Status | ⭐ | Last push | License | Notes |
|---|---|---|---|---|---|---|
| skipperbent/tinder-php-sdk | PHP | 🔴 | 18 | 2017-08-10 | ⚖️ | Its README advises against production use |
| Adewra/Tinder | PHP | 🗄️ | 8 | 2015-04-14 | GPL-3.0 |
Other languages
| Project | Language | Status | ⭐ | Last push | License | Notes |
|---|---|---|---|---|---|---|
| jcrodriguez1989/Rtinder | R | 🔴 | 15 | 2019-06-03 | ⚖️ | |
| imryan/swifty-tinder | Swift | 🔴 | 2 | 2016-08-04 | MIT | |
| graves/extinder | Elixir | 🔴 | 2 | 2016-05-08 | ⚖️ |
API documentation
| Project | Language | Status | ⭐ | Last push | License | Notes |
|---|---|---|---|---|---|---|
| NastyDevs/tinder-api-documentation | - | 🟡 | 34 | 2025-03-16 | ⚖️ | The only OpenAPI schema |
| fbessez/Tinder | Python | 🔴 | 866 | 2023-01-29 | MIT | The classic endpoint table (2018–2019) |
| ultralytics/tinder | Python | 🟢 | 75 | 2026-08-28 | AGPL-3.0 | Recent push, but the content describes the 2018 API |
| rednit-team/tinder-api-docs | - | 🔴 | 10 | 2021-12-03 | MIT |
Authentication helpers
| Project | Language | Status | ⭐ | Last push | License | Notes |
|---|---|---|---|---|---|---|
| meister245/tinder-token | Python | 🔴 | 8 | 2023-02-15 | MIT | |
| jaebradley/tinder-access-token-generator | JavaScript | 🔴 | 11 | 2023-01-03 | MIT | |
| tinderjs/tinderauth | JavaScript | 🔴 | 27 | 2020-12-22 | ⚖️ | |
| shuheiktgw/tinder_auth_fetcher | Ruby | 🔴 | 7 | 2019-07-19 | MIT |
AI and MCP
| Project | Language | Status | ⭐ | Last push | License | Notes |
|---|---|---|---|---|---|---|
| glassBead-tc/tinder-mcp-server | TypeScript | 🟡 | 26 | 2025-05-24 | ⚖️ | No real MCP SDK; an Express server |
Unofficial apps
| Project | Language | Status | ⭐ | Last push | License | Notes |
|---|---|---|---|---|---|---|
| DylanVanAssche/harbour-sailfinder | C++ | 🗄️ | 18 | 2021-08-23 | GPL-3.0 | |
| Dainius14/twinder | C# | 🗄️ | 18 | 2017-04-09 | MIT |
Research and experiments
| Project | Language | Status | ⭐ | Last push | License | Notes |
|---|---|---|---|---|---|---|
| joelbarmettlerUZH/auto-tinder | Python | 🟡 | 569 | 2025-02-11 | MIT | End-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
| Term | Meaning |
|---|---|
| X-Auth-Token | The session token (a UUID) used to access the app API |
| Refresh token | A long-lived token used to get a new X-Auth-Token when the old one expires. It rotates every time it is used. |
| OTP | One-Time Password: the single-use code sent by SMS |
| Protobuf | Google's Protocol Buffers binary format, used by /v3/auth/login |
| Arkose Labs | The CAPTCHA and bot-detection provider Tinder uses |
| recs | Recommendations: the cards in the swipe deck |
| s_number | A per-card number that is sent back in like and pass requests |
| content_hash | A hash of a card's content |
| teaser | A short preview shown on a card or on the "Likes You" screen |
| fast-match | The API name for the "Likes You" feature |
| Boost | Puts your profile in front for a limited time 💳 |
| Super Like | A like the other person is explicitly notified about |
| Passport | Moves your swiping location to another city 💳 |
| Duos | "Double Date" mode, where two people share a profile |
| Global mode | Recommendations from around the world, with language preferences |
| Tinder U | A discovery mode for university students |
| Soft block | A short-lived restriction: an empty deck or errors |
| Circuit breaker | A design pattern that automatically stops requests after an error |
| Jitter | Random variation added to the delay between requests |
| MCP | Model Context Protocol: a standard for connecting AI agents to external tools |
| OpenAPI / Swagger | A standard for describing REST APIs in a machine-readable way |
| GDPR | The EU General Data Protection Regulation |
| KVKK | Tü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_atcan 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
/v3protobuf 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.shfor repeatable metadata checks