API queries

Last updated:

|Edit this page

API queries enable you to query your data in PostHog. This is useful for:

When should you not use API queries?

  1. When you want to export large amounts of data. Use batch exports instead.
  2. When you want to send data to destinations like Slack or webhooks immediately. Use real-time destinations instead.

Prerequisites

Using API queries requires:

  1. A PostHog project and its project ID which you can get from your project settings.
  2. 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 a kind 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 the query_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 cache
  • async: Executes asynchronously unless fresh results exist in cache
  • force_blocking: Always executes synchronously
  • force_async: Always executes asynchronously
  • force_cache: Only returns cached results (never calculates)
  • lazy_async: Use extended cache period before asynchronous calculation
  • async_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 retrieval
  • TrendsQuery: Time-series trend analysis
  • FunnelsQuery: Conversion funnel analysis
  • RetentionQuery: User retention analysis
  • PathsQuery: 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 query
  • is_cached (for cached responses): Indicates the result came from cache
  • timings (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 result
  • cache_target_age: The timestamp until which the cached result is considered valid
  • last_refresh: When the data was last computed
  • next_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:

JSON
{
"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.

Terminal
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.

Further reading

Questions? Ask Max AI.

It's easier than reading through 622 docs articles.

Community questions

Was this page useful?