import type { Metadata } from "next"; import { CodeBlock } from "@/components/ui/code-block"; import { DocPage } from "@/components/docs/doc-page"; import { A, Code, H2, H3, Li, P, Strong, Table, TBody, Td, Th, THead, Tr, Ul } from "@/components/docs/prose"; import { Callout } from "@/components/docs/callout"; import { Endpoint } from "@/components/docs/endpoint"; import { ParamTable } from "@/components/docs/param-table"; import { CodeTabs } from "@/components/docs/code-tabs"; import { ResponseExample } from "@/components/docs/response-example"; import { apiTabs, fetchTabs } from "@/components/docs/snippets"; export const metadata: Metadata = { title: "Sessions", description: "Sticky sessions keep the same exit identity across requests: create, use, inspect and close them, with TTL, geography, idempotency and expiry rules.", }; const SESSION = { id: "sess_8f2k1m9d3p7q4r6s", label: "checkout-user-42", status: "active", network: "residential", country: "CA", region: "quebec", city: null, request_count: 0, last_used_at: null, expires_at: "2026-09-07T14:32:11.000Z", created_at: "2026-09-07T14:17:11.000Z", }; export default function SessionsPage() { return (

What a session preserves

Normally each retry uses a fresh exit IP. When a request is pinned to a session, retries keep the session's identity so the target never sees the visitor jump between IPs. If the session's exit is being blocked, close the session and create a new one.

Create a session

ISO 3166-1 alpha-2 country for the exit. See Geolocation. }, { name: "region", type: "string", constraints: "≤ 64 chars", description: <>State or province hint. }, { name: "city", type: "string", constraints: "≤ 128 chars", description: <>City hint. }, { name: "network", type: '"auto" | "datacenter" | "residential" | "isp" | "mobile"', default: '"auto"', description: <>Network class. The concrete class picked by the engine is returned in network. Classes outside your plan or without a sticky-capable route return NETWORK_UNAVAILABLE. }, { name: "ttl", type: "integer (seconds)", default: "600", constraints: "60–1,800", description: <>Lifetime of the session. It is fixed at creation and is not extended by use. }, { name: "label", type: "string", constraints: "≤ 128 chars", description: <>Free-form label for your own bookkeeping, shown in the dashboard. }, ]} />

The body may be empty ({"{}"}): you then get a 10-minute session on the best sticky-capable route with no geography constraint. The schema is strict; unknown fields fail with INVALID_REQUEST.

Session identifier (sess_…). Pass it as session in fetch requests. }, { name: "label", type: "string | null", description: <>Your label. }, { name: "status", type: '"active" | "expired" | "closed"', description: <>expired is derived from expires_at; closed means you deleted it. }, { name: "network", type: "string", description: <>Concrete class serving the session. }, { name: "country / region / city", type: "string | null", description: <>Normalised geography (region and city as slugs). }, { name: "request_count", type: "integer", description: <>Number of fetch requests that used the session. }, { name: "last_used_at", type: "string | null", description: <>ISO 8601 timestamp of the last fetch, or null. }, { name: "expires_at", type: "string", description: <>ISO 8601 expiry, created_at + ttl. }, { name: "created_at", type: "string", description: <>ISO 8601 creation time. }, ]} />

Idempotency-Key

Send an Idempotency-Key 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 24 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) is the most useful.

Use a session in a fetch

Reference the id in the session field. If you omit country, the session's country applies; if network is auto, the session's class applies. metadata.session echoes the id.

Condition Result
Id does not exist, or belongs to another project 404 SESSION_NOT_FOUND
Session is past expires_at, or was closed 410 SESSION_EXPIRED

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 a pinned session is allowed but defeats the purpose.

List sessions

Returns the 100 most recent sessions of the project, newest first, including expired and closed ones.

Get a session

Returns the same object as creation, with a live status. Unknown ids return 404 SESSION_NOT_FOUND.

Close a session

Closing is immediate and irreversible; later fetches with the id return SESSION_EXPIRED. Sessions you do not close simply expire at expires_at. There is no per-plan limit on the number of sessions.

Two-step login example

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{" "} Examples page.

); }