> Documentation index: https://unbrowse.ai/llms.txt. Fetch it to find every page.

# Run from your own IP

> Send a run's requests to the website yourself, from your own IP, while Unbrowse still decides every request and reads every response. Useful when a site must see your address (your region, your allowlisted IP, your network), or when you would rather no third party connects to it on your behalf.

## How it works

Start a run as usual and add `"egress": "client"`. Instead of calling the site, Unbrowse answers with the next request to send:

```bash
curl -X POST https://api.unbrowse.ai/api/v1/runs \
  -H "Authorization: Bearer $UNBROWSE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"capability":"hn.top_stories","input":{"limit":3},"egress":"client"}'
```

```json
{
  "status": "egress_required",
  "egressId": "eg_mB1s…",
  "requests": [
    {
      "id": "rq_1",
      "method": "GET",
      "url": "https://hn.algolia.com/api/v1/search?tags=front_page&hitsPerPage=12",
      "headers": { "accept": "application/json", "user-agent": "…" },
      "redirect": "manual",
      "curl": "curl -sS -i -X GET 'https://hn.algolia.com/api/v1/search?tags=front_page&hitsPerPage=12' -H 'accept: application/json' …"
    }
  ]
}
```

Send that request from your machine, then post the site's raw response back:

```bash
curl -X POST https://api.unbrowse.ai/api/v1/egress/eg_mB1s… \
  -H "Authorization: Bearer $UNBROWSE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"requestId":"rq_1","response":{"status":200,"headers":[["content-type","application/json"]],"body":"{\"hits\":[…]}"}}'
```

Unbrowse reads it and answers with the next request (`egress_required` again), or with the finished run — the same verified result a normal run returns:

```json
{ "runId": "run_…", "status": "succeeded", "capabilityId": "hn.top_stories", "result": { "stories": [ … ] } }
```

A multi-step flow (sign in, then search, then read a detail) is one request at a time: you only ever hold the request in front of you.

## With the SDK

`runOnClient` does the loop: it sends each request with your `fetch` and posts the response back.

```ts
import { Unbrowse } from "@unbrowse/sdk";

const unbrowse = new Unbrowse({ apiKey: process.env.UNBROWSE_API_KEY });
const run = await unbrowse.runOnClient({ capability: "hn.top_stories", input: { limit: 3 } });
console.log(run.result);
```

Pass `fetch` to send through your own proxy or network stack, and `onRequest` to see or veto each request before it goes out.

## Answering

| Field | What to send |
|---|---|
| `requestId` | The `id` of the request you sent. |
| `response.status` | The HTTP status the site returned. |
| `response.headers` | `[name, value]` pairs (keeps repeated headers such as `set-cookie`) or an object. |
| `response.body` | The body, decoded (after gzip/brotli). Text, or base64 with `"bodyEncoding": "base64"`. Up to 10 MB. |
| `response.url` | The final URL, if you followed redirects. |
| `error` | Instead of `response`, when the request could not be sent at all (DNS, TLS, refused). The run treats it as a network error. |

When `redirect` is `manual`, do not follow redirects: return the 3xx as it came (Unbrowse follows it itself and asks you for the next hop). You have 120 seconds to answer each request; an abandoned run can be closed with `DELETE /api/v1/egress/{egressId}`. `GET /api/v1/egress/{egressId}` shows what the run is waiting for.

## What stays where

- **Yours**: the connection to the site and your IP. Each request you send is visible to you, as any request your machine makes is.
- **Unbrowse's**: which capability fits, which request comes next and why, what is taken from each response, and the verified result.
- In a client-egress run Unbrowse never contacts the site: no cloud browser, no residential proxy. A capability that needs a browser (a page that only renders in JavaScript, a bot check only a browser clears) fails with a clear error instead of silently using Unbrowse's own network.
- Billing is unchanged: a verified success is one call.
