POST-only public endpoints

Last updated:

Update: These endpoints can now be accessed with either your Team API key or your personal API key.

As explained in our API overview page, PostHog provides two different APIs.

This page refers to our public endpoints, which use the same API key as the PostHog snippet. The endpoints documented here are used solely with POST requests, and will not return any sensitive data from your PostHog instance.

Note: For this API, you should use your 'Project API Key' from the 'Project' page in PostHog. This is the same key used in your frontend snippet.

Sending events

To send events to PostHog, you can use any of our libraries or any Mixpanel library by changing the api_host setting to the address of your instance.

If you'd prefer to do the requests yourself, you can send events in the following format:

Single event

Note: Timestamp is optional. If not set, it'll automatically be set to the current time.

Terminal
POST https://[your-instance].com/capture/
Content-Type: application/json
Body:
{
"api_key": "<ph_project_api_key>",
"event": "[event name]",
"properties": {
"distinct_id": "[your users' distinct id]",
"key1": "value1",
"key2": "value2"
},
"timestamp": "[optional timestamp in ISO 8601 format]"
}

Batch events

You can send multiple events in one go with the Batch API.

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 (see API overview).

Note: Timestamp is optional. If not set, it'll automatically be set to the current time.

Terminal
POST https://[your-instance].com/batch/
Content-Type: application/json
Body:
{
"api_key": "<ph_project_api_key>",
"batch": [
{
"event": "[event name]",
"properties": {
"distinct_id": "[your users' distinct id]",
"key1": "value1",
"key2": "value2"
},
"timestamp": "[optional timestamp in ISO 8601 format]"
},
...
]
}

Sample requests

Here are some sample curl queries for each event type. Do note that you need to insert your API key into the api_key field.

Additionally, if you're self-hosting, you'll have to substitute https://app.posthog.com/ for the URL of your instance.

Alias

Terminal
curl -v -L --header "Content-Type: application/json" -d '{
"api_key": "<ph_project_api_key>",
"properties": {
"distinct_id": "123",
"alias": "456"
},
"timestamp": "2020-08-16 09:03:11.913767",
"event": "$create_alias"
}' https://app.posthog.com/capture/

Capture

Terminal
curl -v -L --header "Content-Type: application/json" -d '{
"api_key": "<ph_project_api_key>",
"properties": {},
"timestamp": "2020-08-16 09:03:11.913767",
"distinct_id": "1234",
"event": "$event"
}' https://app.posthog.com/capture/

Group

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

Terminal
curl -v -L --header "Content-Type: application/json" -d '{
"api_key": "<ph_project_api_key>",
"event": "$groupidentify",
"properties": {
"distinct_id": "groups_setup_id",
"$group_type": "<group_type>",
"$group_key": "<company_name>",
"$group_set": {
"name": "<company_name>",
"subscription": "premium"
"date_joined": "2020-01-23T00:00:00.000Z"
}
}
}' https://app.posthog.com/capture/

Identify

Terminal
curl -v -L --header "Content-Type: application/json" -d '{
"api_key": "<ph_project_api_key>",
"timestamp": "2020-08-16 09:03:11.913767",
"context": {},
"distinct_id": "1234",
"$set": {},
"event": "$identify"
}' https://app.posthog.com/capture/

Page view

Terminal
curl -v -L --header "Content-Type: application/json" -d '{
"api_key": "<ph_project_api_key>",
"properties": {},
"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": {},
"timestamp": "2020-08-16T09:03:11.913767",
"distinct_id": "1234",
"event": "$screen"
}' https://app.posthog.com/capture/

Responses

Status code: 200

Responses
JavaScript
{
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 (token) 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
JavaScript
{
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.

Status code: 401

Responses
JavaScript
{
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 token/API key you provided is invalid.


JavaScript
{
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)

Responses
JavaScript
{
type: 'server_error',
code: 'fetch_team_fail',
detail: 'Unable to fetch team from database.'
}

Meaning: (Deprecated) This error will only occur 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 (token), returning a failure response if an error is encountered.

However, we will 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

The three cases above will cause the event to not be ingested, but you will still receive a 200: OK response from us.

This approach allows us to process events asynchronously if necessary, ensuring reliability and low latency for our event ingestion endpoints.

Feature flags

PostHog offers support for feature flags, and you can use our APIs to create and make use of feature flags. However, it is important to note that while creating a feature flag is a private action that only your team should be able to perform, checking if a feature flag is active is not.

As such, to create feature flags, you will need to use another endpoint, which we're currently still documenting. However, to check if a feature flag is enabled, you can use the following endpoint:

/decide

/decide is the endpoint used to determine if a given flag is enabled for a certain user or not. This endpoint is used by our JavaScript Library's methods for feature flags.

To get the feature flags that are enabled for a given user, you will need to perform the following request:

Terminal
POST <ph_instance_address>/decide/ # e.g. https://posthog.yourcompany.com for self-hosted users
Content-Type: application/json
Body:
{
"api_key": "<ph_project_api_key>",
"distinct_id": "[user's distinct id]"
}

Example request & response: /decide v3

/decide version 3 introduces support for feature flag payloads and a change in response structure: flags that were evaluated as false are also passed in. Further, this endpoint returns a parameter errorComputingFlags, which is true when we didn't manage to compute some flags, because of, say the database being down or some other reason. This allows for making partial updates to currently active flags in your clients.

You can optionally pass in an object containing person or group properties to override server values. This is how posthog-js handles overriding server properties, which can be useful if you want to make feature flag decisions without waiting for these changes to be ingested and available on PostHog's servers.

Request

Terminal
curl -v -L --header "Content-Type: application/json" -d ' {
"api_key": "<ph_project_api_key>",
"distinct_id": "1234",
"groups" : {
"<groupType>": "<groupKey>"
},
"person_properties": {"<personProp1>": "<personVal1>"},
"group_properties": {"group type": {"<groupProp1>":"<groupVal1>"}}
}' https://app.posthog.com/decide?v=3

Response

Terminal
{
"config": {
"enable_collect_everything": true
},
"editorParams": {},
"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"}
}
}

Example request & response: /decide v2 (legacy)

/decide version 2 introduces support for multivariate feature flags and has a slightly different schema for the response. posthog-js version 1.13 and up will use version 2 of the decide endpoint by default.

Request

Terminal
curl -v -L --header "Content-Type: application/json" -d ' {
"api_key": "<ph_project_api_key>",
"distinct_id": "1234",
"groups" : {
"<groupType>": "<groupKey>"
}
}' https://app.posthog.com/decide?v=2

Response

Terminal
{
"config": {
"enable_collect_everything": true
},
"editorParams": {},
"isAuthenticated": false,
"supportedCompression": [
"gzip",
"lz64"
],
"featureFlags": {
"my-awesome-flag": true,
"my-awesome-flag-2": true,
"my-multivariate-flag": "some-string-value"
}
}

Example request & response: /decide v1 (legacy)

/decide version 1 is still the default if the query parameter v is not specified, although the latest posthog-js library no longer uses version 1 of the decide endpoint, and we recommend using version 2 as described above.

Request

Terminal
curl -v -L --header "Content-Type: application/json" -d ' {
"api_key": "<ph_project_api_key>",
"distinct_id": "1234"
}' https://app.posthog.com/decide?v=1

Response

Terminal
{
"config": {
"enable_collect_everything": true
},
"editorParams": {},
"isAuthenticated": false,
"supportedCompression": [
"gzip",
"lz64"
],
"featureFlags": [
"my-awesome-flag-1",
"my-awesome-flag-2",
"my-multivariate-flag"
]
}

From this response, if you are looking to use feature flags in your backend, you will most likely need only the values for the featureFlags key, which indicate what flags are on for the user with the distinct ID you provided. These flags persist for users (unless you change your flag settings), so you can cache them rather than send a request to the endpoint each time if you so wish.

Note that if a multivariate feature flag is enabled for a given user, it will still show up in featureFlags under decide version 1, but its value will only be accessible when using decide version 2.

Reading data from PostHog

We have another set of APIs to read/modify anything in PostHog. See our API documentation for more information.

Also, feel free to reach out if you'd like help with the API.

Questions?

Was this page useful?