Pull engagement metrics into your own dashboard

Collect publication events, poll platform metrics, and backfill a table you control.

Who it's for

Developers building an internal reporting dashboard or a creator's personal analytics view.

What you'll have

A table of post URLs, publication times and engagement, backed by the latest metrics and historical snapshots.

Before you start

  • An active ThreadMaster subscription and a key from Settings → Developer API; set THREADMASTER_API_KEY in your environment.
  • Python 3 with requests installed (python -m pip install requests). Run each recipe's Python blocks in order in the same script.
  • Published posts, publish:read and webhooks:manage scopes, an HTTPS receiver and durable storage.
  • A daily job runner and the signature verification function linked below for your webhook receiver.

Build the workflow

  1. Subscribe to publication events

    Register once and save the one-time signing secret securely. Use the linked verifier on raw webhook bytes before accepting any event.

    import os
    import requests
    
    BASE = "https://threadmaster.ai/api/public/v1"
    session = requests.Session()
    session.headers.update({"Authorization": "Bearer " + os.environ["THREADMASTER_API_KEY"]})
    
    def api(method, path, **kwargs):
        response = session.request(method, BASE + path, timeout=90, **kwargs)
        response.raise_for_status()
        return response.json()
    
    webhook = api("POST", "/webhooks/", json={
        "url": "https://your-site.example/metrics-events",
        "events": ["post.published"],
        "description": "Dashboard publication inbox"
    })
    # Store webhook["secret"] securely for verification.
    print(webhook["endpoint"]["id"])
  2. Store each platform, job id and permalink

    Verify the signature, save and queue the event, then acknowledge promptly so processing can happen in a worker. Save or update one record per (platform, job_id) to avoid duplicates, using published_at for the job's completed_at.

    def publication_record(event):
        if event["type"] != "post.published":
            return None
        job = event["data"]
        return {
            "platform": job["platform"], "job_id": job["id"],
            "permalink": job["permalink"], "published_at": job["completed_at"]
        }
    # Your worker: upsert publication_record(verified_event) in durable storage.
  3. Fetch latest metrics and snapshots daily

    Each day, load saved publication records and fetch metrics from each platform's endpoint. Save latest for the dashboard and snapshots for trends, keeping one snapshot per post and capture time to avoid duplicates.

    def fetch_metrics(record):
        job_id = record["job_id"]
        paths = {
            "x": f"/x/posts/{job_id}/metrics/",
            "threads": f"/threads-app/posts/{job_id}/metrics/",
            "facebook": f"/facebook/posts/{job_id}/metrics/",
        }
        metrics = api("GET", paths[record["platform"]])
        return metrics["latest"], metrics["snapshots"]
    # Daily runner: load records, fetch_metrics(record), then persist the results.
  4. Shape a table without inventing metrics

    Build shared columns for platform, post, published_at, likes, impressions/views and replies, with separate Facebook reactions and comments. Leave unsupported Facebook metrics empty and preserve None as missing data so the table does not imply zero engagement.

    def dashboard_row(record, latest):
        platform = record["platform"]
        return {
            "platform": platform,
            "post": record["permalink"],
            "published_at": record["published_at"],
            "likes": latest.get("likes"),
            "impressions/views": latest.get("impressions" if platform == "x" else "views"),
            "replies": latest.get("replies"),
            "reactions": latest.get("reactions"),
            "comments": latest.get("comments"),
        }
  5. Backfill older X posts with a cursor

    Choose a since timestamp and follow every page of GET /x/posts/?since= to collect older completed jobs. Replace print with storage that saves or updates records using the same platform/job-id key as live events, avoiding duplicates.

    params = {"since": "2026-01-01T00:00:00Z", "limit": 200}
    while True:
        page = api("GET", "/x/posts/", params=params)
        for job in page["data"]:
            if job["status"] == "posted":
                print(publication_record({"type": "post.published", "data": job}))
        if not page["has_more"]:
            break
        params["cursor"] = page["next_cursor"]

Use the documented Python or Node signature verification snippet in your receiver.

Watch out for

  • X metrics: likes, impressions, replies, retweets, quotes and bookmarks. Threads metrics: views, likes, replies, reposts, quotes and shares.
  • Facebook metrics: reactions, comments and shares. Facebook has no impressions metric; reactions are not a likes count.
  • Budget daily polling and backfills against the shared per-key request allowance. Check the linked rate limits and X-RateLimit-Remaining; space requests to respect burst limits too.

Check the API rate limits and quotas before running your workflow at scale.

Get started

Write your next Twitter thread or LinkedIn post today

Sign up in under a minute. $9 a month, cancel anytime.

Get started →