The docs are unusually agent-aware — llms.txt, llms-full.txt, .md suffix on every page, an MCP server, and shipped CLAUDE.md/AGENTS.md seed files — but a handful of real platform-level issues (an empty changelog mirror, mixed-TLD deployment URLs, inconsistent webhook event verbs, plan-tier names that don't line up across pages, and undefined version-fallback behavior) undercut that good foundation.
1. The agent-facing changelog markdown is empty (critical)
Problem: The rendered HTML changelog at /release-notes contains real release entries — "May 26, 2026: OCR word confidence on parse chunk and block metadata", "April 9, 2026: Batch Extract and Batch Parse APIs", "April 6, 2026: Splitter Composer", etc. The .md mirror at /release-notes.md — the version the docs explicitly tell AI agents to use ("For clean Markdown of any page, append .md to the page URL") — contains only the header, a pointer to llms.txt, and the single line "Stay up to date on what's shipping in the Extend platform." Zero entries.
Consequence: Every agent following Extend's own instructions to fetch .md URLs gets a content-free changelog. An agent asked "does Extend support batch parse?" or "when did OCR confidence land?" cannot answer from the markdown corpus, even though the HTML page has the answer. The same pattern likely affects any other JS-rendered list pages.
The fix: Render the changelog server-side into the .md output (and into llms-full.txt) the same way every other page is rendered. The changelog filter UI (All / api / parsing / extraction / workflows) is dynamic; the underlying entries should not be.
2. US2 deployment uses a different top-level domain than every other deployment (critical)
Location: /developers/deployments
Problem: The Available Environments list reads:
Production: https://api.extend.ai
US2: https://api.us2.extend.app
EU1: https://api.eu1.extend.ai
US2 is .app; everything else is .ai. There is no explanation of why, no warning that this is intentional, and no note about whether the .ai variant of US2 redirects or 404s.
Code verification
Runtime snippet checks
Run the public examples from this audit in an isolated sandbox and attach the pass, fixed, skipped, and failed counts to the report.
Consequence: Developers copying the pattern will type api.us2.extend.ai and get connection errors. Agents inferring a base URL from "the pattern" will do the same. Domain mismatches across an otherwise-uniform list are exactly the kind of inconsistency that bites at 2 AM during an incident.
The fix: Either standardize on one TLD across deployments, or add an inline note on the deployments page explaining why US2 lives on .app and confirming whether api.us2.extend.ai is reserved/unreserved.
3. Studio and dashboard URLs straddle two domains in the same onboarding flow (significant)
Problem: Step 1 says "Open the Extend Studio" — .app. Step 2 says "Replace YOUR_API_KEY with your actual key, available in the Developers page" — .ai. Same product, same five-minute Quick Start, two different dashboard domains. The Authentication page also points at dashboard.extend.ai/developers.
Consequence: Developers (and agents) cannot infer "the Extend dashboard URL" — it changes within a single onboarding doc. If one domain ever stops resolving or gets redirected, half the Quick Start breaks silently. Bookmark and SSO confusion are guaranteed.
The fix: Pick a single canonical dashboard origin and update every doc to use it. If dashboard.extend.app and dashboard.extend.ai are intentional aliases, document that once and stop mixing them mid-flow.
4. Webhook event verbs are inconsistent for the same logical state (significant)
Problem: Workflow completion uses workflow_run.completed. Batch parse completion uses batch_parse_run.processed. Batch processor (extract/classify/split) completion uses batch_processor_run.processed. The corresponding failure events are workflow_run.failed and batch_parse_run.failed. So "done" is sometimes .completed and sometimes .processed depending on which resource emitted it — and there is no explanation of the distinction.
Compounding this: the async-processing doc's terminal-states table lists PROCESSED (not COMPLETED) as the success state for extract/classify/split/parse/edit/workflow runs. So the workflow webhook says completed but the workflow run state says PROCESSED.
Consequence: Anyone writing a webhook handler that dispatches on event suffix (switch (event.type.split('.').pop())) has to special-case workflows. Agents generating handler scaffolds will pick one verb and silently miss the other. Logs and dashboards searching for "completed" will miss half the platform's success events.
The fix: Standardize on one verb (.processed matches the run state field, so it's the natural choice) and either rename workflow_run.completed or document that the two are synonyms — with a deprecation timeline for the old name.
5. API version fallback behavior is documented two different ways (significant)
Location: /developers/api-versioning vs. /developers/api-reference/endpoints/extract/extract-file
Problem: The versioning doc says: "When using an API key made after April 21, 2025, you will receive an error and be required to specify an API version." That's deterministic — no version, get a 400. The Extract File reference, on the same parameter, says: "If you are not using an SDK and do not specify a version, you will either receive a 400 Bad Request or be set to a previous legacy version." That's nondeterministic — sometimes 400, sometimes legacy.
The versioning doc also adds a third path: "Some organizations may default to a legacy version for backward compatibility predating the introduction of the x-extend-api-version header."
Consequence: A developer omitting the version header cannot predict from the docs whether they will get a hard failure, silent legacy routing, or org-specific legacy routing. Agents generating API calls will assume the friendliest path (legacy fallback) and ship code that 400s in some orgs and succeeds in others. This is the worst possible failure mode for a versioning policy.
The fix: Pick one canonical rule, state it once, and link the reference page to it. If the real rule is "depends on when the key was created and whether the org has a legacy default", spell that decision tree out explicitly with all three branches.
6. extraction_light confidence behavior is a footnote with two sentinel values (significant)
Location: /product/extraction/confidence-scores
Problem: The doc states: "The extraction_light base processor does not support logprobs. As a result: For JSON Schema processors, logprobsConfidence will be null. For legacy Fields Array processors, confidence values exposed in those results will be 0." Same underlying condition, two different sentinel values (null vs 0) depending on processor flavor.
The same page exposes confidence through conditional-step templating as {{extractionStepName.avgConfidence}} and {{extractionStepName.output.metadata.field_name.logprobsConfidence}} — neither of which signals which sentinel will arrive when the processor is Light-tier.
Consequence: Code branching on confidence < threshold will treat 0 as "extremely low confidence" and flag every Light-tier Fields Array extraction for review, while the same code with JSON Schema processors will hit a null and likely throw a TypeError. A user moving from Performance to Light to save credits gets silently different semantics depending on which schema flavor they happen to be using.
The fix: Pick one sentinel (null is the honest one — 0 is a numeric lie) and migrate Fields Array to match. If migration is impossible, surface the difference prominently in the pricing table where users decide between Performance and Light, not buried two pages deep in a confidence doc.
7. Plan-tier names don't line up between rate limits and pricing (significant)
Location: /product/general/rate-limits vs. /product/general/how-credits-work
Problem: The Rate Limits page lists five plan tiers in its QPS/Runs table: Sandbox, Starter, Scale, Growth, Enterprise. The "How Credits Work" pricing tables list three columns: Pay as you go, Scale, Enterprise. Only "Scale" and "Enterprise" appear in both. "Sandbox," "Starter," and "Growth" never appear in pricing; "Pay as you go" never appears in rate limits.
Consequence: A developer reading the rate-limits table cannot work out what their plan's credit pricing is, and a developer reading the pricing table cannot work out what QPS they'll get. Agents asked "what are the limits and cost for the Starter plan?" have no way to join the two tables — they either guess or refuse. This is the most common cross-page question for any usage-based product, and the docs make it unanswerable.
The fix: Pick one canonical set of plan names and use it on both pages. If "Pay as you go" is the new public name for "Sandbox" (or "Starter"), say so; if "Growth" is being retired, remove it from rate limits. Either way, the same column headers should appear in both tables.
8. Go SDK is missing from the polling-methods table (significant)
Location: /developers/async-processing vs. /developers/sdks
Problem: The SDKs page advertises "official SDKs in TypeScript, Python, Java, and Go." The async-processing doc's "Available Polling Methods" table — the canonical place to learn the createAndPoll helper for each resource — has columns only for TypeScript, Python, and Java. Go is absent from every row (Extract, Classify, Split, Parse, Edit, Workflow).
Consequence: Either the Go SDK lacks createAndPoll (in which case the SDKs page is misleading and Go users will hunt for a helper that doesn't exist) or the table is incomplete (in which case Go users will hand-roll a polling loop that the SDK already provides — wasting time and increasing the chance of busy-loops against the API). Agents generating Go integrations will skip the helper entirely.
The fix: Add a Go column to the polling-methods table with the actual method name (client.ExtractRuns.CreateAndPoll() or equivalent). If Go genuinely doesn't have the helper yet, say so explicitly on the SDKs page and link to the workaround.
9. Password-Protected Files appears to be missing from llms.txt (minor)
Location: /llms.txt vs. /product/password-protected-files
Problem: The Password-Protected Files page exists, is referenced by the Supported File Types page ("Password-protected PDFs are supported. See [Password-Protected Files]…"), and is the documented recovery path for the PASSWORD_PROTECTED_FILE parsing error code. The scraped excerpt of llms.txt does not include this page in the index — only the header, agent instructions, and version list are visible. The full index should be verified, but the page does not appear in the captured corpus.
Consequence: If the omission is real, agents indexing the platform via llms.txt — exactly as the docs instruct — will not discover the password-handling page and will not know that file.settings.password is the recovery path. They will report a password-protected file as unprocessable when it is one config field away from working.
The fix: Verify /product/password-protected-files is listed in llms.txt; if not, add it. Then audit the rest of the page tree for similar orphans — if one feature page got missed, others probably did too.
10. CLAUDE.md / AGENTS.md "downloads" have no link target in the markdown mirror (minor)
Location: /product/getting-started.md, "Coding with an agent?" section
Problem: The markdown reads "Download CLAUDE.md / Download AGENTS.md" as plain text — no URL, no path, no wget example. The rendered HTML presumably has buttons, but the .md mirror an agent fetches strips the affordance entirely. The instructions then say "Download one of these files and save it in your project root as CLAUDE.md or AGENTS.md" — but nowhere does it say where from.
Consequence: An agent following Extend's own seed instructions cannot self-bootstrap from the markdown source. Ironic for a docs site that prides itself on agent-readiness.
The fix: Inline the canonical raw URLs (e.g., https://docs.extend.ai/CLAUDE.md and …/AGENTS.md) as actual markdown links so the .md rendering preserves them. Bonus: include a one-line curl command.
What they do well
Real agent affordances:.md on every URL, llms.txt + llms-full.txt, an MCP server, and shipped CLAUDE.md/AGENTS.md seed files. Few docs sites take agent ingestion this seriously.
API versioning is genuinely versioned: A migration table with six dated versions, explicit "Current/Stable/Legacy" status, and per-version migration guides — far better than the typical "we just deploy and hope."
Error codes are tabulated with a retryable column. That's the contract developers and agents actually need, not prose.
Top 3 recommendations
Fix the empty changelog markdown — render /release-notes.md server-side with the actual entries. This is the highest-leverage fix because it silently breaks every agent the docs are explicitly designed for.
Reconcile the dashboard and deployment domains — pick one TLD per role (dashboard.extend.ai, api.extend.ai, regional siblings) and remove the .app variants from prose, or document the alias explicitly once.
Align cross-page tables — plan tiers (rate limits ↔ pricing), webhook verbs (completed ↔ processed), SDK coverage (SDKs page ↔ polling-methods table), and version-fallback behavior (versioning doc ↔ endpoint reference). Each contradiction is the kind of "one doc says A, another says B" trap that breaks integrations weeks after they ship.