Back to Blog
Guide

Threads Analytics 2026: Track Any Profile's Growth Without Meta's API

April 1, 2026
8 min read
S
By SociaVault Team
ThreadsAnalyticsSocial MediaMetaEngagementAPI

Threads Analytics 2026: Track Any Profile's Growth Without Meta's API

Threads hit 300 million monthly active users in early 2026. Brands finally care about it. But Meta's API access for Threads is still limited — you need approval, it's read-only for your own account, and there's no competitor data.

That leaves a gap: how do you track someone else's Threads performance? How do you benchmark your own growth against competitors? How do you know if Threads is even worth your time compared to X or Bluesky?

You can't answer those questions staring at the Threads app.

This guide shows you how to build Threads analytics using API data — track any profile's metrics, analyze their content performance, and decide whether your brand should invest more in Threads.


What Threads Metrics Can You Track?

Here's what's available from any public Threads profile:

Profile Data

  • Username and display name
  • Bio text and bio links
  • Follower count
  • Verification status
  • Profile picture URL

Post Data

  • Full post text
  • Like count per post
  • Reply count per post
  • Repost/reshare count per post
  • Timestamp of each post
  • Post URL (pk identifier)

That's enough to calculate engagement rates, posting frequency, content analysis, and growth tracking.


Track Any Profile's Performance

Start with the basics — pull a profile and their recent posts:

const axios = require('axios');

const API_KEY = process.env.SOCIAVAULT_API_KEY;
const BASE_URL = 'https://api.sociavault.com';

async function analyzeThreadsProfile(handle) {
  // Get profile data
  const profileResponse = await axios.get(`${BASE_URL}/v1/scrape/threads/profile`, {
    params: { handle },
    headers: { 'X-API-Key': API_KEY }
  });
  const profile = profileResponse.data.data;

  // Get recent posts
  const postsResponse = await axios.get(`${BASE_URL}/v1/scrape/threads/user-posts`, {
    params: { handle },
    headers: { 'X-API-Key': API_KEY }
  });
  const posts = postsResponse.data.data?.posts || [];

  console.log(`\n=== THREADS ANALYTICS: @${profile.username} ===`);
  console.log(`Name: ${profile.full_name}`);
  console.log(`Bio: ${profile.biography}`);
  console.log(`Followers: ${profile.follower_count?.toLocaleString()}`);
  console.log(`Verified: ${profile.is_verified ? 'Yes' : 'No'}`);

  // Analyze post performance
  const postMetrics = posts.map(post => ({
    text: post.caption?.text?.substring(0, 80) || '[no text]',
    likes: post.like_count || 0,
    replies: post.text_post_app_info?.direct_reply_count || 0,
    reposts: post.text_post_app_info?.reshare_count || 0,
    timestamp: post.taken_at,
    engagement: (post.like_count || 0) +
                (post.text_post_app_info?.direct_reply_count || 0) +
                (post.text_post_app_info?.reshare_count || 0)
  }));

  // Calculate averages
  const avgLikes = postMetrics.reduce((s, p) => s + p.likes, 0) / Math.max(postMetrics.length, 1);
  const avgReplies = postMetrics.reduce((s, p) => s + p.replies, 0) / Math.max(postMetrics.length, 1);
  const avgReposts = postMetrics.reduce((s, p) => s + p.reposts, 0) / Math.max(postMetrics.length, 1);
  const avgEngagement = postMetrics.reduce((s, p) => s + p.engagement, 0) / Math.max(postMetrics.length, 1);

  // Engagement rate (per follower)
  const engagementRate = profile.follower_count > 0
    ? ((avgEngagement / profile.follower_count) * 100).toFixed(2)
    : 'N/A';

  console.log(`\nPost Performance (${postMetrics.length} recent posts):`);
  console.log(`  Avg likes: ${Math.round(avgLikes).toLocaleString()}`);
  console.log(`  Avg replies: ${Math.round(avgReplies).toLocaleString()}`);
  console.log(`  Avg reposts: ${Math.round(avgReposts).toLocaleString()}`);
  console.log(`  Avg engagement: ${Math.round(avgEngagement).toLocaleString()}`);
  console.log(`  Engagement rate: ${engagementRate}%`);

  // Find top performing post
  postMetrics.sort((a, b) => b.engagement - a.engagement);
  if (postMetrics.length > 0) {
    const top = postMetrics[0];
    console.log(`\nTop post: "${top.text}..."`);
    console.log(`  ${top.likes} likes | ${top.replies} replies | ${top.reposts} reposts`);
  }

  return { profile, posts: postMetrics, engagementRate };
}

await analyzeThreadsProfile('zuck');

Cost: 2 credits (1 profile + 1 posts).


Benchmark Against Competitors

Running the same analysis against multiple accounts in your niche gives you a competitive benchmark:

import requests
import os

API_KEY = os.getenv('SOCIAVAULT_API_KEY')
BASE_URL = 'https://api.sociavault.com'
headers = {'X-API-Key': API_KEY}

def benchmark_threads_accounts(handles):
    """Compare Threads performance across multiple accounts"""
    benchmarks = []

    for handle in handles:
        # Get profile
        profile_resp = requests.get(
            f'{BASE_URL}/v1/scrape/threads/profile',
            params={'handle': handle},
            headers=headers
        )
        profile = profile_resp.json().get('data', {})

        # Get posts
        posts_resp = requests.get(
            f'{BASE_URL}/v1/scrape/threads/user-posts',
            params={'handle': handle},
            headers=headers
        )
        posts = posts_resp.json().get('data', {}).get('posts', [])

        # Calculate metrics
        total_likes = sum(p.get('like_count', 0) for p in posts)
        total_replies = sum(
            p.get('text_post_app_info', {}).get('direct_reply_count', 0) for p in posts
        )
        total_reposts = sum(
            p.get('text_post_app_info', {}).get('reshare_count', 0) for p in posts
        )
        post_count = len(posts)
        followers = profile.get('follower_count', 0)

        avg_engagement = (total_likes + total_replies + total_reposts) / max(post_count, 1)
        er = (avg_engagement / max(followers, 1)) * 100

        benchmarks.append({
            'handle': handle,
            'followers': followers,
            'posts_analyzed': post_count,
            'avg_likes': round(total_likes / max(post_count, 1)),
            'avg_replies': round(total_replies / max(post_count, 1)),
            'avg_reposts': round(total_reposts / max(post_count, 1)),
            'avg_engagement': round(avg_engagement),
            'engagement_rate': round(er, 2),
            'verified': profile.get('is_verified', False)
        })

    # Sort by engagement rate
    benchmarks.sort(key=lambda x: x['engagement_rate'], reverse=True)

    print('\n=== THREADS BENCHMARK REPORT ===\n')
    print(f'{"Handle":<20} {"Followers":>12} {"Avg Likes":>10} {"Avg Replies":>12} {"ER%":>8}')
    print('-' * 70)

    for b in benchmarks:
        badge = ' ✓' if b['verified'] else ''
        print(f"@{b['handle']:<19}{badge} {b['followers']:>10,} {b['avg_likes']:>10,} "
              f"{b['avg_replies']:>12,} {b['engagement_rate']:>7.2f}%")

    # Calculate benchmark averages
    avg_er = sum(b['engagement_rate'] for b in benchmarks) / len(benchmarks)
    print(f'\nBenchmark average ER: {avg_er:.2f}%')
    print(f'Top performer: @{benchmarks[0]["handle"]} ({benchmarks[0]["engagement_rate"]}%)')

    return benchmarks

# Benchmark tech company Threads accounts
benchmark_threads_accounts([
    'zuck',
    'maboroshi.exe',
    'garyvee',
    'mkbhd'
])

Cost: 2 credits per account (profile + posts).


Content Performance Analysis

Not all Threads posts perform equally. Analyze what types of content get the most engagement:

async function contentAnalysis(handle) {
  const postsResponse = await axios.get(`${BASE_URL}/v1/scrape/threads/user-posts`, {
    params: { handle },
    headers: { 'X-API-Key': API_KEY }
  });
  const posts = postsResponse.data.data?.posts || [];

  // Classify posts by content type
  const categories = {
    questions: { posts: [], pattern: /\?/ },
    links: { posts: [], pattern: /https?:\/\// },
    short: { posts: [], check: (text) => text.length < 100 },
    long: { posts: [], check: (text) => text.length >= 280 },
    medium: { posts: [], check: (text) => text.length >= 100 && text.length < 280 }
  };

  posts.forEach(post => {
    const text = post.caption?.text || '';
    const engagement = (post.like_count || 0) +
      (post.text_post_app_info?.direct_reply_count || 0) +
      (post.text_post_app_info?.reshare_count || 0);

    const item = { text: text.substring(0, 60), engagement, likes: post.like_count || 0 };

    if (categories.questions.pattern.test(text)) categories.questions.posts.push(item);
    if (categories.links.pattern.test(text)) categories.links.posts.push(item);
    if (text.length < 100) categories.short.posts.push(item);
    else if (text.length >= 280) categories.long.posts.push(item);
    else categories.medium.posts.push(item);
  });

  console.log(`\n=== CONTENT ANALYSIS: @${handle} ===\n`);

  Object.entries(categories).forEach(([type, data]) => {
    if (data.posts.length === 0) return;

    const avgEng = data.posts.reduce((s, p) => s + p.engagement, 0) / data.posts.length;
    console.log(`${type.toUpperCase()} (${data.posts.length} posts): avg engagement ${Math.round(avgEng).toLocaleString()}`);
  });

  // Find optimal content patterns
  const allPosts = posts.map(p => ({
    text: p.caption?.text || '',
    engagement: (p.like_count || 0) +
      (p.text_post_app_info?.direct_reply_count || 0) +
      (p.text_post_app_info?.reshare_count || 0),
    likes: p.like_count || 0,
    replies: p.text_post_app_info?.direct_reply_count || 0,
    reposts: p.text_post_app_info?.reshare_count || 0,
    length: (p.caption?.text || '').length,
    hasQuestion: /\?/.test(p.caption?.text || ''),
    hasLink: /https?:\/\//.test(p.caption?.text || '')
  }));

  // Engagement by characteristics
  const withQuestions = allPosts.filter(p => p.hasQuestion);
  const withoutQuestions = allPosts.filter(p => !p.hasQuestion);

  if (withQuestions.length > 0 && withoutQuestions.length > 0) {
    const qAvg = withQuestions.reduce((s, p) => s + p.engagement, 0) / withQuestions.length;
    const noQAvg = withoutQuestions.reduce((s, p) => s + p.engagement, 0) / withoutQuestions.length;
    const diff = ((qAvg / noQAvg - 1) * 100).toFixed(0);
    console.log(`\nPosts with questions get ${diff}% ${parseInt(diff) > 0 ? 'more' : 'less'} engagement`);
  }

  return allPosts;
}

await contentAnalysis('garyvee');

Weekly Growth Tracking

Track follower growth and engagement changes over time:

import json
from datetime import datetime

def track_threads_growth(handles):
    """Snapshot Threads metrics for weekly tracking"""
    timestamp = datetime.now().strftime('%Y-%m-%d')
    snapshots = []

    for handle in handles:
        profile_resp = requests.get(
            f'{BASE_URL}/v1/scrape/threads/profile',
            params={'handle': handle},
            headers=headers
        )
        profile = profile_resp.json().get('data', {})

        posts_resp = requests.get(
            f'{BASE_URL}/v1/scrape/threads/user-posts',
            params={'handle': handle},
            headers=headers
        )
        posts = posts_resp.json().get('data', {}).get('posts', [])

        avg_likes = sum(p.get('like_count', 0) for p in posts) / max(len(posts), 1)

        snapshots.append({
            'date': timestamp,
            'handle': handle,
            'followers': profile.get('follower_count', 0),
            'avg_likes': round(avg_likes),
            'posts_sampled': len(posts)
        })

    # Save snapshots
    log_file = 'threads-growth-tracking.json'
    history = []
    if os.path.exists(log_file):
        with open(log_file, 'r') as f:
            history = json.load(f)
    history.extend(snapshots)
    with open(log_file, 'w') as f:
        json.dump(history, f, indent=2)

    # Show week-over-week changes
    for snap in snapshots:
        prev = [h for h in history
                if h['handle'] == snap['handle'] and h['date'] != snap['date']]
        if prev:
            last = prev[-1]
            follower_change = snap['followers'] - last['followers']
            likes_change = snap['avg_likes'] - last['avg_likes']
            print(f"@{snap['handle']}: {snap['followers']:,} followers "
                  f"({'+' if follower_change >= 0 else ''}{follower_change:,}) | "
                  f"avg likes: {snap['avg_likes']:,} "
                  f"({'+' if likes_change >= 0 else ''}{likes_change:,})")
        else:
            print(f"@{snap['handle']}: {snap['followers']:,} followers | "
                  f"avg likes: {snap['avg_likes']:,} (first snapshot)")

    return snapshots

# Track weekly
track_threads_growth(['zuck', 'garyvee', 'mkbhd'])

Threads Engagement Benchmarks (2026)

Based on publicly available data across accounts of different sizes:

Follower RangeAverage ERAvg LikesAvg Replies
<10K3-8%50-2005-20
10K-50K1.5-4%200-80010-40
50K-200K0.8-2%500-2,00020-80
200K-1M0.3-1%1,000-5,00030-150
1M+0.1-0.5%2,000-20,00050-500

Threads engagement rates are currently higher than Twitter/X — mostly because the algorithm still pushes content to followers and the platform is less saturated.


Should Your Brand Invest in Threads?

Use this framework to decide:

Threads is worth it if:

  • Your audience is 18-35 and text-savvy
  • You already have an Instagram presence (cross-follow effect)
  • Your content is conversational (takes, opinions, tips)
  • You're in tech, media, fashion, or lifestyle

Skip Threads if:

  • Your audience is primarily professional (LinkedIn is better)
  • Your content is visual-first (stay on Instagram)
  • You're in B2B SaaS (Threads audience is B2C-heavy)
  • You don't have bandwidth for another platform

The data will tell you. Run the benchmark analysis above. If your competitors are getting solid engagement on Threads, you should be there too.


Get Started

Sign up free — track any Threads profile's performance in minutes.

Full Threads API docs: docs.sociavault.com/api-reference/threads


Found this helpful?

Share it with others who might benefit

Ready to Try SociaVault?

Start extracting social media data with our powerful API. No credit card required.