How to Search Twitter (X) Videos via API
Searching Twitter (X) for videos programmatically is one of the highest-signal workflows in social monitoring — video content typically has higher engagement, longer dwell, and stronger emotional payload than text-only tweets. Content moderators, OSINT analysts, brand-monitor teams, and journalists all need this endpoint pattern.
This guide walks the exact operator combos + endpoint call + cost math with runnable Python. Contrast with the twitter.com UI-based search which caps at manual page-by-page browsing — the API path unlocks bulk pulls, historical windows, and pipeline-ready JSON output.
The two operators — `filter:videos` vs `has:media`
filter:videos — narrower, matches only tweets with attached video content (uploaded native video, GIF-as-video). Best for pure video workflows.
has:media — broader, matches tweets with images OR videos OR GIFs. Use when you want ANY media attachment.
filter:native_video — narrowest, only X-native uploaded video (excludes linked YouTube/Vimeo URLs that render as video cards).
Combining: — high-signal English original video tweets on the topic.
Practical rule: start with filter:videos for video-only workflows; drop to has:media if you want combined image+video signal.
Runnable — video search with filters
One end-to-end example showing the operator combo pattern:
import os, requests
HEADERS = {"X-API-Key": os.environ["TWITTERAPI_IO_KEY"]}
BASE = "https://api.twitterapi.io"
def search_videos(topic: str, min_faves: int = 20, max_pages: int = 10) -> list:
query = f"{topic} filter:videos lang:en min_faves:{min_faves} -filter:retweets"
tweets, cursor = [], None
for _ in range(max_pages):
params = {"query": query}
if cursor: params["cursor"] = cursor
r = requests.get(f"{BASE}/twitter/tweet/advanced_search", headers=HEADERS, params=params, timeout=15)
r.raise_for_status()
resp = r.json()
tweets.extend(resp.get("tweets", []))
cursor = resp.get("next_cursor")
if not cursor: break
return tweets
# High-signal AI news videos with 50+ engagement
vids = search_videos("OpenAI OR Anthropic OR GPT", min_faves=50)
print(f"{len(vids):,} video tweets found")
# Access video URLs from the attachments field
for t in vids[:5]:
media = t.get("media", []) or t.get("attachments", [])
for m in media:
if m.get("type") in ("video", "animated_gif"):
print(f" @{t.get('author', {}).get('userName')}: {m.get('video_url', m.get('url', 'no-url'))}")
# Cost per twitterapi.io/pricing: len(vids) × $0.000154 real-world video-search combos
Each pulls a signal-only stream at 1-5% of raw-keyword volume + high engagement density = cost-efficient by design.
What the response includes for video tweets
For each video tweet, the API returns:
Media object array: each media item has type (video / animated_gif / photo), video_url (streamable URL), preview_image_url (thumbnail), duration_ms (video length), variants (multiple bitrate versions).
Author metadata: userName, followers_count, verified — for filtering by author quality.
Engagement: favorite_count, retweet_count, reply_count, quote_count — standard tweet-level engagement fields.
Text + created_at: the tweet text (often a caption/context for the video) + timestamp.
conversation_id: link to the reply thread for context.
Bulk pattern — download video URLs for a large window
For workflows that need many video URLs across a date range (research corpus building, content moderation batch processing, competitive intel archives), use windowed pagination + concurrent workers within safe rate limits.
# 30-day bulk video pull for a topic — window-sliced pattern.
import os, requests, json
from datetime import date, timedelta
from pathlib import Path
HEADERS = {"X-API-Key": os.environ["TWITTERAPI_IO_KEY"]}
BASE = "https://api.twitterapi.io"
OUT = Path("video_corpus.jsonl")
def pull_day(topic: str, d: date) -> int:
query = f"{topic} filter:videos lang:en min_faves:10 -filter:retweets since:{d} until:{d + timedelta(days=1)}"
n, cursor = 0, None
with open(OUT, "a") as f:
for _ in range(50):
params = {"query": query}
if cursor: params["cursor"] = cursor
r = requests.get(f"{BASE}/twitter/tweet/advanced_search", headers=HEADERS, params=params, timeout=15)
r.raise_for_status()
resp = r.json()
for t in resp.get("tweets", []):
f.write(json.dumps(t) + "\n"); n += 1
cursor = resp.get("next_cursor")
if not cursor: break
return n
TOPIC = "climate change"
END = date.today()
START = END - timedelta(days=30)
d = START
total = 0
while d < END:
n = pull_day(TOPIC, d)
print(f" {d}: {n:,} video tweets")
total += n
d += timedelta(days=1)
print(f"\n30d total: {total:,} video tweets")
print(f"Cost per twitterapi.io/pricing: {total} × $0.00015 = ${total * 0.00015:.2f}")twitterapi.io vs X official — same grammar, different price
Common video-search workflows
Content moderation batch: pull videos matching harmful-content signal keywords, score each via computer-vision pipeline, queue for human review.
Brand crisis monitoring: watch for video tweets mentioning your brand + negative-sentiment keyword combos — video crises spread 2-3× faster than text.
OSINT event coverage: pull all videos from a specific date+location window for incident reconstruction (journalism, researcher, investigator use cases).
Competitor product demos: track videos where competitors demo their product — feature launches, price announcements, integration reveals.
Creator sourcing / UGC pull: pull high-engagement videos in your niche → build a curated list of creators to reach out to for partnerships.
# Video sentiment corpus pull for downstream ML pipeline.
import os, requests, csv, json
from pathlib import Path
HEADERS = {"X-API-Key": os.environ["TWITTERAPI_IO_KEY"]}
BASE = "https://api.twitterapi.io"
BRAND = "anthropic"
query = f"{BRAND} filter:videos lang:en min_faves:20 -filter:retweets"
tweets, cursor = [], None
for _ in range(20):
params = {"query": query}
if cursor: params["cursor"] = cursor
r = requests.get(f"{BASE}/twitter/tweet/advanced_search", headers=HEADERS, params=params, timeout=15)
r.raise_for_status()
resp = r.json()
tweets.extend(resp.get("tweets", []))
cursor = resp.get("next_cursor")
if not cursor: break
rows = []
for t in tweets:
media = t.get("media", []) or t.get("attachments", [])
video_urls = [m.get("video_url") for m in media if m.get("type") in ("video", "animated_gif") and m.get("video_url")]
if not video_urls: continue
rows.append({
"tweet_id": t.get("id"),
"author": t.get("author", {}).get("userName"),
"created_at": t.get("created_at"),
"favorite_count": t.get("favorite_count", 0),
"text": (t.get("text") or "").replace("\n", " ")[:200],
"video_url": video_urls[0],
})
out = Path(f"{BRAND}_videos.csv")
with open(out, "w") as f:
w = csv.DictWriter(f, fieldnames=["tweet_id", "author", "created_at", "favorite_count", "text", "video_url"])
w.writeheader()
w.writerows(rows)
print(f"saved {len(rows)} video tweets to {out}")
# Cost per twitterapi.io/pricing: len(tweets) × $0.00015
# Downstream: pipe video_url through your CV/audio-transcription pipelineQuestions readers ask
How do I get the direct video download URL?
Each returned tweet has a media array; video items have video_url (streamable MP4) and variants (multi-bitrate). Use the highest bitrate variant for archive-quality downloads.
What's the difference between `filter:videos` and `filter:native_video`?
filter:videos includes any video-tagged tweet (native uploads + GIF-as-video). filter:native_video includes only X-native uploaded video (excludes GIFs + linked-out YouTube cards). For pure X video pulls, use filter:native_video.
Can I search for videos of a specific length?
Not directly via operator. Post-filter using the duration_ms field in the media object after pulling — e.g. duration_ms > 30000 for 30+ second videos.
How much video content is on X vs image content?
Typically ~10-20% of engaged media tweets are video (rest images). Native uploaded video is ~5-10% of all engaged media. Ratios shift over time — worth measuring for your specific keyword rather than assuming.
Do video URLs expire?
The video_url on media objects points to X's CDN. URLs are stable long-term for public tweets but not guaranteed indefinitely. For archival, download the video within your pull workflow rather than storing just the URL.
Any rate-limit concern for large video pulls?
Standard per-key throughput on twitterapi.io comfortably handles thousands of requests/hour. For very large corpus pulls (10K+ videos), pace across hours with ThreadPoolExecutor(max_workers=10-20) for parallel windowed queries.
Can I filter by video content (not just presence)?
Not via the search API directly. Approach: pull videos matching your keyword, then run each video_url through a CV / speech-to-text pipeline (OpenAI Whisper for audio, CLIP for visual similarity) to filter by content.
Continue
- Twitter (X) API — cluster hub
- Twitter (X) search filters — programmatic API
- Twitter (X) search operators — complete reference
- Twitter (X) images API extraction guide (sibling media pull)
- Twitter (X) API pricing
- twitterapi.io pricing
Stop reading. Start building.
Starter credits cover real testing on real data. Google sign-in, no card, no application queue.
Get an API key