PostHog provides POST-only public endpoints to capture events, update user or group data, evaluate feature flags, and more. These use your project API key and do not return any sensitive data from your PostHog instance.
Sending events
PostHog is event-based meaning your analytics data takes the form of a value sent by a specific user with optional additional data. Much of the functionality of PostHog, including updating users or groups, combining users, and evaluating feature flags, is driven by this event-based structure. Our SDKs handle the different types of events for you, but with the API, you need to send the right type of event (listed below) to get the functionality you want. The default PostHog events have the $
prefix.
Note: Make sure to send API requests to the correct domain. These are
https://app.posthog.com
for US Cloud,https://eu.posthog.com
for EU Cloud, and your self-hosted domain for self-hosted instances. Confirm yours by checking your URL from your PostHog instance.
Here are examples of the types of events you can send:
Single event
curl -v -L --header "Content-Type: application/json" -d '{"event": "event name","api_key": "<ph_project_api_key>","distinct_id": "user distinct id","properties": {"account_type": "pro"},"timestamp": "[optional timestamp in ISO 8601 format]"}' https://app.posthog.com/capture/
Batch events
You can a list of multiple events in one request with the /batch
API route.
There is no limit on the number of events you can send in a batch, but the entire request body must be less than 20MB by default.
curl -v -L --header "Content-Type: application/json" -d '{"api_key": "<ph_project_api_key>","historical_migration": false,"batch": [{"event": "batched_event_name_1","properties": {"distinct_id": "user distinct id","account_type": "pro"},"timestamp": "[optional timestamp in ISO 8601 format]"}# ...]}' https://app.posthog.com/batch/
The historical_migration
field is optional and must be set to true
when running imports from other systems: this will ensure that events are processed in order without triggering our spike detection systems. For realtime events, it can be left out or set to false
.
Alias
This assigns another distinct ID to the same user. Read more in our identify docs.
curl -v -L --header "Content-Type: application/json" -d '{"api_key": "<ph_project_api_key>","distinct_id": "123","properties": {"alias": "456"},"timestamp": "[optional timestamp in ISO 8601 format]","event": "$create_alias"}' https://app.posthog.com/capture/
Groups
Captures a group event. Read more in our group analytics docs.
Note:
company
is a group type. You can set it to the value you want such asorganization
,project
, orchannel
.
Note: This event will NOT create a new group if a new key being used. To create a group, see the Group identify event.
curl -v -L --header "Content-Type: application/json" -d '{"api_key": "<ph_project_api_key>","event": "$event","distinct_id": "1234","properties": {"$groups": {"company": "<company_name>"}}}' https://app.posthog.com/capture/
Group identify
Updates group information. Read more in our group analytics docs.
curl -v -L --header "Content-Type: application/json" -d '{"api_key": "<ph_project_api_key>","event": "$groupidentify","distinct_id": "groups_setup_id","properties": {"$group_type": "<group_type>","$group_key": "<company_name>","$group_set": {"name": "<company_name>","subscription": "premium""date_joined": "[optional timestamp in ISO 8601 format]"}}}' https://app.posthog.com/capture/
Identify
Updates user information. Read more in our identify docs.
curl -v -L --header "Content-Type: application/json" -d '{"api_key": "<ph_project_api_key>","timestamp": "2020-08-16 09:03:11.913767","properties": {"$set": {},},"distinct_id": "1234","event": "$identify"}' https://app.posthog.com/capture/
Pageview
curl -v -L --header "Content-Type: application/json" -d '{"api_key": "<ph_project_api_key>","properties": {"title": "TheTitle"},"timestamp": "2020-08-16T09:03:11.913767","distinct_id": "1234","event": "$pageview"}' https://app.posthog.com/capture/
Screen view
curl -v -L --header "Content-Type: application/json" -d '{"api_key": "<ph_project_api_key>","properties": {"$screen_name": "TheScreen"},"timestamp": "2020-08-16T09:03:11.913767","distinct_id": "1234","event": "$screen"}' https://app.posthog.com/capture/
Feature flags
PostHog's feature flags enable you to safely deploy and roll back new features.
There are 3 steps to implement feature flags using the PostHog API:
Step 1: Evaluate the feature flag value using the /decide
/decide
is the endpoint used to determine if a given flag is enabled for a certain user or not.
Request
curl -v -L --header "Content-Type: application/json" -d ' {"api_key": "<ph_project_api_key>","distinct_id": "distinct_id_of_your_user","groups" : { # Required only for group-based feature flags"group_type": "group_id" # Replace "group_type" with the name of your group type. Replace "group_id" with the id of your group.},"person_properties": {"<personProp1>": "<personVal1>"}, # Optional. Include any properties used to calculate the value of the feature flag."group_properties": {"group type": {"<groupProp1>":"<groupVal1>"}} # Optional. Include any properties used to calculate the value of the feature flag.}' https://app.posthog.com/decide?v=3 # or https://eu.posthog.com/decide?v=3
Note:
person_properties
andgroup_properties
are optional.By default, flag evaluation uses the user and group properties stored in PostHog. You only need to provide
person_properties
andgroup_properties
if you wish to use different values than the ones in PostHog.
Response
{"config": {"enable_collect_everything": true},"editorParams": {},"errorComputingFlags": false,"isAuthenticated": false,"supportedCompression": ["gzip","lz64"],"featureFlags": {"my-awesome-flag": true,"my-awesome-flag-2": true,"my-multivariate-flag": "some-string-value","flag-thats-not-on": false,},"featureFlagPayloads": {"my-awesome-flag": "example-payload-string","my-awesome-flag-2": "{\"color\": \"blue\", \"animal\": \"hedgehog\"}"}}
Note:
errorComputingFlags
will returntrue
if we didn't manage to compute some flags (for example, if there's an ongoing incident involving flag evaluation).This enables partial updates to currently active flags in your clients.
Step 2: Include feature flag information when capturing events
If you want use your feature flag to breakdown or filter events in your insights, you'll need to include feature flag information in those events.
This ensures that the feature flag value is attributed correctly to the event.
Note: this step is only required for events captured using our server-side SDKs or API.
To do this, include the $feature/feature_flag_name
property in your event:
curl -v -L --header "Content-Type: application/json" -d ' {"api_key": "<ph_project_api_key>","distinct_id": "distinct_id_of_your_user","properties": {"$feature/feature-flag-key": "variant-key", # replace feature-flag-key with your flag key. Replace 'variant-key' with the key of your variant},"event": "your_event_name"}' https://app.posthog.com/capture/ # or https://eu.posthog.com/capture/
Step 3: Send a $feature_flag_called
event
To track usage of your feature flag and view related analytics in PostHog, submit the $feature_flag_called
event whenever you check a feature flag value in your code.
You need to include two properties with this event:
$feature_flag_response
: This is the name of the variant the user has been assigned to e.g., "control" or "test"$feature_flag
: This is the key of the feature flag in your experiment.
curl -v -L --header "Content-Type: application/json" -d ' {"api_key": "<ph_project_api_key>","distinct_id": "distinct_id_of_your_user","properties": {"$feature_flag": "feature-flag-key","$feature_flag_response": "variant-name"},"event": "$feature_flag_called"}' https://app.posthog.com/capture/ # or https://eu.posthog.com/capture/
Advanced: Overriding server properties
Sometimes, you may want to evaluate feature flags using person properties, groups, or group properties that haven't been ingested yet, or were set incorrectly earlier.
You can provide properties to evaluate the flag with by using the person properties
, groups
, and group properties
arguments. PostHog will then use these values to evaluate the flag, instead of any properties currently stored on your PostHog server.
For example:
curl -v -L --header "Content-Type: application/json" -d ' {"api_key": "<ph_project_api_key>","distinct_id": "distinct_id_of_your_user","groups" : { # Required only for group-based feature flags"group_type": "group_id" # Replace "group_type" with the name of your group type. Replace "group_id" with the id of your group.},"person_properties": {"<personProp1>": "<personVal1>"},"group_properties": {"group_type": {"property_name":"property_value"}} #}' https://app.posthog.com/decide?v=3 # or https://eu.posthog.com/decide?v=3
Overriding GeoIP properties
By default, a user's GeoIP properties are set using the IP address they use to capture events on the frontend. You may want to override the these properties when evaluating feature flags. A common reason to do this is when you're not using PostHog on your frontend, so the user has no GeoIP properties.
To override the GeoIP properties used to evaluate a feature flag, provide an IP address in the HTTP_X_FORWARDED_FOR
when making your /decide
request:
curl -v -L \--header "Content-Type: application/json" \--header "HTTP_X_FORWARDED_FOR: the_client_ip_address_to_use " \-d ' {"api_key": "<ph_project_api_key>","distinct_id": "distinct_id_of_your_user","groups": { # Optional. Required only for group-based feature flags"group_type": "group_id" # Replace "group_type" with the name of your group type. Replace "group_id" with the id of your group.},"person_properties": {"<personProp1>": "<personVal1>"}, # Optional. Include any properties used to calculate the value of the feature flag."group_properties": {"group type": {"<groupProp1>":"<groupVal1>"}} # Optional. Include any properties used to calculate the value of the feature flag.}' https://app.posthog.com/decide?v=3 # or https://eu.posthog.com/decide?v=3
The list of properties that this overrides:
- $geoip_city_name
- $geoip_country_name
- $geoip_country_code
- $geoip_continent_name
- $geoip_continent_code
- $geoip_postal_code
- $geoip_time_zone
Responses
Status code: 200
Response:
{"status": 1}
Meaning: A 200: OK
response means we have successfully received the payload, it is in the correct format, and the project API key (api_key) is valid. It does not imply that events are valid and will be ingested. As mentioned under Invalid events, certain event validation errors may cause an event not to be ingested.
Status code: 400
Responses:
{"type": "validation_error","code": "invalid_project","detail": "Invalid Project ID.","attr": "project_id"}
Meaning: We were unable to determine the project to associate the events with.
{"type": "validation_error","code": "invalid_payload","detail": "Malformed request data","attr": null}
Meaning: Request payload data formatted incorrectly.
Status code: 401
Responses:
{"type": "authentication_error","code": "invalid_api_key","detail": "Project API key invalid. You can find your project API key in PostHog project settings."}
Meaning: The project API key you provided is invalid.
{"type": "authentication_error","code": "invalid_personal_api_key","detail": "Invalid Personal API key."}
Meaning: The personal API key you used for authentication is invalid.
Status code: 503 (Deprecated)
Response:
{"type": "server_error","code": "fetch_team_fail","detail": "Unable to fetch team from database."}
Meaning: (Deprecated) This error only occurs in self-hosted Postgres instances if the database becomes unavailable. On ClickHouse-backed instances database failures cause events to be added to a dead letter queue, from which they can be recovered.
Invalid events
We perform basic validation on the payload and project API key (api_key), returning a failure response if an error is encountered.
PostHog does not return an error to the client when the following happens:
- An event does not have a name
- An event does not have the
distinct_id
field set - The
distinct_id
field of an event has an empty value
These three cases above cause the event to not be ingested, but you still receive a 200: OK
response from PostHog.
This approach enables us to process events asynchronously if necessary, ensuring reliability and low latency for our event ingestion endpoints.