How do you check if one X account follows another?
Last updated August 24, 2026
To check Twitter following for a specific pair, send GET user/check_follow_relationship with source_user_id and target_user_id. One response carries following, followed_by, blocking, blocked_by, muting and can_dm, so both directions of the edge settle at once. The probe costs $0.0008 and replaces walking a roster page by page to prove a negative.
Every rate here is the pricing TwitterAPIs publishes. The billed rate is $0.0008 per call; $0.04 per 1,000 tweets is derived from it at a full 20-tweet page, which is the default page size rather than a guaranteed yield (source: twitterapis.com/pricing).
One request settles the whole question
GET user/check_follow_relationship takes two required arguments, source_user_id and target_user_id, both numeric account identifiers sent as strings. There is no cursor, no page size and nothing to loop over, because the answer is a single object rather than a collection. The pair is evaluated upstream and the verdict comes back whether the accounts involved have four followers or forty million. That property is what makes this route worth knowing about: every other way of answering the same question scales with the size of somebody else's audience, and this one does not scale with anything. The documented call probes 745273 against 2178758961, and the entire request is those two query parameters plus an Authorization header carrying your key as a bearer token.
The relationship object, field by field
The response has a single top-level key, relationship. Inside it, source_id and target_id echo the pair you asked about, which is useful when a batch of answers comes back out of order. following is the source account following the target. followed_by is the same edge read the other way, so the two together give four states: no connection, outbound only, inbound only, and mutual when both are true. Alongside those sit blocking, blocked_by, muting and can_dm. Nothing is paginated, nothing is truncated, and one request has already paid for all eight fields whether or not your code reads them. The route is filed under ordinary user reads rather than session reads, so no logged in X account has to be registered against your key for it to answer.
Booleans and nulls are not the same answer
The documented example is instructive about types. following comes back true, followed_by false, can_dm true, and blocking, blocked_by and muting all come back null rather than false. So these are not clean booleans across the board. Treat them as three-valued: true means the state is confirmed, false means it is confirmed absent, and null means X did not supply a verdict for that field on this pair. Code that reads null as falsy will quietly report no block where the honest answer is that the block status is unknown, which is a different and considerably more misleading claim to store. Store the raw value rather than a coerced boolean, so a later pass can still tell a confirmed absence apart from a missing verdict without paying to probe the pair again.
A null relationship is a resolution failure
There is a second null to handle and it sits one level up. The relationship object itself is null when either the source or the target cannot be resolved. That is a documented outcome rather than a fault, and it is a categorically different result from a confirmed absence of a follow edge. Branch on it explicitly and record it as unknown. If you fold it into the same bucket as a plain no, a deleted, suspended or renamed account will read for months as an account that simply does not follow the target, and nothing downstream will ever question the row. The cheap follow-up is to re-resolve each side by its identifier and see which of the two is missing, which turns an ambiguous null into a specific cause you can record.
Numeric identifiers, so resolve handles first
Both parameters are numeric identifiers. A pair of @handles has to be turned into ids before this route will accept them, which a profile lookup does one account at a time. Two lookups plus the probe is three requests for a cold pair, $0.0024. The cache pays for itself immediately: identifiers are permanent, so once a pair is stored, every later check on that pair is a single request forever, and a rename on either side leaves the cached entry correct while a handle-keyed one would have broken silently. The parameters are named source_user_id and target_user_id to keep the direction unambiguous at the call site, which matters because a swapped pair returns a confident wrong answer rather than an error.
Why a probe beats reading a roster
The alternative is downloading a follower or following list and searching it. That works, and it is expensive in a specific way: a hit can stop early, but a miss is only certain after the final page, so the cheapest outcome to confirm is the one you were probably hoping for and the expensive one is the common result. Worse, the cost is a function of the other account's popularity, which you do not control and cannot predict from the input. Checking one pair against a large account can mean hundreds of requests for a one-bit answer. It also makes a batch job's runtime unpredictable, since one unusually popular target can take longer to settle than every other pair in the run put together.
The arithmetic, spelled out
Requests are flat priced, so cost is request count and nothing else. A roster needing 250 pages bills 250 times the standard rate, $0.20, and consumes close to half of a minute's request allowance for a single verdict. The probe answers the same question for $0.0008. That is a factor of 250 on one pair. Run it as a nightly job over a thousand pairs and the roster approach is $200 a night against eighty cents, at which point the difference is no longer an implementation detail worth debating. None of that assumes a discount tier or a negotiated rate, because there is only one rate here: every request in either approach bills the same standard amount, which is what reduces the comparison to a straight count of calls.
Checking a thousand pairs at once
Because each check is independent, the job is embarrassingly parallel and the only limits are the account ones: 20 requests in flight at any moment, 600 issued per minute per key. A thousand cached pairs therefore clears in under two minutes of wall clock, and the arithmetic scales linearly from there. Key your results on the id pair rather than the handles so a rerun skips what it already knows, and record the null-relationship cases separately so a later pass can retry them instead of treating them as settled. Nothing about the route is stateful and there is no cursor to lose, so retries are safe and a partial failure can be re-driven for just the affected pairs instead of restarting the batch.
When the question is who do we both know
A different route answers the warm-introduction version. user/followers_you_know returns the accounts that follow a target and that your own logged in account also follows, which is the shared-connection set a profile page shows as social proof. It takes user_id, an optional count defaulting to 20, and a cursor, and returns count, next_cursor and users. Unlike the pair probe it is a Session Read, so it needs the auth_token and ct0 cookies registered against your key, because the answer is defined relative to whose account is asking. That registration is a free call carrying the auth_token and ct0 cookies from a logged in browser session, and it is the one prerequisite the pair probe does not share. The per call price is the same, so the difference between the two is access rather than cost. That rate is $0.0008 a call, per our own rate card.
Five ways to answer does A follow B, and what each one costs you
| Approach | Requests needed | Certain on a negative? |
|---|---|---|
| check_follow_relationship with cached ids | 1 | Yes, immediately |
| The same probe plus two handle lookups | 3, then 1 forever after | Yes, immediately |
| Paging the target's follower roster | One per page, to the end | Only after the final page |
| Paging the source account's following roster | One per page, to the end | Only after the final page |
| followers_you_know for shared connections | One per page of mutuals | Answers a different question |
The Follows endpoints let you follow and unfollow users, and retrieve follower and following lists for any user.
Questions and answers
- Which endpoint answers whether A follows B?
- user/check_follow_relationship. It is a GET taking source_user_id and target_user_id, and it returns one relationship object rather than a collection, so there is nothing to page through. The response arrives at the same speed whether the accounts have a hundred followers or a hundred million, because the pair is evaluated upstream instead of being searched for in a downloaded list. It is also an ordinary user read rather than a session read, so no X login has to be registered against your key first.
- Does the probe tell me which direction the follow goes?
- Both directions arrive in the same object. following describes the source account following the target, and followed_by describes the reverse edge. Reading them as a pair gives four states: no connection, outbound only, inbound only, and mutual when both are true. Reciprocity therefore needs no second request, which is the usual reason people reach for two roster downloads instead. source_id and target_id are echoed back in the same object, which keeps a batch of answers matchable when they return out of order.
- Why do blocking and muting come back null?
- Because those fields are three-valued rather than boolean. In the documented example following is true and followed_by is false, while blocking, blocked_by and muting are all null. A null means X returned no verdict for that field on this pair, which is not the same claim as false. Code that treats null as falsy will record an absent block where the truthful answer is unknown.
- What does the response look like when an account does not exist?
- The relationship object itself comes back null. That is the documented behaviour when either the source or the target fails to resolve, so a null at that level is a lookup failure rather than a verdict on the follow edge. Branch on it explicitly, otherwise a deleted, suspended or renamed account reads forever as an account that merely does not follow the target.
- Can I pass handles instead of numeric IDs?
- No, both parameters are numeric identifiers, so each @name has to be resolved through a profile lookup first. Cache the results. Identifiers are permanent and handles are not, so a stored pair of ids stays correct indefinitely, while a stored pair of handles quietly breaks the first time either account rebrands and gives no signal that it has. The parameter names, source_user_id and target_user_id, also make the direction explicit at the call site, which is worth asserting on.
- Is a block visible through the same call?
- It is. blocking and blocked_by sit in the same object as the follow flags, along with muting. All three can come back null, meaning no verdict rather than no block, so read them as three-valued. Because they arrive in the response you have already paid for, checking them costs nothing extra beyond the field access in your own code. The same is true of can_dm, so one probe covers connection, block state and deliverability together.
- What is the can_dm field for?
- It reports whether a direct message to the target could be delivered at all, which is the check worth running before any outreach attempt rather than after a send fails. Like the other relationship flags it comes back inside the single response, so a workflow that already probes the follow edge gets the deliverability answer for free in the same round trip.
- How many pairs can I check per minute?
- Up to 600 requests a minute on one key, with 20 open concurrently, and each pair is one request once its identifiers are cached. That puts a thousand pairs at under two minutes of wall clock. The work is independent per pair, so it parallelises cleanly, and results keyed on the id pair let a rerun skip everything it already resolved.
- Is the probe really cheaper than downloading a follower list?
- By a wide margin on any sizeable account. A roster that needs 250 pages bills 250 requests, twenty cents, for a single yes or no, and burns a large slice of the per minute allowance doing it. The probe settles the same pair for one request. Over a nightly run of a thousand pairs that is the difference between $200 and eighty cents.
- What is followers_you_know actually for?
- It answers the warm introduction question rather than the yes or no one. Given a target account it returns the accounts that follow the target and that your logged in session also follows, which is the shared connection set a profile shows as social proof. It takes user_id with an optional count, pages with a cursor, and needs a registered session.
Keep reading
Start with $0.50 in free credits
No credit card. Roughly 12,500 tweets to test every endpoint.