How to Search Tweets by Date via API — A Developer's Guide
Searching tweets by date is the foundation of most longitudinal Twitter (X) workflows — historical research, campaign post-mortems, brand-mention trend studies, deletion-rate sampling, anything that compares 'how was the conversation in Q1' to 'how is it now'. The mechanic is the since: / until: operator pair, which both providers support in the same syntax.
This guide walks the date-range query in Python: format conventions, the UTC gotcha, combining with other operators, pagination, and per-provider cost. Pricing references are URL-cited.
The operator pair — `since:` and `until:`
Format: since:YYYY-MM-DD and until:YYYY-MM-DD. The date is evaluated in UTC, regardless of where you or the tweet author are.
Boundary semantics: since: is inclusive (tweets posted on this date or later). until: is exclusive (tweets posted strictly before this date). So since:2025-01-01 until:2025-12-31 includes all of 2025 except December 31 — which is a common surprise. Use until:2026-01-01 to include December 31.
Combining: stacks with any other operator. from:nasa since:2025-06-01 until:2025-06-08 min_faves:100 returns @nasa's high-engagement tweets in the first week of June 2025.
Path 1 — twitterapi.io advanced_search
twitterapi.io's /twitter/tweet/advanced_search accepts the full X advanced-search syntax. Auth is X-API-Key header. Pricing per twitterapi.io/pricing: $0.00015 per returned tweet.
import os, requests
from datetime import date, timedelta
HEADERS = {"X-API-Key": os.environ["TWITTERAPI_IO_KEY"]}
BASE = "https://api.twitterapi.io"
def search_by_date(query_base: str, start: date, end: date, max_pages: int = 20):
# NOTE: until: is exclusive. Add 1 day to end if you want it inclusive.
inclusive_end = end + timedelta(days=1)
query = f"{query_base} since:{start.isoformat()} until:{inclusive_end.isoformat()}"
print(f"running query: {query}")
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
# Example: NASA tweets in 2025 with at least 100 likes
results = search_by_date(
"from:nasa min_faves:100",
start=date(2025, 1, 1),
end=date(2025, 12, 31), # inclusive of Dec 31
)
print(f"found {len(results)} tweets")
for t in results[:5]:
print(f" {t.get('created_at', '?')}: {t.get('text', '')[:80]}")
Path 2 — X official
X's /2/tweets/search/all (academic/enterprise) and /2/tweets/search/recent (last 7 days) both accept the same date operators. Pricing per docs.x.com/x-api/getting-started/pricing: $0.005 per post read.
# pip install tweepy
import tweepy
from datetime import datetime, timezone
client = tweepy.Client(bearer_token="YOUR_X_BEARER")
def search_x_by_date(query: str, start: datetime, end: datetime, max_results: int = 100):
# X official uses start_time / end_time params for date range (ISO-8601)
tweets = []
for page in tweepy.Paginator(
client.search_recent_tweets, # or search_all_tweets for academic
query=query,
start_time=start.replace(tzinfo=timezone.utc).isoformat(),
end_time=end.replace(tzinfo=timezone.utc).isoformat(),
tweet_fields=["created_at", "public_metrics"],
max_results=max_results, limit=10,
):
tweets.extend(page.data or [])
return tweets
from datetime import datetime
results = search_x_by_date(
"from:nasa min_faves:100",
start=datetime(2025, 1, 1),
end=datetime(2025, 12, 31, 23, 59, 59),
)
for t in results[:5]:
print(f"{t.created_at}: {t.text[:80]}")
Three real gotchas — what trips people up
1. UTC boundary, not your local time — until:2025-12-31 evaluated in UTC drops tweets posted on Dec 31 in your local timezone if you're west of UTC. For inclusive-of-target-date semantics, add a buffer day.
2. until: is exclusive — covered above; bears repeating because it's the most common silent bug. The query above intentionally adds a day to the user-facing end date.
3. Date format must be YYYY-MM-DD — not MM/DD/YYYY, not 2025-Jan-01. Malformed dates silently fail (no error returned), the query just runs with no date constraint and returns recent-only results.
Side-by-side — 2 paths for date-range search
Costs derived from each provider's published pricing.
Two practical observations: (a) twitterapi.io's full-archive access makes multi-year date-range pulls economically viable; (b) X official's search_all_tweets is enterprise-only, not in the cheaper tier.
Combining date with other operators
Six common combinations:
- from:user since:X until:Y — single account's posts in date range
- "brand" since:X until:Y lang:en — brand mentions in date range, English only
- #hashtag since:X until:Y min_faves:50 — hashtag posts in date range above engagement floor
- to:user since:X until:Y — replies to user in date range
- (keyword1 OR keyword2) since:X until:Y -is:retweet — keyword OR pair, organic only
- since:X until:Y -filter:replies — exclude reply tweets from results
Pagination + cost framing for multi-year pulls
Cursor pagination: both providers use cursor tokens. Loop until next_cursor is null OR you hit your page-cap (safety against runaway query draining credit).
Cost framing for multi-year pulls (math from twitterapi.io/pricing $0.00015/tweet):
- 1-month pull of a moderate-activity account (~300 tweets) = $0.045
- 1-year pull of the same account (~3,600 tweets) = $0.54
- 5-year archive pull = $2.70
- 1-year brand-mention pull (~50,000 tweets) = $7.50
Storage cost (your warehouse) typically dwarfs the API pull cost for archive workloads.
# Practical example: month-by-month deletion-rate audit for a target account.
import os, requests
from datetime import date
from calendar import monthrange
from collections import OrderedDict
HEADERS = {"X-API-Key": os.environ["TWITTERAPI_IO_KEY"]}
BASE = "https://api.twitterapi.io"
def month_range_search(handle: str, year: int, month: int):
start = date(year, month, 1)
last_day = monthrange(year, month)[1]
end = date(year, month, last_day)
query = f"from:{handle} since:{start.isoformat()} until:{date(year, month, last_day).isoformat()}"
# NOTE: until exclusive — add 1 to end day if you want true end-of-month
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
return len(tweets)
results = OrderedDict()
for m in range(1, 13):
n = month_range_search("target_handle", 2025, m)
results[f"2025-{m:02d}"] = n
print(f" 2025-{m:02d}: {n} tweets returned")
# Cost framing (math from twitterapi.io/pricing):
# 12 months × ~300 tweets/mo × $0.00015 = $0.54 per year-audit
# Vs X official: 3,600 × $0.005 = $18.00 (~33× more)Questions readers ask
Why is `until:2025-12-31` not returning my December 31 tweets?
until: is exclusive — it returns tweets strictly before that date in UTC. Use until:2026-01-01 to include December 31. This is the single most common silent bug in date-range queries.
Can I search tweets from before 2010?
Yes via the archive — twitterapi.io's /twitter/tweet/advanced_search covers back to the early years of the platform. X official's search_all_tweets (academic/enterprise) covers similarly far. The recent-search endpoint is 7-day window only.
Does the time-of-day in the date matter?
since: and until: are date-level operators, not datetime — they snap to UTC midnight. For finer time-of-day filtering, use the X official's start_time / end_time ISO-8601 params, or filter created_at field client-side after pulling the day.
How do I search a multi-year window without hitting pagination limits?
Loop monthly: query January, store, query February, etc. Smaller per-query result sets stay within pagination caps. The end-to-end cost is the same (per-tweet pricing), and you can resume mid-loop if your script crashes.
What about deleted tweets in the date range?
The search endpoints return tweets that existed at the time the index was built. If a tweet was posted in your date range but later deleted, it may still appear (with the API field marking it deleted in some cases) or may be filtered out. For deletion-rate research, this is an active gotcha; pull your data soon after the date window closes for the cleanest sample.
Can I do a 'tweets posted in the last 24 hours' query?
Yes — since: accepts a recent date, but for sub-day windows use the relative operator: within_time:24h. twitterapi.io supports this directly; X official prefers start_time ISO-8601 for sub-day precision.
Continue
- twitterapi.io — pricing
- X API — pricing (docs.x.com, 2026 verified)
- X official — Build a query (operators)
- Twitter (X) API — cluster hub
- Twitter (X) advanced search guide
- Twitter (X) history API — export timeline
- Twitter (X) analysis API developer guide
- 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