Docs
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:
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"}'{
"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:
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:
{ "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.
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.