Ruby talks to POST https://api.trendsapi.ai/api with net/http. Auth is a Bearer header. The envelope field body is a JSON string and must be parsed a second time. ENV.fetch fails before the POST if the token is missing. Faraday is optional. source amazon takes a shopper phrase, not an ASIN. Short Amazon windows can collapse when the stored series is coarse. Docs: the API reference. Caps: pricing. Amazon phrases: the Amazon guide.

Net::HTTP::Post

require "json"
require "net/http"
require "uri"

uri = URI("https://api.trendsapi.ai/api")
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
http.read_timeout = 60

req = Net::HTTP::Post.new(uri)
req["Authorization"] = "Bearer #{ENV.fetch("TRENDSAPI_API_KEY")}"
req["Content-Type"] = "application/json"
req.body = {
  mode: "get_growth",
  source: "amazon",
  keyword: "air fryer",
  percent_growth: ["1M", "3M"],
}.to_json

res = http.request(req)
raise "http #{res.code}" unless res.is_a?(Net::HTTPSuccess)

air fryer is a labeled sample. Short Amazon windows can collapse if the stored series is coarse. 1M is the safer first preset.

JSON.parse twice

outer = JSON.parse(res.body)
inner = JSON.parse(outer.fetch("body"))

fetch raises if body is missing. inner["results"] is the growth list on this mode. A series mode returns a JSON array of points instead. JSON.parse(..., symbolize_names: true) is a local taste. The two-step parse stays either way.

ENV.fetch

.env via dotenv is fine in development. Production should inject the variable. Do not print it in a Sidekiq log line.

Faraday is optional

# Faraday.post("https://api.trendsapi.ai/api") do |req|
#   req.headers["Authorization"] = "Bearer #{ENV.fetch("TRENDSAPI_API_KEY")}"
#   req.headers["Content-Type"] = "application/json"
#   req.body = payload.to_json
# end

Same envelope. Same second parse. A 429 is this product's limit. Read pricing. Sidekiq jobs should mark the retry as a plan or burst event, not as a random timeout. The product MCP server at https://api.trendsapi.ai/mcp is not a Ruby gem.