文档
历史 API
使用 History API 在重连后回补已存内容、资料和关注事件,复盘事故窗口,或对账下游机器人和 Discord 路由。
请求
History 在 Pro 和 Scale 可用。未过滤请求会搜索你的全部活跃监控账号;过滤请求只能包含你当前正在监控的 handles。History 返回已存 `TWEET`、`PROFILE` 和 `FOLLOW` 行;delete、pin、unpin 等生命周期事件通过实时流发送。
| 参数 | 必填 | 说明 |
|---|---|---|
| handle, handles, handle[], handles[] | 否 | 单个 handle、重复 handle 或逗号分隔 handles |
| startDate | 否 | ISO datetime 下界 |
| endDate | 否 | ISO datetime 上界 |
| limit | 否 | 默认 100;最大 1000 |
| type | 否 | TWEET、PROFILE 或 FOLLOW。默认 TWEET |
获取历史typescript
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());
回补窗口
History 按最新结果优先返回,并且每次请求最多 1000 行。对于较大的回放窗口,请按时间范围拆分:请求有界的 `startDate` 和 `endDate`,幂等处理,然后从最旧已处理事件继续推进下一个窗口。重连恢复期间保持实时生命周期 handler 可用,避免把状态变化混进历史内容回放。
响应
结果按最新优先返回,并包含 `metadata.count` 以及请求中的时间边界或 handle 过滤条件。
类型typescript
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';
};
};
示例json
{
"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"
}
}
错误
| 状态 | Body | 含义 |
|---|---|---|
| 400 | { "error": "Invalid query parameters" } | 日期、limit、type 或 handle 格式错误 |
| 400 | { "error": "Invalid handle provided", "handle": "..." } | Handle 校验失败 |
| 400 | { "error": "startDate must be before endDate" } | 日期范围反向 |
| 401 | { "error": "Missing or invalid API key" } | Bearer token 缺失或格式错误 |
| 401 | { "error": "Invalid API key" } | Bearer token 格式正确,但不匹配有效 API key |
| 403 | { "error": "This endpoint is only available for Pro and Scale plan users" } | 套餐不包含 History API |
| 403 | { "error": "Your subscription is not active", "message": "Please ensure your subscription is active", "status": "PAST_DUE" } | 订阅状态不允许使用历史 |
| 403 | { "error": "Handle ... is not among your tracked accounts" } | 请求回放的 handle 不在你的监控列表中 |
| 429 | { "error": "Too many history requests", "retryAfterSeconds": 60 } | 超过速率限制 |