Go uses net/http and encoding/json. The URL is POST https://api.trendsapi.ai/api. The envelope is statusCode plus a string body. That string must be parsed a second time. A first-pass struct that types Body as another struct will fail to decode. context.WithTimeout is the request deadline, not a scrape cookie. source steam wants a store display name. Docs: the API reference. Caps: pricing. Steam names: the Steam guide.

Two structs for the envelope

type envelope struct {
    StatusCode int    `json:"statusCode"`
    Body       string `json:"body"`
}

type growthRow struct {
    Period      string  `json:"period"`
    Growth      float64 `json:"growth"`
    RecentValue float64 `json:"recent_value"`
    Status      string  `json:"status"`
}

Add fields as the inner object uses them. Do not invent a gprop field.

json.Unmarshal body as string first

ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
defer cancel()

payload, _ := json.Marshal(map[string]any{
    "mode":            "get_growth",
    "source":          "steam",
    "keyword":         "Elden Ring",
    "percent_growth":  []string{"3M"},
})
req, _ := http.NewRequestWithContext(ctx, http.MethodPost, "https://api.trendsapi.ai/api", bytes.NewReader(payload))
req.Header.Set("Authorization", "Bearer "+os.Getenv("TRENDSAPI_API_KEY"))
req.Header.Set("Content-Type", "application/json")

resp, err := http.DefaultClient.Do(req)
if err != nil {
    log.Fatal(err)
}
defer resp.Body.Close()
if resp.StatusCode >= 300 {
    log.Fatalf("http %d", resp.StatusCode)
}
var env envelope
if err := json.NewDecoder(resp.Body).Decode(&env); err != nil {
    log.Fatal(err)
}
var inner map[string]any
if err := json.Unmarshal([]byte(env.Body), &inner); err != nil {
    log.Fatal(err)
}

Elden Ring is a labeled sample. First store search result wins, so the name must be unambiguous. A worker that fans out sources should share one http.Client and cancel siblings on the first 429.

context.WithTimeout

The context is the request deadline. It is not a Google tz. On 429, stop the worker and read pricing.

table-driven source tests

A test table of {source, keyword} pairs (steam/Elden Ring, google search/bitcoin) catches a typo in source before production. t.Parallel() plus one client is enough. Log StatusCode from the envelope on every test. A 200 with an inner error is still a completed call. The product MCP server at https://api.trendsapi.ai/mcp is not a Go import.