Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

11 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

codex-responses-proxy

License

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.

Background

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.json or $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 output semantics for Shelley-compatible clients in both modes
  • removes request fields the Codex backend rejects, such as max_output_tokens

Status

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.

Requirements

  • Go
  • OpenAI Codex CLI installed
  • Codex CLI authenticated with ChatGPT:
codex login --device-auth

That 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": "..."
  }
}

Run

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." \
  -debug

Flags:

  • -addr: listen address, default 127.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, default web_search_preview=web_search. Format: old=new. An empty new name drops the tool entirely.
  • -file-root: optional root directory for local x_proxy_files expansion
  • -max-file-bytes: maximum bytes per local x_proxy_files attachment, default 25 MiB

Install a binary

Once this repository is public, install the latest version with:

go install github.com/David-Factor/codex-responses-proxy@latest

This installs a codex-responses-proxy binary into your Go bin directory, usually ~/go/bin.

Run as a daemon

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-proxy

Check logs:

journalctl --user -u codex-responses-proxy -f

Optional: 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.

Endpoints

POST /v1/responses

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.

POST /responses

Alias for manual testing.

GET /healthz

Returns:

ok

Quick test

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."
  }' | jq

The response should be a JSON object shaped like an OpenAI Responses API response, including an output array.

Local file attachments

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/workspace

Then 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_file content item with a base64 data URL
  • removes x_proxy_files from 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.

Using with Shelley

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.

Streaming behavior

The Codex backend is always called in streaming mode internally. Downstream behavior depends on the client's request:

  • If stream is omitted or false, 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 stream is true, the proxy returns Content-Type: text/event-stream and forwards Responses-compatible SSE. It preserves text deltas and patches terminal events when needed so the final response.completed payload includes output.

How request patching works

Before forwarding to Codex, the proxy modifies the JSON payload:

  • sets upstream stream: true regardless of the downstream mode
  • sets store: false because the Codex backend rejects store: true
  • converts string input values into list-shaped Responses input messages
  • removes replayed reasoning input items, which are not persisted when store is false
  • adds top-level instructions if omitted
  • deletes max_output_tokens
  • deletes max_completion_tokens
  • renames or drops tool types according to the -tool-rename flag

These changes mirror the constraints of the Codex backend and behavior observed in similar integrations.

Tool type renaming

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.

How auth works

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: ...

Limitations

  • Downstream streaming is opt-in with stream: true; omitted or false stream still 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.

Security notes

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 -debug when prompts or outputs may contain sensitive data

Development

Format and test:

gofmt -w main.go main_test.go
go test ./...
go vet ./...

Build:

go build .

License

Apache-2.0. See LICENSE.

Related work

About

Local OpenAI Responses API proxy for Shelley and other coding agents, backed by Codex CLI / ChatGPT subscription auth

Topics

Resources

Security policy

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages