A collection of PostHog skills for enhancing AI-assisted workflows. Add this repo as a Claude Code plugin marketplace to get access to all PostHog skills: Then install individual plugins: Or browse available plugins: Copy any skill directory to .claude/skills/ in your project: Any directory under skills/ that contains a .claude-plugin/plugin.json is automatically discovered and added to the market
Add this skill
npx mdskills install PostHog/consuming-endpoints-from-client-code@PostHog? Sign in with GitHub to claim this listing.Comprehensive client integration guide with strong auth, error handling, and materialisation rules
1---2name: consuming-endpoints-from-client-code3description: >4 Wire a PostHog endpoint into a client app or SDK. Covers fetching the OpenAPI spec, generating a5 typed client with openapi-generator or @hey-api/openapi-ts, sending the right auth header,6 shaping the variables payload (HogQL code_name vs insight breakdown property), handling7 rate-limit and materialised-endpoint error responses. Use when the user says "how do I call my8 endpoint", "generate a client for this", or "what auth header do I use".9---1011# Consuming endpoints from client code1213This skill is the **caller-side** counterpart to `creating-an-endpoint`. It helps integrate an14existing endpoint into a separate codebase — a mobile app, server backend, customer dashboard,15or downstream pipeline. No PostHog code is modified here.1617## When to use this skill1819- "How do I call my endpoint?" / "What does a request look like?"20- "Generate a typed TypeScript / Python / Go client for this endpoint"21- "I'm getting a 401 calling the endpoint" / auth questions22- "The endpoint rejects my call when I omit `user_id`" → materialised-endpoint variable23 questions24- "How do I handle rate limits?"2526If the user is **creating** the endpoint, use `creating-an-endpoint` first.2728## Available tools2930| Tool | Purpose |31| ----------------------- | ---------------------------------------------------------------------------------------------------------- |32| `endpoint-get` | Full config for a named endpoint, including the query shape and required variables |33| `endpoint-openapi-spec` | OpenAPI 3.0 spec for one endpoint, ready to feed to a code generator |34| `endpoint-run` | A live call against the endpoint — useful to confirm a payload works before sharing it with the user's app |3536## The endpoint URL3738```text39/api/projects/{team_id}/endpoints/{name}/run40```4142- `team_id` is the project ID (numeric). Available in PostHog under project settings, or via43 `posthog-get-projects` if the user doesn't know it.44- `name` is the endpoint name — see `endpoints-get-all` if the user isn't sure.45- The trailing `/run` is required.4647`POST` is the canonical method. `GET` also works for simple cases without a request body but48POST is preferred — variables go in the body.4950## Auth5152Endpoints are authenticated with a **personal API key**. The header is:5354```http55Authorization: Bearer <key>56```5758Keys are scoped — for endpoints, the key needs at least `endpoint:read`. If the user gets a 403,59they're usually missing the scope; if they get a 401, the key is missing or malformed.6061Never put a personal API key in client-side code that's shipped to end users (mobile apps,62browser JS). Personal API keys grant scoped account access. For customer-facing apps, route63through the user's own backend, which holds the key.6465## The request payload6667```json68{69 "variables": { "code_name_1": value, "code_name_2": value },70 "limit": 100,71 "offset": 0,72 "refresh": "cache"73}74```7576| Field | Notes |77| ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |78| `variables` | Keyed by `code_name` for HogQL endpoints; for insight endpoints with breakdowns, key is the **breakdown property name** |79| `limit` | Max rows returned. |80| `offset` | Skip rows. Only HogQL endpoints |81| `refresh` | `"cache"` (return cached results if fresh enough), `"force"` (always recalculate), `"direct"` (bypass materialisation, materialised endpoints only). Default is `"cache"` |8283Call `endpoint-get` to see the exact variable shape. The response includes the query definition84with declared variables — each variable's `code_name` is what the client should send.8586## Materialised endpoints: all variables are required8788If `endpoint-get` shows `is_materialized: true` on the current version, the endpoint requires89**every declared variable** to be passed on each call. This is a security boundary — without90filters, a single call would return the entire pre-aggregated dataset.9192Common symptom: the user's app worked when the endpoint was unmaterialised, then started93returning 400 errors after materialisation was enabled. The error message lists which variables94are missing.9596Optional/partial variables on materialised endpoints are a known limitation the PostHog team plans97to lift. If requiring every variable is blocking the user's use case, send a note via the98`agent-feedback` tool — that demand signal is how the team prioritises it.99100## Generating a typed client101102The endpoint exposes its own OpenAPI 3.0 spec via `endpoint-openapi-spec`. Feed that into a code103generator:104105| Language | Tool | Command shape |106| ---------- | ----------------------- | -------------------------------------------------------------------------------- |107| TypeScript | `@hey-api/openapi-ts` | `openapi-ts -i spec.json -o ./generated` |108| TypeScript | `openapi-generator-cli` | `openapi-generator-cli generate -i spec.json -g typescript-fetch -o ./generated` |109| Python | `openapi-generator-cli` | `openapi-generator-cli generate -i spec.json -g python -o ./generated` |110| Go | `oapi-codegen` | `oapi-codegen -package=client spec.json > client.go` |111112The generated client gives the user types for the variables payload and the response shape. Re-113generate when the endpoint's query changes (each new version may have different variables).114115If the user has multiple endpoints, generate a spec per endpoint and either combine them, or116generate one client per endpoint and use them side-by-side.117118## Response shape119120A typical successful response:121122```json123{124 "results": [[...], [...]],125 "columns": ["col_a", "col_b"],126 "types": ["Int64", "String"],127 "hasMore": false,128 "name": "endpoint_name",129 "endpoint_version": 4,130 "endpoint_version_created_at": "2026-01-15T..."131}132```133134- `results` is an array of rows; each row is an array of cell values in the order of `columns`.135- `endpoint_version` tells the client which version actually ran — useful for logging and for136 pinning to a known version with `?version=N`.137138For insight endpoints, the response shape depends on the query kind (`TrendsQuery`,139`LifecycleQuery`, `RetentionQuery`) — the OpenAPI spec captures the right shape for the current140version. Insight kinds that can't be materialised (e.g. `FunnelsQuery`) still return their inline141result shape.142143## Calling from the PostHog CLI144145For local testing, scripts, or CI, the repo's `posthog-cli` calls endpoints without hand-rolling146HTTP:147148- `posthog-cli exp endpoints run` — execute an endpoint (from a local YAML definition)149- `posthog-cli exp endpoints {list,get,pull,push,diff}` — inspect endpoints, or manage them as YAML150 files in version control (GitOps-style)151152Auth uses the same personal API key, via `posthog-cli login` or the `POSTHOG_CLI_API_KEY` /153`POSTHOG_CLI_PROJECT_ID` / `POSTHOG_CLI_HOST` env vars. (These live under `exp` — experimental, may154change.)155156## Error responses to handle157158| Status | When | Handling |159| ------ | ------------------------------------------------------------------------------ | -------------------------------------------------------------------------------- |160| 400 | Missing required variable on a materialised endpoint, or invalid variable type | Surface the error message; fix the call |161| 401 | Missing / wrong personal API key | Check the Authorization header |162| 403 | Key lacks `endpoint:read` scope, or endpoint is in another project | Adjust key scopes |163| 404 | Endpoint name typo, or endpoint not active | Confirm name; check `is_active` |164| 429 | Rate limited — limits are per team, not per endpoint (see note below) | Exponential backoff; cache responses client-side if possible |165| 5xx | Query execution failure (ClickHouse error, timeout, etc.) | Retry with backoff. If persistent, hand off to `diagnosing-endpoint-performance` |166167## Workflow1681691. Confirm endpoint name. If unknown, list them with `execute-sql` on170 `system.data_modeling_endpoints` (or `endpoints-get-all`).1712. `endpoint-get` to see the full shape: variables, materialisation status, query kind.1723. Decide the auth posture: personal API key on a server, never client-side.1734. If the user wants generated types, call `endpoint-openapi-spec`, hand them the spec, and174 recommend a generator for their language.1755. Build a sample request payload from the variable declarations and walk through a test call.1766. Optionally call `endpoint-run` from this skill to confirm the payload works.1777. Wrap up with handling notes: the materialised-variables rule, the rate-limit shape, and178 pinning to a version with `?version=N`.179180## Example interaction181182```text183User: "I want to call monthly_active_users from my Node.js backend.184 Can you help me wire it up with types?"185186Agent steps:187- endpoint-get monthly_active_users → HogQL, variables: lookback_days188 (int, default 30), org_id (string, no default), is_materialized: true189- "Two things to know upfront:190 1. This endpoint is materialised, so both lookback_days and org_id191 must be passed on every call. Calls without them will be rejected.192 2. You'll need a personal API key with endpoint:read scope. Set it193 as PH_API_KEY in your server env — never ship it in client code."194195- endpoint-openapi-spec monthly_active_users → returns spec196197- "Here's the spec. Drop it in your repo and run:198 pnpm dlx @hey-api/openapi-ts -i spec.json -o ./generated/posthog199 That gives you typed client functions.200201 A minimum call looks like:202 POST /api/projects/12345/endpoints/monthly_active_users/run203 Authorization: Bearer <PH_API_KEY>204 Content-Type: application/json205206 { 'variables': { 'org_id': 'acme', 'lookback_days': 30 } }207208 The response includes columns and rows — your client will pick that209 up from the generated types.210211 Want me to do a sample call to verify the payload works?"212```213214## Important notes215216- **Personal API keys are server-side only.** Never ship them in mobile apps or browser JS.217- **Re-generate the client when the query changes.** Each new endpoint version may add or218 remove variables — keep types in sync by re-fetching the spec.219- **Materialised endpoints reject calls missing variables.** This is intentional. If the user220 reports a 400 after materialisation was enabled, the fix is in the call, not in the endpoint.221- **Pin to a version — don't rely on "latest".** Always call with `?version=N`. Without it the222 latest active version runs, so a future query edit (which cuts a new version) can silently change223 a caller's results. Bump the pinned version deliberately once you've validated the new one.224- **Caching on the client side is fair game.** The endpoint already caches via225 `data_freshness_seconds`, but the client can layer another cache on top for hot paths. Be226 mindful of total staleness (endpoint cache + client cache).227- **Rate limits are per team, by category — not per endpoint.** Calls to non-materialised228 endpoints share the team-wide API-query budget (~240/min burst, ~2400/hour sustained) with all229 other query traffic; materialised endpoints draw on a separate, higher shared bucket230 (~1200/min, ~12000/hour). There is no per-endpoint-name limit, so hammering one endpoint can231 starve others on the same team. Heavy callers should batch where possible and back off on 429.232- **Pricing.** Calling endpoints isn't billed today, but it will be once endpoints ship alongside233 the [managed warehouse](https://posthog.com/data-stack/managed-warehouse). Flag this to the user234 if they're planning high-volume usage so the future cost isn't a surprise.235- **Tell PostHog what's missing.** If an error, a limit, or a missing capability gets in the way,236 use the `agent-feedback` tool — it's the main signal the team uses to improve endpoints and these237 tools.238
Full transparency — inspect the skill content before installing.