Twitter (X) Sentiment Analysis in Python: Pull Posts, Score Them, Compare Two Scorers

This is the how-to: code that pulls posts from X (Twitter), scores them, and turns the scores into numbers you can defend. It is not a dataset page; if you came for ready-made political or election data, our real political data from Twitter (X) page covers what exists and how to get it. Here the data is whatever you search for, pulled live, and the point is the method.
Most tutorials for this keyword still start with Tweepy and a developer account, pick one scorer (TextBlob or VADER in the older ones, a Hugging Face model in the newer ones), print a pie chart, and stop. None of the pages we read reports what happens when you run two scorers on the same posts. That turns out to be the whole story: the share of "positive" posts in your result depends more on the scorer than on the brand you are measuring, and a sentiment number that does not name its scorer is not a finding.
So the page does both halves. First the pipeline: a Python script that searches with /twitter/tweet/advanced_search, pages through results, normalizes the text the way the Cardiff NLP model card recommends, scores with VADER and optionally with cardiffnlp/twitter-roberta-base-sentiment-latest, and aggregates by topic, post type and day. Then the measurement: 480 recent English posts about GitHub Copilot, Cloudflare and Stripe, scored two ways, with the agreement matrix, Cohen's kappa, the reply-versus-original split, and the bill.
What Twitter sentiment analysis means now, and the three scorers you can choose
Sentiment analysis of X posts assigns each post a polarity label (positive, neutral, negative) or a continuous score, then aggregates. Everything interesting happens in two places: which posts you pull (the query, the ordering, the language filter, replies in or out) and which scorer you trust. The pull is a solved problem with one search endpoint; the scorer is a choice with consequences, and this page measures them.
Scorer 1: VADER. A lexicon-and-rule scorer built for social media text (Hutto and Gilbert, ICWSM 2014), MIT-licensed, pip install vaderSentiment, no model download, thousands of posts a second on a laptop. It returns neg, neu, pos and a normalized compound score from -1 to +1; the README's standard thresholds are compound >= 0.05 positive, <= -0.05 negative, neutral between. It understands capitalization, exclamation marks, "but" clauses, some negation, emoticons and slang, which is why it is widely used as the baseline for tweet sentiment.
Scorer 2: a fine-tuned transformer. The Hugging Face model card for cardiffnlp/twitter-roberta-base-sentiment-latest describes it as a RoBERTa-base model trained on about 124M tweets from January 2018 to December 2021 and fine-tuned for sentiment on the TweetEval benchmark, released under CC-BY-4.0, with labels 0 negative, 1 neutral, 2 positive and a recommended preprocessing step that replaces usernames with @user and links with http. It is the default choice in recent tutorials and the model behind the TweetNLP toolkit (the TimeLMs paper, arXiv 2202.03829, describes the training lineage). It needs transformers and torch, about half a gigabyte of weights, and a few milliseconds per post on a CPU.
Scorer 3: a large language model. Prompting an LLM with the post and asking for a label works, handles sarcasm and context best in practitioners' experience, and costs a fraction of a cent per post plus latency. It is also non-deterministic unless you pin temperature and version, and you cannot inspect why it chose a label. Treat it as a third opinion, run it on a sample, and keep one of the two reproducible scorers as your baseline.
We ran VADER on the study sample. The transformer was not run for this page (the collection machine has no PyTorch), so instead of a second black box we wrote a deliberately simple fourth option, a transparent word-list rule (about 50 positive and 55 negative cue words and a handful of emoji, a three-token negation window, label by the sign of the count), and compared it with VADER. The point of that comparison is not that a word list is good; it is to show, with a scorer anyone can read in full, how far the headline share moves when the scorer changes. The RoBERTa code below is ready to run on your machine, and the comparison function accepts any two label lists.
Step 1: pull the posts with one search endpoint
GET /twitter/tweet/advanced_search takes query (X's advanced search operators: quoted phrases, lang:en, from: and -from:, since_time: and until_time: as Unix seconds, min_faves:, -filter:replies), queryType (Latest or Top) and cursor (empty string for the first page). The response has a top-level tweets array, has_next_page and next_cursor. The endpoint documentation says each page returns up to 20 posts, sometimes fewer because ads and non-post items are filtered out. Our three queries returned exactly 20 per page until Stripe ran out of matching posts after five pages.
Fields you will use. Each post object carries id, url, text (the full text, including long-form posts), createdAt in the format Thu Oct 16 12:34:56 +0000 2026, lang, isReply, inReplyToUsername, likeCount, retweetCount, replyCount, quoteCount, viewCount, entities (with urls, hashtags, user_mentions), quoted_tweet, retweeted_tweet, and author with userName, name, followers, isBlueVerified. The engagement counts are top-level fields on the post, not nested in a metrics object.
Query design decides your sample. lang:en keeps the scorers in their trained language. -from:stripe removes the brand's own posts, which are reliably positive and would inflate the share. Latest gives you the most recent matching posts in reverse time order, which is what you want for a time series and what we used; Top returns X's engagement-ranked selection, which skews toward the loud. Add -filter:replies if you want only original posts, or keep replies and split on isReply later as the study does.
Cost. Tweet endpoints bill 15 credits per post returned, $0.15 per 1,000 posts, with a 15-credit minimum per request, so a full page is $0.003 and a search that finds nothing costs $0.00015. A 10-page pull is about $0.03. No X account or developer approval is involved: the key goes in an X-API-Key header.
import os, requests
BASE = "https://api.twitterapi.io"
H = {"X-API-Key": os.environ["TWITTERAPI_IO_API_KEY"]}
def search(query, pages=10, query_type="Latest"):
"""Page /twitter/tweet/advanced_search. Each page returns up to 20 posts (ads are filtered out,
so sometimes fewer) and bills 15 credits per post returned ($0.15 per 1,000)."""
out, cursor = [], ""
for _ in range(pages):
r = requests.get(f"{BASE}/twitter/tweet/advanced_search", headers=H,
params={"query": query, "queryType": query_type, "cursor": cursor}, timeout=30)
r.raise_for_status()
body = r.json()
out.extend(body.get("tweets") or [])
if not body.get("has_next_page") or not body.get("next_cursor"):
break
cursor = body["next_cursor"]
return out
posts = search('"Stripe" lang:en -from:stripe', pages=10)
print(len(posts), "posts")
t = posts[0]
print(t["text"][:80], "|", t["lang"], "| reply:", t["isReply"], "| likes:", t["likeCount"],
"|", t["createdAt"], "| @" + t["author"]["userName"])Step 2: clean the text and score it with VADER
Two normalizations before scoring. Replace every @handle with @user and every link with http: this is the preprocessing the Cardiff NLP model card recommends, it removes tokens that carry no sentiment, and applying the same cleaning to both scorers keeps the comparison fair. Do not lowercase or strip punctuation for VADER; it uses capitalization and exclamation marks as intensity signals.
VADER's polarity_scores returns a dictionary; keep compound and apply the README thresholds. Keep the raw compound as well as the label, because you will want to tune the neutral band later: a wider band (for example 0.2) moves many of VADER's weak positives into neutral, which is exactly the region where the two scorers disagreed in our sample.
On our 480 posts VADER took well under a second. Its mean compound was +0.302 for GitHub Copilot, +0.190 for Cloudflare and +0.174 for Stripe, and its label shares are in the table further down.
# pip install vaderSentiment (pure Python, no model download)
from vaderSentiment.vaderSentiment import SentimentIntensityAnalyzer
sia = SentimentIntensityAnalyzer()
def clean(text):
"""Same normalization the cardiffnlp model card uses: @handles -> @user, links -> http."""
out = []
for tok in text.split():
if tok.startswith("@") and len(tok) > 1:
tok = "@user"
elif tok.startswith("http"):
tok = "http"
out.append(tok)
return " ".join(out)
def vader_label(text):
c = sia.polarity_scores(clean(text))["compound"] # -1 .. +1
# thresholds from the VADER README
return ("positive" if c >= 0.05 else "negative" if c <= -0.05 else "neutral"), c
for t in posts[:5]:
label, c = vader_label(t["text"])
print(f"{label:8} {c:+.3f} {t['text'][:70]!r}")Step 3: score with the cardiffnlp RoBERTa model (code to run on your machine)
The model card's recipe is a transformers pipeline with the model name; the first call downloads the weights. Pass truncation=True because long-form posts can exceed the 512-token input limit, and batch the texts so memory stays flat. The pipeline returns the top label per text (positive, neutral, negative); pass top_k=None if you also want the three probabilities, which lets you set your own confidence floor the same way the compound threshold does for VADER.
We did not run this model for the study on this page, so no RoBERTa numbers appear here. Expect it to be slower (milliseconds per post on CPU, far faster on a GPU) and, in the TweetEval results the model card points to, more accurate than lexicon methods on tweet sentiment, especially on sarcasm, slang and posts whose polarity comes from context rather than from a loaded word. Run it with the --roberta flag in the full script at the end and feed its labels into the same comparison function; the agreement matrix will tell you where it and VADER part ways on your topic.
If you would rather score with an LLM, the shape is the same: a function that maps a list of cleaned texts to a list of labels. Pin the model version and temperature, send the label set in the prompt, and keep VADER or RoBERTa as the reproducible baseline you compare against.
# pip install transformers torch (downloads about 500 MB of weights on first use)
# Not run for the study on this page; this is the model card's recipe, batched for a list of posts.
from transformers import pipeline
clf = pipeline("sentiment-analysis",
model="cardiffnlp/twitter-roberta-base-sentiment-latest",
truncation=True, max_length=512)
texts = [clean(t["text"]) for t in posts] # clean() from the VADER step
labels = []
for i in range(0, len(texts), 32): # batch to keep memory flat
for res in clf(texts[i:i + 32]):
labels.append(res["label"].lower()) # "positive" | "neutral" | "negative"
from collections import Counter
share = Counter(labels)
print({k: round(100 * v / len(labels), 1) for k, v in share.items()})What 480 posts about three software brands showed under two scorers
We pulled 10 pages of Latest results for "GitHub Copilot" lang:en, "Cloudflare" lang:en -from:Cloudflare and "Stripe" lang:en -from:stripe (25 requests; Stripe ran out after 5 pages), 480 posts in total, all lang: en, median text length 195 characters, 28.1% with a link, 47.9% replies. GitHub Copilot's 200 posts spanned 3 days; the Cloudflare and Stripe samples each fit within a single day, which tells you how fast Latest moves for a well-known brand.
The scorer moved the positive share by about 37 points; the topic moved it by 5. Under VADER the three brands sit between 50.0% and 55.5% positive; under the word list between 15.0% and 18.5%. Any reader comparing a VADER number from one report with a different scorer's number from another would conclude that sentiment collapsed. It did not; the ruler changed.
Even the ranking of brands by negativity depends on the scorer. VADER puts Cloudflare most negative (24.5%) and GitHub Copilot least (12.0%); the word list puts Stripe most negative (15.0%) with the other two nearly tied. The Cloudflare sample is 64.0% replies, GitHub Copilot's 29.5%, which is one reason the two topics behave differently under any scorer.
Where the disagreement lives. Of 249 disagreements, 215 (86.3%) are posts VADER labeled positive or negative where the word list found no cue word at all: VADER's larger lexicon and its intensity rules pick up mild signals ("finally", "works", exclamation marks) that a short list misses. Only 30 posts (6.2%) received opposite signs. So the two scorers rarely contradict each other; they disagree about how much evidence is enough to leave neutral. That is also the first thing to test when you add the RoBERTa model: widen VADER's neutral band and watch the agreement rise.

Step 4: aggregate by post type, and measure how much the two scorers agree
Three aggregations cover most questions. Share of positive / neutral / negative per topic, which is the headline above. The same split by post type (isReply), by whether the post carries a link (entities.urls), or by day (parse createdAt with %a %b %d %H:%M:%S %z %Y), which is where the stories hide. And, whenever you use more than one scorer, a 3x3 agreement matrix with Cohen's kappa, which tells you how much of your headline is the scorer talking. The agreement() function in the full script at the end of this page computes all three from any two label lists.
Kappa corrects raw agreement for the agreement two labelers would reach by chance given their own label distributions. Two scorers that both call almost everything neutral will agree often and still have a kappa near zero. Values above 0.6 are usually read as substantial agreement and values around 0.2 to 0.4 as fair; ours, below, is in the fair range.
Splitting by isReply is free once you have the field, and it is where the two scorers told different stories. Under VADER, replies and originals are nearly identical. Under the word list, original posts were twice as negative as replies (16.8% vs 8.3%), because complaints about outages, bugs and pricing tend to be standalone posts with explicit cue words, while replies are shorter and more often conversational.
Posts carrying a link were less negative under VADER (14.8% vs 20.9%) and more often neutral, consistent with link posts being announcements and articles rather than reactions. If your question is "what do people think", consider reporting link-free posts separately.
The agreement matrix below is the single most useful artifact of a two-scorer run. Read it row by row: of the 261 posts VADER called positive, the word list agreed on 73, left 164 neutral and called 24 negative. The neutral row is the calm one: when VADER finds nothing, the word list almost never does either (4 exceptions out of 127).

What the sample cost, and what it cannot tell you
The whole study was 25 requests returning 480 posts: 7,200 credits, $0.072 at twitterapi.io's 15 credits per post returned. The official X API's pay-per-usage rate card (docs.x.com/x-api/getting-started/pricing) prices a post read at $0.005, so the same 480 reads would be $2.40, after creating a developer account and saving a card (which currently comes with $20 of credits). Per 1,000 posts that is $5 against $0.15, about 33x.
Sign-up on twitterapi.io includes up to $1 of free credits (at most 100,000 credits, enough for about 6,600 posts) for new accounts that pass the automatic bot check, one per person or network, expiring after 31 days. Rate limits are 0.2 requests a second before the first payment and 20 after it; the script above stays far below either.
What this sample cannot show. It is one pass per topic on one day (three days for GitHub Copilot), in Latest order, English only, with the brands' own accounts excluded for two of three topics. It says nothing about how sentiment toward these brands moves over weeks, nothing about non-English markets, and nothing about the RoBERTa model, which was not run. Three topics is enough to show that the scorer dominates the topic; it is not a benchmark of scorers, for which you would need human-labeled posts, and the TweetEval benchmark referenced on the model card is the place to look for that. The word-list rule is intentionally minimal and is not a recommendation.
Mistakes that quietly ruin a Twitter sentiment analysis
Not naming the scorer and thresholds. "54% positive" and "17% positive" were both true of the same posts above. Every chart title should carry the scorer and, for VADER, the neutral band.
Scoring the brand's own posts. Exclude them with -from:brand or filter on author.userName, or your positive share includes the marketing team.
Mixing languages. lang:en in the query, and check the lang field anyway. VADER's lexicon and this RoBERTa model are English; a Spanish post scored by either is noise.
Using Top for a time series. Top is an engagement-ranked selection that changes as engagement changes. Use Latest with since_time: and until_time: windows for anything with a time axis.
Treating reposts as opinions. A retweeted_tweet object means the post is a repost; count the original once or weight by retweetCount deliberately, not accidentally.
Ignoring replies, or not separating them. Replies were about half this sample. Report them separately or filter with -filter:replies; the two groups differed under one of our two scorers.
Stripping punctuation and case before VADER. Both are intensity signals in VADER's rules; clean handles and links, leave the rest.
Paging without de-duplication. Under Latest new posts arrive while you page, so a post can appear on two consecutive pages. The script keeps a seen set of id values.
Reading long posts through a 280-character lens. text holds the full text of long-form posts. Truncate for the model's 512-token limit, not at 280.
How we collected this
Question: on the same recent English posts about three software brands, what share comes out positive / neutral / negative under two different scorers, how often do the scorers agree, does the answer differ for replies and original posts, and what does the sample cost at twitterapi.io prices versus the official X API rate card?
Query: /twitter/tweet/advanced_search with queryType=Latest for "GitHub Copilot" lang:en, "Cloudflare" lang:en -from:Cloudflare and "Stripe" lang:en -from:stripe, up to 10 pages each, through a rate-limited, read-only client.
Window and sample: the most recent matching posts at collection time, collected in 2026; 25 requests, 480 posts (200 GitHub Copilot, 200 Cloudflare, 80 Stripe, where the search ran out after five pages). GitHub Copilot's posts spanned 3 days, the other two topics one day each.
Method: text normalized by replacing @handles with @user and links with http. Scorer A: VADER (vaderSentiment 3.3.2), compound at or above 0.05 positive, at or below -0.05 negative. Scorer B: a transparent word-list rule written for this study (about 50 positive and 55 negative cue words plus a few emoji, a three-token negation window, label by the sign of positive minus negative counts), kept in the study's analysis script. The cardiffnlp RoBERTa model was not run; no numbers for it are reported. Agreement is the share of posts with identical 3-class labels; kappa is Cohen's kappa over the 3x3 matrix.
What the sample cannot show: one day per topic, Latest ordering, English only, three brands, no human-labeled ground truth, so this is a comparison of scorers' behavior on real posts, not an accuracy benchmark.
Privacy: the posts are by private individuals, so the raw pages were kept only in temporary storage and discarded after analysis. The study folder holds aggregates and one de-identified row per post (scores, labels, text length, link and reply flags, language, UTC hour) with no post id, URL, text, author id or handle. The brands named are organizations.
"""twitter_sentiment.py - Twitter (X) sentiment analysis in Python, end to end:
search -> page -> clean -> score (VADER, optionally the cardiffnlp RoBERTa model) -> aggregate.
pip install requests vaderSentiment
pip install transformers torch # optional, for --roberta
export TWITTERAPI_IO_API_KEY=... # twitterapi.io dashboard; no X developer account needed
python twitter_sentiment.py '"Stripe" lang:en -from:stripe' --pages 10
python twitter_sentiment.py '"GitHub Copilot" lang:en' --pages 10 --roberta
Cost: 15 credits per post returned ($0.15 per 1,000); 10 pages of 20 posts is about $0.03.
"""
import argparse
import csv
import json
import os
import sys
from collections import Counter, defaultdict
from datetime import datetime
import requests
from vaderSentiment.vaderSentiment import SentimentIntensityAnalyzer
BASE = "https://api.twitterapi.io"
H = {"X-API-Key": os.environ["TWITTERAPI_IO_API_KEY"]}
LABELS = ["positive", "neutral", "negative"]
def search(query, pages=10, query_type="Latest"):
"""Page /twitter/tweet/advanced_search: top-level tweets[], has_next_page, next_cursor."""
out, cursor, seen = [], "", set()
for _ in range(pages):
r = requests.get(f"{BASE}/twitter/tweet/advanced_search", headers=H,
params={"query": query, "queryType": query_type, "cursor": cursor}, timeout=30)
r.raise_for_status()
body = r.json()
for t in body.get("tweets") or []:
if t["id"] not in seen: # pages can overlap slightly under Latest ordering
seen.add(t["id"])
out.append(t)
if not body.get("has_next_page") or not body.get("next_cursor"):
break
cursor = body["next_cursor"]
return out
def clean(text):
"""@handles -> @user, links -> http (the cardiffnlp model card's preprocessing)."""
out = []
for tok in text.split():
if tok.startswith("@") and len(tok) > 1:
tok = "@user"
elif tok.startswith("http"):
tok = "http"
out.append(tok)
return " ".join(out)
def score_vader(texts):
sia = SentimentIntensityAnalyzer()
labels = []
for s in texts:
c = sia.polarity_scores(s)["compound"]
labels.append("positive" if c >= 0.05 else "negative" if c <= -0.05 else "neutral")
return labels
def score_roberta(texts):
from transformers import pipeline # imported here so the script runs without torch
clf = pipeline("sentiment-analysis", model="cardiffnlp/twitter-roberta-base-sentiment-latest",
truncation=True, max_length=512)
labels = []
for i in range(0, len(texts), 32):
labels.extend(r["label"].lower() for r in clf(texts[i:i + 32]))
return labels
def shares(labels):
n = len(labels) or 1
c = Counter(labels)
return {k: round(100 * c[k] / n, 1) for k in LABELS}
def agreement(a, b):
n = len(a)
matrix = {x: {y: 0 for y in LABELS} for x in LABELS}
for x, y in zip(a, b):
matrix[x][y] += 1
p_o = sum(matrix[k][k] for k in LABELS) / n
ra, rb = Counter(a), Counter(b)
p_e = sum(ra[k] * rb[k] for k in LABELS) / (n * n)
return round(100 * p_o, 1), round((p_o - p_e) / (1 - p_e), 3), matrix
def main():
ap = argparse.ArgumentParser()
ap.add_argument("query")
ap.add_argument("--pages", type=int, default=10)
ap.add_argument("--roberta", action="store_true", help="also score with the cardiffnlp RoBERTa model")
ap.add_argument("--csv", default="scored_posts.csv")
args = ap.parse_args()
posts = search(args.query, args.pages)
texts = [clean(t.get("text") or "") for t in posts]
vader = score_vader(texts)
print(f"{len(posts)} posts, {len(posts) * 15:,} credits (${len(posts) * 15 / 100000:.4f})")
print("VADER ", shares(vader))
roberta = None
if args.roberta:
roberta = score_roberta(texts)
print("RoBERTa ", shares(roberta))
pct, kappa, matrix = agreement(vader, roberta)
print(f"agreement {pct}% kappa {kappa}")
for row in LABELS:
print(f" VADER {row:8}", [matrix[row][c] for c in LABELS])
# sentiment by post type and by day, using the real tweet fields
by_type, by_day = defaultdict(list), defaultdict(list)
for t, lab in zip(posts, vader):
by_type["reply" if t.get("isReply") else "original"].append(lab)
day = datetime.strptime(t["createdAt"], "%a %b %d %H:%M:%S %z %Y").strftime("%Y-%m-%d")
by_day[day].append(lab)
for k, labs in by_type.items():
print(f"{k:9} n={len(labs):4}", shares(labs))
for day in sorted(by_day):
print(f"{day} n={len(by_day[day]):4}", shares(by_day[day]))
with open(args.csv, "w", newline="") as f:
w = csv.writer(f)
w.writerow(["id", "createdAt", "author", "lang", "isReply", "likeCount", "vader", "roberta", "text"])
for i, t in enumerate(posts):
w.writerow([t["id"], t["createdAt"], (t.get("author") or {}).get("userName"), t.get("lang"),
t.get("isReply"), t.get("likeCount"), vader[i], roberta[i] if roberta else "", t.get("text")])
print("wrote", args.csv)
if __name__ == "__main__":
main()Questions readers ask
Do I need an X (Twitter) developer account to do sentiment analysis on tweets?
Not for reading. On twitterapi.io the search endpoint /twitter/tweet/advanced_search needs only an API key in the X-API-Key header, at 15 credits per post returned ($0.15 per 1,000). The official X API route requires a developer account, a saved card and $0.005 per post read under its pay-per-usage rate card.
Which is better for Twitter sentiment analysis, VADER or the cardiffnlp RoBERTa model?
For accuracy on tweets, the fine-tuned transformer (cardiffnlp/twitter-roberta-base-sentiment-latest, trained on about 124M tweets and fine-tuned on TweetEval per its model card) is the stronger scorer, especially on sarcasm, slang and context. VADER is instant, dependency-free, inspectable and good enough for a baseline or for very large volumes. Run both on a sample and look at the agreement matrix before you trust either on your topic.
How many tweets do I need for a sentiment analysis?
A few hundred per topic gives stable shares to within a few points; our 80-post Stripe sample already landed close to the 200-post topics under both scorers. For a time series, aim for at least 100 to 200 posts per time bucket so a handful of viral posts cannot swing a day. At $0.15 per 1,000 posts, 10,000 posts is $1.50.
How do I get historical tweets for a sentiment time series?
Use the since_time: and until_time: operators (Unix seconds) in the advanced_search query with queryType=Latest, one window per day or week, and page each window until has_next_page is false. Score each window the same way and plot the positive and negative shares; keep the scorer and thresholds fixed across windows.
Why does my positive share change so much between tools?
Because scorers draw the neutral line in different places. On our 480 posts VADER called 54.4% positive and a transparent word-list rule 16.9%; 86.3% of their disagreements were posts VADER scored where the word list found no cue, and only 6.2% got opposite signs. Report the scorer, its version and its thresholds with every number, and never compare shares produced by different scorers.
Is there a Twitter sentiment analysis dataset I can download instead of pulling tweets?
Labeled benchmark sets exist for training and evaluating scorers; the TweetEval benchmark referenced on the Cardiff NLP model card is the standard one for this model family. For measuring sentiment about your own topic you still need current posts, which is a ten-page search per topic with the script on this page. Our dataset export guide covers saving pulls as CSV or Parquet for reuse.
Should I include replies in a Twitter sentiment analysis?
Decide explicitly. Replies were 47.9% of our sample, and under one of our two scorers original posts were twice as negative as replies. Keep the isReply field, report the two groups separately, or exclude replies with -filter:replies in the query if your question is about standalone opinions.
Can I run sentiment analysis on non-English tweets?
Not with the two scorers on this page as-is: VADER's lexicon and the twitter-roberta-base-sentiment-latest model are English. Filter with lang:xx in the query and check the lang field, then look for a scorer trained on that language (the Cardiff NLP organization on Hugging Face publishes multilingual tweet sentiment models; check the model card for the language list) or use an LLM with the language stated in the prompt.
Continue
- Hugging Face model card: cardiffnlp/twitter-roberta-base-sentiment-latest (training data, labels, preprocessing, license)
- VADER sentiment repository and README (compound thresholds, MIT license, ICWSM 2014 citation)
- TimeLMs: Diachronic Language Models from Twitter (the model's training lineage)
- twitterapi.io API reference: advanced search (query, queryType, cursor; tweets, has_next_page, next_cursor)
- X API pay-per-usage pricing ($0.005 per post read), the official and pricier route
- X (Twitter) API hub: endpoints, pricing and guides
- Real political data from Twitter (X): datasets and how to get them
- Twitter (X) advanced search API guide: every operator
- Export a Twitter (X) dataset for research as CSV or Parquet
- LangChain with the Twitter (X) API: scoring posts with an LLM
- AI for crypto trading with Twitter (X) sentiment signals
- Pay-per-call pricing: 15 credits per post returned
- API reference for advanced_search and tweet fields
- 01How to Get a Grok API Key: Setup, Pricing and Live X Data for the xAI API
- 02The X Developer Portal (X Developer Console) for the Twitter (X) API: Sign-Up, Apps and Keys, Billing, Limits, and When You Do Not Need It
- 03SKILL.md explained: the Agent Skills format, X's own skill, and a Twitter (X) API skill you can install
- 04Twitter (X) Auto Post: Schedule in X, RSS or Sheets to X, and Posting from Code
- 05How to Search and Track Mentions on Twitter (X), Including Someone Else's
- 06Twitter (X) Analysis API — A Developer's Guide
- 07Twitter (X) Competitor Analysis — A Developer's API Guide
- 08Twitter (X) API Cost — A Straight Answer
Stop reading. Start building.
Starter credits cover real testing on real data. Google sign-in, no card, no application queue.
Get an API key