Skip to content

Feed

POST /v1/feed

Merges the latest posts of up to 100 channels into one list, ranked by a score that combines engagement, content richness and freshness.

Disabled by default

The feed is the heaviest endpoint: it needs a page of every requested channel. It answers 503 until enabled with ENABLE_FEED=true. Run it with Redis (REDIS_URL) when you have more than one worker or server, so all of them share the page cache and the popularity ranking.

Request

{"channels": ["durov", "telegram", "stranaua"]}

1–100 valid usernames. Duplicates are ignored. Invalid input is answered with 422 even while the feed is disabled.

Response

{
  "result": [
    {
      "id": 552,
      "content": { … },
      "footer": { … },
      "view": "…",
      "channel": {
        "username": "durov",
        "title": {"string": "Pavel Durov", "html": "Pavel Durov"},
        "description": { … },
        "avatar": "https://…",
        "counters": {"subscribers": "10.3M", "photos": "102", "videos": "47", "links": "200"},
        "labels": ["verified"]
      },
      "_score": 0.1688
    }
  ]
}

Each item is a post in its raw parsed form (fields Telegram did not provide are omitted rather than null), plus:

  • channel — the profile of the channel the post belongs to;
  • _score — the ranking score in [0, 10];
  • _duplicates (optional) — the same story posted by other channels, collapsed into this post: [{"channel": "…", "id": 123}, …];
  • _topic (optional) — the subject this post shares with other posts in the feed: {"id": 3, "keywords": ["starlink", "ограничения", …]}.

The order is the feed order. It is mostly, but not strictly, by _score: the ranker interleaves channels and subjects (see Ranking below). Channels that cannot be loaded (unknown, t.me errors) are skipped.

Freshness

The feed uses stale-while-revalidate caching per channel page:

Age of the cached page What happens
< FEED_FRESH_TTL (60 s) used as is
< FEED_STALE_TTL (15 min) used immediately; a background refresh replaces it
older / missing fetched now — all missing channels in parallel

A background refresher keeps the channels people actually ask for warm: the more often a channel appears in feed requests, the more often it is refreshed (every 30 s for the most popular, up to every 10 min). Most feed requests therefore never wait for t.me. Details: Internals → Feed.

Ranking

Ranking runs natively in Rust and combines:

  • engagement relative to the channel — views adjusted for the post's age and compared with the channel's other posts, so small channels' standout posts compete with large channels;
  • reactions, content richness and audience size;
  • freshness with a half-life of FEED_HALF_LIFE_HOURS (24 h);
  • duplicate collapsing across channels;
  • diversity: fewer runs of one channel or one subject (FEED_DIVERSITY);
  • topics inferred from the texts.

The full algorithm is in Internals → Feed. With PARSER_MODE=simplified reactions are not parsed and do not contribute.

Changed in 2.1

The ranking moved to Rust and changed: _score values and the order differ from 2.0, and _duplicates / _topic were added.