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.

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:

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 v2

/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

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

Response

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

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

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

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.

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.

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

POST https://[your-instance].com/capture/
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

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",
"context": "{}",
"type": "alias",
"event": "$create_alias"
}' https://app.posthog.com/batch/

Capture

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

Identify

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

Page

curl -v -L --header "Content-Type: application/json" -d '{
"api_key": "<ph_project_api_key>",
"properties": {},
"timestamp": "2020-08-16 09:03:11.913767",
"category": "some category",
"context": {},
"distinct_id": "1234",
"type": "page",
"event": "$page",
"name": "a page",
"messageId": "123"
}' https://app.posthog.com/batch/

Screen

curl -v -L --header "Content-Type: application/json" -d '{
"api_key": "<ph_project_api_key>",
"properties": {},
"timestamp": "2020-08-16 09:03:11.913767",
"category": "some category",
"context": {},
"distinct_id": "1234",
"type": "screen",
"event": "$screen",
"name": "a page",
"messageId": "123"
}' https://app.posthog.com/batch/

Responses

Status code: 200

Responses
{
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
{
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
{
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.



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

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 in the PostHog Users Slack if you'd like help with the API.