PYTHON
Twitter API Python client
The Python client for TwitterAPIs is a requests session with one header on it. There is no package to install beyond requests itself, no OAuth flow, and no token to refresh, because the key is a static Bearer credential that goes on every call. Set it once on a session, write one helper that builds the URL and decodes the JSON, and you have covered every read endpoint the API serves.
pip install requests
1import os2import requests34BASE = "https://api.twitterapis.com"5KEY = os.environ["TWITTERAPIS_KEY"]67session = requests.Session()8session.headers["Authorization"] = f"Bearer {KEY}"910def get(path, **params):11 """Call any read endpoint. path is the docs path, e.g. 'user/info'."""12 res = session.get(f"{BASE}/twitter/{path}", params=params, timeout=30)13 res.raise_for_status()14 return res.json()1516# Resolve a handle to the permanent numeric user ID17profile = get("user/info", username="naval")18print(profile["user"]["id"], profile["user"]["followers_count"])1920# Search posts, then page with the returned cursor21page = get("tweet/advanced_search", query="from:naval min_faves:500")22for tweet in page["tweets"]:23 print(tweet["id"], tweet["text"][:80])Paginating a follower list
Cursor-paginated endpoints return a next cursor alongside the page of results. Wrapping that in a generator keeps the cursor logic in one place, so the calling code is an ordinary loop that never has to know pagination exists.
1def paginate(path, **params):2 """Yield every page of a cursor-paginated endpoint."""3 cursor = None4 while True:5 if cursor:6 params["cursor"] = cursor7 page = get(path, **params)8 yield page9 cursor = page.get("next_cursor")10 if not cursor:11 return1213total = 014for page in paginate("user/followers", username="naval"):15 total += len(page["followers"])16print(total)Retries that do not cost you money
Because billing is per call, a retry policy is a spending policy. Retry a server error, never retry a client error, and put a timeout on every request so a hung connection cannot stall a job silently.
1from requests.adapters import HTTPAdapter2from urllib3.util.retry import Retry34# Retry 5xx with backoff. Never retry a 4xx: a 401 is a bad key and a 4045# is an account that does not exist, and repeating either just costs money.6retry = Retry(7 total=3,8 backoff_factor=0.5,9 status_forcelist=[500, 502, 503, 504],10 allowed_methods=["GET"],11)12session.mount("https://", HTTPAdapter(max_retries=retry))1314try:15 profile = get("user/info", username="naval")16except requests.HTTPError as exc:17 if exc.response.status_code == 401:18 raise SystemExit("Key rejected. Check TWITTERAPIS_KEY.")19 raiseWhere to go next
The same client shape in Node and Go, or all six languages together on the language clients page. For the endpoint you are about to call, the search API and followers API pages cover parameters and response shapes, and the Tweepy comparison covers what changes if you are moving off it.
Frequently Asked Questions
No. The client above is the whole thing: a requests session with one header set on it, plus a function that builds the URL and decodes the JSON. Because the header never changes and there is no token to refresh, attaching it to the session once means every subsequent call is authenticated without you thinking about it again.
Read the cursor field from each response and pass it back as the cursor parameter on the next call, stopping when the response no longer carries one. The generator on this page does exactly that, so a caller writes an ordinary for loop and never touches cursor handling. Each page is one billed call, so wrap the loop in a page limit when you are testing.
Read it from the environment, as the client above does with os.environ. A key committed to a repository is a key you must rotate, and a key interpolated into a notebook cell tends to end up in the notebook output. For a scheduled job, put it in whatever secret store your runner already has rather than in the job definition.
Tweepy is a wrapper around the official X API, so it inherits that API's pricing, its OAuth flow and its rate windows. It cannot be pointed at a different provider by swapping a base URL, because the endpoints and response shapes differ. Migrating usually means replacing the handful of Tweepy calls you actually use with the get function above, which is less work than the word migration suggests.
Only if you are fetching many independent resources at once. For a sequential job, requests is simpler and the bottleneck is the network rather than the client. If you do need concurrency, httpx with an AsyncClient takes the same header and the same URLs, so the client function converts almost line for line.
The same as from anywhere else: $0.0008 for a standard read call, billed per call with no plan minimum. The client makes no hidden requests, so the number of calls you are billed for is the number of times your code calls get. Pagination is the case to watch, because one loop over a large follower list is one call per page.