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.
Authorization
bearerAuth Pass your API key as a bearer token on every request: Authorization: Bearer <API_KEY>.
In: header
Query Parameters
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.
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.
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.
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.
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.
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.
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.
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.
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.
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
| Unit | Price |
|---|---|
| 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.
Related endpoints
- Check Follow Relationship: Check whether one user follows another.
- User About Batch: Fetch the About object for up to 100 accounts in one request.
- User Followers: Page through the accounts that follow a user.
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.
User About Batch GET
Read the full About object for up to 100 accounts in one request: the same object GET /user/user_about returns, including X's "About this account" transparency panel (account country, how the account was created, and username-change history). Results come back in request order, each with its about object or an error code. Cost: $0.0008 per billed item.
Audience Summary GET
Sample up to 100 of an account's followers, or of a tweet's retweeters, read each sampled account's About object, and return a country histogram of account_based_in with each country's share of the accounts whose country is known. The response also counts how many sampled accounts show each of seven documented profile signals and how many show three or more (likely_bot_count), a heuristic over profile fields rather than a verdict on any account. Cost: $0.0008 per billed item.