FetchLayer fetchlayer.dev Sign in

+ Integration Guide

Twitter/X API with TypeScript: Complete Guide

How to search X/Twitter and scrape tweets using TypeScript with full type safety. Working examples with typed responses for every FetchLayer endpoint.

Written by Alex P.

  • TypeScript
  • twitter scraping
  • X API
  • Twitter API
  • API integration
  • type safety

This guide shows you how to use the FetchLayer Twitter/X API with TypeScript. You get full type safety on API responses with zero external dependencies.

If you want the official TypeScript package instead of defining the response types yourself, FetchLayer now ships @fetchlayer/twitter. It includes typed response interfaces for all 10 endpoints and is open source on GitHub.


Setup

  1. Get a free API key (no credit card)
  2. TypeScript 5+ and Node 18+ (or Bun/Deno)

Optional, if you want the official SDK with built-in endpoint types:

npm install @fetchlayer/twitter

Type Definitions

Define the response types for the endpoints you’ll use:

interface TwitterAuthor {
  handle: string;
  displayName: string;
  isVerified: boolean;
  followersCount?: number;
}

interface Tweet {
  id: string;
  text: string;
  author: TwitterAuthor;
  createdAt: string;
  likeCount: number;
  retweetCount: number;
  replyCount: number;
  quoteCount?: number;
  viewCount?: number;
  url: string;
  lang?: string;
}

interface SearchResponse {
  results: Tweet[];
  cursor?: string;
}

interface TweetDetailResponse {
  id: string;
  text: string;
  author: TwitterAuthor;
  createdAt: string;
  likeCount: number;
  retweetCount: number;
  replyCount: number;
  quoteCount: number;
  viewCount: number;
  url: string;
  lang: string;
}

interface TweetRepliesResponse {
  replies: Tweet[];
  cursor?: string;
}

interface UserProfile {
  handle: string;
  displayName: string;
  description: string;
  isVerified: boolean;
  followersCount: number;
  followingCount: number;
  tweetsCount: number;
  joinedAt: string;
  location?: string;
  website?: string;
  avatarUrl: string;
  bannerUrl?: string;
}

interface UserTweetsResponse {
  tweets: Tweet[];
  cursor?: string;
}

interface AccountEntry {
  handle: string;
  displayName: string;
  isVerified: boolean;
  followersCount: number;
  description?: string;
}

interface FollowersResponse {
  accounts: AccountEntry[];
  cursor?: string;
}

Typed API Client

const API_KEY = process.env.FETCHLAYER_API_KEY!;
const BASE_URL = 'https://api.fetchlayer.dev/twitter';

async function twitter<T>(endpoint: string, body: Record<string, unknown>): Promise<T> {
  const res = await fetch(`${BASE_URL}/${endpoint}`, {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${API_KEY}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify(body),
  });

  if (!res.ok) {
    throw new Error(`FetchLayer ${res.status}: ${await res.text()}`);
  }

  return res.json() as Promise<T>;
}

Search Twitter/X

const data = await twitter<SearchResponse>('search', {
  query: 'best CI/CD tools',
  product: 'Top',
  count: 10,
});

// Full type safety — TypeScript knows data.results is Tweet[]
for (const tweet of data.results) {
  console.log(`[${tweet.likeCount} likes] @${tweet.author.handle}: ${tweet.text.slice(0, 100)}`);
}

// Paginate
if (data.cursor) {
  const page2 = await twitter<SearchResponse>('search', {
    query: 'best CI/CD tools',
    product: 'Top',
    count: 10,
    cursor: data.cursor,
  });
}

Get a Tweet by ID

const tweet = await twitter<TweetDetailResponse>('tweet-detail', {
  tweetId: '1942939879222220800',
});

console.log(`@${tweet.author.handle} (${tweet.author.followersCount?.toLocaleString()} followers)`);
console.log(`${tweet.likeCount} likes · ${tweet.retweetCount} retweets · ${tweet.replyCount} replies`);

Get Replies to a Tweet

const data = await twitter<TweetRepliesResponse>('tweet-replies', {
  tweetId: '1942939879222220800',
});

const highEngagementReplies = data.replies.filter(r => (r.likeCount ?? 0) > 10);
console.log(`${highEngagementReplies.length} replies with 10+ likes`);

Get a User Profile

const profile = await twitter<UserProfile>('user-profile-details', {
  handle: 'openai',
});

console.log(`${profile.displayName} (@${profile.handle})`);
console.log(`Followers: ${profile.followersCount.toLocaleString()}`);
console.log(`Joined: ${new Date(profile.joinedAt).getFullYear()}`);
console.log(`Bio: ${profile.description}`);

Get a User’s Tweets

const data = await twitter<UserTweetsResponse>('user-tweets', {
  handle: 'rauchg',
  count: 20,
});

const topTweets = (data.tweets ?? [])
  .sort((a, b) => (b.likeCount ?? 0) - (a.likeCount ?? 0))
  .slice(0, 5);

for (const tweet of topTweets) {
  console.log(`[${tweet.likeCount} likes] ${tweet.text.slice(0, 120)}`);
}

Get Followers and Following

// Who a user follows
const following = await twitter<FollowersResponse>('following', {
  handle: 'openai',
  count: 50,
});

for (const account of following.accounts ?? []) {
  console.log(`@${account.handle} — ${account.followersCount.toLocaleString()} followers`);
}

// Verified followers
const verified = await twitter<FollowersResponse>('verified-followers', {
  handle: 'openai',
  count: 20,
});
console.log(`${verified.accounts?.length ?? 0} verified followers`);

Full Example: Audience Analysis

async function analyzeAudience(handle: string) {
  const profile = await twitter<UserProfile>('user-profile-details', { handle });

  // Get recent tweets and their engagement
  const tweets = await twitter<UserTweetsResponse>('user-tweets', { handle, count: 50 });
  const avgLikes = (tweets.tweets ?? []).reduce((sum, t) => sum + (t.likeCount ?? 0), 0) / (tweets.tweets?.length || 1);
  const avgRetweets = (tweets.tweets ?? []).reduce((sum, t) => sum + (t.retweetCount ?? 0), 0) / (tweets.tweets?.length || 1);

  console.log(`${profile.displayName} (@${profile.handle})`);
  console.log(`Followers: ${profile.followersCount.toLocaleString()}`);
  console.log(`Avg engagement: ${avgLikes.toFixed(0)} likes, ${avgRetweets.toFixed(0)} retweets per tweet`);
  console.log(`Engagement rate: ${((avgLikes / profile.followersCount) * 100).toFixed(2)}%`);
}

analyzeAudience(process.argv[2] || 'openai');

What’s Next