Codex subscriptions and gateway
SubLane guide: codex subscriptions and gateway.
With a personal key ready, configure your client below. If you do not have one, complete First request or ask your administrator for a member login.
Client configuration
Create a personal API key in API keys. Use the instance origin plus /v1 as the base URL. Configure a model ID returned by GET /v1/models.
Set SUBLANE_API_KEY in the client process environment. Merge the following settings into the user-level ~/.codex/config.toml, replacing matching existing values:
model = "<model-id-from-your-account>"
model_provider = "sublane"
[model_providers.sublane]
name = "SubLane"
base_url = "http://127.0.0.1:8080/v1"
env_key = "SUBLANE_API_KEY"
wire_api = "responses"
requires_openai_auth = false
supports_websockets = trueUse your instance's actual URL, with HTTPS for network deployments. For HTTP/SSE transport, set supports_websockets = false. Codex CLI and desktop must read the intended user-level configuration. A desktop app already running does not automatically inherit environment variables exported in another terminal; restart it in an environment where the key is available.
The API keys page provides a configuration snippet without embedding or retaining the secret. It also offers an optional CC Switch import that passes the chosen key to the locally installed application after explicit preparation. CC Switch generates its own Codex authentication-file configuration; it does not preserve the environment-variable or WebSocket settings in the manual snippet above. See the official advanced configuration and authentication guidance for client configuration and credential storage.
Account catalogs are cached and survive restarts. Discovery, pool aggregation and model-aware account selection are described in model catalogs.
After setup: send a message and check the response, then continue with Everyday use. The account, quota and protocol details below are references for later.
Add an account
Sign in as the administrator, open Accounts, and choose Add account. Members cannot access subscription management endpoints or credentials.
- Browser authorization: name the account, start authorization, and open the OpenAI link. After signing in, copy the complete
http://localhost:1455/auth/callback?...address from the browser address bar and paste it into SubLane. A localhost connection error at this point is expected: SubLane intentionally does not open another callback listener. This manual-return flow works for local and remote deployments. - Import auth.json: choose a Codex subscription credential file or paste its JSON. Both the CLI's nested
tokensstructure and the flat Codex token format are accepted. Official API keys are rejected. SubLane never scans local client directories; it reads only the content explicitly submitted by the administrator. Imported endpoint/proxy settings are discarded.
Account authorization/import starts model discovery automatically. Use Verify connection to refresh and persist the account's model catalog, or open Models to inspect it. Imported credentials show Not verified until successful use. The account menu provides enable/disable, reauthorization, and removal. Reauthorization must retain the same upstream account identity. Removing an account deletes its stored credentials from SubLane; it does not revoke the ChatGPT login itself.
Codex model discovery sends the instance's effective client version in the catalog query and request headers. Administrators can configure it in Administration → System settings: an explicit version takes priority over the last synchronized official stable release, with 0.155.1 as the built-in fallback. Automatic release checks default to every six hours. This changes protocol metadata without installing Codex or updating the embedded SDK; see system settings.
The upstream catalog depends on that version: an obsolete version can return HTTP 200 with only hidden models or a partial visible catalog, producing an empty or incomplete /v1/models list after visibility filtering. Persisted catalogs include the actual discovery version, so changing the effective version triggers refresh on the next catalog read or model request even when their timestamps remain fresh. Changing pool permissions does not resolve this compatibility issue.
OAuth attempts use PKCE, 256-bit random state, a ten-minute expiry, one active attempt per administrator browser session, and a maximum of eight pending attempts. The callback is bound to the session that started it and consumed before exchanging its code. Closing the form requests cancellation; expiry is the fallback after a lost connection. Pending OAuth state is held only in memory.
Subscription usage
The accounts page shows enabled Codex accounts' quota windows, remaining percentage, reset countdown and observation time, including additional model limits when reported. The available reset-card count is separate: a missing count is Unknown, while an explicit zero is 0. Card viewing and opt-in redemption are explained in Reset cards. Missing quota fields remain unknown; refresh failures retain the old snapshot with a warning. A reset timestamp alone does not confirm replenishment; refresh usage to retrieve the current state.
The usage adapter reads https://chatgpt.com/backend-api/wham/usage and the separate https://chatgpt.com/backend-api/wham/rate-limit-reset-credits endpoint, matching the official Codex backend client. Both requests use the selected account's credential, the existing refresh/retry owner, bounded gateway admission, a shared 20-second deadline, and a 128 KiB bound per response. If the reset-card detail request fails or omits a count, quota remains available and any valid count from the usage response is retained. Quota responses return only normalized quota fields and the count; raw upstream metadata, card IDs, and credentials are excluded. This backend contract may evolve independently of SubLane.
The consolidated schema stores one bounded, normalized quota and reset-card-count snapshot per account in SQLite. Snapshots include the last successful observation time and survive process restarts; deleting an account cascades to its snapshot. The process keeps at most 100 snapshot entries in memory and lazily reloads them from SQLite. No credentials or raw provider responses are stored in this table.
GET /api/accounts/{id}/usage returns a fresh snapshot for up to two minutes (or until a reported upcoming reset, if sooner). An expired snapshot is returned immediately with stale and refreshing flags while a background refresh runs. A first fetch without a snapshot waits for the result. POST /api/accounts/{id}/usage/refresh bypasses freshness and waits for the shared refresh, while honoring a five-second cooldown. Both routes require an enabled administrator session; POST also requires the existing origin/JSON checks.
Concurrent callers for the same account share one upstream operation. Refreshes use the configured workspace operation capacity (128 by default, via SUBLANE_GATEWAY_CAPACITY) and a 45-second overall deadline, survive an individual browser disconnect, and are canceled/awaited during process shutdown. A failed refresh preserves the previous values and timestamp, marks them stale, and backs off for 30 seconds (or a longer valid upstream Retry-After, up to one hour). Admission saturation uses a five-second retry delay. Cache hits still check account existence and enablement. Transient failure/backoff state is process-local; the last successful snapshot is durable.
The browser retains its one-minute query freshness period. It polls the local snapshot endpoint once per second only while a refresh is active, stopping on completion or a network error. The page does not periodically poll the provider while idle. Share allocations use configured token or internal USD budgets and charge reported request usage directly. Observation timestamps and response server time are separate so a restored snapshot does not restart the reset countdown. Disabled accounts are not queried. Model traffic also warms the shared quota cache without waiting. Fresh main-limit exhaustion excludes an account from new selection; bound conversations return a quota-specific rejection. Upstream usage limits describe the subscription, not a member's resource allowance. See quota-aware selection.
Reset cards
Workspace administrators can view and use reset cards on enabled Codex accounts that do not need reauthorization. Members cannot manage them. Scheduled use and use on exhaustion are both off by default; viewing or refreshing the list does not consume a card.
Configure and use
- Open Subscription accounts and select Reset card settings on the Codex account card. Review the card list, available count and observation time. An unknown count is different from zero.
- Enable Use at a scheduled time, Use when quota is exhausted, or both. For a schedule, choose once with a future date and time, daily with a time, or weekly with a weekday and time. Check the displayed instance time zone.
- Set the shared daily cap (1–100, default 1), then select Save settings. Confirm the saved message and, when scheduled, the next run time.
- To use a card immediately, save any edits first and select Use one card now. Each attempt uses at most one card. Check the execution history for the result; opening settings alone does not authorize redemption.
Card list and expiry priority
The list shows titles, status, grant time, expiry and the next eligible card. It distinguishes available, redeeming, redeemed, expired, unknown and unsupported cards. Raw card IDs are not displayed. Next to use describes automatic selection; the dialog has no manual card picker and suppresses that suggestion while an attempt is pending. A reported count does not guarantee an eligible card exists.
Every new manual, scheduled or exhaustion-triggered redemption reads the inventory and explicitly supplies credit_id. Only available, supported, unexpired cards qualify. The earliest expiry wins; cards without an expiry come last. Ties use the oldest grant time, then stable ID order. No eligible card means no redemption. Failed or unknown inventory reads never fall back to an unspecified card. Initial loading failure is distinct from an empty list; a failed refresh retains the old list with a warning. Use Refresh cards to retry.
Scheduling and limits
Exhaustion checks run in the background about once a minute and require fresh quota data, a known positive card count and an explicit denial of the main quota. A displayed 100%, unknown data or a network error alone does not trigger redemption. Manual and scheduled attempts also require fresh quota data and a known positive count.
Both modes and manual use share the daily cap, counted by the instance's local date; successful and pending attempts reserve capacity. Disabled accounts and accounts needing reauthorization are skipped. Runs missed by more than five minutes are not backfilled. Nonexistent DST times are skipped; repeated hours produce one occurrence per local date. Changing the instance time zone recalculates future schedules. Closing the browser does not stop background workers. Demo mode does not start automation or allow redemption.
Results and recovery
| Situation | Action |
|---|---|
| Card details unavailable or no eligible cards | Refresh cards; check status and expiry. No card is consumed without a confirmed selection. |
| Quota unknown or unavailable | Refresh usage and resolve connection or authorization failures. |
| Daily cap reached | Wait for the next local day or deliberately raise and save the cap. |
| Result pending | Select Check pending result. New attempts remain blocked until confirmation. |
The provider controls eligibility and the quota windows reset. Redemption uses POST /backend-api/wham/rate-limit-reset-credits/consume; recognized outcomes are reset, already_redeemed, nothing_to_reset, and no_credit. Success refreshes quota and card counts without clearing unrelated gateway limits; it does not guarantee every restriction is removed.
Before sending, SubLane persists redeem_request_id with the selected card ID. A timeout or unknown response leaves the result pending. Checking it replays both values even if inventory or expiry changes; a changed count does not prove success. Pending requests from older versions preserve their original omitted credit_id. The selected ID is cleared after a definitive outcome and never enters quota snapshots or public execution/audit output. Reset records contain no credentials, raw responses or provider profile metadata. Account deletion removes settings and history.
The administrator-only browser APIs are GET /api/accounts/{id}/resets for settings/history, GET /api/accounts/{id}/resets/cards for normalized card metadata, PUT /api/accounts/{id}/resets to save settings, and POST /api/accounts/{id}/resets/consume or POST /api/accounts/{id}/resets/retry for use or confirmation. They use browser sessions and workspace roles; gateway API keys do not authorize them. Card reads reuse credential, proxy and lifecycle checks and exclude profile identifiers, avatar URLs and raw payloads. The provider's internal contract may change; synthetic tests do not establish live account eligibility or successful real redemption.
Settings changes and successful manual uses are audited; automatic outcomes remain in bounded execution history.
Credential storage and refresh
The consolidated schema creates workspace-scoped subscription accounts and bounded account-affinity records. Access, refresh, and ID tokens are encrypted with AES-256-GCM and account-bound authenticated data. The instance creates a private 32-byte credentials.key file next to its database. The key is not a setup password and requires no manual entry.
Back up the database and credentials.key together after stopping the instance. An existing account database will not start with a missing, invalid, or mismatched key. Do not replace the key to recover access. Encryption protects credential contents in a database-only copy; access to both the database and the key permits decryption.
SubLane is the sole refresh owner. It serializes credential refresh with administrator changes and persists rotated tokens before publishing them to requests. Tokens near expiry refresh before use. An upstream 401 permits one refresh and one retry on the same account; concurrent rejections of an old token reuse the already refreshed credential. Older in-flight results cannot invalidate a later refresh or reauthorization.
Gateway contract
All gateway routes require a SubLane bearer key, independently of browser cookies:
| Route | Behavior |
|---|---|
GET /v1/models | Return deduplicated native model IDs from persisted account catalogs within the pool and its policy |
POST /v1/responses | Responses JSON or SSE using the requested stream setting |
POST /v1/responses/compact | Non-streaming Codex compaction |
GET /v1/responses with WebSocket upgrade | Responses turns, local prewarm, incremental input reconstruction, and compact transcript replacement |
POST /v1/chat/completions | SDK translation to/from the selected provider protocol |
HTTP requests must include complete input. HTTP previous_response_id is rejected instead of silently losing context because the Codex HTTP backend is stateless. WebSocket continuations use bounded, connection-local history; unknown continuation IDs require full transcript input. Every WebSocket turn rechecks the key and member enablement. A revoked key cannot start a new turn on an existing connection.
Account affinity is scoped to the member, pool and client session/prompt-cache key, retained for 24 hours of activity, and capped at 4,096 bindings. Native requests use one binding across providers; explicit legacy-prefix requests retain provider-specific bindings. New sessions distribute across eligible accounts reporting the requested model within their pool, respecting per-account concurrency and cooldown. Sessionless requests select independently without creating affinity. Existing sessions fail when their account is disabled or removed instead of switching upstream identity. For an intentional model change, complete portable history can select a supporting account if the original account cannot serve the new model; provider continuations stay pinned. See model routing. Restart the conversation after other intentional account changes. Retrying a request never moves it to another account automatically.
Each workspace defaults to 128 operations, including waiting requests and shared metadata work, eight WebSocket connections, a 30-minute request/turn deadline and a five-minute inference response-header timeout. Request and reconstructed-context bounds default to 128 MiB; responses and stream events have separate 8 MiB bounds. Request-body reads are limited to 30 seconds; shared model discovery uses a 45-second deadline. These are resource bounds, not throughput guarantees. See deployment for configuration and resource-group runtime for admission and cooldown. Member limits: team controls.
Provider credentials, browser cookies, arbitrary client headers, and raw provider error bodies are not forwarded to members. Client bearer keys are replaced by the selected upstream credential. The application does not log prompts, responses, access tokens, refresh tokens, or OAuth callback URLs.
Verification boundary
Automated tests use temporary databases, synthetic credentials, and local fake upstreams. They cover OAuth ownership/expiry/replay, encryption and key mismatch, refresh serialization, immutable identity, account affinity, role/ownership isolation, streaming cancellation, SSE completion repair, and WebSocket revocation between turns.
An opt-in integration test runs an installed Codex CLI with a fresh child-process home, no inherited credentials, and a local synthetic model response:
SUBLANE_TEST_CODEX=1 go test ./internal/server -run TestCodexCLIProtocol -count=1 -vCodex CLI 0.152.1 passed both HTTP/SSE and WebSocket modes against the synthetic upstream. Actual subscription authorization and live model requests require an explicitly authorized test account. The real desktop application has not yet been validated; shared configuration and protocol tests alone do not establish live desktop compatibility.
Before the v7 upgrade, a local macOS arm64 preview (CLIProxyAPI v6.10.9, Go 1.26.0, production binary, one encrypted synthetic account) measured 23,376 KiB RSS, approximately 22.8 MiB, after about twelve minutes idle on 2026-09-19. This is an idle observation only; real models, concurrency, payload size, and platform change memory use. Docker Compose configuration validated locally, but the Docker daemon was unavailable for a local image build.
Execution reference
SubLane uses CLIProxyAPI v8.0.35’s public executors and translators behind internal/upstream. SubLane owns authorization, encrypted credential storage, refresh, account selection, and the public gateway. The SDK runs as a private executor registry with no live credentials, a no-op watcher, blocked loopback routes, and automatic refresh disabled. See architecture for the execution boundary and provider setup for provider connection steps. This guide’s authorization and quota details describe Codex.