Kota Developer Products Documentation Audit
Kota ships a genuinely broad set of developer docs — Hosted, Embed, and API integration paths, an SDK reference, webhooks, contribution reporting, an llms.txt index, and an MCP server — but the surface is riddled with copy-paste-breaking code bugs (an inverted key-prefix convention, an invalid-JSON error example, an SDK casing/enum/handler mismatch, a wrong-host typo), truncated/duplicated webhook content, two undocumented parallel webhook catalogs, broken navigation cards on the first pages a developer hits, and a systematic wrong-anchor pattern in the generated API specs. The result is a docs set that looks complete but quietly fails the developer (and the agent) at exactly the points where they copy code or follow a link.
1. pk_ keys are labeled "secret keys" — inverted from the universal publishable/secret convention (critical)
Location: /core-components/authentication (also restated on /api-reference)
Problem: The Authentication page states verbatim: "Test mode secret keys have the prefix pk_test_ and live mode secret keys have the prefix pk_live_." The API Introduction page restates the same pk_ = "secret key" convention. Across the industry (Stripe being the canonical example this docs set otherwise mirrors), the pk_ prefix denotes a publishable key safe for client-side use, and sk_ denotes a secret key. Here pk_-prefixed keys are described as secrets you must "never share them in publicly accessible areas such as GitHub or client-side code."
Consequence: A developer or AI agent that has internalized the pk_/sk_ convention will mentally classify these as client-safe publishable keys and may legitimately expose them in frontend code or commits — the exact leak the warning is trying to prevent. Either the prefix is wrong or the label is wrong; both readings are dangerous, and the contradiction sits in the security-critical auth flow.
The fix: Decide which is true. If these really are secret server-side keys, prefix them sk_test_/sk_live_. If the pk_ prefix is intentional, stop calling them "secret keys" and document the matching sk_ secret key. Either way, make the prefix and the secrecy classification agree, and say so on both the Authentication and API Introduction pages.
2. The 429 "Too Many Requests" JSON error example has a trailing comma — it is invalid JSON (critical)
Location: /api-reference/errors
Problem: The "Too Many Requests" (429) example object on the Errors page includes a trailing comma after the "trace_id" field. JSON does not permit trailing commas, so the example as printed will not parse. (The full JSON block was not fully captured in the page excerpt — confirm against the live page before publishing the fix, but the malformed-comma pattern is documented in the scraped errors content.)
Consequence: A developer building error handling who copies this payload into a test fixture, a parser, or a mock will get a parse failure — and they'll debug their own code first, not suspect the docs. An AI agent generating error-handling code from this example will reproduce malformed JSON verbatim.
The fix: Remove the trailing comma. Add a JSON-lint step to the docs build so every fenced ```json block is validated at publish time (this would also catch issue #11).
3. Webhook intro sentence is truncated mid-event-name — and the broken text is published on two separate pages (critical)
Location: /core-components/webhooks-and-events and /api-reference/events-and-webhooks
Problem: Both pages contain the identical sentence describing opt-out policies: "...you will receive both a employee.health_insurance.policy.created event and an employee.health_insurance.policy." — the second event name is cut off entirely, mid-token. The same truncation appears verbatim on both pages (the second page is content-duplicated from the first).
Consequence: A developer wiring up opt-out handling cannot tell which second event to subscribe to. They will miss or mishandle one of the two events a single action emits. Because it's duplicated, "check the other page" yields the same broken sentence.
The fix: Complete the sentence with the actual second event name, fix it in the single source, and de-duplicate so the two pages share content instead of drifting independently.
4. Two parallel webhook catalogs (V1 and V2) with conflicting envelope field names and no version-selection guidance (critical)
Location: /core-components/webhooks-and-events/working-with-events; /api-reference/events-and-webhooks/types-of-events-v-2/employee-created-v-2-webhook; /llms.txt
Problem: The llms.txt index lists two full webhook catalogs — "Types of Events" (V1) and "Types of Events V2" — confirming both are published. "Working with Events" documents the V1 envelope with a field api_version set to "1.0" and states "Our API currently uses version 1.0." The V2 employee.created webhook spec instead uses a required field named version (not api_version), and its data object (EmployeeEventResponse) is a thinner shape. Nothing documents what V2 is, how it differs from V1, how a consumer chooses which version they receive, or whether there's a migration path.
Consequence: A developer cannot tell which catalog applies to them. Code that reads api_version breaks against V2 payloads (the field is version); code that expects the rich V1 data shape breaks against V2's minimal data. With registration handled manually over email/Slack, the consumer has no self-serve way to even know which version their endpoint is configured for.
The fix: Add a single page that explains V1 vs V2: the envelope field-name change (api_version → version), the payload-shape differences, which is the default, how to opt into V2, and the migration path. Stop asserting "currently uses version 1.0" while a V2 catalog with a different schema is published.
5. The .policy.created event referenced in prose is not among the documented event types (significant)
Location: /core-components/webhooks-and-events and /api-reference/events-and-webhooks
Problem: The intro on both pages references employee.health_insurance.policy.created as an event consumers "will receive." The published policy events referenced elsewhere are .policy.activated, .policy.cancelled, and .policy.expired — .policy.created does not appear among them. (The full V1/V2 "Types of Events" catalog pages were not directly captured in the scrape; confirm the absence of a .policy.created entry against both catalogs before finalizing.)
Consequence: A developer who registers a handler for employee.health_insurance.policy.created, following the prose, may register for an event that is never delivered. The policy-creation flow they think they've handled silently does nothing — and webhooks failing silently is the worst failure mode because nothing errors.
The fix: Reconcile the prose with the catalog. Either document .policy.created as a real event in both V1 and V2 catalogs, or correct the prose to name the events that actually fire (.activated, etc.).
6. Dead link: "plan your integration" → /get-started/api-basics returns 404 (significant)
Location: /api-reference (Introduction → "Scoping out an integration?")
Problem: The page invites readers to "plan your integration." That URL returns "Page Not Found." (Because the Mintlify host returns HTTP 200 for any non-existent .md path, a naive link checker won't catch this — the rendered page is a soft 404.)
Consequence: The single CTA aimed at developers in the scoping/evaluation phase — a high-intent moment — drops them on a 404. There's no fallback path to the planning material it promises.
The fix: Point the link at the real planning page (the API Integration Overview at /api, or wherever the basics now live) and add link-checking that follows rendered content, not just HTTP status, given the host's soft-404 behavior.
7. SDK reference contradicts itself on method casing and call signature (significant)
Location: /embed/sdk-reference
Problem: The method heading and every code sample use lowercase Kota.Health.employer(...) / const employerEmbed = Kota.Health.employer();, but the prose immediately below says "Use Kota.Health.Employer(employerAccessToken, options?)" with a capital Employer (and likewise capital Employee). Separately, the documented signature lists employerAccessToken as Required, yet the worked example calls Kota.Health.employer() with no arguments and then uses a separate, undocumented .init(employerAccessToken, {...}) method to pass the token.
Consequence: JavaScript is case-sensitive — a developer who copies the capitalized Kota.Health.Employer(...) from the prose gets undefined is not a function. A developer who follows the signature and passes the "required" token directly to employer(token) diverges from the example's .init() pattern. The reference can't be followed as written either way.
The fix: Pick one casing and one initialization pattern, then make the heading, prose, signature, and example all match it. If .init() is the real entry point, document it as the signature and mark the token as supplied there, not in the constructor.
8. SDK dependants_cover example capitalizes an enum value the schema defines as lowercase (significant)
Location: /embed/sdk-reference
Problem: The dependants_cover option is described with one enum value capitalized as Full, while the documented default and allowed values are lowercase full. The two spellings refer to the same value, and the capitalized one appears in copyable example context.
Consequence: Enum validation is case-sensitive in most SDKs. A developer who copies the Full form sends a value the API doesn't recognize and gets a validation rejection — for a field that gates how dependants are covered. It's the same copy-paste-breaking class as the casing bug in issue #7, just on a value instead of a method.
The fix: Normalize the example to the schema's lowercase full, and add a check that example enum values are drawn from the documented allowed set.
9. SDK event-handler examples carry a wrong comment and omit the required namespace prefix (significant)
Location: /embed/sdk-reference
Problem: In the event-handler examples, the Employee pageLoaded snippet's comment reads "// Handle employer pageLoaded event" (it is the employee example), and the generic window.addEventListener('eventName', handler) example omits the required employer./employee. namespace prefix that real event names use elsewhere on the page.
Consequence: A developer who copies the generic addEventListener('eventName', ...) shape registers a listener that never fires, because the real events are namespaced (employer.… / employee.…). The mislabeled comment compounds the confusion about which role each handler belongs to. Both are exactly the copy-paste-then-debug-your-own-code trap.
The fix: Correct the Employee example's comment, and make the generic handler example use a real, namespaced event name (or clearly mark eventName as a placeholder for a fully-qualified role.resource.action string).
10. Theming docs mandate rem; the SDK example uses px — and the two pages disagree on the init call (significant)
Location: /embed/theming and /embed/sdk-reference
Problem: Theming states the theme object "should use rem for the 'radius'" and "Use rem units." The SDK Reference theme example uses radius: '12px'. Additionally, Theming shows initialization as Health.employer.init() while the SDK Reference shows Kota.Health.employer() followed by .init() — a different namespace/notation for the same call.
Consequence: A developer copying radius: '12px' from the SDK Reference may hit a validation rejection or a silently ignored value if the platform enforces the rem requirement. The mismatched init notation compounds the confusion from issue #7 about how the SDK is actually invoked.
The fix: Make the SDK Reference example use rem (e.g. radius: '0.75rem'), or relax the Theming requirement to allow px and document both. Standardize the init notation across both pages.
11. Theming theme object is shown in a ```json block but is actually a TypeScript type definition (significant)
Location: /embed/theming
Problem: The theme object is presented inside a ```json fenced block, but its contents are a TypeScript-style type definition: unquoted keys, string; type annotations, and semicolons. That is not valid JSON.
Consequence: An agent or developer who treats the block as JSON config (which the fence label invites) will fail to parse it and won't know whether the field values are real defaults or type placeholders. It reads as a copy-pasteable config but isn't one.
The fix: Either label the block ```typescript and present it clearly as a type/interface, or provide a genuine JSON example with real default values. Don't label a type definition as JSON.
12. Hosted session example uses test-api.kota.io; every other example uses test.api.kota.io (significant)
Location: /hosted/server-side
Problem: The Hosted session cURL targets https://test-api.kota.io/hosted/sessions (hyphenated test-api). Every other example across the docs — including the per-endpoint specs like Create Employer (https://test.api.kota.io/employers) — uses the dotted https://test.api.kota.io. This is the only occurrence of the hyphenated host in the docs.
Consequence: test-api.kota.io and test.api.kota.io are different hostnames and will resolve differently; the hyphenated one likely fails DNS or hits the wrong endpoint. A developer copying the Hosted example gets a connection error and has no way to know the host is a typo rather than a real Hosted-specific domain.
The fix: Correct the Hosted example to https://test.api.kota.io/hosted/sessions (or, if Hosted genuinely uses a different host, document that explicitly and consistently). Add the base URL to a single shared variable so a typo can't diverge in one example.
13. Idempotency guidance demands "at least 30 characters of entropy" but the worked example is 16 characters (significant)
Location: /api-reference (and contrast with /core-components/authentication)
Problem: The API Introduction says idempotency keys should have "at least 30 characters of entropy," yet its own worked example key KG5LxwFBepaKHyUD is only 16 characters. Separately, the Authentication page's idempotency example uses a UUID, while the API Introduction uses this 16-char non-UUID string — two different formats for the same concept.
Consequence: A developer following the example produces keys that violate the stated requirement; one following the rule rejects the example as wrong. The format inconsistency (UUID vs short string) leaves them guessing what shape the API actually expects, which matters because idempotency keys gate safe retries of money-moving operations.
The fix: Make the example satisfy the rule (use a UUID or a ≥30-char token) and standardize one recommended format across the Authentication and API Introduction pages.
14. Systematic wrong-anchor pattern: /api#errors, /api#idempotent-requests point to a page that has no such sections (significant)
Location: /api-reference/errors, /api-reference/employers/create-employer (and the generated specs generally)
Problem: Errors.md links "the API introduction" to /api#errors, and the Create Employer spec links its Idempotency-Key description to /api#idempotent-requests. But /api is the "API Integration Overview" page, which has no Errors or Idempotent-requests section — those sections live on /api-reference (titled "API Introduction"). The wrong-anchor target repeats across the auto-generated endpoint specs.
Consequence: Readers (and agents resolving cross-references) land on a page that doesn't contain the anchor, with no error and no redirect — the browser just sits at the top of an unrelated overview. Because it's systematic in the generated specs, it's not one broken link but a pattern across the reference.
The fix: Fix the anchor base in the spec generator so error/idempotency links resolve to /api-reference#.... Then resolve the underlying naming collision in issue #15 so /api vs /api-reference stops being ambiguous.
15. Two different pages titled "API Introduction" (/api-reference) and "API Integration Overview" (/api) are cross-linked as "/api" ambiguously (significant)
Location: /api (API Integration Overview) and /api-reference (API Introduction)
Problem: /api-reference is titled "API Introduction"; /api is a distinct page titled "API Integration Overview." Other pages link to /api as though it contains the Introduction's content (Errors, Idempotent requests), and the Requirements guidance links the "requirements API" to the generic index rather than the specific Requirements endpoints. The two near-identically-named pages are referenced interchangeably.
Consequence: Both readers and agents cannot reliably tell which page is canonical for a given topic, so cross-references (issue #14) land on the wrong one. The brand naming compounds this: the docs variously call the product "Kota Developer Products," "Kota Embed," and "Kota Embedded" — agents indexing by product name can't tell whether these are one product or three.
The fix: Rename one of the two pages so the titles are unambiguous, pick a single canonical home for Errors/Idempotency, and standardize the product name (Embed vs Embedded) across all pages.
16. "View Employer/Employee API documentation" links point to the list endpoints, not the create endpoints they sit beside (significant)
Location: /core-components/employer-employee-management
Problem: The "Create an Employer" section (which documents a single POST request) labels its reference link "[View Employer API documentation]" but points to /api-reference/employers/list-employers. The "Create an Employee" section does the same, pointing to /list-employees. Both create sections link to the list endpoints.
Consequence: A developer following the obvious "view the API docs for what I'm doing" link from the Create section lands on the List endpoint reference — wrong method, wrong parameters, wrong response. They'll either copy the wrong call or bounce around the reference hunting for the create spec.
The fix: Point each section's reference link at the matching endpoint: create-employer and create-employee.
17. Per-endpoint generated specs document only 200/400; global 401/403/429/500 are absent — and a .NET class name leaks into the schema (significant)
Location: /api-reference/employers/create-employer (representative of the generated specs)
Problem: The Create Employer spec documents only 200 and 400 responses. The 401, 403, 429, and 500 responses that the API Introduction documents globally are missing from the per-endpoint spec. Additionally the 400 response uses the schema name HttpValidationProblemDetails — an ASP.NET-framework class name leaking into public-facing docs. (Confirm the response set and schema name against the live generated spec; the page body was captured only down to the endpoint header.)
Consequence: A developer (or an agent generating a typed client) from this endpoint's spec won't handle auth failures (401/403), rate limiting (429), or server errors (500) for this operation, because the machine-readable spec says they can't happen. The leaked .NET class name signals the spec is auto-emitted from internal types rather than curated, undermining trust in the schema names.
The fix: Include the global error responses (401/403/429/500) in every endpoint's response set, and rename HttpValidationProblemDetails to a neutral, documented schema name (e.g. ValidationProblemDetails per RFC 9457).
18. Webhook cross-references are inconsistent — different paths for the same guide, and the same example links to V1 on one page and V2 on another (significant)
Location: /hosted/server-side and /embed/server-side
Problem: The Hosted server-side page links "Working with webhooks" to /api-reference/events-and-webhooks/working-with-webhooks, a different path than the canonical /core-components/webhooks-and-events/working-with-events used elsewhere. And the two near-identical server-side pages link the same action_required reason example to different catalogs: Hosted points to the V2 event path, while Embed points to the V1 path.
Consequence: The diverging "Working with webhooks" path is a likely broken/duplicate link, and steering Hosted vs Embed integrators to different catalog versions for the identical event reinforces the V1/V2 confusion in issue #4 — a developer comparing the two integration guides gets two different answers for the same payload.
The fix: Standardize on one canonical "Working with Events" path and one catalog version per example across the Hosted and Embed server-side pages, and verify the /api-reference/events-and-webhooks/working-with-webhooks link resolves.
19. No changelog or API version history, despite the docs explicitly relying on API versioning (significant)
Location: /changelog (404) and site-wide
Problem: /changelog returns HTTP 404 and there is no release-notes or version-history page anywhere in the docs or the llms.txt index. Meanwhile the docs explicitly discuss versioning — "Our API currently uses version 1.0... monitoring our announcements and documentation for new versions" — and maintain parallel V1/V2 webhook catalogs (issue #4).
Consequence: A developer told to "monitor our announcements and documentation for new versions" has nowhere to monitor. With V1 and V2 webhook schemas already diverging, there's no record of what changed, when, or what action consumers must take — so version migrations happen blind.
The fix: Publish a changelog/release-notes page (and list it in llms.txt), starting with the V1→V2 webhook changes. Link it from the versioning prose.
20. Broken navigation cards on the first two pages a developer hits (significant)
Location: /getting-started (Overview) and /integration-types
Problem: The Overview "Next Steps" block ends in an orphaned sentence — "Understand employers, employees, groups, policies, platforms, and how they work together" — with no rendered link or card around it. On Integration Types, the "Core Integration Components" and "Choose Your Integration Approach" sections render as bare descriptive sentences ("Learn how to authenticate...", "Learn how to create and manage employers...") with no accompanying link text or cards. The components they describe (Authentication, Employer/Employee management, Contribution reporting, Webhooks) are named but not linked.
Consequence: These are the first two pages in the navigation — the primary onboarding path. The "next step" affordances that are supposed to route a new developer (or an agent crawling the entry pages) into the core components are dead text, so the intended journey from Overview → Key Concepts → integration components has no clickable on-ramp.
The fix: Restore the card/link components so each "Next Steps" and "Core Integration Components" item links to its target page, and add a render check that flags card blocks whose link text failed to populate.
21. Key Concepts links to Groups/Group Policies/Policies endpoints that are absent from the llms.txt API index (minor)
Location: /key-concepts and /llms.txt
Problem: Key Concepts links to /api-reference/groups/, /api-reference/group-policies, and /api-reference/policies. The llms.txt index that AI tooling relies on does not list these Groups/Group Policies/Policies endpoint families.
Consequence: An AI agent that indexes the docs via llms.txt (which the docs actively promote, alongside an MCP server) cannot discover the Groups/Policies endpoints even though human-facing prose treats them as core concepts. The agent's view of the API is incomplete in a way the human's isn't.
The fix: Add the Groups, Group Policies, and Policies reference pages to llms.txt so the machine index matches the human navigation.
22. Locale inconsistency splits machine-facing identifiers from human-facing prose (minor)
Location: /api (US "enrollment"), /llms.txt and webhook catalog (British "enrolment_intent"), /key-concepts ("dependants") vs /api ("dependents")
Problem: The human-facing API Integration guide uses US spellings ("enrollment/enroll"), while machine-facing identifiers use British spellings (enrolment_intent). Within a single page (/api) the docs mix "dependents" and "dependants"; Key Concepts uses "dependants."
Consequence: When the divergence lands in identifiers an agent must match on (enrolment_intent), prose-derived guesses using the US spelling (enrollment_intent) silently won't match — a class of bug that only bites at integration time and is hard to trace back to spelling.
The fix: Pick one locale for the documentation prose, freeze the wire-format spellings exactly as the API emits them, and call out any intentional prose/identifier spelling difference where it occurs.
23. Two reference pages have no H1 title (minor)
Location: /hosted and /core-components/contribution-reporting
Problem: Unlike every other doc page, the Hosted overview opens directly with body prose and the Contribution Reporting page opens with ## What are Contribution Reports? — neither has an H1. Contribution Reporting is also the largest page in the set (~46KB).
Consequence: Missing H1s degrade the page outline that screen readers, search indexes, and agents use to identify and rank a page's primary topic — the biggest content page in the docs has no top-level title for tooling to anchor on.
The fix: Add an H1 (e.g. "Kota Hosted" and "Contribution Reporting") to both pages, matching the convention used elsewhere.
24. Hosted is described as both "hosted by Kota" and running on "your own custom domain," with a stale "early 2026" availability date (minor)
Location: /hosted, /hosted/server-side, /integration-types
Problem: Integration Types describes Hosted as "a fully managed experience hosted by Kota," while the Hosted pages say users go to "a fully managed experience hosted on your own custom domain." Separately, both Hosted pages state "Employer setup and management flows are coming in early 2026" — a date now in the past (today is 2026-06-27) — yet the Hosted "Minimum requirements" still lists "Communicate required actions with Employers and their Employees" among employer-facing flows said to be unavailable.
Consequence: A developer evaluating Hosted can't tell whether the experience lives on Kota's domain or theirs (a material architecture/branding decision), and a past-due "coming in early 2026" date leaves them unsure whether employer flows have shipped or slipped.
The fix: Reconcile the hosting-domain description across both pages, replace the absolute "early 2026" date with a maintained status (or remove it), and align the Minimum-requirements list with which flows are actually available.
25. RFC 9457 type field reuses one generic URI for distinct problems, defeating its purpose (minor)
Location: /api-reference/errors
Problem: The error object conforms to RFC 9457 (Problem Details), whose type field is meant to "identify the problem type." But "Invalid request," "Invalid state," and the malformed-JSON case all reuse the identical generic URI rfc9110#section-15.5.1, so type does not actually distinguish one problem from another.
Consequence: A developer who follows the spec and branches error handling on type cannot differentiate these cases — they all collapse to the same URI — forcing them to fall back to fragile prose-message matching instead.
The fix: Assign distinct, stable type URIs per problem category (e.g. a kota.io/errors/invalid-request namespace), so type carries the discriminating signal RFC 9457 intends.
26. Non-standard code-fence info-strings won't syntax-highlight (minor)
Location: /core-components/employer-employee-management
Problem: Code blocks use heading-style fence info-strings such as ```cURL POST /employers rather than a recognized language identifier (bash, sh, json). A fence info-string is a language token, not a place for an HTTP method/path label.
Consequence: Renderers and agents that key off the fence language won't highlight these blocks and may misclassify the content; the POST /employers label is dropped on the floor instead of becoming a real caption. It's cosmetic, but it's systematic across the example blocks on this page.
The fix: Use a valid language token for the fence (e.g. ```bash) and move the POST /employers label into a heading or caption above the block.
What they do well
- Strong AI-agent affordances at the index level — an
llms.txtindex,.mdsuffix for clean Markdown, section-level/llms.txt, and an MCP server are documented up front; the intent to be agent-consumable is real (the gaps are in execution, not ambition). - Clear three-tier integration framing — the Hosted / Embed / API comparison table cleanly maps each approach to its audience, integration effort, and customization ceiling, which is exactly the orientation an evaluator needs.
- Genuinely structured domain model — Key Concepts and Contribution Reporting lay out the entity model (employers, employees, groups, policies) and ID-prefix conventions (
ctr_,ct_,er_,ee_,pt_,evt_) in a consistent, parseable way.
Top 3 recommendations
- Fix the copy-paste landmines first — the inverted
pk_"secret key" labeling (#1), the invalid-JSON 429 example (#2), thetest-apivstest.apihost typo (#12), and the SDK casing/enum/handler contradictions (#7, #8, #9). Add JSON-lint, enum-value validation, and case-sensitive link/host checks to the docs build so these can't reappear. - Resolve the webhook mess as one project — complete the truncated event name (#3), reconcile
.policy.createdagainst the catalog (#5), standardize the V1-vs-V2 cross-reference links (#18), and publish a single page that explains V1 vs V2 (api_versionvsversion, payload shapes, version selection, migration) plus a changelog (#4, #19). This is where silent failures concentrate. - Make navigation and cross-references trustworthy — restore the broken entry-page cards (#20), fix the
/api#...wrong-anchor pattern and the/get-started/api-basics404 (#6, #14), disambiguate "API Introduction" vs "API Integration Overview" (#15), point create-section links at create endpoints (#16), and add the missing Groups/Policies endpoints tollms.txt(#21) so both humans and agents can navigate without dead ends.