POST-only public endpoints

Last updated:

|Edit this page

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

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

Terminal
curl -v -L --header "Content-Type: application/json" -d '{
"api_key": "<ph_project_api_key>",
"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/

Alias

This assigns another distinct ID to the same user. Read more in our identify docs.

Terminal
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 as organization, project, or channel.

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

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

Terminal
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

Terminal
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

Terminal
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

Terminal
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 and group_properties are optional.

By default, flag evaluation uses the user and group properties stored in PostHog. You only need to provide person_properties and group_properties if you wish to use different values than the ones in PostHog.

Response

Terminal
{
"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 return true 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:

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

  1. $feature_flag_response: This is the name of the variant the user has been assigned to e.g., "control" or "test"
  2. $feature_flag: This is the key of the feature flag in your experiment.
Terminal
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:

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

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

  1. $geoip_city_name
  2. $geoip_country_name
  3. $geoip_country_code
  4. $geoip_continent_name
  5. $geoip_continent_code
  6. $geoip_postal_code
  7. $geoip_time_zone

Responses

Status code: 200

Response:

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

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

JSON
{
"type": "validation_error",
"code": "invalid_payload",
"detail": "Malformed request data",
"attr": null
}

Meaning: Request payload data formatted incorrectly.

Status code: 401

Responses:

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

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

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

Questions?

Was this page useful?