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:
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:
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- kindproperty 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_logtable.
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
DELETErequest 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:
You can then poll the status by sending a GET request to the /api/projects/:project_id/query/:query_id/ endpoint.
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.