Codex Proxy is a self-hosted, OpenAI-compatible gateway that lets a team use a pool of authorized Codex subscription accounts through one endpoint. It handles quota-aware routing, transparent failover, per-user policy, usage accounting, request archives, notifications, and a responsive operations dashboard.
Looking for a Claude Code proxy instead? See the companion Claude Code Proxy.
The screenshots in this README are generated with internally consistent synthetic data. The overview contains 4.80B tokens (3.84B input + 960M output) across 36,840 requests, so its 80%/20% split is exact; 2.304B cached input tokens produce the displayed 60% cache hit rate. Five-hour resets are always less than five hours away and weekly resets are less than seven days away. No credential, prompt, or production record is used.
This is an independent project, not an OpenAI product. Add only accounts you control and are permitted to use this way; check the terms that apply to your organization and subscription.
The full-width TPS over time chart follows the selected date range, user, and model filters. It shows per-model averages of measured request TPS in hourly buckets for ranges up to two days and daily buckets for longer ranges. Missing measurements appear as gaps. Select model names in the legend to hide or show lines. The dashboard no longer includes the Top users ranking.
The supported installation path is agent-assisted: copy the complete prompt in INSTALLATION_AGENTS.md into your coding or infrastructure agent. It will ask where to install, whether to use a reverse proxy, and whether to use an existing Postgres instance before it builds, configures credentials, creates the first user, installs the client helper, and verifies the deployment. No manual installation steps are required.
If you use the dashboard from a Mac, the companion menu-bar app provides compact
Overview and Accounts views, including the same All, Authenticated, and
Usable account filters. The latest ad-hoc-signed build is published at the
Codex Proxy macOS releases
page. Download Codex-Proxy-macOS.dmg, open the disk image, and move Codex
Proxy.app to Applications. This build is intended for local use; macOS may
require Control-clicking the app and choosing Open because it is not
notarized.
This project was built quickly and entirely through AI-assisted development—
plainly, it was vibe-coded. I have not personally reviewed a single line of code
on main, so main should be treated as working but not human-audited software.
Review it for your own environment and risk model before putting sensitive
accounts or production traffic behind it.
That caveat is about code review, not a lack of real-world use. We have used the proxy internally for roughly four months. During the last three months it has handled more than 20 billion tokens and about 70,000 requests for us, and it has been stable without known operational problems. Our experience is not a substitute for an independent security review, but this is running software—not an untested demo.
I intend to review the code as time permits. The human branch will contain only
code I have personally reviewed, so it will naturally lag behind main: humans
are the bottleneck now (pun intended).
- Priority-ordered account pools; lower numeric priority is served first.
- PostgreSQL advisory-lock concurrency lanes per account, with reserved lanes for higher-priority users during bursts. Accounts are not pinned to users.
- Provider quota windows (five-hour, weekly, and any provider-reported windows), reset times, thresholds, cooldowns, and deterministic tie-breaking.
- OAuth refresh with serialized refresh-token rotation and reauthentication detection.
- Capacity, overload, 401, 404, 429, quota, and connection failures are handled before response bytes reach the client; the request advances to the next eligible account or fallback.
- Optional demand-triggered and manual warm-up of cold accounts.
- Codex reset-credit inspection and guarded redemption when the provider exposes an eligible credit.
- Multiple users and multiple labelled API keys per user. Key labels are required and plaintext keys are shown only once.
- User priorities and bulk priority assignment, independent of account priority.
- Per-user fallback opt-in (off by default), model allowlists, exact model rewrites, request modes, thinking levels, and model access policies.
- Per-user monthly/lifetime token and spend budgets, plus per-key request/token limits and revocation.
- Hashed API keys and Fernet-encrypted OAuth/fallback credentials.
- Create, disable, delete, and manage database-backed dashboard members from the Team page. Each member can be assigned an explicit checked list of exact permission strings; there are no required display names or role presets.
- Permissions are split across analytics, archived event bodies, accounts, proxy users, API keys, fallbacks, notifications, and team members. Mutating areas distinguish read, write, and delete access.
- Authorization is enforced by the API as well as navigation visibility. Unknown administrative routes fail closed for non-owner members.
- The environment-configured root login remains the break-glass owner. The dashboard deliberately does not store UI audit history or expose session revocation controls.
The dashboard and GET /api/v1/models use a deterministic local catalog, so a
client startup never waits for every pooled account to be queried. The current
Codex catalog is:
| Model | Notes |
|---|---|
gpt-5.5 |
Legacy Codex text model |
gpt-5.6-luna |
Codex GPT 5.6 family |
gpt-5.6-sol |
Codex GPT 5.6 family |
gpt-5.6-terra |
Codex GPT 5.6 family |
gpt-6-astra |
Codex GPT 6 family |
gpt-6-sol |
Codex GPT 6 family |
gpt-6-luna |
Codex GPT 6 family |
gpt-6.1-sol |
Codex GPT 6.1 Sol (live-verified; native catalogs may lag rollout) |
codex-auto-review |
Codex browser auto-review model |
The proxy's catalog is the set of supported IDs; individual users can still have narrower model permissions through their preset or user overrides. New OpenAI API models are added only after they appear in the Codex subscription catalog for an active account.
Model rewrites are shown in events as target (requested), for example
gpt-5.6-sol (gpt-6-astra).
Supported routes:
| Route | Purpose |
|---|---|
POST /api/v1/responses |
Native Responses API, streaming and non-streaming |
POST /api/v1/chat/completions |
OpenAI Chat Completions compatibility, translated upstream |
GET /api/v1/models |
Filtered local model catalog |
GET /api/v1/me/usage |
Consistent pooled five-hour/weekly usage and reset data |
GET /api/health |
Liveness/readiness probe |
OpenAI-compatible API fallbacks are tried only after no eligible subscription account can serve a request. Fallbacks have their own priorities, health, cooldowns, monthly spend caps, encrypted write-only credentials, and per-user opt-in. The fallback feature is disabled for every user by default.
The overview combines pool health, request volume, token and cost totals, and
the same reusable controls used throughout the dashboard: automatic refresh,
manual refresh, local-calendar date ranges, user selection, and model selection.
Times are rendered in the viewer's local timezone and use a consistent 12-hour
format. Token values are compacted with K, M, or B where appropriate.
Accounts are displayed in priority lanes with a two-column responsive card grid. Cards retain quota windows, spend, last use, provider checks, team/workspace, authentication state, cooldown/degraded state, reset credits, and actions for edit, refresh, warm-up, reauthentication, limit reset, disable, and delete. Accounts needing reauthentication are ordered first, followed by accounts in their normal usage order. The green action color is reserved for an action that is currently available (including an eligible limit reset or reauthentication).
Bulk actions can assign a priority to selected accounts without changing any other card setting.
Automatic weekly limit-reset redemption is an opt-in account setting. It is disabled by default, including for accounts created before this setting was introduced; administrators can enable it in each account's Edit dialog. It is a last resort: another available account that can serve the requested model always prevents automatic redemption. Once the pool is exhausted, choose an opted-in, recoverable account from the lowest-numbered priority group with valid reset credits. Within that group, the latest natural weekly reset date wins; unknown reset dates sort last. If no account in that group has usable credits, recovery considers the next priority group. Within that account, redeem the earliest-expiring valid credit. Recovery stops once capacity returns. A pool-wide database lock prevents concurrent workers from spending several credits. The background refresher updates every account before deciding to claim. Manual administrator redemption keeps its existing usage/expiry safeguards.
Users use the same two-column card language as accounts. Each card shows user priority, active state, request mode, thinking levels, allowed models, model rewrites, API-key count, all-time and current-month tokens/spend, and configured budgets. Drag-and-drop lanes and bulk assignment make priority changes explicit; the save operation is atomic.
The Users page also manages reusable policy presets. A preset defines allowed models, global request modes and reasoning levels, exact model rewrites, and optional per-model request modes/reasoning levels. A user may select a preset or No preset. With a preset, edits to individual policy fields—including allowed models and model rewrites—are explicit overrides; other fields stay inherited. With no preset, the entire policy is edited directly. Switching to No preset preserves the current effective policy across restarts. Upgraded installations create a Current configuration preset and assign existing users without changing their effective policies. User-wide request limits can be set independently per minute, rolling hour, and rolling 24 hours; they apply across all of the user's API keys. Zero or blank disables a limit.
Every request is an operational timeline: receipt, account attempt, selection, capacity or rate-limit result, cooldown, fallback attempt, response, and exhaustion. The event table is filterable by date range, user, model, and event type, paginated, color-coded by event family, and includes model, thinking level, account label, status codes, input/output/cache tokens, estimated cost, and request outcome. The default Individual requests view shows one collapsed summary per request, including its metrics and a shortened request ID. Use the Show events / Hide events button beside its timestamp to expand the complete event history. Pagination counts requests and keeps their events together. Event/date filters select the matching requests; each returned request includes its complete lifecycle, so expansion requires no additional fetch or loading row.
Agent run groups by Codex's root turn ID: normally one user message and the model/tool requests it triggers. Requests without that identifier stay separate.
Successful and failed requests are both retained. When request archiving is enabled, an event can open the exact captured request and response body from S3-compatible storage; authorization and cookie headers are never archived. Web-search requests still generate routing events, but their bodies are not archived, so those events do not offer a capture viewer.
Request summaries are green for success, red for failure, and neutral while pending. Stream outcomes are recorded after completion rather than inferred from HTTP 200 headers. Event records retain a user-name snapshot; deleted users with older records without a name are labeled Deleted user.
The event log is an operational timeline. One request can produce several
events, while token usage and cost are recorded once per request and attached
to related rows by request_id. Do not add the cost shown on multiple event
rows together.
| Event | When it occurs | Status | What happens next |
|---|---|---|---|
request.received |
An authenticated request enters routing. | — | Account selection begins. |
account.attempt |
A pooled subscription account is tried. | — | The request is sent upstream. |
account.busy |
The account's concurrency lane is full. | — | The account is skipped and another is tried. |
account.capacity |
The provider reports model capacity/unavailability before output. | Upstream | The account is temporarily cooled down and another is tried. |
account.cooldown |
The proxy records the cooldown action. | — | The account is kept out of selection temporarily. |
account.transient_error |
A retryable pre-output overload/server failure occurs. | Upstream | A short transient cooldown is applied; quota is not marked exhausted. |
account.rate_limited |
The provider reports an account/workspace quota limit. | 429 |
The provider reset/retry time is used before retrying the account. |
account.error |
The request fails before a usable upstream response arrives. | — | The account is excluded for this request and routing continues. |
account.response_received |
A pooled account has returned a response candidate. | Candidate | Account failover stops and the response is processed. |
fallback.attempt |
A configured pay-as-you-go provider is tried after subscription accounts fail. | — | The fallback request is sent upstream. |
fallback.busy |
A fallback provider reaches its concurrency ceiling. | — | Another fallback provider is tried, if available. |
fallback.capacity |
A fallback provider reports model capacity/unavailability. | Upstream | The fallback is cooled down and another provider is tried. |
fallback.transient_error |
A fallback emits a retryable pre-output error. | Upstream | A short circuit-breaker cooldown is applied. |
fallback.error |
A fallback request fails before a response arrives. | — | The provider is marked degraded/cooling down. |
fallback.response_received |
A fallback provider has returned a response candidate. | Candidate | Fallback routing stops and the response is processed. |
response.returned |
The proxy starts returning the selected upstream response. | HTTP status | For streams, usage is recorded when the stream finishes or disconnects. |
request.exhausted |
No eligible subscription account or fallback can serve the request. | 503 |
The client receives 503; pool/elevated-503 notifications may be queued. |
Operational rows can show the request's input/output/cache/cost values even when that particular event did not consume tokens. Those values come from the single usage record for the same request. A dash means no usage record or no reported value. Zero means a usage record exists with zero captured tokens; an interrupted stream may end before the provider reports usage. Pending requests have no final token counts, cost, or TPS yet. Unknown models have no pricing entry and therefore have an estimated cost of zero.
Fallback entries show label, provider, priority, enabled state, health, spend, and cap. Operators can enable fallback globally or opt individual users in; new users remain opted out until explicitly changed. Subscription traffic is always preferred.
Telegram notifications use an encrypted bot token, destination/chat and optional topic, timezone-aware schedules, per-event enablement, templates, and repeat cooldowns. Delivery is handled by a persistent outbox worker so Telegram latency never blocks inference. Account, pool, user/key guardrail, and daily, weekly, and monthly report events are supported.
client key
│
├─ authenticate user/key and enforce rate/token/spend budgets
├─ apply allowlist and exact model rewrite
├─ choose highest-priority eligible user lane
├─ choose highest-priority account with a free concurrency lane
├─ refresh OAuth if needed and forward the request
├─ on capacity/429/quota/connection/credential failure: cooldown + next account
├─ if the subscription pool is exhausted: optional user-enabled API fallback
└─ stream response, record usage/cost, emit events, return consistent usage data
Failures detected before output can trigger account failover. Once streaming
starts, provider failure events are preserved so clients receive the real outcome;
the proxy never fabricates a successful completion. SSE error inspection is
bounded to five seconds, including streams without a Content-Type header, and
comment keepalives are sent every ten seconds during upstream silence. This lets
long inline compactions continue without withholding all events until completion.
Usage headers and /api/v1/me/usage use the same pool-level calculation rather
than whichever account happened to answer last.
The default ceiling is 3 concurrent requests per account. It is deliberately
configurable and should be calibrated for the provider, subscription tier, and
model; see the TODO in backend/app/utils/account_limiter.py before changing it.
- In Accounts, start the Codex device authorization flow, approve each account in the displayed browser page, and complete login in the dashboard.
- In Users, create a user and a labelled API key. The
usr_...secret is shown once; the setup dialog can generate an installer for native Codex. - Configure the CLI with the generated command, or point an OpenAI-compatible
client at
http://localhost:8080/api/v1withAuthorization: Bearer usr_.... - Add fallbacks only if you need them, and opt each user in explicitly.
The native installer edits only the proxy values in ~/.codex/config.toml,
keeps a backup, stores the key with mode 0600, and leaves the normal Codex
binary and auth.json untouched.
Copy .env.example for the complete annotated list. The most important values
are:
| Variable | Default | Purpose |
|---|---|---|
DOMAIN |
localhost |
Public host and CORS origin |
HTTP_PORT / HTTPS_PORT |
8080 / 8443 |
Bundled gateway ports |
POSTGRES_* |
— | PostgreSQL connection |
FERNET_KEY |
required | Encrypts OAuth and fallback credentials |
JWT_SECRET |
required | Signs admin sessions (at least 32 characters) |
ADMIN_USERNAME |
admin |
Dashboard username |
ADMIN_PASSWORD |
required | Dashboard password |
QUOTA_REFRESH_INTERVAL_SECONDS |
60 |
Provider quota refresh interval |
MAX_CONCURRENT_REQUESTS_PER_ACCOUNT |
3 |
Per-account concurrency ceiling |
DEFAULT_KEY_RATE_LIMIT_PER_MINUTE |
0 |
Default key limit; zero means unlimited |
ARCHIVE_ENABLED |
false |
Store exact request/response bodies in S3-compatible storage |
ARCHIVE_REQUIRED |
true |
Fail closed when a required archive write fails |
WARMUP_ENABLED |
true |
Enable demand-triggered/manual warm-up |
Use the repository's blue-green script for live updates. It keeps one shared Traefik gateway, starts the next Compose slot, waits for health checks, probes the new API, promotes API before frontend, and drains the old slot only after both routes identify the new generation.
scripts/blue-green.sh status
BG_COMPOSE_FILES=docker-compose.yaml:docker-compose.live.yml \
BG_URL=https://codex-proxy.example.com \
make deploy-blue-greenFor an independent availability log during a rollout:
scripts/probe-availability.sh https://codex-proxy.example.com/api/health &
probe_pid=$!
make deploy-blue-green
kill "$probe_pid"; wait "$probe_pid" || trueValidate rendered Compose labels before changing containers:
BG_SLOT=blue BG_PRIORITY=1 BG_API_PRIORITY=2 \
docker compose -f docker-compose.yaml -f docker-compose.live.yml config --quietOAuth and fallback secrets are encrypted with Fernet. User keys are stored as
one-way hashes. Archived bodies are optional, compressed, and written to an
S3-compatible bucket; prompt and tool content may contain sensitive data, so
restrict bucket access and configure retention. Authorization and cookie
headers are excluded from captures. Review SECURITY.md before exposing the
dashboard publicly.
make verify
cd backend && uv sync && uv run pytest
cd frontend && pnpm install && pnpm lint && pnpm buildThe main directories are backend/ (FastAPI, SQLAlchemy, Alembic), frontend/
(Next.js dashboard), clients/ (Codex setup helpers), and the Compose/deployment
files at the repository root.
Issues and pull requests are genuinely welcome. Feel free to open an issue for anything that could be clearer or better, or send a PR if you want to fix or improve something yourself.
- Use Issues for reproducible bugs, focused feature requests, and documentation
problems. Search first, use one issue per concern, reproduce against the latest
main, and include sanitized logs when relevant. There is no support SLA. - Never disclose credentials, archived prompts, private logs, or database data in an issue. Report vulnerabilities through the repository's private vulnerability-reporting flow.
- Small fixes and documentation PRs do not require a prior issue. Discuss large features, migrations, protocol changes, and architectural work in an issue before implementation.
- PRs branch from
main, stay focused, include relevant tests and UI screenshots, update documentation, and pass backend/frontend CI. A merge intomaindoes not mean the code received line-by-line human review; usehumanwhen that distinction matters. - Draft PRs are welcome. There is currently no CLA; contributions are made under AGPL-3.0 and participation follows the Code of Conduct.
See CONTRIBUTING.md, SECURITY.md, CODE_OF_CONDUCT.md, and the previous detailed README for the complete policy and upgrade context.
AGPL-3.0. If you run a modified version as a network service, make the corresponding source available to its users.
The proxy can use several approved public egress IPs from one EC2 instance. AWS
assigns each secondary private address to the existing ENI and an Elastic IP
maps to that private address. The host boot service in ops/aws-egress/ makes
the secondary addresses visible to Linux after reboot. The separate
docker-compose.egress.yml project runs one authenticated CONNECT relay per
source address; the application containers reach it through
host.docker.internal.
Set EGRESS_TARGETS_JSON in the protected .env with one target per relay,
then start the relay project. Each account's Edit dialog has an Egress
network path selector. Accounts default to (and existing accounts are
initialized to) the first enabled target in configuration order; selecting a
different target pins every provider/OAuth/quota request for that account to
it. There is no automatic rotation or cross-target failover. The selector
displays private and public address metadata but never relay credentials. Keep
the relay token out of Git and restrict its allowlist to the provider hosts
(api.openai.com/chatgpt.com for Codex).
Verify with docker compose -f docker-compose.egress.yml ps, the relay health
endpoint, and a provider request that returns an expected authentication error
instead of a connection error. During production updates run
scripts/blue-green.sh; never start a second Traefik or publish relay ports
publicly. Add or remove AWS EIPs only after confirming the ENI/private-IP
mapping and the approved company egress list.
Each preset and user policy has an Allow 1M context window option. When it is off, the proxy advertises the standard 272,000-token Codex context and ceiling. When it is on, the authenticated user's model catalog advertises a 1,000,000-token context and ceiling with a 900,000-token automatic-compaction limit. Existing user Devasheesh is explicitly migrated with the option enabled; new users and everyone else default to the standard window.
Presets include Allow API fallback providers. Users inherit this setting unless they select a per-user fallback override. Clearing the override restores preset inheritance. The migration preserves existing user fallback permissions.





