IP Router · self-hosted proxy

Don’t limit your work. Give your AI agent a global reach with an IP location that can change around the world.

Country or city targeting, a JSON fetch API, an HTTP gateway, and sticky sessions — one selectRoute kernel. Bring your own pool or backconnect.

cc / city
Geo targeting
Fetch API
POST /api/v1/fetch
:7777
HTTP gateway
sticky
Session pin
Built for

The models and the harnesses people actually run — Hermes Agent, OpenClaw, Claude Code, Cursor, and the rest of the stack. Drop in a fetch tool or point a browser at the gateway.

Models
  • OpenAI
  • Claude
  • Gemini
  • xAI
  • Llama
  • Mistral
  • DeepSeek
  • Qwen
  • Kimi
  • Cohere
  • Hugging Face
  • Ollama
  • Perplexity
  • Groq
Agent harnesses
  • Hermes Agent
  • OpenClaw
  • Claude Code
  • Cursor
  • Cline
  • Kilo Code
  • OpenCode
  • Copilot
  • Codex
  • Goose
  • LangChain
  • LangGraph
  • CrewAI
  • LlamaIndex
  • OpenHands
  • MCP
  • n8n
  • Pydantic
  • Windsurf
  • Mastra
  • OpenRouter

Console :3000

  • Landing /
  • Open console
  • Playground → routedFetch

Fetch API

  • Agent / LLM
  • POST /api/v1/fetch
  • Bearer ipr_live_dev_key

Gateway :7777

  • curl -x / CONNECT
  • Basic user:key
  • parseUsername
selectRoute · proxy pool · addLogShared kernel in src/lib/router.ts
For dummies

How it works, without the kernel.

You ask for a country or city. IP Router picks an exit there, sends the request out through that IP, and hands the page back. Same brain for a JSON agent and a real browser.

Open the dummy guide

The website never sees your laptop

It sees the exit IP. Change the exit, change the page — prices, SERP, store locator, paywall, all of it.

Country is not language

cc-JP puts you on a Japanese IP. lang-ja tells the site you read Japanese. You usually want both.

Sticky means “same person”

Logins, carts, and cookies die if the IP flips mid-flow. A sessid pins you to one exit until it expires.

Two doors, one kernel

Agents POST JSON to /api/v1/fetch. Browsers and curl talk CONNECT on :7777. Both call selectRoute.

Use cases

How it works for each job.

city / country

See search the way someone in that city sees it

Google (and Bing, and maps) do not show one internet. They show the internet from where you are standing. Rankings, the local pack, ads, even which spelling they think you meant — all of that keys off the exit IP and the language header. If your agent fetches from a US datacenter, you are grading a US result page, even when the question is “what does this look like in Manchester.”

  1. 01Name the market: country GB, city London, language en-GB. City matters here — Manchester and London are different local packs.
  2. 02Use the Fetch API if you want HTML/JSON back for the agent to read. Use the gateway if a headless browser is driving the SERP.
  3. 03Send a stable sessid if you will paginate or click through. A new session mid-scroll looks like a new visitor.
  4. 04Read route.reason. You want city pool match (or backconnect geo injection). Direct fallback means you are still on the host.
cc-GBcity-londonlang-en_GB
Full dummy guide for Local SERP

First match in selectRoute. Click a case to light the path.

targeting inEnabled proxies?store.proxies.filter(enabled)422 empty poolRouteErrorSticky session?getSession(id) + live nodesticky sessionreuse proxyId, refresh TTLFilter typekeep all if none matchCountry setcity → country → bc → directNo countrybc → pool → directcity pool matchcountry pool matchbc geo injectionusername += -cc -citydirect fallbackthis machinebc rotatepool rotate
First match winscity pool match
Targeting
cc-GB city-london
Pool
London residential pool node

Exact city on a non-direct pool node wins first after sticky.

Country specified

1
city pool match
pool node, city matches, not direct
2
country pool match
pool node, country matches, not direct
3
backconnect geo injection
no pool geo; mode === backconnect
4
direct fallback (no geo node)
type === direct still enabled
5
422 RouteError
no city, country, backconnect, or direct

No country

1
backconnect rotate
any enabled backconnect
2
pool rotate
non-direct pool nodes
3
direct
only the machine IP left
PICK()

Session set → hashPick(nodes, session). Else Math.random().

finish() upserts the session and, for backconnect, rewrites the vendor username.

TYPE FILTER

If targeting.type is set, keep that class when any node matches; otherwise keep the full enabled set. Direct nodes can still satisfy geo as a last resort.

Source: src/lib/router.ts selectRoute. City matching via cityMatches in src/lib/geo.ts.