Detect X Paid Promotions with TwexAPI
Organic spread and paid promotion can look similar in a timeline. For market research, campaign review, or competitor monitoring, the distinction matters: a post amplified by users should not be interpreted the same way as a post amplified by paid distribution.
TwexAPI returns is_paid_promotion on tweet objects. With POST /twitter/tweets/lookup, you can check a set of tweet IDs, label which posts are marked as paid promotions, and store raw responses, candidate sources, and engagement metrics together.
Why This Field Matters
Why this field matters means it helps separate content performance into paid exposure and organic discussion. Manual timeline review can miss promoted posts that are no longer shown to you; batch lookup lets the decision live in a stable data workflow.
- Paid-promotion marker: When
is_paid_promotion === true, label the post as a paid-promotion sample. - Batch processing: Submit candidate tweet IDs as an array and receive tweet objects in one response.
- Evidence storage: Save raw JSON, candidate source, fetch time, and engagement metrics so campaign reviews can explain the label.
- Research loop: Collect candidate IDs with Advanced Search, campaign lists, or manual samples, then verify the promotion marker with lookup.
Important: false or a missing value should not be written as "definitely no paid distribution." A safer label is "not marked paid in this response." That avoids mistaking platform visibility, sampling, or missing fields for certainty.
Technical Implementation: Batch Labeling
Batch labeling means submitting a tweet ID array to POST /twitter/tweets/lookup and reading is_paid_promotion from each tweet object in response data. This flow does not discover every ad; it turns candidate tweet IDs you already collected into auditable labels.
1import requests
2from datetime import datetime, timezone
3
4API_URL = "https://api.twexapi.io/twitter/tweets/lookup"
5TOKEN = "YOUR_TWEXAPI_BEARER_TOKEN"
6
7def clean_tweet_ids(tweet_ids):
8 cleaned = []
9 seen = set()
10
11 for value in tweet_ids:
12 tweet_id = str(value).strip()
13 if not tweet_id or tweet_id in seen:
14 continue
15 cleaned.append(tweet_id)
16 seen.add(tweet_id)
17
18 return cleaned
19
20def lookup_tweets(tweet_ids):
21 cleaned_ids = clean_tweet_ids(tweet_ids)
22 if not cleaned_ids:
23 return []
24
25 response = requests.post(
26 API_URL,
27 headers={
28 "Authorization": f"Bearer {TOKEN}",
29 "Content-Type": "application/json",
30 "Accept": "application/json",
31 },
32 json=cleaned_ids,
33 timeout=30,
34 )
35 response.raise_for_status()
36 payload = response.json()
37
38 if payload.get("code", 200) >= 400:
39 raise RuntimeError(payload.get("msg", "TwexAPI returned an error"))
40
41 return payload.get("data") or []
42
43def classify_paid_promotions(tweet_ids, candidate_source):
44 fetched_at = datetime.now(timezone.utc).isoformat()
45 rows = []
46
47 for tweet in lookup_tweets(tweet_ids):
48 if not tweet:
49 continue
50
51 text = tweet.get("full_text") or tweet.get("text") or ""
52 user = tweet.get("user") or {}
53 is_paid = tweet.get("is_paid_promotion") is True
54
55 rows.append({
56 "tweet_id": tweet.get("tweet_id") or tweet.get("id"),
57 "is_paid_promotion": is_paid,
58 "promotion_label": "paid" if is_paid else "not_marked_paid",
59 "candidate_source": candidate_source,
60 "screen_name": user.get("screen_name"),
61 "created_at_datetime": tweet.get("created_at_datetime"),
62 "favorite_count": tweet.get("favorite_count"),
63 "retweet_count": tweet.get("retweet_count"),
64 "reply_count": tweet.get("reply_count"),
65 "view_count": tweet.get("view_count"),
66 "text_preview": text[:160],
67 "fetched_at": fetched_at,
68 "raw": tweet,
69 })
70
71 return rows
72
73candidate_ids = ["123456789", "987654321", "555666777"]
74results = classify_paid_promotions(candidate_ids, candidate_source="competitor_launch_search")
75
76paid_rows = [row for row in results if row["is_paid_promotion"]]
77print(f"Checked {len(results)} tweets; paid promotion marked: {len(paid_rows)}")Where Candidate IDs Come From
Candidate ID sources means how you decide which tweets should enter lookup. Do not only collect posts that already went viral; that biases the sample toward high-engagement content.
Common sources include:
- Advanced Search: Collect candidates by competitor account, brand term, campaign hashtag, or URL domain.
- Landing-page logs: Parse X post URLs that drove traffic into tweet IDs.
- Manual sample pools: Let researchers add suspected promoted posts to the candidate list.
- Historical campaign lists: Backfill old tweet IDs to separate organic spread from paid amplification.
When using Advanced Search, save the query, sortBy, collection time, and source tweet IDs. If you later find sampling bias, you can return to the source and adjust the candidate logic.
Market Research Workflow
Market research workflow means using is_paid_promotion as a label, not as a whole analysis by itself. Useful applications include:
- Paid vs organic share: Measure what share of posts for a competitor, campaign hashtag, or product term are marked as paid.
- Creative comparison: Compare video, image, thread, and link posts only within confirmed paid-promotion samples.
- Timing analysis: Look at when promoted content appears by hour, weekday, or campaign stage.
- Campaign review: Separate organic breakout content from paid amplification before judging creative quality.
Do not treat one boolean field as a budget estimate. Use it as evidence that a post was marked as promoted, then analyze it with publish time, engagement metrics, creative format, target page, and campaign stage.
Fields To Store
Fields to store means the minimum evidence needed to explain each paid or not-marked-paid label later.
| Field | Use |
|---|---|
tweet_id | Deduplication and rechecks |
is_paid_promotion | Whether the response returned the paid-promotion marker |
promotion_label | Separates paid, not_marked_paid, missing, and other analysis states |
candidate_source | Explains how the tweet entered the dataset |
created_at_datetime and fetched_at | Supports timing and fetch-time review |
user.screen_name | Account-level aggregation |
text, urls, media | Creative and landing-page analysis |
favorite_count, retweet_count, reply_count, view_count | Performance comparison |
| Raw JSON | Reprocessing when fields or rules change |
Common Misreads
Common misreads means over-interpreting the field or confusing sampling bias with a market conclusion.
is_paid_promotion === truecan be used as paid-promotion evidence;falseor missing only means the response did not return the marker.- Candidate source affects conclusions. Collecting only high-engagement posts can overstate the role of promotion in "viral" content.
- Engagement cannot be read directly as spend. Views, likes, and reposts also depend on creative quality, audience, timing, and organic discussion.
Summary
Summary means turning paid-promotion detection into a repeatable labeling workflow: collect candidate tweet IDs, call /twitter/tweets/lookup, read is_paid_promotion, and store raw response, candidate source, and engagement metrics. That lets competitor monitoring, campaign review, and creative analysis separate paid-promotion samples from organic discussion.