Discover signals

Public, no-auth endpoints for Google Discover volatility — the 0–10 index that flags unannounced Discover updates, plus category, topic and format signals.

The Discover signals are open, no-auth aggregates over the same Google Discover data that powers the app. They report how much the Discover feed is reshuffling — overall, by editorial format, by topic, and by content category — plus a live category heatmap. No API key is needed: these endpoints sit at the same trust level as /health, expose only non-sensitive aggregates, and take no auth header. Every response is Redis-cached server-side, so poll on a sensible interval.

All five endpoints share one region parameter, country, which takes an ISO 3166-1 alpha-2 code (us, ru, de) or all for the global feed. Codes are case-insensitive and trimmed. Only a handful of markets have enough history for a full-confidence score; young regions fall back to a global default scale with lower confidence.

#Discover volatility index

GET/public/discover/volatilityPublic

The flagship early-warning signal: a 0..10 Google Discover volatility index for the last 30 days, scoring how much the feed reshuffles period-over-period. This is the primary unannounced-update detector.

Query parameters
countrystringoptionaldefault all
Region to score. An ISO 3166-1 alpha-2 code (e.g. us, ru), or all for the global feed. Case-insensitive and trimmed.
granularitystringoptionaldefault day
day compares each day to the trailing 30 days. 3h buckets the last 30 days into 3-hour windows, each compared to the same 3h-of-day slot on prior days (diurnally normalised) to locate an update's onset within a day. Must match ^(day|3h)$.
curl
curl "https://d2tr.com/api/public/discover/volatility?country=us&granularity=day"
Response
{
  "country": "us",
  "granularity": "day",
  "window_days": 30,
  "baseline_days": 30,
  "generated_at": "2026-07-01T09:12:44.512300+00:00",
  "scale": "0..10 (multi-window 3/7/30d): ~3 = typical period, >=7 = anomalous (likely Discover update)…",
  "calibration": {
    "method": "multiwindow_level_transient_fixed_scale",
    "windows_days": [3, 7, 30],
    "center": 3.0,
    "gain_per_sigma": 2.0,
    "spike_index": 7.0,
    "sustain_index": 5.0
  },
  "weights": { "new_domains": 0.5, "domain_shift": 0.3, "format_shift": 0.2 },
  "summary": {
    "current_index": 4.12,
    "mean_index": 3.87,
    "max_index": 8.35,
    "trend": "rising",
    "current_state": "active",
    "episodes": [
      {
        "start": "2026-06-18",
        "peak_index": 8.35,
        "peak_period": "2026-06-19",
        "end": "2026-06-22",
        "status": "ended",
        "periods": 5
      }
    ],
    "likely_update_days": ["2026-06-18", "2026-06-19"]
  },
  "series": [
    {
      "date": "2026-06-02",
      "volatility_index": 3.44,
      "is_spike": false,
      "state": "calm",
      "confidence": "high",
      "baseline_days": 30,
      "components": { "new_domains": 0.0821, "domain_shift": 0.1934, "format_shift": 0.0412 },
      "signals": { "activity_3d": 0.08123, "mid_7d": 0.09541, "displacement_30d": 0.11203 },
      "metrics": {
        "total_impressions": 48213400,
        "total_articles": 10233,
        "unique_domains": 512,
        "new_domains_count": 14,
        "new_domain_impression_share": 0.0731,
        "domain_distribution_jsd": 0.1841,
        "unique_domain_change": 0.0312,
        "format_distribution_jsd": 0.0412
      },
      "top_new_domains": [
        { "domain": "example.com", "impressions": 40213 }
      ]
    }
  ]
}

There is no pagination — the series is capped to the last 30 days (or 30 days × 8 buckets for 3h) and ordered ascending by period; summary.current_index is the last scored period. trend compares the last value to the mean of the prior ~7 periods (rising / falling / stable, needs ≥4 scored periods). Responses are Redis-cached for at least 15 minutes. A backend RPC failure returns 500 "Failed to compute volatility".

#Category (format-group) volatility

GET/public/discover/category-volatilityPublic

Per-editorial-format-group volatility (0..10) for the last 30 days. Same model as /volatility, but computed within each enrichment_format_group (News / Analysis / Service / Ritual / Atmosphere), where the factors read as publisher churn plus sub-format churn.

Query parameters
countrystringoptionaldefault all
Region — alpha-2 code or all.
granularitystringoptionaldefault day
Time grouping. day or 3h; must match ^(day|3h)$.
categorystringoptionaldefault null
Filter to one format group — one of News, Analysis, Service, Ritual, Atmosphere. When set, returns that group only with full per-period detail; otherwise a compact series per category.
curl
curl "https://d2tr.com/api/public/discover/category-volatility?country=all&granularity=day&category=News"
Response
{
  "country": "all",
  "granularity": "day",
  "category_dimension": "enrichment_format_group",
  "category_filter": "News",
  "window_days": 30,
  "generated_at": "2026-07-01T09:12:44.512300+00:00",
  "scale": "0..10 (multi-window 3/7/30d): ~3 = typical period, >=7 = anomalous …",
  "calibration": { "method": "multiwindow_level_transient_fixed_scale", "windows_days": [3, 7, 30], "center": 3.0, "gain_per_sigma": 2.0, "spike_index": 7.0, "sustain_index": 5.0 },
  "weights": { "new_domains": 0.5, "domain_shift": 0.3, "format_shift": 0.2 },
  "factors": {
    "new_domains": "new publishers in the format group",
    "domain_shift": "publisher impression-mix shift",
    "format_shift": "sub-format (format type) mix shift within the group"
  },
  "categories": [
    {
      "category": "News",
      "summary": {
        "current_index": 5.02, "mean_index": 4.11, "max_index": 7.8,
        "trend": "stable", "current_state": "active",
        "episodes": [ { "start": "2026-06-19", "peak_index": 7.8, "peak_period": "2026-06-20", "end": null, "status": "ongoing", "periods": 3 } ],
        "likely_update_days": ["2026-06-20"]
      },
      "series": [
        {
          "date": "2026-06-02",
          "volatility_index": 4.02,
          "is_spike": false,
          "state": "calm",
          "confidence": "high",
          "baseline_days": 30,
          "components": { "new_domains": 0.07, "domain_shift": 0.18, "format_shift": 0.03 },
          "signals": { "activity_3d": 0.081, "mid_7d": 0.095, "displacement_30d": 0.112 },
          "metrics": { "total_impressions": 20031400, "total_articles": 4021, "unique_domains": 210, "new_domains_count": 6, "new_domain_impression_share": 0.041, "domain_distribution_jsd": 0.171, "unique_domain_change": 0.02, "format_distribution_jsd": 0.03 },
          "top_new_domains": [ { "domain": "example-news.com", "impressions": 12044 } ]
        }
      ]
    }
  ]
}

Without a category filter, each category's series is compacted to { date|bucket, volatility_index, is_spike, state } to bound the payload, and categories is ranked by current_index descending (categories with no scored value sink to the bottom). With a filter, you get full per-period detail for that one group.

#Topic volatility

GET/public/discover/topic-volatilityPublic

Per-topic volatility (0..10), daily only. Same model as /volatility, computed within each top-level Google-NL topic taxonomy category (Sports, News, Law & Government, Business & Industrial, Arts & Entertainment, Finance, Health, …).

Query parameters
countrystringoptionaldefault all
Region — alpha-2 code, or all for a global view across regions.
categorystringoptionaldefault null
Filter to one topic — a top-level Google-NL topic name, e.g. Sports, News, Finance. When set, returns that topic only with full per-day detail; otherwise a compact daily series per topic.
curl
curl "https://d2tr.com/api/public/discover/topic-volatility?country=us&category=Sports"
Response
{
  "country": "us",
  "granularity": "day",
  "category_dimension": "gnl_topic_category",
  "category_filter": "Sports",
  "window_days": 30,
  "generated_at": "2026-07-01T09:12:44.512300+00:00",
  "scale": "0..10 (multi-window 3/7/30d): ~3 = typical period …",
  "calibration": { "method": "multiwindow_level_transient_fixed_scale", "windows_days": [3, 7, 30], "center": 3.0, "gain_per_sigma": 2.0, "spike_index": 7.0, "sustain_index": 5.0 },
  "weights": { "new_domains": 0.5, "domain_shift": 0.3, "format_shift": 0.2 },
  "factors": {
    "new_domains": "publishers new to the topic category",
    "domain_shift": "publisher impression-mix shift within the topic",
    "format_shift": "editorial format mix shift within the topic"
  },
  "categories": [
    {
      "category": "Sports",
      "summary": { "current_index": 6.41, "mean_index": 4.02, "max_index": 8.9, "trend": "rising", "current_state": "active", "episodes": [], "likely_update_days": ["2026-06-28"] },
      "series": [
        {
          "date": "2026-06-02",
          "volatility_index": 3.9,
          "is_spike": false,
          "state": "calm",
          "confidence": "high",
          "baseline_days": 30,
          "components": { "new_domains": 0.06, "domain_shift": 0.2, "format_shift": 0.04 },
          "signals": { "activity_3d": 0.08, "mid_7d": 0.09, "displacement_30d": 0.11 },
          "metrics": { "total_impressions": 8231400, "total_articles": 1904, "unique_domains": 142, "new_domains_count": 4, "new_domain_impression_share": 0.03, "domain_distribution_jsd": 0.19, "unique_domain_change": 0.02, "format_distribution_jsd": 0.04 },
          "top_new_domains": [ { "domain": "example-sports.com", "impressions": 9044 } ]
        }
      ]
    }
  ]
}

Unfiltered, categories is ranked by current_index descending and each series is compacted to { date, volatility_index, is_spike, state } — the same shape as the category-volatility list view. Backed by api_discover_topic_volatility_daily (min 1000 impressions per period inside the RPC), Redis-cached for 1 hour; an RPC failure returns 500.

#Format interest index

GET/public/discover/format-interestPublic

A Google Discover format-preference index for the last 30 days. For each editorial format type (enrichment_format_type — the ~19 T-codes, e.g. T01_news_brief, T03_listicle, T16_ritual), it reports a daily index of how much Discover favours that format, computed as 100 × (0.5 × impression_share + 0.5 × article_share) — impression share and article share weighted equally. Values run roughly 0..100 and sum to ~100 per day across formats.

Query parameters
countrystringoptionaldefault all
Region — alpha-2 code or all.
formatstringoptionaldefault null
Filter to one format type — an enrichment_format_type value, e.g. T03_listicle. Shares are always computed against all-format day totals, then filtered.
curl
curl "https://d2tr.com/api/public/discover/format-interest?country=us&format=T03_listicle"
Response
{
  "country": "us",
  "granularity": "day",
  "window_days": 30,
  "format_dimension": "enrichment_format_type",
  "format_filter": "T03_listicle",
  "generated_at": "2026-07-01T09:12:44.512300+00:00",
  "index": "0..100 balanced share of Discover format attention: 50% impression-share + 50% article-share that day",
  "holidays": {
    "2026-07-04": [ { "country": "us", "name": "Independence Day", "kind": "public" } ]
  },
  "holidays_note": "date -> [{country, name, kind}] for the selected country (all served countries when country=all); join with series by date",
  "formats": [
    {
      "format_type": "T03_listicle",
      "summary": {
        "current_index": 12.44,
        "mean_index": 11.02,
        "peak_index": 15.9,
        "shift_vs_mean": 1.42,
        "trend": "rising"
      },
      "series": [
        {
          "date": "2026-06-02",
          "interest_index": 11.203,
          "impressions": 5402100,
          "articles": 1204,
          "impression_share": 0.1121,
          "article_share": 0.1119
        }
      ]
    }
  ]
}

Formats are ranked by current_index descending. summary.trend compares the current value to the prior-week mean, with relative (15%) and absolute (0.5 pt) guards and needs ≥8 days (rising / falling / stable). The holidays map is joinable with each series by date and is non-essential — it becomes an empty object on lookup failure. Responses are Redis-cached for 1 hour; an RPC failure returns 500 "Failed to compute format interest".

#Top Discover categories

GET/public/discover/categoriesPublic

The top Google Discover content categories over the last 24 hours (Google-NL taxonomy, e.g. /News/Politics, /Sports/Team Sports/Soccer). This mirrors the web category heatmap.

Query parameters
countrystringoptionaldefault all
all sums every country; an alpha-2 code narrows to one market (across all languages).
limitintegeroptionaldefault 100
Top-N categories to return — an integer from 1 to 500 inclusive.
curl
curl "https://d2tr.com/api/public/discover/categories?country=us&limit=50"
Response
{
  "country": "us",
  "window_hours": 24,
  "generated_at": "2026-07-01T09:12:44.512300+00:00",
  "metrics": {
    "impressions": "Discover reach estimate (est_url_reach) over 24h",
    "delta_pct": "change vs the preceding 24h, %",
    "publications": "distinct articles",
    "publishers": "distinct domains",
    "trends": "distinct trends touching the category",
    "lifespan_hours": "avg hours an article stayed visible",
    "share_of_impressions": "category reach / total window reach, %"
  },
  "categories_count": 50,
  "categories": [
    {
      "category": "/News/Politics",
      "impressions": 18402100,
      "delta_pct": 12.4,
      "publications": 842,
      "publishers": 121,
      "trends": 37,
      "lifespan_hours": 9.3,
      "share_of_impressions": 14.2
    }
  ]
}

The window is a hour-aligned 24 hours; delta_pct compares against the preceding 24h and may be null when there is no prior data, while lifespan_hours and share_of_impressions default to 0.0 when absent. There is no offset pagination — use limit only (max 500); ordering is by the RPC (reach/impressions). Responses are Redis-cached for 1 hour, and the hour-aligned window makes that cache exact. An RPC failure returns 500 "Failed to compute categories".