TypeScript 97.5%
SQL 1.4%
Python 0.8%
1import type { Metadata } from "next";2import Link from "next/link";3import { Badge } from "@/components/ui/badge";4import { Button } from "@/components/ui/button";5import { formatDateOnly } from "@/lib/format";6import { cn } from "@/lib/utils";78export const metadata: Metadata = {9 title: "Changelog",10 description: "Release notes for the Fetcha web access API, dashboard and SDKs. What shipped, what changed, and what is not available yet.",11 alternates: { canonical: "/changelog" },12};1314type ChangeKind = "added" | "changed" | "fixed" | "not-yet";1516type Entry = {17 date: string; // ISO date18 version: string;19 title: string;20 tag?: "preview" | "stable" | "beta";21 summary: string;22 changes: Array<{ kind: ChangeKind; text: string }>;23};2425const ENTRIES: Entry[] = [26 {27 date: "2026-09-08",28 version: "v0.2.1",29 title: "Turnstile captcha solving, real Chrome",30 tag: "stable",31 summary: "The managed browser now solves interactive Cloudflare Turnstile challenges through a human-verification service and runs on a real Google Chrome build.",32 changes: [33 { kind: "added", text: "Captcha solver (2captcha) for Cloudflare Turnstile: widget parameters are intercepted in-page, a token is obtained and injected with the matching user agent. New request field solve_captcha (default true); attempts report captcha_solved in metadata.debug." },34 { kind: "changed", text: "Managed browser runs Google Chrome (stable channel) as a real window on the node instead of the headless Chromium build, with the native user agent and client hints kept coherent." },35 { kind: "changed", text: "Admin → System lists the anti-bot configuration (solver key presence, browser channel and mode); /internal/browser exposes solver statistics." },36 ],37 },38 {39 date: "2026-09-08",40 version: "v0.2.0",41 title: "Private access, managed browser, crawl & map",42 tag: "stable",43 summary: "Fetcha becomes a private, invitation-only platform with a single unlimited plan, and the browser layer goes live: rendered fetches with automatic escalation from plain HTTP, hardened HTTP fingerprints, much broader block detection, Markdown output with page metadata and links, and a new Crawl & Map API with its dashboard page.",44 changes: [45 { kind: "changed", text: "Private access: signup is invitation-only (administrators manage the allowlist and send invitations). One plan, unlimited: no request quota, 200 concurrent requests, 120 s max timeout, 5 retries, all network classes, 90-day retention, browser rendering and crawl jobs. No billing, no checkout." },46 { kind: "added", text: "Managed browser rendering: browser: true renders the page in a headless Chromium on the same network, country and session; wait_for, wait_ms, wait_until, javascript, block_resources and screenshot control the render. 8 concurrent renders per organization; BROWSER_TIMEOUT when a page does not settle." },47 { kind: "added", text: "Automatic escalation: with browser_fallback (default on), an HTTP attempt blocked by a JavaScript challenge is retried in the browser. metadata.mode reports http or browser; debug attempts carry mode and block_reason." },48 { kind: "changed", text: "Hardened HTTP fingerprints: ordered Chrome, Firefox and Safari header profiles rotated per attempt, HTTP/2, Chrome-like TLS cipher ordering, a cookie jar carried across redirects, jittered backoff between retries and Retry-After honoured within the timeout. referer: auto | none | url controls the Referer strategy." },49 { kind: "changed", text: "Expanded block detection: Cloudflare challenge and Turnstile, DataDome, PerimeterX / HUMAN, Akamai, Kasada, Imperva, AWS WAF, Vercel attack mode and soft 200 blocks (challenge, interstitial or empty-shell pages returned with a 200)." },50 { kind: "added", text: "format: \"markdown\" returns the page as Markdown (main content first, boilerplate removed) in markdown. Every HTML response now includes page { title, description, canonical, lang, og, links_count }; links: true adds links[] with absolute URLs, anchor text, internal and nofollow flags." },51 { kind: "added", text: "Crawl API: POST /v1/crawl starts an asynchronous job (max_pages, max_depth, include/exclude patterns, robots, sitemap, concurrency, delay, format, browser); GET /v1/crawl, GET /v1/crawl/:id, GET /v1/crawl/:id/pages (cursor pagination, content per page) and DELETE /v1/crawl/:id. 2,000 pages per job, 5 concurrent jobs. Every page is a normal request in the log with source crawl." },52 { kind: "added", text: "Map API: POST /v1/map lists a site's URLs from its sitemap and links, synchronously, with search filtering." },53 { kind: "added", text: "Dashboard: Crawls page (jobs, status, stats, create dialog, job detail with pages and content preview, cancel, Map tool). Playground: markdown format, live Browser rendering section, Include links, Referer, mode badge, page metadata, Markdown / Links / Screenshot tabs." },54 { kind: "added", text: "SDKs 0.2.0 (JavaScript and Python): markdown format, browser options, links and referer on fetch; crawl.create / get / pages / cancel / wait and map(). User-Agent fetcha-sdk-js/0.2.0 and fetcha-sdk-python/0.2.0." },55 { kind: "changed", text: "BROWSER_UNAVAILABLE is now only returned when the browser pool is disabled or unavailable. New codes CRAWL_NOT_FOUND (404) and CRAWL_LIMIT_REACHED (429)." },56 { kind: "not-yet", text: "Browser actions (POST /v1/browser), structured extraction (/v1/extract), webhook delivery (crawl webhook_url is stored, not called), datacenter, ISP and mobile network classes, teams, OAuth, 2FA, CLI." },57 ],58 },59 {60 date: "2026-09-07",61 version: "v0.1.0",62 title: "Public preview",63 tag: "preview",64 summary: "First public release of Fetcha. One endpoint, automatic routing over the residential network, sticky sessions, and a dashboard to try requests and read logs. Billing checkout was intentionally not part of this release; every account ran on the Free plan (superseded by the single unlimited plan in 0.2.0).",65 changes: [66 { kind: "added", text: "Fetch API: POST /v1/fetch with country, region and city targeting, custom headers, cookies, body, timeout, format (html, text, json, raw), redirect controls and per-request retries." },67 { kind: "added", text: "Automatic routing (network: \"auto\"): weighted per-domain scoring, circuit breakers and cheap-to-premium escalation with a fresh IP on every retry." },68 { kind: "added", text: "Residential network class, live in 45 target countries. auto resolves to residential during the preview." },69 { kind: "added", text: "Sticky sessions: POST /v1/sessions with TTL 60–1,800 s, labels, Idempotency-Key support; list, inspect and close sessions." },70 { kind: "added", text: "Playground in the dashboard: run real requests, inspect attempts and timings, copy generated cURL, JavaScript and Python." },71 { kind: "added", text: "Request logs with per-request id (X-Fetcha-Request-ID), status, network, attempts, latency and bytes. Sensitive headers redacted before storage." },72 { kind: "added", text: "Usage metering: monthly requests and residential bandwidth per project, GET /v1/usage and GET /v1/me." },73 { kind: "added", text: "SDK source for JavaScript/TypeScript (@fetcha/sdk) and Python (fetcha). Install from the repository until the packages are published to npm and PyPI." },74 { kind: "added", text: "Documentation: quickstart, authentication, fetch reference, networks, geolocation, sessions, errors, retries, rate limits, SDKs and examples." },75 { kind: "added", text: "Public status page with 24-hour success rate and 30-day history, and this changelog." },76 { kind: "not-yet", text: "Browser execution (browser: true returns BROWSER_UNAVAILABLE), structured extraction (/v1/extract), datacenter, ISP and mobile network classes." },77 { kind: "not-yet", text: "Billing checkout and self-serve upgrades (email sales@fetcha.co), teams and invitations, webhook delivery, OAuth login, 2FA and passkeys, CLI." },78 ],79 },80];8182const KIND_META: Record<ChangeKind, { label: string; variant: "success" | "info" | "warning" | "outline" }> = {83 added: { label: "Added", variant: "success" },84 changed: { label: "Changed", variant: "info" },85 fixed: { label: "Fixed", variant: "warning" },86 "not-yet": { label: "Not yet", variant: "outline" },87};8889export default function ChangelogPage() {90 return (91 <div className="container-page py-14 sm:py-20">92 <header className="max-w-2xl">93 <p className="text-[11.5px] font-semibold uppercase tracking-[0.12em] text-accent">Changelog</p>94 <h1 className="mt-2 text-[34px] font-semibold leading-[1.1] tracking-tight sm:text-[44px]">What shipped, and what has not.</h1>95 <p className="mt-4 text-[15.5px] leading-relaxed text-fg-muted">96 Release notes for the API, dashboard and SDKs. We list unavailable features explicitly so you never build against something that does not exist yet. Follow the <Link href="/status" className="text-accent underline-offset-4 hover:underline">status page</Link> for incidents. Fetcha is invitation-only: <a href="mailto:hello@fetcha.co?subject=Fetcha%20access" className="text-accent underline-offset-4 hover:underline">request access</a> or <Link href="/login" className="text-accent underline-offset-4 hover:underline">log in</Link>.97 </p>98 </header>99100 <ol className="mt-14 relative" aria-label="Releases">101 <div className="absolute left-[7px] top-2 bottom-2 w-px bg-border sm:left-[151px]" aria-hidden />102 {ENTRIES.map((e) => (103 <li key={e.version} className="relative grid gap-4 pb-16 last:pb-0 sm:grid-cols-[120px_minmax(0,1fr)] sm:gap-12">104 <div className="flex items-center gap-3 pl-6 sm:block sm:pl-0 sm:text-right">105 <time dateTime={e.date} className="font-mono text-[13px] tabular text-fg-muted">106 {formatDateOnly(e.date)}107 </time>108 <span className="absolute left-0 top-[7px] size-[15px] rounded-full border-2 border-accent bg-bg sm:left-[144px]" aria-hidden />109 </div>110 <article className="min-w-0 pl-6 sm:pl-0" aria-labelledby={`rel-${e.version}`}>111 <div className="flex flex-wrap items-center gap-2">112 <Badge variant="solid" className="font-mono">113 {e.version}114 </Badge>115 {e.tag ? <Badge variant={e.tag === "stable" ? "success" : "accent"}>{e.tag === "preview" ? "Public preview" : e.tag === "beta" ? "Beta" : "Stable"}</Badge> : null}116 </div>117 <h2 id={`rel-${e.version}`} className="mt-3 text-[24px] font-semibold tracking-tight">118 {e.title}119 </h2>120 <p className="mt-2 max-w-2xl text-[14.5px] leading-relaxed text-fg-muted">{e.summary}</p>121 <ul className="mt-6 divide-y divide-border rounded-lg border border-border">122 {e.changes.map((c, i) => (123 <li key={i} className={cn("flex items-start gap-3 px-4 py-3 text-[14px] leading-relaxed", c.kind === "not-yet" && "bg-bg-subtle/60 text-fg-muted")}>124 <Badge variant={KIND_META[c.kind].variant} className="mt-0.5 w-[74px] shrink-0 justify-center">125 {KIND_META[c.kind].label}126 </Badge>127 <span className="min-w-0 text-fg [&_code]:font-mono">{c.text}</span>128 </li>129 ))}130 </ul>131 </article>132 </li>133 ))}134 </ol>135136 <section className="mt-20 flex flex-col items-start justify-between gap-4 rounded-lg border border-border bg-bg-subtle/60 p-6 sm:flex-row sm:items-center">137 <div>138 <h2 className="text-[16px] font-semibold tracking-tight">Missing something you need?</h2>139 <p className="mt-1 text-[13.5px] text-fg-muted">Roadmap priorities follow real usage. Tell us what would unblock you.</p>140 </div>141 <Button asChild variant="outline">142 <a href="mailto:support@fetcha.co?subject=Fetcha%20feature%20request">Send feedback</a>143 </Button>144 </section>145 </div>146 );147}148