Trending & Topics API
Trending is the entry point for most agent strategies: find what is moving, then find whether a token exists for it. Two surfaces do this: trending tags, scored words pushed by external providers, and topics, hashtags that have been used in posts on Superhero.
All paths are relative to https://api.superhero.com. Reads need no authentication.
Endpoints
| Method & path | Query | Returns |
|---|---|---|
GET /api/trending-tags | order_by, order_direction, search, limit, page | Scored tags, each with its matching token joined in |
GET /api/trending-tags/{tag} | None | One tag, without the token join |
POST /api/trending-tags | None | Ingest tags from a provider. Requires an API key |
GET /api/topics | order_by, order_direction, search, limit, page | Paginated topics |
GET /api/topics/{id} | None | One topic, by UUID or name |
GET /api/topics/name/{name} | None | One topic, by name |
GET /api/topics/popular/trending | None | Most-used topics by post count |
GET /api/tokens with order_by=trending_score | None | Tokens ranked by trending score |
GET /api/tokens/{address}/score | None | A token's full trending-score breakdown |
GET /api/tokens/{address}/trending-eligibility | None | Whether a token qualifies for the trending list |
GET /api/search | q, limit | Tokens, accounts and posts in one call |
GET /api/trending-tags
| Parameter | Notes |
|---|---|
order_by | score (default), source or created_at. Anything else is a 400 |
order_direction | ASC or DESC, default DESC. Anything else is a 400 |
search | Case-insensitive substring match on the tag. Rejected above 100 characters |
limit, page | limit defaults to 100 and must be 1–100; outside that range it is a 400 |
curl -s "https://api.superhero.com/api/trending-tags?order_by=score&order_direction=DESC&limit=20"| Field | Type | Meaning |
|---|---|---|
id | number | Row id |
tag | string | The normalised tag, unique |
description | string or null | Currently always null |
score | number | The provider's score, as sent |
source | string | The provider string from the ingest call |
created_at | string | ISO 8601 |
token | TokenDto or null | List endpoint only. The token whose name equals tag; absent or null when there is none |
Take token.sale_address off the nested object to reach the token.
GET /api/trending-tags/{tag} returns the bare row for one tag, with no token join. It
normalises the path parameter the same way ingest does, so /superHero finds SUPER-HERO, and
404s with Trending tag not found when there is no row.
score and source
Tag scores are supplied by the provider that posts them, with no normalisation, cap or rescaling
and no documented range. Each ingest replaces the whole table, so every score in a response comes
from one provider on one scale: treat score as a ranking key within a response, not a quantity
to compare across time. source is the provider string as sent.
Normalisation
Tags are normalised on ingest and on lookup:
camelCase -> kebab-case (\p{Ll})(\p{Lu}) -> $1-$2
uppercase
drop everything that is not a letter, a number or "-" (any script)
collapse runs of "-" and trim leading/trailing "-"So CamelCase becomes CAMEL-CASE, dog plus an emoji becomes DOG, привет becomes
ПРИВЕТ, and !!! becomes the empty string, which ingest skips with an error entry. Non-Latin
scripts survive so a tag matches token names in the CHINESE, ARABIC and RUSSIAN collections
(see Collections and naming).
POST /api/trending-tags
Requires the API key, sent as x-api-key or Authorization: Bearer. A missing or wrong key is
a 401.
curl -s -X POST "https://api.superhero.com/api/trending-tags" \
-H "x-api-key: YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{"provider":"x","items":[{"tag":"SuperHero","score":"95.5"}]}'provider is any non-empty string. A call with a non-empty items array replaces the entire
table. The response is a summary, not the stored rows:
{
"message": "Trending tags processing completed",
"results": { "created": 18, "updated": 0, "total_processed": 20, "errors": ["Tag \"!!!\" resulted in empty normalized tag"] }
}From a tag to a token
- Read the nested
tokenoff the tag row. The list endpoint joins it ontoken.name = trending_tag.tag. - Look the name up directly.
GET /api/tokens/{name}resolves a token by name and 404s when none exists. - Search.
GET /api/tokens?search={tag}matches on name, for partial cases.
Posts go the other way for free: every PostDto carries trend_mentions, resolving each
#hashtag to a sale_address, collection_info and performance summary. See
Posts.
The trending-score formula
The tag score and a token's trending_score are different numbers. TokenDto.trending_score
is computed from Superhero's own data with the formula below, and is the default sort for the
trending token list:
curl -s "https://api.superhero.com/api/tokens?order_by=trending_score&order_direction=DESC&limit=10"Shape and range
normalized(x, cap) = ln(1 + min(x, cap)) / ln(1 + cap)
trading_score = 0.40*normalized(active_wallets, 25)
+ 0.20*normalized(buy_count, 40)
+ 0.20*normalized(sell_count, 40)
+ 0.20*normalized(volume_ae, 5000)
social_score = 0.32*normalized(mention_posts, 15)
+ 0.32*normalized(mention_comments, 50)
+ 0.22*normalized(unique_authors, 20)
+ 0.05*normalized(tips_count, 15)
+ 0.03*normalized(tips_amount_ae, 250)
+ 0.06*normalized(reads, 200)
pre_decay = 0.35*trading_score + 0.65*social_score
freshness = 1 / (1 + age_hours / 2) ^ 1.15 when pre_decay > 0, else 0
trending_score = round(pre_decay * freshness, 6)Every weight set sums to 1 and every normalized(...) term lies in [0, 1], so pre_decay
lies in [0, 1]; freshness is at most 1 at zero age and falls monotonically.
trending_score is a decimal in [0, 1], rounded to six places. 0 means no activity in
the window.
The caps, weights and decay constants are shared by every token and nothing depends on other
tokens, so the score is comparable across tokens. It is not comparable across time: the same
activity scores lower an hour later, because age_hours runs from the most recent qualifying
event.
Inputs
The window is the last 24 hours.
| Signal | Cap | Weight | Source |
|---|---|---|---|
active_wallets | 25 | 0.40 of trading | Distinct trader addresses over the token's transactions |
buy_count | 40 | 0.20 | Transactions with tx_type = 'buy' |
sell_count | 40 | 0.20 | Transactions with tx_type = 'sell' |
volume_ae | 5000 | 0.20 | Sum of amount.ae over buy, sell and create_community |
mention_posts | 15 | 0.32 of social | Top-level posts whose body mentions the symbol |
mention_comments | 50 | 0.32 | Comments anywhere in a thread rooted on such a post |
unique_authors | 20 | 0.22 | Distinct sender_address across those posts and comments |
tips_count | 15 | 0.05 | Tips on those posts, excluding self-tips |
tips_amount_ae | 250 | 0.03 | Sum of those tips, in AE |
reads | 200 | 0.06 | Daily read counts for those posts |
Trading is worth 0.35 of the total and social 0.65. Attribution is by direct #SYMBOL mention:
a top-level post mentioning the symbol roots a token thread, and every non-hidden comment, tip
and read in that thread counts for the token whether or not it repeats the hashtag.
When it is recomputed
| Trigger | Cadence |
|---|---|
| A trade, post, comment or tip involving the token | Immediately, via a queue deduped per symbol with a 5-second delay |
| Active refresh: tokens with activity in the last 120 minutes | 0 */2 * * *, every two hours, up to 250 tokens |
| Stale backfill: tokens whose score is older than 30 minutes | 30 */6 * * *, every six hours, up to 150 tokens |
GET /api/tokens/{address}/score | On demand, and it persists the result |
Decay is applied at write time, so a token with no new activity keeps its last score until a
backfill reaches it. trending_score_update_at tells you how stale.
Which tokens are eligible
Sorting by trending_score applies a gate before the sort: at least 5 holders, 2
all-time posts mentioning the symbol and 3 all-time trades.
GET /api/tokens/{address}/trending-eligibility shows one token's standing against those
thresholds. If no token clears the gate the list is re-queried without it.
Topics
A topic is a hashtag that exists because someone posted it, with a post count attached.
Parameter on GET /api/topics | Notes |
|---|---|
order_by | name, post_count, created_at |
order_direction | ASC or DESC |
search | Substring match on the name |
limit, page | Standard pagination |
curl -s "https://api.superhero.com/api/topics/name/superhero"
curl -s "https://api.superhero.com/api/topics?order_by=post_count&order_direction=DESC&limit=20"GET /api/topics/name/{name} returns one topic with a posts array and a post_count number.
GET /api/topics/{id} accepts a UUID or a name, and GET /api/topics/popular/trending is a
ready-made "most used" list with no parameters.
| Field | Type | Meaning |
|---|---|---|
id | string | UUID |
name | string | The hashtag, unique, at most 100 characters |
description | string or null | At most 500 characters |
post_count | number | Posts carrying this topic |
token | TokenDto or null | The token whose symbol matches this topic name, when one exists |
created_at / updated_at | string | ISO 8601 |
version | number | Indexer bookkeeping |
GET /api/search
One request across all three entity types:
| Parameter | Notes |
|---|---|
q | Search term, max 100 characters. A q shorter than 2 characters returns empty arrays |
limit | Max results per category, clamped to 1–20, default 5 |
curl -s "https://api.superhero.com/api/search?q=super&limit=5"Returns tokens (full TokenDto objects), accounts (address plus chain_name) and
posts (full PostDto objects).
Related
- Tokens API:
trending_scorein context, and resolving a tag name to a token - Posts API:
trend_mentions, the pre-resolved tag-to-token links on every post - API Conventions: pagination and the
limitcap - Trending score: what the score is meant to represent
- Collections and naming: which tags can become tokens at all