| name | codex-shim-byok-models |
| description | Run Codex Desktop with Factory BYOK models and ChatGPT GPT-5.5 via local API shim |
| triggers | ["how do I use custom models in Codex Desktop","set up codex-shim with Factory models","expose my OpenAI API key to Codex","add custom models to Codex picker","route Codex through local shim server","patch Codex Desktop model allowlist","configure BYOK models for Codex","use ChatGPT subscription with Codex Desktop"] |
Codex Shim BYOK Models
Skill by ara.so — Codex Skills collection.
Overview
codex-shim is a local Python API shim that intercepts Codex Desktop's model requests and routes them to:
- Any model in your
~/.factory/settings.json (Factory BYOK)
- Your ChatGPT subscription GPT-5.5
- Custom OpenAI/Anthropic/generic chat completion endpoints
It exposes a local Responses API endpoint that Codex Desktop points to, bypassing Codex's server-side Statsig model allowlist.
Key capabilities:
- Use any OpenAI, Anthropic, DeepSeek, Gemini, OpenRouter, or Z.ai model
- Keep ChatGPT subscription GPT-5.5 alongside BYOK models
- No modification to
~/.codex/config.toml (uses launch-time overrides)
- Translates between Codex Responses API ↔ upstream APIs (OpenAI chat completions, Anthropic messages)
Installation
git clone https://github.com/0xSero/codex-shim ~/Documents/codex-shim
cd ~/Documents/codex-shim
python3 -m pip install --user aiohttp pytest
ln -s "$PWD/bin/codex-shim" ~/.local/bin/codex-shim
ln -s "$PWD/bin/codex-app" ~/.local/bin/codex-app
ln -s "$PWD/bin/codex-model" ~/.local/bin/codex-model
Verify installation:
codex-shim --help
Configuration
Factory Settings File
The shim reads ~/.factory/settings.json by default. Structure:
{
"customModels": [
{
"model": "gpt-5.5",
"provider": "openai",
"baseUrl": "https://api.openai.com/v1",
"apiKey": "${OPENAI_API_KEY}",
"displayName": "OpenAI GPT-5.5",
"maxContextLimit": 400000
},
{
"model": "claude-opus-4-7-20251109",
"provider": "anthropic",
"baseUrl": "https://api.anthropic.com/v1",
"apiKey": "${ANTHROPIC_API_KEY}",
"displayName": "Claude Opus 4.7"
},
{
"model": "deepseek-v4-pro",
"provider": "anthropic",
"baseUrl": "https://api.deepseek.com/anthropic",
"apiKey": "${DEEPSEEK_API_KEY}",
"displayName": "DeepSeek V4 Pro",
"noImageSupport": true
}
]
}
Supported providers:
openai → OpenAI /v1/chat/completions
generic-chat-completion-api → OpenAI-compatible endpoints
anthropic → Anthropic /v1/messages
Custom Config Path
codex-shim --settings /path/to/custom-models.json generate
codex-shim --settings /path/to/custom-models.json start
Environment Variables
Store API keys in environment:
export OPENAI_API_KEY="sk-..."
export ANTHROPIC_API_KEY="sk-ant-..."
export DEEPSEEK_API_KEY="..."
Key Commands
Generate Catalog
Reads Factory settings and creates .codex-shim/custom_model_catalog.json:
codex-shim generate
Start/Stop Daemon
codex-shim start
codex-shim status
codex-shim stop
codex-shim restart
List Available Models
codex-shim list
codex-model list
Launch Codex Desktop
codex-app
codex-app /path/to/project
codex-shim app /path/to/project
Switch Active Model
codex-model list
codex-model openai-gpt-5-5
codex-app
Run Codex CLI Through Shim
codex-shim codex -- chat "explain this code"
codex-shim codex -- edit main.py "add error handling"
Model Routing Architecture
Codex Desktop → /v1/responses → codex-shim (127.0.0.1:8765)
┃
┏━━━━━━━━━━━━━━╋━━━━━━━━━━━━━━┓
┃ ┃ ┃
slug "openai-gpt-5-5" provider provider
┃ "openai" "anthropic"
┃ ┃ ┃
chatgpt.com/backend-api baseUrl/ baseUrl/
/codex/responses chat/ messages
(Bearer token) completions (x-api-key)
The shim:
- Receives Codex Responses API request
- Looks up slug in catalog
- Translates to upstream format (OpenAI chat completions or Anthropic messages)
- Streams upstream response
- Translates back to Responses API format
Code Examples
Python: Implementing Custom Provider Translation
async def translate_to_openai(responses_request):
"""Convert Codex Responses API → OpenAI chat completions."""
return {
"model": responses_request["model"],
"messages": responses_request["messages"],
"stream": True,
"temperature": responses_request.get("temperature", 1.0),
"max_tokens": responses_request.get("max_tokens"),
}
async def translate_to_anthropic(responses_request):
"""Convert Codex Responses API → Anthropic messages."""
messages = []
system = None
for msg in responses_request["messages"]:
if msg["role"] == "system":
system = msg["content"]
else:
messages.append({
"role": msg["role"],
"content": msg["content"]
})
body = {
"model": responses_request["model"],
"messages": messages,
"stream": True,
"max_tokens": responses_request.get("max_tokens", 4096),
}
if system:
body["system"] = system
return body
Python: Adding Custom Model Programmatically
import json
from pathlib import Path
def add_custom_model(model_config):
"""Add model to Factory settings."""
settings_path = Path.home() / ".factory" / "settings.json"
if settings_path.exists():
with open(settings_path) as f:
settings = json.load(f)
else:
settings = {"customModels": []}
settings["customModels"].append(model_config)
with open(settings_path, "w") as f:
json.dump(settings, f, indent=2)
add_custom_model({
"model": "anthropic/claude-3.5-sonnet",
"provider": "openai",
"baseUrl": "https://openrouter.ai/api/v1",
"apiKey": "${OPENROUTER_API_KEY}",
"displayName": "Claude 3.5 Sonnet (OpenRouter)",
"maxContextLimit": 200000
})
Shell: Automated Setup Script
#!/bin/bash
set -e
CODEX_SHIM_DIR="$HOME/Documents/codex-shim"
if [ ! -d "$CODEX_SHIM_DIR" ]; then
git clone https://github.com/0xSero/codex-shim "$CODEX_SHIM_DIR"
fi
cd "$CODEX_SHIM_DIR"
python3 -m pip install --user aiohttp
mkdir -p "$HOME/.local/bin"
ln -sf "$CODEX_SHIM_DIR/bin/codex-shim" "$HOME/.local/bin/"
ln -sf "$CODEX_SHIM_DIR/bin/codex-app" "$HOME/.local/bin/"
ln -sf "$CODEX_SHIM_DIR/bin/codex-model" "$HOME/.local/bin/"
codex-shim generate
codex-shim start
echo "✓ Shim installed and running"
codex-shim status
macOS Picker Patch
Codex Desktop's Statsig config hides models not on a server-side allowlist. Apply this one-time ASAR patch to bypass:
APP=/Applications/Codex.app
sudo cp -R "$APP" "$APP.unpatched-$(date +%Y%m%d-%H%M%S)"
cd /tmp && rm -rf codex-asar-patch && mkdir codex-asar-patch && cd codex-asar-patch
npx --yes @electron/asar extract "$APP/Contents/Resources/app.asar" extracted
PATCH_FILE=$(grep -RIl 'useHiddenModels' extracted/webview/assets/model-queries-*.js | head -n1)
sed -i.bak -E 's/let u=c\.useHiddenModels&&o!==`amazonBedrock`,d;/let u=!1,d;/' "$PATCH_FILE"
diff "$PATCH_FILE.bak" "$PATCH_FILE" && echo "ERROR: No changes made" && exit 1
rm "$PATCH_FILE.bak"
npx --yes @electron/asar pack extracted app.asar.new
sudo cp app.asar.new "$APP/Contents/Resources/app.asar"
HEADER_HASH=$(python3 - "$APP/Contents/Resources/app.asar" <<'PY'
import struct, hashlib, sys
with open(sys.argv[1], 'rb') as f:
data_size, header_size, _, json_size = struct.unpack('<4I', f.read(16))
header_json = f.read(json_size)
print(hashlib.sha256(header_json).hexdigest())
PY
)
sudo /usr/libexec/PlistBuddy -c \
"Set :ElectronAsarIntegrity:Resources/app.asar:hash $HEADER_HASH" \
"$APP/Contents/Info.plist"
sudo codesign --force --deep --sign - "$APP"
echo "✓ Patch applied. Launch Codex Desktop."
Rollback:
sudo rm -rf "$APP"
sudo mv "$APP.unpatched-YYYYMMDD-HHMMSS" "$APP"
ChatGPT GPT-5.5 Passthrough
If ~/.codex/auth.json exists with auth_mode: chatgpt, the shim auto-generates a synthetic slug openai-gpt-5-5 that proxies to:
https://chatgpt.com/backend-api/codex/responses
Authorization: Bearer <access_token from auth.json>
This bypasses Factory and uses your ChatGPT subscription quota.
Disable:
jq 'del(.models[] | select(.slug == "openai-gpt-5-5"))' \
.codex-shim/custom_model_catalog.json > tmp.json && mv tmp.json .codex-shim/custom_model_catalog.json
Common Patterns
Multi-Provider Setup
{
"customModels": [
{
"model": "gpt-5.5",
"provider": "openai",
"baseUrl": "https://api.openai.com/v1",
"apiKey": "${OPENAI_API_KEY}",
"displayName": "GPT-5.5"
},
{
"model": "claude-opus-4-7-20251109",
"provider": "anthropic",
"baseUrl": "https://api.anthropic.com/v1",
"apiKey": "${ANTHROPIC_API_KEY}",
"displayName": "Claude Opus 4.7"
},
{
"model": "gemini-2.0-flash-exp",
"provider": "openai",
"baseUrl": "https://generativelanguage.googleapis.com/v1beta/openai",
"apiKey": "${GOOGLE_API_KEY}",
"displayName": "Gemini 2.0 Flash"
}
]
}
OpenRouter Aggregation
{
"model": "meta-llama/llama-3.3-70b-instruct",
"provider": "openai",
"baseUrl": "https://openrouter.ai/api/v1",
"apiKey": "${OPENROUTER_API_KEY}",
"displayName": "Llama 3.3 70B (OpenRouter)"
}
DeepSeek with Anthropic-Style Thinking
{
"model": "deepseek-v4-pro",
"provider": "anthropic",
"baseUrl": "https://api.deepseek.com/anthropic",
"apiKey": "${DEEPSEEK_API_KEY}",
"displayName": "DeepSeek V4 Pro",
"noImageSupport": true
}
The shim translates extended thinking blocks from thinking items to reasoning.encrypted_content.
Troubleshooting
Models Don't Appear in Picker
Cause: Statsig allowlist still active.
Solution: Apply macOS picker patch (see section above).
"Connection refused" When Launching Codex
Cause: Shim daemon not running.
codex-shim status
codex-shim start
401 Unauthorized Errors
Cause: API key not found or invalid.
Check environment variables:
echo $OPENAI_API_KEY
echo $ANTHROPIC_API_KEY
Verify settings file:
cat ~/.factory/settings.json | jq '.customModels[].apiKey'
Test upstream directly:
curl https://api.openai.com/v1/models \
-H "Authorization: Bearer $OPENAI_API_KEY"
ChatGPT Passthrough Not Working
Verify auth.json exists:
cat ~/.codex/auth.json | jq '.auth_mode'
Check access token validity:
TOKEN=$(jq -r '.access_token' ~/.codex/auth.json)
curl https://chatgpt.com/backend-api/me \
-H "Authorization: Bearer $TOKEN"
Shim Logs
tail -f ~/.codex-shim/codex-shim.log
tail -n 50 ~/.codex-shim/codex-shim.log
Port Already in Use
codex-shim --port 9876 start
codex-shim --port 9876 app
Extended Thinking Not Appearing
For Anthropic-shaped providers (Claude, DeepSeek), thinking blocks appear as reasoning.encrypted_content items in Codex UI.
Verify provider is set to anthropic:
jq '.customModels[] | select(.model=="deepseek-v4-pro") | .provider' \
~/.factory/settings.json
Should return "anthropic", not "openai".
Testing
Run test suite:
cd ~/Documents/codex-shim
python3 -m pytest tests/ -v
Test specific translation:
import pytest
from codex_shim.translator import translate_to_anthropic
@pytest.mark.asyncio
async def test_system_message_extraction():
request = {
"model": "claude-opus-4-7",
"messages": [
{"role": "system", "content": "You are helpful"},
{"role": "user", "content": "Hello"}
]
}
result = await translate_to_anthropic(request)
assert result["system"] == "You are helpful"
assert len(result["messages"]) == 1
MCP Tool Forwarding
Codex Desktop forwards three generic MCP tools to all models (built-in and shim-routed):
list_mcp_resources
list_mcp_resource_templates
read_mcp_resource
These are available in the function calling schema for every routed model. The model calls list_mcp_resources to discover available resources.
Note: Codex Desktop does not flatten individual MCP server tools. That's a Codex client behavior, not a shim limitation.
Official Repository: https://github.com/0xSero/codex-shim
License: MIT
Requires: Python 3.11+, aiohttp