Skip to content

How do you get Twitter trends with an API?

Last updated August 24, 2026

A WOEID is the Where On Earth ID that scopes a trend list to one place, and 1 means Worldwide. Call trends/locations once to find the id for a city or country, cache it, then hit trends with either woeid or a country name. Both routes bill $0.0008, so polling one location every 15 minutes is 96 reads a day.

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).

WOEID, and the lookup you run once

trends/locations takes no parameters at all and returns every place X publishes a trend list for. Each row carries name, woeid, country and placeType, alongside a parentid that positions it in the hierarchy and a url. Worldwide is woeid 1. The reference says outright that this list changes rarely and to cache it rather than calling it before each trends request, which is the difference between one billed call per polling cycle and two, doubled across every location you watch. One quirk deserves handling in your parser rather than a surprise in production: countryCode and placeType come back in camelCase here, unlike the rest of the API, because that is X's upstream shape passed through unchanged. Renaming it would break consumers already parsing it, so it stays.

Two ways to name the place

The trends route accepts woeid as an integer, or country as a name that gets resolved to its id server-side, and you supply one or the other rather than both. A country string X does not recognise comes back as a 400, and the reference is careful to say 400 and not 502. That distinction is worth coding against: a 400 is a rejected input, not an upstream failure, so it belongs in your validation branch and never in your retry branch. Retrying a misspelled country name just spends calls at the same rate as a working one. If the value originates from a user rather than from your own configuration, check it against the cached location list before sending it, and fall back to the numeric woeid whenever you already have it.

What a trend row actually holds

Each entry in the trends array carries name, the display string such as #Formula1. It also carries query, which is that same term already URL encoded and therefore ready to drop into a search request without you escaping it a second time. tweet_volume gives a figure when X reports one. promoted_content flags a paid placement, so a dashboard can separate bought positions from organic ones. url points at the corresponding twitter.com search page, which is what you link a human to. The query field is the useful hook for automation: hand it to the query parameter on tweet/advanced_search and one more standard read takes you from knowing a term is trending to reading the posts that are driving it.

tweet_volume is null more often than you expect

Volume comes back null whenever X reports no figure for that trend, and the reference is explicit that this is X's behaviour rather than a gap on the response side. The null is passed through untouched rather than filled with a placeholder, which keeps a genuine zero distinguishable from an absent measurement, and that distinction is the reason not to coerce it. Any chart or ranking built on tweet_volume therefore needs a null branch rather than a zero default, or a trend with no published figure will render as the quietest thing on the board when it may well be the loudest. Fall back to the row's position in the list when volume is missing, since X returns the trends in its own order and that order carries information.

as_of is your staleness check

Alongside the rows sits as_of, an ISO 8601 timestamp recording when X computed this particular list, plus a location object echoing the resolved name and woeid and a count of the rows returned. Compare as_of against the value on your last stored snapshot and you can tell a genuinely refreshed list from a repeat of one you already hold. That single comparison stops a scheduled job filling a table with thousands of identical rows, and it turns your history into a record of when the rankings actually moved rather than a record of when your cron happened to fire. It also gives you a cheap health signal: an as_of that stops advancing means the upstream computation has stalled, not that your job has.

The envelope, and what count is counting

A trends response has exactly four top-level fields: trends, location, as_of and count. count describes how many rows came back in this response, bounded by the optional count parameter if you sent one, which is the only lever you have over response size. There is no cursor here and no next page, because a trend list is a snapshot with a natural size rather than a collection to walk. That makes the request shape unusually simple compared with the paged read routes: one call in, one complete list out, nothing to loop over, no termination condition to get wrong and no opaque token to persist between runs. The whole integration is a scheduled call and a write.

Cadence, and what a scheduled poll costs

Trend rankings move over minutes rather than seconds, so a quarter-hourly job usually tracks them closely enough for a dashboard or an alert. That works out to 96 reads a day at the standard rate, roughly eight cents, or near $2.30 across a month for a single location. Running much faster than that mostly rewrites the same rankings and bills a call each time it does, which is the trap in a naive one-minute cron. The location lookup you cached at deploy time adds nothing after the first run, so the recurring cost of the whole job really is one billed call per cycle. The $0.50 credited at signup covers 625 reads, about a week of single-location polling.

Watching several places at once

Multiple locations multiply the cadence arithmetic directly: five cities on a fifteen minute cycle is 480 calls a day, around forty cents, and twenty cities is 1,920 calls, near a dollar and a half. All of them fit comfortably inside the 600 per minute and 20 concurrent ceilings, since a fan-out of twenty requests once every fifteen minutes uses a rounding error of the allowance. Fire them concurrently rather than in sequence and the whole sweep completes in one upstream round trip. Then compare the resulting lists against each other, because a term appearing in one country's list and not its neighbour's is usually the signal worth acting on, rather than the worldwide list that everybody else is already looking at.

What the trends route will not tell you

There is no date parameter anywhere on this endpoint, so it answers only for now. Any back-history is whatever your own snapshots hold, which is the practical reason to store every response keyed on as_of and location rather than overwriting a single current row each cycle. It also returns no posts, only the terms and their volumes, so working out why something is trending means a second call carrying that trend's query string into search. And it covers only the places trends/locations enumerates, so a city missing from that list has no trend feed to request at all, and no parameter will conjure one. There is likewise no filter on the rows themselves, so removing promoted placements or terms below a volume threshold happens in your own code after the list has already arrived and metered. That rate is $0.0008 a call, per our own rate card.

Scoping a trend request

InputExampleBehaviour
woeidwoeid=1Worldwide list, ids come from trends/locations
countrycountry=United StatesName resolved to its WOEID server-side
An unknown countrycountry=AtlantisRejected with a 400, not a 502
Both omittedno scope givenThere is no default place, so supply one of the two
countcount=10Caps how many trend rows come back
A place not in the listan uncovered cityNo feed exists to request, so there is nothing to call
The Trends by WOEID endpoint returns trending topics for a specific geographic location, identified by a Yahoo! Where On Earth ID (WOEID).
X Developer Platform, Trends documentation. Source

Questions and answers

What is a WOEID and where do I find one?
It is the Where On Earth ID that identifies a place, and 1 is the Worldwide list. Valid values come from trends/locations, which returns every covered place together with its id, country and place type. Look yours up once, keep it in configuration next to the rest of your job settings, and pass it on every trend request from then on. There is no discovery call needed at runtime once that value is pinned.
Can I ask for trends by country name?
Yes. Pass country instead of woeid and the name is resolved to an id before the lookup runs, so you never touch the numeric value. A name that does not resolve returns a 400, so validate against the cached location list whenever your input comes from a user rather than from your own config. Supply one of the two parameters, not both, and prefer the woeid when you already hold it.
Why is tweet_volume sometimes null?
Because X does not publish a volume figure for every trending term. The null is passed straight through rather than filled with a placeholder, which keeps a real zero distinguishable from an absent measurement. Treat it as unknown in any ranking, branch on it explicitly in your chart code, and sort on the row's position in the returned list when the number is missing, since X returns the trends in a meaningful order of its own.
How often should a trend job run?
A quarter-hourly cadence tracks the list closely without churn, since these rankings shift over minutes rather than seconds. Read as_of on each response and skip storing a snapshot whose timestamp matches the previous one, so your history stays a record of changes rather than of polls. Running much faster than that mostly rewrites the same rankings and bills a call every time it does, which is real spend for no new information.
Do I need to call the locations endpoint every time?
No, and you should not. The reference says the list changes rarely and to cache it instead of calling it before each trends request. One fetch at deploy time, or a weekly refresh job, is plenty for a list this stable. Doing it that way keeps the recurring polling job down to a single billed call per cycle instead of doubling it, and that doubling compounds across every location you happen to be watching.
Can I get the posts behind a trend?
Not from this route, which returns terms rather than content. Each row does carry a query field holding the term already URL encoded, so hand that value straight to the query parameter on tweet/advanced_search and one more standard read gives you the posts. That two-call pattern is how a trend dashboard goes from a list of names to something a reader can act on, and it costs $0.0016 per trend you expand.
Is there any trend history in the API?
No. There is no date parameter, so the endpoint describes the present moment only. Whatever back-history you want has to come from your own stored snapshots, which is the practical argument for writing every response into a table keyed on as_of and location rather than overwriting a single current row each cycle. Storage is cheap and the data is small, so keep everything from the first run onward.
Why are some fields camelCase here?
On trends/locations specifically, countryCode and placeType arrive in camelCase while the rest of the API uses snake_case. That is X's upstream shape passed through unchanged, kept as-is deliberately because renaming it would break consumers already parsing the field. Handle both conventions in whatever maps this response into your own types, and the inconsistency stops mattering after the first ten lines of your adapter.
How many locations can I watch at once?
As many as your budget allows, since the request ceilings are not the constraint here. Twenty locations on a fifteen minute cycle is 1,920 calls a day, comfortably inside 600 requests a minute and 20 concurrent. Fire the fan-out concurrently rather than sequentially, then compare lists across places, because a term trending in one country and not its neighbour is usually the more interesting signal for anyone doing regional monitoring.
What does trend monitoring cost to run?
Both routes sit at the standard read rate of $0.0008. A fifteen minute cycle on one location is 96 calls a day, roughly eight cents, and the cached location lookup does not repeat after the first run. Five locations on the same cycle is 480 calls a day. The $0.50 credited at signup covers 625 reads, which is about a week of single-location polling before a card enters the picture.

Start with $0.50 in free credits

No credit card. Roughly 12,500 tweets to test every endpoint.