creating-an-endpoint — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited creating-an-endpoint (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
This skill walks through creating a new endpoint with the right configuration. Endpoints expose saved HogQL or insight queries as callable HTTP routes — the configuration choices made at creation time determine cost, latency, and how callers integrate.
The materialisation deep-dive lives at references/materializing.md. Pull it in when the materialisation decision is non-obvious.
and the user is choosing how to deliver it
Endpoints are right when:
Endpoints are wrong when:
only adds an external API surface you don't need internally
execute-sql tool (or the SQL editor) directlyHeavy aggregation is not a reason to avoid an endpoint. Endpoints are themselves saved queries, and a heavy, frequently-called aggregation is often the _best_ case for an endpoint with materialisation turned on.
If the user is unsure, ask what's calling the endpoint and what shape they expect.
Names are URL-safe (letters, numbers, hyphens, underscores), start with a letter, max 128 chars, must be unique within the project. Lean toward:
weekly_active_users_by_org over metricsThe name appears in the URL: /api/projects/{team_id}/endpoints/{name}/run. It's not trivially renameable later (callers depend on the path) — get it right at creation.
Two options exist:
HogQLQuery) — raw SQL written by the user. Variables defined via {variables.x}syntax, matched on code_name. Recommended for new endpoints when the caller cares about the exact column shape of the response.
TrendsQuery,LifecycleQuery, and RetentionQuery: these can be materialised, and the breakdown can act as a variable (Trends and Retention only; Lifecycle has no breakdown). Other insight kinds such as FunnelsQuery can run inline but cannot be materialised and don't expose breakdown variables — rewrite those as HogQL if you need either.
HogQL is the more flexible choice. Pick insight only when the user is genuinely re-publishing an existing insight (see "Creating from an existing insight" below) rather than building a new query.
Anything that should change per-caller goes in variables; the rest is hard-coded in the query.
For HogQL endpoints, variables are declared in the query payload with code_name, type, and default. Each execution call passes { "variables": { "<code_name>": value } }.
Common patterns:
date_from, date_to, or a single lookback_days integeruser_id, account_id, team_idlimit / offset (these are first-class on the run endpoint already)For insight endpoints, the breakdown property acts as the variable (Trends and Retention only — Lifecycle has no breakdown). Pass the breakdown property name as the key. date_from / date_to are accepted as variables only on non-materialised insight endpoints — a materialised endpoint bakes its date range into the view, so callers can't shift the window.
Avoid:
fundamentally different result shapes, ship separate endpoints.
where_clause variable that lets callersinject arbitrary SQL.
There's no server-side "make an endpoint from insight N" operation. To do it: read the insight's query (via the insight tools), pass that query to endpoint-create, and set derived_from_insight to the insight's short id so the origin is recorded. The endpoint then owns its own copy of the query — later edits to the insight don't propagate. Starting from scratch instead? Build the query first with the insight / sql-variables tools, then create the endpoint from it.
data_freshness_secondsThis one field does two jobs, so set it deliberately:
warehouse recomputes the materialised view.
So a lower value means fresher data _and_ more frequent recompute/refresh cost; a higher value is cheaper on both counts but staler.
The value must be one of a fixed set: 900 (15 min), 1800 (30 min), 3600 (1 h), 21600 (6 h), 43200 (12 h), 86400 (24 h, default), 604800 (7 d). There is no sub-15-minute option — 900 is the floor.
data_freshness_seconds | When to pick it |
|---|---|
| 900–1800 | Freshest available — dashboards where staleness is visible |
| 3600–43200 | Most cases — fresh enough for product usage, cheap to recompute |
| 86400–604800 | Reports, weekly/daily metrics, anything aggregated over long periods |
Bias toward higher values unless the user explicitly needs fresher data. On a materialised endpoint, remember this also sets the refresh cadence.
See references/materializing.md for the full decision tree. Short version:
and the user can tolerate staleness equal to the refresh interval (typically 5-15 minutes for scheduled materialisation, or hourly).
know yet if it'll get called), and queries where freshness is critical.
If unsure, create unmaterialised and add is_materialized: true later once usage stabilises. That avoids paying for materialisation on a query nobody ends up calling.
the alternative.
data_freshness_seconds based on the user's freshness requirement (ask if not clear) —remembering it also sets the materialisation refresh cadence.
endpoint-create with the agreed config.endpoint-run with a sample payload to verify the response shape.consuming-endpoints-from-client-code if the user is about to wire it up.User: "I want to expose our monthly active users count as an API
for our analytics partner"
Agent:
- "Quick check: is the partner going to call this on demand, or
should we be pushing data to them? Endpoints are pull-only."
- User: "On demand"
- "Got it. A few choices:
- Name: how about monthly_active_users? Lowercase, descriptive,
no version baggage.
- The query — is it HogQL or are you exposing an existing insight?
HogQL gives you exact column control; insight reuses what you've
already built.
- What should the partner be able to filter by? Org, time window,
both?
- How fresh? If MAU drifts within a day is fine, we can cache
results for 1-24 hours and serve almost everything from cache.
- Materialisation: if the partner is going to hit this many times
per day, materialising it would make responses near-instant.
The trade-off is ~10-15 minute staleness.
What's your starting point on these?"
[user picks: HogQL, lookback_days + org_id variables, 3600s TTL,
not materialised yet]
- endpoint-create monthly_active_users {query, variables, ...}
- endpoint-run with sample payload {org_id: "test", lookback_days: 30}
- "Created and tested. Want help wiring up the client code?
That's consuming-endpoints-from-client-code."user has a specific reason to wrap an existing insight.
endpoint is testable from the playground without specifying every variable.
rejected — this is intentional (security: prevents returning unfiltered data). Pair the materialisation recommendation with a note to the user about which variables become required. (Optional/partial variables on materialised endpoints are a known limitation the PostHog team plans to lift — if it's blocking the user, nudge them via the agent-feedback tool.)
endpoints-materialization-preview first to confirm eligibility and see the rejection reason if any.
automatically (the old version stays accessible via ?version=N). data_freshness_seconds and materialisation are per-version. Adjust as the endpoint evolves.
?version=N rather thanrelying on "latest" — that way a future query edit (which cuts a new version) can't silently change their results. They bump the pinned version deliberately once they've validated the new one.
required variables, the TTL enum), send the PostHog team a note — it's how the product and these tools improve.
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.