Monitor X trending topics on a schedule

A single POST call returns ranked X trending topics as JSON. This page covers polling intervals, deduplication, alert thresholds, and monthly request budgeting so a cron job or serverless worker can watch the feed without burning quota.

Pulling the live X trending feed once is straightforward. Keeping a reliable monitor running for weeks without duplicate alerts, quota overruns, or silent failures takes explicit scheduling and state. The X trending topics API returns ranked topic names as JSON; this page covers how to wire that response into cron, serverless, and alert pipelines.

What one poll returns

Each call uses the same body documented on the X API page:

{
  "mode": "top_trends",
  "type": "X (Twitter)",
  "limit": 50
}

The response includes as_of_ts, count, and a data array of [rank, topic_name] pairs. Store the full payload, not just topic names. Rank position matters when the alert logic cares about a topic climbing from #18 to #3 in one interval.

Request budgeting

Trends MCP counts one source per get_top_trends call. The free tier allows 100 requests per month with no credit card. Paid plans raise that ceiling; see pricing for current limits.

Polling interval Requests per day (24h) Requests per 30 days
Every 15 minutes 96 2,880
Every 30 minutes 48 1,440
Every 60 minutes 24 720
Every 4 hours 6 180
Twice daily (9am, 5pm) 2 60

Continuous polling at sub-hourly intervals exceeds the free tier within days. Practical free-tier setups:

Cron patterns

Linux cron (hourly, weekdays 8am-6pm UTC)

0 8-18 * * 1-5 /usr/local/bin/poll-x-trends.sh

GitHub Actions (twice daily)

on:
  schedule:
    - cron: "0 13 * * 1-5"
    - cron: "0 21 * * 1-5"
jobs:
  poll:
    runs-on: ubuntu-latest
    steps:
      - name: Fetch X trending topics
        env:
          TRENDS_API_KEY: ${{ secrets.TRENDS_API_KEY }}
        run: |
          curl -s -X POST https://api.trendsmcp.ai/api \
            -H "Authorization: Bearer $TRENDS_API_KEY" \
            -H "Content-Type: application/json" \
            -d '{"mode":"top_trends","type":"X (Twitter)","limit":50}' \
            -o /tmp/x-trends.json

AWS EventBridge + Lambda: trigger on a rate expression of rate(4 hours) and keep the handler under 30 seconds. Persist state in DynamoDB or S3.

Python monitor with deduplication

import json
import requests
from pathlib import Path

STATE_FILE = Path("x_trends_state.json")
API_URL = "https://api.trendsmcp.ai/api"
HEADERS = {"Authorization": "Bearer YOUR_API_KEY"}

def load_state():
    if STATE_FILE.exists():
        return json.loads(STATE_FILE.read_text())
    return {"topics": {}, "last_poll": None}

def save_state(state):
    STATE_FILE.write_text(json.dumps(state, indent=2))

def poll_x_trends():
    res = requests.post(
        API_URL,
        headers=HEADERS,
        json={"mode": "top_trends", "type": "X (Twitter)", "limit": 50},
        timeout=30,
    )
    if res.status_code == 429:
        raise RuntimeError("Monthly quota exhausted; stop polling until next cycle")
    res.raise_for_status()
    return res.json()

def diff_topics(current, previous):
    alerts = []
    prev_names = set(previous.get("topics", {}).keys())
    for rank, name in current["data"]:
        name_lower = name.lower()
        if name_lower not in prev_names:
            alerts.append({"type": "new", "topic": name, "rank": rank})
        else:
            old_rank = previous["topics"].get(name_lower, {}).get("rank", 99)
            if old_rank - rank >= 5:
                alerts.append({"type": "climb", "topic": name, "from": old_rank, "to": rank})
    return alerts

def main():
    state = load_state()
    payload = poll_x_trends()
    alerts = diff_topics(payload, state)

    new_state = {
        "last_poll": payload["as_of_ts"],
        "topics": {t[1].lower(): {"rank": t[0]} for t in payload["data"]},
    }
    save_state(new_state)

    for alert in alerts:
        print(json.dumps(alert))

if __name__ == "__main__":
    main()

Run this script from cron. Pipe alert JSON to a Slack relay as described on the Slack trend alerts page.

Alert thresholds that reduce noise

Raw diffs fire on every minor rank shuffle. Tighter rules:

Skip alerts when count in the response is zero or when as_of_ts is older than two polling intervals. Stale timestamps usually mean an upstream fetch failed silently.

Cross-check before alerting

A topic can trend on X without broader search interest. Before paging a team, one extra request validates whether the spike is isolated:

growth = requests.post(
    API_URL,
    headers=HEADERS,
    json={
        "source": "google search",
        "keyword": alert["topic"],
        "percent_growth": ["7D", "30D"],
    },
).json()

If 7-day Google Search growth is flat or negative, label the Slack message as "X-only spike" so reviewers know the signal may be platform-specific chatter.

Error handling

Status Action
401 Fix API key in secrets manager; do not retry
429 Stop scheduled jobs until next billing cycle
500 Retry once after 60 seconds; log and alert ops if second attempt fails
Timeout Retry once; if feed is consistently slow, widen the cron interval

Compared with the official X API pricing model, where each post read costs $0.005, a monitoring stack that tried to infer trends by reading thousands of tweets would burn credits fast. The aggregated trending feed avoids that cost structure entirely.

When monitoring is not enough

X trending data is live-only. There is no historical time series for rank position over months. Teams that need longitudinal social volume should pair this monitor with Reddit or Google Search series from the same API. For brand-specific mention tracking at post level, enterprise social listening platforms remain the fit; see the social listening API pricing comparison for contract-scale options.

Common questions

Every 15 to 30 minutes is enough for most monitoring use cases. X trending lists shift throughout the day, but minute-level polling rarely changes decisions and burns monthly quota fast. A 30-minute cron uses roughly 48 requests per day if run continuously, which exceeds the free tier. Budget polling to business hours or reduce frequency to hourly on the free plan.
One get_top_trends call with type X (Twitter) counts as one Trends MCP request regardless of limit. Pulling 25 topics costs the same as pulling 200. Batch related checks into the same cron run rather than firing separate jobs per topic.
Yes. Store the previous response data array in a database or object store keyed by date. On each poll, diff the current topic names against the stored set. Topics that appear for the first time or jump more than five rank positions in one interval are typical alert candidates.
Trends MCP returns HTTP 429 with a rate_limited code. Monitoring jobs should treat 429 as a hard stop for the rest of the billing cycle, log the timestamp, and resume on the next cycle. Do not retry in a tight loop.