The HTTP JSON is an envelope. Official docs state every response is {"statusCode": int, "body": string}. body is a JSON string. Parse it a second time before reading points, results, or data. The envelope fields themselves are on the response envelope. The POST is https://api.trendsapi.ai/api.
resp.json is not enough
POST https://api.trendsapi.ai/api
Authorization: Bearer <api_key>
Content-Type: application/json
{"mode":"get_time_series","source":"google search","keyword":"bitcoin"}
requests or fetch decodes the transport JSON. That yields statusCode and a string. It does not yield date or value.
Python, from the docs:
payload = res.json()
data = json.loads(payload["body"]) if isinstance(payload.get("body"), str) else payload
Node:
const data = JSON.parse((await res.json()).body)
Guard the isinstance check. If a proxy already parsed body, a second loads will throw.
Python and Node
After the second parse, branch on mode. get_time_series is a list. Index [0]["value"]. get_growth is an object. Read results or source_results. get_top_trends is an object. Read data.
Official SDKs (pip install trendsapi, npm install trendsapi) return the inner value. Raw clients should copy the docs snippet above, not invent a third envelope.
What each mode hides inside body
| Mode | After the second parse |
|---|---|
get_time_series |
Array of {date, value, keyword, source} |
get_growth |
Object with results, or source_results when several sources |
get_top_trends |
Object with as_of_ts and data |
A 200 envelope can still hold an inner row with status error. That is a completed lookup, not a transport failure. Transport 401 and 429 are on errors. Caps stay on pricing.