What Are People Reading on NYTimes.com?

Using the NYT Most Popular API in R

Author

Kevin Villa

Published

October 26, 2026

Which sections of The New York Times produced the most-viewed articles over the past 30 days, and what topics were those articles about?

Data source

The data comes from the New York Times Most Popular API, using this endpoint:

https://api.nytimes.com/svc/mostpopular/v2/viewed/30.json

It returns the 20 most-viewed articles on NYTimes.com over a period of 1, 7, or 30 days. Each article comes with its title, section, byline, publish date, topic tags, and images.

Parameter Value Notes
period 30 set in the URL path (the past 30 days)
api-key my NYT API key stored in the NYT_API_KEY environment variable

The full request, with the key hidden:

https://api.nytimes.com/svc/mostpopular/v2/viewed/30.json?api-key=HIDDEN

Setup

library(httr2)
library(dplyr)

Attaching package: 'dplyr'
The following objects are masked from 'package:stats':

    filter, lag
The following objects are masked from 'package:base':

    intersect, setdiff, setequal, union
library(purrr)
library(tidyr)
library(tibble)
library(ggplot2)

api_key <- Sys.getenv("NYT_API_KEY")
if (api_key == "") {
  stop("NYT_API_KEY is not set. Add it to .Renviron and restart R.")
}

The API key is read from my .Renviron file, so it does not appear anywhere in this document.

Pulling the data

resp <- request("https://api.nytimes.com/svc/mostpopular/v2/viewed/30.json") |>
  req_url_query(`api-key` = api_key) |>
  req_error(is_error = \(r) FALSE) |>
  req_perform()

if (resp_status(resp) != 200) {
  stop("Request failed with status ", resp_status(resp))
}

resp_status(resp)
[1] 200

A status of 200 means the request worked.

raw <- resp_body_json(resp)
names(raw)
[1] "status"      "copyright"   "num_results" "results"    
raw$num_results
[1] 20
results <- raw$results
names(results[[1]])
 [1] "uri"            "url"            "id"             "asset_id"      
 [5] "source"         "published_date" "updated"        "section"       
 [9] "subsection"     "nytdsection"    "adx_keywords"   "column"        
[13] "byline"         "type"           "title"          "abstract"      
[17] "des_facet"      "org_facet"      "per_facet"      "geo_facet"     
[21] "media"          "eta_id"        

The JSON comes back as nested lists. The articles are stored in results, one list per article, and each article has 22 fields.

Building the data frames

articles <- tibble(
  rank       = seq_along(results),
  title      = map_chr(results, "title", .default = NA_character_),
  section    = map_chr(results, "section", .default = NA_character_),
  subsection = map_chr(results, "subsection", .default = NA_character_),
  byline     = map_chr(results, "byline", .default = NA_character_),
  type       = map_chr(results, "type", .default = NA_character_),
  published  = map_chr(results, "published_date", .default = NA_character_),
  caption    = map_chr(results, list("media", 1, "caption"),
                       .default = NA_character_),
  url        = map_chr(results, "url", .default = NA_character_)
) |>
  mutate(
    published = as.Date(published),
    byline    = sub("^By ", "", byline),
    across(c(subsection, byline, caption), \(x) na_if(trimws(x), ""))
  )

glimpse(articles)
Rows: 20
Columns: 9
$ rank       <int> 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, …
$ title      <chr> "‘It Was Horrific’: 8 Killed in Shooting in Erie, Pa.", "Ob…
$ section    <chr> "U.S.", "U.S.", "New York", "Opinion", "Opinion", "U.S.", "…
$ subsection <chr> NA, "Politics", NA, NA, NA, "Politics", NA, "Television", N…
$ byline     <chr> "Emmett Lindner and Tim Arango", "Tim Balk, Emily Davies, J…
$ type       <chr> "Article", "Article", "Article", "Article", "Article", "Art…
$ published  <date> 2026-10-10, 2026-10-24, 2026-10-01, 2026-10-14, 2026-10-18…
$ caption    <chr> "Police officers at the scene of a shooting that left multi…
$ url        <chr> "https://www.nytimes.com/2026/10/10/us/erie-shooting-pennsy…
colSums(is.na(articles))
      rank      title    section subsection     byline       type  published 
         0          0          0         14          1          0          0 
   caption        url 
        12          0 

articles has one row per article.

topics <- tibble(
  rank  = seq_along(results),
  topic = map(results, \(a) unlist(a$des_facet))
) |>
  unnest_longer(topic) |>
  filter(!startsWith(topic, "internal-"))

head(topics, 10)
# A tibble: 10 × 2
    rank topic                                   
   <int> <chr>                                   
 1     1 Murders, Attempted Murders and Homicides
 2     1 Domestic Violence                       
 3     2 Midterm Elections (2026)                
 4     2 Elections, Senate                       
 5     2 Elections, Governors                    
 6     2 United States Politics and Government   
 7     3 Sex Crimes                              
 8     3 Fraternities and Sororities             
 9     3 Colleges and Universities               
10     3 Suits and Litigation (Civil)            

topics has one row for each topic tag on each article.

Cleaning notes

  • Nested fields: media is a list of images, and each image has its own list of sizes. I kept only the caption of the first image. The topic tags (des_facet) are also a list inside each article, so I put them in a separate long table instead of cramming them into one column.
  • Missing values: Most articles have an empty subsection, and some have no byline or no image. I changed empty text to NA and kept every article.
  • Internal tags: A few articles carry tags that start with internal-. These are NYT website settings, not topics, so I removed them.
  • Types and text: I converted published_date from text to a date and removed “By” from the bylines.
  • Fields left out: IDs, adx_keywords, and the people, place, and organization tags, since they don’t relate to the question.

Results

articles |>
  mutate(published = format(published, "%b %d")) |>
  select(rank, title, section, published) |>
  knitr::kable()
rank title section published
1 ‘It Was Horrific’: 8 Killed in Shooting in Erie, Pa. U.S. Oct 10
2 Obama Issues Searing Attacks on Trump and Ramaswamy in Return to Campaign Trail U.S. Oct 24
3 How a Cornell Student Went From Shame to Action New York Oct 01
4 America Is in Decline, and Trump Is Leading the Fall Opinion Oct 14
5 I Teach at a Business School. It’s Time to Shut Them Down. Opinion Oct 18
6 Behind a White House Feud That Is Bitter Even by Trump-Era Standards U.S. Oct 15
7 Tennessee Fails to Execute Woman After 2 Doses of Lethal Injection U.S. Sep 30
8 The 100 Best TV Shows of the 21st Century Arts Sep 20
9 She Was Offered $40 Million for Silence on Elon Musk. She Spoke Out Instead. Movies Oct 09
10 Latest Polls Polls Sep 10
11 In Six Decades of Public Life, I Never Thought I Would Write This Piece Opinion Oct 21
12 How Cornell Punished Each of the 7 Men Accused of Sexual Assault New York Oct 03
13 O.J. Simpson, a Secret Informant and a Decades-Old Mystery U.S. Oct 18
14 Trump’s New Press Secretary: Not-Quite Attorney at Law U.S. Oct 14
15 Trump’s Bubble Is About to Burst Opinion Oct 17
16 They Argued About Trump at a Dog Park. Then They Pulled Their Guns. U.S. Oct 05
17 John Roberts and His Island U.S. Oct 01
18 Katie Zacharia Is Ousted After 5 Days as White House Press Secretary U.S. Oct 16
19 This Photo Is an A.I. Fake. Can You Tell? Technology Oct 08
20 Religious Scholars Met With Anthropic. What They Heard Stunned Them. U.S. Sep 29
by_section <- articles |> count(section, sort = TRUE)

ggplot(by_section, aes(x = n, y = reorder(section, n))) +
  geom_col(fill = "#2a78d6", width = 0.6) +
  geom_text(aes(label = n), hjust = -0.4, color = "#1F2933") +
  scale_x_continuous(expand = expansion(mult = c(0, 0.12))) +
  labs(title = "Sections of the 20 most-viewed NYT articles",
       subtitle = "Past 30 days",
       x = "Articles", y = NULL,
       caption = "Source: NYT Most Popular API") +
  theme_minimal(base_size = 14) +
  theme(plot.title.position = "plot",
        plot.title = element_text(face = "bold", size = 13),
        panel.grid.major.y = element_blank(),
        panel.grid.minor = element_blank())

top_topics <- topics |> count(topic, sort = TRUE) |> head(5)
knitr::kable(top_topics)
topic n
United States Politics and Government 9
Murders, Attempted Murders and Homicides 4
Colleges and Universities 3
Midterm Elections (2026) 3
Artificial Intelligence 2

The U.S. section had the most articles in the top 20, with 10 of 20, followed by Opinion with 4. The most common topic tag was “United States Politics and Government,” which appeared on 9 articles. The list changes every day, so these results describe the 30 days before October 26, 2026.