TypeScript 97.5%
SQL 1.4%
Python 0.8%
1import type { Metadata } from "next";2import { CodeBlock } from "@/components/ui/code-block";3import { DocPage } from "@/components/docs/doc-page";4import { A, Code, H2, H3, Li, P, Strong, Table, TBody, Td, Th, THead, Tr, Ul } from "@/components/docs/prose";5import { Callout } from "@/components/docs/callout";6import { ParamTable } from "@/components/docs/param-table";7import { CodeTabs } from "@/components/docs/code-tabs";89export const metadata: Metadata = {10 title: "SDKs",11 description: "Official JavaScript/TypeScript and Python clients for Fetcha: installation from source, configuration, fetch/text/json helpers, sessions, usage, error classes.",12};1314const JS_INSTALL = `# From a checkout of the Fetcha repository, inside your project:15pnpm add ./packages/sdk # or: npm install ./packages/sdk1617# Or simply copy the single source file into your codebase:18cp packages/sdk/src/index.ts src/lib/fetcha.ts`;1920const PY_INSTALL = `# From a checkout of the Fetcha repository:21pip install ./sdk-python2223# Requirements: Python >= 3.9, httpx >= 0.27 (installed automatically)`;2425const JS_USAGE = `import { Fetcha, FetchaError } from "@fetcha/sdk";2627const fetcha = new Fetcha({ apiKey: process.env.FETCHA_API_KEY! });2829// Full control: every /v1/fetch field is available30const r = await fetcha.fetch({ url: "https://example.com/products/42", country: "CA", format: "html", retries: 2 });31console.log(r.success, r.status, r.metadata.network, r.metadata.attempts);32console.log(r.content?.slice(0, 200));3334// Readable text (format: "text")35const text = await fetcha.text("https://example.com/about", { country: "CA" });3637// Parsed JSON (format: "json"); the generic sets the return type38type Price = { amount: number; currency: string };39const price = await fetcha.json<Price>("https://api.example.com/v1/price?sku=42");`;4041const PY_USAGE = `from fetcha import Fetcha, FetchaError4243client = Fetcha(api_key=os.environ["FETCHA_API_KEY"])4445# Full control: every /v1/fetch field is a keyword argument46r = client.fetch("https://example.com/products/42", country="CA", format="html", retries=2)47print(r.success, r.status, r.metadata["network"], r.metadata["attempts"])48print(r.content[:200])4950# Readable text (format="text")51text = client.text("https://example.com/about", country="CA")5253# Parsed JSON (format="json")54price = client.json("https://api.example.com/v1/price?sku=42")5556# The client owns an httpx.Client; close it when done (or use it as a context manager)57with Fetcha(api_key=os.environ["FETCHA_API_KEY"]) as c:58 print(c.me()["project"]["name"])`;5960const JS_SESSIONS = `// Create (optionally idempotent), use, inspect, list, close61const s = await fetcha.sessions.create({ country: "CA", ttl: 900, label: "user-42" }, "user-42-login");62const page = await fetcha.fetch({ url: "https://www.example.ca/account", session: s.id, cookies: { sid: "…" } });6364const same = await fetcha.sessions.get(s.id); // { id, status, network, country, expires_at, created_at, … }65const all = await fetcha.sessions.list(); // { data: Session[] }66await fetcha.sessions.close(s.id); // { id, status: "closed" }`;6768const PY_SESSIONS = `# Create, use, inspect, list, close69s = client.sessions.create(country="CA", ttl=900, label="user-42")70page = client.fetch("https://www.example.ca/account", session=s.id, cookies={"sid": "…"})7172same = client.sessions.get(s.id) # Session dataclass; .raw has the full payload73all_sessions = client.sessions.list() # list[Session]74client.sessions.close(s.id) # {"id": ..., "status": "closed"}`;7576const JS_ACCOUNT = `const me = await fetcha.me();77// { project: { id, name }, organization: { id, name, plan }, key: { id, name, scopes } }7879const usage = await fetcha.usage.current();80// { period_start, plan, organization, project, remaining_requests } (see Rate limits)`;8182const PY_ACCOUNT = `me = client.me()83# {"project": {...}, "organization": {..., "plan": "developer"}, "key": {..., "scopes": [...]}}8485usage = client.usage()86# {"period_start": ..., "plan": ..., "organization": ..., "project": ..., "remaining_requests": ...}`;8788const JS_ERRORS = `try {89 await fetcha.fetch({ url: "http://169.254.169.254/latest/meta-data/" });90} catch (e) {91 if (e instanceof FetchaError) {92 e.code; // "URL_NOT_ALLOWED"93 e.status; // 40094 e.message; // "Requests to local, private or internal hosts are not allowed."95 e.requestId; // "req_…" | null96 }97}`;9899const PY_ERRORS = `try:100 client.fetch("http://169.254.169.254/latest/meta-data/")101except FetchaError as e:102 e.code # "URL_NOT_ALLOWED"103 e.status # 400104 e.message # "Requests to local, private or internal hosts are not allowed."105 e.request_id # "req_…" or None106 str(e) # "URL_NOT_ALLOWED: Requests to local, private or internal hosts are not allowed."`;107108export default function SdksPage() {109 return (110 <DocPage path="/docs/sdks" eyebrow="Tools" title="SDKs" description="Thin, typed clients over the REST API for JavaScript/TypeScript and Python. They add no behaviour of their own: every option maps 1:1 to a request field, and every error maps to an API error code." status="Beta">111 <Callout variant="warning" title="Not yet published to npm or PyPI">112 Both SDKs exist as source in the Fetcha repository (<Code>packages/sdk</Code> and <Code>sdk-python</Code>) and are installed from there until the packages are published. The CLI is not113 available yet. Package names are reserved: <Code>@fetcha/sdk</Code> on npm and <Code>fetcha</Code> on PyPI.114 </Callout>115116 <Table>117 <THead>118 <Tr>119 <Th>SDK</Th>120 <Th>Package</Th>121 <Th>Version</Th>122 <Th>Dependencies</Th>123 <Th>Runtime</Th>124 </Tr>125 </THead>126 <TBody>127 <Tr>128 <Td className="font-medium text-fg">JavaScript / TypeScript</Td>129 <Td mono>@fetcha/sdk</Td>130 <Td mono>0.1.0</Td>131 <Td>None (uses the global <Code>fetch</Code>)</Td>132 <Td>Node 18+, Bun, Deno, edge runtimes; ESM; ships TypeScript source</Td>133 </Tr>134 <Tr>135 <Td className="font-medium text-fg">Python</Td>136 <Td mono>fetcha</Td>137 <Td mono>0.1.0</Td>138 <Td>139 <Code>httpx>=0.27</Code>140 </Td>141 <Td>Python 3.9+; synchronous client</Td>142 </Tr>143 </TBody>144 </Table>145 <P>146 Requests made through the SDKs are tagged with source <Code>sdk</Code> in your request log (the SDKs send a <Code>fetcha-sdk-js/…</Code> or <Code>fetcha-sdk-python/…</Code> User-Agent147 to the Fetcha API; this does not affect the User-Agent sent to targets).148 </P>149150 <H2>Installation</H2>151 <CodeTabs152 tabs={[153 { label: "JavaScript", lang: "bash", code: JS_INSTALL },154 { label: "Python", lang: "bash", code: PY_INSTALL },155 ]}156 />157 <P>158 The JavaScript package's entry point is its TypeScript source (<Code>src/index.ts</Code>), so use it from a TypeScript-aware toolchain (tsx, Bun, Deno, Vite, Next.js, esbuild) or copy the159 file into your own build. It has zero runtime dependencies.160 </P>161162 <H2>Configuration</H2>163 <H3>JavaScript</H3>164 <ParamTable165 rows={[166 { name: "apiKey", type: "string", required: true, description: <>Your <code>fch_live_…</code> or <code>fch_test_…</code> key. The constructor throws if it is empty.</> },167 { name: "baseUrl", type: "string", default: '"https://www.fetcha.co"', description: <>API origin. Trailing slash is removed.</> },168 { name: "clientTimeout", type: "number (ms)", default: "150000", description: <>Client-side abort applied on top of the API's own <code>timeout</code>. Keep it above the largest <code>timeout</code> you send.</> },169 { name: "fetch", type: "typeof fetch", default: "globalThis.fetch", description: <>Custom fetch implementation (proxies, instrumentation, tests).</> },170 ]}171 />172 <H3>Python</H3>173 <ParamTable174 rows={[175 { name: "api_key", type: "str", required: true, description: <>Your API key. <code>ValueError</code> if empty.</> },176 { name: "base_url", type: "str", default: '"https://www.fetcha.co"', description: <>API origin.</> },177 { name: "timeout", type: "float (seconds)", default: "150.0", description: <>httpx timeout for the HTTP call to Fetcha.</> },178 ]}179 />180181 <H2>Fetching</H2>182 <P>183 <Code>fetch</Code> takes the same fields as <A href="/docs/fetch">POST /v1/fetch</A> and returns the response document unchanged. <Code>text</Code> and <Code>json</Code> are shortcuts that set{" "}184 <Code>format</Code> and unwrap the corresponding field.185 </P>186 <CodeTabs187 tabs={[188 { label: "JavaScript", lang: "typescript", code: JS_USAGE },189 { label: "Python", lang: "python", code: PY_USAGE },190 ]}191 />192 <Ul>193 <Li>194 JavaScript: <Code>fetch(options): Promise<FetchResult></Code>, <Code>text(url, options?)</Code> returns <Code>string</Code> (empty when the body was binary),{" "}195 <Code>json<T>(url, options?)</Code> returns <Code>T</Code> (<Code>undefined</Code> when the body did not parse).196 </Li>197 <Li>198 Python: <Code>fetch(url, **options)</Code> returns a <Code>FetchResult</Code> dataclass (<Code>request_id</Code>, <Code>success</Code>, <Code>status</Code>, <Code>url</Code>,{" "}199 <Code>final_url</Code>, <Code>content</Code>, <Code>content_type</Code>, <Code>headers</Code>, <Code>cookies</Code>, <Code>metadata</Code>, <Code>text</Code>, <Code>json</Code>, plus{" "}200 <Code>raw</Code> with the untouched payload). <Code>text()</Code> returns <Code>str</Code>, <Code>json()</Code> returns the parsed value or <Code>None</Code>.201 </Li>202 <Li>203 A fetch that reached the target never raises, even when <Code>success</Code> is <Code>false</Code>. Check it.204 </Li>205 </Ul>206207 <H2>Sessions</H2>208 <CodeTabs209 tabs={[210 { label: "JavaScript", lang: "typescript", code: JS_SESSIONS },211 { label: "Python", lang: "python", code: PY_SESSIONS },212 ]}213 />214 <P>215 The JavaScript <Code>sessions.create(options, idempotencyKey?)</Code> second argument sets the <Code>Idempotency-Key</Code> header. The Python client does not expose the header yet; pass216 your own de-duplication logic or call the REST endpoint directly for idempotent creation. See <A href="/docs/sessions">Sessions</A>.217 </P>218219 <H2>Account and usage</H2>220 <CodeTabs221 tabs={[222 { label: "JavaScript", lang: "typescript", code: JS_ACCOUNT },223 { label: "Python", lang: "python", code: PY_ACCOUNT },224 ]}225 />226227 <H2>Errors</H2>228 <P>229 Any non-2xx response is raised as <Code>FetchaError</Code> with the API's <Code>code</Code>, the HTTP <Code>status</Code>, the <Code>message</Code> and the request id. If the response230 body is not a Fetcha envelope (for example a gateway error), <Code>code</Code> is <Code>{`"HTTP_ERROR"`}</Code>. Transport failures (DNS, connection reset, client-side timeout) surface as the231 runtime's native error, not as <Code>FetchaError</Code>.232 </P>233 <CodeTabs234 tabs={[235 { label: "JavaScript", lang: "typescript", code: JS_ERRORS },236 { label: "Python", lang: "python", code: PY_ERRORS },237 ]}238 />239240 <H2>Types</H2>241 <P>The JavaScript SDK exports the following types; they mirror the API exactly.</P>242 <CodeBlock243 lang="typescript"244 code={`import type {245 FetchOptions, // request fields of POST /v1/fetch246 FetchResult, // response document247 FetchaNetwork, // "auto" | "datacenter" | "residential" | "isp" | "mobile"248 FetchaFormat, // "html" | "text" | "json" | "raw"249 SessionOptions, // body of POST /v1/sessions250 Session, // session object251 FetchaClientOptions, // constructor options252} from "@fetcha/sdk";`}253 />254255 <H2>Other languages</H2>256 <P>257 There is no official SDK for Go, PHP, Ruby, Java or C# yet. The REST API is small enough that the raw-HTTP samples in this documentation (see the language tabs on the{" "}258 <A href="/docs/fetch">Fetch API</A> page) are a complete integration. If you publish a community client, tell us at <A href="mailto:hello@fetcha.co">hello@fetcha.co</A> and we will link it259 here.260 </P>261 <H3>CLI</H3>262 <P>263 A <Code>fetcha</Code> command-line tool is planned but <Strong>not available</Strong>. Until then, cURL with an exported <Code>FETCHA_API_KEY</Code> covers interactive use; see the{" "}264 <A href="/docs/quickstart">Quickstart</A>.265 </P>266 </DocPage>267 );268}269