Executive Summary

🔍 Key Findings

This analysis reveals Twitter's internal API architecture used by their web client for community and direct messaging operations. All endpoints require OAuth2 authentication with a static bearer token plus dynamic CSRF tokens.

📚 API Categories Discovered

Category Endpoints Description
Communities 6 Full CRUD operations for communities, member lists, tweets
Direct Messages 7 Inbox, conversations, read receipts, permissions
User & Preferences 3 Settings, affiliates, following lists
Timeline 3 Various feed types including community timelines
Media/Video 31 HLS streaming URLs (6 pattern types × multiple resolutions/bitrates)
Notifications 1 Badge counts for unread items
Analytics 3 Event tracking and user flow
⚠️ Important Disclaimer: These APIs are unofficial and reverse-engineered. They are subject to change without notice and usage may violate Twitter's Terms of Service. This documentation is for educational and research purposes only.

Authentication Architecture

Required Authentication Components

1. Bearer Token (Static - Application Level)

Application-level token, appears to be static across sessions:

[YOUR_STATIC_BEARER_TOKEN]
2. CSRF Token (Dynamic - Session Level)

Must be extracted from cookies (ct0 cookie) and sent in x-csrf-token header:

[YOUR_CSRF_TOKEN_FROM_CT0_COOKIE]
3. Session Cookies

Essential cookies from authenticated session:

auth_token=[YOUR_AUTH_TOKEN] ct0=[YOUR_CSRF_TOKEN] twid=u%3D[YOUR_USER_ID] kdt=[YOUR_KDT_TOKEN] att=[YOUR_ATT_TOKEN]
4. Required Headers authorization: Bearer [YOUR_BEARER_TOKEN] x-csrf-token: [YOUR_CSRF_TOKEN] x-twitter-auth-type: OAuth2Session x-twitter-active-user: yes x-twitter-client-language: en x-client-transaction-id: [UNIQUE_PER_REQUEST] content-type: application/json cookie: [YOUR_SESSION_COOKIES]

Authentication Flow

  1. User authenticates via standard Twitter OAuth2 login flow
  2. Session cookies are set (including CSRF token in ct0 cookie)
  3. Static bearer token is used for application-level auth
  4. CSRF token from cookies must be included in request headers
  5. Each request requires unique x-client-transaction-id (base64 random string)

Complete API Catalog

🏘️
Communities APIs

6 Endpoints

Purpose: Manage communities, fetch member lists, get community tweets, explore communities

GET /i/api/graphql/{hash}/CommunityQuery

Operation Hash: 2W09l7nD7ZbxGQHXvfB22w

Purpose: Get detailed information about a specific community

Rate Limit: 50 requests/15min

variables: { "communityId": "[COMMUNITY_ID]" } features: { "c9s_list_members_action_api_enabled": false, "c9s_superc9s_indication_enabled": false }

Returns: Community details including name, description, member count, rules, join policy

GET /i/api/graphql/{hash}/CommunityTweetsTimeline

Operation Hash: aexCVeGbmrF7669joR04vQ

Purpose: Get tweets from a specific community timeline

Rate Limit: 500 requests/15min

variables: { "communityId": "[COMMUNITY_ID]", "count": 20, "displayLocation": "Community", "rankingMode": "Relevance", "withCommunity": true }
GET /i/api/graphql/{hash}/CommunitiesCreateButtonQuery

Operation Hash: ScODPHsG2d28Xr-rDSBThg

Purpose: Query for create button UI state

Rate Limit: 500 requests/15min

GET /i/api/graphql/{hash}/CommunitiesRankedTimeline

Operation Hash: CtB3bu37M7nYOng5B1PG1w

Purpose: Get ranked/recommended communities timeline

Rate Limit: 500 requests/15min

GET /i/api/graphql/{hash}/CommunitiesExploreTimeline

Operation Hash: AQDy3iNv8tYQZTy9O60idw

Purpose: Explore communities discovery timeline

Rate Limit: 500 requests/15min

GET /i/api/graphql/{hash}/CommunitiesFetchOneQuery

Operation Hash: ppTuEDxay9XWJg8dPI8j5w

Purpose: Fetch single community by ID

Rate Limit: 500 requests/15min

💬
Direct Messages APIs

7 Endpoints

Purpose: Manage DM inbox, conversations, read receipts, permissions

GET /i/api/1.1/dm/inbox_timeline/trusted.json

Purpose: Get DM inbox with conversation list

Rate Limit: 450 requests/15min

// Query Parameters ?max_id=[MESSAGE_ID] &include_profile_interstitial_type=1 &dm_users=true &include_groups=true &supports_reactions=true
POST /i/api/1.1/dm/conversation/{id}/mark_read.json

Purpose: Mark DM conversation as read

Request Body: conversationId={id}&last_read_event_id={message_id}

Content-Type: application/x-www-form-urlencoded

GET /i/api/graphql/{hash}/DMPinnedInboxQuery

Operation Hash: sIC-NZ_cqXLO_WH4jDWFQA

Purpose: Get pinned DM conversations

Rate Limit: 500 requests/15min

GET /i/api/1.1/dm/permissions.json

Purpose: Check DM permissions for recipient users

Rate Limit: 450 requests/15min

GET /i/api/1.1/dm/conversation/{id}.json

Purpose: Fetch conversation history and messages

Rate Limit: 900 requests/15min

POST /i/api/1.1/dm/welcome_messages/add_to_conversation.json

Purpose: Add welcome message to conversation

GET /i/api/1.1/dm/user_updates.json

Purpose: Poll for DM updates (new messages, typing indicators)

Rate Limit: 450 requests/15min

👤
User APIs

2 Endpoints

Purpose: User preferences, flow tracking

GET /i/api/graphql/{hash}/UserPreferences

Operation Hash: xFxU-O8hEYe74ovNVU74jA

Purpose: Get user preferences and settings

Rate Limit: 500 requests/15min

POST /i/api/1.1/graphql/user_flow.json

Purpose: Track user flow events for analytics

Rate Limit: 2000 requests/15min

🔔
Notifications APIs

1 Endpoint

Purpose: Badge counts and notification updates

GET /i/api/2/badge_count/badge_count.json

Purpose: Get unread notification badge counts

Rate Limit: 180 requests/15min

// Query Parameters ?supports_ntab_urt=1 &include_xchat_count=1

🎥
Media/Video APIs

31 Unique URLs

Purpose: HLS video streaming with multiple quality levels (m3u8 playlists, m4s segments)

📊 About the 31 endpoints: The capture identified 31 unique video/audio URLs, but they follow 6 main URL patterns shown below. Each pattern has multiple variations for different resolutions, bitrates, and video IDs.
GET /amplify_video/{id}/pl/{hash}.m3u8

Purpose: Master playlist - entry point for adaptive streaming

Format: HLS M3U8 playlist with variant streams

GET /amplify_video/{id}/pl/avc1/{resolution}/{hash}.m3u8

Purpose: Video stream playlist for specific resolution

Resolutions: 320x320, 480x270, 540x540, 1908x1072, 1920x1080, etc.

Codec: AVC1 (H.264)

GET /amplify_video/{id}/vid/avc1/{start}/{end}/{resolution}/{hash}.m4s

Purpose: Video segment with specific bitrate range

Format: MPEG-4 segment (M4S)

Example: /vid/avc1/3000/6000/1908x1072/{hash}.m4s (3-6 Mbps)

GET /amplify_video/{id}/pl/mp4a/{bitrate}/{hash}.m3u8

Purpose: Audio stream playlist

Codec: MP4A (AAC)

Bitrate: 32000 (32 kbps) or 128000 (128 kbps)

GET /amplify_video/{id}/aud/mp4a/{start}/{end}/{bitrate}/{hash}.m4s

Purpose: Audio segment

Format: MPEG-4 audio segment

GET /amplify_video/{id}/vid/avc1/0/0/{resolution}/{hash}.mp4

Purpose: Full video file (not segmented)

Format: MP4

🎬 URL Pattern Breakdown (31 total unique URLs):
  • Master playlists: 6 unique URLs (entry points for different videos)
  • Resolution-specific playlists: 7 URLs (320x320, 320x400, 320x568, 480x270, 540x540, 1908x1072, 1920x1080)
  • Video segments (.m4s): 12 URLs (various bitrate ranges: 0-3000, 3000-6000, 6000-9000, 9000-12000, 21000-30000 kbps)
  • Audio playlists: 2 URLs (32 kbps)
  • Audio segments (.m4s): 3 URLs (128 kbps at different time ranges)
  • Full MP4 files: 2 URLs (non-segmented videos)

Note: All URLs use HLS adaptive bitrate streaming. The patterns above represent the 6 endpoint types that generate 31 unique URLs across different videos, resolutions, and bitrates captured in the session.

📰
Timeline APIs

1 Endpoint

Purpose: Timeline queries for various contexts

GET /i/api/graphql/{hash}/membersSliceTimeline_Query

Operation Hash: gwNDrhzDr9kuoulEqgSQcQ

Purpose: Get community members timeline slice

Rate Limit: 500 requests/15min

variables: { "communityId": "[COMMUNITY_ID]", "cursor": null }

🔷
GraphQL - p2

1 Endpoint

Purpose: GraphQL CES (Client Event Service) endpoint

POST /i/api/1.1/graphql/ces/p2

Purpose: Client event tracking and analytics

Operation: p2 (performance/analytics)

🔗
GraphQL - affiliatesQuery

1 Endpoint

Purpose: Query user affiliates and connections

GET /i/api/graphql/{hash}/affiliatesQuery

Operation Hash: 1Sjin33Vk96OVlNTUG6zSg

Purpose: Get user affiliates data

Rate Limit: 50 requests/15min

🎠
GraphQL - CarouselQuery

1 Endpoint

Purpose: Carousel UI component data

GET /i/api/graphql/{hash}/CarouselQuery

Operation Hash: 7gBMELiDhLZ_MtyTElxYsA

Purpose: Get carousel content and layout

Rate Limit: 500 requests/15min

🎡
GraphQL - TopicCarouselQuery

1 Endpoint

Purpose: Topic-specific carousel UI component

GET /i/api/graphql/{hash}/TopicCarouselQuery

Operation Hash: byVjaS0CUtBqAao_lhZFgA

Purpose: Get topic carousel content

Rate Limit: 500 requests/15min

📢
Ads/Promoted APIs

1 Endpoint

Purpose: Promoted content tracking and logging

POST /i/api/1.1/promoted_content/log.json

Purpose: Log promoted content impressions and interactions

Used for: Ad tracking, impression logging, engagement metrics

📡
Live/Streaming APIs

1 Endpoint

Purpose: Live pipeline for real-time updates

POST /1.1/live_pipeline/update_subscriptions

Purpose: Subscribe/unsubscribe to live event streams

Base URL: api.x.com (not x.com)

Used for: Real-time notifications, live events, typing indicators

📦
Other APIs

2 Endpoints

Purpose: Miscellaneous endpoints that don't fit other categories

GET /i/api/1.1/friends/following/list.json

Purpose: Get list of users followed by a specific user

Rate Limit: 12000 requests/15min (very high)

// Query Parameters ?user_id=[USER_ID] &count=3 &cursor=-1 &with_total_count=true
GET /ext_tw_video/{id}/pu/vid/avc1/{bitrate}/{resolution}/{hash}.m4s

Purpose: External Twitter video segments (alternative to amplify_video)

Note: Similar to amplify_video but for user-uploaded content

API Dependencies & Architecture

💡 Dependency Analysis: This diagram shows how different API categories depend on each other and the authentication layer.

How to Read This Graph

  • 🔐 Authentication Banner (Top): Required by ALL APIs shown below - must establish valid session with bearer token + CSRF before making any API calls
  • Content APIs (Row 1): Communities, DMs, Timeline, User - these provide the primary data and can include media URLs in their responses
  • Media/Video (Row 2): Media URLs are obtained from Content API responses (tweets contain video links, DMs contain images, etc.), then accessed directly
  • Independent APIs (Row 3): Notifications, GraphQL queries (p2, Affiliates, Carousels), Ads, Live/Streaming - these operate independently after authentication without depending on other APIs
  • Other (Row 2): Miscellaneous endpoints like following lists and external video segments
  • Arrows: Show data dependencies only (not authentication requirements, which apply to all)
📌 Simplified View: The graph now shows only meaningful data dependencies. Since ALL 13 API categories require authentication, we display it as a banner rather than cluttering the graph with redundant arrows. This makes the actual data flow relationships clear and easy to follow.

Rate Limits & Quotas

⚠️ Rate Limit Headers: All API responses include rate limit information in headers:
  • x-rate-limit-limit: Total requests allowed per window
  • x-rate-limit-remaining: Requests remaining in current window
  • x-rate-limit-reset: Unix timestamp when limit resets
API Category Endpoint Limit Window Notes
Communities CommunityQuery 50 15 min Lower limit for detailed queries
Communities CommunityTweetsTimeline 500 15 min Standard GraphQL limit
Direct Messages dm/inbox_timeline 450 15 min REST API limit
Direct Messages dm/conversation/{id} 900 15 min Higher limit for conversations
User friends/following/list 12,000 15 min Very high limit
Notifications badge_count.json 180 15 min Frequent polling expected

Implementation Examples

Python Client - Complete Implementation

import requests import json class TwitterInternalAPI: """Complete Twitter Internal API Client""" def __init__(self, auth_token, csrf_token): self.base_url = "https://x.com" self.bearer_token = "[YOUR_STATIC_BEARER_TOKEN]" self.csrf_token = csrf_token self.auth_token = auth_token def _get_headers(self): return { "authorization": f"Bearer {self.bearer_token}", "x-csrf-token": self.csrf_token, "x-twitter-auth-type": "OAuth2Session", "x-twitter-active-user": "yes", "x-twitter-client-language": "en", "content-type": "application/json", "cookie": f"auth_token={self.auth_token}; ct0={self.csrf_token}" } # === Community APIs === def get_community(self, community_id): """Get detailed information about a specific community""" url = f"{self.base_url}/i/api/graphql/2W09l7nD7ZbxGQHXvfB22w/CommunityQuery" variables = {"communityId": community_id} features = { "c9s_list_members_action_api_enabled": False, "c9s_superc9s_indication_enabled": False } params = { "variables": json.dumps(variables), "features": json.dumps(features) } response = requests.get(url, headers=self._get_headers(), params=params) return response.json() def get_community_tweets(self, community_id, count=20): """Get tweets from a community timeline""" url = f"{self.base_url}/i/api/graphql/aexCVeGbmrF7669joR04vQ/CommunityTweetsTimeline" variables = { "communityId": community_id, "count": count, "displayLocation": "Community", "rankingMode": "Relevance", "withCommunity": True } features = { "responsive_web_graphql_timeline_navigation_enabled": True, "view_counts_everywhere_api_enabled": True } params = { "variables": json.dumps(variables), "features": json.dumps(features) } response = requests.get(url, headers=self._get_headers(), params=params) return response.json() # === Direct Messages APIs === def get_dm_inbox(self): """Get DM inbox with conversation list""" url = f"{self.base_url}/i/api/1.1/dm/inbox_timeline/trusted.json" params = { "include_profile_interstitial_type": "1", "include_blocking": "1", "dm_users": "true", "include_groups": "true", "include_inbox_timelines": "true", "supports_reactions": "true" } response = requests.get(url, headers=self._get_headers(), params=params) return response.json() def get_conversation(self, conversation_id): """Get messages from a specific conversation""" url = f"{self.base_url}/i/api/1.1/dm/conversation/{conversation_id}.json" response = requests.get(url, headers=self._get_headers()) return response.json() def mark_conversation_read(self, conversation_id, message_id): """Mark DM conversation as read""" url = f"{self.base_url}/i/api/1.1/dm/conversation/{conversation_id}/mark_read.json" headers = self._get_headers() headers["content-type"] = "application/x-www-form-urlencoded" data = f"conversationId={conversation_id}&last_read_event_id={message_id}" response = requests.post(url, headers=headers, data=data) return response.json() # === User & Notifications === def get_badge_count(self): """Get unread notification badge counts""" url = f"{self.base_url}/i/api/2/badge_count/badge_count.json" params = { "supports_ntab_urt": "1", "include_xchat_count": "1" } response = requests.get(url, headers=self._get_headers(), params=params) return response.json() # === Usage Examples === if __name__ == "__main__": # Initialize the API client api = TwitterInternalAPI( auth_token="[YOUR_AUTH_TOKEN]", csrf_token="[YOUR_CSRF_TOKEN]" ) # Get community information community = api.get_community("[COMMUNITY_ID]") print("Community:", community) # Get community tweets tweets = api.get_community_tweets("[COMMUNITY_ID]", count=20) print("Tweets:", tweets) # Get DM inbox inbox = api.get_dm_inbox() print("Inbox:", inbox) # Get badge counts badges = api.get_badge_count() print("Badges:", badges)

cURL Examples

Get Community Details

curl -X GET \ 'https://x.com/i/api/graphql/2W09l7nD7ZbxGQHXvfB22w/CommunityQuery?variables=%7B%22communityId%22%3A%22[COMMUNITY_ID]%22%7D&features=%7B%22c9s_list_members_action_api_enabled%22%3Afalse%7D' \ -H 'authorization: Bearer [YOUR_BEARER_TOKEN]' \ -H 'x-csrf-token: [YOUR_CSRF_TOKEN]' \ -H 'x-twitter-auth-type: OAuth2Session' \ -H 'x-twitter-active-user: yes' \ -H 'content-type: application/json' \ -H 'cookie: auth_token=[YOUR_AUTH_TOKEN]; ct0=[YOUR_CSRF_TOKEN]'

Get DM Inbox

curl -X GET \ 'https://x.com/i/api/1.1/dm/inbox_timeline/trusted.json?include_profile_interstitial_type=1&dm_users=true' \ -H 'authorization: Bearer [YOUR_BEARER_TOKEN]' \ -H 'x-csrf-token: [YOUR_CSRF_TOKEN]' \ -H 'x-twitter-auth-type: OAuth2Session' \ -H 'cookie: auth_token=[YOUR_AUTH_TOKEN]; ct0=[YOUR_CSRF_TOKEN]'

Mark DM as Read

curl -X POST \ 'https://x.com/i/api/1.1/dm/conversation/[CONVERSATION_ID]/mark_read.json' \ -H 'authorization: Bearer [YOUR_BEARER_TOKEN]' \ -H 'x-csrf-token: [YOUR_CSRF_TOKEN]' \ -H 'content-type: application/x-www-form-urlencoded' \ -H 'cookie: auth_token=[YOUR_AUTH_TOKEN]; ct0=[YOUR_CSRF_TOKEN]' \ -d 'conversationId=[CONVERSATION_ID]&last_read_event_id=[MESSAGE_ID]'

JavaScript/Node.js Client

const axios = require('axios'); class TwitterAPI { constructor(authToken, csrfToken) { this.baseURL = 'https://x.com'; this.bearerToken = '[YOUR_STATIC_BEARER_TOKEN]'; this.authToken = authToken; this.csrfToken = csrfToken; } getHeaders() { return { 'authorization': `Bearer ${this.bearerToken}`, 'x-csrf-token': this.csrfToken, 'x-twitter-auth-type': 'OAuth2Session', 'x-twitter-active-user': 'yes', 'content-type': 'application/json', 'cookie': `auth_token=${this.authToken}; ct0=${this.csrfToken}` }; } async getCommunity(communityId) { const url = `${this.baseURL}/i/api/graphql/2W09l7nD7ZbxGQHXvfB22w/CommunityQuery`; const params = { variables: JSON.stringify({ communityId }), features: JSON.stringify({ c9s_list_members_action_api_enabled: false }) }; const response = await axios.get(url, { headers: this.getHeaders(), params }); return response.data; } async getDMInbox() { const url = `${this.baseURL}/i/api/1.1/dm/inbox_timeline/trusted.json`; const params = { include_profile_interstitial_type: '1', dm_users: 'true', include_groups: 'true' }; const response = await axios.get(url, { headers: this.getHeaders(), params }); return response.data; } async getBadgeCount() { const url = `${this.baseURL}/i/api/2/badge_count/badge_count.json`; const params = { supports_ntab_urt: '1', include_xchat_count: '1' }; const response = await axios.get(url, { headers: this.getHeaders(), params }); return response.data; } } // Usage const api = new TwitterAPI('[YOUR_AUTH_TOKEN]', '[YOUR_CSRF_TOKEN]'); api.getCommunity('[COMMUNITY_ID]') .then(data => console.log(data)) .catch(err => console.error(err));