0005 - OpenAI-compatible API as the gateway contract
- Status: Accepted
- Date: 2026-04-19
- Deciders: nexus core team
- Tags: gateway, wire-contract, sdk, edge
- Supersedes: none
Context and Problem Statement
Section titled “Context and Problem Statement”The gateway has to expose some wire shape to clients. That shape constrains which SDKs drop in with only a base_url change, which frameworks work out of the box, and which existing tools (agentic IDEs, evaluation harnesses, internal scripts) can be pointed at nexus without rewriting anything.
We studied three reference points:
- Helicone exposes an OpenAI-compatible surface as its primary edge (
worker/src, see helicone-lessons-learned “patterns to adopt” item 1). - Bifrost exposes per-SDK prefixes (
/openai,/anthropic,/genai,/litellm,/bedrock) — see bifrost-lessons-learned “patterns to adopt” item 6. - The behavior-focused contract tests in
test-suite/node/exercise the OpenAI-compatible surface and must keep passing as the gateway evolves.
Decision Drivers
Section titled “Decision Drivers”- Minimize client-side change when swapping an SDK’s
base_urlto the gateway. - Preserve SSE streaming fidelity; every OpenAI SDK assumes it.
- Preserve native provider-feature exposure (Anthropic
thinking, GooglethinkingConfig, OpenAIreasoning_effort) through OpenAI’sprovider_options/extra_bodyescape hatch. - Keep the initial gateway surface small: one wire, one provider family, one endpoint (
/v1/chat/completions) — per methodology Rule 1. - Leave the door open for Bifrost-style per-SDK prefixes as an additive expansion once the primary edge is stable.
Considered Options
Section titled “Considered Options”- OpenAI-compatible
/v1/chat/completions+/v1/chat/completionsstreaming (chosen). - Anthropic Messages-compatible — Strong fit for Claude users, but narrower SDK ecosystem and still requires the OpenAI shape for most tools.
- A nexus-native shape (proto-over-HTTP / custom JSON). Rejected: forces every client to integrate a new SDK, undermines the “base_url swap” selling point.
- Multiple parallel edges from day one (Bifrost style). Rejected under methodology Rule 2 (skeleton-then-grow); added later if customer demand appears.
Decision Outcome
Section titled “Decision Outcome”Chosen option: OpenAI-compatible REST + SSE is the canonical primary edge.
Contract surface for the initial OpenAI chat slice:
POST /v1/chat/completions— request and response bodies match the public OpenAI schema formodel,messages,temperature,top_p,stream,tools,tool_choice,response_format,max_tokens,logprobs,logit_bias,seed,user,stop.- Streaming: SSE with OpenAI’s
data: {...}\n\nframe shape; final framedata: [DONE]\n\n. Helicone’s per-provider stream parsers (worker/src/lib/dbLogger/streamParsers) inform the Rust implementation. GET /v1/models— returns the tenant-visible model list from the registry once that route ships.- Authentication:
Authorization: Bearer nxs_...where the token is a nexus-issued API key (format owned by the identity/control-plane workstream). - Provider-specific features (Anthropic
thinking, GooglethinkingConfig, OpenAIreasoning_effort) flow through the OpenAIextra_body/ unknown-top-level-fields escape hatch; the gateway forwards them to the native provider SDK. - Error shape matches OpenAI’s
{ "error": { "message", "type", "param", "code" } }envelope so SDKs’ built-in retry/backoff classification keeps working.
Custom nexus headers
Section titled “Custom nexus headers”These are not part of the OpenAI surface; they are reserved on top of the compatible surface:
nexus-session-id,nexus-session-path,nexus-session-name— session correlation (Helicone pattern, adopted).nexus-property-*— custom dimensional tagging (Helicone pattern, adopted).nexus-classification— data-classification tier (ADR-0008 defines the header; PDP enforcement is a separate ADR set).
What is out of scope for this ADR
Section titled “What is out of scope for this ADR”- Anthropic-compatible and Gemini-compatible edges are additive once the OpenAI slice is stable; they are not alternatives to this ADR’s primary edge.
- The
/v1/*paths are reserved for the OpenAI-compatible contract. Every non-OpenAI dialect edge is served under its own top-level prefix that matches the native SDK’s base-URL convention (the Anthropic Messages edge is mounted under/anthropic, so the Anthropic SDK resolves/anthropic/v1/messages). These prefixes are the primary route for their dialect, not optional sugar; clients change only their base URL, never their credentials. - Model-string grammar (
"model","provider/model", comma-separated fallback lists, reservednexus/auto, and later deployment forms) lives innexus-registryand grows with parser changes there. - Routing behaviour (BYOK-first, then PTB, cost-sorted) is ADR-0006.
Consequences
Section titled “Consequences”- Any OpenAI SDK (official
openai, Vercel AI SDK, LangChain, LlamaIndex,llmCLI, etc.) points at nexus with abase_urlchange and nothing else. - The gateway implementation has a single canonical request shape; provider translations (
toAnthropic(body),toGemini(body)) are pure functions inside the gateway, isolated from the router. - When OpenAI ships new request fields, we add pass-through support for them in the gateway’s request decoder rather than waiting for a schema rev. Unknown fields are preserved verbatim and forwarded to providers that understand them.
- The
/v1/embeddings,/v1/audio/*,/v1/images/*,/v1/responses,/v1/filesand Assistants endpoints are explicitly out of scope for the initial OpenAI chat slice; each needs its own ADR before it ships. - When OpenAI makes a backwards-incompatible change (historically rare), we version the edge path (
/v2/chat/completions) and freeze the old one. We do not try to absorb the break silently.
