The reference material (auth, parse error handling, webhooks, security, schema rules) is genuinely strong, but the onboarding surface is fractured across two namespaces (/product/... and top-level), and the single page Extend recommends developers start with — the 5-minute Quick Start — is the most stale page in the set, contradicting the rest of the docs on the dashboard domain, the Java client constructor, several next-step links that 404, and a long-superseded processor version.
1. Java SDK pinned to three different versions across four pages (critical)
Problem: The four pages that tell you how to install the Java SDK disagree. /sdks and /developers/sdks both pin Maven to 0.0.1-beta. /api-quickstart installs ai.extend:extend-java-sdk:1.12.0. /agent-quickstart shows a bare ai.extend:extend-java-sdk with no version at all.
Consequence: A Java developer (or a coding agent following agents.md) has no way to know which coordinate is correct. 0.0.1-beta and 1.12.0 are not adjacent patches — one of them is almost certainly wrong, and a build pinned to a stale beta will miss every feature the current API documents. An agent that copies the versionless snippet gets a non-resolving dependency.
The fix: Single-source the SDK version table (it's auto-generated from the API spec, per the docs' own claim) and render every install snippet from it. Pick the real current Java version and make all four pages emit it.
2. Java client is initialized three incompatible ways (critical)
Problem: Three pages show three different Java client constructions for the same client:
Auth page: ExtendClient.builder().token(...)
API Quickstart: ExtendClientWrapper.builder().apiKey(...)
Recommended 5-minute Quick Start: new ExtendClient("")
The class name (ExtendClient vs ExtendClientWrapper), the builder-vs-constructor pattern, and the credential method (.token() vs .apiKey()) all differ. The Quick Start additionally uses request types (ExtractFileRequest, ExtractorReference, , ) that appear on no other page, while the rest of the docs use / .
Consequence: Only one of these compiles against the current SDK. A developer following the "Recommended" path writes code against type names that don't exist in the package the other pages tell them to install, and gets compile errors with no hint which page is authoritative.
The fix: Settle on one client class, one initialization pattern, and one set of request/response types, then regenerate every Java snippet from the same SDK version. Delete the divergent type names from the Quick Start.
3. The schema example violates the schema page's own stated rules (critical)
Location: /product/extraction/schema
Problem: The canonical /extraction/schema page states two hard rules: every primitive field "must be nullable (use array type with 'null' ... Non-nullable primitive types will be rejected with a 400 error)," and date fields "must be strings and use the extend:type keyword with the value date." The legacy /product/extraction/schema page's example breaks both: it declares invoice_date: { type: "date" } (an invalid type, not a nullable string with extend:type: "date"), and its required array lists "signature" while the actual property is named invoice_signature.
Consequence: A developer who copies this example gets a 400 rejection (invalid date type) and a schema whose required references a non-existent property. The bug is in example code, which is exactly what people paste first.
The fix: Fix the example to "type": ["string", "null"] with "extend:type": "date", correct required to "invoice_signature", and ideally retire the legacy duplicate page entirely (see Issue 8).
4. The "Recommended" Quick Start uses the wrong dashboard domain (significant)
Problem: This page links the Studio as https://dashboard.extend.app/studio — the .app TLD. Every other page (/api-quickstart, /cli, auth) uses dashboard.extend.ai.
Consequence: This is the first link in the flow Extend explicitly marks "Recommended." If .app isn't the live console (and the rest of the docs imply .ai is), the developer's very first click in onboarding lands on the wrong or dead domain.
The fix: Replace dashboard.extend.app with dashboard.extend.ai and add a lint/redirect check for the .app domain across all docs.
5. Migration guide for a breaking API version 404s (significant)
Location: /api-reference/api-versioning
Problem: The version changelog describes 2025-04-21 as introducing "Breaking changes for the processor config schema" and links "See our migration guide for details" → /developers/migrating-to-2025-04-21-api-version. That URL returns "Page Not Found." (The 2026-02-09 row uses a different, working /2026-02-09/api-reference/migrations/... scheme, so the link styles are also inconsistent.)
Consequence: A developer hitting the documented breaking change has no migration guide — the one link meant to help them upgrade is dead. Migration friction on a breaking schema change is exactly where you can't afford a 404.
The fix: Restore the 2025-04-21 migration guide (or repoint the link to its real location), and standardize migration-guide URLs onto one scheme.
6. Quick Start "Next steps" link to the Create Extract Run endpoint 404s (significant)
Problem: The Quick Start's "Next steps" links to /developers/api-reference/endpoints/extract/create-extract-run, which returns "Page Not Found." The live endpoint reference is at /api-reference/endpoints/extract/create-extract-run (no /developers prefix) — the same stale-prefix pattern affects this page's links to /developers/sdks.
Consequence: A developer finishing the recommended tutorial and clicking through to the actual API reference lands on a dead page. The /developers/... prefix is a legacy path scheme that some pages still emit.
The fix: Repoint all /developers/api-reference/... links to the canonical /api-reference/... paths and add a redirect from the legacy prefix.
7. Password-Protected Files is linked by a path that 404s (significant)
Location: /general/supported-file-types
Problem: The supported-file-types page closes with "See Password-Protected Files" — that target 404s. The working page is /password-protected-files, which /parsing/error-handling links correctly. So two pages reference the same doc by two different paths, one of them broken.
Consequence: A developer processing a password-protected PDF who arrives from the file-types page hits a dead link, while one arriving from the parse error page gets the real doc. The feature looks undocumented from one entry point.
The fix: Change the link on /general/supported-file-types to /password-protected-files and add a link-checker to CI.
8. Two divergent, drifting copies of the schema and SDK pages (significant)
Location: /extraction/schema vs /product/extraction/schema; /sdks vs /developers/sdks
Problem: The schema docs exist twice — canonical /extraction/schema ("Schema") and legacy /product/extraction/schema ("Schema (JSON Schema)"), the latter carrying extra fields (customFieldConfig, customExtractionRules, includeBoundingBoxCitations) and the rule-violating bugs in Issue 3. The SDK docs also exist twice — /sdks and /developers/sdks — with the language list reversed ("Python, TypeScript, Java, and Go" vs "TypeScript, Python, Java, and Go") and different install ordering.
Consequence: Search and agents surface whichever copy they hit first; the two drift independently, so a reader can land on the stale one with no signal it's outdated. This is how the schema bug in Issue 3 survives — it lives only on the duplicate.
The fix: Pick one canonical page per topic, 301 the legacy /product/... and /developers/... duplicates to it, and remove the orphaned copies from navigation.
9. Rate limits page omits the 429, Retry-After, and the limit headers (significant)
Location: /general/rate-limits
Problem: The page gives QPS and Runs-Per-Minute tables but never states that exceeding them returns HTTP 429, never names a Retry-After header, and never says which response header surfaces remaining quota — even though the error page marks RATE_LIMIT_EXCEEDED as retryable ("Back off and retry"). The Scale and Enterprise rows list "25+" / "120+" and "75+" / "300+" with open-ended + values that are never defined.
Consequence: A developer can't implement correct backoff. "Back off and retry" with no Retry-After and no documented status code means they have to reverse-engineer the limit from failed calls, and the + tiers give no number to plan against.
The fix: Document the 429 status, the Retry-After (and any X-RateLimit-*) headers with an example response, and replace 25+/120+ with concrete defaults or a clear "negotiated, starts at N" statement.
10. Confidence docs still lead field-access examples with a field being removed, and never say what the aggregates aggregate (significant)
Location: /extraction/confidence-scores
Problem: The page states logprobsConfidence "is being phased out" — extraction_light "has never returned it," and extraction_performance "version 4.6.0 and later return null" (confirmed on the model-versioning page). Yet the page's "Specific-field access" bullets still lead with {{...field_name.logprobsConfidence}} as the first documented field-access pattern (the ocrConfidence variant is listed second). Separately, the minConfidence / avgConfidence aggregates used in the conditional-logic examples never state which underlying score they aggregate.
Consequence: A developer on the current performance processor (default 4.8.0) who follows the first field-access bullet and builds routing on logprobsConfidence is reading a field that is always null, so their "route to manual review if low confidence" condition silently never fires. And because the aggregates are undefined, they can't tell whether minConfidence/avgConfidence are affected by the same phase-out or remain reliable.
The fix: Reorder the field-access bullets to lead with ocrConfidence and mark the logprobsConfidence variant as legacy, and state explicitly which underlying score minConfidence/avgConfidence aggregate on 4.6.0+.
11. llms.txt exposes 3 API versions; the changelog lists 6 (significant)
Location: /llms.txt vs /api-reference/api-versioning
Problem: The agent index at /llms.txt advertises three versions (2026-02-09, 2025-04-21, 2024-12-23). The API Version Changelog lists six (the same three plus 2024-11-14, 2024-07-30, 2024-02-01, all "Legacy").
Consequence: This file exists specifically so agents can index the docs. An agent trusting llms.txt as the version list will conclude three legacy versions don't exist — and won't know to send the right x-extend-api-version header for an integration still pinned to one of them.
The fix: Either list all six versions in llms.txt or state plainly that only three are agent-discoverable and link the full changelog as authoritative.
12. US2 base URL uses a different TLD than every other deployment, unexplained (significant)
Location: /api-reference/deployments
Problem: Production and EU1 use api.extend.ai and api.eu1.extend.ai, but US2 is https://api.us2.extend.app — the .app TLD. The deployments page lists all three base URLs but never explains why US2 alone is on .app, and provides no table mapping the SDK's region configuration to these hosts.
Consequence: A developer configuring US2 can't tell from the docs whether the .app host is intentional or a typo — especially given the Quick Start's wrong .app dashboard link (Issue 4) makes .app look like a known mistake. Anyone hard-coding base URLs may pick the wrong TLD.
The fix: Add a table mapping each region/deployment to its base URL, and either normalize US2 onto .ai or explicitly call out why it's .app.
13. The dedicated Changelog page is empty (minor)
Location: /changelog
Problem: The /changelog page contains only "Stay up to date on what's shipping in the Extend platform." — no entries — even though detailed history exists in the API Versioning changelog and the per-processor Model Versioning pages.
Consequence: A developer who navigates to the obvious "Changelog" link sees nothing and concludes the platform isn't actively shipping, when in fact 4.8.0 landed 2026-05-13. The real change history is buried in pages they won't think to check.
The fix: Populate /changelog by aggregating the API-version and model-version entries, or redirect it to whichever page is authoritative.
14. Retryable flag rendered as text on one error page and emoji on another (minor)
Location: /api-reference/error-handling vs /parsing/error-handling
Problem: The general error table renders the Retryable column as textual "Yes"/"No"; the parse error table renders the same concept as ✅/❌. The general page also never enumerates HTTP status codes while the parse page does.
Consequence: An agent (or a script) parsing both tables to drive retry logic has to handle two encodings of one boolean, and a reader cross-referencing them sees two formats for the same field — a small but real source of doubt about whether they mean the same thing.
The fix: Standardize the Retryable column to one representation across all error tables, and surface HTTP status codes consistently on both.
Problem: The manual-verification steps say "Compute a HMAC 256 digest on the resulting string." "HMAC 256" is not an algorithm name; the intended one is HMAC-SHA-256.
Consequence: A Go developer — who per the same page has no SDK helper and must verify manually — has to guess the hash function. Choosing the wrong one produces signatures that never match, and the failure mode (every webhook rejected) is painful to debug.
The fix: Change "HMAC 256" to "HMAC-SHA-256" and show the exact construction (v0:${timestamp}:${requestBody}, keyed with the signing secret) in a code snippet.
16. Quick Start sample config pins a long-superseded processor version (minor)
Problem: The Quick Start's sample config pins baseVersion: 4.1.1 (released 2025-07-10). The model-versioning page shows the current extraction_performance default is 4.8.0 (2026-05-13) — and notably, logprobsConfidence was removed at 4.6.0, so 4.1.1 predates that change.
Consequence: Developers copy a config nearly a year behind the default, missing array-strategy controls, large-array support, and the current base model — and unknowingly opting into the old confidence behavior. They have no signal the pin is stale.
The fix: Update the sample to the current default version, or omit baseVersion so the example tracks the latest, and note where to find the version changelog.
17. Edit pricing on the Light tier is undocumented; the same page disagrees with itself on names (significant)
Location: /general/how-credits-work
Problem: The Performance pricing table lists Edit Filling (1 credit/page) and Edit Schema Generation (2 credits/page), and the 💡 Edit pricing prose explains both endpoints — but the Light pricing table omits both Edit rows entirely, and the prose never breaks out Light. So there is no documented Edit cost for the Light tier. The same page also labels the action Edit Filling in the table while the prose names the endpoint /edit, and the page heading reads "Pricing Made Simple" while the nav/title is "How Credits Work."
Consequence: A developer on the Light tier cannot price an /edit call from the docs at all — they either guess or file a support ticket. The table-label-vs-endpoint-name mismatch (Edit Filling vs /edit) is the same parse hazard flagged elsewhere in this audit, and the heading-vs-title drift makes it ambiguous which name is canonical.
The fix: Add the Edit rows (or an explicit "Edit not available on Light") to the Light table, reconcile the Edit Filling label with the /edit endpoint name, and pick one of "Pricing Made Simple" / "How Credits Work" as the page title.
18. "Community SDKs" entry names a maintainer with no link and no package (minor)
Location: /sdks
Problem: The Community SDKs section attributes a community SDK to "the Mercury engineering team" but provides no repository link, no package name, and no install instructions.
Consequence: A developer who wants that SDK has nothing to act on — there's no name to search a package registry for and no URL to follow. The reference is undiscoverable, which is worse than omitting it.
The fix: Add the repository URL and the exact package name/coordinate for the community SDK, or remove the entry until it can be linked.
19. CLI and SDK use confusingly similar but distinct package names, with undocumented credential prefixes (minor)
Location: /cli
Problem: The CLI installs as npm install -g @extend-ai/cli (or brew install extend-hq/tap/extend), while the JavaScript SDK npm package is extend-ai — near-identical names for two different things. The page's examples also use an sk_ API-key prefix that appears on no other page, while webhook signing secrets use wss_ and workspace IDs use ws_, none of which are explained together anywhere.
Consequence: A developer can easily npm install extend-ai expecting the CLI (or vice versa) and install the wrong package. And with three different credential prefixes (sk_, wss_, ws_) scattered across pages and never tabulated, it's easy to paste the wrong token into the wrong field.
The fix: Call out explicitly that @extend-ai/cli is the CLI and extend-ai is the SDK, and add a short table of credential prefixes (sk_, wss_, ws_) and what each identifies.
What they do well
Parse error handling is excellent — /parsing/error-handling cleanly separates sync vs. async failure surfacing, enumerates HTTP status codes, and gives a complete custom-error-code table with retryability.
Webhooks are thoroughly specified — endpoints vs. subscriptions, a once-only signing secret, HMAC verification, replay protection, and an honest, repeated caveat that run events fire only for API-created runs and that the Go SDK lacks a verification helper.
Security/data-handling is consistent and concrete — explicit "we do not train on your data," ZDR foundation-model terms, and per-resource deletion endpoints that all use correct, working /api-reference/... paths.
Top 3 recommendations
Rebuild the recommended Quick Start from the canonical sources. It is the worst offender — wrong dashboard domain (.app), 404 next-step links, a Java client pattern and type names that exist nowhere else, and a stale baseVersion: 4.1.1. Regenerate its snippets from the same SDK version and path scheme the reference uses.
Kill the duplicate namespaces. Collapse /product/... and /developers/... duplicates of schema and SDK pages into one canonical page each with 301 redirects, and run an automated link checker — that single step resolves the dead migration link, the Create-Extract-Run 404, the password-protected-files 404, and the schema-bug-on-the-legacy-copy problem.
Make machine-readable surfaces agree with the prose. Sync llms.txt to the full version list, document the 429 / Retry-After / rate-limit headers, standardize the Retryable column, fill the Light-tier Edit pricing gap, and demote logprobsConfidence in the confidence examples — the things agents and scripts parse should not contradict the reference.
Fixed
0
Skip
37
Fail
0
Summary
Verified runnable public documentation examples for Extend (https://docs.extend.ai/) against the live API using the supplied workspace-scoped test key (EXTEND_API_KEY) and the public sample file bank_statement_example.pdf.
83 snippets classified: 46 PASS, 37 SKIP, 0 FAIL.
Runtimes available: curl, Node v24, Python 3.9 (pip bootstrapped via ensurepip). No bun, java/Gradle/Maven, go, ruby, elixir, or Homebrew — so all Java/Go install + code snippets and the brew/go install lines were SKIPped as "runtime unavailable."
Live calls that PASSED end-to-end:GET /extractors (auth), POST /parse (by URL), POST /files/upload, POST /webhook_endpoints; the Python and TypeScript parse programs; SDK installs (pip install extend-ai, npm install extend-ai); CLI install (npm install -g @extend-ai/cli), extend --version (v0.4.3), extend extractors list, extend parse, extend skill install; and the curl …/agents.md fetch.
All JSON schema/config/payload blocks validated as well-formed (validate-only), except webhooks/events block 0, which is an illustrative event payload containing a // etc. ellipsis (SKIP, not literal JSON — not a failure).
SKIPs are dominated by Java/Go runtime gaps, placeholder identifiers (file_xK9…, ws_xxx, wh_…, workflow_id_here, <API_TOKEN>), and server-handler fragments (webhook signature verifiers, confidence-score accessors) that cannot run without a surrounding app/live request.
Documentation-structure issues observed while locating snippets (reported for context only — not code-execution failures): the 2025-04-21 migration guide (/developers/migrating-to-2025-04-21-api-version) and the Quick Start "Create Extract Run" link (/developers/api-reference/endpoints/extract/create-extract-run) and /product/password-protected-files all 404; /product/extraction/schema and /product/extraction/quick-start-5-minutes 308-redirect to canonical pages whose .md export returns a "Page Not Found" stub; and the Java SDK install version and client-init pattern differ across pages (0.0.1-beta vs 1.12.0; ExtendClient.builder().token() vs ExtendClientWrapper.builder().apiKey() vs new ExtendClient("")).
Required credentials
EXTEND_API_KEY — workspace-scoped Bearer token. Provided by the user; used for all live curl/SDK/CLI calls. (Value never printed.)
EXTEND_WORKSPACE_ID / X-Extend-Workspace-Id (ws_xxx) — required only for organization-scoped keys; not provided and not needed for the workspace key. The org-key auth variant was SKIPped.
Webhook signing secret (wss_…) — referenced by the manual webhook-verification fragments; not needed (those snippets were SKIPped). The POST /webhook_endpoints response signingSecret was redacted, not printed.
<API_TOKEN> placeholder — used in the password-protected-file snippets (SKIPped).