Learn
Sessions, cookies, and APIs: how state changes a crawl
HTTP is stateless; cookies and tokens add state. Learn how sessions work, when to keep cookies, how to use an official API, and where authentication becomes a legal boundary.
Tested with Python 3.14.2, requests 2.32.5 on 2 November 2026. Library versions and dates are part of the evidence, not decoration.
A session is server-side state tied to a client by an identifier, usually a cookie. Authentication is how a client proves who it is; authorisation is what that identity may do. A crawler that keeps a session can fetch pages that depend on state; a crawler that uses an API can fetch data under a documented contract. Neither grants permission to access data you should not have.
How cookies work
A server sends Set-Cookie: session=abc123; Path=/; HttpOnly; Secure; SameSite=Lax. The client stores it and sends Cookie: session=abc123 on matching requests. The server looks up the session ID and remembers the user, the cart, the locale, or the CSRF token.
Cookie attributes that matter:
Domain: which hosts receive the cookie.Path: which paths receive it.Expires/Max-Age: persistence.Secure: HTTPS only.HttpOnly: not readable by JavaScript.SameSite: whether cross-site requests carry it.
A crawler that needs a login must preserve the cookie jar across requests and handle expiry. A crawler that does not need a session should not keep cookies: they are identifiers, and hoarding them is a privacy and hygiene problem.
A session in requests
import requests
with requests.Session() as session:
session.headers["User-Agent"] = "CrawlerBros-Example/1.0 (+https://www.crawlerbros.com/about)"
first = session.get("http://127.0.0.1:8765/session", timeout=10)
second = session.get("http://127.0.0.1:8765/whoami", timeout=10)
print(first.status_code, second.status_code)
print(second.text.strip())
Tested on 2 November 2026 against a local fixture that sets a session cookie:
200 200
session=fixture
requests.Session keeps the cookie jar, connection pool, and default headers. The second request carries the cookie automatically. If you use httpx, the equivalent is httpx.Client().
When a session is not enough
Many sites pair a session cookie with a CSRF token. The token is embedded in a form or a meta tag and must be sent back in a header or a form field. A crawler that replays only the cookie gets a 403.
The pattern is:
GETthe form or page.- Parse the CSRF token from the HTML or a cookie.
POSTthe form with the token and the other fields.- Keep the session cookie from the response.
This is normal for a documented API or a form you are permitted to use. It is not a technique for bypassing a login you do not own. If you find yourself reverse-engineering an anti-bot token to get past an access control, stop and use the official API or ask for permission.
Tokens and API keys
API authentication commonly uses one of:
- API key in a header (
X-API-Key) or a query parameter. - Bearer token in
Authorization: Bearer <token>. - OAuth 2.0 access token, often with a refresh token and scopes.
- Signed requests with a timestamp and HMAC.
Read the API documentation for the exact scheme, the scopes, the rate limits, and the terms. Store credentials in environment variables or a secret manager, never in a notebook, a repository, or a screenshot.
import os
import requests
token = os.environ["EXAMPLE_API_TOKEN"]
response = requests.get(
"https://api.example.com/v1/items",
headers={
"Authorization": f"Bearer {token}",
"Accept": "application/json",
"User-Agent": "CrawlerBros-Example/1.0 (+https://www.crawlerbros.com/about)",
},
params={"limit": 100},
timeout=20,
)
response.raise_for_status()
payload = response.json()
print(payload["items"][0]["id"])
This example is illustrative; it is not run against a live third-party API. The shape is the point: a documented endpoint, a token from the environment, an explicit Accept, and a timeout.
API pagination
APIs paginate with one of four schemes:
| Scheme | Example | How to continue |
|---|---|---|
| Offset/limit | ?offset=100&limit=100 |
Increase offset until a short page |
| Page number | ?page=3 |
Increase page until empty |
| Cursor | ?cursor=abc |
Use the cursor from the response |
| Link header | Link: <...>; rel="next" |
Follow the next URL |
A cursor or a next link is more stable than an offset: new records inserted at the front do not shift the page boundary. Stop when the response has no next cursor, an empty item list, or a repeated cursor. Do not trust “page 999” to be empty without checking the response.
Authentication is a boundary, not a puzzle
A login wall, a paywall, a private API, or a CAPTCHA is an access control. Using someone else’s session cookie, credential stuffing, or a token you found in a client bundle is not “scraping”; it is unauthorised access, and it can be a crime or a breach of contract depending on the facts.
The defensible paths are:
- Use the official API with your own credentials.
- Ask the site for permission or a data licence.
- Scrape only public data, at a polite rate, within the terms.
- Stop.
The terms of service guide and the CFAA guide explain the legal side. The responsible crawling checklist is the practical version.
Key takeaways
- HTTP is stateless; cookies and tokens add state.
- Keep a session only when the crawl needs one, and store credentials outside the code.
- A CSRF token is normal for a permitted form or API; bypassing an access control is not.
- Prefer cursor or
next-link pagination over offsets. - Authentication is not authorisation, and neither is permission to scrape.