Apideck Documentation Audit
Apideck ships an unusually agent-friendly surface (llms.txt, llms-full.txt, per-page .md mirrors, an MCP server, a CLI, public OpenAPI specs) — but the pages those indexes point at have accumulated invalid JSON, broken deep links, a blank Base URL, a security coupling that's never called out, and an MCP coverage gap that contradicts the very index advertising it.
1. Two reference code blocks aren't valid JSON, and one of them silently teaches the wrong semantics (critical)
Location: /guides/pass-through.md and /guides/using-raw-mode.md.
Problem: The pass-through guide's "Removing Data" example contains a bare JavaScript identifier inside a fenced json block:
"extend_object": {
"baz": undefined
}
undefined is not a JSON token — no parser will accept it. Worse, pass-through is a wire-level feature: undefined cannot be serialized over HTTP, so the example also fails to specify what "delete this field" actually looks like on the wire (omit the key? send a sentinel? send null?). Separately, the "Using Raw Mode" payload is also broken JSON — the hs_object_id string is never terminated ("hs_object_id": "551 with no closing quote, followed by a } on the next line), and the results array carries a trailing comma after its sole object.
Consequence: A coding agent copy-pasting the pass-through block will "fix" undefined to null — which is precisely wrong, because null is a real value to pass through, not a delete signal. Non-JS developers (Python, Go, Java, PHP) have no documented way to express "delete" at all because the only example uses a JS-only sentinel. The raw-mode block fails any parser outright. The criticality here is driven by the semantic error in pass-through, not just the parse error.
The fix: In /guides/pass-through.md, document the on-the-wire deletion contract explicitly (e.g., "omit the key" or the real sentinel) and show language-specific encodings for JS/TS, Python, Go, and PHP. Replace the undefined example with valid JSON plus prose. Fix the truncated hs_object_id and the trailing comma in raw-mode. Add a CI lint that parses every fenced json block under /guides/*.
2. Webhook signing reuses the API key as the HMAC secret with no security note (critical)
Location: /guides/webhook-signature-verification.md.
Problem: "Creating a hash using HMAC SHA-256, with your API key as the secret key." The same API key authenticates outbound requests to Unify (per Getting Started). There is no callout warning that rotating the API key simultaneously invalidates pending webhook signatures, no recommendation to use a dedicated signing secret, and no rotation playbook.
Consequence: An operator who rotates the API key after a suspected leak — the case where rotation matters most — silently breaks signature verification on every in-flight delivery, with no path to dual-key the rotation. It is also a defense-in-depth weakness: leaking the API key leaks the webhook integrity boundary at the same time. There is no signal to either the developer or an LLM that these two concerns are coupled.
The fix: Either ship a dedicated webhook_signing_secret (recommended) or, at minimum, document the coupling: "Rotating your API key invalidates webhook signatures issued before the rotation. To rotate safely, pause delivery, rotate, then resume." Add a Security Considerations section to the webhook signing page.
3. MCP server covers 5 of 7 advertised Unified APIs, with no acknowledgment of the gap (critical)
Location: /mcp.md, /building-with-llms.md, /llms.txt.
Problem: The MCP page states "229 tools across 5 Unified APIs (CRM, Accounting, HRIS, ATS, File Storage)." The llms.txt index advertises 7 Unified APIs (those five plus E-commerce and Issue Tracking). Nowhere in mcp.md or building-with-llms.md is the gap acknowledged, dated, or roadmapped.
Consequence: An agent reading llms.txt sees "200+ services across CRM, Accounting, HRIS, ATS, File Storage, E-commerce, Issue Tracking" and assumes MCP covers all of it. It will plan tool calls for E-commerce or Issue Tracking that don't exist, then fail at execution. A human evaluating Apideck for an Issue Tracking integration via MCP only discovers the gap after starting to build.
The fix: In mcp.md, add an explicit "Coverage" section listing covered APIs and uncovered APIs ("E-commerce and Issue Tracking are not yet exposed via MCP — use the SDK or CLI for those"). Mirror this in building-with-llms.md and in the llms.txt entry for MCP.
4. get-started.md cross-references step anchors that don't match its own headings (significant)
Location: /get-started.md.
Problem: The "Step 4: Create a consumer" section says:
"Alternatively use the Vault URL generated in step 4."
Step 4 is the page you're on; the anchor adds an -optional suffix the heading doesn't have. The same section says "Search for a connector that you enabled in step 3" — but step 3's heading is "Get your API key," not "enable connectors."
Consequence: The Getting Started page is the highest-trafficked surface in any developer docs. Cross-step references that don't resolve break trust immediately, and they break agents that follow internal links to gather context (a common RAG pattern), sending them after 404 anchors.
The fix: Audit every #step-N-* anchor on the Getting Started page; align prose references to the actual headings. Where a step does two things, split it so the prose reference is unambiguous.
5. Vault Properties table points the most important link at the wrong domain (significant)
Location: /guides/vault.md, Properties table.
Problem: The token property — the single most important field on the Vault component — links to https://docs.apideck.com/apis/vault/reference#operation/sessionsCreate. Every other reference link in the audited content uses developers.apideck.com; docs.apideck.com is not mentioned anywhere in llms.txt and appears to be a legacy host.
Consequence: If docs.apideck.com 301s correctly, every reader pays a redirect hop at the moment they're trying to mint a JWT. If the redirect doesn't preserve the #operation/sessionsCreate fragment (common after doc-platform migrations), the developer lands somewhere wrong while trying to figure out auth — the exact moment they're most likely to abandon.
The fix: Rewrite every docs.apideck.com reference to developers.apideck.com. Add a redirect-and-anchor checker to CI.
6. Vault API reference page has an empty Base URL section (significant)
Location: /apis/vault/reference.md.
Problem: The Vault reference renders a ## Base URL header followed by nothing. Compare with /apis/crm/reference.md and /apis/webhook/reference.md, both of which render "The base URL for all API requests is https://unify.apideck.com" under the same header. Vault is the outlier.
Consequence: Vault is the auth substrate — it's the first API a new integrator touches, and its docs are the place where "where do I send my POST" matters most. The empty section forces a reader to infer the base URL from sibling pages, and agents fingerprinting "Base URL" via header-followed-by-content will find no value and either fail or hallucinate (e.g., api.apideck.com, vault.apideck.com).
The fix: Populate the Vault reference Base URL with https://unify.apideck.com. Add a generalizable invariant to CI: every ## Base URL header in /apis/*/reference.md must be followed by a non-empty paragraph — that single test catches Vault today and any other reference page that regresses tomorrow.
7. Webhook reference lists two pairs of endpoints with identical human summaries (significant)
Location: /apis/webhook/reference.md, Webhooks resource.
Problem: Two pairs of endpoints share identical summaries in the index:
POST /webhook/webhooks/{id}/execute/{serviceId}— "Execute a webhook" (webhooksExecute)POST /webhook/webhooks/{id}/x/{serviceId}— "Execute a webhook" (webhooksShortExecute)GET /webhook/w/{id}/{serviceId}— "Resolve and Execute a connection webhook" (webhookVerify)POST /webhook/w/{id}/{serviceId}— "Resolve and Execute a connection webhook" (webhooksResolve)
The operation IDs in the markdown filenames do differentiate the four endpoints — so this is purely a human-readable-summary problem, not a deeper API design issue.
Consequence: Webhook execution is a sensitive surface — picking the wrong endpoint can mean wrong retry semantics or a different signing path. The index forces developers to open four pages to choose. Agents will pick the first matching summary and stop. Because the operation IDs already disambiguate, this is fixable purely at the summary level.
The fix: Rewrite the four summaries so each is unique — e.g., "Execute a webhook (canonical path)" vs. "Execute a webhook (short alias)" and "Resolve and trigger via GET" vs. "Resolve and trigger via POST" — and add one line per operation page explaining when to use it. No spec-level changes required.
8. HRIS onboarding guide deep-links into the wrong reference tags (significant)
Location: /guides/onboard-and-offboard-employees-hris-api.md.
Problem: The guide links Departments and Companies endpoints under the Employees tag:
[/hris/departments](/apis/hris/reference#tag/Employees/operation/departmentsAll)[/hris/companies](/apis/hris/reference#tag/Employees/operation/companiesAll)
The fragment routes both into tag/Employees; they belong under tag/Departments and tag/Companies.
Consequence: These anchors almost certainly fail to scroll (operation not found in the named tag) or land readers in the wrong section. Agents that index by anchor will associate departmentsAll with the Employees tag, polluting retrieval results.
The fix: Rewrite to #tag/Departments/operation/departmentsAll and #tag/Companies/operation/companiesAll. Add a doc test that validates every #tag/X/operation/Y fragment against the published OpenAPI spec.
9. Getting Started SDK paragraph contradicts five published SDKs (significant)
Location: /get-started.md SDK section vs. /sdks/*.md, /api-explorer.md, and /changelog.md.
Problem: Getting Started says: "At the moment we have SDKs for Javascript and PHP. SDKs for other popular languages such as Python, C#, Java and Rust are coming soon." Meanwhile, llms.txt lists SDK pages for node, java, and go; /api-explorer.md advertises "Node.js, Python, .NET, PHP, Java, Go"; and /changelog.md enumerates published packages on NPM (@apideck/unify), PyPI (apideck-unify), Packagist, NuGet, and Maven — five SDKs shipped.
Consequence: A developer landing on Getting Started concludes that only JS and PHP are GA and goes shopping for a Python-native unified-API vendor. Agents reading Getting Started for "what SDK should I install" pick the truncated answer and ignore real, published SDKs.
The fix: Rewrite the Getting Started SDK paragraph. List currently published SDKs by name and registry link, drop the "coming soon" framing, and explicitly say whether Rust is on the roadmap or not.
10. Connector-specific deep links don't appear in the docs index (significant)
Location: /guides/refresh-token-race-condition.md.
Problem: The race-condition guide links to /apis/accounting/xero and /apis/crm/salesforce as if those are real pages. /connectors.md instead routes per-connector traffic to https://developers.apideck.com/connectors and to per-API coverage pages, and the audited llms.txt enumerates neither /apis/accounting/xero nor /apis/crm/salesforce.
Consequence: Either these links 404 in a guide that handles a sensitive concurrency topic, or per-connector pages exist but aren't in llms.txt — in which case agents indexing the site via the advertised index miss the entire per-connector surface. Both failure modes are bad.
The fix: If per-connector pages exist, enumerate them in llms.txt or in /connectors.md. If they don't, replace the broken deep links with the real https://developers.apideck.com/connectors URL and stop minting /apis/<api>/<connector> paths in prose.
11. authorize-connections.md intro lists three options that don't match its section headings (significant)
Location: /guides/authorize-connections.md.
Problem: The intro lists three ways to let customers authorize connections:
- Use React Vault
- Redirect to Hosted Vault
- Completely build into your own app
The first numbered section that follows is titled "Option 1: Vault JS" — not "React Vault." None of the three intro phrases map cleanly onto the section heading the reader scrolls to next.
Consequence: The page that walks a developer through choosing an auth integration path opens with a mismatch between the menu and the chapters. Readers can't tell whether Vault JS is the same thing as React Vault, a superset, or a third option. Agents summarizing "how do I let users authorize a connection" pull conflicting structure from the same page.
The fix: Either rename the section headings to match the intro list, or rewrite the intro list to match the headings — and link each bullet to its anchor. Pick one set of names and use it across both the menu and the chapters.
12. samples.md promises specific patterns but doesn't link to any specific sample (significant)
Location: /samples.md.
Problem: The page advertises:
Backend patterns: signed-webhook verification, token refresh, rate-limit handling, and pass-through requests.
And then provides exactly three out-links: to the Apideck samples GitHub organization, to the SDK index, and to two unrelated quickstarts (File Picker, Vault). There is no link from "signed-webhook verification" to a specific repo or directory. Same for token refresh, rate-limit handling, and pass-through.
Consequence: The page whose entire job is wayfinding to samples doesn't wayfind. A developer who lands on /samples.md looking for "show me a signed-webhook verification example" must leave the docs, browse a GitHub org, and guess which repo matches. Agents asking the same question get a list of patterns with no addressable code.
The fix: Link each named pattern to a specific repo or file. If the samples don't exist yet, remove the pattern list rather than promising what isn't there.
13. connection-states.md references a guide not in the agent index (minor)
Location: /guides/connection-states.md.
Problem: Inline link to [Connector Statuses](/guides/connector-statuses). That page is not enumerated in llms.txt.
Consequence: Either it's a 404 in a frequently-referenced concept page, or it's an HTML-only page invisible to the agent layer that llms.txt advertises as complete. Either way, llms.txt is no longer the canonical index it claims to be.
The fix: If the page exists, add its .md mirror and list it in llms.txt. If it doesn't, replace the link with inline prose.
14. Data Scopes pages use inconsistent wording and there is no canonical feature matrix (minor)
Location: /guides/data-scopes-for-accounting.md and /guides/data-scopes-for-hris.md.
Problem: Both pages agree on the factual support set — HRIS and Accounting. But the Accounting variant says "Data Scopes are available for both HRIS and Accounting APIs" (stable framing) while the HRIS variant says "Data Scopes are currently available only for the HRIS & Accounting APIs" (roadmap framing). Neither page links to a canonical feature-availability matrix.
Consequence: A reader can't tell from either page alone whether more APIs are coming. Agents quoting one page will give a more confident answer than the other warrants, even though the actual coverage is identical.
The fix: Reconcile the two intros to identical wording and link both to a single feature-availability table on a parent page (e.g., /guides/data-scopes.md). State explicitly which Unified APIs are not yet supported and whether that's roadmap or won't-fix.
15. changelog.md is an empty stub that points at the HTML version (minor)
Location: /changelog.md.
Problem: The .md changelog contains no release entries. It is a meta-page that says:
The live changelog with full release notes lives at https://developers.apideck.com/changelog.
… followed by general notes on how updates are communicated. There are zero dated entries, zero version numbers, zero breaking-change notices.
Consequence: llms.txt and the per-page markdown surface are explicitly designed so an agent can fetch .md mirrors and get the same content as a human. For the changelog — the one page where temporal precision matters most — the agent gets a redirect notice. Any LLM-based workflow asking "what changed in the last release?" against the agent index gets nothing useful.
The fix: Render the actual release entries into /changelog.md (or at least the last N entries) so the markdown surface contains real content. If the live changelog is too large, paginate by quarter under /changelog/<year>-<quarter>.md and enumerate those in llms.txt.
What they do well
- Agent-readable index is real, not theatrical. llms.txt, llms-full.txt, per-page
.mdmirrors, public OpenAPI specs on GitHub, an MCP server, and a CLI is more than most peers ship. - Coverage transparency is a first-class concept. Per-API
/connectors/coveragepages are the right pattern for a 200-connector platform. - Multiple programmatic entry points are advertised side-by-side (CLI, SDKs, MCP, API Explorer), with reasonable cross-linking from
/api-explorer.mdand/building-with-llms.md.
Top 3 recommendations
- Promote the webhook-signing coupling to a Security Considerations callout, or ship a dedicated signing secret. The lack of any warning that API-key rotation breaks pending webhook signatures is the single highest-impact gap in the audited content.
- Add CI that validates every fenced
jsonblock parses, every#tag/.../operation/...anchor exists in the OpenAPI spec, and every## Base URLheader is followed by a non-empty paragraph. Three of the issues above (pass-through, raw-mode, HRIS deep links, Vault Base URL) are caught by a single ~50-line linter. - Build one canonical Coverage Matrix (Unified APIs × {REST, SDK, CLI, MCP, Skills}) on
/building-with-llms.md, kept in sync with the actual code, and link it fromget-started.md,mcp.md, and both Data Scopes pages. That eliminates issues #3, #9, and #14 in one place.