# Local endpoint implementation contract This document describes the current local/self-hosted SIWC implementation, not provider approval or live account, model, Windows, Docker, n8n, or VPS acceptance. See the [2026-10-05 source check](openai-source-check-2026-10-05.md) for the official source register, data handling, and open questions. ## Scope Relmio's local open-source flow follows OpenAI's documented Sign in with ChatGPT (SIWC) registration and plan-use flow. It needs no commercial approval, partner-issued client ID, or client secret. The user still signs in through OpenAI and separately grants ChatGPT plan use. Identity verification, plan permission, account eligibility, model access/admission, and a completed request are distinct checks. The public website's `/api/chat` remains `410 Gone`. It is separate from the local open-source flow and remains off pending distinct hosted-access work. This specification does not describe or implement hosted chat. ## Local targets | Target | Interface | Authentication boundary | | --- | --- | --- | | `codex-chatgpt` | Loopback WebSocket relay to the official Codex App Server over stdio | Selected SIWC registration and a high-trust local bearer for a trusted native client | | `codex-chat` | Relmio-specific `POST /chat` with bounded JSON or event-stream output | Selected SIWC registration and a separate bearer for a trusted local backend; read-only turn | | `n8n-openai-oauth` | Private OpenAI-compatible `/v1/models`, `/v1/responses`, and a `/v1/chat/completions` compatibility route with bounded function tools on the selected n8n network | Selected SIWC registration in the sidecar and a one-time Relmio bearer entered manually in n8n | | Other local services | SuperGrok, local-model, Assistant, and new-n8n routes documented separately | These do not share or inherit an SIWC registration; see [local endpoints](local-endpoints.md) | The two Codex targets are local protocols, not general OpenAI `/v1` endpoints. The Chat Adapter does not expose App Server WebSocket or read/write tools. The raw App Server target is high trust even though the relay is loopback-bound and requires its local bearer. The relay forwards every client JSON-RPC call except a short deny-list to the official App Server, which runs as the same user that owns the SIWC store. Connect only trusted local clients. ## Registration and plan use - New SIWC registration uses Relmio's own app identity, a stable host ID, and OpenAI's dynamic registration. No personal `~/.codex` credential is imported. - Relmio stores the issued client ID in a protected pending record before exchanging the authorization code. A failed or cancelled exchange does not create a verified, selectable account; retry uses the pending registration. - The callback is bound to loopback, state, PKCE, nonce, issued client ID, and verified issuer/client/subject. Email is a display label, not the account key. An optional `scope` is accepted and ignored; an optional `iss` must equal the discovered issuer. A valid-state `access_denied` makes no token request and reports a declined sign-in or declined plan use without changing an existing verified registration. A request with the wrong state is rejected without cancelling the sign-in in progress. - The returned token scopes, not requested scopes, determine whether `chatgpt.tokens.use.direct` was granted. Identity-only registrations remain connected but cannot make plan requests. - Plan use requires the separate ChatGPT grant and the first-use confirmation for that registration. n8n background workflows require an additional, explicit background-use consent. - The selected registration's protected record holds identity, issued client, granted scopes, tokens, plan state, and host/runtime ownership. Browser account responses contain safe metadata, never access, refresh, or ID tokens. - Refresh is serialized under a per-account lock. The owner record binds the holder process, its PID namespace, and on Linux the boot ID. A lock from an earlier boot is reclaimed at once; one held from another container or PID namespace on the same boot is reclaimed only after a 10-minute lease. A holder stops without further writes after its 2-minute operation deadline. A waiting caller retries for up to 2.5 minutes, then gets HTTP `503` `siwc_lock_unavailable` with recovery `retry-later`. A discovery failure before the refresh POST leaves the session unchanged. Before sending a refresh POST, Relmio persists a frozen state and disables plan use. A token-endpoint `503` with `temporarily_unavailable` restores the pre-refresh session and reports retry-later; this assumes no rotation occurred, which OpenAI does not document. Any other response or an ambiguous network result leaves token bytes protected but unusable. Never retry the possibly rotated refresh token; a fresh SIWC sign-in is required. A successful replacement token is stored frozen until ID-token verification completes. Terminal `invalid_grant` clears unusable token state. Sign-out clears local tokens; frozen refresh state remains revocation-unconfirmed even when OpenAI returns HTTP 200. Local deletion alone is not provider revocation. ## n8n sidecar The sidecar accepts a Relmio bearer and forwards only `GET /v1/models`, `POST /v1/responses`, and a compatibility `POST /v1/chat/completions` route, plus `POST /v1/images/generations` and `/v1/images/edits` when the image add-on is on. Responses requests are bound to the selected registration and public `api.openai.com/v1/responses`; only a completed provider response is success. Streamed Responses events pass through unchanged. When OpenAI's final event has an empty `output`, non-streaming and Chat responses are rebuilt from the `response.output_item.done` events. The sidecar's catalog `client_version` uses the newest stable Codex release from npm, checked about every 12 hours, with Relmio's pin (0.160.0) as the floor. This sends no credentials to `registry.npmjs.org`, which sees the server's IP address. The catalog cache lasts 5 minutes. OpenAI's version filter is undocumented and could change. The wizard's pre-install picker and local Codex gateways keep the pinned version. Local n8n sidecars have checks off by default and no check controls; completed traffic and model-level failures still update their model records. See [discovery rules](n8n-configuration.md#model-discovery-and-checks). The compatibility route accepts text user, assistant, and developer messages, assistant tool calls, and matching tool results. Function tools go upstream in one developer `additional_tools` input item; n8n executes them, not the gateway. Limits are 32 tool calls, 128 KiB of arguments per call, and 2 MiB of streamed arguments in total. A named `tool_choice`, tool namespaces, custom tools in streamed requests, and system messages are rejected. Chat clients receive only final-answer text; reasoning items and `commentary` text are not passed on. On 2026-10-05 a two-turn LangChain tool test passed through the real gateway on one ChatGPT account, streaming over both routes; other accounts, models, and non-streaming tool calls were not tested live. Unsupported request fields and tool types, background requests, stored response or conversation IDs, audio/video, audio endpoints, Files management, Moderations, Live/Realtime, Video, and other unimplemented routes are rejected, not forwarded; output-token caps are dropped instead. The image routes answer only through the opt-in image add-on, which uses a separate Codex sign-in; without it they return `images_off`. For the local sidecar, the dashboard runs the add-on's CLI with `docker compose exec` in the owned running sidecar, under the local operation lock, and stores its tokens in the sidecar's `siwc-store` Docker volume. SIWC sign-out and removal sign out of images first and report the revocation result. Sign-out continues if that step fails; removal stops and keeps the volume unless the install predates the image module. Model listing is not proof of entitlement or admission. Errors do not switch registrations or fall back to Platform API billing. `store:false` is not a zero-retention promise. The n8n bearer identifies that private sidecar, not the OpenAI account. It is generated locally, displayed once, and stored only as a verifier in Compose; the owner enters it in n8n. The sidecar joins only the reviewed existing n8n network, publishes no host port, and does not edit n8n's configuration, credentials, image, or lifecycle. ## Codex App Server The local WebSocket relay starts the official App Server over stdio with the selected SIWC access token in `ACCESS_TOKEN`, a public Responses provider, and `supports_websockets=false`. It is not a web app, general OpenAI gateway, or OS sandbox. Requests are tied to the selected identity and model catalog; the relay does not switch accounts. The generated configuration disables agent spawning and sets native request and stream retries to zero. The RPC gate rejects deferred/dynamic `tool_search`, hosted connector/MCP OAuth, account/auth RPCs, marketplace/plugin/configuration mutations, per-thread provider/model/fallback overrides, and path/history/ rollout selectors on thread resume/fork. Inline function/custom tools and local direct tools remain within the high-trust boundary; this is not full App Server, MCP, or agent feature parity. A trusted same-UID tool can inspect its own process environment or files. The relay records successful thread starts/forks in a protected, registration- and runtime-bound registry before forwarding their IDs. A restart can resume a known thread only for the same verified issuer/client/subject, model catalog, and child-returned thread ID. Unknown or foreign IDs, thread selectors, and unavailable models are rejected. Access-token expiry closes the socket shortly before expiry; a reconnect starts a new child, and interrupted turns are never replayed. ## Installation, transfer, and policy boundary Local Codex targets and n8n sidecars bind the selected registration, owner, and runtime. n8n installs also bind the selected container and network; they re-attest those resources and require reviewed confirmation before installation. n8n remains operator-owned and unchanged. Credential-copy legacy targets are never silently adopted. A local migration requires a fresh SIWC registration, separate migration consent, and final confirmation of the attested old target. Relmio stops only that owned service, keeps its old credential/workspace volumes offline, and creates a separate SIWC runtime. Tokens and history are not merged; uncertain migrations remain stopped and preserved for inspection. A VPS destination has its own host ID. The source is frozen before the protected transfer; the destination becomes the refresh owner only after a matching receipt. Unknown transfer outcomes remain frozen. SSH host-fingerprint verification happens before authentication, and a separate confirmation is required before remote writes. The reviewed n8n container and network IDs are re-attested before the first write and before the transfer; a changed ID requires a new review. After a VPS install completes, Relmio binds only the SSH host identity and the Docker network ID, so status and sign-out keep working if n8n is recreated or the SSH login method changes. An interrupted local or VPS install is reported as `staged` and can be resumed after a review that names the selected account, without deleting data or creating a second refresh writer. A same-account resume uses the account's current generation, so plan toggles or token refreshes do not block it. A lost acceptance acknowledgment is reconciled from the destination's receipt; with no receipt the sender stays frozen and needs a fresh sign-in. A fresh account can resume only after a confirmed not-accepted result, and is refused if the original receipt appears or, on a VPS, while a one-off helper container is still present. A finalization failure after transfer still shows the one-time key once, with a warning not to use it until resolved. Relmio never restarts an old writer automatically. VPS file publication is atomic and confined to `/docker/n8n-openai-oauth`, and every SSH command has a finite deadline. OpenAI's self-hosted VM guide and SIWC Terms do not resolve persistent remote VPS token storage. These implementation safeguards do not settle that question; this specification makes no provider-approval claim for VPS transfer/storage. See [VPS and n8n](vps-and-n8n.md) and the [source check](openai-source-check-2026-10-05.md). ## Other documented security boundaries - Local endpoints bind to loopback; private n8n companions publish no host port and reject browser-origin requests where implemented. - Managed paths reject symlinks/unmanaged ownership. Docker calls use bounded, allowlisted operations against the selected daemon and owned resources. - Provider tokens, bearer values, prompts, and raw child output are not deliberately returned in wizard account responses. Runtime, Docker, SSH, workflow, browser, provider, and host logs are not all controlled by Relmio. - See [security.md](security.md), [local-endpoints.md](local-endpoints.md), [n8n configuration](n8n-configuration.md), and [troubleshooting](troubleshooting.md) for operational instructions and recovery boundaries.