History API
Use History API to backfill stored content, profile, and follow events after reconnects, review incident windows, or reconcile downstream bots and Discord routes.
Request
History is available on Pro and Scale. Unfiltered requests search across your active tracked accounts; filtered requests can only include handles you currently track. History returns stored `TWEET`, `PROFILE`, and `FOLLOW` rows; lifecycle events such as delete, pin, and unpin are delivered on the live stream.
| Parameter | Required | Notes |
|---|---|---|
| handle, handles, handle[], handles[] | No | One handle, repeated handles, or comma-separated handles |
| startDate | No | ISO datetime lower bound |
| endDate | No | ISO datetime upper bound |
| limit | No | Defaults to 100; maximum 1000 |
| type | No | TWEET, PROFILE, or FOLLOW. Defaults to TWEET |
const response = await fetch(
"https://api.tweetstream.io/api/history?handles=marketdesk&limit=25&type=TWEET",
{
headers: {
Authorization: `Bearer ${process.env.TWEETSTREAM_API_KEY}`,
},
},
);
console.log(await response.json());
Backfill windows
History returns newest results first and caps each request at 1000 rows. For large replay windows, split by time range: request a bounded `startDate` and `endDate`, process idempotently, then move the next window forward from the oldest processed event. Keep live lifecycle handlers active during reconnect recovery so state changes are not mixed into historical content replay.
Response
Results are newest first and include `metadata.count` plus any requested bounds or handle filters.
type VerifiedType = 'blue' | 'business' | 'government' | 'none';
type TweetVerifiedLabel = {
badge: string | null;
description: string;
url: string | null;
};
type TweetAuthor = {
banner?: string;
bio?: string;
followersCount?: number;
followingCount?: number;
id?: string;
joinedAt?: number;
location?: string;
metrics?: {
likes?: number;
tweets?: number;
};
// Includes a leading @ when present, for example "@elonmusk".
handle?: string;
name?: string;
platform?: 'twitter' | 'truth_social';
profileImage?: string;
url?: string;
verifiedLabel?: TweetVerifiedLabel;
verifiedType?: VerifiedType;
};
type AccountEventActor = TweetAuthor & {
websiteUrl?: string;
};
type ProfileUpdateEvent = {
kind: 'PROFILE';
eventId: string;
observedAt: number;
receivedAt?: number;
actor: AccountEventActor;
changes: {
avatar?: string;
banner?: string;
bio?: string;
handle?: string;
location?: string;
name?: string;
verifiedLabel?: TweetVerifiedLabel | null;
websiteUrl?: string | null;
};
previous?: {
avatar?: string;
banner?: string;
bio?: string;
handle?: string;
location?: string;
name?: string;
verifiedLabel?: TweetVerifiedLabel | null;
websiteUrl?: string | null;
};
};
type FollowEvent = {
kind: 'FOLLOW' | 'UNFOLLOW';
eventId: string;
observedAt: number;
receivedAt?: number;
actor: AccountEventActor;
target: AccountEventActor;
};
type TweetMeta = {
tweetId: string;
ocr?: {
text: string;
};
detected?: {
tokens?: Array<{
symbol?: string;
name?: string;
contract?: string;
chain?: string;
networkId?: number;
priceUsd?: number;
sources: Array<'text' | 'ocr'>;
}>;
cex?: Array<{
exchange: 'bybit' | 'binance' | 'hyperliquid';
symbol?: string;
priceUsd?: number;
url?: string;
baseAsset?: string;
quoteAsset?: string;
sources: Array<'text' | 'ocr'>;
}>;
prediction?: Array<{
exchange: 'polymarket' | 'kalshi';
marketId?: string;
title?: string;
priceUsd?: number;
url?: string;
sources: Array<'text' | 'ocr'>;
}>;
};
};
type HistoryMedia = {
url: string;
type?: 'image' | 'video' | 'gif';
thumbnail?: string;
};
type TweetUrl = {
url: string;
name?: string;
tco?: string;
};
type TweetMention = {
handle?: string;
id?: string;
name?: string;
};
type TweetContentKind = 'post' | 'reply' | 'quote' | 'retweet';
type HistoryTweetReference = {
type: 'reply' | 'quote' | 'retweet';
tweetId?: string;
text?: string;
translatedText?: string;
author?: TweetAuthor;
media?: HistoryMedia[];
quoted?: HistoryTweetReference;
};
type TweetContent = {
tweetId: string;
kind: TweetContentKind;
// Original tweet text when the stored content includes both original and translated text.
text: string;
translatedText?: string;
createdAt: number;
author: TweetAuthor;
link?: string;
media?: HistoryMedia[];
mentions?: TweetMention[];
// Epoch ms from the realtime payload when the stored content includes it.
receivedAt?: number;
urls?: TweetUrl[];
ref?: HistoryTweetReference;
};
type HistoricalContent = TweetContent | ProfileUpdateEvent | FollowEvent;
type HistoricalTweetResponse = {
tweetId: string;
twitterId: string;
twitterHandle: string | null;
body: string;
time: string;
// ISO persistence receive time for the history row.
receivedTime: string;
link: string;
messageType: 'TWEET' | 'PROFILE' | 'FOLLOW';
content: HistoricalContent;
meta?: TweetMeta;
};
type HistoryResult = {
data: HistoricalTweetResponse[];
metadata: {
count: number;
handle?: string;
handles?: string[];
startDate?: string;
endDate?: string;
type?: 'TWEET' | 'PROFILE' | 'FOLLOW';
};
};
{
"data": [
{
"tweetId": "follow_1",
"twitterId": "123",
"twitterHandle": "tracked",
"body": "Followed @newaccount",
"time": "2026-04-09T01:00:00.000Z",
"receivedTime": "2026-04-09T01:00:00.500Z",
"link": "https://x.com/newaccount",
"messageType": "FOLLOW",
"content": {
"kind": "FOLLOW",
"eventId": "follow_1",
"observedAt": 1744160400000,
"actor": {
"id": "123",
"handle": "tracked",
"name": "Tracked Account",
"followersCount": 125000,
"followingCount": 321,
"verifiedType": "business"
},
"target": {
"id": "456",
"handle": "newaccount",
"name": "New Account",
"profileImage": "https://pbs.twimg.com/profile_images/newaccount_normal.jpg",
"websiteUrl": "https://newaccount.example"
}
}
}
],
"metadata": {
"count": 1,
"handle": "tracked",
"handles": ["tracked"],
"startDate": "2026-04-01T00:00:00.000Z",
"endDate": "2026-04-30T23:59:59.999Z",
"type": "FOLLOW"
}
}
Errors
| Status | Body | Meaning |
|---|---|---|
| 400 | { "error": "Invalid query parameters" } | Malformed dates, limit, type, or handle |
| 400 | { "error": "Invalid handle provided", "handle": "..." } | Handle validation failed |
| 400 | { "error": "startDate must be before endDate" } | Date range is reversed |
| 401 | { "error": "Missing or invalid API key" } | Missing or malformed bearer token |
| 401 | { "error": "Invalid API key" } | Bearer token was well-formed but does not match an active API key |
| 403 | { "error": "This endpoint is only available for Pro and Scale plan users" } | Plan does not include History API |
| 403 | { "error": "Your subscription is not active", "message": "Please ensure your subscription is active", "status": "PAST_DUE" } | Subscription state does not allow history |
| 403 | { "error": "Handle ... is not among your tracked accounts" } | Requested replay handle is outside your watchlist |
| 429 | { "error": "Too many history requests", "retryAfterSeconds": 60 } | Rate limit exceeded |