SPB Git forge

spb/fetcha

Public
11commits 1branches 0releases
1.5 MBsize
maindefault branch
16 days agolast push
TypeScript 97.5% SQL 1.4% Python 0.8%
12.7 KB · 269 lines tsx
Raw Blame History
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&gt;=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&apos;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&apos;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&lt;FetchResult&gt;</Code>, <Code>text(url, options?)</Code> returns <Code>string</Code> (empty when the body was binary),{" "}195          <Code>json&lt;T&gt;(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&apos;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&apos;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