TwitterAPIs Docs
API ReferenceUser Reads

Twitter Follow Relationship Batch API | 100 Pairs a Call

Check the directional relationship between one fixed account and up to 100 others in one request: one source against up to 100 targets, or one target against up to 100 sources. Each item carries the same relationship object GET /user/check_follow_relationship returns, read from the source's side: whether the source follows the target (following) and the target follows the source (followed_by), plus block, mute, and DM-eligibility flags. Results come back in request order, each with its relationship object or an error code. Cost: $0.0008 per billed item.

Check Follow Relationship Batch checks one account against up to 100 others in a single request. Fix one source and list up to 100 targets, or fix one target and list up to 100 sources. Each result carries the same relationship object GET /user/check_follow_relationship returns, always read from the source's side: following means the source follows the target, followed_by means the target follows the source. $0.0008 per billed pair.

GET
/user/check_follow_relationship/batch

Authorization

bearerAuth
AuthorizationBearer <token>

Pass your API key as a bearer token on every request: Authorization: Bearer <API_KEY>.

In: header

Query Parameters

source_user_id?string

Numeric ID of the single source account, when the list is targets. Used as sent, with no lookup; wins over source_username when both are sent.

source_username?string

Handle of the single source account, when the list is targets. Resolved to its numeric ID first; an unknown handle returns a 404 and nothing is billed.

target_usernames?string

Comma-separated target handles, with or without the leading @, checked against the single source. Send this or target_user_ids, never both, and never together with a source list. Between 1 and 100 distinct entries after duplicates are removed (handles compare case-insensitively). A malformed handle returns a 400 for the whole request.

target_user_ids?string

Comma-separated numeric target IDs, as an alternative to target_usernames. Same 1 to 100 limit. Leading zeros are ignored for duplicates (012 and 12 are one account); results echo each ID as you sent it.

target_user_id?string

Numeric ID of the single target account, when the list is sources. Used as sent, with no lookup; wins over target_username when both are sent.

target_username?string

Handle of the single target account, when the list is sources. Resolved to its numeric ID first; an unknown handle returns a 404 and nothing is billed.

source_usernames?string

Comma-separated source handles, with or without the leading @, each checked against the single target. Send this or source_user_ids, never both, and never together with a target list. Between 1 and 100 distinct entries after duplicates are removed (handles compare case-insensitively). A malformed handle returns a 400 for the whole request.

source_user_ids?string

Comma-separated numeric source IDs, as an alternative to source_usernames. Same 1 to 100 limit. Leading zeros are ignored for duplicates (012 and 12 are one account); results echo each ID as you sent it.

fields?string

Comma-separated list of dotted field paths to keep in the response (one parameter, never repeated), applied to every object in the returned lists and to nested objects (for example fields=id,text,author.username; the list's own name may prefix a path, fields=tweets.id). A path prefix that lands on an array applies to each element (media.media_url_https). Pagination and envelope keys (next_cursor, cursor, has_more, count, partial, error, message, reason) are always kept. Unknown paths are ignored; up to 50 paths of up to 6 segments. Projection never changes status codes or billing, and applies to data reads only: account, feedback, session status, monitor (other than deliveries), webhook and stream-compat reads return their bodies unchanged.

compact?string

Set to 1 (or true) to apply the built-in compact preset: ids, url, text, created_at, lang, the engagement counts, the is_retweet/is_reply/is_quote flags, conversation ids, the author's id/username/name/followers_count/verification, and the quoted or retweeted tweet's id/url/author username. The preset trims what it recognises and leaves alone what it would empty, so it never turns a body into {}. Combine with fields to add paths on top of the preset. Typically cuts a tweet page by 70 to 90 percent for LLM tool callers.

Response Body

application/json

application/json

application/json

application/json

application/json

application/json

application/json

curl -X GET "https://example.com/user/check_follow_relationship/batch?source_user_id=102812444&source_username=stripe&target_usernames=nasa%2Copenai&target_user_ids=11348282%2C4398626122&target_user_id=102812444&target_username=stripe&source_usernames=nasa%2Copenai&source_user_ids=11348282%2C4398626122&fields=id%2Ctext%2Ccreated_at%2Cauthor.username&compact=1"
{  "source_user_id": "102812444",  "requested": 3,  "served": 2,  "billed_items": 2,  "results": [    {      "target_username": "nasa",      "relationship": {        "source_id": "102812444",        "target_id": "11348282",        "following": false,        "followed_by": true,        "blocking": null,        "blocked_by": null,        "muting": null,        "can_dm": false      }    },    {      "target_username": "openai",      "relationship": {        "source_id": "102812444",        "target_id": "4398626122",        "following": true,        "followed_by": true,        "blocking": null,        "blocked_by": null,        "muting": null,        "can_dm": true      }    },    {      "target_username": "no_such_handle",      "error": "not_found"    }  ]}
{  "error": "bad_request",  "message": "Send a list on ONE side only: target_usernames/target_user_ids or source_usernames/source_user_ids."}
{  "error": "unauthorized",  "message": "Missing or invalid x-api-key header."}
{  "error": "insufficient_credits",  "message": "Not enough credits for 3 items. Top up, or send a smaller batch."}
{  "error": "not_found",  "message": "Could not resolve @no_such_handle."}
{  "error": "batch_in_progress",  "message": "One batch per API key at a time. Retry when the running batch returns. Not billed."}
{  "error": "upstream_unavailable",  "message": "No item could be fetched right now. This is temporary; retry shortly.",  "retry_after_seconds": 5}

Pricing

UnitPrice
Per billed item$0.0008
Per 1,000 billed items$0.80

Billed per pair, not per call: each pair X answered with a relationship costs $0.0008, so a 100-pair batch costs at most $0.08. A pair that came back not_found (an account does not exist or a listed handle did not resolve) is free, as the unknown-account 404 on Check Follow Relationship is. Pairs X refused (forbidden) and pairs that failed on our side (rate_limited, unavailable) are free. The balance is checked for every pair before any pair is checked, so a key that cannot cover the whole list gets a 402 and nothing is fetched or billed. An unknown handle on the fixed side returns a 404 before any pair is checked, and nothing is billed.

When to use

Use it to check a whole list against one account at once, for example which of 100 shortlisted creators already follow your brand: fix the brand as target_username and list the creators as source_usernames. For a single pair, call Check Follow Relationship.

FAQ

Which side can be the list?

Either side, but only one. Send one source (source_user_id or source_username) with up to 100 targets (target_usernames or target_user_ids), or one target (target_user_id or target_username) with up to 100 sources (source_usernames or source_user_ids). A list on both sides returns a 400. Duplicates are removed first (handles compare case-insensitively, a leading @ is ignored, and 012 and 12 are one ID), so the limit applies to distinct accounts. A single target_user_id or target_username on the list side (or source_ on the source list side) is refused with a 400, never silently dropped.

Which direction do following and followed_by read?

Always from the source's side, whichever side is the list. following is true when the source follows the target, and followed_by is true when the target follows the source. To learn which of your creators follow a brand, fix the brand as target_username, list the creators as source_usernames, and read following on each item.

Am I billed for pairs that come back not_found?

No. not_found means one of the two accounts does not exist or a listed handle did not resolve, so there is no relationship to report; like the unknown-account 404 on Check Follow Relationship, it is not billed. Pairs X refused (forbidden) are free, and retrying them will not help. Pairs that failed on our side (rate_limited, unavailable) are free and safe to retry. billed_items in the response tells you exactly how many pairs the call charged.

What if the fixed account does not exist?

An unknown handle on the fixed side returns a 404 with error not_found before any pair is checked, and nothing is billed. A numeric ID on the fixed side is used as sent with no lookup, and when both an ID and a username are sent for the fixed side, the ID wins. The response echoes the fixed account's numeric ID at the top level as source_user_id or target_user_id.

How do fields and compact work on a batch?

They apply inside each item's relationship object, exactly as on Check Follow Relationship: fields=following or fields=relationship.following keeps that field. A path that starts with results matches nothing and returns relationship: {} (still billed). The fixed account's ID, requested, served, billed_items, and each item's key and error are never trimmed.

What do the 429 and 503 errors mean here?

A 429 with error batch_in_progress means a batch on the same API key is still running; it carries a Retry-After header of 5 seconds and is not billed. The per-key rate limit is also a 429, and resolving a fixed-side handle can return a retryable 429 or 503. A 503 with error upstream_unavailable means no pair could be checked (Retry-After 5 seconds); a 503 with error billing_unavailable means the balance check could not run. Nothing is billed on either 503.

Can I run several batches at once?

One batch per API key runs at a time, shared with User About Batch and Audience Summary. A second batch sent while the first is still running gets a 429 with error batch_in_progress and is not billed, so send the next batch when the previous one returns. Pairs a batch did not start (it stops early after repeated upstream failures, a long run, or a disconnect) come back as unavailable, are not billed, and are safe to retry.