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.