Skip to main content
The Query endpoint provides a powerful interface for analyzing event data using PostHog’s query language. This is the recommended way to retrieve and analyze events programmatically.

Endpoint

POST /api/projects/:project_id/query

Authentication

Use a Personal API Key in the Authorization header:
Authorization: Bearer <your_personal_api_key>

Request Parameters

project_id
string
required
The ID of your PostHog project. Found in your project settings.

Request Body

query
object
required
The query object defining what data to retrieve. The structure depends on the query type.Supported query types:
  • HogQLQuery - SQL-like queries using HogQL
  • EventsQuery - Structured event queries
  • TrendsQuery - Time-series analysis
  • FunnelsQuery - Funnel analysis
  • RetentionQuery - Retention analysis
  • PathsQuery - User path analysis
refresh
string
default:"blocking"
Controls query execution and caching behavior:
  • blocking - Wait for fresh results, use cache if fresh
  • async - Return cached results immediately, refresh in background
  • force_blocking - Always calculate fresh results
  • force_async - Always return cached results, refresh in background
  • lazy_async - Use cache, only calculate if cache is missing
client_query_id
string
Optional identifier to track this specific query. Used for monitoring and cancellation.
filters_override
object
Dashboard filters to apply to the query. Used when executing queries in a dashboard context.
variables_override
object
Variable values to override in the query. Allows parameterized queries.

Response

The response structure varies based on the query type.
results
array
Query results. Structure depends on the query type.
query_status
object
Information about query execution status.
complete
boolean
Whether the query has completed execution.
error
boolean
Whether an error occurred during execution.
error_message
string
Error message if an error occurred.
columns
array
Column names for the query results.
types
array
Data types for each column.

Examples

curl -X POST https://app.posthog.com/api/projects/12345/query \
  -H "Authorization: Bearer <your_personal_api_key>" \
  -H "Content-Type: application/json" \
  -d '{
    "query": {
      "kind": "HogQLQuery",
      "query": "SELECT event, timestamp, distinct_id, properties FROM events WHERE timestamp > now() - INTERVAL 1 DAY LIMIT 100"
    }
  }'

HogQL Query Language

HogQL is PostHog’s SQL-like query language that provides powerful event analysis capabilities.

Basic Syntax

SELECT column1, column2, aggregate_function(column3)
FROM events
WHERE conditions
GROUP BY column1, column2
ORDER BY column1
LIMIT 100

Available Tables

  • events - All captured events
  • persons - Person records
  • person_distinct_ids - Mapping of distinct IDs to persons
  • sessions - Session data
  • cohorts - User cohorts

Common Functions

  • count() - Count rows
  • countDistinct(column) - Count unique values
  • avg(column) - Average value
  • sum(column) - Sum values
  • min(column), max(column) - Min/max values
  • toDate(timestamp) - Convert to date
  • dateDiff('day', date1, date2) - Date difference

Event Properties

Access event properties using dot notation:
SELECT properties.button_name, count() 
FROM events 
WHERE event = 'button_clicked'
GROUP BY properties.button_name

Query Status

For async queries, check the query status using:
GET /api/projects/:project_id/query/:query_id
Response:
{
  "query_status": {
    "complete": false,
    "error": false,
    "progress": 0.45
  }
}

Canceling Queries

Cancel a running query:
curl -X DELETE https://app.posthog.com/api/projects/12345/query/my_query_id \
  -H "Authorization: Bearer <your_personal_api_key>"

Rate Limits

Query endpoint rate limits:
  • Burst: 20 requests per 10 seconds
  • Sustained: 100 requests per hour
  • HogQL specific: Additional limits based on query complexity
Rate limits are higher for teams with paid plans. Contact support for enterprise limits.

Best Practices

  1. Use time filters: Always filter by timestamp to limit data scanned
  2. Limit results: Use LIMIT to avoid retrieving excessive data
  3. Use async for large queries: Set refresh: "async" for queries that may take time
  4. Cache when possible: Use refresh: "blocking" to benefit from query caching
  5. Monitor query performance: Use client_query_id to track query execution
  6. Aggregate at query time: Use GROUP BY and aggregations to reduce data transfer

Error Handling

Common error scenarios:
  • 400 Bad Request: Invalid query syntax or parameters
  • 401 Unauthorized: Invalid or missing API key
  • 403 Forbidden: Insufficient permissions
  • 429 Too Many Requests: Rate limit exceeded
  • 500 Internal Server Error: Query execution error
try:
    response = requests.post(url, headers=headers, json=data)
    response.raise_for_status()
    result = response.json()
    
    if result.get("query_status", {}).get("error"):
        error_msg = result["query_status"].get("error_message")
        print(f"Query error: {error_msg}")
    else:
        # Process results
        pass
except requests.exceptions.HTTPError as e:
    print(f"HTTP error: {e}")
except requests.exceptions.RequestException as e:
    print(f"Request failed: {e}")

Build docs developers (and LLMs) love