Skip to content
unzoi docs

curl

export UNZOI_KEY=nai_...
curl -s -G "https://api.unzoi.com/search" \
  -H "x-api-key: $UNZOI_KEY" \
  --data-urlencode "q=semiconductor export controls" \
  --data-urlencode "mode=hybrid" \
  --data-urlencode "limit=10"

Stories

curl -s -G "https://api.unzoi.com/stories" \
  -H "x-api-key: $UNZOI_KEY" \
  --data-urlencode "q=port congestion" \
  --data-urlencode "limit=5" | jq '.stories[] | {count, outlets, title}'

Reading the limit headers

curl -sD /dev/stderr -o /dev/null -G "https://api.unzoi.com/search" \
  -H "x-api-key: $UNZOI_KEY" \
  --data-urlencode "q=inflation" 2>&1 | grep -i '^x-'
x-ratelimit-limit: 600
x-ratelimit-remaining: 599
x-ratelimit-reset: 60
x-quota-limit: 50000
x-quota-used: 12484
x-quota-remaining: 37516
x-quota-reset: 394200
x-plan-history-days: 365

Check your window before a long query

curl -s "https://api.unzoi.com/account" -H "x-api-key: $UNZOI_KEY" | jq '{plan, history_days, quota_remaining}'

One MCP tool call

curl -s -X POST https://api.unzoi.com/mcp \
  -H "x-api-key: $UNZOI_KEY" \
  -H "content-type: application/json" \
  -H "accept: application/json, text/event-stream" \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/call",
       "params":{"name":"list_stories","arguments":{"q":"grid outage","limit":5}}}' \
  | jq -r '.result.content[0].text' | jq '.stories[] | {count, outlets, title}'

Two jq passes because the payload is a JSON string inside a content block — that is MCP's envelope, and it is applied exactly once. More on the shape.

Paging a whole result set

offset=0
while :; do
  page=$(curl -s -G "https://api.unzoi.com/search" -H "x-api-key: $UNZOI_KEY" \
    --data-urlencode "q=lithium supply" \
    --data-urlencode "from=2026-08-01" \
    --data-urlencode "limit=100" --data-urlencode "offset=$offset")

  echo "$page" | jq -c '.results[] | {id, source, title}'

  [ "$(echo "$page" | jq -r '.has_more')" = "true" ] || break
  offset=$((offset + 100))
  sleep 0.2   # stay under the per-minute rate
done

Stop on has_more rather than by comparing offset to total — for ranked queries the total is a candidate count, not a corpus count. Why.