Distr Documentation Audit
The Distr docs ship a real product story (open-source control plane, agents, registry, license keys, SDK), but the navigation is structurally broken: the live sidebar and product menu point at a legacy /docs/guides/... URL space that now 301-redirects everywhere, the API reference is an empty Stoplight shell on a separate host, and several technical specifics contradict each other across pages. Agents trying to crawl or copy from these docs will hit redirect chains, a 403 on llms.txt, and conflicting numbers.
1. Entire site navigation points at redirecting legacy URLs (critical)
Location: Header Product menu and docs sidebar (visible on /docs/guides/docker-env-var-template/ and other live pages)
Problem: The live header navigation still links to /docs/product/agents/, /docs/product/customer-portal/, /docs/product/registry/, /docs/product/license-management/, and the sidebar still links to /docs/guides/container-registry/, /docs/guides/preflight-checks/, /docs/guides/secrets/, /docs/guides/vulnerability-scanning/, /docs/guides/license-keys/, /docs/guides/application-entitlements/, /docs/guides/application-links/, /docs/guides/docker-secrets/, and /docs/use-cases/air-gapped/. Every one of those URLs returns a redirect to a different /docs/agents/..., /docs/platform/..., /docs/registry/..., or /docs/use-cases/air-gapped-deployments/ path. The top-level /docs/guides/ itself redirects to /docs/quickstart/. The "Product" parent link in the header points at /docs/product/agents/ and the first child item under it also points at /docs/product/agents/ — the parent and first child are the same redirecting URL.
Consequence: Every primary navigation click and every sidebar click is a redirect. Search engines and AI crawlers index the redirect targets, but external links, bookmarks, and social shares pointing at the linked-from-docs URLs all hit a 301. Users following the canonical sidebar trail end up on URLs that no longer match the sidebar they came from, breaking back-button context. It also doubles every page-load latency for users actually navigating the docs.
The fix: Update the site header (Menu > Product > ...) and the docs sidebar templates to point at the current canonical paths (/docs/agents/..., /docs/platform/..., /docs/registry/..., /docs/use-cases/air-gapped-deployments/). Either remove the redirect rules or, at minimum, stop emitting the legacy URLs from your own templates.
2. llms.txt and llms-full.txt return 403 (significant)
Location: https://distr.sh/llms.txt, https://distr.sh/llms-full.txt
Problem: Both llms.txt and llms-full.txt respond with HTTP 403 Forbidden. They are not absent — they are explicitly forbidden, which is worse than 404 because well-behaved crawlers will treat the resource as authentication-gated and back off.
Consequence: Agents (Claude, Cursor, Copilot, ChatGPT browsing, Perplexity, etc.) cannot use the standard llms.txt discovery flow to map Distr's docs. Combined with the redirecting sidebar (issue 1) and the API reference being on a separate app.distr.sh host (issue 3), Distr is effectively invisible to the crawlers most likely to recommend it to developers writing integration code.
The fix: Either serve a real llms.txt (and ideally llms-full.txt) at the root with the canonical URLs of the docs, or return 404 if you do not intend to provide one. Do not 403 well-known crawler discovery files.
3. The actual API reference is an empty Stoplight shell on a separate host (critical)
Location: https://distr.sh/docs/integrations/api/ directs readers to https://app.distr.sh/docs
Problem: /docs/integrations/api/ explicitly says it "lacks endpoint specifications, request/response schemas, and code examples beyond a single authentication curl command" and tells readers to go to "https://app.distr.sh/docs." That URL renders only a Stoplight Elements page title — "no visible endpoints, request/response examples, or detailed API documentation content." There is no public link to the OpenAPI spec, and no endpoint list anywhere in /docs/.
Consequence: A developer who needs to call the Distr API has nothing to read. They cannot discover endpoints, payload shapes, error codes, or pagination behavior. AI coding agents have nothing to ground generated code on — they will invent endpoints. The README's "REST API for programmatic management" claim is unsupported by any reachable schema.
The fix: Publish an OpenAPI spec at a stable URL on distr.sh (e.g. /openapi.yaml), embed Stoplight or Scalar inline in /docs/integrations/api/ rather than pointing readers at a different subdomain, and link to the raw spec from the API page so agents can ingest it directly.
4. License-key signing algorithm contradicts itself across pages (significant)
Location: /docs/platform/license-management/ vs /docs/platform/license-keys/
Problem: /docs/platform/license-management/ says "The system uses Ed25519 signing." /docs/platform/license-keys/ says the JWT is "signed with EdDSA (Ed25519 algorithm)" and the verification example calls importSPKI(PUBLIC_KEY_PEM, 'EdDSA'). Ed25519 is a key type and EdDSA is the JWT alg — but a developer reading only the first page will write jwtVerify(token, key, { algorithms: ['Ed25519'] }), which is not a valid JWT alg name and will fail.
Consequence: Customers building offline license verification pick the wrong alg string and get verification failures with no obvious cause. This is precisely the silent-failure mode the audit lens cares about — the two pages disagree on the literal string a developer must paste.
The fix: Standardize on "JWT signed with EdDSA, using an Ed25519 key" everywhere, and put a copy-pasteable alg: 'EdDSA' snippet on the license-management overview page so the signing algorithm cannot be mis-read.
5. License keys cannot be revoked before expiration (significant)
Location: /docs/platform/license-keys/
Problem: The page states verbatim: "Deleting a license key does not revoke tokens that have already been issued. A token remains valid until its expiration date." There is no documented revocation list, no jti blacklist mechanism, no shortened-window guidance, and no recommendation to issue short-lived tokens with a refresh strategy. This is the entire offline-verification security story.
Consequence: A vendor that learns a customer leaked a license key (or that a customer is in breach of contract) has no way to invalidate the existing token. The token continues to authorize seats, features, and usage limits in the customer's deployed application until exp. Combined with the EdDSA/Ed25519 wording confusion (issue 4), the offline license-verification story is fragile in exactly the cases it is sold to handle.
The fix: Either ship a revocation mechanism (a public revocation-list endpoint the application can poll, or a jti-blacklist API) and document it, or document the recommended mitigation explicitly: keep exp short, re-issue often, and treat license keys as bearer credentials that cannot be revoked early. Put the recommendation on the same page as the limitation, not buried.
6. API base path is split between /api/v1 and /api/public/v1 with no cross-reference (significant)
Location: /docs/integrations/api/ vs /docs/platform/license-keys/
Problem: /docs/integrations/api/ states "The API is accessible at /api/v1 on any Distr instance (self-managed or SaaS)." But /docs/platform/license-keys/ documents the public-key endpoint at GET /api/public/v1/license-keys/public-key. Two different versioned roots are exposed; neither page references the other, and there is no enumeration of what else lives under /api/public/v1/ versus /api/v1/.
Consequence: A developer integrating Distr cannot tell whether other unauthenticated endpoints exist under /api/public/v1/, nor whether the auth requirements (Authorization: AccessToken ...) and rate limits documented for /api/v1 apply to /api/public/v1. Agents writing client code will guess one or the other and silently get 404s.
The fix: Document both base paths on /docs/integrations/api/, list which categories of endpoints live under each, and state explicitly whether /api/public/v1/* requires auth or is rate-limited the same way as /api/v1/*.
7. Registry login port is 8585, not the standard 443 — and it's mentioned only once (significant)
Location: /docs/registry/configuration/
Problem: The only authentication snippet says: echo "YOUR-ACCESS-TOKEN" | docker login registry.distr.sh:8585 --password-stdin -u -. The push/pull example immediately below uses registry.distr.sh/slug/hello-world:1.0.0 — no port. The Registry overview at /docs/registry/ does not mention port 8585 at all. The air-gapped page says customers should "Request IP allowlist access" without listing the port.
Consequence: A customer behind a corporate firewall will allowlist registry.distr.sh:443 (the obvious default), then docker login on port 8585 silently fails. The push/pull example also implicitly requires port 8585 to resolve via DNS+default-port logic that is never explained — developers who follow only the tag/push lines will not know which port their proxy must forward.
The fix: Either drop the non-standard port entirely (front the registry with TLS on 443), or document the port consistently on the Registry overview, the air-gapped IP-allowlist guide, and the push example. Add an explicit "Network requirements" section listing the host:port pairs customers must allow.
8. Rate-limit and payload-size docs lack the response details developers need to handle them (significant)
Location: /docs/integrations/api/
Problem: The page states a 1 MiB request size limit and rate limits of "5 requests per second," "60 requests per minute," and "2000 requests per hour" with "429 responses for violations." There is no documentation of: which of the three windows triggers first, whether headers like Retry-After or X-RateLimit-Remaining are returned, what status code the 1 MiB violation returns (413? 400?), or how to inspect remaining quota.
Consequence: SDK and integration authors cannot tell from the docs which window will trigger first or how to back off correctly. There is no documented Retry-After semantics, so naive retry loops written by humans or agents will hit 429 storms. The 1 MiB cap has no documented status code or response body, so error handling is guesswork.
The fix: Document the response headers returned with 429s, the precedence among the three windows, and the exact status code and response body for the 1 MiB cap. Add a short "handling rate limits" code snippet.
9. SDK is JavaScript-only, with no published OpenAPI spec to compensate (significant)
Location: /docs/integrations/sdk/ (cross-reference to /docs/integrations/api/)
Problem: The SDK page says: "Currently, only JavaScript/TypeScript is supported. The SDK is available on npm. Future language support is planned but not yet implemented." Combined with issue 3 (no OpenAPI spec is reachable from distr.sh), there is no path for a Python, Go, Java, or Ruby team to integrate other than hand-rolling HTTP calls against an API reference that is itself an empty Stoplight shell.
Consequence: The README's headline "REST API for programmatic management" is undermined for any team not on Node. Distr's CI/CD-integration value proposition is gated on a single language. Agents asked to generate, say, a Python deploy script have no canonical SDK to import and no schema to ground requests against.
The fix: Publish the OpenAPI spec at a stable URL on distr.sh (this also resolves issue 3) so non-JS users can generate clients. Until additional first-party SDKs ship, link prominently to the spec from /docs/integrations/sdk/ with a "non-JavaScript users start here" callout.
10. Pro feature flags silently reset on application restart for self-hosters (significant)
Location: /docs/self-hosting/feature-flags/
Problem: The page tells self-hosters to enable features with update organization set features ='{pre_post_scripts}' where id = 'your-organization-id';, then warns: "Distr Pro feature flags will be reset on application start and require the Distr Enterprise license." There is no list of which flags are "Pro" vs free, no explanation of what "reset" means (cleared? overwritten with defaults?), and no pointer to which constants in types.go are gated.
Consequence: A Community Edition self-hoster runs the SQL UPDATE, sees the feature work, then on the next restart the flag is silently wiped. The warning is on the same page as the SQL, but readers who skim past it have no second chance — there is no enumeration of which flags will survive a restart.
The fix: Enumerate the gated flags inline (don't just link types.go), explain the reset mechanism, and either return an error when an unlicensed instance tries to set a Pro flag or surface a warning banner in the UI. Cross-link this from /docs/subscription/ so prospective self-hosters see the constraint before choosing Community Edition.
11. Quick-start commands diverge between README and docs (significant)
Location: github.com/distr-sh/distr README vs /docs/self-hosting/kubernetes/ and /docs/agents/preflight-checks/
Problem: The README's Docker quick start uses docker-compose up -d (hyphenated, the v1 binary) and then registration at http://localhost:8080/register. The Pre-flight Checks page (/docs/agents/preflight-checks/) explicitly warns customers about "Docker version mismatches" with "docker compose v2 syntax." The docs never reconcile which form Distr itself requires. Separately, the Kubernetes self-hosting page enables postgresql.enabled=true and rustfs.enabled=true "for a quick testing setup" with no warning that these embedded dependencies are not production-grade — only a vague "revisit all available configuration values."
Consequence: A developer trying the README on a fresh Ubuntu 24.04 box (which ships only docker compose v2) gets docker-compose: command not found. A team that takes the Kubernetes one-liner to staging or production runs an embedded Postgres and an embedded object store with no persistence/backup guidance.
The fix: Update the README to docker compose up -d. Add an explicit "production checklist" callout to /docs/self-hosting/kubernetes/ listing the values that must be overridden (external Postgres, external S3, ingress, secret management) before any real deployment.
12. Two canonical URLs serve the same GitHub Actions guide (minor)
Location: /docs/guides/automatic-deployments-from-github/ and /docs/integrations/github-actions/
Problem: Both URLs serve the same content (Step 6: Create the GitHub Actions Workflow at .github/workflows/push-distr.yaml), and /docs/guides/automatic-deployments-from-github/ is not redirected — unlike every other /docs/guides/... URL in the site. The Pre-flight Checks "Next Steps" section links to /docs/integrations/github-actions/, but the live sidebar still links to /docs/guides/automatic-deployments-from-github/.
Consequence: Duplicate canonical URLs split SEO weight and let two copies of the same doc drift independently. Agents indexing both will treat them as separate sources and may surface contradictory steps if either is edited.
The fix: Pick one canonical (presumably /docs/integrations/github-actions/), 301-redirect the other, and add a <link rel="canonical"> to be safe.
13. Helm imagePullSecrets injection only works for arrays — not flagged as a breaking constraint (significant)
Location: /docs/guides/helm-chart-registry-auth/
Problem: The "After injection" example shows myAppDb.imagePullSecrets: {} left untouched (because it is a map, not a list), while myAppServer.imagePullSecrets: [] and image.pullSecrets: [] are populated. The page states "The agent injects credentials into array-type fields but leaves non-array structures unchanged" — but this is a one-line aside, not a callout, and there is no validation step that warns the chart author.
Consequence: A Helm chart author whose subchart uses a map-shaped imagePullSecrets (a common Bitnami / community-chart pattern) silently does not receive credentials. Pulls fail in the customer environment with ImagePullBackOff, and the failure mode is far from the actual cause (a values.yaml shape).
The fix: Promote the array-only constraint to a "Caution" callout matching the style used on other pages (e.g. application-links.md). Add a values-shape pre-flight check, or list the exact sub-paths that must be arrays, with example before/after for the most common third-party charts.
14. FAQs page has only three entries and ignores the most likely real questions (minor)
Location: /docs/faqs/
Problem: The FAQ contains only three questions: white-labeling, vendor/customer portal toggling, and macOS testing. Nothing on: how license keys work offline, that license keys cannot be revoked before expiry, what to do when a registry push hits the 1 MiB body limit, how the SDK handles rate limits, what <no value> means in an application link, why a deployment didn't pick up an updated env template, or that the registry login uses port 8585.
Consequence: The single highest-traffic support-deflection page contains nothing that would actually deflect support. New users hit the same problems in sequence and either email support or abandon.
The fix: Mine GitHub Discussions and support tickets for the top 15 actual questions and add them. At minimum, add FAQs for: env-template updates not propagating (already a Caution on the env-var page), <no value> in links, registry port 8585, license-key revocation, and Pro flag resets on Community Edition.
15. "Book Demo" CTA in the docs header points at /docs instead of a demo-booking flow (minor)
Location: Site header on /docs/ (and other doc pages)
Problem: The header CTA labeled "Book Demo" links to /docs — i.e. the docs root, not a demo-booking page or contact form. A user already reading the docs who clicks "Book Demo" stays on the same page they were already on.
Consequence: The single highest-intent conversion CTA on the docs surface is broken. Sales pipeline opportunities that originate from docs reading are dropped on the floor — visitors silently fail to book a demo because the button is a no-op.
The fix: Point "Book Demo" at the correct booking URL (Calendly, HubSpot meeting link, or a contact form). If the marketing team intentionally collapsed the demo flow into self-service onboarding, rename the CTA to match — don't keep a "Book Demo" label that goes nowhere.
16. Page title "Create a Application" is grammatically wrong and used as a sidebar entry (minor)
Location: /docs/guides/application/ (sidebar entry "Create a Application")
Problem: The page title and sidebar both read "Create a Application." This is a small thing in isolation, but it appears in the primary onboarding flow (Getting Started → Create a Application → Create a Deployment) and is the second item a new user clicks.
Consequence: First impression of polish on the onboarding path is poor; for non-native English readers the article mismatch can also be parsed as "Create a-application" (some compound).
The fix: Rename to "Create an Application" in the sidebar, page title, and the source file website/src/content/docs/docs/guides/application/....
What they do well
- License-key spec is concrete: claim names, reserved fields, a public-key endpoint, and a Node.js verification snippet using
jose. - Application Links page is honest about failure modes (
<no value>, "existing deployments are not automatically updated"). - Pre-flight Checks page gives a real, copy-pasteable bash example with a meaningful scenario (Docker CLI version), not a hello-world.
Top 3 recommendations
- Fix the navigation. Update header and sidebar templates so they stop emitting legacy
/docs/guides/...and/docs/product/...URLs that 301 — every primary nav click is currently a redirect. While you're in the header template, fix the "Book Demo" CTA so it actually books a demo. - Make the API discoverable. Publish an OpenAPI spec at a stable
distr.shURL, embed it on/docs/integrations/api/instead of bouncing to an empty Stoplight shell onapp.distr.sh, document both/api/v1and/api/public/v1base paths, and serve a realllms.txt(or 404) instead of 403. - Reconcile the contradictions and close the security gaps. Standardize "EdDSA / Ed25519" wording for license keys, document license-key non-revocability prominently (with a recommended
expstrategy), document the registry's non-standard port 8585 on every page that needs it (including the air-gapped allowlist), and make the Pro-flag reset on Community Edition impossible-to-miss in the self-hosting feature-flags page.