R talks to Trends API with httr2 and jsonlite. The request is POST https://api.trendsapi.ai/api. The envelope field body is a JSON string and must be parsed a second time. gtrendsR is a scrape client and is not in this pipe. percent_growth must be an R list so it encodes as a JSON array. source wikipedia wants an article title, not a URL. Docs: the API reference. Caps: pricing. Wikipedia titles: the Wikipedia guide.

httr2 req_body_json

library(httr2)
library(jsonlite)
library(tibble)

key <- Sys.getenv("TRENDSAPI_API_KEY")
if (!nzchar(key)) stop("TRENDSAPI_API_KEY missing")

req <- request("https://api.trendsapi.ai/api") |>
  req_auth_bearer_token(key) |>
  req_headers("Content-Type" = "application/json") |>
  req_body_json(list(
    mode = "get_growth",
    source = "wikipedia",
    keyword = "ChatGPT",
    percent_growth = list("3M", "12M")
  )) |>
  req_timeout(60)

resp <- req_perform(req)
stop_for_status(resp)
outer <- resp_body_json(resp)
inner <- fromJSON(outer$body)

ChatGPT plus wikipedia is a labeled sample. percent_growth is an R list so it encodes as a JSON array.

jsonlite parse twice

resp_body_json is parse one. fromJSON(outer$body) is parse two. fromJSON(resp_body_string(resp)) still needs the inner fromJSON on $body. Printing outer$body in the console shows escaped quotes if the second parse was skipped.

tibble from results

rows <- inner$results
if (is.null(rows)) rows <- inner$data
tbl <- as_tibble(rows)

If both are null, str(inner) once. Do not bind gtrendsR columns onto this tibble.

Sys.getenv for the key

~/.Renviron can hold TRENDSAPI_API_KEY=. Restart the session after editing. Do not commit .Renviron with a live token. A 429 from req_perform is this product's limit, not a Google cookie. Read pricing. Quarto and R Markdown should load the key from the environment, not from a chunk that gets published as HTML. The product MCP server at https://api.trendsapi.ai/mcp is for agents, not for Rscript.