API queries enable you to query your data in PostHog. This is useful for:
- Building user or customer-facing analytics.
- Pulling aggregated PostHog data into your own or other apps.
When should you not use API queries?
- When you want to export large amounts of data. Use batch exports instead.
- When you want to send data to destinations like Slack or webhooks immediately. Use real-time destinations instead.
Prerequisites
Using API queries requires:
- A PostHog project and its project ID which you can get from your project settings.
- A personal API key for your project with the Query Read permission. You can create this in your user settings.
Creating a query
To create a query, you make a POST
request to the /api/projects/:project_id/query/
endpoint. The body of the request should be a JSON object with a query
property with a kind
and query
property.
For example, to create a query that gets events where the $current_url
contains blog, you use kind: HogQLQuery
and SQL like:
curl \-H 'Content-Type: application/json' \-H "Authorization: Bearer $POSTHOG_PERSONAL_API_KEY" \<ph_app_host>/api/projects/:project_id/query/ \-d '{"query": {"kind": "HogQLQuery","query": "select properties.$current_url from events where properties.$current_url like '\''%/blog%'\'' limit 100"}}'
This is also useful for querying non-event data like persons, data warehouse, session replay metadata, and more. For example, to get a list of all people with the email
property:
curl \-H 'Content-Type: application/json' \-H "Authorization: Bearer $POSTHOG_PERSONAL_API_KEY" \<ph_app_host>/api/projects/:project_id/query/ \-d '{"query": {"kind": "HogQLQuery","query": "select properties.email from persons where properties.email is not null"}}'
Every query you run is logged in the query_log
table along with details like duration, read bytes, read rows, and more.
Query parameters
Top level request parameters include:
query
(required): Specifies what data to retrieve. This must include akind
property that defines the query type.client_query_id
(optional): A client-provided identifier for tracking the query.refresh
(optional): Controls caching behavior and execution mode (sync vs async).filters_override
(optional): Dashboard-specific filters to apply.variables_override
(optional): Variable overrides for queries that support variables.name
(optional): A name for the query to better identify it in thequery_log
table.
Caching and execution modes
The refresh
parameter controls the execution mode of the query. It can be one of the following values:
blocking
(default): Executes synchronously unless fresh results exist in cacheasync
: Executes asynchronously unless fresh results exist in cacheforce_blocking
: Always executes synchronouslyforce_async
: Always executes asynchronouslyforce_cache
: Only returns cached results (never calculates)lazy_async
: Use extended cache period before asynchronous calculationasync_except_on_cache_miss
: Use cache but execute synchronously on cache miss
Tip: To cancel a running query, send a
DELETE
request to the/api/projects/:project_id/query/:query_id/
endpoint.
Query types
The kind
property in the query
parameter can be one of the following values.
HogQLQuery
: Queries using PostHog's version of SQL.EventsQuery
: Raw event data retrievalTrendsQuery
: Time-series trend analysisFunnelsQuery
: Conversion funnel analysisRetentionQuery
: User retention analysisPathsQuery
: User journey path analysis
Beyond HogQLQuery
, these are mostly used to power PostHog internally and are not useful for you, but you can see the frontend query schema for a complete list and more details.
Response structure
The response format depends on the query type, but all responses include:
results
: The data returned by the queryis_cached
(for cached responses): Indicates the result came from cachetimings
(when available): Performance metrics for the query execution
Cached responses
API queries are cached by default. You can check if a response is cached by checking the is_cached
property. Responses also contain cache-related details like:
cache_key
: A unique identifier for the cached resultcache_target_age
: The timestamp until which the cached result is considered validlast_refresh
: When the data was last computednext_allowed_client_refresh
: The earliest time when a client can request a fresh calculation
Asynchronous queries
For asynchronous queries (like ones with refresh: async
), the initial response includes a query status with its completion status, query ID, start time, and more:
{"query_status": {"id": "2fbd4b19413342a4ad08c307155187bc","team_id": 123,"complete": false}}
You can then poll the status by sending a GET
request to the /api/projects/:project_id/query/:query_id/
endpoint.
curl \-H "Authorization: Bearer $POSTHOG_PERSONAL_API_KEY" \<ph_app_host>/api/projects/:project_id/query/$QUERY_ID/
Rate limits
API queries on our free plans are limited to:
- 1 query running concurrently
- 10,000 rows
- 1TB read bytes during query processing
Our ridiculously cheap plan lifts this to:
- 3 queries running concurrently
If you need higher limits than these, get in touch with our sales team. We promise they're friendly and technical enough to know what an API is.