[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill-posthog-diagnosing-endpoint-performance":3,"mdc--f21kx8-key":51,"related-org-posthog-diagnosing-endpoint-performance":1012,"related-repo-posthog-diagnosing-endpoint-performance":1177},{"slug":4,"name":4,"fn":5,"description":6,"org":7,"tags":11,"stars":26,"repoUrl":27,"updatedAt":28,"license":29,"forks":30,"topics":31,"repo":46,"sourceUrl":49,"mdContent":50},"diagnosing-endpoint-performance","diagnose and optimize PostHog endpoint performance","Diagnose why a PostHog endpoint is slow or expensive and propose a concrete fix — bump the cache TTL, enable materialisation, restructure variables, or rewrite the query. Use when the user says \"this endpoint is slow\", \"my endpoint times out\", \"we're hitting the cost cap on this one\", or asks \"should I materialise this?\". Focuses on a single named endpoint, not a project-wide audit.\n",{"slug":8,"name":9,"logoUrl":10,"githubOrg":8},"posthog","PostHog","https:\u002F\u002Fpexgzepcugksgbtrxkhf.supabase.co\u002Fstorage\u002Fv1\u002Fobject\u002Fpublic\u002Forg-logos\u002Fposthog.png",[12,16,17,20,23],{"name":13,"slug":14,"type":15},"Performance","performance","tag",{"name":9,"slug":8,"type":15},{"name":18,"slug":19,"type":15},"Analytics","analytics",{"name":21,"slug":22,"type":15},"SQL","sql",{"name":24,"slug":25,"type":15},"Debugging","debugging",35568,"https:\u002F\u002Fgithub.com\u002FPostHog\u002Fposthog","2026-06-08T08:08:32.444834",null,2977,[32,33,19,34,35,36,37,38,39,40,41,42,43,44,45],"ab-testing","ai-analytics","cdp","data-warehouse","experiments","feature-flags","javascript","product-analytics","python","react","session-replay","surveys","typescript","web-analytics",{"repoUrl":27,"stars":26,"forks":30,"topics":47,"description":48},[32,33,19,34,35,36,37,38,39,40,41,42,43,44,45],"🦔 PostHog is an all-in-one developer platform for building successful products. We offer product analytics, web analytics, session replay, error tracking, feature flags, experimentation, surveys, data warehouse, a CDP, and an AI product assistant to help debug your code, ship features faster, and keep all your usage and customer data in one stack.","https:\u002F\u002Fgithub.com\u002FPostHog\u002Fposthog\u002Ftree\u002FHEAD\u002Fproducts\u002Fendpoints\u002Fskills\u002Fdiagnosing-endpoint-performance","---\nname: diagnosing-endpoint-performance\ndescription: >\n  Diagnose why a PostHog endpoint is slow or expensive and propose a concrete fix — bump the cache\n  TTL, enable materialisation, restructure variables, or rewrite the query. Use when the user says\n  \"this endpoint is slow\", \"my endpoint times out\", \"we're hitting the cost cap on this one\", or\n  asks \"should I materialise this?\". Focuses on a single named endpoint, not a project-wide audit.\n---\n\n# Diagnosing endpoint performance\n\nThis skill walks through a specific endpoint that is slow, expensive, or unreliable, and produces\na concrete recommendation. It is the deep-dive counterpart to `auditing-endpoints` (which finds\ncandidates).\n\n## When to use this skill\n\n- \"This endpoint is slow \u002F timing out\"\n- \"Why is my endpoint hitting the cost cap?\"\n- \"Should I materialise X?\"\n- An endpoint surfaced from `auditing-endpoints` as a failing materialisation or expensive caller\n- The user has a specific endpoint in mind and wants advice\n\nIf the question is project-wide (\"what should I clean up?\"), use `auditing-endpoints` first.\n\n## Available tools\n\n| Tool                                  | Purpose                                                                                        |\n| ------------------------------------- | ---------------------------------------------------------------------------------------------- |\n| `endpoint-get`                        | Full endpoint config: query, current version, `data_freshness_seconds`, materialisation status |\n| `endpoint-versions`                   | History of every version (query + materialisation state); which version is current             |\n| `endpoint-materialization-status`     | Whether materialisation is eligible, current state, last run, last error                       |\n| `endpoints-materialization-preview`   | What the materialised query would look like, plus the rejection reason if ineligible           |\n| `endpoint-materialization-suggestion` | Server-side AI rewrite of an ineligible SQL query, validated against the live checks           |\n| `endpoint-materialization-conditions` | Source code of the live eligibility checks + the rewrite contract, for DIY rewriting           |\n| `endpoints-last-execution-times`      | When was it last called (endpoint-level sanity-check that it is in active use)                 |\n| `execute-sql`                         | Query `query_log` for endpoint-level call frequency and per-call duration\u002Fbytes                |\n\n## The decision tree\n\nWhen deciding what to recommend, walk these in order — the first one that applies is the cheapest\nfix.\n\n### Step 1 — Is it cached at all?\n\nFetch the endpoint and look at `data_freshness_seconds` (it sets both the cache TTL and, when\nmaterialised, the refresh cadence). If the user's traffic\ncalls the same parameters repeatedly within that window, every call after the first is a cache\nhit and effectively free.\n\n- TTL is at the default (24h \u002F 86400s) and the data really doesn't need fresher than that →\n  done, no change needed.\n- TTL is at the 900s floor (15 min) and the user is hitting the endpoint many times per minute →\n  bump the TTL. This is almost always the cheapest first move. (`data_freshness_seconds` is an\n  enum: 900, 1800, 3600, 21600, 43200, 86400, 604800 — there is no sub-15-minute value.)\n- TTL is at the floor _because the data must be fresh_ (e.g. real-time dashboard) → cache won't\n  help, skip to step 2.\n\nThe shape of the variables matters here: if every call passes different `user_id` or `date_from`\nvalues, the cache has many distinct keys and a higher TTL helps less. If almost every call uses\nthe same handful of parameter combinations, the cache helps a lot.\n\n### Step 2 — Should it be materialised?\n\nMaterialisation pre-computes the query into a saved view that's refreshed on a schedule. Reads\nbecome near-instant — at the cost of staleness equal to the refresh interval, plus storage and\ncompute for the materialisation itself.\n\nCall `endpoints-materialization-preview`. The response tells you:\n\n- **Eligible + clean transform** → strong candidate. Recommend enabling, especially for\n  endpoints with predictable filter shapes (variables, breakdowns).\n- **Not eligible**, with a rejection reason → cannot materialise. The reason often hints at the\n  next step (see step 3 — rewrite).\n- **Eligible but the transform is gnarly** (lots of range pairs, complex aggregation\n  re-derivation) → materialisation will work but may not save much. Worth flagging before\n  flipping the switch.\n\nWhen materialisation is enabled, callers **must pass all materialised variables** — calls without\nthem are rejected (security: prevents returning unfiltered data). Pair the recommendation with\na note about which variables become required.\n\n### Step 3 — Does the query need rewriting?\n\nFor a SQL endpoint that isn't eligible, try the fast path first: call\n`endpoint-materialization-suggestion`. PostHog rewrites the query into a semantically equivalent\nform and validates it against the live eligibility checks before returning it — `ok` means the\nrewrite passes the checks plus variable- and output-column parity, but semantic equivalence is\nthe model's claim, not proven. Before applying, run the original and the rewrite with the same\nrepresentative variable values (via `execute-sql` or the endpoint playground) and compare the\nresults; only then apply it with `endpoint-update` (creates a new version), then confirm with\n`endpoint-materialization-status`. `cannot_fix` means no equivalent rewrite exists (e.g. an\n`OR {variables.x} = 'all'` optional-variable idiom) — say so rather than forcing a change in\nbehaviour. Requires the org's AI data processing approval; without it, or to reason about the\nrewrite yourself, call `endpoint-materialization-conditions` — it returns the actual source code\nof the checks this instance enforces plus the rewrite contract. Treat that as authoritative; the\nbullet list below is a summary and may lag it.\n\nOtherwise, the rejection reason from `endpoints-materialization-preview` is usually the lead:\n\n- **Cohort breakdown \u002F compare mode rejection** → regular property breakdowns materialise fine;\n  only cohort breakdowns and compare mode are blocked. Swap a cohort breakdown for a property\n  breakdown, or drop compare mode (expose the comparison window as a variable instead).\n- **JOINs combined with variables** → a top-level `JOIN` plus a variable filter is rejected for\n  materialisation, because applying the variable changes the joined row cardinality and silently\n  produces wrong results (e.g. `LEFT JOIN` non-matches lose the variable column). Restructure so the\n  variable filters a single table — push the filter into a subquery\u002FCTE that's then joined, rather\n  than filtering across the join. This is the most common \"looks fine but won't materialise\" trap.\n- **\"Missing variables\" \u002F unbounded scan** → the query reads too much data without a filter.\n  Encourage adding a required time-window variable (e.g. `date_from`, `lookback_days`).\n- **HogQL with `*` \u002F non-deterministic functions** → narrow the columns selected, replace\n  `now()` \u002F `today()` with a variable when possible.\n\nCheck `endpoint-versions` to see whether the query was recently changed. Often the regression\ncame from a specific commit and reverting that version is faster than rewriting.\n\n### Step 4 — Is the slow version even the one being called?\n\nOnly the latest version runs by default; older versions run only when a caller pins `?version=N`.\nSo the version to tune is almost always the current one — unless a pinned older version is the\nculprit. Call `endpoint-versions` and read each version's `last_executed_at` to see which versions\nhave been hit recently; a materialised version with a null or long-stale `last_executed_at` is a\ncandidate to unmaterialise or delete rather than tune (confirm first — that signal only counts\nAPI-key runs and can be sparse).\n\nFor endpoint-level call frequency and per-call cost, query `query_log` with `execute-sql` — it\ncarries `query_duration_ms`, `read_rows`, and `read_bytes`, handy for confirming how heavy the\nendpoint's calls actually are:\n\n```sql\nSELECT count() AS calls, max(query_start_time) AS last_called, avg(query_duration_ms) AS avg_ms\nFROM query_log\nWHERE name = '\u003Cendpoint_name>' AND endpoint LIKE '%\u002Fendpoints\u002F%' AND is_personal_api_key_request\n```\n\n## Workflow\n\n1. Identify the endpoint by name. If the user gave a URL, parse the name from\n   `\u002Fapi\u002Fprojects\u002F{team_id}\u002Fendpoints\u002F{name}\u002Frun`.\n2. `endpoint-get` to fetch full config. Note: `data_freshness_seconds`, `is_materialized`,\n   current version, query kind.\n3. `endpoint-materialization-status` to see materialisation health (failed? eligible?).\n4. `endpoint-versions` to see the query history and which version is current (the one callers\n   hit unless they pin `?version=N`).\n5. If the user gave specific symptoms (\"times out\", \"memory limit\", \"very slow\"), walk the\n   decision tree above. Otherwise, present the four checkpoints and ask which matches.\n6. End with a concrete recommendation — bump TTL, materialise, rewrite, or rollback — and\n   confirm before applying it.\n\n## Example interaction\n\n```text\nUser: \"monthly_active_users is timing out for some callers\"\n\nAgent steps:\n- endpoint-get monthly_active_users → HogQL endpoint, data_freshness_seconds=900,\n  is_materialized=false, current version v4\n- endpoint-materialization-status monthly_active_users → not materialised; eligible: true\n- endpoint-versions monthly_active_users\n  → v4 is current; v1–v3 are prior query revisions (callers hit v4 unless pinned)\n- endpoints-materialization-preview → eligible, clean transform with one range pair\n\n- \"Three things stand out:\n   1. Cache TTL is 900 seconds (the 15-min floor), but the data is\n      aggregating a month — that aggressive a refresh probably isn't\n      necessary. Bumping to 1 hour (3600s) would dramatically reduce\n      ClickHouse load.\n   2. It's eligible for materialisation with a clean transform. If\n      callers can tolerate 5-15 minute staleness, materialisation\n      would make reads near-instant.\n   3. v4 is the current version, so callers hit it by default — the\n      tuning target is clear.\n\n   Quickest win: bump TTL to 3600s. Bigger win: materialise.\n\n   Which trade-off matches your needs? If you need real-time freshness,\n   neither helps and we'd need to rewrite the query — likely narrowing\n   the aggregation window.\"\n```\n\n## Important notes\n\n- **Cache is almost always the first fix.** It's free, instantly reversible, and doesn't change\n  data semantics. Resist jumping to materialisation if a higher TTL would do.\n- **Materialisation has hidden costs.** Storage of the materialised view, refresh compute, and\n  the requirement that callers pass all variables.\n- **Don't rewrite the query without the user.** A query change creates a new version and may\n  break callers!!! Surface the suggested change, get sign-off, then apply.\n- **Three usage signals.** `endpoint-get`'s `last_executed_at` is endpoint-level recency;\n  `endpoint-versions` gives each version's own `last_executed_at`; `query_log` (via `execute-sql`)\n  gives endpoint-level call frequency and per-call cost. All count only personal-API-key calls, and\n  per-version recency can be sparse — confirm with the user before calling a version dead.\n- **The \"right\" fix depends on the SLA, not the query.** Always ask the user about acceptable\n  staleness before recommending materialisation. A 15-minute-stale materialised view is wrong\n  for a real-time dashboard, regardless of how cheap it'd be.\n- **Tell PostHog what's missing.** If the diagnosis runs into a product limitation (an eligibility\n  rule, the TTL enum, required variables), nudge the team via `agent-feedback`.\n",{"data":52,"body":53},{"name":4,"description":6},{"type":54,"children":55},"root",[56,64,79,86,123,135,141,321,327,332,339,351,384,405,411,416,428,462,474,480,545,557,655,667,673,708,750,789,795,872,878,888,894,1006],{"type":57,"tag":58,"props":59,"children":60},"element","h1",{"id":4},[61],{"type":62,"value":63},"text","Diagnosing endpoint performance",{"type":57,"tag":65,"props":66,"children":67},"p",{},[68,70,77],{"type":62,"value":69},"This skill walks through a specific endpoint that is slow, expensive, or unreliable, and produces\na concrete recommendation. It is the deep-dive counterpart to ",{"type":57,"tag":71,"props":72,"children":74},"code",{"className":73},[],[75],{"type":62,"value":76},"auditing-endpoints",{"type":62,"value":78}," (which finds\ncandidates).",{"type":57,"tag":80,"props":81,"children":83},"h2",{"id":82},"when-to-use-this-skill",[84],{"type":62,"value":85},"When to use this skill",{"type":57,"tag":87,"props":88,"children":89},"ul",{},[90,96,101,106,118],{"type":57,"tag":91,"props":92,"children":93},"li",{},[94],{"type":62,"value":95},"\"This endpoint is slow \u002F timing out\"",{"type":57,"tag":91,"props":97,"children":98},{},[99],{"type":62,"value":100},"\"Why is my endpoint hitting the cost cap?\"",{"type":57,"tag":91,"props":102,"children":103},{},[104],{"type":62,"value":105},"\"Should I materialise X?\"",{"type":57,"tag":91,"props":107,"children":108},{},[109,111,116],{"type":62,"value":110},"An endpoint surfaced from ",{"type":57,"tag":71,"props":112,"children":114},{"className":113},[],[115],{"type":62,"value":76},{"type":62,"value":117}," as a failing materialisation or expensive caller",{"type":57,"tag":91,"props":119,"children":120},{},[121],{"type":62,"value":122},"The user has a specific endpoint in mind and wants advice",{"type":57,"tag":65,"props":124,"children":125},{},[126,128,133],{"type":62,"value":127},"If the question is project-wide (\"what should I clean up?\"), use ",{"type":57,"tag":71,"props":129,"children":131},{"className":130},[],[132],{"type":62,"value":76},{"type":62,"value":134}," first.",{"type":57,"tag":80,"props":136,"children":138},{"id":137},"available-tools",[139],{"type":62,"value":140},"Available tools",{"type":57,"tag":142,"props":143,"children":144},"table",{},[145,164],{"type":57,"tag":146,"props":147,"children":148},"thead",{},[149],{"type":57,"tag":150,"props":151,"children":152},"tr",{},[153,159],{"type":57,"tag":154,"props":155,"children":156},"th",{},[157],{"type":62,"value":158},"Tool",{"type":57,"tag":154,"props":160,"children":161},{},[162],{"type":62,"value":163},"Purpose",{"type":57,"tag":165,"props":166,"children":167},"tbody",{},[168,194,211,228,245,262,279,296],{"type":57,"tag":150,"props":169,"children":170},{},[171,181],{"type":57,"tag":172,"props":173,"children":174},"td",{},[175],{"type":57,"tag":71,"props":176,"children":178},{"className":177},[],[179],{"type":62,"value":180},"endpoint-get",{"type":57,"tag":172,"props":182,"children":183},{},[184,186,192],{"type":62,"value":185},"Full endpoint config: query, current version, ",{"type":57,"tag":71,"props":187,"children":189},{"className":188},[],[190],{"type":62,"value":191},"data_freshness_seconds",{"type":62,"value":193},", materialisation status",{"type":57,"tag":150,"props":195,"children":196},{},[197,206],{"type":57,"tag":172,"props":198,"children":199},{},[200],{"type":57,"tag":71,"props":201,"children":203},{"className":202},[],[204],{"type":62,"value":205},"endpoint-versions",{"type":57,"tag":172,"props":207,"children":208},{},[209],{"type":62,"value":210},"History of every version (query + materialisation state); which version is current",{"type":57,"tag":150,"props":212,"children":213},{},[214,223],{"type":57,"tag":172,"props":215,"children":216},{},[217],{"type":57,"tag":71,"props":218,"children":220},{"className":219},[],[221],{"type":62,"value":222},"endpoint-materialization-status",{"type":57,"tag":172,"props":224,"children":225},{},[226],{"type":62,"value":227},"Whether materialisation is eligible, current state, last run, last error",{"type":57,"tag":150,"props":229,"children":230},{},[231,240],{"type":57,"tag":172,"props":232,"children":233},{},[234],{"type":57,"tag":71,"props":235,"children":237},{"className":236},[],[238],{"type":62,"value":239},"endpoints-materialization-preview",{"type":57,"tag":172,"props":241,"children":242},{},[243],{"type":62,"value":244},"What the materialised query would look like, plus the rejection reason if ineligible",{"type":57,"tag":150,"props":246,"children":247},{},[248,257],{"type":57,"tag":172,"props":249,"children":250},{},[251],{"type":57,"tag":71,"props":252,"children":254},{"className":253},[],[255],{"type":62,"value":256},"endpoint-materialization-suggestion",{"type":57,"tag":172,"props":258,"children":259},{},[260],{"type":62,"value":261},"Server-side AI rewrite of an ineligible SQL query, validated against the live checks",{"type":57,"tag":150,"props":263,"children":264},{},[265,274],{"type":57,"tag":172,"props":266,"children":267},{},[268],{"type":57,"tag":71,"props":269,"children":271},{"className":270},[],[272],{"type":62,"value":273},"endpoint-materialization-conditions",{"type":57,"tag":172,"props":275,"children":276},{},[277],{"type":62,"value":278},"Source code of the live eligibility checks + the rewrite contract, for DIY rewriting",{"type":57,"tag":150,"props":280,"children":281},{},[282,291],{"type":57,"tag":172,"props":283,"children":284},{},[285],{"type":57,"tag":71,"props":286,"children":288},{"className":287},[],[289],{"type":62,"value":290},"endpoints-last-execution-times",{"type":57,"tag":172,"props":292,"children":293},{},[294],{"type":62,"value":295},"When was it last called (endpoint-level sanity-check that it is in active use)",{"type":57,"tag":150,"props":297,"children":298},{},[299,308],{"type":57,"tag":172,"props":300,"children":301},{},[302],{"type":57,"tag":71,"props":303,"children":305},{"className":304},[],[306],{"type":62,"value":307},"execute-sql",{"type":57,"tag":172,"props":309,"children":310},{},[311,313,319],{"type":62,"value":312},"Query ",{"type":57,"tag":71,"props":314,"children":316},{"className":315},[],[317],{"type":62,"value":318},"query_log",{"type":62,"value":320}," for endpoint-level call frequency and per-call duration\u002Fbytes",{"type":57,"tag":80,"props":322,"children":324},{"id":323},"the-decision-tree",[325],{"type":62,"value":326},"The decision tree",{"type":57,"tag":65,"props":328,"children":329},{},[330],{"type":62,"value":331},"When deciding what to recommend, walk these in order — the first one that applies is the cheapest\nfix.",{"type":57,"tag":333,"props":334,"children":336},"h3",{"id":335},"step-1-is-it-cached-at-all",[337],{"type":62,"value":338},"Step 1 — Is it cached at all?",{"type":57,"tag":65,"props":340,"children":341},{},[342,344,349],{"type":62,"value":343},"Fetch the endpoint and look at ",{"type":57,"tag":71,"props":345,"children":347},{"className":346},[],[348],{"type":62,"value":191},{"type":62,"value":350}," (it sets both the cache TTL and, when\nmaterialised, the refresh cadence). If the user's traffic\ncalls the same parameters repeatedly within that window, every call after the first is a cache\nhit and effectively free.",{"type":57,"tag":87,"props":352,"children":353},{},[354,359,371],{"type":57,"tag":91,"props":355,"children":356},{},[357],{"type":62,"value":358},"TTL is at the default (24h \u002F 86400s) and the data really doesn't need fresher than that →\ndone, no change needed.",{"type":57,"tag":91,"props":360,"children":361},{},[362,364,369],{"type":62,"value":363},"TTL is at the 900s floor (15 min) and the user is hitting the endpoint many times per minute →\nbump the TTL. This is almost always the cheapest first move. (",{"type":57,"tag":71,"props":365,"children":367},{"className":366},[],[368],{"type":62,"value":191},{"type":62,"value":370}," is an\nenum: 900, 1800, 3600, 21600, 43200, 86400, 604800 — there is no sub-15-minute value.)",{"type":57,"tag":91,"props":372,"children":373},{},[374,376,382],{"type":62,"value":375},"TTL is at the floor ",{"type":57,"tag":377,"props":378,"children":379},"em",{},[380],{"type":62,"value":381},"because the data must be fresh",{"type":62,"value":383}," (e.g. real-time dashboard) → cache won't\nhelp, skip to step 2.",{"type":57,"tag":65,"props":385,"children":386},{},[387,389,395,397,403],{"type":62,"value":388},"The shape of the variables matters here: if every call passes different ",{"type":57,"tag":71,"props":390,"children":392},{"className":391},[],[393],{"type":62,"value":394},"user_id",{"type":62,"value":396}," or ",{"type":57,"tag":71,"props":398,"children":400},{"className":399},[],[401],{"type":62,"value":402},"date_from",{"type":62,"value":404},"\nvalues, the cache has many distinct keys and a higher TTL helps less. If almost every call uses\nthe same handful of parameter combinations, the cache helps a lot.",{"type":57,"tag":333,"props":406,"children":408},{"id":407},"step-2-should-it-be-materialised",[409],{"type":62,"value":410},"Step 2 — Should it be materialised?",{"type":57,"tag":65,"props":412,"children":413},{},[414],{"type":62,"value":415},"Materialisation pre-computes the query into a saved view that's refreshed on a schedule. Reads\nbecome near-instant — at the cost of staleness equal to the refresh interval, plus storage and\ncompute for the materialisation itself.",{"type":57,"tag":65,"props":417,"children":418},{},[419,421,426],{"type":62,"value":420},"Call ",{"type":57,"tag":71,"props":422,"children":424},{"className":423},[],[425],{"type":62,"value":239},{"type":62,"value":427},". The response tells you:",{"type":57,"tag":87,"props":429,"children":430},{},[431,442,452],{"type":57,"tag":91,"props":432,"children":433},{},[434,440],{"type":57,"tag":435,"props":436,"children":437},"strong",{},[438],{"type":62,"value":439},"Eligible + clean transform",{"type":62,"value":441}," → strong candidate. Recommend enabling, especially for\nendpoints with predictable filter shapes (variables, breakdowns).",{"type":57,"tag":91,"props":443,"children":444},{},[445,450],{"type":57,"tag":435,"props":446,"children":447},{},[448],{"type":62,"value":449},"Not eligible",{"type":62,"value":451},", with a rejection reason → cannot materialise. The reason often hints at the\nnext step (see step 3 — rewrite).",{"type":57,"tag":91,"props":453,"children":454},{},[455,460],{"type":57,"tag":435,"props":456,"children":457},{},[458],{"type":62,"value":459},"Eligible but the transform is gnarly",{"type":62,"value":461}," (lots of range pairs, complex aggregation\nre-derivation) → materialisation will work but may not save much. Worth flagging before\nflipping the switch.",{"type":57,"tag":65,"props":463,"children":464},{},[465,467,472],{"type":62,"value":466},"When materialisation is enabled, callers ",{"type":57,"tag":435,"props":468,"children":469},{},[470],{"type":62,"value":471},"must pass all materialised variables",{"type":62,"value":473}," — calls without\nthem are rejected (security: prevents returning unfiltered data). Pair the recommendation with\na note about which variables become required.",{"type":57,"tag":333,"props":475,"children":477},{"id":476},"step-3-does-the-query-need-rewriting",[478],{"type":62,"value":479},"Step 3 — Does the query need rewriting?",{"type":57,"tag":65,"props":481,"children":482},{},[483,485,490,492,498,500,505,507,513,515,520,522,528,530,536,538,543],{"type":62,"value":484},"For a SQL endpoint that isn't eligible, try the fast path first: call\n",{"type":57,"tag":71,"props":486,"children":488},{"className":487},[],[489],{"type":62,"value":256},{"type":62,"value":491},". PostHog rewrites the query into a semantically equivalent\nform and validates it against the live eligibility checks before returning it — ",{"type":57,"tag":71,"props":493,"children":495},{"className":494},[],[496],{"type":62,"value":497},"ok",{"type":62,"value":499}," means the\nrewrite passes the checks plus variable- and output-column parity, but semantic equivalence is\nthe model's claim, not proven. Before applying, run the original and the rewrite with the same\nrepresentative variable values (via ",{"type":57,"tag":71,"props":501,"children":503},{"className":502},[],[504],{"type":62,"value":307},{"type":62,"value":506}," or the endpoint playground) and compare the\nresults; only then apply it with ",{"type":57,"tag":71,"props":508,"children":510},{"className":509},[],[511],{"type":62,"value":512},"endpoint-update",{"type":62,"value":514}," (creates a new version), then confirm with\n",{"type":57,"tag":71,"props":516,"children":518},{"className":517},[],[519],{"type":62,"value":222},{"type":62,"value":521},". ",{"type":57,"tag":71,"props":523,"children":525},{"className":524},[],[526],{"type":62,"value":527},"cannot_fix",{"type":62,"value":529}," means no equivalent rewrite exists (e.g. an\n",{"type":57,"tag":71,"props":531,"children":533},{"className":532},[],[534],{"type":62,"value":535},"OR {variables.x} = 'all'",{"type":62,"value":537}," optional-variable idiom) — say so rather than forcing a change in\nbehaviour. Requires the org's AI data processing approval; without it, or to reason about the\nrewrite yourself, call ",{"type":57,"tag":71,"props":539,"children":541},{"className":540},[],[542],{"type":62,"value":273},{"type":62,"value":544}," — it returns the actual source code\nof the checks this instance enforces plus the rewrite contract. Treat that as authoritative; the\nbullet list below is a summary and may lag it.",{"type":57,"tag":65,"props":546,"children":547},{},[548,550,555],{"type":62,"value":549},"Otherwise, the rejection reason from ",{"type":57,"tag":71,"props":551,"children":553},{"className":552},[],[554],{"type":62,"value":239},{"type":62,"value":556}," is usually the lead:",{"type":57,"tag":87,"props":558,"children":559},{},[560,570,596,621],{"type":57,"tag":91,"props":561,"children":562},{},[563,568],{"type":57,"tag":435,"props":564,"children":565},{},[566],{"type":62,"value":567},"Cohort breakdown \u002F compare mode rejection",{"type":62,"value":569}," → regular property breakdowns materialise fine;\nonly cohort breakdowns and compare mode are blocked. Swap a cohort breakdown for a property\nbreakdown, or drop compare mode (expose the comparison window as a variable instead).",{"type":57,"tag":91,"props":571,"children":572},{},[573,578,580,586,588,594],{"type":57,"tag":435,"props":574,"children":575},{},[576],{"type":62,"value":577},"JOINs combined with variables",{"type":62,"value":579}," → a top-level ",{"type":57,"tag":71,"props":581,"children":583},{"className":582},[],[584],{"type":62,"value":585},"JOIN",{"type":62,"value":587}," plus a variable filter is rejected for\nmaterialisation, because applying the variable changes the joined row cardinality and silently\nproduces wrong results (e.g. ",{"type":57,"tag":71,"props":589,"children":591},{"className":590},[],[592],{"type":62,"value":593},"LEFT JOIN",{"type":62,"value":595}," non-matches lose the variable column). Restructure so the\nvariable filters a single table — push the filter into a subquery\u002FCTE that's then joined, rather\nthan filtering across the join. This is the most common \"looks fine but won't materialise\" trap.",{"type":57,"tag":91,"props":597,"children":598},{},[599,604,606,611,613,619],{"type":57,"tag":435,"props":600,"children":601},{},[602],{"type":62,"value":603},"\"Missing variables\" \u002F unbounded scan",{"type":62,"value":605}," → the query reads too much data without a filter.\nEncourage adding a required time-window variable (e.g. ",{"type":57,"tag":71,"props":607,"children":609},{"className":608},[],[610],{"type":62,"value":402},{"type":62,"value":612},", ",{"type":57,"tag":71,"props":614,"children":616},{"className":615},[],[617],{"type":62,"value":618},"lookback_days",{"type":62,"value":620},").",{"type":57,"tag":91,"props":622,"children":623},{},[624,637,639,645,647,653],{"type":57,"tag":435,"props":625,"children":626},{},[627,629,635],{"type":62,"value":628},"HogQL with ",{"type":57,"tag":71,"props":630,"children":632},{"className":631},[],[633],{"type":62,"value":634},"*",{"type":62,"value":636}," \u002F non-deterministic functions",{"type":62,"value":638}," → narrow the columns selected, replace\n",{"type":57,"tag":71,"props":640,"children":642},{"className":641},[],[643],{"type":62,"value":644},"now()",{"type":62,"value":646}," \u002F ",{"type":57,"tag":71,"props":648,"children":650},{"className":649},[],[651],{"type":62,"value":652},"today()",{"type":62,"value":654}," with a variable when possible.",{"type":57,"tag":65,"props":656,"children":657},{},[658,660,665],{"type":62,"value":659},"Check ",{"type":57,"tag":71,"props":661,"children":663},{"className":662},[],[664],{"type":62,"value":205},{"type":62,"value":666}," to see whether the query was recently changed. Often the regression\ncame from a specific commit and reverting that version is faster than rewriting.",{"type":57,"tag":333,"props":668,"children":670},{"id":669},"step-4-is-the-slow-version-even-the-one-being-called",[671],{"type":62,"value":672},"Step 4 — Is the slow version even the one being called?",{"type":57,"tag":65,"props":674,"children":675},{},[676,678,684,686,691,693,699,701,706],{"type":62,"value":677},"Only the latest version runs by default; older versions run only when a caller pins ",{"type":57,"tag":71,"props":679,"children":681},{"className":680},[],[682],{"type":62,"value":683},"?version=N",{"type":62,"value":685},".\nSo the version to tune is almost always the current one — unless a pinned older version is the\nculprit. Call ",{"type":57,"tag":71,"props":687,"children":689},{"className":688},[],[690],{"type":62,"value":205},{"type":62,"value":692}," and read each version's ",{"type":57,"tag":71,"props":694,"children":696},{"className":695},[],[697],{"type":62,"value":698},"last_executed_at",{"type":62,"value":700}," to see which versions\nhave been hit recently; a materialised version with a null or long-stale ",{"type":57,"tag":71,"props":702,"children":704},{"className":703},[],[705],{"type":62,"value":698},{"type":62,"value":707}," is a\ncandidate to unmaterialise or delete rather than tune (confirm first — that signal only counts\nAPI-key runs and can be sparse).",{"type":57,"tag":65,"props":709,"children":710},{},[711,713,718,720,725,727,733,734,740,742,748],{"type":62,"value":712},"For endpoint-level call frequency and per-call cost, query ",{"type":57,"tag":71,"props":714,"children":716},{"className":715},[],[717],{"type":62,"value":318},{"type":62,"value":719}," with ",{"type":57,"tag":71,"props":721,"children":723},{"className":722},[],[724],{"type":62,"value":307},{"type":62,"value":726}," — it\ncarries ",{"type":57,"tag":71,"props":728,"children":730},{"className":729},[],[731],{"type":62,"value":732},"query_duration_ms",{"type":62,"value":612},{"type":57,"tag":71,"props":735,"children":737},{"className":736},[],[738],{"type":62,"value":739},"read_rows",{"type":62,"value":741},", and ",{"type":57,"tag":71,"props":743,"children":745},{"className":744},[],[746],{"type":62,"value":747},"read_bytes",{"type":62,"value":749},", handy for confirming how heavy the\nendpoint's calls actually are:",{"type":57,"tag":751,"props":752,"children":756},"pre",{"className":753,"code":754,"language":22,"meta":755,"style":755},"language-sql shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","SELECT count() AS calls, max(query_start_time) AS last_called, avg(query_duration_ms) AS avg_ms\nFROM query_log\nWHERE name = '\u003Cendpoint_name>' AND endpoint LIKE '%\u002Fendpoints\u002F%' AND is_personal_api_key_request\n","",[757],{"type":57,"tag":71,"props":758,"children":759},{"__ignoreMap":755},[760,771,780],{"type":57,"tag":761,"props":762,"children":765},"span",{"class":763,"line":764},"line",1,[766],{"type":57,"tag":761,"props":767,"children":768},{},[769],{"type":62,"value":770},"SELECT count() AS calls, max(query_start_time) AS last_called, avg(query_duration_ms) AS avg_ms\n",{"type":57,"tag":761,"props":772,"children":774},{"class":763,"line":773},2,[775],{"type":57,"tag":761,"props":776,"children":777},{},[778],{"type":62,"value":779},"FROM query_log\n",{"type":57,"tag":761,"props":781,"children":783},{"class":763,"line":782},3,[784],{"type":57,"tag":761,"props":785,"children":786},{},[787],{"type":62,"value":788},"WHERE name = '\u003Cendpoint_name>' AND endpoint LIKE '%\u002Fendpoints\u002F%' AND is_personal_api_key_request\n",{"type":57,"tag":80,"props":790,"children":792},{"id":791},"workflow",[793],{"type":62,"value":794},"Workflow",{"type":57,"tag":796,"props":797,"children":798},"ol",{},[799,812,836,846,862,867],{"type":57,"tag":91,"props":800,"children":801},{},[802,804,810],{"type":62,"value":803},"Identify the endpoint by name. If the user gave a URL, parse the name from\n",{"type":57,"tag":71,"props":805,"children":807},{"className":806},[],[808],{"type":62,"value":809},"\u002Fapi\u002Fprojects\u002F{team_id}\u002Fendpoints\u002F{name}\u002Frun",{"type":62,"value":811},".",{"type":57,"tag":91,"props":813,"children":814},{},[815,820,822,827,828,834],{"type":57,"tag":71,"props":816,"children":818},{"className":817},[],[819],{"type":62,"value":180},{"type":62,"value":821}," to fetch full config. Note: ",{"type":57,"tag":71,"props":823,"children":825},{"className":824},[],[826],{"type":62,"value":191},{"type":62,"value":612},{"type":57,"tag":71,"props":829,"children":831},{"className":830},[],[832],{"type":62,"value":833},"is_materialized",{"type":62,"value":835},",\ncurrent version, query kind.",{"type":57,"tag":91,"props":837,"children":838},{},[839,844],{"type":57,"tag":71,"props":840,"children":842},{"className":841},[],[843],{"type":62,"value":222},{"type":62,"value":845}," to see materialisation health (failed? eligible?).",{"type":57,"tag":91,"props":847,"children":848},{},[849,854,856,861],{"type":57,"tag":71,"props":850,"children":852},{"className":851},[],[853],{"type":62,"value":205},{"type":62,"value":855}," to see the query history and which version is current (the one callers\nhit unless they pin ",{"type":57,"tag":71,"props":857,"children":859},{"className":858},[],[860],{"type":62,"value":683},{"type":62,"value":620},{"type":57,"tag":91,"props":863,"children":864},{},[865],{"type":62,"value":866},"If the user gave specific symptoms (\"times out\", \"memory limit\", \"very slow\"), walk the\ndecision tree above. Otherwise, present the four checkpoints and ask which matches.",{"type":57,"tag":91,"props":868,"children":869},{},[870],{"type":62,"value":871},"End with a concrete recommendation — bump TTL, materialise, rewrite, or rollback — and\nconfirm before applying it.",{"type":57,"tag":80,"props":873,"children":875},{"id":874},"example-interaction",[876],{"type":62,"value":877},"Example interaction",{"type":57,"tag":751,"props":879,"children":883},{"className":880,"code":882,"language":62,"meta":755},[881],"language-text","User: \"monthly_active_users is timing out for some callers\"\n\nAgent steps:\n- endpoint-get monthly_active_users → HogQL endpoint, data_freshness_seconds=900,\n  is_materialized=false, current version v4\n- endpoint-materialization-status monthly_active_users → not materialised; eligible: true\n- endpoint-versions monthly_active_users\n  → v4 is current; v1–v3 are prior query revisions (callers hit v4 unless pinned)\n- endpoints-materialization-preview → eligible, clean transform with one range pair\n\n- \"Three things stand out:\n   1. Cache TTL is 900 seconds (the 15-min floor), but the data is\n      aggregating a month — that aggressive a refresh probably isn't\n      necessary. Bumping to 1 hour (3600s) would dramatically reduce\n      ClickHouse load.\n   2. It's eligible for materialisation with a clean transform. If\n      callers can tolerate 5-15 minute staleness, materialisation\n      would make reads near-instant.\n   3. v4 is the current version, so callers hit it by default — the\n      tuning target is clear.\n\n   Quickest win: bump TTL to 3600s. Bigger win: materialise.\n\n   Which trade-off matches your needs? If you need real-time freshness,\n   neither helps and we'd need to rewrite the query — likely narrowing\n   the aggregation window.\"\n",[884],{"type":57,"tag":71,"props":885,"children":886},{"__ignoreMap":755},[887],{"type":62,"value":882},{"type":57,"tag":80,"props":889,"children":891},{"id":890},"important-notes",[892],{"type":62,"value":893},"Important notes",{"type":57,"tag":87,"props":895,"children":896},{},[897,907,917,927,979,989],{"type":57,"tag":91,"props":898,"children":899},{},[900,905],{"type":57,"tag":435,"props":901,"children":902},{},[903],{"type":62,"value":904},"Cache is almost always the first fix.",{"type":62,"value":906}," It's free, instantly reversible, and doesn't change\ndata semantics. Resist jumping to materialisation if a higher TTL would do.",{"type":57,"tag":91,"props":908,"children":909},{},[910,915],{"type":57,"tag":435,"props":911,"children":912},{},[913],{"type":62,"value":914},"Materialisation has hidden costs.",{"type":62,"value":916}," Storage of the materialised view, refresh compute, and\nthe requirement that callers pass all variables.",{"type":57,"tag":91,"props":918,"children":919},{},[920,925],{"type":57,"tag":435,"props":921,"children":922},{},[923],{"type":62,"value":924},"Don't rewrite the query without the user.",{"type":62,"value":926}," A query change creates a new version and may\nbreak callers!!! Surface the suggested change, get sign-off, then apply.",{"type":57,"tag":91,"props":928,"children":929},{},[930,935,937,942,944,949,951,956,958,963,965,970,972,977],{"type":57,"tag":435,"props":931,"children":932},{},[933],{"type":62,"value":934},"Three usage signals.",{"type":62,"value":936}," ",{"type":57,"tag":71,"props":938,"children":940},{"className":939},[],[941],{"type":62,"value":180},{"type":62,"value":943},"'s ",{"type":57,"tag":71,"props":945,"children":947},{"className":946},[],[948],{"type":62,"value":698},{"type":62,"value":950}," is endpoint-level recency;\n",{"type":57,"tag":71,"props":952,"children":954},{"className":953},[],[955],{"type":62,"value":205},{"type":62,"value":957}," gives each version's own ",{"type":57,"tag":71,"props":959,"children":961},{"className":960},[],[962],{"type":62,"value":698},{"type":62,"value":964},"; ",{"type":57,"tag":71,"props":966,"children":968},{"className":967},[],[969],{"type":62,"value":318},{"type":62,"value":971}," (via ",{"type":57,"tag":71,"props":973,"children":975},{"className":974},[],[976],{"type":62,"value":307},{"type":62,"value":978},")\ngives endpoint-level call frequency and per-call cost. All count only personal-API-key calls, and\nper-version recency can be sparse — confirm with the user before calling a version dead.",{"type":57,"tag":91,"props":980,"children":981},{},[982,987],{"type":57,"tag":435,"props":983,"children":984},{},[985],{"type":62,"value":986},"The \"right\" fix depends on the SLA, not the query.",{"type":62,"value":988}," Always ask the user about acceptable\nstaleness before recommending materialisation. A 15-minute-stale materialised view is wrong\nfor a real-time dashboard, regardless of how cheap it'd be.",{"type":57,"tag":91,"props":990,"children":991},{},[992,997,999,1005],{"type":57,"tag":435,"props":993,"children":994},{},[995],{"type":62,"value":996},"Tell PostHog what's missing.",{"type":62,"value":998}," If the diagnosis runs into a product limitation (an eligibility\nrule, the TTL enum, required variables), nudge the team via ",{"type":57,"tag":71,"props":1000,"children":1002},{"className":1001},[],[1003],{"type":62,"value":1004},"agent-feedback",{"type":62,"value":811},{"type":57,"tag":1007,"props":1008,"children":1009},"style",{},[1010],{"type":62,"value":1011},"html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"items":1013,"total":1176},[1014,1029,1040,1052,1063,1076,1092,1109,1123,1138,1148,1166],{"slug":1015,"name":1015,"fn":1016,"description":1017,"org":1018,"tags":1019,"stars":26,"repoUrl":27,"updatedAt":1028},"analyzing-expensive-users","analyze expensive users in AI observability","Analyze the most expensive users in AI observability and explain why they cost so much. Use when the user asks about top spenders, expensive users, per-user LLM cost, user-level cost drivers, or patterns behind high AI observability spend.\n",{"slug":8,"name":9,"logoUrl":10,"githubOrg":8},[1020,1021,1024,1027],{"name":18,"slug":19,"type":15},{"name":1022,"slug":1023,"type":15},"Cost Optimization","cost-optimization",{"name":1025,"slug":1026,"type":15},"Observability","observability",{"name":9,"slug":8,"type":15},"2026-07-28T05:34:11.117757",{"slug":76,"name":76,"fn":1030,"description":1031,"org":1032,"tags":1033,"stars":26,"repoUrl":27,"updatedAt":1039},"audit PostHog project endpoints","Audit every endpoint in a PostHog project for staleness, failed materialisations, and unused materialised versions. Use when the user asks \"what endpoints can I clean up?\", \"are any of my endpoints broken?\", \"which materialised versions are still being called?\", or wants a one-shot cleanup pass over the Endpoints product. Produces a prioritised report grouped by issue type, with recommended actions but does not modify anything without explicit confirmation.\n",{"slug":8,"name":9,"logoUrl":10,"githubOrg":8},[1034,1035,1038],{"name":18,"slug":19,"type":15},{"name":1036,"slug":1037,"type":15},"Audit","audit",{"name":9,"slug":8,"type":15},"2026-06-08T08:08:33.693989",{"slug":1041,"name":1041,"fn":1042,"description":1043,"org":1044,"tags":1045,"stars":26,"repoUrl":27,"updatedAt":1051},"auditing-warehouse-source-health","audit PostHog data warehouse source health","Audit the health of a PostHog project's data warehouse sources and syncs — find every broken or degraded source connection, sync schema, and webhook channel. Use when the user asks \"why are my imports failing?\", \"what's broken with my sources?\", \"why is my warehouse data stale?\", or wants a one-shot triage of source\u002Fsync health before deciding where to dig in. Produces a prioritized report grouped by severity, with recommended next steps. For materialized-view health use `auditing-warehouse-view-health`; for a single failing sync use `diagnosing-failed-warehouse-syncs`.\n",{"slug":8,"name":9,"logoUrl":10,"githubOrg":8},[1046,1047,1049,1050],{"name":1036,"slug":1037,"type":15},{"name":1048,"slug":35,"type":15},"Data Warehouse",{"name":1025,"slug":1026,"type":15},{"name":9,"slug":8,"type":15},"2026-06-18T08:22:57.67984",{"slug":1053,"name":1053,"fn":1054,"description":1055,"org":1056,"tags":1057,"stars":26,"repoUrl":27,"updatedAt":1062},"auditing-warehouse-view-health","audit PostHog materialized view health","Audit the health of a PostHog project's materialized views (saved queries) — find every failed materialization and flag unused or stale materialized views that cost storage and compute. Use when the user asks \"which of my views are broken?\", \"why is this materialized view failing?\", \"are any of my views wasting compute?\", or wants a one-shot triage of view health. For source\u002Fsync health use `auditing-warehouse-source-health`.\n",{"slug":8,"name":9,"logoUrl":10,"githubOrg":8},[1058,1059,1060,1061],{"name":1036,"slug":1037,"type":15},{"name":1048,"slug":35,"type":15},{"name":13,"slug":14,"type":15},{"name":9,"slug":8,"type":15},"2026-06-18T08:25:10.936787",{"slug":1064,"name":1064,"fn":1065,"description":1066,"org":1067,"tags":1068,"stars":26,"repoUrl":27,"updatedAt":1075},"authoring-error-tracking-alerts","author PostHog error tracking alerts","Author error tracking alerts that fire when an issue is created, reopened, or starts spiking. Use when the user asks to set up error notifications, route exceptions to Slack\u002Fwebhook\u002FLinear, or evaluate which error events are worth alerting on. Covers trigger-event selection, integration choice, dedup against existing alerts, and shipping with the canonical message body shape.\n",{"slug":8,"name":9,"logoUrl":10,"githubOrg":8},[1069,1072,1073,1074],{"name":1070,"slug":1071,"type":15},"Alerting","alerting",{"name":24,"slug":25,"type":15},{"name":1025,"slug":1026,"type":15},{"name":9,"slug":8,"type":15},"2026-06-18T08:24:40.318583",{"slug":1077,"name":1077,"fn":1078,"description":1079,"org":1080,"tags":1081,"stars":26,"repoUrl":27,"updatedAt":1091},"authoring-log-alerts","author log alerts in PostHog","Author useful, low-noise log alerts on services in a PostHog project. Use when the user asks to set up alerts for their logs, suggest alerts they should add, or evaluate whether a service is worth monitoring. Covers service triage, baseline characterisation, threshold drafting, back-testing via simulate, and shipping with a notification destination.\n",{"slug":8,"name":9,"logoUrl":10,"githubOrg":8},[1082,1083,1086,1087,1090],{"name":18,"slug":19,"type":15},{"name":1084,"slug":1085,"type":15},"Monitoring","monitoring",{"name":1025,"slug":1026,"type":15},{"name":1088,"slug":1089,"type":15},"Operations","operations",{"name":9,"slug":8,"type":15},"2026-07-18T05:10:54.430898",{"slug":1093,"name":1093,"fn":1094,"description":1095,"org":1096,"tags":1097,"stars":26,"repoUrl":27,"updatedAt":1108},"building-workflows","build and edit PostHog workflows","Build, edit, test, enable, and monitor PostHog workflows over MCP. Author the action\u002Fedge graph so it runs and opens cleanly in the visual editor, then change drafts surgically with patch operations. Use when asked to build, set up, automate, change, fix, or debug a workflow, campaign, broadcast, drip sequence, or event-triggered automation in the workflows product.",{"slug":8,"name":9,"logoUrl":10,"githubOrg":8},[1098,1101,1104,1105],{"name":1099,"slug":1100,"type":15},"Automation","automation",{"name":1102,"slug":1103,"type":15},"MCP","mcp",{"name":9,"slug":8,"type":15},{"name":1106,"slug":1107,"type":15},"Workflow Automation","workflow-automation","2026-07-28T05:34:12.167015",{"slug":1110,"name":1110,"fn":1111,"description":1112,"org":1113,"tags":1114,"stars":26,"repoUrl":27,"updatedAt":1122},"check-posthog-loading","inspect PostHog SDK loading across URLs","Inspect how the PostHog JavaScript SDK is loaded across a list of URLs. Use to confirm consistent installation across pages, find pages missing the snippet, detect mismatched API keys or hosts between pages, and verify the load method (head snippet vs deferred vs array.js).\n",{"slug":8,"name":9,"logoUrl":10,"githubOrg":8},[1115,1116,1117,1120,1121],{"name":18,"slug":19,"type":15},{"name":24,"slug":25,"type":15},{"name":1118,"slug":1119,"type":15},"Frontend","frontend",{"name":1025,"slug":1026,"type":15},{"name":9,"slug":8,"type":15},"2026-05-07T05:56:19.828048",{"slug":1124,"name":1124,"fn":1125,"description":1126,"org":1127,"tags":1128,"stars":26,"repoUrl":27,"updatedAt":1137},"consuming-endpoints-from-client-code","integrate PostHog endpoints into client applications","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\u002Fopenapi-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\".\n",{"slug":8,"name":9,"logoUrl":10,"githubOrg":8},[1129,1132,1133,1134],{"name":1130,"slug":1131,"type":15},"API Development","api-development",{"name":1118,"slug":1119,"type":15},{"name":9,"slug":8,"type":15},{"name":1135,"slug":1136,"type":15},"SDK","sdk","2026-06-08T08:08:34.929454",{"slug":1139,"name":1139,"fn":1140,"description":1141,"org":1142,"tags":1143,"stars":26,"repoUrl":27,"updatedAt":1147},"copying-endpoints-across-projects","copy PostHog endpoints across projects","Copy a PostHog endpoint (a saved HogQL\u002Finsight query exposed as an API route) to another project in the same organization, or duplicate it under a new name in the same project. Use when the user wants to duplicate an endpoint, promote an endpoint from staging to production, replicate an endpoint's query\u002Fvariables\u002Ffreshness config in another workspace, or clone an endpoint to iterate on it. Unlike feature flags and experiments, endpoints have NO native cross-project copy tool — this skill covers the read-then-recreate flow (endpoint-get then endpoint-create), the active-project switching it requires, name-collision checks, and the safe defaults (land unmaterialised in the target, verify with endpoint-run). Does not cover editing endpoint versions (see managing-endpoint-versions) or authoring a brand-new endpoint from scratch (see creating-an-endpoint).\n",{"slug":8,"name":9,"logoUrl":10,"githubOrg":8},[1144,1145,1146],{"name":1130,"slug":1131,"type":15},{"name":1088,"slug":1089,"type":15},{"name":9,"slug":8,"type":15},"2026-07-15T05:29:58.442727",{"slug":1149,"name":1149,"fn":1150,"description":1151,"org":1152,"tags":1153,"stars":26,"repoUrl":27,"updatedAt":1165},"creating-ai-subscription","schedule recurring AI-generated PostHog reports","Create a recurring AI-generated PostHog report — schedule a free-text prompt to run on a cron, with the LLM-synthesized markdown delivered to email or Slack on each tick. Use when the user wants a recurring AI summary of X on any cadence (daily, weekly, monthly, yearly) rather than a one-off report. (To attach an AI summary to an existing insight\u002Fdashboard subscription instead of a free-text prompt, see `managing-subscriptions` and its `summary_enabled` option.)\n",{"slug":8,"name":9,"logoUrl":10,"githubOrg":8},[1154,1155,1158,1159,1162],{"name":1099,"slug":1100,"type":15},{"name":1156,"slug":1157,"type":15},"Email","email",{"name":9,"slug":8,"type":15},{"name":1160,"slug":1161,"type":15},"Reporting","reporting",{"name":1163,"slug":1164,"type":15},"Slack","slack","2026-06-09T07:32:27.935712",{"slug":1167,"name":1167,"fn":1168,"description":1169,"org":1170,"tags":1171,"stars":26,"repoUrl":27,"updatedAt":1175},"creating-an-endpoint","create PostHog API endpoints","Create a PostHog endpoint with the right shape on the first try — covers query kind choice, name conventions, what to expose as variables (HogQL code_name vs insight breakdown), data_freshness_seconds, and whether to materialise on day one. Use when the user says \"create an endpoint\", \"expose this query as an API\", \"turn this insight into an endpoint\", or asks for help structuring a new endpoint. Steers away from common mistakes: materialising a query with cohort breakdowns or compare mode, inline-only variables on a materialised endpoint, unbounded date ranges, ambiguous names.\n",{"slug":8,"name":9,"logoUrl":10,"githubOrg":8},[1172,1173,1174],{"name":18,"slug":19,"type":15},{"name":1130,"slug":1131,"type":15},{"name":9,"slug":8,"type":15},"2026-06-08T08:08:29.624498",231,{"items":1178,"total":1228},[1179,1186,1192,1199,1206,1213,1221],{"slug":1015,"name":1015,"fn":1016,"description":1017,"org":1180,"tags":1181,"stars":26,"repoUrl":27,"updatedAt":1028},{"slug":8,"name":9,"logoUrl":10,"githubOrg":8},[1182,1183,1184,1185],{"name":18,"slug":19,"type":15},{"name":1022,"slug":1023,"type":15},{"name":1025,"slug":1026,"type":15},{"name":9,"slug":8,"type":15},{"slug":76,"name":76,"fn":1030,"description":1031,"org":1187,"tags":1188,"stars":26,"repoUrl":27,"updatedAt":1039},{"slug":8,"name":9,"logoUrl":10,"githubOrg":8},[1189,1190,1191],{"name":18,"slug":19,"type":15},{"name":1036,"slug":1037,"type":15},{"name":9,"slug":8,"type":15},{"slug":1041,"name":1041,"fn":1042,"description":1043,"org":1193,"tags":1194,"stars":26,"repoUrl":27,"updatedAt":1051},{"slug":8,"name":9,"logoUrl":10,"githubOrg":8},[1195,1196,1197,1198],{"name":1036,"slug":1037,"type":15},{"name":1048,"slug":35,"type":15},{"name":1025,"slug":1026,"type":15},{"name":9,"slug":8,"type":15},{"slug":1053,"name":1053,"fn":1054,"description":1055,"org":1200,"tags":1201,"stars":26,"repoUrl":27,"updatedAt":1062},{"slug":8,"name":9,"logoUrl":10,"githubOrg":8},[1202,1203,1204,1205],{"name":1036,"slug":1037,"type":15},{"name":1048,"slug":35,"type":15},{"name":13,"slug":14,"type":15},{"name":9,"slug":8,"type":15},{"slug":1064,"name":1064,"fn":1065,"description":1066,"org":1207,"tags":1208,"stars":26,"repoUrl":27,"updatedAt":1075},{"slug":8,"name":9,"logoUrl":10,"githubOrg":8},[1209,1210,1211,1212],{"name":1070,"slug":1071,"type":15},{"name":24,"slug":25,"type":15},{"name":1025,"slug":1026,"type":15},{"name":9,"slug":8,"type":15},{"slug":1077,"name":1077,"fn":1078,"description":1079,"org":1214,"tags":1215,"stars":26,"repoUrl":27,"updatedAt":1091},{"slug":8,"name":9,"logoUrl":10,"githubOrg":8},[1216,1217,1218,1219,1220],{"name":18,"slug":19,"type":15},{"name":1084,"slug":1085,"type":15},{"name":1025,"slug":1026,"type":15},{"name":1088,"slug":1089,"type":15},{"name":9,"slug":8,"type":15},{"slug":1093,"name":1093,"fn":1094,"description":1095,"org":1222,"tags":1223,"stars":26,"repoUrl":27,"updatedAt":1108},{"slug":8,"name":9,"logoUrl":10,"githubOrg":8},[1224,1225,1226,1227],{"name":1099,"slug":1100,"type":15},{"name":1102,"slug":1103,"type":15},{"name":9,"slug":8,"type":15},{"name":1106,"slug":1107,"type":15},61]