> ## Documentation Index
> Fetch the complete documentation index at: https://docs.zenrows.com/llms.txt
> Use this file to discover all available pages before exploring further.

> ## Agent Instructions
> The Zenrows API (Fetch, Extract via extract=auto, and Batch) is described in OpenAPI 3.1 at https://www.zenrows.com/openapi.json and listed in the RFC 9727 API catalog at https://www.zenrows.com/.well-known/api-catalog. Use the spec for exact parameter names and types.

# Keep the same IP and submit forms

> Keep the same IP across several requests, submit forms and send your own cookies and headers with Zenrows Fetch.

Multi-step flows need continuity: the same IP from one request to the next, form data posted to the target, the site's cookies sent back. Fetch covers all three with `session_id`, POST forwarding and `custom_headers`.

**Uses:** [Fetch](/fetch/api-reference) with [`session_id`](/fetch/features/other#session-id), [POST and PUT requests](/fetch/features/other#post-/-put-requests) and [`custom_headers`](/fetch/features/headers)

## Ask your AI agent

With the [Zenrows MCP server](/mcp/overview) connected to Claude, Cursor or any MCP client, paste this prompt. Your agent picks the right Zenrows tools and settings on its own.

```text theme={"dark"}
Use the Zenrows browser to log in at https://www.scrapingcourse.com/login with email
admin@example.com and password password, and tell me the heading on the page you land on.
Close the browser session when you're done.
```

Your agent does this job in one browser session, which keeps the same IP and cookies across every step of the login.

## Keep the same IP across requests

Add the same `session_id` (a number from 1 to 99999) to every request that must share an IP. Zenrows keeps that IP for 10 minutes. This example calls `https://httpbin.io/ip`, which echoes the caller's IP.

<CodeGroup>
  ```python Python (Requests) theme={"dark"}
  import requests

  def get_ip(session_id):
      params = {
          "apikey": "YOUR_ZENROWS_API_KEY",
          "url": "https://httpbin.io/ip",
          "premium_proxy": "true",
          "session_id": session_id,
      }
      response = requests.get("https://api.zenrows.com/v1/", params=params, timeout=180)
      response.raise_for_status()
      return response.json()["origin"].split(":")[0]  # drop the port

  print("Request 1, session 12345:", get_ip(12345))
  print("Request 2, session 12345:", get_ip(12345))
  print("Request 3, session 67890:", get_ip(67890))
  ```

  ```javascript Node.js theme={"dark"}
  async function getIp(sessionId) {
    const params = new URLSearchParams({
      apikey: "YOUR_ZENROWS_API_KEY",
      url: "https://httpbin.io/ip",
      premium_proxy: "true",
      session_id: String(sessionId),
    });
    const response = await fetch(`https://api.zenrows.com/v1/?${params}`, {
      signal: AbortSignal.timeout(180_000),
    });
    if (!response.ok) throw new Error(`${response.status}: ${await response.text()}`);
    const { origin } = await response.json();
    return origin.split(":")[0]; // drop the port
  }

  console.log("Request 1, session 12345:", await getIp(12345));
  console.log("Request 2, session 12345:", await getIp(12345));
  console.log("Request 3, session 67890:", await getIp(67890));
  ```

  ```bash cURL theme={"dark"}
  curl "https://api.zenrows.com/v1/?apikey=YOUR_ZENROWS_API_KEY&url=https%3A%2F%2Fhttpbin.io%2Fip&premium_proxy=true&session_id=12345"
  ```

  ```bash CLI theme={"dark"}
  zenrows fetch https://httpbin.io/ip --manual --premium-proxy --session-id 12345
  ```
</CodeGroup>

## Example output

```text theme={"dark"}
Request 1, session 12345: 187.94.6.149
Request 2, session 12345: 187.94.6.149
Request 3, session 67890: 190.80.50.164
```

The same `session_id` returns the same IP. A new value starts a new session with a new IP.

## Submit a login form with POST

Send a POST to the Fetch endpoint and Zenrows forwards the body to the target. Here the body is the login form of a demo site, and the response is the page the site redirects to after login.

```python Python (Requests) theme={"dark"}
import requests

params = {
    "apikey": "YOUR_ZENROWS_API_KEY",
    "url": "https://www.scrapingcourse.com/login",
}

response = requests.post(
    "https://api.zenrows.com/v1/",
    params=params,
    data={"email": "admin@example.com", "password": "password"},
    timeout=180,
)
response.raise_for_status()

print("Landed on:", response.headers["Zr-Final-Url"])
print("Logged in:", "Welcome, Scraper!" in response.text)
print("Site cookies:", response.headers.get("Zr-Cookies", "")[:60], "...")
```

Output:

```text theme={"dark"}
Landed on: https://www.scrapingcourse.com:443/dashboard
Logged in: True
Site cookies: AWSALB=tKmea1Q0IhUR/BlFkSNE8R05Nh0CEN60rD/V1Vtrs+GFswmavQHgy ...
```

The target's own response headers come back with a `Zr-` prefix. `Zr-Cookies` holds the cookies the site set, ready to send back on the next request.

## Send form and JSON bodies

Fetch forwards the body and its `Content-Type` as you send them. `https://httpbin.io/anything` echoes what it receives, so you can see both shapes arrive.

<CodeGroup>
  ```python Python (Requests) theme={"dark"}
  import requests

  params = {
      "apikey": "YOUR_ZENROWS_API_KEY",
      "url": "https://httpbin.io/anything",
  }

  # Form body (application/x-www-form-urlencoded)
  form = requests.post(
      "https://api.zenrows.com/v1/",
      params=params,
      data={"email": "admin@example.com", "remember": "1"},
      timeout=180,
  )
  form.raise_for_status()
  print("Form:", form.json()["method"], form.json()["form"])

  # JSON body (application/json)
  body = requests.post(
      "https://api.zenrows.com/v1/",
      params=params,
      json={"query": "hoodie", "page": 2},
      timeout=180,
  )
  body.raise_for_status()
  print("JSON:", body.json()["method"], body.json()["json"])
  ```

  ```javascript Node.js theme={"dark"}
  const params = new URLSearchParams({
    apikey: "YOUR_ZENROWS_API_KEY",
    url: "https://httpbin.io/anything",
  });

  const response = await fetch(`https://api.zenrows.com/v1/?${params}`, {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ query: "hoodie", page: 2 }),
    signal: AbortSignal.timeout(180_000),
  });
  if (!response.ok) throw new Error(`${response.status}: ${await response.text()}`);

  const echo = await response.json();
  console.log("JSON:", echo.method, echo.json);
  ```

  ```bash cURL theme={"dark"}
  curl -X POST "https://api.zenrows.com/v1/?apikey=YOUR_ZENROWS_API_KEY&url=https%3A%2F%2Fhttpbin.io%2Fanything" \
    -d "email=admin@example.com&remember=1"
  ```
</CodeGroup>

Output:

```text theme={"dark"}
Form: POST {'email': ['admin@example.com'], 'remember': ['1']}
JSON: POST {'page': 2, 'query': 'hoodie'}
```

## Send your own cookies and headers

Set `custom_headers=true` and Zenrows forwards the headers you add to the request, such as `Cookie`, `Referer` or an authorization header. It keeps managing the browser fingerprint headers for you.

<CodeGroup>
  ```python Python (Requests) theme={"dark"}
  import requests

  params = {
      "apikey": "YOUR_ZENROWS_API_KEY",
      "url": "https://httpbin.io/headers",
      "custom_headers": "true",
  }
  headers = {
      "Referer": "https://www.google.com/",
      "Cookie": "currency=EUR; region=de",
  }

  response = requests.get("https://api.zenrows.com/v1/", params=params, headers=headers, timeout=180)
  response.raise_for_status()

  sent = response.json()["headers"]
  print("Referer:", sent.get("Referer"))
  print("Cookie:", sent.get("Cookie"))
  ```

  ```bash cURL theme={"dark"}
  curl "https://api.zenrows.com/v1/?apikey=YOUR_ZENROWS_API_KEY&url=https%3A%2F%2Fhttpbin.io%2Fheaders&custom_headers=true" \
    -H "Referer: https://www.google.com/" \
    -H "Cookie: currency=EUR; region=de"
  ```
</CodeGroup>

Output:

```text theme={"dark"}
Referer: ['https://www.google.com/']
Cookie: ['currency=EUR; region=de']
```

Combine the three: log in with POST, read `Zr-Cookies`, then send those cookies with `custom_headers=true` and the same `session_id` on every following request.

## Customize it

| Change | How |
| - | - |
| Sessions in a specific country | Add `proxy_country`, see [Get content as seen from any country](/academy/fetch/geotargeting) |
| PUT instead of POST | Send a PUT to the Fetch endpoint, see [POST and PUT requests](/fetch/features/other#post-/-put-requests) |
| SDK helpers | `client.post(url, data=...)` in Python, `client.post(url, config, { data })` in Node.js, see [Python POST and PUT](/fetch/sdk/python/post-put-requests) |
| Login flows with JavaScript, CSRF tokens or multiple pages | Use Browser, see [Log in and reuse sessions](/academy/browser/login-and-reuse-sessions) |
| Language or currency set by cookie | See [Custom headers](/fetch/features/headers#language-localization-and-currency) |

## Troubleshooting

* **The IP changes between requests with the same `session_id`**: the requests ran on different configurations. Send the same parameters on each one, such as `premium_proxy=true`, with the same `session_id`.
* **Every request on a session comes back blocked**: that IP is flagged by the site. Switch to a new `session_id` value.
* **The site does not see your header**: add `custom_headers=true` to the request. Browser fingerprint headers such as `User-Agent` and `Accept-Language` stay managed by Zenrows.
* **The POST returns the login page again**: the form needs a token from the page, such as a CSRF field. Use [Log in and reuse sessions](/academy/browser/login-and-reuse-sessions) to run the flow in a real browser.

## Related recipes

<CardGroup cols={2}>
  <Card title="Log in once and reuse the session" icon="login" href="/academy/browser/login-and-reuse-sessions">
    Run full login flows in a real browser and keep the cookies.
  </Card>

  <Card title="Click, fill, and scroll before you scrape" icon="click" href="/academy/fetch/page-interactions">
    Fill a field and click a button inside a Fetch request.
  </Card>

  <Card title="Get content as seen from any country" icon="world" href="/academy/fetch/geotargeting">
    Pick the country your session comes from.
  </Card>

  <Card title="Fill and submit multi-step forms" icon="forms" href="/academy/browser/fill-forms">
    Drive complex forms step by step in the browser.
  </Card>
</CardGroup>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.