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.