posthog/auditing-warehouse-source-coverage
Audit already-implemented Data warehouse import sources for endpoints, schemas, and tables the vendor's API offers but we never wired up. Use when asked whether a source is missing endpoints, to find new endpoints a vendor has added since a source was built, to refresh COVERAGE_GAPS.md, to prioritize which source to deepen next, or to check coverage before an integration review. Covers dumping our real endpoint inventory credential-free, ranking sources by production adoption, diffing against vendor OpenAPI/GraphQL specs, and recording findings. Not for implementing a source (use implementing-warehouse-sources), adding a vendor API version (warehouse-source-new-version), or writing source docs (documenting-warehouse-sources).
npx skills add https://github.com/PostHog/posthog --skill auditing-warehouse-source-coverage
Finds endpoint gaps in sources that already work: the vendor exposes an object users want,
and we never added a table for it.
This is the reverse of implementing a new source.
Nothing here is about the ~646 scaffolded stubs.
Output goes in
products/warehouse_sources/backend/temporal/data_imports/sources/COVERAGE_GAPS.md.
Read it first: it records what a previous audit already found, so you extend it rather than rediscover it.
There are ~586 implemented sources.
You cannot diff all of them against vendor docs in one pass, and a flat list of "source X has N tables"
tells you nothing, because N should be 3 for some vendors and 40 for others.
So the audit is: **establish our real inventory, rank by who actually uses it, diff the top of that
ranking by hand, and sweep the long tail with a batched workflow.**
Depth over breadth on the sources that matter; breadth via fan-out for the rest.
Ten spec-verified sources beat 200 guesses.
Both halves have been run once. COVERAGE_GAPS.md holds the hand-audited high-adoption sources and
COVERAGE_GAPS_APPENDIX.md holds the swept remainder, so a re-run is a refresh, not a cold start.
The tail is too big to audit inline but parallelizes perfectly, since sources are independent.
What worked: one parallel() fan-out, batches of 8 sources per agent, 69 agents for 547 sources.
It cost about 6.8M subagent tokens and 3,000 tool calls, and returned 4,540 findings with zero agent
errors, so budget accordingly before starting.
Requires explicit user opt-in to run a workflow at that scale.
Design notes that mattered:
[{s, l, t, d}] (source type, label, current tables, known docs URLs) to a scratchpad JSON, give each agent an index range, and have it read its own slice. Inlining 136KB of source data into 69 prompts is pure waste.get_schemas() and is authoritative, so agents spend their budget on vendor research instead of re-reading our code.verified boolean and the exact doc_url diffed against. That URL is what makes the result auditable afterwards.gaps/thin/adequate results were verified: true and only the 5 genuine failures came back could-not-verify.Then validate the output before trusting it:
doc_urls with curl -o /dev/null -w "%{http_code}". About 95% should return 200; a low rate means agents were inventing sources.Do not read settings.py files by hand and do not trust canonical_descriptions.py alone
(it can lag the real endpoint map).
Use the registry, which reports what get_schemas actually returns.
Run scripts/dump_source_inventory.py from the repo root:
flox activate -- bash -c "PYTHONPATH=. python .agents/skills/auditing-warehouse-source-coverage/scripts/dump_source_inventory.py > /tmp/inventory.json"
It leans on Source.get_documented_tables(), which builds a credential-free placeholder config,
so it needs no secrets and hits no vendor API.
Gotchas:
{.SQLSource subclasses (Postgres, MySQL, BigQuery, Snowflake, MongoDB, Supabase, Redshift, MSSQL, ClickHouse, Neon, Convex) and file sources (Google Sheets, Custom) introspect user schemas and legitimately return nothing. Exclude them from the audit entirely; there is no fixed endpoint set to be missing.A gap only matters in proportion to who hits it.
Rank by distinct projects with a live connection of that source type, pulling the synced Postgres
replicas in the internal dogfood project (US project 2), which cover both regions:
SELECT source_type, count() AS connections, count(DISTINCT team_id) AS teams
FROM (
SELECT source_type, team_id FROM postgres_posthog_externaldatasource WHERE deleted = false
UNION ALL
SELECT source_type, team_id FROM eu_postgres_posthog_externaldatasource WHERE deleted = false
)
GROUP BY source_type
ORDER BY teams DESC
Confirm column names against system.information_schema.columns first; the replica schema drifts.
See the analyzing-insights-across-teams skill for how these replicas are set up.
Then join the ranking to the inventory table counts.
The interesting signal is a low table count with high adoption — that is where a small amount of
work reaches the most people.
Connection counts are internal operational data.
Use them to prioritize, but never write them into a committed doc, PR description, or commit message.
This repo is public. Convert them to relative tiers before publishing anything.
Work down the ranking. For each source, get a machine-readable spec and diff it against our
endpoint list.
Prefer specs over prose docs, and prefer curl over WebFetch.
WebFetch summarizes with a small model and reliably drops most of an API reference; it answered
"I cannot find a list of resources" for both the Stripe and HubSpot references. Fetch the spec and
parse it yourself instead.
Specs that worked on 2026-07-26:
| Vendor | Spec |
| ---------- | -------------------------------------------------------------------------------------------------------------------- |
| Stripe | https://raw.githubusercontent.com/stripe/openapi/master/openapi/spec3.json |
| GitHub | https://raw.githubusercontent.com/github/rest-api-description/main/descriptions/api.github.com/api.github.com.json |
| Klaviyo | https://raw.githubusercontent.com/klaviyo/openapi/main/openapi/stable.json |
| Clerk | https://raw.githubusercontent.com/clerk/openapi-specs/main/bapi/2024-10-01.yml |
| Zendesk | https://developer.zendesk.com/zendesk/oas.yaml |
| Mailchimp | https://api.mailchimp.com/schema/3.0/Swagger.json?expand |
| Sentry | https://raw.githubusercontent.com/getsentry/sentry-api-schema/main/openapi-derefed.json |
| Cloudflare | https://raw.githubusercontent.com/cloudflare/api-schemas/main/openapi.json |
For a vendor with no published spec, try in order: an llms.txt, a public SDK's resource modules
(client libraries enumerate every resource), the GraphQL introspection schema, then the docs sitemap.
HubSpot's llms.txt covers only apps and CMS, not the API reference, so it is not useful here.
Extract the resource list by pattern-matching paths that are collection GETs, then set-difference
against our tables. Sketch:
import json, re
spec = json.load(open("spec.json"))
tops = {
m.group(1)
for p, ops in spec["paths"].items()
if "get" in ops and (m := re.fullmatch(r"/v1/([a-z0-9_]+(?:/[a-z0-9_]+)?)", p))
}
Two things to get right:
issues/comments and pulls/comments, which are two of its most valuable endpoints. Run the extraction at one and two segments and read both.reports/{id}/email-activity and Klaviyo's flow messages are nested under a parent we already sync, so a top-level-only diff reports full coverage.A raw set difference is noise. Most vendors have endpoints nobody wants in a warehouse.
For each missing endpoint ask:
dealstage ID with no pipelines table; Linear issues carry a state ID with no workflow_states. Small endpoint, large unlock. Always check for these first.sessions, Zendesk satisfaction_ratings, Mailchimp per-recipient opens and clicks. If a vendor's own marketing leads with a number we cannot produce, that is a real gap.ENDPOINTS tuple. Flag these separately; they need design.Also check our side for half-finished threads before writing a gap up as new work:
cd products/warehouse_sources/backend/temporal/data_imports/sources
grep -rniE "TODO|FIXME|not (yet )?(supported|implemented)" <source>/*.py | grep -vi test
HubSpot's WEB_ANALYTICS_EVENTS_ENDPOINT is defined in settings.py and referenced nowhere,
which makes it a cheaper item than it looks.
Before writing up, re-read your per-source findings for repeats.
Themes are more actionable than 40 separate bullets, and they change how the work gets scheduled.
Tag the findings rather than eyeballing them. Regex the endpoint and why fields of every gap into
theme buckets and count. A measured prevalence is far more persuasive to whoever schedules the work
than "this seems common", and it tells you which theme to fund first.
Patterns found so far, all still open, with their measured share of the 4,540 swept gaps:
When you find a new theme, add it to the patterns section of COVERAGE_GAPS.md.
Update COVERAGE_GAPS.md in place. Keep its conventions:
State these rather than letting a reader assume otherwise:
fields[...] params, or properties allowlists. That is a separate and probably larger audit than this one.implementing-warehouse-sources — building a source, or adding the endpoints this audit found.warehouse-source-new-version — a vendor shipped a new API version. A version bump often adds endpoints, so it is a good trigger to re-audit that one source.documenting-warehouse-sources — the public posthog.com docs for a source, which render from get_documented_tables(), the same call this audit uses.Take posthog/auditing-warehouse-source-coverage from the repository into ~/.claude/skills for personal
use, or into .claude/skills inside a project.
The agent identifies a skill by the name field in its header. Two skills with the
same name cannot sit side by side — one of them will be ignored.