Skip to content

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

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.
  • Minimize client-side change when swapping an SDK’s base_url to the gateway.
  • Preserve SSE streaming fidelity; every OpenAI SDK assumes it.
  • Preserve native provider-feature exposure (Anthropic thinking, Google thinkingConfig, OpenAI reasoning_effort) through OpenAI’s provider_options / extra_body escape 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.
  • OpenAI-compatible /v1/chat/completions + /v1/chat/completions streaming (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.

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 for model, 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\n frame shape; final frame data: [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, Google thinkingConfig, OpenAI reasoning_effort) flow through the OpenAI extra_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.

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).
  • 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, reserved nexus/auto, and later deployment forms) lives in nexus-registry and grows with parser changes there.
  • Routing behaviour (BYOK-first, then PTB, cost-sorted) is ADR-0006.
  • Any OpenAI SDK (official openai, Vercel AI SDK, LangChain, LlamaIndex, llm CLI, etc.) points at nexus with a base_url change 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/files and 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.