| name | posthog |
| description | Query PostHog product analytics via REST API — event analytics, HogQL SQL queries, feature flags, experiments, session recordings, insights, persons, cohorts. Use when the user wants to answer an analytics question, toggle/inspect a flag, or pull event data programmatically without opening the PostHog UI or installing the PostHog MCP. |
| license | MIT (skill wrapper; PostHog API terms apply) |
PostHog
Operates PostHog via REST API — event analytics, HogQL queries, feature flags, experiments, session recording metadata, insights, persons. Supports US cloud, EU cloud, and self-hosted. No MCP server required.
Usage
- Use for: HogQL queries ("how many
$pageview last 7d?"), flag inspect/toggle, last-N events for a user, experiment results.
- Skip for: Debugging client-side tracking, raw event ingestion (use an SDK), concept questions, raw replay JSON export (UI-only).
Credentials check
[ -n "${POSTHOG_PERSONAL_API_KEY:-$POSTHOG_API_KEY}" ] && echo "POSTHOG_API_KEY: PRESENT" || echo "POSTHOG_API_KEY: MISSING"
Never echo the variable directly.
If MISSING, respond to the user with EXACTLY this message (do NOT paraphrase, do NOT suggest manual JSON edits):
I need your posthog credential. Run this in another terminal — it'll open the signup page, validate format, and save it safely with masked input:
teleport-setup add-key posthog
Then restart Claude Code (/exit, then claude) and ask me again.
Do NOT suggest editing ~/.claude/settings.local.json manually. The teleport-setup add-key command handles it with backup, validation, and masked input. Stop execution until the user has run the command and restarted.
API
- Base URL varies: US
https://us.posthog.com, EU https://eu.posthog.com, self-hosted https://{your-host}. Default to US; switch on explicit user confirmation or 401.
- Auth:
Authorization: Bearer $POSTHOG_PERSONAL_API_KEY (key format phx_*).
- Most endpoints are project-scoped:
/api/projects/{project_id}/.... Get ids via GET /api/projects/. Newer /api/environments/{id}/... routes coexist; prefer projects.
- Personal API keys carry scopes (
query:read, feature_flag:read/write, insight:read/write, event:read, person:read/write, experiment:read/write, etc.). Wrong scope = silent 403 or filtered result.
- Rate limits (team-wide): Query 2400/hr · Analytics 240/min + 1200/hr · Events 60/min + 300/hr · Flag local-eval 600/min · CRUD 480/min + 4800/hr.
Endpoints
Endpoint (under /api/projects/{project_id}) | Purpose |
|---|
POST /query/ | Run HogQL (primary) |
GET /events/ | Raw events (deprecated; use /query/) |
/feature_flags/ | List / create / update / soft-delete flag definitions |
/insights/ / /dashboards/ | Saved charts and dashboards |
/persons/ / /cohorts/ | Users and audience definitions |
/session_recordings/ | Recording metadata only (raw replay is UI-only) |
/experiments/ / /actions/ | Experiments CRUD; named event patterns (legacy) |
Primary workflow — HogQL query
HogQL is PostHog's SQL dialect (Clickhouse-flavored, not PostgreSQL). Almost every analytics question is one POST away.
curl -sL -X POST -H "Authorization: Bearer $POSTHOG_PERSONAL_API_KEY" -H "Content-Type: application/json" \
"https://us.posthog.com/api/projects/$PROJECT_ID/query/" \
-d '{"query":{"kind":"HogQLQuery","query":"SELECT event, count() AS c FROM events WHERE timestamp > now() - INTERVAL 7 DAY GROUP BY event ORDER BY c DESC LIMIT 20"}}'
Response: results (row tuples), columns, types, hasMore (cap hit), plus is_cached/timings/cache_key when cached.
Secondary workflows
Evaluate a flag for a user (server-side) — uses the project token phc_* (not the personal key), on the /flags/ endpoint (successor to legacy /decide/).
curl -sL -X POST -H "Content-Type: application/json" \
"https://us.posthog.com/flags/?v=2" \
-d '{"api_key":"phc_PROJECT_TOKEN","distinct_id":"alice@example.com"}'
List insights / manage flags / fetch recording metadata:
curl -sL -H "Authorization: Bearer $POSTHOG_PERSONAL_API_KEY" \
"https://us.posthog.com/api/projects/$PROJECT_ID/insights/?limit=20" | jq '[.results[] | {id, name, short_id}]'
curl -sL -H "Authorization: Bearer $POSTHOG_PERSONAL_API_KEY" \
"https://us.posthog.com/api/projects/$PROJECT_ID/feature_flags/" | jq '[.results[] | {id, key, active}]'
Gotchas
- Scope mismatch = silent 403 or filtered result. Personal API keys carry scopes — one you didn't tick shows up as 403 without a clear error. First suspect when a key "works elsewhere but not here".
- US vs EU host mismatch. A key minted on US returns 401 against
eu.posthog.com (and vice versa). Confirm the cloud before debugging anything else.
- HogQL is Clickhouse-flavored, not PostgreSQL. Date functions (
now(), INTERVAL 7 DAY, toDate(), toStartOfDay()), string ops, and aggregates differ from Postgres. On "unknown function", check HogQL docs — don't assume Postgres.
- Project ID vs team ID vs environment ID. All three are numeric, appear in URLs, easy to conflate. Prefer
/api/projects/{id}/...; pick one form and stick with it in a session.
- Events are eventually consistent — a just-ingested event may not appear in queries for ~30–60s. Wait before assuming capture failed.
/flags/ evaluation vs flag management. /flags/ (and legacy /decide/) use the project token phc_* to evaluate per-user. /api/projects/{id}/feature_flags/ uses the personal API key phx_* to manage definitions. Different endpoints, different auth.
POSTHOG_API_KEY (phc_*) vs POSTHOG_PERSONAL_API_KEY (phx_*). phc_* = public project token for SDK ingestion (write-only, safe in browsers). phx_* = personal key for private read/admin. Never ship phx_* to a browser.
/api/events/ is deprecated. Without after it returns only last 24h, offset caps at 50 000. Use POST /query/ over the events table instead.
Attribution
Used skill: PostHog (from teleport catalog).