@pikoloo/codex-proxy 1.0.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +199 -0
  3. package/bin/cli.js +118 -0
  4. package/docs/ACCOUNTS.md +202 -0
  5. package/docs/API.md +289 -0
  6. package/docs/ARCHITECTURE.md +129 -0
  7. package/docs/CLAUDE_INTEGRATION.md +163 -0
  8. package/docs/OAUTH.md +85 -0
  9. package/docs/OPENCLAW.md +34 -0
  10. package/docs/legal.md +11 -0
  11. package/images/dashboard-screenshot.png +0 -0
  12. package/images/demo-screenshot.png +0 -0
  13. package/images/f757093f-507b-4453-994e-f8275f8b07a9.png +0 -0
  14. package/package.json +61 -0
  15. package/public/css/style.css +1502 -0
  16. package/public/index.html +827 -0
  17. package/public/js/app.js +601 -0
  18. package/src/account-manager.js +528 -0
  19. package/src/account-rotation/index.js +93 -0
  20. package/src/account-rotation/rate-limits.js +293 -0
  21. package/src/account-rotation/strategies/base-strategy.js +48 -0
  22. package/src/account-rotation/strategies/index.js +31 -0
  23. package/src/account-rotation/strategies/round-robin-strategy.js +42 -0
  24. package/src/account-rotation/strategies/sticky-strategy.js +97 -0
  25. package/src/claude-config.js +153 -0
  26. package/src/cli/accounts.js +557 -0
  27. package/src/direct-api.js +164 -0
  28. package/src/format-converter.js +420 -0
  29. package/src/index.js +46 -0
  30. package/src/kilo-api.js +68 -0
  31. package/src/kilo-format-converter.js +285 -0
  32. package/src/kilo-models.js +103 -0
  33. package/src/kilo-streamer.js +243 -0
  34. package/src/middleware/credentials.js +116 -0
  35. package/src/middleware/sse.js +96 -0
  36. package/src/model-api.js +189 -0
  37. package/src/model-mapper.js +157 -0
  38. package/src/oauth.js +666 -0
  39. package/src/response-streamer.js +409 -0
  40. package/src/routes/accounts-route.js +332 -0
  41. package/src/routes/api-routes.js +98 -0
  42. package/src/routes/chat-route.js +229 -0
  43. package/src/routes/claude-config-route.js +121 -0
  44. package/src/routes/logs-route.js +43 -0
  45. package/src/routes/messages-route.js +203 -0
  46. package/src/routes/models-route.js +119 -0
  47. package/src/routes/settings-route.js +143 -0
  48. package/src/security.js +142 -0
  49. package/src/server-settings.js +56 -0
  50. package/src/server.js +58 -0
  51. package/src/signature-cache.js +106 -0
  52. package/src/thinking-utils.js +312 -0
  53. package/src/utils/logger.js +156 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,199 @@
1
+ # Codex Claude Proxy
2
+
3
+ ![Codex Proxy dashboard screenshot](./images/dashboard-screenshot.png)
4
+
5
+ _Current dashboard preview: a real capture of the local Web UI with the macOS-style glass layout._
6
+
7
+ [![MIT License](https://img.shields.io/badge/License-MIT-green.svg)](https://choosealicense.com/licenses/mit/)
8
+ [![Node.js Version](https://img.shields.io/badge/Node.js-18%2B-blue.svg)](https://nodejs.org/)
9
+ [![GitHub stars](https://img.shields.io/github/stars/surajmandalcell/codex-proxy?style=social)](https://github.com/surajmandalcell/codex-proxy)
10
+
11
+ > **Use Claude Code CLI with the power of ChatGPT Codex models.**
12
+ > A local proxy that translates Anthropic API requests into ChatGPT Codex calls, enabling you to use the `claude` CLI tool with your ChatGPT Free/Plus/Pro subscription.
13
+
14
+ | Role | Details |
15
+ | --- | --- |
16
+ | Maintainer | Suraj Mandal |
17
+ | GitHub | [surajmandalcell](https://github.com/surajmandalcell) |
18
+ | Contact | [surajmandalcell@gmail.com](mailto:surajmandalcell@gmail.com) |
19
+ | Package | [@pikoloo/codex-proxy](https://www.npmjs.com/package/@pikoloo/codex-proxy) |
20
+
21
+ ---
22
+
23
+ ## 🚀 Features
24
+
25
+ - **Seamless Translation**: Translates Anthropic Messages API calls to ChatGPT Codex format.
26
+ - **Model Mapping**: maps Claude model aliases to current OpenAI models, with direct GPT model IDs passed through.
27
+ - **Personal Account Mode**: Uses the active ChatGPT account by default for local-only personal use, with account switching and auto-refresh.
28
+ - **Web Dashboard**: Built-in macOS-style UI (`http://localhost:8081`) for managing accounts, viewing logs, adjusting settings, and testing prompts.
29
+ - **Streaming Support**: Full Server-Sent Events (SSE) support for real-time responses.
30
+ - **Native Tool Calling**: Supports Claude's tool use capabilities by translating them to Codex function calls.
31
+
32
+ ---
33
+
34
+ ## Security & Privacy
35
+
36
+ **Is this a malicious proxy? No.**
37
+
38
+ - **Local Execution**: This server binds to `127.0.0.1` by default.
39
+ - **Direct Communication by Default**: Claude and GPT model requests connect directly to OpenAI/ChatGPT endpoints.
40
+ - **No Rotation by Default**: Requests use the active account only. Multi-account rotation is disabled unless `CODEX_CLAUDE_PROXY_ENABLE_MULTI_ACCOUNT_ROTATION=true` is set.
41
+ - **Third-Party Opt-In**: The explicit `kilo` model route uses Kilo/OpenRouter-backed free models only when `CODEX_CLAUDE_PROXY_ENABLE_KILO=true` is set. Default routing is OpenAI-only.
42
+ - **Open Source**: The full source code is available here for you to audit.
43
+ - **No Data Collection**: We do not track your prompts, keys, or personal data.
44
+
45
+ ---
46
+
47
+ ## ⚙️ How it works
48
+
49
+ This tool acts as a "translation layer" between the Claude CLI and ChatGPT's Codex backend.
50
+
51
+ 1. **Intercept**: Claude Code CLI sends a request to `localhost:8081` (thinking it's Anthropic's API).
52
+ 2. **Translate**: The proxy converts the Anthropic-format JSON into the specific payload format required by ChatGPT's internal Codex API.
53
+ 3. **Forward**: The request is sent securely to ChatGPT using your own authenticated session.
54
+ 4. **Stream**: The response from ChatGPT is converted back into Anthropic's Server-Sent Events (SSE) format and streamed to your terminal.
55
+
56
+ ```
57
+ ┌──────────────────┐ ┌─────────────────────┐ ┌────────────────────────────┐
58
+ │ Claude Code │────▶│ This Proxy Server │────▶│ ChatGPT Codex Backend API │
59
+ │ (Anthropic API) │ │ (Anthropic ⇄ OpenAI)│ │ (codex/responses) │
60
+ └──────────────────┘ └─────────────────────┘ └────────────────────────────┘
61
+ ```
62
+
63
+ ---
64
+
65
+ ## Installation
66
+
67
+ Install globally to use the CLI commands anywhere:
68
+
69
+ ```bash
70
+ npm install -g @pikoloo/codex-proxy
71
+ codex-proxy start
72
+ ```
73
+
74
+ Or run the published package without a global install:
75
+
76
+ ```bash
77
+ npx @pikoloo/codex-proxy@latest start
78
+ ```
79
+
80
+ For release work from this checkout, use `make update` and `make publish`.
81
+
82
+ The legacy `codex-claude-proxy` command remains available after installing this package.
83
+
84
+ ---
85
+
86
+ ## 🚦 Quick Start
87
+
88
+ ### 1. Start the Proxy
89
+
90
+ ```bash
91
+ codex-proxy start
92
+ ```
93
+ The server will start at `http://localhost:8081`.
94
+
95
+ ### 2. Add Your Account
96
+
97
+ #### **Option A: Web Dashboard (Local Desktop)**
98
+
99
+ 1. Open the dashboard at **[http://localhost:8081](http://localhost:8081)**
100
+ 2. Go to the **Accounts** tab
101
+ 3. Click **Add Account** and login with your ChatGPT account
102
+
103
+ #### **Option B: CLI (Desktop or Headless/VM)**
104
+
105
+ ```bash
106
+ # Desktop (opens browser)
107
+ codex-proxy accounts add
108
+
109
+ # Headless/VM server (manual code input)
110
+ codex-proxy accounts add --no-browser
111
+ ```
112
+
113
+ For **headless/VM servers** without a browser:
114
+ 1. Run the command with `--no-browser`
115
+ 2. It will print a URL - copy and open it on a device with a browser
116
+ 3. Complete login on that device
117
+ 4. After redirect, copy the callback URL (or just the code)
118
+ 5. Paste it back in the terminal
119
+
120
+ ### 3. Configure Claude Code
121
+ Run this command to automatically configure your `claude` CLI to use the proxy:
122
+ ```bash
123
+ curl -X POST http://localhost:8081/claude/config/proxy
124
+ ```
125
+
126
+ *Alternatively, set the environment variables manually:*
127
+ ```bash
128
+ export ANTHROPIC_BASE_URL=http://localhost:8081
129
+ export ANTHROPIC_API_KEY=dummy-key # The key is ignored but required by the CLI
130
+ ```
131
+
132
+ 4. **Run Claude**:
133
+ ```bash
134
+ claude
135
+ ```
136
+
137
+ ---
138
+
139
+ ## 🧠 Model Mapping
140
+
141
+ The proxy automatically maps Claude model names to current OpenAI backend models. Direct `gpt-*` model IDs are passed through.
142
+
143
+ | Requested Model ID | Upstream Model | Auth Required | Description |
144
+ | :--- | :--- | :---: | :--- |
145
+ | `claude-sonnet-4-5` | `gpt-5.5` | ✅ | Current default high-intelligence model |
146
+ | `claude-opus-4-5` | `gpt-5.5` | ✅ | Current default high-intelligence model |
147
+ | `claude-haiku-4` | `gpt-5.4-mini` | ✅ | OpenAI small-model lane |
148
+ | `codex` | `gpt-5.3-codex` | ✅ | Latest Codex-optimized model |
149
+ | `kilo` | Selected Kilo target | ❌ | Explicit third-party free-model route, disabled unless `CODEX_CLAUDE_PROXY_ENABLE_KILO=true` |
150
+
151
+ ---
152
+
153
+ ## 🛠️ Configuration & API
154
+
155
+ ### Web Dashboard
156
+
157
+ The dashboard uses a clean desktop split-view layout with a compact toolbar, native-feeling glass surfaces, account management, live logs, settings, and prompt test panels. The screenshot at the top of this README is captured from the actual local app.
158
+
159
+ Visit `http://localhost:8081` to:
160
+ - **Manage Accounts**: Add, remove, or switch active ChatGPT accounts.
161
+ - **Personal Mode**: Requests use the active account only unless multi-account rotation is explicitly enabled by environment variable.
162
+ - **View Logs**: See real-time request/response logs for debugging.
163
+ - **Test Models**: Run quick tests against the configured models.
164
+
165
+ ### API Endpoints
166
+ - `GET /health`: Check server status.
167
+ - `GET /accounts`: List configured accounts.
168
+ - `POST /v1/messages`: Anthropic-compatible chat completion endpoint.
169
+
170
+ See [API Documentation](./docs/API.md) for full details.
171
+
172
+ ---
173
+
174
+ ## 🤝 Contributing
175
+
176
+ Contributions are welcome! Please feel free to submit a Pull Request.
177
+
178
+ 1. Fork the repository
179
+ 2. Create your feature branch (`git checkout -b feature/AmazingFeature`)
180
+ 3. Commit your changes (`git commit -m 'Add some AmazingFeature'`)
181
+ 4. Push to the branch (`git push origin feature/AmazingFeature`)
182
+ 5. Open a Pull Request
183
+
184
+ ---
185
+
186
+ ## 📄 License
187
+ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
188
+
189
+ ## ⚠️ Disclaimer
190
+ This project is an independent open-source tool and is not affiliated with, endorsed by, or sponsored by Anthropic or OpenAI. "Claude" is a trademark of Anthropic PBC. "ChatGPT" and "Codex" are trademarks of OpenAI. Use responsibly and in accordance with applicable Terms of Service.
191
+
192
+ ---
193
+
194
+ <div align="center">
195
+ <p>If you find this project useful, please give it a star! ⭐️</p>
196
+ <a href="https://github.com/surajmandalcell/codex-proxy">
197
+ <img src="https://img.shields.io/github/stars/surajmandalcell/codex-proxy?style=social" alt="Star on GitHub">
198
+ </a>
199
+ </div>
package/bin/cli.js ADDED
@@ -0,0 +1,118 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { fileURLToPath } from 'url';
4
+ import { dirname, join } from 'path';
5
+ import { readFileSync } from 'fs';
6
+
7
+ const __filename = fileURLToPath(import.meta.url);
8
+ const __dirname = dirname(__filename);
9
+
10
+ const packageJson = JSON.parse(
11
+ readFileSync(join(__dirname, '..', 'package.json'), 'utf-8')
12
+ );
13
+
14
+ const CLI_NAME = 'codex-proxy';
15
+ const LEGACY_CLI_NAME = 'codex-claude-proxy';
16
+ const args = process.argv.slice(2);
17
+ const command = args[0];
18
+
19
+ function showHelp() {
20
+ console.log(`
21
+ ${CLI_NAME} v${packageJson.version}
22
+
23
+ Proxy server for using ChatGPT Codex models with Claude Code CLI.
24
+
25
+ USAGE:
26
+ ${CLI_NAME} <command> [options]
27
+
28
+ COMMANDS:
29
+ start Start the proxy server (default port: 8081)
30
+ accounts Manage ChatGPT accounts (interactive)
31
+ accounts add Add a new ChatGPT account via OAuth
32
+ accounts add --no-browser Add account manually (headless/VM)
33
+ accounts list List all configured accounts
34
+ accounts remove Remove accounts interactively
35
+ accounts verify Verify account tokens are valid
36
+ accounts clear Remove all accounts
37
+
38
+ OPTIONS:
39
+ --help, -h Show this help message
40
+ --version, -v Show version number
41
+
42
+ ENVIRONMENT:
43
+ PORT Server port (default: 8081)
44
+
45
+ EXAMPLES:
46
+ ${CLI_NAME} start
47
+ PORT=3000 ${CLI_NAME} start
48
+ ${CLI_NAME} accounts add
49
+ ${CLI_NAME} accounts add --no-browser
50
+ ${CLI_NAME} accounts list
51
+ ${CLI_NAME} accounts verify
52
+
53
+ ALIASES:
54
+ ${LEGACY_CLI_NAME} Legacy command name, still supported
55
+
56
+ HEADLESS/VM USAGE:
57
+ 1. Run: ${CLI_NAME} accounts add --no-browser
58
+ 2. Copy the URL shown and open in browser on another device
59
+ 3. After login, paste the callback URL back in terminal
60
+
61
+ CONFIGURATION:
62
+ Claude Code CLI (~/.claude/settings.json):
63
+ {
64
+ "env": {
65
+ "ANTHROPIC_BASE_URL": "http://localhost:8081",
66
+ "ANTHROPIC_API_KEY": "dummy"
67
+ }
68
+ }
69
+ `);
70
+ }
71
+
72
+ function showVersion() {
73
+ console.log(packageJson.version);
74
+ }
75
+
76
+ async function main() {
77
+ if (args.includes('--help') || args.includes('-h')) {
78
+ showHelp();
79
+ process.exit(0);
80
+ }
81
+
82
+ if (args.includes('--version') || args.includes('-v')) {
83
+ showVersion();
84
+ process.exit(0);
85
+ }
86
+
87
+ switch (command) {
88
+ case 'start':
89
+ case undefined:
90
+ await import('../src/index.js');
91
+ break;
92
+
93
+ case 'accounts': {
94
+ const subCommand = args[1] || 'add';
95
+ process.argv = ['node', 'accounts-cli.js', subCommand, ...args.slice(2)];
96
+ await import('../src/cli/accounts.js');
97
+ break;
98
+ }
99
+
100
+ case 'help':
101
+ showHelp();
102
+ break;
103
+
104
+ case 'version':
105
+ showVersion();
106
+ break;
107
+
108
+ default:
109
+ console.error(`Unknown command: ${command}`);
110
+ console.error(`Run "${CLI_NAME} --help" for usage information.`);
111
+ process.exit(1);
112
+ }
113
+ }
114
+
115
+ main().catch((err) => {
116
+ console.error('Error:', err.message);
117
+ process.exit(1);
118
+ });
@@ -0,0 +1,202 @@
1
+ # Account Management
2
+
3
+ ## Storage Structure
4
+
5
+ ### Main Registry
6
+
7
+ **Location:** `~/.codex-claude-proxy/accounts.json`
8
+
9
+ ```json
10
+ {
11
+ "accounts": [
12
+ {
13
+ "email": "user@gmail.com",
14
+ "accountId": "d41e9636-16d8-42be-91da-7ea8773bfb7e",
15
+ "planType": "plus",
16
+ "accessToken": "eyJhbGciOiJSUzI1NiIs...",
17
+ "refreshToken": "rt_WpTMn1...",
18
+ "idToken": "eyJhbGciOiJSUzI1NiIs...",
19
+ "expiresAt": 1770886178000,
20
+ "addedAt": "2026-02-13T04:00:00.000Z",
21
+ "lastUsed": "2026-02-13T04:30:00.000Z",
22
+ "quota": {
23
+ "usage": {...},
24
+ "account": {...},
25
+ "lastChecked": "2026-02-14T10:00:00.000Z"
26
+ }
27
+ }
28
+ ],
29
+ "activeAccount": "user@gmail.com",
30
+ "version": 1
31
+ }
32
+ ```
33
+
34
+ ### Per-Account Tokens
35
+
36
+ **Location:** `~/.codex-claude-proxy/accounts/<email>/auth.json`
37
+
38
+ ```json
39
+ {
40
+ "auth_mode": "chatgpt",
41
+ "OPENAI_API_KEY": null,
42
+ "tokens": {
43
+ "id_token": "...",
44
+ "access_token": "...",
45
+ "refresh_token": "...",
46
+ "account_id": "..."
47
+ },
48
+ "last_refresh": "2026-02-14T10:00:00.000Z"
49
+ }
50
+ ```
51
+
52
+ ## Operations
53
+
54
+ ### Add Account (OAuth)
55
+
56
+ ```bash
57
+ curl -X POST http://localhost:8081/accounts/add
58
+
59
+ # Returns OAuth URL to open in browser
60
+ ```
61
+
62
+ ### Import from Codex App
63
+
64
+ ```bash
65
+ curl -X POST http://localhost:8081/accounts/import
66
+
67
+ # Imports from ~/.codex/auth.json
68
+ ```
69
+
70
+ ### List Accounts
71
+
72
+ ```bash
73
+ curl http://localhost:8081/accounts
74
+
75
+ # Response
76
+ {
77
+ "accounts": [
78
+ {
79
+ "email": "user@gmail.com",
80
+ "accountId": "...",
81
+ "planType": "plus",
82
+ "addedAt": "...",
83
+ "lastUsed": "...",
84
+ "isActive": true,
85
+ "tokenExpired": false,
86
+ "quota": {...}
87
+ }
88
+ ],
89
+ "activeAccount": "user@gmail.com",
90
+ "total": 1
91
+ }
92
+ ```
93
+
94
+ ### Switch Active Account
95
+
96
+ ```bash
97
+ curl -X POST http://localhost:8081/accounts/switch \
98
+ -H "Content-Type: application/json" \
99
+ -d '{"email":"other@gmail.com"}'
100
+ ```
101
+
102
+ Switching:
103
+ 1. Updates `activeAccount` in `accounts.json`
104
+ 2. Updates auth file for the account
105
+ 3. Next API calls use new account's credentials
106
+
107
+ ### Remove Account
108
+
109
+ ```bash
110
+ curl -X DELETE http://localhost:8081/accounts/user@gmail.com
111
+ ```
112
+
113
+ Removes:
114
+ - Account from registry
115
+ - Per-account token directory
116
+
117
+ ### Refresh Tokens
118
+
119
+ ```bash
120
+ # Active account
121
+ curl -X POST http://localhost:8081/accounts/refresh
122
+
123
+ # Specific account
124
+ curl -X POST http://localhost:8081/accounts/user@gmail.com/refresh
125
+
126
+ # All accounts
127
+ curl -X POST http://localhost:8081/accounts/refresh/all
128
+ ```
129
+
130
+ ## Token Lifecycle
131
+
132
+ ### Expiration
133
+
134
+ - Access tokens expire in ~1 hour (3600 seconds)
135
+ - Refresh tokens are long-lived (weeks/months)
136
+
137
+ ### Auto-Refresh
138
+
139
+ - Background refresh every **55 minutes**
140
+ - Startup refresh 2 seconds after server start
141
+ - Proactive refresh 5 minutes before expiry
142
+
143
+ ### Token Validation
144
+
145
+ Before each API call:
146
+ 1. Check if token is expired or expiring within 5 minutes
147
+ 2. If yes, refresh using refresh token
148
+ 3. Use new access token for the call
149
+
150
+ ## Quota Tracking
151
+
152
+ ### Fetch Quota
153
+
154
+ ```bash
155
+ curl http://localhost:8081/accounts/quota
156
+
157
+ # Response
158
+ {
159
+ "success": true,
160
+ "email": "user@gmail.com",
161
+ "quota": {
162
+ "usage": {
163
+ "totalTokenUsage": 15,
164
+ "limit": 100,
165
+ "remaining": 85,
166
+ "percentage": 15,
167
+ "resetAt": "..."
168
+ },
169
+ "account": {...}
170
+ },
171
+ "cached": false
172
+ }
173
+ ```
174
+
175
+ ### Web UI Quota Display Rules
176
+
177
+ - The Accounts table displays **remaining quota** as a percentage.
178
+ - Remaining percentage is normalized to `0-100` to avoid broken UI values.
179
+ - If `limitReached=true` or `allowed=false`, UI shows quota as exhausted even when percentage data is missing.
180
+ - If usage data is unavailable, UI shows `-` instead of rendering a broken bar.
181
+ - Reset window is shown using `usage.resetAt` (with fallback to `usage.raw.rate_limit.primary_window.reset_at`).
182
+ - UI also shows a relative countdown (e.g. `Resets in 6d 13h`) when reset data is available.
183
+
184
+ ### Refresh All Quotas
185
+
186
+ ```bash
187
+ curl http://localhost:8081/accounts/quota/all
188
+ ```
189
+
190
+ ## Account Persistence
191
+
192
+ On server startup:
193
+ 1. `ensureAccountsPersist()` loads accounts
194
+ 2. Restores active account's auth
195
+ 3. Starts auto-refresh timer
196
+
197
+ ## Security
198
+
199
+ - Tokens stored locally in `~/.codex-claude-proxy/`
200
+ - Directory permissions: user read/write only
201
+ - Never logged or exposed in API responses
202
+ - Per-account isolation via separate directories