WorkOS Documentation Audit
The WorkOS docs are large and visually polished, but the foundation is leaking: a major SDK breaking change (v9 portal → adminPortal) hasn't been reflected in the corresponding product/reference pages, multiple advertised pages 404, the API authentication rule (HTTPS only) is contradicted by the Sessions JWKS instructions (plain http://), and the AuthKit API reference renders parameter blocks as empty ##### headings.
1. Admin Portal docs still use the deprecated workos.portal.generateLink() after the v9 rename (critical)
Location: /docs/admin-portal and /docs/reference/admin-portal/portal-link/generate
Problem: Both the product guide and the dedicated API-reference page show the same code:
const { link } = await workos.portal.generateLink({ ... });
But the Node SDK changelog at /docs/sdks/node lists v9.0.0 (April 21, 2026) as a BREAKING CHANGE: "rename portal to adminPortal (#1562)". The Python SDK v6.0.0 release on /docs/sdks/python similarly states client.portal has been renamed to client.admin_portal. Neither the Admin Portal guide nor the canonical endpoint reference has been updated.
Consequence: A developer who installed the SDK in the last several weeks will copy-paste the documented call and get an immediate runtime error (workos.portal is undefined). Agents in particular have no way to tell which form is current, because the page is structured authoritatively as "the" example.
The fix: Update every Admin Portal example to workos.adminPortal.generateLink(...) (and client.admin_portal... for Python), add a small "v9 rename" callout, and link the migration guide. Audit all other product pages for stale workos.portal references.
2. Sessions page tells developers to fetch JWKS over plain HTTP (critical)
Location: /docs/authkit/sessions
Problem: The Access Token section reads:
"The signing JWKS can be found at
http://api.workos.com/sso/jwks/<clientId>."
But /docs/reference/api-authentication states explicitly: "All API requests must be made over HTTPS. Any requests made over plain HTTP will fail."
Consequence: A developer who copies the JWKS URL verbatim into their jose setup is configuring a JWT verifier to trust public keys fetched over an unencrypted channel — a textbook MITM vector for forging access tokens. Even if the WorkOS server redirects to HTTPS, documenting http:// here is dangerous because some libraries pin URLs and skip redirects.
The fix: Change the JWKS URL on this page to https://api.workos.com/sso/jwks/<clientId> and add an explicit "must be HTTPS" note adjacent to it.
3. AuthKit API reference renders parameter/return blocks as empty ##### headings (critical)
Location: /docs/reference/authkit/authentication (and the /docs/reference/authkit parent — same pattern)
Problem: Every authentication method shown on the page (e.g., authenticateWithCode, authenticateWithPassword) renders like this:
#### `Parameters` `object`
#####
#####
#####
...
#### `Returns` `object`
#####
#####
There are no parameter names, types, or descriptions — just empty heading lines. The same hollow ##### blocks appear on /docs/reference/authkit for /user_management/cors_origins and /user_management/redirect_uris.
Consequence: The API reference for the headline product is effectively non-functional. Developers can't tell what fields exist; AI agents indexing the page learn nothing about the schema; and there is no OpenAPI fallback discoverable from /docs/llms.txt. Postman is mentioned as a collection but is not a substitute for reference docs.
The fix: Restore the parameter rendering (broken template / failed data-binding to the OpenAPI source). At minimum, expose the underlying schema JSON as a link from each method.
4. Multiple advertised pages 404, including high-traffic migration and SDK landing pages (critical)
Location: Several routes referenced from /docs/llms.txt, the SDK index, and in-page "Up next" navigation.
Problem: Confirmed 404s during audit (linked-but-missing):
/docs/sdks/kotlin— but/docs/sdksadvertises a Kotlin client/docs/sdks/authkit-react-native— referenced as React Native Expo integration/docs/deprecations— listed as a top-level section in the llms.txt index/docs/changelog— 404s;/changelogexists but renders only a list of dates with no body content/docs/authkit/migrate-from-auth0— primary migration target/docs/authkit/migrating— the literal "Up next" link on the AuthKit Branding page
Expected-but-missing (no real referrer, but the absence is conspicuous):
/docs/authkit/authentication-methods— a natural URL for AuthKit's marquee feature list (email/password, social, passkeys, MFA, magic auth, enterprise SSO).
Consequence: Devs migrating from Auth0 (the largest competitor) hit a dead page; the linked "Up next" from a real doc page goes nowhere; and the changelog is a black hole. Agents following llms.txt will index broken targets without any error signal.
The fix: Either restore these pages or remove their references everywhere (SDK index, llms.txt, Branding "Up next", integration cards). Add a 404 → redirect rule for renamed pages such as /docs/reference/user-management → /docs/reference/authkit, which currently works but isn't applied to peer routes.
5. Errors reference is a six-line table with no error codes or example bodies (critical)
Location: /docs/reference/errors
Problem: The entire Errors page is one-line summaries of HTTP status codes — 400, 401, 403, 404, 422, 429, 5xx — and bizarrely includes 200 ("Successful request") in the list of errors. There are no error_code strings, no example error response bodies, no schema, and no list of domain-specific codes despite an event payload elsewhere (/docs/events) showing error.code: "invalid_one_time_code".
Consequence: Developers writing error handling have nothing to map against. An agent generating a try/catch path has no enum to switch on. Compare to the MFA error referenced in /docs/mfa ("Expired Error") — the codes exist in practice but are not catalogued anywhere.
The fix: Replace the page with (a) an HTTP status reference, (b) a full error_code table grouped by product (AuthKit, SSO, DSync, MFA, FGA…), and (c) one example error body per status. Remove 200 from the Errors enumeration.
6. Idempotency is required by Audit Logs but has no canonical reference page (significant)
Location: /docs/audit-logs (Idempotency section) → /docs/reference/idempotency
Problem: Audit Logs tells developers to send an idempotency-key header and describes hashing behavior. The natural canonical reference URL /docs/reference/idempotency silently redirects to /docs/reference (the API reference root) — there is no page documenting the header format, retry semantics, key TTL, max length, or scope.
Consequence: Developers integrating audit logs at scale have one product-specific paragraph and no platform-level contract. Other endpoints may or may not honor the header — nobody knows.
The fix: Publish a real /docs/reference/idempotency page that defines the header name, key format, dedup window, scope (per-endpoint vs. global), and example. Link it from every endpoint that supports it.
7. Rate limits page mixes units across rows without explaining how they compose (significant)
Location: /docs/reference/rate-limits
Problem: The "General" table says "All requests" → 6,000 requests per 60 seconds per IP address. The "AuthKit" table then introduces /user_management/* reads at 1,000 requests per 10 seconds — which is 6,000/minute per environment, not per IP. Hosted AuthKit adds yet more axes (per IP, per email+IP, per email+challenge ID). No row explains precedence when multiple limits apply, or which limit returns 429 first.
Consequence: Developers can't predict throttling. A high-traffic AuthKit tenant could plausibly hit the 6,000/min/IP cap before the 6,000/min/environment cap on the same endpoint, and the docs give no guidance. Agents writing retry/backoff logic have no canonical bucket to honor.
The fix: Add a header column showing scope (per IP / per environment / per email / per user / per challenge ID) and a short note describing precedence when multiple limits intersect. Document what header is returned with the 429 so clients can read Retry-After.
8. Standalone-SSO → AuthKit migration code diff is syntactically broken (significant)
Location: /docs/migrate-standalone-sso
Problem: Step 1's "before/after" code block shows both lines stacked inside a single function body without diff markers:
const authorizationUrl = workos.sso.getAuthorizationUrl({
organization,
const authorizationUrl = workos.userManagement.getAuthorizationUrl({
organizationId: organization,
...
That's not valid JavaScript — it's an unannotated overlay of "old" and "new." Step 2 (getProfileAndToken → authenticateWithCode) has the same problem.
Consequence: Copy-pasting this code straight into an editor produces parse errors. Agents extracting the snippet to scaffold a migration will produce broken PRs. The page advertises itself as a migration guide — the one thing that has to be unambiguous.
The fix: Render side-by-side diff code fences (- old line / + new line) or split into two complete, runnable "before" and "after" examples.
9. Email page contradicts itself on Resend suppression support (significant)
Location: /docs/email (Section E, "Check suppression status")
Problem: Two consecutive paragraphs say opposite things:
"Suppression management is available when using the default WorkOS email provider or a custom email provider."
"Resend is not currently supported for suppression management. If you use Resend as your custom email provider, manage suppressions directly in the Resend dashboard."
Resend is explicitly a custom email provider on this page, so the second sentence directly contradicts the first.
Consequence: Customers on Resend who read the first sentence build their suppression-handling assuming WorkOS will manage it; they will discover at integration time (or worse, in production) that they need to call the Resend dashboard manually. Agents reasoning about which provider supports which capability will pick whichever sentence they saw first.
The fix: Rewrite as a single statement with the exclusion in line: "Suppression management is available when using the default WorkOS email provider or any custom email provider except Resend — for Resend, manage suppressions directly in the Resend dashboard."
10. SSO Launch Checklist references "Allow Profiles Outside Organization" without saying whether it still exists (significant)
Location: /docs/sso/launch-checklist
Problem: The page documents the "Allow Profiles Outside Organization" toggle and its security implications as an active part of the launch checklist. The current Node SDK release log on /docs/sdks/node shows the SDK has moved to v9.0.0 with breaking changes around Organizations, and the checklist itself does not say whether this toggle is still surfaced in the dashboard, still SDK-manageable, or has been retired. The reader is left to guess.
Consequence: A developer running this checklist sees a security-sensitive question ("should I enable this?") with no current grounding — they don't know whether to look for the toggle in the dashboard, in the SDK, or to skip the section. The launch checklist is exactly the page that needs to be unambiguous about current behavior.
The fix: Either confirm the toggle is still part of the current configuration model (and link to where it lives in the dashboard or SDK), or, if retired, replace the section with a short migration note explaining what replaced it.
11. auth.md and pricing.md (the agent-facing handouts) have broken markdown escaping (significant)
Location: /auth.md, /pricing.md
Problem: The agent-targeted Markdown files contain escaped backslashes throughout the body:
\*\*WorkOS one-shot environments\*\*(literal backslashes around what should be bold)\*\*user claimed\*\*- Pricing table footnotes render as
\\*(double-escaped) and the footnote source line is\\*Business hours are…
Consequence: These files exist specifically for AI agents per the llms.txt index. Agents will ingest literal \*\* tokens as content, not formatting — corrupting any quoting back to a user. The pricing footnote markers don't bind to anything because \\* doesn't match * references.
The fix: Regenerate the .md exports without the additional escape pass. These files are pre-rendered Markdown; they don't need backslash-escaped asterisks.
12. Changelog page has no body content and SDK changelogs contain date typos (significant)
Location: /changelog, /docs/sdks/node, /docs/sdks/authkit-nextjs, /docs/events
Problem: /changelog renders only as a list of dates ("April 22, 2026", "April 21, 2026", …) with zero descriptions when scraped. The Node SDK page has v7.79.3 dated January 2, 2026 followed by v7.79.2 and v7.79.0 dated December 30, 2026 — i.e., later than the next entry. The AuthKit Next.js page mixes 2026 versions (v2.17.0) and 2025 versions (v2.3.0 on April 7, 2025) on the same scrolling list, and the events catalog stamps payloads with 2025-12-24 and 2023-11-18 side by side (the source labelling notes 2022 dates also appear).
Consequence: Developers can't determine which release fixed a given bug. The primary /changelog URL is effectively empty content for any agent crawl. Date typos in the Node changelog suggest copy-paste errors that no one has caught.
The fix: Fix the /changelog rendering (likely JS-hydrated content not server-rendered). Audit Node SDK changelog dates around v7.79.0–v7.79.3. Decide whether the events page should show normalized recent timestamps or genuine first-emitted timestamps and label accordingly.
13. AuthKit getting-started page is one paragraph and two install lines (significant)
Location: /docs/authkit
Problem: AuthKit is the marquee product (free up to 1M MAU per llms.txt), but the "Get started with AuthKit" landing page is only:
"Set up AuthKit in your app using either the AI Installer & CLI or a manual framework-specific guide."
Followed by npx workos@latest and a "framework-specific guide" link. There is no narrative for what AuthKit is, what gets provisioned, what the dev needs to set up first, or what to expect after running the CLI.
Consequence: A first-time developer either runs an unfamiliar CLI blind or scrolls back to find conceptual docs. AI installers being a primary recommendation makes the lack of context worse for agents, who can't reason about side effects.
The fix: Add 6–10 lines of "what AuthKit installs and changes," a prerequisites list, a "what to expect" section after npx workos@latest, and a short manual-quickstart that doesn't require the CLI.
14. Widgets landing and Quick Start are thinner than the feature warrants (significant)
Location: /docs/widgets, /docs/widgets/quick-start
Problem: /docs/widgets is a title, one sentence ("Learn how to integrate WorkOS Widgets in your app"), and a two-sentence intro before linking out to Quick Start. The Quick Start jumps straight to:
"The
WorkOsWidgetscomponent should be rendered at the top of your application. This component is responsible for managing context for data fetching and providing theme styles to all Widgets."
There is no explanation of what context is being managed, what data is being fetched, where theme styles come from, or how WorkOsWidgets relates to AuthKit sessions/tokens.
Consequence: Developers (and agents) have no mental model for what WorkOsWidgets is before being told to wrap their app in it. The "providing theme styles" and "managing context for data fetching" phrases are unanchored — there's no link to the data layer or the theming page.
The fix: On /docs/widgets, add a "What Widgets are / what they wrap" section with a small architecture sketch. On Quick Start, anchor the WorkOsWidgets root component to specific pages: the auth context it consumes, the theme/styling page, and the CORS prerequisite.
15. FGA and RBAC both exist with no decision guide, and FGA's marquee performance number has no scope (significant)
Location: /docs/rbac, /docs/fga
Problem: Both pages describe authorization products with similar conceptual surfaces (roles, permissions, IdP integration). FGA's landing page lists "User groups," "IdP role assignment for sub-resources," and "Permission assignment" as "coming soon" — three features that RBAC arguably already provides. Neither page links to the other or explains when to pick which.
Separately, FGA's landing page states "Sub-50ms p95" access checks as a bulleted feature with no qualifier — no endpoint scope, no region, no cache-warm vs. cold-start distinction, no SLA backing. It sits next to "Edge caches for low-latency global access (coming soon)," which implies the sub-50ms number is not an edge-cached p95.
Consequence: Buyers (and AI agents researching the right product to integrate) can't tell which one to choose. The "coming soon" list on FGA reads as "FGA will eventually catch up to RBAC," but nothing on either page actually frames the choice. The unqualified "Sub-50ms p95" line is the kind of performance promise that comes back at procurement time.
The fix: Add a comparison block — RBAC if: …, FGA if: … — at the top of both pages, with concrete examples (e.g., "RBAC for org-scoped roles tied to SSO; FGA for resource-relationship permissions like Google-Drive-style sharing"). Qualify the sub-50ms claim with endpoint scope, region, and warm-cache conditions, or move it to a benchmarks page that does.
16. Glossary alphabetization is broken and miscategorizes entries (minor)
Location: /docs/glossary
Problem: The alphabet jumps J → O, skipping K, L, M, N entirely. Under O, the first entry is "Sign-out redirect" (an S term), followed by actual O entries (OAuth 2.0, OIDC).
Consequence: Glossary lookups by letter fail; users searching "MFA" or "Magic Auth" don't see them where alphabetization would put them, and the "Sign-out redirect" item is unfindable for anyone scanning for S.
The fix: Restore K/L/M/N sections (or hide letters that have no entries), and move "Sign-out redirect" into S.
17. Integrations directory has no alias for "Azure AD" (minor)
Location: /docs/integrations
Problem: Microsoft Entra ID entries are listed under E ("Entra ID OIDC (formerly Azure AD)" etc.). There is no Azure AD entry under A that redirects users searching for the old, still-widely-used name. The integrations page also visibly skips Q, T, U, Y, Z without indicating whether this is filtered or simply empty.
Consequence: A developer or agent searching "Azure AD" sees nothing under A and may assume Entra is not supported. Skipped alphabet letters with no marker create uncertainty about whether the list is complete.
The fix: Add an "Azure AD" alias entry under A that redirects/anchors to the Entra ID section. Either show empty-letter headers explicitly or remove letter dividers entirely.
18. Custom Domains landing page is four bullets for a paid feature (minor)
Location: /docs/custom-domains
Problem: The page promises configurable custom domains for Email, AuthKit, Admin Portal, and Authentication API — and then ends. There is no DNS prerequisite, no SSL/cert behavior, no propagation timing, and no link to the four sub-configurations beyond Email.
Consequence: A customer paying for the feature has no operational guide on this landing page. They must hunt by URL for AuthKit/Admin Portal/Auth API custom domain pages.
The fix: Add a brief overview of certs/DNS, propagation expectations, and link cards to each of the four sub-pages.
19. Email page has a malformed DMARC DNS example (minor)
Location: /docs/email
Problem: The example DMARC record block renders the host name as _dmarc<.example.com> — angle brackets around the domain instead of a placeholder convention like _dmarc.example.com with example.com in a clearly marked variable.
Consequence: Copy-pasting the example into a DNS provider yields an invalid record. Agents producing Terraform/DNS config will reproduce the broken syntax.
The fix: Replace <.example.com> with a clearly labeled placeholder (e.g., _dmarc.{your-domain} plus a note "Replace {your-domain} with your actual domain"). Same applies elsewhere on the page.
20. Directory Sync page leaks image alt-text into the body copy (minor)
Location: /docs/directory-sync
Problem: Three concept blocks (Directory, Directory group, Directory user) lead with raw alt-text like "A diagram showing that directory providers relay the directory data to WorkOS, then WorkOS normalizes the data, and then your app interfaces with WorkOS, receiving and pushing updates" before the actual explanatory paragraph.
Consequence: It reads as if a diagram description got pasted as body text. Screen-reader users hear the description twice; sighted users hit a wall of nearly identical sentences.
The fix: Move alt text into the alt= attribute of the image only and let the diagram speak visually.
21. SSO test-SSO page buries a critical pre-condition (minor)
Location: /docs/sso/test-sso
Problem: Under "Identity provider-initiated SSO," the line "Ensure AuthKit is disabled before testing" appears as a single sentence with no callout styling and no link to where the toggle lives.
Consequence: Developers attempting an IdP-initiated test with AuthKit enabled get an unexplained failure. Agents scaffolding tests miss the precondition entirely.
The fix: Promote the line to a warning callout, link directly to the AuthKit toggle in the dashboard, and explain why it matters.
22. /docs/sso vs /docs/sso/index duplicate route with a self-looping "Up next" (minor)
Location: /docs/sso, /docs/sso/index
Problem: Both URLs render the same content, but the "Up next" footer differs: on /docs/sso it points to "Test SSO"; on /docs/sso/index it points back to itself ("Single Sign-On"). The duplicate URL creates an SEO/crawl problem and the self-link in the index variant traps navigation.
Consequence: Agents and search engines see two canonical-looking URLs with subtly different navigation. Users on the /index variant can't progress.
The fix: Pick one canonical URL, 301-redirect the other, and ensure "Up next" always points forward.
What they do well
llms.txtexists with a clear product summary and a comprehensive sitemap-style index — agents have a real entry point.- AuthKit framework-specific SDKs are first-class (Next.js, Remix, React Router, TanStack, React, JS) with active version histories.
- Rate limit numbers are stated for many endpoint groups — the data is there even when the presentation needs work (#7).
Top 3 recommendations
- Sync the docs to v9/v6 SDK reality. Sweep every page for
workos.portal.*and replace withworkos.adminPortal.*(and Pythonclient.admin_portal); add a visible v9/v6 migration banner on/docs/admin-portaland the Node/Python SDK index pages. Restore the dead migration pages (/docs/authkit/migrate-from-auth0,/docs/authkit/migrating) or remove their inbound links. - Fix the broken reference rendering. The AuthKit API reference rendering empty
#####parameter blocks and the Errors page conflating 200 with errors are both showstoppers for agents and humans. Restore the parameter/return data binding and publish a realerror_codetable grouped by product, plus a canonical/docs/reference/idempotencypage. - Audit the public surface for HTTPS, copy-paste, and contradictions. Change the JWKS URL on
/docs/authkit/sessionstohttps://, fix the syntactically-broken diff blocks on/docs/migrate-standalone-sso, resolve the Resend suppression-management contradiction on/docs/email, fix the empty/changelogrendering and date typos in the SDK changelogs, and unbreak the markdown-escaping in/auth.mdand/pricing.mdso the agent-facing handouts render correctly.