PostHog
Access the PostHog API with managed authentication. Query product analytics events with HogQL, manage feature flags, analyze user behavior, view session recordings, and run A/B experiments.
Quick Start
# List projects
python <<'EOF'
import urllib.request, os, json
req = urllib.request.Request('https://gateway.maton.ai/posthog/api/projects/')
req.add_header('Authorization', f'Bearer {os.environ["MATON_API_KEY"]}')
print(json.dumps(json.load(urllib.request.urlopen(req)), indent=2))
EOF
Base URL
https://gateway.maton.ai/posthog/{native-api-path}
Replace {native-api-path} with the actual PostHog API endpoint path. The gateway proxies requests to {subdomain}.posthog.com and automatically injects your credentials.
Authentication
All requests require the Maton API key in the Authorization header:
Authorization: Bearer $MATON_API_KEY
Environment Variable: Set your API key as MATON_API_KEY:
export MATON_API_KEY="YOUR_API_KEY"
Getting Your API Key
- Sign in or create an account at maton.ai
- Go to maton.ai/settings
- Copy your API key
Connection Management
Manage your PostHog OAuth connections at https://ctrl.maton.ai.
List Connections
python <<'EOF'
import urllib.request, os, json
req = urllib.request.Request('https://ctrl.maton.ai/connections?app=posthog&status=ACTIVE')
req.add_header('Authorization', f'Bearer {os.environ["MATON_API_KEY"]}')
print(json.dumps(json.load(urllib.request.urlopen(req)), indent=2))
EOF
Create Connection
python <<'EOF'
import urllib.request, os, json
data = json.dumps({'app': 'posthog'}).encode()
req = urllib.request.Request('https://ctrl.maton.ai/connections', data=data, method='POST')
req.add_header('Authorization', f'Bearer {os.environ["MATON_API_KEY"]}')
req.add_header('Content-Type', 'application/json')
print(json.dumps(json.load(urllib.request.urlopen(req)), indent=2))
EOF
Get Connection
python <<'EOF'
import urllib.request, os, json
req = urllib.request.Request('https://ctrl.maton.ai/connections/{connection_id}')
req.add_header('Authorization', f'Bearer {os.environ["MATON_API_KEY"]}')
print(json.dumps(json.load(urllib.request.urlopen(req)), indent=2))
EOF
Response:
{
"connection": {
"connection_id": "ce2b0840-4e39-4b58-b607-7290fa7a3595",
"status": "ACTIVE",
"creation_time": "2026-02-23T09:37:57.686121Z",
"last_updated_time": "2026-02-23T09:39:11.851118Z",
"url": "https://connect.maton.ai/?session_token=...",
"app": "posthog",
"metadata": {}
}
}
Open the returned url in a browser to complete OAuth authorization.
Delete Connection
python <<'EOF'
import urllib.request, os, json
req = urllib.request.Request('https://ctrl.maton.ai/connections/{connection_id}', method='DELETE')
req.add_header('Authorization', f'Bearer {os.environ["MATON_API_KEY"]}')
print(json.dumps(json.load(urllib.request.urlopen(req)), indent=2))
EOF
Specifying Connection
If you have multiple PostHog connections, specify which one to use with the Maton-Connection header:
python <<'EOF'
import urllib.request, os, json
req = urllib.request.Request('https://gateway.maton.ai/posthog/api/projects/')
req.add_header('Authorization', f'Bearer {os.environ["MATON_API_KEY"]}')
req.add_header('Maton-Connection', 'ce2b0840-4e39-4b58-b607-7290fa7a3595')
print(json.dumps(json.load(urllib.request.urlopen(req)), indent=2))
EOF
If omitted, the gateway uses the default (oldest) active connection.
API Reference
Organizations
Get Current Organization
GET /posthog/api/organizations/@current/
Projects
List Projects
GET /posthog/api/projects/
Response:
{
"count": 1,
"next": null,
"previous": null,
"results": [
{
"id": 136209,
"uuid": "019583c6-377c-0000-e55c-8696cbc33595",
"organization": "019583c6-3635-0000-5798-c18f20963b3b",
"api_token": "phc_XXX",
"name": "Default project",
"timezone": "UTC"
}
]
}
Get Current Project
GET /posthog/api/projects/@current/
Users
Get Current User
GET /posthog/api/users/@me/
Query (HogQL)
The query endpoint is the recommended way to retrieve events and run analytics queries.
Run HogQL Query
POST /posthog/api/projects/{project_id}/query/
Content-Type: application/json
{
"query": {
"kind": "HogQLQuery",
"query": "SELECT event, count() FROM events GROUP BY event ORDER BY count() DESC LIMIT 10"
}
}
Response:
{
"columns": ["event", "count()"],
"results": [
["$pageview", 140504],
["$autocapture", 108691],
["$identify", 5455]
],
"types": [
["event", "String"],
["count()", "UInt64"]
]
}
Persons
List Persons
GET /posthog/api/projects/{project_id}/persons/?limit=10
Response:
{
"results": [
{
"id": "5d79eecb-93e6-5c8b-90f9-8510ba4040b8",
"uuid": "5d79eecb-93e6-5c8b-90f9-8510ba4040b8",
"name": "user@example.com",
"is_identified": true,
"distinct_ids": ["user-uuid", "anon-uuid"],
"properties": {
"email": "user@example.com",
"name": "John Doe"
}
}
],
"next": "https://us.posthog.com/api/projects/{project_id}/persons/?limit=10&offset=10"
}
Get Person
GET /posthog/api/projects/{project_id}/persons/{person_uuid}/
Dashboards
List Dashboards
GET /posthog/api/projects/{project_id}/dashboards/
Get Dashboard
GET /posthog/api/projects/{project_id}/dashboards/{dashboard_id}/
Create Dashboard
POST /posthog/api/projects/{project_id}/dashboards/
Content-Type: application/json
{
"name": "My Dashboard",
"description": "Analytics overview"
}
Update Dashboard
PATCH /posthog/api/projects/{project_id}/dashboards/{dashboard_id}/
Content-Type: application/json
{
"name": "Updated Dashboard Name"
}
Insights
List Insights
GET /posthog/api/projects/{project_id}/insights/?limit=10
Get Insight
GET /posthog/api/projects/{project_id}/insights/{insight_id}/
Create Insight
POST /posthog/api/projects/{project_id}/insights/
Content-Type: application/json
{
"name": "Daily Active Users",
"query": {
"kind": "InsightVizNode",
"source": {
"kind": "TrendsQuery",
"series": [{"kind": "EventsNode", "event": "$pageview", "math": "dau"}],
"interval": "day",
"dateRange": {"date_from": "-30d"}
}
}
}
Feature Flags
List Feature Flags
GET /posthog/api/projects/{project_id}/feature_flags/
Get Feature Flag
GET /posthog/api/projects/{project_id}/feature_flags/{flag_id}/
Create Feature Flag
POST /posthog/api/projects/{project_id}/feature_flags/
Content-Type: application/json
{
"key": "my-feature-flag",
"name": "My Feature Flag",
"active": true,
"filters": {
"groups": [{"rollout_percentage": 100}]
}
}
Update Feature Flag
PATCH /posthog/api/projects/{project_id}/feature_flags/{flag_id}/
Content-Type: application/json
{
"active": false
}
Delete Feature Flag
Use soft delete by setting deleted: true:
PATCH /posthog/api/projects/{project_id}/feature_flags/{flag_id}/
Content-Type: application/json
{
"deleted": true
}
Cohorts
List Cohorts
GET /posthog/api/projects/{project_id}/cohorts/
Get Cohort
GET /posthog/api/projects/{project_id}/cohorts/{cohort_id}/
Create Cohort
POST /posthog/api/projects/{project_id}/cohorts/
Content-Type: application/json
{
"name": "Active Users",
"groups": [
{
"properties": [
{"key": "$pageview", "type": "event", "value": "performed_event"}
]
}
]
}
Actions
List Actions
GET /posthog/api/projects/{project_id}/actions/
Create Action
POST /posthog/api/projects/{project_id}/actions/
Content-Type: application/json
{
"name": "Signed Up",
"steps": [{"event": "$identify"}]
}
Session Recordings
List Session Recordings
GET /posthog/api/projects/{project_id}/session_recordings/?limit=10
Response:
{
"results": [
{
"id": "019c8795-79e3-7a05-ac56-597b102f1960",
"distinct_id": "user-uuid",
"recording_duration": 1807,
"start_time": "2026-02-22T23:00:46.389000Z",
"end_time": "2026-02-22T23:30:53.297000Z",
"click_count": 0,
"keypress_count": 0,
"start_url": "https://example.com/register"
}
],
"has_next": false
}
Get Session Recording
GET /posthog/api/projects/{project_id}/session_recordings/{recording_id}/
Annotations
List Annotations
GET /posthog/api/projects/{project_id}/annotations/
Create Annotation
POST /posthog/api/projects/{project_id}/annotations/
Content-Type: application/json
{
"content": "New feature launched",
"date_marker": "2026-02-23T00:00:00Z",
"scope": "project"
}
Surveys
List Surveys
GET /posthog/api/projects/{project_id}/surveys/
Create Survey
POST /posthog/api/projects/{project_id}/surveys/
Content-Type: application/json
{
"name": "NPS Survey",
"type": "popover",
"questions": [
{
"type": "rating",
"question": "How likely are you to recommend us?"
}
]
}
Experiments
List Experiments
GET /posthog/api/projects/{project_id}/experiments/
Create Experiment
POST /posthog/api/projects/{project_id}/experiments/
Content-Type: application/json
{
"name": "Button Color Test",
"feature_flag_key": "button-color-test"
}
Event Definitions
List Event Definitions
GET /posthog/api/projects/{project_id}/event_definitions/?limit=10
Property Definitions
List Property Definitions
GET /posthog/api/projects/{project_id}/property_definitions/?limit=10
Pagination
PostHog uses offset-based pagination:
GET /posthog/api/projects/{project_id}/persons/?limit=10&offset=20
Response includes pagination info:
{
"count": 100,
"next": "https://us.posthog.com/api/projects/{project_id}/persons/?limit=10&offset=30",
"previous": "https://us.posthog.com/api/projects/{project_id}/persons/?limit=10&offset=10",
"results": [...]
}
For session recordings, use has_next boolean:
{
"results": [...],
"has_next": true
}
Code Examples
JavaScript
const response = await fetch(
'https://gateway.maton.ai/posthog/api/projects/',
{
headers: {
'Authorization': `Bearer ${process.env.MATON_API_KEY}`
}
}
);
const data = await response.json();
Python
import os
import requests
response = requests.get(
'https://gateway.maton.ai/posthog/api/projects/',
headers={'Authorization': f'Bearer {os.environ["MATON_API_KEY"]}'}
)
data = response.json()
Python - HogQL Query
import os
import requests
response = requests.post(
'https://gateway.maton.ai/posthog/api/projects/@current/query/',
headers={
'Authorization': f'Bearer {os.environ["MATON_API_KEY"]}',
'Content-Type': 'application/json'
},
json={
'query': {
'kind': 'HogQLQuery',
'query': 'SELECT event, count() FROM events GROUP BY event LIMIT 10'
}
}
)
data = response.json()
Notes
- Use
@currentas a shortcut for the current project ID (e.g.,/api/projects/@current/dashboards/) - Project IDs are integers (e.g.,
136209) - Person UUIDs are in standard UUID format
- The Events endpoint is deprecated; use the Query endpoint with HogQL instead
- Session recordings include activity metrics like click_count, keypress_count
- PostHog uses soft delete: use
PATCHwith{"deleted": true}instead of HTTP DELETE - IMPORTANT: When piping curl output to
jqor other commands, environment variables like$MATON_API_KEYmay not expand correctly in some shell environments. Use Python examples instead.
Error Handling
| Status | Meaning |
|---|---|
| 400 | Missing PostHog connection |
| 401 | Invalid or missing Maton API key |
| 429 | Rate limited |
| 4xx/5xx | Passthrough error from PostHog API |
Rate Limits
- Analytics endpoints (insights, persons, recordings): 240/minute, 1200/hour
- HogQL query endpoint: 120/hour
- CRUD endpoints: 480/minute, 4800/hour