How do you track a tweet with an API?
Last updated August 24, 2026
Two shapes answer this. Re-reading tweet/detail on a timer costs $0.0008 each time and gives you fresh favorite_count and view_count numbers. A monitor is a standing watch on a handle that pushes new posts to your endpoint for nothing. Prove it is working through monitor/health, which reports deliveries_24h split into pending, delivered and failed.
Every rate here is the pricing TwitterAPIs publishes. The billed rate is $0.0008 per call; $0.04 per 1,000 tweets is derived from it at a full 20-tweet page, which is the default page size rather than a guaranteed yield (source: twitterapis.com/pricing).
Polling one post versus watching an account
These answer different questions. tweet/detail resolves one id into its object with favorite_count, retweet_count, reply_count and view_count as they stood at fetch time, so calling it repeatedly is how you chart a post's numbers moving. A monitor never revisits a post that already exists: it watches a handle, tells you when a new one appears, and stops caring about it after that. Charting a known post means polling, catching arrivals means a monitor, and a system that needs both runs both. They also differ in how they prove themselves. A poll proves itself by returning a body. A watch is silence by design, so it needs its own health routes before you can trust the quiet.
What one poll actually returns
tweet/detail takes a single required parameter, id, the numeric post id as a string. The response is small: a top-level id echoing what you asked for, and a tweet object holding text with URLs already expanded, created_at, a nested author, and the four engagement counters. Every counter is documented as a value at fetch time rather than a live figure, so two reads minutes apart can legitimately disagree and neither one is wrong. view_count is the field to treat as optional, since it is present only when the source exposes it. Store the moment of each read alongside the numbers, otherwise a genuinely flat series and a collector that quietly stopped look identical in your table later.
What a polling loop costs
A standard read is $0.0008, so the arithmetic is worth doing before you write the loop rather than after. Checking one post every minute is 1,440 calls a day, which comes to $1.15 a day and about $34.56 across thirty days for that single post. Ten posts on the same cadence is ten times that. The $0.50 of signup credit covers 625 calls, so a once-a-minute watch exhausts it in roughly ten hours. Dropping to once every five minutes takes the same job to 288 calls a day. Pick the interval from how fast the numbers you care about actually move, since most engagement curves stop repaying minute resolution after the first hour.
Creating a standing watch
A monitor takes handle, the X username without the @, plus the webhook_id it should deliver to. The response echoes a normalized handle, lowercased with the @ stripped, along with id, status, domain_filter, include_replies, poll_interval_ms and created_at. The first poll after creation sets a baseline at that account's newest post, and there is no backfill, so watching an account with ten thousand existing posts sends zero webhooks for any of them. Only what the handle publishes afterwards is delivered. Creation can be refused with 503 capacity_unavailable when the shared polling pool cannot take another watch at the published interval, which is a retry-later answer rather than a fault in your request.
Narrowing what gets delivered
Two optional switches shape delivery without changing what is polled. domain_filter restricts delivery to posts linking a given host and is normalized server-side: lowercased, with scheme, path, query, fragment, a leading www. and a trailing port all stripped, then rejected with 400 if what remains is not a valid hostname shape. Matching is by hostname boundary rather than substring, so example.com matches blog.example.com and never notexample.com. include_replies defaults to true and has to be a real boolean, because a string or a number is refused with 400 rather than coerced. Filtered-out posts still advance the cursor and count toward the account's tweets_domain_filtered metric, so they are excluded from delivery rather than lost.
Reading one watch's cursor
GET monitor/{id}/health returns id, status, degraded, poll_interval_ms, events_possibly_missed, last_tweet_id and last_poll_at. The two booleans are what you alert on. degraded means the shared pool is not polling this watch as expected, and events_possibly_missed means a gap in polling could have swallowed a post. last_tweet_id and last_poll_at are both null until the first poll tick has run, so a freshly created watch legitimately looks empty for a moment and your alerting should not read that as a fault. Compare last_poll_at against poll_interval_ms to judge how stale a cursor really is, rather than trusting the interval figure on its own. The id on the response is only an echo of the one you passed in the path, so it tells you nothing new.
The account-wide rollup
GET monitor/health answers for the whole key with no path parameter, which also means it never returns 404. The body has three parts: status, either operational or degraded when any active watch is being served slower than the published interval, a monitors object counting active, paused and total, and a deliveries_24h object counting pending, delivered and failed attempts over the last day. A key with no watches gets 200 with every count at zero rather than an error, so it is safe to hit from a dashboard on a fixed timer. failed climbing while delivered stays flat points at your receiver, not at the polling side. pending is the bucket to read next, because a queue that keeps growing means attempts are being made and not landing.
Measuring real lag instead of assuming it
GET monitor/deliveries lists recent events newest first, with limit accepting 1 to 200 and defaulting to 50. Each event carries id, monitor_id, tweet_id, status, tweet_created_at, detected_at, delivered_at and three integers. detected_lag_ms is X's own post timestamp subtracted from the moment the scheduler queued the send, delivery_lag_ms is queue to POST, and total_lag_ms adds the two. A null in any of them means one timestamp was unavailable for that specific event rather than an error. The documented sample event shows 33984, 3596 and 37580 respectively, which tells you the shape of the answer rather than promising anything about yours. The feed is scoped to your own key, so nothing belonging to another account ever appears in it.
Why poll_interval_ms is not a guarantee
The field reporting the interval says so itself: it is documented as not a published SLA yet, subject to change as real X search-index ingestion lag gets measured. The deliveries route repeats the point, describing detected_lag_ms as the honest number for how fast detection currently is while noting it is bound by the shared poll interval rather than by any commitment. So do not derive an alert threshold from the interval. Pull a few hundred real events, read your own detected_lag_ms distribution, and set the threshold from that, then keep watching it, because the figure moves with the shared pool rather than with anything on your account. Alert on a change in that distribution rather than on any absolute millisecond number. $0.0008 a call, per the rates we publish.
Polling a post against running a monitor
| What you are asking | Poll tweet/detail | Run a monitor |
|---|---|---|
| What it answers | How this post's counters changed | That the account posted something new |
| Cost per check | $0.0008 | $0 |
| Reach into older posts | Any id you already hold | None, the baseline starts at creation |
| Who sets the timing | You, by choosing the interval | The shared pool, reported as poll_interval_ms |
| Filtering available | None, you fetch then discard | domain_filter and include_replies |
| How you confirm it works | The response body arrives | monitor/health and monitor/deliveries |
| What makes it stop | Your scheduler or a spent balance | A deleted webhook or a paused status |
Filtered Stream prioritizes data hydration and delivery, with approximately 6-7 seconds of P99 latency.
Questions and answers
- Can I be notified when a specific tweet gets replies?
- Not with a monitor. A monitor is keyed to a handle and delivers that account's new posts, never activity on an existing one. To track replies to a particular id you re-read tweet/replies on an interval and diff the result against what you already stored. Each of those pages is an ordinary billed read, so the cadence you pick is a spending decision as much as a freshness one.
- How do I know a watch is still delivering?
- Call monitor/health. It needs no path parameter, is scoped by your key and never 404s, so a key with nothing configured answers 200 with every counter at zero. Read the deliveries_24h object rather than the status string alone: failed rising while delivered stays flat is a receiver problem, and both sitting at zero for an account that posts is worth checking per watch.
- Does creating a monitor pull in the account's old posts?
- No, and that is deliberate. The opening poll records the newest existing post as a baseline and delivers nothing for it, so an account with a decade of history sends you nothing at all until it posts again. If you want that history it is a separate read job against the timeline endpoints, billed per page like any other read and completely independent of the watch.
- What does degraded actually mean?
- It says the shared polling pool is currently serving that watch more slowly than its published interval. The monitor keeps running and this is not an outage, but paired with events_possibly_missed it tells you a posting window may have slipped past unseen. Check last_poll_at on the same response to see how stale the cursor is, then check the account-wide status field to see whether the condition is yours alone.
- Can I pause a watch instead of deleting it?
- Yes. POST to monitor/{id} with status set to paused, and the same route resumes it with active. Resuming re-runs the capacity and per-account cap checks that creation runs, because it puts load back on the shared pool, so a resume can be refused where a create would be. Sending anything other than exactly active or paused in that field is a 400 rather than a quiet no-op.
- What does watching an account cost per month?
- Nothing at all. Every monitoring and webhook route is zero-rated, so a standing watch adds no line to your balance however many posts it forwards. The comparison worth making is against the loop it replaces: checking one account once a minute is 1,440 billed reads a day, and the watch delivering that same information costs nothing and reacts faster than the interval you would have picked.
- Can I turn off replies without recreating the monitor?
- Send include_replies set to false in a POST to monitor/{id}. Replies keep being polled and keep advancing the cursor, they simply stop being delivered to you. An update carrying only that field never re-runs the capacity or per-account cap checks, because narrowing delivery changes neither poll cadence nor how much of the shared pool you take, which makes it safe on a live watch.
- What is a 503 capacity_unavailable telling me?
- That the shared polling pool cannot currently serve one more watch at the published interval. It describes the pool at that moment rather than anything malformed in your request, so the documented response is to retry later rather than to change parameters. The same check runs when you resume a paused watch, which is why a resume can fail on a request that succeeded when you first created it.
- Can I move a monitor to a different account?
- No. A monitor's watched handle cannot be changed, so switching accounts means deleting it and creating a new one, which also resets the baseline to the new account's newest post. Everything else is editable in place: status, webhook_id, domain_filter and include_replies can all change in one call, and sending domain_filter as null clears an existing filter entirely. Recreating also resets the cursor, so the replacement delivers nothing for posts made before it existed.
- What latency should I expect from a monitor?
- Measure it rather than assume it. The field reporting the poll interval is explicitly documented as not a published SLA, and detected_lag_ms is described as the honest figure for how fast detection currently is, bound by the shared interval. Pull a few hundred events from the deliveries route, look at detected_lag_ms and delivery_lag_ms separately, and set alerting from the distribution you actually observe.
Keep reading
Start with $0.50 in free credits
No credit card. Roughly 12,500 tweets to test every endpoint.