Gaffa Documentation Audit
Gaffa's docs are unusually agent-friendly in form — llms.txt and llms-full.txt exist, every page has a .md twin, and endpoint pages embed OpenAPI JSON — but the content has drifted: the canonical pricing page is a price cut behind the changelog and homepage, the free-tier story contradicts itself across surfaces, a tutorial aimed at AI assistants recommends action types that don't exist, and placeholder text ("jjjj") is live on four API reference pages.
1. Pricing page charges double the current proxy bandwidth rate (critical)
Location: https://gaffa.dev/docs/credits-and-pricing vs https://gaffa.dev/docs/changelog/2026/june and https://gaffa.dev/#pricing
Problem: The canonical Credits and Pricing page says proxy requests "are billed at 1500 credits per 1GB of bandwidth used." The June 2026 changelog announces the opposite: "Residential proxy bandwidth is now billed at 750 credits per GB, down from 1,500," and the homepage pricing FAQ agrees ("billed at 750 credits per 1GB"). The dedicated pricing doc was never updated after the cut.
Consequence: A 2x discrepancy on the single number that dominates scraping costs. Developers budgeting from the docs page overestimate proxy costs by 100%; an AI assistant fed llms-full.txt (which contains both figures) will pick one at random. The same page also omits the current gpt-4o-mini token rates that the homepage FAQ lists, so "Credits and Pricing" is the least accurate pricing source Gaffa publishes.
The fix: Update Credits and Pricing to 750 credits/GB, add the AI token pricing that the homepage FAQ already shows, and make the changelog PR checklist include the pricing page.
2. Free tier: homepage says "open internet right now," docs say paid plan + queue (critical)
Location: https://gaffa.dev/ vs https://gaffa.dev/docs/get-started and https://gaffa.dev/docs/features/browser-requests/parameters
Problem: The homepage says: "Every new account gets 500 free credits… Start building real automations on the open internet right now, not just a demo" and its FAQ repeats "run automations on the open internet right away, not just our demo site." The Get Started doc says the opposite: "When you're ready to use Gaffa on the open web, you'll need to choose a plan that suits your needs and pay for it," plus a warning that new accounts sit in an approval queue. The Parameters page adds: "In order to access public sites and use proxy servers, you'll need to sign up for a paid account."
Consequence: A developer's first decision — "can I try this on my own target site for free?" — has two contradictory answers depending on which page they land on. Either the docs are stale or the homepage overpromises; both cost signups or support tickets.
The fix: Decide what the free 500 credits can actually reach and state it identically on the homepage, Get Started, and Parameters. If the queue/paid-plan gating is gone, delete those hints.
3. LLMs.txt tutorial tells AI assistants to use actions that don't exist (critical)
Location: https://gaffa.dev/docs/tutorials/using-the-gaffa-llms.txt-file-with-your-ai-assistant (Step 5)
Problem: "Using the Gaffa LLMs.txt File with Your AI Assistant" ends with: "you can modify the actions list to use other supported operations, such as generate_pdf, screenshot, or extract_text." None of these are Gaffa action types — the documented actions are print, capture_screenshot, and generate_markdown.
Consequence: This page exists specifically to be pasted into ChatGPT/Claude as ground truth, and it ships in llms-full.txt. An assistant that trusts it will emit requests with invalid type values that fail validation — the exact failure mode the tutorial was written to prevent.
The fix: Replace the fabricated names with real action types: print, capture_screenshot, capture_dom.
4. max_cache_age is seconds on one page, milliseconds on another (significant)
Location: https://gaffa.dev/docs/features/browser-requests/tips-and-best-practices vs https://gaffa.dev/docs/features/browser-requests/parameters
Problem: The Parameters page defines max_cache_age as "a number in seconds equal to or greater than 0," and the OpenAPI spec agrees ("The maximum age of a cached result in seconds"). The Tips & Best Practices FAQ says: "Set max_cache_age in milliseconds to say how old a result you'll accept."
Consequence: A 1000x unit error. A developer following the FAQ who wants a 1-hour cache sends 3,600,000 — which the API reads as ~41 days of acceptable staleness. Cache bugs like this are silent: requests succeed, data is just quietly wrong.
The fix: Correct the Tips FAQ to seconds and add the unit to every mention of the parameter.
5. Placeholder text "jjjj" published on four API reference pages (significant)
Location: https://gaffa.dev/docs/api-reference/post-v1-schemas (and the PUT/GET/DELETE schema pages)
Problem: The live pages for POST v1/schemas, PUT v1/schemas, GET v1/schemas, and DELETE v1/schemas/{id} each open with the literal string "jjjj" as their first body line (verified on https://gaffa.dev/docs/api-reference/post-v1-schemas.md).
Consequence: Beyond looking abandoned, this junk string sits at the top of the exact pages an agent fetches to learn the Schema API, and it signals nobody has proofread the reference section — which matches finding 6's schema-type mismatch on the same pages.
The fix: Delete the placeholder and add a lint/CI check for placeholder patterns in published docs.
6. Schema field type is a string in every example but an integer enum in the OpenAPI spec (significant)
Location: https://gaffa.dev/docs/api-reference/post-v1-schemas vs https://gaffa.dev/docs/features/browser-requests/actions/parse-json
Problem: All parse_json documentation and curl examples define schema fields with string types — "type": "string", "type": "decimal", "type": "boolean" — and the docs list eight named types. The embedded OpenAPI spec on the schema endpoints defines schemaField.type as "enum":[0,1,2,3,4,5,6,7],"type":"integer" with no mapping from number to name.
Consequence: An agent or codegen tool that consumes the OpenAPI JSON (advertised as "interactive API definitions") will produce integer type values, and a developer reading the spec has no way to know whether 3 means decimal or datetime. One of the two representations must be wrong or lossy.
The fix: Make the spec match the accepted payloads (string enum of the eight documented names), or document the integer↔name mapping.
7. max_pages is recommended throughout parse_json docs but missing from its parameters table (significant)
Location: https://gaffa.dev/docs/features/browser-requests/actions/parse-json
Problem: The parse_json page's tips say "Use input_token_cap and max_pages to keep costs predictable," two FAQs repeat it ("Use max_pages to limit how many pages get sent to the model"), and the 2025 changelog lists max_pages as a shipped parameter. The page's Parameters table documents only data_schema_id, data_schema, instruction, model, input_token_cap, selector, and output_type — no max_pages, no type, no default. The same table also marks both data_schema_id and data_schema as Required=true while the prose says to send "one or the other," and the "Inline Schema Example" contains a trailing comma after the data_schema object, making the JSON invalid as written.
Consequence: The one paid action — where cost control matters most — has its main PDF cost-control knob undocumented, a required-fields contradiction that breaks strict request builders, and a copy-paste example that fails JSON parsing.
The fix: Add a max_pages row (type, default, PDF-only scope), mark the two schema parameters as "one of," and remove the trailing comma.
8. Dead links in changelog and Introduction, including two 404 destinations for shipped tutorials (significant)
Location: https://gaffa.dev/docs/changelog/2026/q1, https://gaffa.dev/docs, https://gaffa.dev/docs/tutorials/capture-a-full-height-screenshot-of-a-webpage
Problem: The Q1 2026 changelog links "Read the tutorial" — 404. The same page links "Read the post" — 404 (the live post is at ...-python-(the-easy-way)). The Introduction and the Infinitely Scroll example expose GitBook-internal refs like /pages/kzTlst3tKo255yz4YpDi in their markdown versions — https://gaffa.dev/pages/kzTlst3tKo255yz4YpDi returns 404. A tutorial also links the Browser Requests docs via the stale off-domain space https://gaffa-1.gitbook.io/gaffa/features/browser-requests instead of gaffa.dev/docs.
Consequence: The changelog is the page users check to adopt new features, and both of its deep links to the form-filling tutorial and table-scraping post are dead. The /pages/... refs 404 for every agent consuming the advertised .md surface.
The fix: Point the changelog links at the live tutorial/blog URLs, replace /pages/... content-refs with /docs/... paths, and swap the gitbook.io link for the canonical domain.
9. time_limit default: OpenAPI says 60000, Settings page says "your plan's maximum" (significant)
Location: https://gaffa.dev/docs/features/browser-requests/settings vs https://gaffa.dev/docs/api-reference/post-v1-browser-requests and https://gaffa.dev/docs/features/browser-requests/parameters
Problem: The Settings page says of time_limit: "This value cannot exceed your plan's maximum request runtime. If omitted, it defaults to that maximum" — with plan maximums of 120,000 ms (Startup) and 300,000 ms (Growth). The OpenAPI spec on POST v1/browser/requests says "Cap the maximum time the request should take to complete, in milliseconds (default: 60000)". The Parameters page separately claims the settings object "currently supports four parameters" while the Settings page documents six (record_request, max_media_bandwidth, time_limit, block_ads, log_redirects, actions), and log_redirects is absent from the OpenAPI browserRequestSettings_object entirely.
Consequence: A Growth customer omitting time_limit expects 5 minutes per the Settings page but gets 60 s if the spec is right — long pagination loops die mid-run. Agents reading the spec never discover log_redirects exists.
The fix: Reconcile the default (per-plan or 60000) in both places, update "four parameters" to the real count, and add log_redirects to the OpenAPI schema.
10. Getting-started tutorial's Python script fails three ways before it runs (significant)
Location: https://gaffa.dev/docs/tutorials/convert-any-webpage-into-llm-ready-markdown-using-gaffa
Problem: "Convert any webpage into LLM-ready Markdown" imports only requests and openai but calls os.getenv(...) (NameError: os is not defined). Its final block indents the entry point as if __name__ == "__main__": with a leading space (IndentationError). And it calls openai.ChatCompletion.create(...), which was removed in openai v1.0 — while the prerequisites say pip install requests openai, which installs v1+.
Consequence: A newcomer following the flagship tutorial hits a NameError, then an IndentationError, then a deprecated-API crash — none of them Gaffa's actual API, all of them Gaffa's docs. Agents extracting the snippet inherit all three bugs verbatim.
The fix: Add import os, fix the indentation, and port the OpenAI call to the v1 client (from openai import OpenAI), or pin openai<1 in the install step.
11. No error responses documented anywhere in the API reference (significant)
Location: https://gaffa.dev/docs/api-reference/post-v1-browser-requests and all endpoint pages; https://gaffa.dev/docs/api-reference/api-authentication
Problem: The OpenAPI definitions embedded in the endpoint pages document only success responses — e.g. POST v1/schemas defines a single "200" response, and the POST v1/browser/requests schema documents no error statuses. The API Authentication page explains how to send X-API-Key but never says what an invalid or deleted key returns, and no page documents 401/403, validation failures, or rate limits.
Consequence: Developers can't write error handling from the docs: they don't know what a bad API key returns or what an over-limit request looks like. Agents generating clients from the embedded specs assume every call returns 200.
The fix: Add 4xx/5xx responses to the embedded OpenAPI specs and publish an error-code reference covering auth failures, validation errors, and rate limits.
12. capture_element: outerHTML in the intro, innerHTML in the usage example (minor)
Location: https://gaffa.dev/docs/features/browser-requests/actions/capture-element
Problem: The page opens: "Returns the outerHTML of the element matching your selector, the element itself, and everything inside it." Its Usage section says the same example will "return an HTML file containing the div's innerHTML."
Consequence: outerHTML vs innerHTML determines whether the element's own tag and attributes are included — exactly what a scraper post-processing the output needs to know before writing parsing code.
The fix: Change the Usage sentence to outerHTML to match the intro and FAQ.
13. Parameter tables contradict their own required/default flags (minor)
Location: https://gaffa.dev/docs/features/browser-requests/actions/wait and https://gaffa.dev/docs/features/browser-requests/actions/scroll
Problem: The wait page says "When using selector, timeout is required" yet gives timeout a default of 5,000 ms — a parameter with a documented default can't also be required. The scroll page marks percentage Required=true while giving it "Default: 100 (% - scroll to bottom)."
Consequence: Strict request builders (and agents mapping tables to validation logic) can't tell which fields must be sent; a human just guesses.
The fix: Pick one semantics per parameter — optional-with-default or required — and fix the flags.
14. download_file supported-formats list omits .jpeg, which the changelog says shipped (minor)
Location: https://gaffa.dev/docs/features/browser-requests/actions/download-file vs https://gaffa.dev/docs/changelog/2025/2025-changelog
Problem: The download_file page (twice) lists supported types as ".pdf, .jpg, .png, .gif, .bmp, .webp, .svg, .tiff, .tif, .img". The 2025 changelog says Q3 shipped "expanded file type support (.pdf, .jpg, .jpeg, .png, .gif, .bmp, .webp, .svg, .tiff, .tif, .img)".
Consequence: .jpeg is a common extension for the same format; a developer checking the feature page will conclude their target file is unsupported and build a workaround they don't need.
The fix: Sync the list on the action page with the shipped set.
15. Shipped editorial debris: "(TODO)", "customer header", "Prerequistes" (minor)
Location: https://gaffa.dev/docs/features/browser-requests/api-playground-examples/infinitely-scroll-an-e-commerce-site, https://gaffa.dev/docs/api-reference/api-authentication, https://gaffa.dev/docs/tutorials/convert-any-webpage-into-llm-ready-markdown-using-gaffa
Problem: The Infinitely Scroll example page ends with "Read more about screen recording here. (TODO)". API Authentication says "Our API is secured with a customer header X-API-Key" (presumably "custom header"). The markdown tutorial's prerequisites heading reads "Prerequistes".
Consequence: Individually trivial; collectively — alongside "jjjj" — they tell readers the docs aren't reviewed before publish, which undermines trust in the load-bearing pages too.
The fix: One proofreading pass, plus a TODO/placeholder grep in CI.
What they do well
- First-class agent surface: llms.txt + llms-full.txt,
.mdversions of every page, and an?ask=query endpoint for dynamic doc questions. - Action pages have a consistent, genuinely useful shape: parameters table, runnable JSON usage, tips, and FAQs that answer real debugging questions (loop failure semantics are documented in unusual depth).
- Real, runnable playground examples against a purpose-built demo site, cross-linked from every feature page.
Top 3 recommendations
- Reconcile pricing and access claims across homepage, Credits and Pricing, and Get Started — the two numbers developers act on first (cost per GB, what the free tier can reach) currently have two answers each.
- Fix the machine-readable layer agents actually consume: correct the fabricated action names in the llms.txt tutorial, the seconds/milliseconds cache contradiction, the integer-enum schema types, and add
log_redirects+ error responses to the OpenAPI specs. - Add a docs CI pass: link checker (two changelog 404s,
/pages/...refs), placeholder/TODO grep ("jjjj", "(TODO)"), and Python syntax check on tutorial snippets.