rpm.

openclaude

OpenAI-compatible API over the Claude CLI · 2026

An OpenAI-compatible API over the local Claude CLI, so agent frameworks run on a Claude login, no API key.

TypeScriptNode.jsExpressServer-Sent EventsClaude Code CLIpm2

Every agent framework worth using speaks OpenAI's /v1/chat/completions protocol. LangChain, LangGraph, Deep Agents — they all take a base_url and an API key and get on with it. Claude is not in that shape, so using it means an Anthropic API key and a billing account, separate from the Claude subscription you might already be paying for.

But there's already an authenticated Claude on the machine: the claude CLI, logged in through Claude Code. openclaude is the adapter between those two facts — an OpenAI-compatible server that spawns the CLI per request and translates in both directions.

from langchain_openai import ChatOpenAI
 
llm = ChatOpenAI(
    base_url="http://localhost:8787/v1",
    api_key="not-needed",       # only checked if you set API_KEY in .env
    model="claude-sonnet",
    streaming=True,
)

How the translation works

The CLI's print mode is not a chat API, so most of the work is reconciling two different shapes.

OpenAI concept How it maps
model Alias table → claude --model. Unknown values pass through verbatim, so you aren't limited to the built-in aliases.
messages[] Print mode takes one prompt and is stateless, so history is re-sent every call: system becomes --system-prompt, longer histories flatten into a User:/Assistant: transcript.
stream: true --output-format stream-json, forwarding only text_delta events as choices[0].delta.content, terminated with [DONE].
reasoning_effort An extension, passed to claude --effort. Accepts low/medium/high plus the CLI's own xhigh/max.
temperature, max_tokens Accepted for client compatibility but not forwarded — print mode has no sampling flags. Silently ignored rather than erroring.

That last row is the kind of thing worth being explicit about. Quietly accepting a parameter you can't honour is a lie the caller will eventually trip over, so it's documented as a known limitation rather than hidden.

Tools are off by default, deliberately

The obvious risk with wrapping a coding agent in an HTTP API is that the model can reach the filesystem on behalf of whoever calls it. Every CLI invocation runs with --tools "", so it behaves like a plain chat model, not an agent with shell access.

The single exception is attachments. Plain text under 300KB is inlined into the prompt directly — cheap, no tool round-trip. Anything else (images, PDFs) gets written to a per-request temp directory, and only then does the CLI get a scoped, read-only Read tool with --add-dir pointed at that directory. It costs one extra agentic turn, and it's the only documented way to feed binary content to Claude Code in print mode, since there is no --image flag. The directory is deleted afterwards.

Remote image_url values are deliberately not fetched — that's an SSRF hole, and there's no CLI primitive for it either. You get the URL echoed back with a note that it wasn't loaded.

Treating it like a real service

A wrapper around a subprocess fails in ways a normal API client doesn't expect, so the unglamorous parts got the attention:

  • Concurrency is capped (MAX_CONCURRENCY, default 3) with an in-memory FIFO queue, so a burst of requests doesn't spawn unbounded processes.
  • Timeouts SIGTERM a stuck call, then SIGKILL after a grace period, and return a 504 rather than hanging the caller.
  • Errors — bad model, auth failure, non-zero exit, unparseable output, a wrong CLAUDE_CLI_PATH — all map to OpenAI's { error: { message, type, code } } shape, so client SDKs handle them exactly as they'd handle a real OpenAI error.
  • The working directory is a neutral scratch dir, not the repo, so the model is never implicitly exposed to openclaude's own source through CLAUDE.md project discovery.

Known limitations

Written down in the README rather than discovered later:

  • No OpenAI function/tool calling.
  • temperature and max_tokens are accepted but not enforced.
  • Multi-turn history is flattened to text, so it won't reproduce the Messages API's multi-turn caching behaviour.
  • Each request is a fresh process, so there's real CLI startup latency per call — typically a few seconds. This is not a low-latency inference server.

It's pinned to a specific CLI version (2.1.220 on macOS) because CLI flags move. All the flag and model knowledge lives in two files, src/claudeCli.ts and src/models.ts, so when something drifts there's exactly one place to diff against claude --help.

All systems nominal© 2026 Ramprasad Mondal