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%
10.4 KB · 183 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 { Endpoint } from "@/components/docs/endpoint";7import { ParamTable } from "@/components/docs/param-table";8import { CodeTabs } from "@/components/docs/code-tabs";9import { ResponseExample } from "@/components/docs/response-example";10import { apiTabs, fetchTabs } from "@/components/docs/snippets";1112export const metadata: Metadata = {13  title: "Sessions",14  description: "Sticky sessions keep the same exit identity across requests: create, use, inspect and close them, with TTL, geography, idempotency and expiry rules.",15};1617const SESSION = {18  id: "sess_8f2k1m9d3p7q4r6s",19  label: "checkout-user-42",20  status: "active",21  network: "residential",22  country: "CA",23  region: "quebec",24  city: null,25  request_count: 0,26  last_used_at: null,27  expires_at: "2026-09-07T14:32:11.000Z",28  created_at: "2026-09-07T14:17:11.000Z",29};3031export default function SessionsPage() {32  return (33    <DocPage path="/docs/sessions" eyebrow="Core API" title="Sessions" description="A session pins a stable exit identity for a period of time so that a sequence of requests looks like one visitor. Use it for login flows, paginated listings and any site that ties state to the client IP." status="Stable">34      <H2>What a session preserves</H2>35      <Ul>36        <Li>37          <Strong>Exit identity.</Strong> All requests that reference the session are routed through the same network route with the same sticky key, so the target sees the same IP for the38          session&apos;s lifetime, as long as the network keeps that exit available.39        </Li>40        <Li>41          <Strong>Network class and geography.</Strong> The class chosen at creation and the country you asked for are applied to every request that omits them.42        </Li>43        <Li>44          <Strong>Cookies you pass.</Strong> Fetcha does not maintain a cookie jar. Read <Code>cookies</Code> from a response and send them back in the next request&apos;s <Code>cookies</Code>{" "}45          field. Because the IP is stable, the target accepts them as belonging to the same visitor.46        </Li>47      </Ul>48      <Callout variant="info" title="Retries inside a session">49        Normally each retry uses a fresh exit IP. When a request is pinned to a session, retries keep the session&apos;s identity so the target never sees the visitor jump between IPs. If the50        session&apos;s exit is being blocked, close the session and create a new one.51      </Callout>5253      <H2>Create a session</H2>54      <Endpoint method="POST" path="/v1/sessions" scope="sessions:write" status="Live" />55      <ParamTable56        rows={[57          { name: "country", type: "string", constraints: "exactly 2 chars", description: <>ISO 3166-1 alpha-2 country for the exit. See <A href="/docs/geolocation">Geolocation</A>.</> },58          { name: "region", type: "string", constraints: "≤ 64 chars", description: <>State or province hint.</> },59          { name: "city", type: "string", constraints: "≤ 128 chars", description: <>City hint.</> },60          { name: "network", type: '"auto" | "datacenter" | "residential" | "isp" | "mobile"', default: '"auto"', description: <>Network class. The concrete class picked by the engine is returned in <code>network</code>. Classes outside your plan or without a sticky-capable route return <code>NETWORK_UNAVAILABLE</code>.</> },61          { name: "ttl", type: "integer (seconds)", default: "600", constraints: "60–1,800", description: <>Lifetime of the session. It is fixed at creation and is <strong>not</strong> extended by use.</> },62          { name: "label", type: "string", constraints: "≤ 128 chars", description: <>Free-form label for your own bookkeeping, shown in the dashboard.</> },63        ]}64      />65      <P>66        The body may be empty (<Code>{"{}"}</Code>): you then get a 10-minute session on the best sticky-capable route with no geography constraint. The schema is strict; unknown fields fail67        with <Code>INVALID_REQUEST</Code>.68      </P>69      <CodeTabs tabs={apiTabs({ method: "POST", path: "/v1/sessions", body: { country: "CA", region: "QC", ttl: 900, label: "checkout-user-42" }, idempotencyKey: "checkout-user-42-2026-09-07" })} />70      <ResponseExample status={200} body={SESSION} />71      <ParamTable72        showDefault={false}73        rows={[74          { name: "id", type: "string", description: <>Session identifier (<code>sess_…</code>). Pass it as <code>session</code> in fetch requests.</> },75          { name: "label", type: "string | null", description: <>Your label.</> },76          { name: "status", type: '"active" | "expired" | "closed"', description: <><code>expired</code> is derived from <code>expires_at</code>; <code>closed</code> means you deleted it.</> },77          { name: "network", type: "string", description: <>Concrete class serving the session.</> },78          { name: "country / region / city", type: "string | null", description: <>Normalised geography (region and city as slugs).</> },79          { name: "request_count", type: "integer", description: <>Number of fetch requests that used the session.</> },80          { name: "last_used_at", type: "string | null", description: <>ISO 8601 timestamp of the last fetch, or <code>null</code>.</> },81          { name: "expires_at", type: "string", description: <>ISO 8601 expiry, <code>created_at + ttl</code>.</> },82          { name: "created_at", type: "string", description: <>ISO 8601 creation time.</> },83        ]}84      />8586      <H3>Idempotency-Key</H3>87      <P>88        Send an <Code>Idempotency-Key</Code> header with the create request to make it safe to retry. If Fetcha has already created a session for that key in your project within the last 2489        hours, it returns the existing session instead of creating a new one. Keys are scoped to the project; any string works, though a value derived from your own entity (user id, job id) is90        the most useful.91      </P>9293      <H2>Use a session in a fetch</H2>94      <P>95        Reference the id in the <Code>session</Code> field. If you omit <Code>country</Code>, the session&apos;s country applies; if <Code>network</Code> is <Code>auto</Code>, the session&apos;s class96        applies. <Code>metadata.session</Code> echoes the id.97      </P>98      <CodeTabs99        tabs={fetchTabs(100          { url: "https://www.example.ca/account", session: "sess_8f2k1m9d3p7q4r6s", cookies: { sid: "3f9a…" } },101          {102            javascript: `console.log(data.metadata.session, data.metadata.country); // "sess_8f2k1m9d3p7q4r6s" "CA"`,103            python: `print(data["metadata"]["session"], data["metadata"]["country"])`,104          },105        )}106      />107      <Table dense>108        <THead>109          <Tr>110            <Th>Condition</Th>111            <Th>Result</Th>112          </Tr>113        </THead>114        <TBody>115          <Tr>116            <Td>Id does not exist, or belongs to another project</Td>117            <Td mono>404 SESSION_NOT_FOUND</Td>118          </Tr>119          <Tr>120            <Td>121              Session is past <Code>expires_at</Code>, or was closed122            </Td>123            <Td mono>410 SESSION_EXPIRED</Td>124          </Tr>125        </TBody>126      </Table>127      <P>128        Both are raised before any network activity, so they cost nothing and do not consume an attempt. Pass the same geography as the session or omit it; requesting a different country on a129        pinned session is allowed but defeats the purpose.130      </P>131132      <H2>List sessions</H2>133      <Endpoint method="GET" path="/v1/sessions" scope={null} status="Live" />134      <P>Returns the 100 most recent sessions of the project, newest first, including expired and closed ones.</P>135      <CodeTabs tabs={apiTabs({ method: "GET", path: "/v1/sessions" }, ["curl", "javascript", "python"])} />136      <ResponseExample status={200} body={{ data: [SESSION] }} />137138      <H2>Get a session</H2>139      <Endpoint method="GET" path="/v1/sessions/:id" scope={null} status="Live" />140      <CodeTabs tabs={apiTabs({ method: "GET", path: "/v1/sessions/sess_8f2k1m9d3p7q4r6s" }, ["curl", "javascript", "python"])} />141      <P>142        Returns the same object as creation, with a live <Code>status</Code>. Unknown ids return <Code>404 SESSION_NOT_FOUND</Code>.143      </P>144145      <H2>Close a session</H2>146      <Endpoint method="DELETE" path="/v1/sessions/:id" scope="sessions:write" status="Live" />147      <CodeTabs tabs={apiTabs({ method: "DELETE", path: "/v1/sessions/sess_8f2k1m9d3p7q4r6s" }, ["curl", "javascript", "python"])} />148      <ResponseExample status={200} body={{ id: "sess_8f2k1m9d3p7q4r6s", status: "closed" }} />149      <P>150        Closing is immediate and irreversible; later fetches with the id return <Code>SESSION_EXPIRED</Code>. Sessions you do not close simply expire at <Code>expires_at</Code>. There is no per-plan151        limit on the number of sessions.152      </P>153154      <H2>Two-step login example</H2>155      <P>156        A typical flow: create a session, POST credentials, keep the returned cookies, then GET the protected page with the same session and cookies. The SDK version is on the{" "}157        <A href="/docs/examples">Examples</A> page.158      </P>159      <CodeBlock160        lang="bash"161        title="Terminal"162        code={`# 1. Create a Canadian session for 15 minutes163SESSION=$(curl -s https://www.fetcha.co/v1/sessions -X POST \\164  -H "Authorization: Bearer $FETCHA_API_KEY" -H "Content-Type: application/json" \\165  -d '{"country": "CA", "ttl": 900}' | jq -r .id)166167# 2. Log in (cookies come back in .cookies)168curl -s https://www.fetcha.co/v1/fetch -X POST \\169  -H "Authorization: Bearer $FETCHA_API_KEY" -H "Content-Type: application/json" \\170  -d "{\\"url\\": \\"https://www.example.ca/login\\", \\"method\\": \\"POST\\", \\"session\\": \\"$SESSION\\",171       \\"headers\\": {\\"Content-Type\\": \\"application/x-www-form-urlencoded\\"},172       \\"body\\": \\"user=me&pass=secret\\"}" | jq .cookies173174# 3. Fetch the account page with the same session and the returned cookies175curl -s https://www.fetcha.co/v1/fetch -X POST \\176  -H "Authorization: Bearer $FETCHA_API_KEY" -H "Content-Type: application/json" \\177  -d "{\\"url\\": \\"https://www.example.ca/account\\", \\"session\\": \\"$SESSION\\", \\"format\\": \\"text\\",178       \\"cookies\\": {\\"sid\\": \\"3f9a…\\"}}" | jq -r .text`}179      />180    </DocPage>181  );182}183