Measure Documentation Audit
One-line state of the docs: a genuinely deep, multi-platform mobile observability product whose reference material is strong per-page but riddled with cross-platform asymmetry, copy-paste defects in the exact code/ops snippets you'd run, and — most pointed given that Measure ships an MCP server for AI agents — an agent-facing llms-full.txt whose internal links systematically 404.
1. The AI-facing docs export (llms-full.txt) mangles every internal cross-reference into a 404 (critical)
Location: https://measure.sh/llms-full.txt
Problem: llms-full.txt is published as "Complete documentation in a single file" for AI tools, and Measure also ships an MCP server aimed at exactly those tools. Yet every internal cross-link in that file has path segments dropped. The Self-Hosting section emits https://measure.sh/docs/google-oauth, /docs/github-oauth, /docs/smtp-email, /docs/slack; the SDK Upgrade section emits /docs/android-v0.16.0, /docs/ios-v0.9.0; other sections emit /docs/feature-screenshot-masking-swiftui, /docs/configuration-options, plus broken relative forms like https://measure.sh/docs/../hosting/migration-guides and .../../backend/symboloader. These all return HTTP 404 (verified by curl). The correct canonical paths (/docs/hosting/google-oauth, /docs/sdk-upgrade-guides/android-v0.16.0, etc.) return 200 and are exactly what the human-rendered pages and the companion llms.txt index use. Mailto links (e.g. the security contact) are dropped from this file entirely.
Consequence: This does not break the product, and the human-rendered docs and the llms.txt index are correct — but it degrades the one consumption path Measure explicitly built for the agents it also serves via MCP. An agent (or a human) following any cross-reference in llms-full.txt lands on a 404 instead of OAuth setup, migration guides, or upgrade notes — silently, with no fallback — and the file contradicts Measure's own llms.txt index, which lists the correct URLs.
The fix: Fix the link generator that produces llms-full.txt so internal hrefs use the canonical /docs/... paths (and resolve ../ segments) instead of flattening them. Add a CI check that crawls every link in llms-full.txt and fails the build on any non-200, since this file is explicitly marketed to automated consumers.
2. Self-host healthcheck commands have the API and Dashboard labels swapped (significant)
Location: https://measure.sh/docs/hosting — "How to perform healthcheck of Measure services?"
Problem: The Caddyfile earlier on the same page maps measure.yourcompany.com → :3000 (the dashboard) and measure-api.yourcompany.com → :8080 (the API). But the FAQ inverts them: the "API service" healthcheck runs curl -s https://measure.yourcompany.com | grep measure / http://localhost:3000, which hits the dashboard, while the "Dashboard service" healthcheck runs curl -s https://measure-api.yourcompany.com/ping | grep pong / http://localhost:8080/ping, which hits the API.
Consequence: A self-hoster verifying their deployment tests the wrong service under each label. They can declare "API healthy" when they only confirmed the dashboard responded, masking a down API — the kind of mistake you make precisely during an incident or first bring-up when you can least afford it.
The fix: Swap the two labeled blocks so the API healthcheck targets measure-api/:8080/ping and the Dashboard healthcheck targets the dashboard host/:3000, matching the Caddyfile in the same document.
3. The "Run on macOS locally" section hands you the production start command (significant)
Location: https://measure.sh/docs/hosting — "Run on macOS locally"
Problem: The section is framed as a quick local try-out, but the only start command it documents is the production one: compose.prod.yml --profile migrate. There is no separate lightweight/dev compose path shown for someone who just wants to evaluate Measure on their machine.
Consequence: A developer wanting to "run it locally" is steered into bringing up the full production stack, including the migrate profile, on their laptop — conflating "try it locally" with "stand up production." At best it's confusing; at worst they run migration steps they didn't intend, on an environment they assumed was a throwaway trial.
The fix: Document a dedicated local/dev start path (a non-prod compose file or profile) for the "run locally" use case, or relabel the section to state plainly that it stands up the production deployment and explain when the migrate profile is appropriate.
4. "Status is mandatory when ending a span" is contradicted by the React Native examples, and the intro examples ship logic bugs (significant)
Location: https://measure.sh/docs/features/feature-performance-tracing
Problem: The "End a Span" section states "Status is mandatory to set when ending a span" and shows span.setStatus(...).end() for Android/iOS/Flutter — but the React Native entry on the same line is just span.end() with no status, and every React Native span in the intro example also ends without a status. Separately, the intro examples contain bugs: the Android example ends the root span with onboardingSpan.end(SpanStatus.Error) inside a finally block, so a successful run is recorded as Error — yet the rendered trace shows onboarding-flow ━━━ [2.4s] ✓. The Flutter intro example calls onboardingSpan.end() twice (once in catch, once in finally).
Consequence: A developer can't tell whether status is required (the rule) or optional (the RN example). Copying the Android/Flutter intro verbatim produces spans whose status is wrong or set twice — exactly the data-integrity bug a tracing tool is supposed to catch, now baked into the onboarding snippet.
The fix: Either make RN's end() accept/require a status like the others, or correct the "mandatory" wording to describe RN's actual behavior. Move onboardingSpan.end(SpanStatus.Ok) to the success path (not finally) in the Android example, and remove the duplicate end() in the Flutter example.
5. The same "track a handled error" concept has three different method names across four platforms (significant)
Location: https://measure.sh/docs/features/feature-error-tracking
Problem: For one concept the docs use three names: Android Measure.trackHandledException(e), iOS and React Native Measure.trackError(...), Flutter Measure.trackHandledError(e, stackTrace). Argument shapes also diverge (Android positional + AttributesBuilder; iOS enum-wrapped values like .string("Login"), .int(2); RN object-wrapped { error: e, attributes: {...} }; Flutter documents no attribute support at all). Even the allowed attribute value types differ between pages: Android/iOS say "int, long, double, float or boolean," while RN says "string, number, or boolean."
Consequence: A developer or coding agent that learns the API on one platform and applies it to another writes code that doesn't compile or silently drops attributes. There's no cross-platform mapping table, so the inconsistency is invisible until build/runtime. (The RN argument-shape problem is broader than this method — see Finding 18.)
The fix: Either align the method name across SDKs or add a single cross-platform mapping table (concept → per-platform signature → supported attribute types). Explicitly document whether Flutter supports attributes; if not, say so.
6. React Native is a supported platform but is missing across the docs hub and README index (significant)
Location: https://measure.sh/docs (docs hub), https://raw.githubusercontent.com/measure-sh/measure/main/README.md, vs. the platform pages
Problem: The README states "Measure supports Android, iOS, Flutter and React Native," and RN is fully documented in integration (@measuresh/react-native@0.1.1), configuration, error tracking, tracing, network monitoring, and identify-users pages. But the docs hub's "Explore Features" list and "Bug Reporting" subsection cover only Android/iOS/Flutter — React Native is absent — and the "SDK Upgrade Guides" tagline reads "for Android and iOS" even though a Flutter upgrade guide exists. There is no React Native upgrade guide at all, despite RN being separately versioned. The README compounds this: its "Important Docs" entry #1 reads "Integrate Android or iOS SDK and start measuring in no time," omitting Flutter and RN even though the same README's Platforms section lists all four.
Consequence: A React Native developer browsing the hub (or the README's docs index) reasonably concludes RN isn't supported, or can't find bug-report/feature docs that actually exist. RN adopters get no breaking-change guidance when upgrading a 0.x SDK that is still moving fast.
The fix: Add React Native to the hub's feature list, bug-report platform list, and upgrade-guides section; fix the "for Android and iOS" tagline and the README "Android or iOS SDK" wording; and publish at least a baseline RN upgrade/changelog page.
7. The iOS upgrade guide is a near-verbatim copy of the Android guide (significant)
Location: https://measure.sh/docs/sdk-upgrade-guides/ios-v0.9.0 vs. https://measure.sh/docs/sdk-upgrade-guides/android-v0.16.0
Problem: The iOS v0.9.0 upgrade guide reproduces the Android v0.16.0 guide almost word-for-word: the same removed-MeasureConfig-property list, the same wording, and all sections citing the same pull request (PR 3077). The removed-property names in that list (trackScreenshotOnCrash, httpHeadersBlocklist, traceSamplingRate, coldLaunchSamplingRate, enableFullCollectionMode, etc.) are Android-API spellings.
Consequence: An iOS developer following their platform's upgrade guide is reading Android-specific property names and behavior presented as if they apply to iOS. If the iOS SDK's property names or removed set differ even slightly, the guide silently misdocuments the breaking changes during the one operation — a version upgrade — where getting the breaking-change list wrong causes build failures or dropped configuration.
The fix: Rewrite the iOS upgrade guide against the actual iOS SDK surface: confirm each removed/renamed property exists with that exact name in iOS, cite the iOS-relevant change, and remove any Android-only carryover. If the change truly was identical, state that explicitly rather than leaving it as an apparent copy-paste.
8. MCP setup: the "XCode" link points to VSCode's documentation (significant)
Location: https://measure.sh/docs/features/feature-mcp — "Connecting to MCP via Coding Agents"
Problem: In the coding-agent list, the "XCode" entry links to https://code.visualstudio.com/docs/copilot/customization/mcp-servers — the exact same URL as the "VSCode" entry (verified against the live rendered DOM).
Consequence: An iOS developer trying to wire Measure's MCP server into Xcode is sent to Microsoft's VSCode/Copilot MCP docs, which don't apply to Xcode. Unlike the README link defect in Finding 16 (a marketing blurb), this misroute sits inside a functional setup path: for the platform whose primary IDE is Xcode, the configuration steps dead-end in the wrong tool's instructions.
The fix: Point the "XCode" link to Apple/Xcode's MCP configuration documentation (or remove the entry if Xcode MCP isn't actually supported yet).
9. The recommended Flutter SDK maps to an unreleased backend, and the iOS compatibility table predates the version you're told to install (significant)
Location: https://measure.sh/docs/sdk-integration-guide — Self-host Compatibility tables
Problem: The integration guide tells Flutter users to install measure_flutter: ^0.6.0. The Flutter self-host compatibility table's matching row is >= 0.4.0 → 0.10.0 (releasing soon) — so the recommended SDK maps to a backend version that, by the page's own words, hasn't been released yet. The iOS case is a freshness gap rather than an absence: the guide pins branch: "ios-v0.11.0", and the iOS table's newest row is the open-ended >= 0.7.0 → 0.9.0. Because 0.11.0 ≥ 0.7.0, the table does nominally document a minimum (backend 0.9.0) — but that row predates ios-v0.11.0 by several releases and has no entry for the version actually recommended, so a reader can't be confident 0.9.0 is still the correct minimum.
Consequence: A Flutter self-hoster following the recommended SDK is pointed at a server that isn't out yet — they literally cannot satisfy the documented requirement. An iOS self-hoster installing ios-v0.11.0 is relying on a stale open-ended bucket; if the real minimum moved between 0.7.0 and 0.11.0, ingest can silently reject data with no warning in the table.
The fix: Add explicit table rows for the SDK versions the integration guide recommends (Flutter 0.6.x, iOS 0.11.x). Don't recommend a Flutter SDK whose required backend is still "releasing soon." Confirm and state whether backend 0.9.0 remains the minimum for ios-v0.11.0, or update the row.
10. Google OAuth self-host guide tells you to choose User Type "Internal," which can lock out your users (significant)
Location: https://measure.sh/docs/hosting/google-oauth
Problem: The guide's stated goal is "so that your users can login using their Google accounts," but step 3 says "choose User Type as Internal." In Google Cloud, "Internal" restricts sign-in to accounts within the same Google Workspace organization. The closing "Go back to self host guide" link also points to the docs root (/docs) rather than the hosting overview.
Consequence: Any self-hoster whose intended users are not all members of one Workspace org (a very common case — contractors, mixed-org teams, personal Gmail) will find those users blocked from logging in, directly contradicting the page's own goal. This also sits in tension with the security page's claim that auth "strictly follow[s] the latest OAuth standards."
The fix: Document the User Type choice explicitly — "External" for general access, "Internal" only if all users share one Workspace org — and explain the consequence of each. Fix the trailing link to point at /docs/hosting.
11. MCP server documents only interactive browser sign-in — no headless/CI path, no errors, no rate limits (significant)
Location: https://measure.sh/docs/features/feature-mcp
Problem: The only documented auth flow is "your coding agent will open a browser window for you to sign in to Measure. After authenticating subsequent requests will work automatically." There is no documented token-based or headless authentication path, no error responses, and no rate-limit information for the MCP endpoint (https://api.measure.sh/mcp / https://[your-measure-api-domain]/mcp).
Consequence: As documented, the MCP server can't be used from any environment without an interactive browser — CI, remote dev containers, automated agents — which are exactly the contexts where MCP is most valuable. With no documented error shapes or rate limits, integrators can't build retry/backoff or handle auth expiry programmatically.
The fix: Document a non-interactive auth option (API token / service credential) if one exists — or state explicitly that MCP is interactive-browser-only today, so integrators don't assume a headless path that isn't there. Either way, document the error responses tools can return, token lifetime/refresh behavior, and any rate limits.
12. Configuration page never states which options apply to which platform, and the per-platform examples disagree (significant)
Location: https://measure.sh/docs/features/configuration-options
Problem: The Android example exposes trackActivityIntentData, maxDiskUsageInMb, requestHeadersProvider, enableFullCollectionMode, enableDiagnosticMode; iOS drops trackActivityIntentData/enableDiagnosticMode; and the Flutter and React Native examples expose only enableLogging, autoStart, enableDiagnosticMode. Options like maxDiskUsageInMb, requestHeadersProvider, and enableFullCollectionMode are never shown for Flutter/RN, and the page never states which options apply to which platform (only trackActivityIntentData is called out). The defaults table also lists "Bug Report session timeline duration | 300" with no unit while every sibling row says "300 seconds (5 minutes)," and enableFullCollectionMode is rendered as a top-level (#) heading while its siblings are sub-headings (##).
Consequence: A Flutter/RN developer can't tell whether maxDiskUsageInMb or requestHeadersProvider is unsupported on their platform or just omitted from the example — so they either can't configure it or guess wrong. The unitless "300" is ambiguous (seconds? frames?) next to rows that spell out the unit.
The fix: Add a per-platform support matrix for every config option (supported / not supported / different name). Give the Bug Report duration its unit. Demote the enableFullCollectionMode heading to match its siblings.
13. Downtime migration guide v0.10.x ships a no-op checkout command and a typo'd step title (significant)
Location: https://measure.sh/docs/hosting/migration-guides/v0.10.x
Problem: The page warns "Steps mentioned in this document will cause downtime," then in step 2 shows git checkout with no tag argument (the comment says "replace with the chosen tag. example: v0.10.0," but the command as written checks out nothing). Step 4 is titled "Set up an ingest endopint." This is the migration that introduces the server-side Google OAuth code flow requiring the new OAUTH_GOOGLE_SECRET variable.
Consequence: An operator copy-pasting the migration block runs a git checkout that does nothing, then proceeds believing they're on the new tag — during a procedure explicitly flagged as causing downtime, on a release with breaking auth/env changes. That's a recipe for a stuck or half-migrated deployment.
The fix: Show the real command, e.g. git checkout v0.10.0 (with a clear placeholder), and fix the "endopint" heading.
14. Published performance benchmarks are stale relative to the versions the integration guide installs (minor)
Location: https://measure.sh/docs/features/performance-impact
Problem: The Android benchmarks are labeled "for v0.16.0" and the Firebase comparison uses "Measure Android SDK version 0.10.0," while the integration guide installs measure-android:0.18.0. The iOS benchmark is "(v0.6.0)" while the integration guide pins branch: "ios-v0.11.0". The methodology link is also a broken repo-relative reference (/docs/../../android/measure-android/benchmarks).
Consequence: A developer evaluating Measure's startup-time overhead is reading numbers for SDK versions one to several releases behind what they'll actually ship, with no note on whether the figures still hold. The broken methodology link prevents them from re-checking.
The fix: Re-run and re-label benchmarks against the currently recommended SDK versions (or state the last-validated version and that newer releases are unmeasured), and fix the methodology link.
15. Self-host integration guide shows a malformed API URL placeholder with a double dot (minor)
Location: https://measure.sh/docs/sdk-integration-guide — "Connecting to a Self-hosted Server"
Problem: The example reads: set the API URL to https://measure-api..com, replacing with your own domain. The placeholder collapsed to a literal double dot (..com).
Consequence: A reader skimming for the URL format sees an invalid hostname and has to guess the intended shape (https://measure-api.yourcompany.com). An agent extracting the value would copy an unparseable URL.
The fix: Render the placeholder as a valid example domain, e.g. https://measure-api.yourcompany.com.
16. README links "Network Performance" to the User Journeys page (minor)
Location: https://raw.githubusercontent.com/measure-sh/measure/main/README.md
Problem: Under "Network Performance," the link text "network performance" points to https://measure.sh/product/user-journeys — the User Journeys page — instead of the network-performance product page. (The same line also contains the typo "endoints.")
Consequence: A reader clicking through to learn about network monitoring lands on an unrelated feature page. This is a marketing-blurb misroute rather than a setup path (contrast Finding 8), but since the README is the canonical entry point and is itself ingested into the docs, the wrong link propagates.
The fix: Point the Network Performance link at its own product page (e.g. /product/network-performance) and fix "endoints."
17. Alerts page table of contents omits a section that exists in the body (minor)
Location: https://measure.sh/docs/features/feature-alerts
Problem: The in-page TOC lists "Stopping Alerts" and "Disabling Slack Integration" but omits the "Listing Active Alert Channels" section (the /list-alert-channels slash command) that appears in the body. The intro also reads "Measure also sends out daily dummaries."
Consequence: A reader relying on the TOC won't discover the /list-alert-channels command, which is the only way to audit which channels are subscribed. The typo undermines confidence in a page about production alerting.
The fix: Add the "Listing Active Alert Channels" anchor to the TOC and fix "dummaries."
18. React Native SDK uses object-wrapped arguments inconsistently with every other platform — and with itself (minor)
Location: https://measure.sh/docs/features/feature-identify-users (plus error-tracking, tracing, network, init)
Problem: On React Native, Measure.setUserId({ userId: "user-id" }) takes an object while the sibling Measure.clearUserId() takes none, and all other platforms use a positional string setUserId("user-id"). The object-wrapping pattern recurs across the RN SDK (trackError({ error }), startSpan({ name }), trackHttpEvent({...}), init({ config })) but isn't applied uniformly even within RN. This is the broader form of the divergence noted in Finding 5.
Consequence: Developers porting code between platforms — or an agent generating RN code from an Android/iOS example — will pass a positional string where an object is required (or vice versa), producing runtime/type errors. The within-RN inconsistency (setUserId object vs clearUserId no-arg) means you can't even infer a single rule.
The fix: Document the RN argument-shape convention explicitly and apply it consistently, or note per method which shape it expects in a short RN API table.
19. Removed MeasureConfig options still appear as config, with no code-to-dashboard mapping for older SDKs (minor)
Location: https://measure.sh/docs/features/configuration-options + https://measure.sh/docs/sdk-upgrade-guides/android-v0.16.0
Problem: The Android v0.16.0 upgrade guide states that a large set of MeasureConfig properties (traceSamplingRate, screenshotMaskLevel, httpUrlBlocklist, journeySamplingRate, etc.) were removed from code and are now controlled from the dashboard ("Settings → Apps → Data Control"). But these same names still surface in the configuration documentation as options, and there is no single table mapping each removed code option to its new dashboard equivalent.
Consequence: A developer still on a pre-0.16.0 SDK, or one migrating, sees the same option name in two places (code config and dashboard) with no authoritative mapping of which is now canonical. They can set a value in code that the new SDK ignores, or hunt through dashboard settings for the equivalent of a code option without a name-to-name guide.
The fix: Add a "removed code option → dashboard setting" mapping table to the configuration page and the upgrade guide, and mark each affected option in the config docs as dashboard-controlled from the relevant SDK version onward.
20. Pricing page is too thin to answer the questions a self-hoster or buyer actually has (minor)
Location: https://measure.sh/pricing
Problem: The page lists only Free ($0, 5 GB/mo) and Pro ($50/mo, 25 GB) tiers and ends with a dangling "Get started:" followed by no content (an empty CTA). There is no enterprise tier, no explicit statement of whether self-hosting is free, and no cloud-vs-self-host breakdown — even though this is the only place a self-hoster could confirm self-hosting pricing.
Consequence: A self-hoster can't confirm that running their own instance is free of Measure charges, and a larger team sees no path beyond 25 GB/$50 except per-GB overage. The empty "Get started:" reads as a broken or truncated page.
The fix: State explicitly that self-hosting is free (if true), add cloud-vs-self-host clarity and an enterprise/contact tier, and either populate or remove the dangling "Get started:" CTA.
What they do well
- Genuinely deep multi-platform coverage — error tracking, tracing, network monitoring, config, and self-hosting are each documented per platform with runnable snippets, not just prose.
- Honest about footguns — e.g. the React Native page explicitly warns that Android HTTP events are not tracked without the manual
OkHttpClientProvidersetup, and migration guides flag downtime up front. - Self-host compatibility is taken seriously — per-platform SDK↔backend version tables exist at all, and a separate
llms.txtindex ships the correct canonical site map.
Top 3 recommendations
- Fix
llms-full.txtlink generation and gate it in CI — it's the file built for the agents you also serve via MCP, and it currently 404s on every internal cross-reference. The human docs are fine, so this is a high-leverage, contained fix. - Correct the copy-paste and onboarding defects in the snippets people actually run — swapped healthcheck labels, the "run locally" section that hands you the production migrate command, the no-op
git checkoutin a downtime migration, the success-recorded-as-Error tracing example, the iOS upgrade guide that clones Android's, and the malformedmeasure-api..comURL. - Resolve cross-platform inconsistency — add React Native to the docs hub and README index, publish a per-platform config/API support matrix (including a removed-code-option → dashboard mapping), and reconcile the divergent error-tracking method names and RN argument shapes so a developer (or agent) can't be silently wrong by following one platform's example on another.