This guide walks through installing, configuring, and running Free Provider end to end.
- Node.js 20.12+ (uses native
fetch,process.loadEnvFile, ESM) - Optional: pnpm or npm for installing the server dependency (
express) - Optional: Git and gh CLI if you cloned / forked the repo
# From the repo root, install the client deps (undici for proxy support)
npm install # or: pnpm install
# Then the server dependency (express + undici)
cd server
npm install # or: pnpm installThe chat client itself has zero hard dependencies — only proxy rotation needs
undici, which is installed above. The HTTP server needsexpress.
npm start # or: pnpm startYou should see output like:
Free Provider Server
Frontend: http://localhost:3000
API: http://localhost:3000/v1
API Key: sk-free-… (generated, saved in .api-key)
-> export OPENAI_API_KEY="sk-free-…"
Models: gpt-5.6-luna, gpt-4o, gpt-4o-mini, gpt-3.5-turbo, gemini-3-pro, gemini-3-flash, gemini-3-lite
Open http://localhost:3000 in your browser to use the chat UI.
All configuration lives in server/config.mjs and can be overridden with environment variables:
.envis auto-loaded from the repository root on startup (process.loadEnvFile). Values already set in your shell win over.env. A template with all options lives at.env.example.
| Variable | Default | Description |
|---|---|---|
PORT |
3000 |
HTTP port |
OPENAI_API_KEY |
(generated) | Bearer token required by clients. If unset, a random key is generated and saved to .api-key |
DEFAULT_MODEL |
auto |
Fallback model when none is sent. The default auto alternates each request between ChatGPT and Gemini (see Auto model balancing) |
GEMINI_DEFAULT_MODEL |
gemini-3-flash |
Default Gemini model for gemini-* requests |
CHATGPT_COOKIES |
client/cookies.json |
Optional file of ChatGPT cookies for authenticated mode |
PROXY_URLS |
(empty) | Comma-separated proxies (socks5://…, http://…) rotated on Cloudflare blocks |
PROXY_FILE |
client/proxies.txt |
File with one proxy URL per line, merged with PROXY_URLS; blank lines and # comments are ignored |
COOLDOWN_SECONDS |
30 |
Seconds ALL requests pause after MAX_CONSECUTIVE_BLOCKS consecutive blocks |
MAX_COOLDOWN_SECONDS |
300 |
Upper bound of the (escalating) cooldown period |
MAX_CONSECUTIVE_BLOCKS |
3 |
Consecutive blocks before the global cooldown engages |
INTER_REQUEST_DELAY_MS |
0 |
Minimum ms between outgoing requests (anti-hammering; 0 = off) |
MAX_REQ_PER_WINDOW |
12 |
Requests allowed per pacing window before throttling (0 disables pacing) |
PACING_WINDOW_SECONDS |
30 |
Length of the pacing window |
INIT_TTL_MS |
600000 |
How long the page-init (buildId/deviceId) is cached to skip GET / per message |
AGENT_COUNT |
3 |
Independent client agents per provider (chatgpt + gemini), round-robin. Each has its own proxy rotation, cooldown, pacing and session state. In auto mode the N-th window of each provider form pair N. Set 1 for single-agent behavior |
FETCH_TIMEOUT_MS |
120000 |
Max ms per outgoing request to ChatGPT (the pipeline does several sequential requests per answer) |
STREAM_RETRIES |
2 |
Extra retries for streaming requests before giving up |
PROXY_COOLDOWN_SECONDS |
60 |
Seconds a proxy is benched after being blocked |
A template with all options lives at .env.example.
export OPENAI_API_KEY="sk-my-own-key"
npm startYou can also edit .api-key directly — it is auto-read on startup if OPENAI_API_KEY is not set.
Just type a message. Configure the server URL and API key under Settings (gear icon).
API_KEY=$(cat /mnt/d/Open-source/AI_Proofy_unoffical/.api-key) # or your exported key
curl http://localhost:3000/v1/chat/completions \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{"model":"gpt-4o","messages":[{"role":"user","content":"Hello"}],"stream":true}'Point the base URL at the server and use any model listed in GET /v1/models:
const client = new OpenAI({
baseURL: "http://localhost:3000/v1",
apiKey: "sk-free-…",
});Requests are routed by model prefix: gemini-* → the Gemini web backend, everything else → ChatGPT. Both return identical OpenAI-compatible responses, so you can mix models freely.
Leave model off (or send "auto") and requests split evenly across both backends instead of hitting one provider:
req #1 → chatgpt, pair 0 req #3 → chatgpt, pair 1
req #2 → gemini, pair 0 req #4 → gemini, pair 1 … wraps after AGENT_COUNT pairs
Pair i = ChatGPT window i + Gemini window i, so alternating requests hop chatgpt → gemini and rotate through the pairs. Great when you want to halve the hammering on a single backend and share the load. Send an explicit model any time you want to pin a backend.
curl http://localhost:3000/v1/chat/completions \
-H "Authorization: Bearer $API_KEY" -H "Content-Type: application/json" \
-d '{"model":"auto","messages":[{"role":"user","content":"Hello"}]}'Besides the ChatGPT backend, Free Provider also speaks to Gemini through a cookie-free, guest-mode client. No account or cookies are needed — it scrapes gemini.google.com, obtains an anonymous session, and uses the same agent-pool/cooldown machinery as ChatGPT.
- Models:
gemini-3-pro,gemini-3-flash(default),gemini-3-lite— use any listed in/v1/models. - Works over plain HTTPS (no proxy required); Gemini blocks surface as
429just like ChatGPT blocks. - Swap a chat:
"model":"gemini-3-flash"instead of"model":"gpt-4o".
Standalone smoke test (no server):
node client/gemini.mjs "Hello" # one-shot
node client/gemini.mjs --stream "Hello" # token streamDrop a file at client/cookies.json:
[
{ "name": "__Secure-next-auth.session-token", "value": "…" },
{ "name": "cf_clearance", "value": "…" }
]See client/cookies.json.example for the exact shape. When present, the client uses authenticated endpoints and unlocks more capabilities.
The ChatGPT web backend rate-limits and bot-checks aggressive usage. Free Provider layers several defenses:
The transport runs a sliding-window token bucket: requests fire instantly until a burst limit, then the next ones are spaced just under the threshold — so normal usage stays instant while bursts don't trip the guard.
MAX_REQ_PER_WINDOW— allowed requests perPACING_WINDOW_SECONDS(default12per30s). Set0to disable.- After any Cloudflare block the bucket tightens (halves capacity) until a request succeeds, then relaxes again.
- Combined with
INTER_REQUEST_DELAY_MS, every outbound request (init, tokens, conduit, conversation) is counted, not just completions.
On top of that, per-message init() is now cached (INIT_TTL_MS, default 10 min), so each message costs 3 requests instead of 4.
- After
MAX_CONSECUTIVE_BLOCKS(default 3) consecutive Cloudflare blocks, the server enters a global cooldown. The wait escalates (30s → 60s → 120s … capped atMAX_COOLDOWN_SECONDS, default 300s) and resets after a success. During cooldown,/v1/chat/completionsreturns429with aRetry-Afterheader:{ "error": { "message": "Server cooling down after Cloudflare blocks (retry in 30s)", "type": "rate_limit_error", "code": "server_cooldown" } } - Each block also triggers exponential backoff (1s → 2s → 4s … capped at 16s) and User-Agent rotation.
Configure a pool of proxies and the client rotates to a fresh IP on each block:
# .env or shell
PROXY_URLS=socks5://user:pass@host:1080,http://user:pass@proxy.example.com:8080or a file (client/proxies.txt, gitignored):
socks5://user:pass@proxy1:1080
http://user:pass@proxy2:8080
- Supported protocols:
http://,https://,socks4://,socks5://. HTTP(S) proxies useundici.ProxyAgent;socks5://uses undici's built-inSocks5ProxyAgent(native TLS over the tunnel);socks4://uses a custom connector that TLS-wraps the tunneled socket. - A blocked proxy is benched for
PROXY_COOLDOWN_SECONDSbefore reuse; the pool round-robins through the rest. - If every proxy is benched, the whole instance cools down and reports the wait time.
Streaming requests now retry like normal requests (STREAM_RETRIES, default 2) with backoff, proxy + UA rotation — previously they gave up on the first failure.
SDK/tool clients (OpenAI SDK, mentor, etc.) only retry rate-limit errors cleanly. Free Provider returns:
429+Retry-Afterfor rate-limit situations (all agents cooling down, a proxy pool exhausted, a ChatGPT block/flag). The SDK then respects the backoff instead of hammering.200+ SSE for a normal stream; if the stream fails after the first token, an error event is sent and the SDK surfaces it as a stream error.500only for genuine unexpected provider failures.
Streaming headers are flushed on the first chunk (not before), so a block that happens during init/tokens still returns a proper 429 instead of a 200 with an error event.
With AGENT_COUNT > 1 (default 3), the server runs several independent ChatGPT client instances. Each agent has its own:
- ChatGPT client + session state (buildId, deviceId, tokens)
- CFBypass transport with its own pacing window, cooldown, proxy rotation and internal lock
Incoming requests are distributed round-robin across agents, so several can be in flight at once (each under its own lock). If one agent enters cooldown from Cloudflare blocks, the others keep serving — the server only returns 429 when all agents are cooling down simultaneously.
Set AGENT_COUNT=1 to get the original single-agent behavior.
When ChatGPT flags your IP ("Unusual activity") or Cloudflare challenges it, the only reliable fix is clean proxy IPs. Free Provider supports a pool of mixed proxies:
# client/proxies.txt (already created, gitignored)
socks5://user:pass@proxy1.net:1080
socks5://user:pass@proxy2.net:1080
http://user:pass@proxy3.net:8080
- All agents rotate through the same list round-robin.
- A flagged/blocked proxy is benched for
PROXY_COOLDOWN_SECONDSwhile the others keep serving — the pool self-heals. - Mix
http://,https://,socks4://,socks5://freely.
Monitor the live pool with GET /v1/pool (Bearer key required) — it reports each agent's readiness, cooldown, and every proxy's status/bench time left.
Tor as a quick test. No real proxies handy? A local Tor instance works too — just point the pool at it:
# .env PROXY_URLS=socks5://127.0.0.1:9050(start
torso it listens on 9050). This is the equivalent ofproxychains node server/index.mjsbut stays inside the agent pool. Note: Tor exit nodes are slow and often blocked/flagged bychatgpt.com, so use realhttp/socks5proxies for production.
Every chat request is logged — user messages and AI responses — both to the terminal and to server/chat.log:
{"timestamp":"…","type":"completion","stream":true,"model":"gpt-4o","user":["Hi"],"assistant":"Hello!"}See Security for what log output may contain.
EADDRINUSEon startup — another process holds port3000. Stop it or setPORTto another value.- Conversation returns 404 — the provider is stateless on purpose; context travels in the
messagesarray. Sending a full history keeps things working. Blocked by Cloudflare— the web backend sometimes challenges a datacenter/abusive IP. Wait for the cooldown, add proxies (PROXY_URLS/proxies.txt), lower concurrency withINTER_REQUEST_DELAY_MS, or use a residential proxy.Flagged as unusual activity by ChatGPT— ChatGPT's backend (not Cloudflare) temporarily flags an IP/device that sent too many anonymous requests. The transport now detects this, retries with UA/cookie rotation (and proxy rotation if proxies are configured), then escalates cooldown and reports the wait as a429+Retry-Afterso client tools respect the backoff. The flag is time-based: wait and retry, or useCHATGPT_COOKIES(logged-in) or clean proxies to sidestep the flag — see f) Multiple proxies.- HTTP 500 with
Conversation failed: 4xx— the underlying call tochatgpt.comwas rejected.429/403/503are now retried with backoff, UA + proxy rotation; the response body is what actually failed, so checkchat.logfor the reason. - Repeated
500/"fetch failed"despite a healthy provider — you may be hitting a stale server that kept running after a config change (it still holds the old port/proxies). Checkss -tlnp | grep 3000for what's actually bound, kill it, and start fresh with the new config.