Claude
Skills
Sign in
Back

consuming-endpoints-from-client-code

Included with Lifetime
$97 forever

Wire a PostHog endpoint into a client app or SDK. Covers fetching the OpenAPI spec, generating a typed client with openapi-generator or @hey-api/openapi-ts, sending the right auth header, shaping the variables payload (HogQL code_name vs insight breakdown property), handling rate-limit and materialised-endpoint error responses. Use when the user says "how do I call my endpoint", "generate a client for this", or "what auth header do I use".

Backend & APIs

What this skill does


# Consuming endpoints from client code

This skill is the **caller-side** counterpart to `creating-an-endpoint`. It helps integrate an
existing endpoint into a separate codebase — a mobile app, server backend, customer dashboard,
or downstream pipeline. No PostHog code is modified here.

## When to use this skill

- "How do I call my endpoint?" / "What does a request look like?"
- "Generate a typed TypeScript / Python / Go client for this endpoint"
- "I'm getting a 401 calling the endpoint" / auth questions
- "The endpoint rejects my call when I omit `user_id`" → materialised-endpoint variable
  questions
- "How do I handle rate limits?"

If the user is **creating** the endpoint, use `creating-an-endpoint` first.

## Available tools

| Tool                    | Purpose                                                                                                    |
| ----------------------- | ---------------------------------------------------------------------------------------------------------- |
| `endpoint-get`          | Full config for a named endpoint, including the query shape and required variables                         |
| `endpoint-openapi-spec` | OpenAPI 3.0 spec for one endpoint, ready to feed to a code generator                                       |
| `endpoint-run`          | A live call against the endpoint — useful to confirm a payload works before sharing it with the user's app |

## The endpoint URL

```text
/api/projects/{team_id}/endpoints/{name}/run
```

- `team_id` is the project ID (numeric). Available in PostHog under project settings, or via
  `posthog-get-projects` if the user doesn't know it.
- `name` is the endpoint name — see `endpoints-get-all` if the user isn't sure.
- The trailing `/run` is required.

`POST` is the canonical method. `GET` also works for simple cases without a request body but
POST is preferred — variables go in the body.

## Auth

Endpoints are authenticated with a **personal API key**. The header is:

```http
Authorization: Bearer <key>
```

Keys are scoped — for endpoints, the key needs at least `endpoint:read`. If the user gets a 403,
they're usually missing the scope; if they get a 401, the key is missing or malformed.

Never put a personal API key in client-side code that's shipped to end users (mobile apps,
browser JS). Personal API keys grant scoped account access. For customer-facing apps, route
through the user's own backend, which holds the key.

## The request payload

```json
{
  "variables": { "code_name_1": value, "code_name_2": value },
  "limit": 100,
  "offset": 0,
  "refresh": "cache"
}
```

| Field       | Notes                                                                                                                                                                     |
| ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `variables` | Keyed by `code_name` for HogQL endpoints; for insight endpoints with breakdowns, key is the **breakdown property name**                                                   |
| `limit`     | Max rows returned.                                                                                                                                                        |
| `offset`    | Skip rows. Only HogQL endpoints                                                                                                                                           |
| `refresh`   | `"cache"` (return cached results if fresh enough), `"force"` (always recalculate), `"direct"` (bypass materialisation, materialised endpoints only). Default is `"cache"` |

Call `endpoint-get` to see the exact variable shape. The response includes the query definition
with declared variables — each variable's `code_name` is what the client should send.

## Materialised endpoints: all variables are required

If `endpoint-get` shows `is_materialized: true` on the current version, the endpoint requires
**every declared variable** to be passed on each call. This is a security boundary — without
filters, a single call would return the entire pre-aggregated dataset.

Common symptom: the user's app worked when the endpoint was unmaterialised, then started
returning 400 errors after materialisation was enabled. The error message lists which variables
are missing.

Optional/partial variables on materialised endpoints are a known limitation the PostHog team plans
to lift. If requiring every variable is blocking the user's use case, send a note via the
`agent-feedback` tool — that demand signal is how the team prioritises it.

## Generating a typed client

The endpoint exposes its own OpenAPI 3.0 spec via `endpoint-openapi-spec`. Feed that into a code
generator:

| Language   | Tool                    | Command shape                                                                    |
| ---------- | ----------------------- | -------------------------------------------------------------------------------- |
| TypeScript | `@hey-api/openapi-ts`   | `openapi-ts -i spec.json -o ./generated`                                         |
| TypeScript | `openapi-generator-cli` | `openapi-generator-cli generate -i spec.json -g typescript-fetch -o ./generated` |
| Python     | `openapi-generator-cli` | `openapi-generator-cli generate -i spec.json -g python -o ./generated`           |
| Go         | `oapi-codegen`          | `oapi-codegen -package=client spec.json > client.go`                             |

The generated client gives the user types for the variables payload and the response shape. Re-
generate when the endpoint's query changes (each new version may have different variables).

If the user has multiple endpoints, generate a spec per endpoint and either combine them, or
generate one client per endpoint and use them side-by-side.

## Response shape

A typical successful response:

```json
{
  "results": [[...], [...]],
  "columns": ["col_a", "col_b"],
  "types": ["Int64", "String"],
  "hasMore": false,
  "name": "endpoint_name",
  "endpoint_version": 4,
  "endpoint_version_created_at": "2026-01-15T..."
}
```

- `results` is an array of rows; each row is an array of cell values in the order of `columns`.
- `endpoint_version` tells the client which version actually ran — useful for logging and for
  pinning to a known version with `?version=N`.

For insight endpoints, the response shape depends on the query kind (`TrendsQuery`,
`LifecycleQuery`, `RetentionQuery`) — the OpenAPI spec captures the right shape for the current
version. Insight kinds that can't be materialised (e.g. `FunnelsQuery`) still return their inline
result shape.

## Calling from the PostHog CLI

For local testing, scripts, or CI, the repo's `posthog-cli` calls endpoints without hand-rolling
HTTP:

- `posthog-cli exp endpoints run` — execute an endpoint (from a local YAML definition)
- `posthog-cli exp endpoints {list,get,pull,push,diff}` — inspect endpoints, or manage them as YAML
  files in version control (GitOps-style)

Auth uses the same personal API key, via `posthog-cli login` or the `POSTHOG_CLI_API_KEY` /
`POSTHOG_CLI_PROJECT_ID` / `POSTHOG_CLI_HOST` env vars. (These live under `exp` — experimental, may
change.)

## Error responses to handle

| Status | When                                                                           | Handling                                                                         |
| ------ | ------------------------------------------------------------------------------ | -------------------------------------------------------------------------------- |
| 400    | Missing required variable on a materialised endpoint, or invalid variable type | Surface the error message; fix the call                                          |
| 401    | Missing / wrong personal API key                                               | Check the Authorization header       

Related in Backend & APIs