The docs are well-organized and agent-aware (llms.txt, dedicated skill file, MCP setup), but the billing/response contract is genuinely inconsistent across pages, MCP auth is unexplained, and several reference pages are one-line stubs.
1. The price/amount field has three different units across the docs (critical)
Problem: The same conceptual field — cost of a call — is documented in incompatible units depending on which page you read:
/quickstart shows the Run response with "price": "0.10" (string, dollars) at the response root; the JS sample destructures const { data, price } = await response.json() and the Python reads result['price'].
/api-reference/run shows the same call as "priceCents": 10 (integer, cents). No price field at all.
/api-reference/details shows "price": "$0.03" (string with $).
/api-reference/list-endpoints shows "price": "$0.005" (string with $).
/mcp/tools shows "priceCents": 0.4 — a fractional "cents" value (0.4 cents = $0.004) — which contradicts the integer-cents contract /api-reference/run documents for the same field.
/api-reference/balance shows the same account's balance as both "balance": "$5.00" (text response) and, under /v1/credits/check, "balanceCents": 500000. 500,000 cents is $5,000.00, not $5.00 — so balanceCents is evidently in 1/100,000 of a dollar, not cents.
/api-reference/transactions then names that same unit amountCents and shows -3000 for "API call: apollo /v1/people/match" — but /details lists that endpoint at $0.03. -3000 is only $0.03 if the unit is 1/100,000 of a dollar. The Python example confirms this by computing amount = tx['amountCents'] / 100000.
Consequence: An agent that follows /quickstart reads response.price, an agent that follows reads — they break each other. An agent that uses to compute spend will be off by 1000× depending on which endpoint it called. Treasury reporting, budget caps, and "stop at $X" logic all silently produce wrong numbers. The MCP is the MCP-side instance of this same root cause: a field typed as integer cents being shown as a fractional value somewhere it has to round-trip through JSON schema and TS types.
The fix: Pick one canonical unit (whole cents is the conventional choice) and one canonical field name, then sweep every example — Quickstart, Run, Details, List Endpoints, MCP Tools, Balance, Transactions. If transactions/balance legitimately need sub-cent precision, rename those fields (amountMicros, amountHundredThousandths) and document the unit explicitly in the field table.
2. Error response contract contradicts itself between the API overview and /v1/run (critical)
Location:/api-reference/introduction vs /api-reference/run
Problem:/api-reference/introduction declares the error envelope as:
and lists named codes: UNAUTHORIZED, INSUFFICIENT_CREDITS, RATE_LIMITED, NOT_FOUND, UPSTREAM_ERROR.
/api-reference/run shows a different envelope — { "success": false, "priceCents": 10, "error": "Insufficient credits..." } with no code field — and its "Error Codes" table is the HTTP numeric codes 400/401/402/404/5xx, not the named strings above.
Consequence: A developer who writes if (resp.code === "INSUFFICIENT_CREDITS") based on the overview will never match a real response from /v1/run. Agents that map code strings to retry behavior get neither contract right.
The fix: State once, at the API reference root, which envelope shape /v1/run actually returns; show a real 401/402/404 response body with the code field present; align the Run page's "Error Codes" table with the named codes (or remove the named-code list if it doesn't exist on the wire).
3. MCP setup never explains how the server authenticates the caller (critical)
Location:/mcp/setup, /mcp/overview, /mcp/tools
Problem: The setup config in every section is exactly:
There is no Authorization header, no headers block, no env var reference, no OAuth flow described. The Troubleshooting accordion says "Check your account has credits" — implying the server knows which account is calling — but never tells the developer how that linkage is established.
Consequence: Every MCP use call is billable. A developer following these instructions cannot tell whether their calls will be billed to them, anonymous, or rejected. Agents installed via this snippet will appear to "work" on free endpoints and then mysteriously 401 on paid ones.
The fix: Add a step that shows the actual auth mechanism — whether that's "headers": { "Authorization": "Bearer ..." } in the JSON, an interactive OAuth handshake on first connect, or an ORTHOGONAL_API_KEY env var the server reads. Whichever it is, document it in /mcp/setup and link it from Troubleshooting.
4. Apollo example transaction is inconsistent with Apollo's documented price (critical)
Location:/api-reference/transactions vs /api-reference/details
Problem: The sample transaction shows:
"type": "api_call",
"amountCents": -3000,
"description": "API call: apollo /v1/people/match"
/api-reference/details documents that exact endpoint at "price": "$0.03". If amountCents is actually in cents, -3000 represents -$30.00, which is 1000× the listed price. The numbers only reconcile if amountCents is in 1/100,000 of a dollar — which is also what the Python example below assumes (tx['amountCents'] / 100000).
Consequence: A developer comparing the catalog price to their transaction history sees a 1000× discrepancy and will assume they're being overbilled. This is the kind of thing that triggers chargebacks. This is a worked, concrete instance of Finding 1, severe enough on its own that reconciliation tooling built from these docs will silently disagree with the catalog.
The fix: Either rename the field to reflect its actual unit, or change the example to use real cent values consistent with /details (e.g., -3 for a $0.03 call) and fix the Python divisor to / 100.
5. CLI command for installing a skill is documented two different ways (significant)
Location:/index (home) vs /skill
Problem: The home page Install example uses:
orth skills add linkedin-scraper
The Agent Skill reference (/skill.md, which is the file agents are explicitly meant to read) uses:
orth skills install company-intel
Consequence: Half the agents will try add, half will try install, and one of them will fail. Humans use judgment; agents stop.
The fix: Pick one verb, replace all occurrences, and consider adding the other as an alias in the CLI itself so existing docs/snippets keep working.
6. CLI command for checking balance is documented two different ways (significant)
Location:/skill vs /api-reference/balance
Problem:/skill.md's CLI cheatsheet documents:
orth account # Check balance and usage
/api-reference/balance.md documents:
orth balance
Same operation, two verbs, in the two files most likely to be consumed by agents (the skill file and the canonical reference).
Consequence: Same shape as Finding 5 — an agent picks one verb based on which page it loaded first and stalls on command not found for the other. This is the parallel CLI inconsistency to the add/install mismatch and equally agent-breaking.
The fix: Pick one (whichever the CLI actually ships) and sweep both files. If both verbs are real, document them together with the canonical one first.
7. Authentication page is a one-sentence stub for a billable API (significant)
Location:/authentication
Problem: The entire page after the H1 is: "All requests to Orthogonal require an API key in the Authorization header:". There is no information on:
where the key comes from (dashboard URL? account creation flow?),
key formats — orth_live_ appears in /api-reference/introduction and /integrate, but the test-vs-live distinction (orth_test_ keys, "limited APIs and no charges") only exists in /skill.md under "Common Gotchas",
key rotation, expiration, or revocation,
the user-key vs organization-key distinction (mentioned only in /api-reference/balance),
scopes/permissions.
Consequence: A developer who lands on /authentication from a search engine learns nothing they didn't already know. Critical operational facts (test vs live, org vs user) live in pages they wouldn't think to read.
The fix: Expand /authentication to cover key creation, the live vs test distinction with both prefixes, the user vs organization key distinction, rotation, and what happens when a key is leaked. Then link to it from /quickstart, /skill, and /api-reference/introduction instead of repeating snippets.
8. /api-reference/search is missing its request parameter section (significant)
Location:/api-reference/search
Problem: The scraped page for the canonical Search endpoint contains only ## Response — no ## Request block, no parameter table, no example. The MCP version (/mcp/tools#search) documents the parameters (prompt, limit) but the REST reference page that /v1/search should live on does not.
Consequence: A developer trying to call /v1/search directly (the way /quickstart shows) has to reverse-engineer the request body from the curl snippet on the Quickstart page. Agents reading /api-reference/search.md (which is in llms.txt) get an incomplete tool signature.
The fix: Add the Request section with prompt (string, required), limit (number, default 10, max 50), a curl example, and a representative response body. Mirror what's already on /mcp/tools.
9. Rate-limit documentation lists a RATE_LIMITED error but no actual limits (significant)
Problem: "How It Works" promises "We handle rate limits and retries for each provider." The introduction lists RATE_LIMITED as a returnable error code. Nowhere in the scraped docs is there a number — per-second, per-minute, per-day, per-endpoint, per-account, burst, anything — describing when Orthogonal's own gateway will return RATE_LIMITED.
Consequence: Agents and backend services can't size their concurrency, can't decide retry-with-backoff intervals, and can't tell whether a 429 is from Orthogonal or the upstream provider.
The fix: Add a "Rate Limits" section to /api-reference/introduction with the actual numerical limits (or, if limits are per-plan, link to the dashboard plan page) and the recommended backoff strategy. Distinguish between Orthogonal-gateway limits and upstream-provider limits.
10. MCP server is missing list-endpoints, the discovery tool the REST API documents (significant)
Location:/mcp/overview vs /api-reference/introduction, /skill
Problem:/mcp/overview says "The MCP server provides four tools: search, get_details, integrate, use." The REST surface and /skill.md both treat /v1/list-endpoints as a first-class discovery endpoint (it appears in the Discovery API table in /api-reference/introduction and in /skill.md's CLI cheatsheet). It is not exposed as an MCP tool.
Consequence: An MCP-only agent (which is precisely the integration model the docs push for Claude Desktop and Cursor) has no way to enumerate the API catalog — it can only search with a natural-language prompt. There's no "list everything" call, so an agent can't introspect the surface to plan multi-step workflows. This is a capability gap, not just a doc gap, but it's surfaced here because the docs don't acknowledge it.
The fix: Either add a list_endpoints MCP tool that mirrors the REST endpoint, or explicitly document on /mcp/overview that exhaustive listing is REST-only and explain the workaround for MCP-only agents.
11. "Credits" vs "dollars" is never defined, and the transactions example makes the conversion ambiguous (significant)
Problem: The pricing page talks in dollars ("$0.01 as low as per call"). The transactions example shows "amountCents": 1000000, "description": "Credit purchase: 10,000 credits" — so 10,000 credits maps to 1,000,000 amountCents. If the unit is the 1/100,000-of-a-dollar interpretation forced by Findings 1 and 4, that's $10 for 10,000 credits, or $0.001/credit. None of this is stated anywhere. Balance is shown as "$5.00" (dollars) in one place and "balanceCents": 500000 (some unit) in another. There's no page that says "1 credit = $X" or "credits and dollars are interchangeable."
Consequence: A developer trying to budget — "I have $X to spend, what does that buy me?" — has to do the cross-page math themselves and reverse-engineer the unit. Compounds Finding 1: even if you trust the cents-vs-micros field, you still don't know what a "credit" is.
The fix: Add a "Credits" section to /concepts/pricing that states the credit-to-dollar conversion explicitly, document the unit on amountCents and balanceCents field tables, and make the transactions example use round numbers that obviously demonstrate the conversion.
12. Three service domains, no explanation of which is which (significant)
Problem: The REST base URL is https://api.orthogonal.com. The MCP server is https://mcp.orth.sh. The x402 endpoints live under https://x402.orth.sh/... (visible in /details under usage.x402). Three hostnames across two TLDs are introduced as if obvious. There's no overview page that says "Orthogonal runs these endpoints on these domains, allowlist all three."
Consequence: Firewall admins and corporate networks have no clean list to allowlist. Developers confused by the orth.sh vs orthogonal.com split can't tell whether mcp.orth.sh is the real service or a phishing target.
The fix: Add a "Domains" section to /api-reference/introduction listing all three with their purposes and IP egress notes, and link to it from /mcp/setup.
13. /v1/credits/check is buried inside the Balance page without its own contract (significant)
Location:/api-reference/balance
Problem:/api-reference/balance.md is titled "Balance" and documents GET /v1/credits/balance. Halfway down, under "Check Sufficiency", it introduces an entirely different endpoint — POST /v1/credits/check — with a different request body ({"amountCents": 100}) and a different response shape (sufficient, balanceCents, requiredCents, shortfallCents). The second endpoint has no heading of its own, no parameter table, no error-response section, and isn't in llms.txt.
Consequence: Agents indexing the API surface from llms.txt will miss /v1/credits/check entirely. Developers reading the Balance page top-to-bottom will encounter a sub-endpoint without knowing its full contract.
The fix: Promote /v1/credits/check to its own reference page (with request, response, and error tables) and add it to llms.txt. Cross-link from /balance.
14. The x402 deep link points to a section that isn't visible on the pricing page (minor)
Location:/concepts/how-it-works
Problem: "See the x402 documentation for details." The scraped /concepts/pricing content only shows the "Pay Per Call" section; no #x402-direct-payment heading is present. x402 itself appears as a code-snippet format (x402-fetch, x402-python) and as a URL pattern (https://x402.orth.sh/...) in other pages, but is never explained in prose.
Consequence: Following the only narrative link to "x402 documentation" lands on a page that doesn't define x402. Developers who want to understand what they're opting into when they pay providers directly with crypto have no entry point.
The fix: Either add the #x402-direct-payment anchor and a real explanation to /concepts/pricing, or move the x402 explainer into its own /concepts/x402 page and update the link.
15. "OpenClaw" is referenced as a supported agent but never defined anywhere in the docs (minor)
Location:/index (two mentions: "Install It" step and "Why Orthogonal?" card)
Problem: The home page says "Skills work with OpenClaw, Claude Code, Codex, and any agent that reads markdown." OpenClaw is not defined, linked, or mentioned again in any scraped page — not in /mcp/overview, /mcp/setup, /skill, or the use cases. It also doesn't appear in llms.txt.
Consequence: Either OpenClaw is a real product that needs a link, or it's a typo. Either way, a reader can't verify compatibility.
The fix: If it's a real product, link to it on first mention. If it's a typo, fix both occurrences on /index.
16. The @orth/sdk TypeScript SDK has no documentation page (minor)
Location:/api-reference/integrate, llms.txt, marketing site
Problem:/api-reference/integrate shows orth-sdk snippets that import Orthogonal from "@orth/sdk", and the public landing page leads with that same SDK import. llms.txt lists no SDK reference page — no install instructions, no method signatures, no error semantics. The setup line npm install @orth/sdk && export ORTHOGONAL_API_KEY=orth_live_... appears only inside a sample response from /v1/integrate.
Consequence: A developer who picks the SDK path (which the marketing site recommends) has no canonical reference. Agents that prefer the SDK have nothing to read.
The fix: Add an SDK reference page to the docs and include it in llms.txt. At minimum: install command, client constructor, run method signature, error types.
What they do well
llms.txt exists and is complete — every doc page has a .md counterpart, which is rare and very agent-friendly.
Dedicated /skill.md aimed at agents with a Gotchas section that surfaces the real failure modes (key types, slug typos, MCP restart).
Use Cases page actually chains multiple APIs together rather than showing isolated snippets — this is the right shape for an agent-aware platform.
Top 3 recommendations
Unify the money fields and define "credit". Pick one unit (cents or micros), one field name (priceCents or amountMicros), one credit-to-dollar conversion, and sweep every example — Quickstart, Run, Details, List Endpoints, MCP Tools, Balance, Transactions — so agents don't see three different contracts for the same number.
Document MCP authentication and parity. The setup snippet has no auth field; either show the real headers block or describe the OAuth handshake. While you're there, expose list_endpoints as an MCP tool or explain its absence — MCP-only agents can't enumerate the catalog today.
Promote the test/live and user/org key facts out of the Skill file and into /authentication, then link to it from Quickstart and the API Reference introduction. A one-sentence auth page on a billable platform is a footgun.
Verified runnable snippets from the public Orthogonal docs against the live https://api.orthogonal.com gateway using the supplied user API key (substituted in for YOUR_API_KEY / process.env.ORTHOGONAL_API_KEY).
Discovery surfaces (/v1/search, /v1/list-endpoints, /v1/credits/balance, /v1/credits/transactions, /v1/credits/check) and most JSON config blocks work as advertised. A consistent set of doc bugs caused the failures:
Wrong API slugs/paths in examples.apollo /v1/people/match, linkup /v1/search, and hunter /domain-search are quoted throughout the docs, but /v1/list-endpoints shows the registered paths are apollo /api/v1/people/match, linkup /search, hunter /v2/domain-search. Calls to the documented paths return 404 from the gateway. The shofo API slug used heavily in use-cases.md is not registered at all (returns the upstream provider's HTML 400 error).
Quickstart /v1/run body for sixtyfour /enrich-lead is missing the required struct body parameter that /v1/details declares; the API returns 400 "Struct is required." The /find-email and /find-phone examples use a lead_info key, but the actual schema requires lead (400 "Required").
Response-field mismatch. Quickstart code reads result.price / destructures price, but /v1/run actually returns priceCents. Python snippet crashes with KeyError: 'price'. JS prints Cost: $undefined. Same pattern bites details, integrate, and transactions Python snippets where the documented JSON keys (endpoint, snippets, type) are missing or differ (transactionType) in real responses.
Single-quoted Authorization: Bearer $ORTHOGONAL_API_KEY headers in mcp/tools.md and skill.md prevent shell expansion, so curl sends the literal $ORTHOGONAL_API_KEY and the gateway returns 401.
Use-case workflows are JS/Python scripts that wire several runs together; every one calls at least one missing slug or wrong path, so the documented multi-step workflows do not deliver the promised result (UC1 Python additionally crashes with KeyError: 'data' when hunter returns 404 without a data field).
CLI snippets (orth ...) are SKIPped — orth is not installed in this environment. The x402-fetch Node snippet is SKIPped — the x402-fetch/viem packages and PRIVATE_KEY are not available.
Required credentials
ORTHOGONAL_API_KEY — user-supplied live API key, exported into the environment and substituted for YOUR_API_KEY placeholders. No other credentials were used.
HTTP 400 {"detail":"Struct is required"} — example body omits the required struct body param documented at /v1/details. Suggested fix: add a non-empty struct object to the body.
3
bash curl POST /v1/run sixtyfour /find-email
FAIL
HTTP 400 Required — example uses lead_info, but /v1/details lists the required field as lead. Suggested fix: rename lead_info to lead.
4
bash curl POST /v1/run sixtyfour /find-phone
FAIL
HTTP 400 Required — same lead_info vs lead mismatch as above.
5
JS fetch POST /v1/search
PASS
Runs; logs results.
6
JS fetch POST /v1/run sixtyfour /enrich-lead
FAIL
Runs without throwing but prints Cost: $undefined and the API error body — body lacks required struct (HTTP 400), and the destructured price field does not exist on the response (real field is priceCents).
7
Python POST /v1/search
PASS
Runs; prints results.
8
Python POST /v1/run sixtyfour /enrich-lead
FAIL
KeyError: 'price' — /v1/run returns priceCents, not price. Body also lacks required struct.
HTTP 404 Endpoint /v1/people/match not found in API apollo. /v1/list-endpoints shows the registered apollo path is /api/v1/people/match. Suggested fix: change path to /api/v1/people/match.
2
bash curl POST /v1/run linkup /v1/search
FAIL
HTTP 404 Endpoint /v1/search not found in API linkup. Registered linkup path is /search. Suggested fix: change path to /search.
3
bash curl POST /v1/run olostep /v1/scrapes
PASS
HTTP 200; priceCents:1, returns scrape data.
4
JS fetch apollo /v1/people/match
FAIL
HTTP 404; prints Enriched for $NaN and undefined — destructured priceCents not present.
5
Python apollo /v1/people/match
FAIL
KeyError: 'priceCents' — apollo path is wrong (HTTP 404), so the error body has no priceCents.
Each numbered use case has a JS and a Python variant. All ten fail to deliver the documented chained workflow because the snippets invoke API slugs/paths that are not registered in the gateway (verified via /v1/list-endpoints): shofo (not a registered slug), apollo /v1/people/match (real path /api/v1/people/match), hunter /domain-search (real path /v2/domain-search). Some scripts complete without throwing because of ?. / .get() defensive access, but the intended workflow never executes; UC1 Python additionally crashes.
#
Snippet
Status
Notes
1
UC1 JS researchLead('stripe')
FAIL
Runs to completion, but the hunter '/domain-search' step returns HTTP 404, so topContact is undefined, the apollo enrichment is skipped, and the workflow ends with no enriched contact. Documented apollo path /v1/people/match would also have 404'd.
2
UC1 Python research_lead('stripe')
FAIL
KeyError: 'data' — hunter 404 response has no data field, and contacts['data'] in the fallback return statement throws.
3
UC2 JS analyzeCompetitor('notion.so','notionhq')
FAIL
Runs, but shofo /linkedin/company-posts returns HTTP 400 (the gateway proxies to an upstream that returns Google's HTML 400 page) because shofo is not a registered slug — verified against /v1/list-endpoints.
4
UC2 Python analyze_competitor(...)
FAIL
Same shofo failure mode as #3; script runs but social_activity is an HTML error string.
5
UC3 JS findEngagedProspects(...)
FAIL
Inside the loop, shofo /linkedin/user-posts is unreachable (slug missing). Workflow cannot deliver "engaged" recent posts as documented.
6
UC3 Python find_engaged_prospects(...)
FAIL
Same shofo missing-slug failure as #5.
7
UC4 JS abmResearch('figma.com')
FAIL
Inside the loop, apollo /v1/people/match returns HTTP 404 (wrong path; real is /api/v1/people/match), so every enrichment step fails and no decision-maker contacts are produced.
8
UC4 Python abm_research('figma.com')
FAIL
Same wrong apollo path as #7.
9
UC5 JS monitorTriggers('stripe','stripe.com')
FAIL
First step shofo /x/user-posts returns the gateway's upstream HTML 400 because shofo is not registered.