A small Go proxy that exposes an OpenAI-compatible Responses API endpoint backed by the OpenAI Codex backend used by the Codex CLI.
The initial use case is running API-oriented coding agents against models available through an existing ChatGPT/Codex subscription, without needing a separate OpenAI API key. It was built for Shelley, but anything that can talk to POST /v1/responses may be able to use it.
Inspired by Simon Willison's write-up, "A pelican for GPT-5.5 via the semi-official Codex backdoor API", and his reference implementation/plugin simonw/llm-openai-via-codex.
OpenAI's Codex CLI authenticates via ChatGPT and calls a Codex-specific endpoint:
https://chatgpt.com/backend-api/codex/responses
Simon Willison documented using that endpoint to access models available through a Codex subscription. His post cites public comments from OpenAI folks indicating this pattern is intended to be supported for tools such as OpenCode, Pi, Claude Code, and similar coding environments.
This project adapts that idea into a local HTTP proxy:
client -> http://127.0.0.1:8787/v1/responses -> chatgpt.com/backend-api/codex/responses
The proxy:
- reads Codex CLI auth from
~/.codex/auth.jsonor$CODEX_HOME/auth.json - refreshes expired ChatGPT access tokens using the stored refresh token
- forwards requests to the Codex backend with the required headers
- always forwards upstream requests with
stream: true, because the Codex backend expects streaming - returns buffered final JSON by default, or downstream Responses-compatible SSE when the client opts in with
stream: true - preserves final response
outputsemantics for Shelley-compatible clients in both modes - removes request fields the Codex backend rejects, such as
max_output_tokens
Experimental. This depends on Codex backend behavior that may change.
It is not an official OpenAI API client, and it is not a way to avoid paying for access. You need a valid ChatGPT/Codex subscription and a working Codex CLI login.
- Go
- OpenAI Codex CLI installed
- Codex CLI authenticated with ChatGPT:
codex login --device-authThat should create an auth file at:
~/.codex/auth.json
or, if you use a custom Codex home:
$CODEX_HOME/auth.json
The auth file must have:
{
"auth_mode": "chatgpt",
"tokens": {
"access_token": "...",
"refresh_token": "..."
}
}go run .By default the proxy listens on:
127.0.0.1:8787
Useful flags:
go run . \
-addr 127.0.0.1:8787 \
-instructions "You are a helpful coding assistant." \
-debugFlags:
-addr: listen address, default127.0.0.1:8787-instructions: default top-level instructions added when the request does not include any-debug: log patched request bodies and stream/debug details-tool-rename: comma-separated tool type rename pairs, defaultweb_search_preview=web_search. Format:old=new. An empty new name drops the tool entirely.-file-root: optional root directory for localx_proxy_filesexpansion-max-file-bytes: maximum bytes per localx_proxy_filesattachment, default 25 MiB
Once this repository is public, install the latest version with:
go install github.com/David-Factor/codex-responses-proxy@latestThis installs a codex-responses-proxy binary into your Go bin directory, usually ~/go/bin.
For Linux systems with systemd, this repository includes an example user service. A user service is preferable because the proxy needs access to your user-owned Codex auth file at ~/.codex/auth.json.
Install the binary somewhere stable:
mkdir -p ~/.local/bin
go build -o ~/.local/bin/codex-responses-proxy .Install and start the user service:
mkdir -p ~/.config/systemd/user
cp contrib/systemd/user/codex-responses-proxy.service ~/.config/systemd/user/
systemctl --user daemon-reload
systemctl --user enable --now codex-responses-proxyCheck logs:
journalctl --user -u codex-responses-proxy -fOptional: keep the service running after logout on Linux hosts that support lingering:
loginctl enable-linger "$USER"The example service binds to 127.0.0.1:8787. Keep that default unless you add your own authentication layer.
Primary endpoint. Configure clients to use:
http://127.0.0.1:8787/v1
Then a client that normally calls /v1/responses will hit the proxy.
Alias for manual testing.
Returns:
ok
curl -s http://127.0.0.1:8787/v1/responses \
-H 'Content-Type: application/json' \
-d '{
"model": "gpt-5.5",
"input": "Say hello in exactly five words."
}' | jqThe response should be a JSON object shaped like an OpenAI Responses API response, including an output array.
The proxy supports a nonstandard request extension, x_proxy_files, for local experiments where a client such as Shelley knows a file path but cannot build a Responses input_file item itself.
Start the proxy with an explicit file root:
go run . -file-root /path/to/workspaceThen send file specs relative to that root:
{
"model": "gpt-5.5",
"input": "Inspect the attached workbook and summarize the visible sheets.",
"x_proxy_files": [
{
"path": "DOCS/6. MEDIA VENDORS SCHEDULES/OOH/ALLIANCE x TTAS Off Season 2026 v2.xlsx",
"filename": "ALLIANCE x TTAS Off Season 2026 v2.xlsx",
"mime_type": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
}
]
}Before forwarding upstream, the proxy:
- reads each local file under
-file-root - rejects paths outside
-file-root, including symlink escapes - rejects files larger than
-max-file-bytes - appends a Responses
input_filecontent item with a base64 data URL - removes
x_proxy_filesfrom the upstream request
This only tests whether the upstream model/backend accepts and understands input_file content for the provided file type. It does not parse workbook contents itself.
For an opt-in streaming response, include "stream": true:
curl -N http://127.0.0.1:8787/v1/responses \
-H 'Content-Type: application/json' \
-d '{
"model": "gpt-5.5",
"input": "Say hello in exactly five words.",
"stream": true
}'The streaming response uses Content-Type: text/event-stream and emits Responses-compatible SSE events such as response.output_text.delta and response.completed.
Start the proxy:
go run .Configure Shelley or another OpenAI Responses-compatible client to use:
http://127.0.0.1:8787/v1
The proxy handles /v1/responses.
The Codex backend is always called in streaming mode internally. Downstream behavior depends on the client's request:
- If
streamis omitted orfalse, the proxy buffers the Codex SSE stream and returns a completed JSON Responses API object. This remains the default for Shelley-compatible clients that expect final JSON. - If
streamistrue, the proxy returnsContent-Type: text/event-streamand forwards Responses-compatible SSE. It preserves text deltas and patches terminal events when needed so the finalresponse.completedpayload includesoutput.
Before forwarding to Codex, the proxy modifies the JSON payload:
- sets upstream
stream: trueregardless of the downstream mode - sets
store: falsebecause the Codex backend rejectsstore: true - converts string
inputvalues into list-shaped Responses input messages - removes replayed
reasoninginput items, which are not persisted whenstoreis false - adds top-level
instructionsif omitted - deletes
max_output_tokens - deletes
max_completion_tokens - renames or drops tool types according to the
-tool-renameflag
These changes mirror the constraints of the Codex backend and behavior observed in similar integrations.
The Codex backend uses its own internal tool type names that differ from the
standard OpenAI Responses API. For example, the standard web_search_preview
tool must be sent as web_search. The -tool-rename flag handles this:
# Default: rename web_search_preview -> web_search
go run .
# Add additional renames, or drop a tool type:
go run . -tool-rename "web_search_preview=web_search,other_tool="Empty new name (e.g. other_tool=) drops that tool type from the request.
On each request, the proxy reads the Codex CLI auth document. It expects ChatGPT auth mode:
auth_mode = chatgpt
If the access token is still valid, it is reused. If it is expired and a refresh token is present, the proxy refreshes it through:
https://auth.openai.com/oauth/token
The updated tokens are written back to the Codex auth file with 0600 permissions.
If an account ID can be extracted from the tokens, the proxy forwards it as:
ChatGPT-Account-ID: ...
- Downstream streaming is opt-in with
stream: true; omitted or falsestreamstill returns buffered final JSON. - It only implements the Responses endpoint needed by API-oriented agents.
- Tool/function output items are preserved for agent loops. Replayed reasoning input items are removed because the Codex backend does not persist them with
store: false. - It depends on the Codex backend endpoint and auth file format remaining compatible.
See SECURITY.md.
This proxy uses the same ChatGPT/Codex credentials as your Codex CLI login. Treat the machine running it as trusted.
Recommended defaults:
- keep it bound to
127.0.0.1 - do not expose it publicly
- protect
~/.codex/auth.json - avoid running with
-debugwhen prompts or outputs may contain sensitive data
Format and test:
gofmt -w main.go main_test.go
go test ./...
go vet ./...Build:
go build .Apache-2.0. See LICENSE.