| name | posthog-analytics |
| description | Use when querying PostHog analytics — pageviews, unique users, top events, funnels, trends, or any product analytics question. Requires POSTHOG_API_KEY and POSTHOG_PROJECT_ID configured as env vars. |
PostHog Analytics
Query PostHog analytics via REST API. No SDK needed — pure curl or Python requests.
Setup (one time)
Store credentials securely — never hardcode in files:
POSTHOG_API_KEY=phx_your_personal_api_key
POSTHOG_PROJECT_ID=12345
POSTHOG_HOST=https://us.posthog.com
Load in shell:
export $(cat ~/.env.secrets | grep POSTHOG | xargs)
Or in Python:
import os
from dotenv import load_dotenv
load_dotenv(os.path.expanduser("~/.env.secrets"))
API_KEY = os.environ["POSTHOG_API_KEY"]
PROJECT_ID = os.environ["POSTHOG_PROJECT_ID"]
HOST = os.environ.get("POSTHOG_HOST", "https://us.posthog.com")
Personal API Key (not project API key) — create at {HOST}/settings/user/api-keys with scopes: Read Project, Read Insights, Read Events.
Quick Queries
Pageviews last 7 days
import requests, os
r = requests.get(
f"{HOST}/api/projects/{PROJECT_ID}/insights/trend/",
headers={"Authorization": f"Bearer {API_KEY}"},
params={
"events": '[{"id":"$pageview","name":"Pageview","type":"events","order":0}]',
"date_from": "-7d",
"interval": "day"
}
)
data = r.json()
for point in data["result"][0]["data"]:
print(point)
Unique users last 30 days
r = requests.get(
f"{HOST}/api/projects/{PROJECT_ID}/insights/trend/",
headers={"Authorization": f"Bearer {API_KEY}"},
params={
"events": '[{"id":"$pageview","math":"dau"}]',
"date_from": "-30d",
"interval": "week"
}
)
Top pages / URLs
r = requests.get(
f"{HOST}/api/projects/{PROJECT_ID}/insights/trend/",
headers={"Authorization": f"Bearer {API_KEY}"},
params={
"events": '[{"id":"$pageview","math":"total"}]',
"breakdown": "$current_url",
"breakdown_type": "event",
"date_from": "-7d"
}
)
for item in r.json()["result"][:10]:
print(f"{item['breakdown_value']}: {sum(item['data'])}")
Top events (all events, not just pageviews)
r = requests.get(
f"{HOST}/api/projects/{PROJECT_ID}/events/",
headers={"Authorization": f"Bearer {API_KEY}"},
params={"limit": 100, "after": "2026-04-15T00:00:00Z"}
)
from collections import Counter
counts = Counter(e["event"] for e in r.json()["results"])
for event, count in counts.most_common(10):
print(f"{event}: {count}")
Saved insights list
r = requests.get(
f"{HOST}/api/projects/{PROJECT_ID}/insights/",
headers={"Authorization": f"Bearer {API_KEY}"},
params={"limit": 20}
)
for insight in r.json()["results"]:
print(f"[{insight['id']}] {insight['name']}")
Fetch a specific saved insight by ID
r = requests.get(
f"{HOST}/api/projects/{PROJECT_ID}/insights/{INSIGHT_ID}/",
headers={"Authorization": f"Bearer {API_KEY}"}
)
insight = r.json()
print(insight["name"])
print(insight["result"])
HogQL Queries (Advanced)
PostHog supports SQL-like queries for complex analytics:
r = requests.post(
f"{HOST}/api/projects/{PROJECT_ID}/query/",
headers={
"Authorization": f"Bearer {API_KEY}",
"Content-Type": "application/json"
},
json={
"query": {
"kind": "HogQLQuery",
"query": """
SELECT
properties.$current_url AS page,
count() AS views,
count(DISTINCT person_id) AS unique_users
FROM events
WHERE event = '$pageview'
AND timestamp >= now() - interval 7 day
GROUP BY page
ORDER BY views DESC
LIMIT 20
"""
}
}
)
for row in r.json()["results"]:
print(f"{row[0]}: {row[1]} views, {row[2]} unique")
Useful HogQL patterns
SELECT toDate(timestamp) AS day, count(DISTINCT session_id) AS sessions
FROM events WHERE event = '$pageview' AND timestamp >= now() - interval 30 day
GROUP BY day ORDER BY day
SELECT properties.$geoip_country_name AS country, count() AS events
FROM events WHERE timestamp >= now() - interval 7 day
GROUP BY country ORDER BY events DESC LIMIT 10
SELECT
if(min(timestamp) >= now() - interval 7 day, 'new', 'returning') AS user_type,
count(DISTINCT person_id) AS users
FROM events WHERE event = '$pageview'
GROUP BY user_type
Pagination
PostHog returns paginated results. Fetch all pages:
def fetch_all(url, headers, params=None):
results = []
while url:
r = requests.get(url, headers=headers, params=params)
data = r.json()
results.extend(data.get("results", []))
url = data.get("next")
params = None
return results
Common Mistakes
| Issue | Fix |
|---|
| 401 Unauthorized | Use Personal API key, not Project API key |
| Empty results | Check date_from format: use -7d or ISO 2026-04-15 |
$pageview returns 0 | PostHog events are case-sensitive, use exact name |
| Large response slow | Add limit param, use HogQL for aggregations |
| Credentials in code | Always load from ~/.env.secrets, never hardcode |
Security Rules
- Never put API keys in SKILL.md, scripts, or any committed file
- Always load from environment variables or
~/.env.secrets
- Never print/log the API key value
- Personal API key has read-only scopes — safer than project key
- If key leaks: revoke immediately at
{HOST}/settings/user/api-keys