/consuming-endpoints-from-client-code
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
Claude Code
$ npx -y skills add PostHog/ai-plugin --skill consuming-endpoints-from-client-code --agent claude-code
Codex
$ npx skills add PostHog/ai-plugin --skill consuming-endpoints-from-client-code -a codex -y
Cursor
$ npx skills add PostHog/ai-plugin --skill consuming-endpoints-from-client-code -a cursor -y
Gemini CLI
$ npx skills add PostHog/ai-plugin --skill consuming-endpoints-from-client-code -a gemini-cli -y
GitHub Copilot
$ npx skills add PostHog/ai-plugin --skill consuming-endpoints-from-client-code -a github-copilot -y
OpenCode
$ npx skills add PostHog/ai-plugin --skill consuming-endpoints-from-client-code -a opencode -y
Windsurf
$ npx skills add PostHog/ai-plugin --skill consuming-endpoints-from-client-code -a windsurf -y
SKILL.md
consuming-endpoints-from-client-code.SKILL.mdname: consuming-endpoints-from-client-code
description: >
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".
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
/api/projects/{team_id}/endpoints/{name}/run- `team_id` is the project ID (numeric). Available in PostHog under project settings, or via
`projects-get` 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:
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
{
"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-code
Read more
name: consuming-endpoints-from-client-code description: > 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".
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
/api/projects/{team_id}/endpoints/{name}/run- `team_id` is the project ID (numeric). Available in PostHog under project settings, or via
`projects-get` 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:
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
{
"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-code
Official PostHog plugin for AI clients. Access PostHog products directly from your AI coding tool.
Other skills on ai-plugin.
More skills from other plugins.
brainstorming
You MUST use this before any creative work - creating features, building components, adding functionality, modifying behavior, or…
ask-matt
Ask which skill or flow fits your situation. A router over the skills in this repo.
Questions about consuming-endpoints-from-client-code.
- Which coding agents can use consuming-endpoints-from-client-code?
- consuming-endpoints-from-client-code installs in Claude Code, Codex, Cursor, Gemini CLI, GitHub Copilot, OpenCode and Windsurf with the skills CLI, using each agent's flag shown on this page. Flowy's auto-invocation, which fires a skill when your prompt matches, runs in Claude Code only.

