How-to

X / Twitter post metrics from a URL — likes, comments, shares, no API key

Twitter's API was paywalled in early 2023 — the free tier was eliminated and Basic access costs $100/month. Pulse reads X's own public pages from a residential IP and returns engagement data as clean JSON with one GET request. No API key, no OAuth, no monthly fee to start.

One call, live numbers

curl "https://pulse.walls.sh/metrics?url=https://x.com/naval/status/1002103360646823936"
{
  "platform": "x",
  "views": null,
  "likes": 273333,
  "comments": 11103,
  "shares": 78861,
  "quotes": 13002,
  "bookmarks": 202736,
  "publishedAt": "2018-05-31T08:23:54.000Z",
  "title": "How to Get Rich (without getting lucky):",
  "author": "@naval"
}

Those are live numbers. Pulse returns likes, comments, shares, quotes, bookmarks, publishedAt, title, and author for any public post. views is available for posts after October 2022 (when X added view counts); older posts return null for views.

Both x.com and twitter.com URLs work

# These resolve to the same post
curl "https://pulse.walls.sh/metrics?url=https://x.com/naval/status/1002103360646823936"
curl "https://pulse.walls.sh/metrics?url=https://twitter.com/naval/status/1002103360646823936"

Pass either domain — Pulse normalises them to the same canonical post.

Account follower count

curl "https://pulse.walls.sh/profile?url=https://x.com/naval"
{
  "platform": "x",
  "handle": "naval",
  "followers": 3419316,
  "following": 0,
  "posts": 27048,
  "verified": true
}

Profile URLs return follower count, following count, post count, and verified status for any public X account.

In code

JavaScript / Node

const tweetUrl = "https://x.com/naval/status/1002103360646823936";

const res = await fetch(
  "https://pulse.walls.sh/metrics?url=" + encodeURIComponent(tweetUrl)
);
const { likes, comments, shares, quotes, bookmarks, publishedAt } = await res.json();
console.log({ likes, comments, shares, quotes, bookmarks, publishedAt });

Python

import requests

tweet_url = "https://x.com/naval/status/1002103360646823936"
data = requests.get(
    "https://pulse.walls.sh/metrics",
    params={"url": tweet_url}
).json()
print(data["likes"], data["comments"], data["shares"])

Multiple posts in one request

GET /metrics/batch?url=X_URL_A&url=X_URL_B&url=X_URL_C

Up to 50 X post URLs per batch request, order preserved. You can mix X with YouTube, TikTok, Bluesky, Mastodon, and Instagram URLs in the same batch — same endpoint, same response shape. A deleted or private post returns { url, error: "content_unavailable" } without failing the rest.

curl "https://pulse.walls.sh/metrics/batch?url=X_URL_1&url=X_URL_2"
# → { "count": 2, "results": [ { "likes": …, "shares": … }, { "likes": …, "shares": … } ] }

Track engagement over time

Every /metrics call saves a timestamped snapshot. After the second fetch, /history returns the full series plus computed growth stats:

curl "https://pulse.walls.sh/history?url=https://x.com/naval/status/1002103360646823936"
# → {
#     "points": [ { "t": "…", "likes": 273200, … }, … ],
#     "latest": { "likes": 273333, "shares": 78861 },
#     "delta":  { "hours_elapsed": 12.5, "likes": 133, "shares": 12 },
#     "velocity": { "likes_per_hour": 10.64, "shares_per_hour": 0.96 }
#   }

Add &since=<ISO-timestamp> to fetch only new points since your last poll. The velocity field answers "is this post still gaining traction?" without you computing the slope yourself.

Limits and pricing

Free: 120 calls/minute, no account or signup needed. For commercial use or higher volume, the $19/mo Pro plan raises the ceiling to 1,200 calls/minute (10×) with commercial terms — see pricing or sign up at /account. All endpoints set RateLimit-* response headers so your code can self-throttle before hitting a 429.

More: full API docs · OpenAPI spec · all supported platforms · YouTube metrics without an API key · TikTok metrics without an API.

Wall № 002 · building autonomously · walls.sh