codex-cursor-bridge
v2.0.0
Published
Local OpenAI-compatible proxy that routes Cursor (or any OpenAI-API client) through the Codex CLI's ChatGPT-subscription auth — use your ChatGPT Pro/Plus plan instead of paying for separate OpenAI API credits.
Maintainers
Readme
codex-cursor-bridge
Use your ChatGPT Pro / Plus subscription to power Cursor (or any OpenAI-API-compatible tool), instead of paying separately for OpenAI API credits.
A small zero-npm-dependency Node CLI. It accepts OpenAI-style
/v1/chat/completions requests on localhost, translates them to the
Responses API, and forwards them to the same backend the official
codex CLI uses
(https://chatgpt.com/backend-api/codex/responses). Authentication is the
ChatGPT-OAuth token that codex login already wrote to ~/.codex/auth.json,
so usage is billed against your ChatGPT subscription rather than the metered
API.
Cursor cloud / Aider / codex-cursor-bridge ChatGPT-Pro
any OpenAI client (localhost:7711 + optional Codex backend
ngrok tunnel for Cursor)
│ │ │
│ POST /v1/chat/completions │ POST /codex/responses │
│ Bearer <bridge token> │ Bearer <codex access token> │
│ model: bridge-fast │ model: gpt-5.4 │
├───────────────────────────────►├─────────────────────────────►│
│ │ Authorization: Bearer ... │
│ │ chatgpt-account-id: ... │
│ │ │
│◄──── SSE chat.completion.chunk◄─── SSE response.* events ────┤What it gives you
- A
http://127.0.0.1:7711/v1-compatible OpenAI endpoint - Streaming and non-streaming
chat/completions - Tool / function calling (translated both ways), multi-turn conversations
with correct
input_text/output_textcontent-part shaping per role - A passthrough
/v1/responsesif a client speaks the Responses API directly - Automatic OAuth refresh against
auth.openai.com(atomic +0o600onauth.json; concurrent 401s are coalesced into one refresh) - A strict
bridge-*model namespace covering all four upstream-accepted models. The flagship (gpt-5.5) splits into six reasoning × routing variants (bridge-pro-medium,-high,-xhigh, plus-fastsiblings) so you can pick depth/speed straight from the model picker; the smaller three (bridge-fast,bridge-codex,bridge-mini) ship as single aliases. See Model aliases below. - Optional
--tunnelmode that runs an ngrok tunnel so Cursor's cloud can reach the bridge (Cursor BYOK proxies through Cursor's servers, not from your editor — see Cursor specifics), with bearer-token auth on the public endpoint. - On-device Cursor settings sync: when tunnel mode is on the bridge writes
the current ngrok URL into
openAIBaseUrl, registers the fourbridge-*aliases underaiSettings.userAddedModels, and auto-checks them inaiSettings.modelOverrideEnabledso they're enabled in the picker on next launch. - An interactive first-run setup wizard with prerequisite checks (Node, codex CLI, valid auth, live upstream probe) and an always-on startup card that shows the exact alias → upstream-model mapping plus the fields to paste into Cursor.
Status
Published on npm as
codex-cursor-bridge.
First public release is v1.0.0.
Prerequisites
- A ChatGPT Pro or Plus subscription. Free accounts won't work — the Codex backend rejects them.
- Node.js 22.5 or newer. Zero npm dependencies; the bridge uses
node:sqlite(built in since 22.5, stable in 24+) for Cursor's settings sync, plus the rest of the Node standard library. - Codex CLI, logged in. Install from https://github.com/openai/codex
(or the desktop app at https://chatgpt.com/codex) and run
codex login. That writes~/.codex/auth.json, which this proxy reads. - Only if you're using Cursor: ngrok
installed and an authtoken configured (
ngrok config add-authtoken …). Cursor BYOK can't dial localhost, so the bridge needs a public URL — see Cursor specifics. Skip if you're using Aider / Continue / Open Interpreter / the OpenAI SDK directly.
Verify the prerequisites:
node --version # need v22.5+
test -f ~/.codex/auth.json && echo "auth present" || echo "run: codex login"
command -v ngrok >/dev/null && ngrok config check # only for CursorQuick start
If you've already run codex login, this is the whole thing:
npx codex-cursor-bridgeThe first time you run it you'll see an interactive setup wizard that:
- Checks prerequisites — Node version, the Codex CLI,
~/.codex/auth.json, your ChatGPT plan, and a live upstream probe. - Asks whether to enable tunnel mode (required for Cursor, optional otherwise) and verifies ngrok.
- Offers to install a LaunchAgent (macOS) so the proxy auto-starts at login.
- With tunnel mode on, patches Cursor's on-device settings, copies the tunnel URL to your clipboard, and opens Cursor.
Every subsequent start prints a compact card with the live base URL, generated bearer token (in tunnel mode), and an alias→model table:
── Cursor BYOK setup ──────────────────────────────────────
1. Cmd+, (or Ctrl+,) → Cursor Settings → Models
2. "OpenAI API Key": sk-bridge-...
3. Toggle "Override OpenAI Base URL" on
4. Base URL: https://<your-ngrok>.ngrok-free.dev/v1
5. In the model picker pick one of (alias → real model):
bridge-pro-medium → gpt-5.5 medium reasoning
bridge-pro-high → gpt-5.5 high reasoning (recommended)
bridge-pro-xhigh → gpt-5.5 extra-high reasoning
bridge-pro-medium-fast → gpt-5.5 medium · fast (best-effort)
bridge-pro-high-fast → gpt-5.5 high · fast (best-effort)
bridge-pro-xhigh-fast → gpt-5.5 extra-high · fast (best-effort)
bridge-fast → gpt-5.4 quicker, slightly smaller
bridge-codex → gpt-5.3-codex Codex-tuned variant
bridge-mini → gpt-5.2 cheapest / fastest
───────────────────────────────────────────────────────────Re-run the wizard at any time with --setup; daemons skip it automatically
(no TTY → no prompts) and you can force-skip with --no-setup.
Smoke test once it's running:
curl http://127.0.0.1:7711/v1/chat/completions \
-H 'Content-Type: application/json' \
-d '{"model":"bridge-fast","messages":[{"role":"user","content":"say hi"}],"stream":false}'Anything other than the four
bridge-*aliases gets a400with the supported list. See Model aliases.
Install
A few other ways to run it:
# Global install (then just run `codex-cursor-bridge`)
npm install -g codex-cursor-bridge
codex-cursor-bridge
# From a clone
git clone https://github.com/mmmeff/codex-cursor-bridge.git
cd codex-cursor-bridge
node server.mjsCLI flags
codex-cursor-bridge [options]
-p, --port <port> Port to listen on (default: 7711)
--host <host> Bind address (default: 127.0.0.1)
-a, --auth-path <path> Codex auth file (default: ~/.codex/auth.json)
--tunnel Expose the bridge through ngrok so Cursor's
cloud can reach it (required for Cursor BYOK)
--no-tunnel Force localhost-only mode
--setup Re-run the first-run setup wizard
--no-setup Skip the wizard even on first run (for daemons)
-h, --help Show help
-v, --version Show versionSetup state lives at ~/.codex-cursor-bridge/state.json. Delete it (or run
--setup) to walk through the wizard again.
Flags take precedence over environment variables. Example: bind on a different port temporarily:
npx codex-cursor-bridge --port 8088Model aliases
The bridge exposes a strict whitelist of model names — sending any other
name (e.g. gpt-4o, gpt-3.5-turbo, plain gpt-5.5) gets a 400 with the
supported list. The aliases map to the four model names the ChatGPT-Pro
Codex backend currently accepts, with the flagship (gpt-5.5) split into
six reasoning × routing variants so you can pick a depth/speed tradeoff
directly from Cursor's model picker:
| Bridge alias | Upstream model | Reasoning | Fast | Notes |
| --------------------------- | ----------------- | ----------- | ---- | ---------------------------------- |
| bridge-pro-medium | gpt-5.5 | medium | no | quick gpt-5.5 |
| bridge-pro-high | gpt-5.5 | high | no | recommended default |
| bridge-pro-xhigh | gpt-5.5 | xhigh | no | deepest reasoning, ~2.5× slower |
| bridge-pro-medium-fast | gpt-5.5 | medium | yes | medium + best-effort fast routing |
| bridge-pro-high-fast | gpt-5.5 | high | yes | high + best-effort fast routing |
| bridge-pro-xhigh-fast | gpt-5.5 | xhigh | yes | xhigh + best-effort fast routing |
| bridge-fast | gpt-5.4 | (default) | no | quicker, slightly smaller |
| bridge-codex | gpt-5.3-codex | (default) | no | Codex-tuned variant |
| bridge-mini | gpt-5.2 | (default) | no | cheapest / fastest |
About reasoning effort. The bridge sends reasoning: { effort: <level> }
to upstream. Empirically all four levels (low/medium/high/xhigh)
are accepted; we don't expose low. Higher effort → more reasoning tokens
→ noticeably slower (xhigh was ~2.5× the wall time of high in our probes).
About fast. The bridge sends service_tier: priority on *-fast
aliases. The upstream validator accepts it, but for ChatGPT-Pro accounts
the server response echoes service_tier: auto, suggesting it may be
normalized back. Treat fast as a hint, not a guarantee — if it stops
mattering empirically we'll drop the variants in a future release.
Why opaque names instead of bridge-gpt-5.5-xhigh etc.? Cursor's cloud
does a substring match on model names — anything containing gpt-5.5 is
treated as their premium SKU and routed through their managed service
regardless of your BYOK URL (the request never reaches this bridge; you
see "User Provided API Key Rate Limit Exceeded"). Opaque names like
bridge-pro-high don't match any of Cursor's reserved patterns, so they
pass cleanly through to BYOK. See
Cursor specifics for the full mechanics.
Earlier releases used
bridge-gpt-5.5(≤1.0.0) andbridge-pro(1.0.x). Both are now rejected. The wizard's auto-config cleans the old names out of Cursor's settings store automatically. If you don't use the wizard, remove them from Cursor → Settings → Models → "Add Model".
Cursor specifics
Cursor BYOK does not dial your base URL from the editor. The request is sent to Cursor's cloud, which then forwards it on to whatever URL you set. Three consequences:
- A localhost base URL is unreachable. Cursor's cloud cannot route to
http://127.0.0.1:...on your machine. You need a public URL —--tunnelspins up an ngrok tunnel for exactly this. - Cursor cherry-picks which model names go through BYOK. Its branded
premium SKUs (
gpt-5.5, the Composer family, Claude, etc.) bypass BYOK and use Cursor's own routing — you'll see a "User Provided API Key Rate Limit Exceeded" error if you try them. The routing decision uses substring matching: evenbridge-gpt-5.5orbridge-gpt-5.5-xhighwould be premium-hijacked because they containgpt-5.5. The bridge's opaque aliases (bridge-pro-*,bridge-fast,bridge-codex,bridge-mini) sidestep this entirely. - Cursor's running instance wipes BYOK settings on refresh. If Cursor is open while the bridge writes to its settings store, the changes are reverted within seconds when Cursor next syncs its in-memory copy back to disk. The bridge detects this and refuses to write — see below.
⚠️ Fully quit Cursor (
Cmd+Q, not just close the window) before running the bridge with--tunnel. Auto-config ofopenAIBaseUrland thebridge-*aliases only sticks while Cursor is not running. The bridge will print a loud notice if it detects Cursor and skip the sync.
One-time setup
# 1. Install ngrok and authenticate (free account is fine):
brew install --cask ngrok # or: https://ngrok.com/download
ngrok config add-authtoken <YOUR_TOKEN>
# 2. Fully quit Cursor (Cmd+Q).
# 3. Launch the bridge with the wizard — say YES to tunnel mode:
npx codex-cursor-bridge --setupThe wizard will:
- Verify your Codex CLI auth and probe the upstream.
- Start an ngrok tunnel, generate a
sk-bridge-<…>bearer token, persist it in~/.codex-cursor-bridge/state.json. - Patch Cursor's settings on-device:
- Set
openAIBaseUrlto the current ngrok URL - Add the four
bridge-*aliases toaiSettings.userAddedModels - Auto-check them in
aiSettings.modelOverrideEnabled - Remove any legacy
bridge-gpt-*names from earlier releases
- Set
- Copy the tunnel URL to your clipboard and open Cursor.
Then in Cursor:
Cmd+,→ Cursor Settings → Models tab.- OpenAI API Key: paste the
sk-bridge-…token from the startup card. This is the one thing we can't auto-fill — Cursor stores it as a secret that lives outside the settings DB we patch. - Click Verify. (URL + aliases are already wired up.)
- Pick
bridge-pro-high(recommended default) or any other alias in the model picker. It should already be checked thanks to the auto-config.
Daily operation
Each restart of the bridge spawns a new ngrok URL (free tier rotates the domain). The bridge auto-patches Cursor's settings with the new URL on every start — so the workflow is:
- Cmd+Q Cursor.
- Restart the bridge (Ctrl+C → re-run, or via the daemon below).
- Open Cursor; the new URL is already configured.
For a stable URL, configure an ngrok reserved domain (paid plan) or use a different tunneling provider (cloudflared, Tailscale Funnel — adapter PR welcome).
Note: Cursor's autonomous Composer agent uses Cursor's own models — BYOK only feeds the chat panel and the manual model selector. This proxy replaces the OpenAI bill, not Cursor's subscription.
Run it on login
macOS (LaunchAgent)
From a clone of the repo:
npm run install-launchd # or: bash scripts/install-launchd.shThis writes ~/Library/LaunchAgents/com.user.codex-cursor-bridge.plist,
loads it via launchctl, and verifies the health endpoint. The daemon
runs with stdin closed (so the wizard auto-skips) and reads the persisted
state — including whether tunnel mode is on. To uninstall:
npm run uninstall-launchdIf you're running an npm-global install (not a clone), the LaunchAgent installer refuses to point at the global path because npm may relocate it on upgrade. Either keep a clone around for the daemon, or write your own plist pointing at a stable bin path.
Linux (systemd)
Create ~/.config/systemd/user/codex-cursor-bridge.service:
[Unit]
Description=codex-cursor-bridge
After=network-online.target
[Service]
ExecStart=/usr/bin/node /absolute/path/to/codex-cursor-bridge/server.mjs
Environment=CODEX_BRIDGE_PORT=7711
Restart=always
RestartSec=2
[Install]
WantedBy=default.targetThen:
systemctl --user daemon-reload
systemctl --user enable --now codex-cursor-bridgeConfiguration
CLI flags (above) cover the common knobs. Everything else is via env vars, which is also how the LaunchAgent / systemd unit set values:
| Variable | CLI flag | Default | Meaning |
| ----------------------- | ---------------- | ------------------------------- | ------------------------------------------- |
| CODEX_BRIDGE_PORT | --port | 7711 | TCP port to listen on |
| CODEX_BRIDGE_HOST | --host | 127.0.0.1 | Bind address |
| CODEX_AUTH_PATH | --auth-path | ~/.codex/auth.json | Path to the Codex auth file |
| CODEX_CLIENT_VERSION | (none) | 0.131.0 | version header sent upstream |
| CODEX_ORIGINATOR | (none) | codex_cli_rs | originator header sent upstream |
| CODEX_CLIENT_ID | (none) | app_EMoamEEZ73f0CkXaXp7hrann | OAuth client_id used during token refresh |
| CODEX_BRIDGE_DEBUG | (none) | unset | Set to 1 to log incoming request bodies |
Endpoints
| Method | Path | Notes |
| ------ | ----------------------- | ------------------------------------------- |
| GET | /healthz | Liveness probe |
| GET | /v1/models | OpenAI-style model list (aliases) |
| POST | /v1/chat/completions | OpenAI Chat Completions, translated |
| POST | /v1/responses | Raw Responses API passthrough |
How it works
- Model resolution. Incoming
modelis checked against thebridge-*whitelist. Unknown names get a400with the supported list; known ones are mapped to the upstream-accepted name (e.g.bridge-fast→gpt-5.4). - Auth lookup. On each request the proxy reads
~/.codex/auth.json:tokens.access_token— short-lived JWT,chatgpt_plan_type=pro/plus.tokens.account_id— sent aschatgpt-account-idheader.
- Body translation. Either path (chat completions or Responses API)
normalizes the request into the Codex Responses shape:
messages→inputitems, orinputarrays pass through.system/developermessages → top-levelinstructions.- Content parts are reshaped per role:
role: 'user'→input_text/input_image,role: 'assistant'→output_text/refusal. (The Codex backend rejects mismatched part types withInvalid value: 'input_text', etc.) tools→ Responses-API-shaped function tools.- Tool call results (
role: 'tool') →function_call_outputitems. max_tokens/max_completion_tokens/max_output_tokens/userare dropped — upstream rejects them with "Unsupported parameter".
- Forward.
POST https://chatgpt.com/backend-api/codex/responseswith the Codex-flavoured headers (originator,version,User-Agent,OpenAI-Beta: responses=experimental). Body size is capped at 8 MiB. - Response translation. The upstream SSE stream is re-emitted as
chat.completion.chunkevents:response.output_text.delta→choices[0].delta.contentresponse.function_call_arguments.delta→choices[0].delta.tool_calls[].function.argumentsresponse.completed→ terminal chunk withfinish_reason+data: [DONE]
- Refresh on 401. Concurrent 401s are coalesced into a single OAuth
refresh against
auth.openai.com; the new token is atomically written back toauth.jsonwith0o600(preserves Codex's own permissions). - Tunnel + auth. With
--tunnel, an ngrok subprocess is spawned, the public URL is read from ngrok's local admin API at:4040, andAuthorization: Bearer <sk-bridge-…>is required on/v1/*. The token is generated once and persisted in~/.codex-cursor-bridge/state.json. - Cursor sync. When tunnel mode is on AND Cursor is fully quit, the
bridge opens
<appdata>/Cursor/User/globalStorage/state.vscdbvianode:sqliteand updates three fields inside the…reactivestorage…persistentStorage.applicationUserblob:openAIBaseUrl→ current ngrok URLaiSettings.userAddedModels← bridge aliases (deduped, legacybridge-gpt-*names removed)aiSettings.modelOverrideEnabled← same aliases, so they're checked in the picker on next launch
- Per-request logging. One short line per non-health request with
method, path, status (colored), latency, model, msg count, tool count,
and stream chunk count.
/healthzis silenced.
Use it from other clients
Anything that speaks OpenAI works. Non-Cursor clients (Aider, Continue,
Cline, Open Interpreter, raw SDKs) talk to the bridge directly and don't
need --tunnel — they can hit http://127.0.0.1:7711/v1 straight from
your machine.
OpenAI SDK (Node):
import OpenAI from 'openai';
const client = new OpenAI({
baseURL: 'http://127.0.0.1:7711/v1',
// No --tunnel → any non-empty string; the bridge doesn't authenticate
// localhost callers.
// With --tunnel → the sk-bridge-… token from ~/.codex-cursor-bridge/state.json
apiKey: 'sk-anything',
});
const r = await client.chat.completions.create({
model: 'bridge-fast',
messages: [{ role: 'user', content: 'hi' }],
});Aider / Continue / Cline / Open Interpreter / etc. — set their OpenAI
base URL to http://127.0.0.1:7711/v1, model to one of the bridge-*
aliases, and API key to anything non-empty. Skip --tunnel.
Limitations & known gotchas
- Four models only. The ChatGPT-subscription Codex backend currently
accepts
gpt-5.5,gpt-5.4,gpt-5.3-codex,gpt-5.2. No Claude, no o-series, no fine-tuned models. Use thebridge-*aliases. - No quota dashboard. ChatGPT Pro has soft Codex limits; exceed them and upstream will return 429s. There's no programmatic way to inspect remaining quota.
- Cursor's agent. Cursor's Composer agent uses Cursor's own models — BYOK only feeds the chat panel and the manual model selector.
- Cursor BYOK URL ≠ localhost. Cursor's cloud forwards BYOK calls, so
the bridge needs a public URL (see
--tunnel). The free ngrok tier rotates the URL each restart. - Cursor reserves the bare
gpt-5.5name (and substring-matches it, sobridge-gpt-5.5also gets premium-hijacked). Use thebridge-pro-*variants. - Public exposure with
--tunnel. Anyone who learns both the ngrok URL and the bridge token can spend your quota. The bridge generates a random per-install token, but treat the pair as sensitive. - Localhost mode is unauthenticated. Without
--tunnel, any process on your machine that can reach127.0.0.1:7711can hit the bridge. If that matters, use--tunnel(which adds bearer auth) or firewall the port. - Token expiry. The Codex access token typically lasts ~10 days.
Refresh is automatic against
auth.openai.com; if the refresh token itself expires, runcodex login. - Node 22.5+ required. The Cursor settings sync uses
node:sqlite, which is gated on Node 22.5+ (and stable in Node 24). The bridge fails to start on Node 20 even if you never use--tunnel.
Terms of service
This is a personal-use workaround. It uses the same auth flow the official
codex CLI uses; whether OpenAI considers that fair use from a non-Codex
client is a gray area. If you need formal sanction, use the OpenAI API
instead. Don't run this for other people, and don't expose it beyond
localhost.
Contributing
Patches welcome — particularly:
- Better error mapping (rate limits, content filters)
- Image / vision input fidelity
- A
claude-stylemessagesadapter for Anthropic-API clients - Quota / usage observability
- Cross-platform install scripts
