cli2api
Endpoints

Text

Chat completions, messages, and the responses API — transparent proxies.

All three text endpoints are transparent proxies to MuleRun's native OpenAI/Anthropic-compatible APIs, with SSE streaming preserved.

Which models your account can hit depends on what MuleRun has enabled. A typical muk- studio key opens deepseek-v4-* plus the GPT-5.x code-plane (openai/gpt-5.5, openai/gpt-5.4-mini, …). Unprefixed gpt-5 / claude-* need an LLM-gateway key. If the gateway returns Model '…' is not supported, that's an account-tier issue — see Troubleshooting.

POST /v1/chat/completions

curl http://localhost:51222/v1/chat/completions \
  -H "Authorization: Bearer $CLI2API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"model":"gpt-5","messages":[{"role":"user","content":"hello"}]}'

A vendor/ prefix on the model routes via the code-plane (currently openai/* goes to /vendors/openai/v1/chat/completions with the prefix stripped before forwarding). See Models.

POST /v1/responses

Transparent proxy to MuleRun's /vendors/openai/v1/responses (the OpenAI Agents SDK entrypoint). Supports "stream": true; "background": true for async jobs.

curl http://localhost:51222/v1/responses \
  -H "Authorization: Bearer $CLI2API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"model":"gpt-5","input":"Summarize Black-Scholes in one paragraph."}'

Built-in tools (web_search, file_search, code_interpreter, image_generation)

The Responses API exposes OpenAI's server-side tool catalog as part of the request body — cli2api doesn't intercept tools, they pass straight through. This is the path to use when your app needs live web search: MuleRun has no standalone /v1/search endpoint, but the model can call web_search itself and you get the results inlined in the response.

curl http://localhost:51222/v1/responses \
  -H "Authorization: Bearer $CLI2API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "gpt-5.5",
    "input": "What is the latest stable Go release as of today? Cite the source.",
    "tools": [{"type": "web_search"}],
    "max_output_tokens": 500
  }'

The response's output array interleaves model events:

{
  "status": "completed",
  "output": [
    {"type": "web_search_call", "status": "completed",
     "action": {"type": "search", "query": "latest stable Go release 2026"}},
    {"type": "message", "status": "completed",
     "content": [{"type": "output_text",
                  "text": "The latest stable release is go1.26.4 …",
                  "annotations": [{"type": "url_citation", "url": "https://go.dev/dl/", "title": "Downloads - The Go Programming Language"}]}]}
  ]
}

Tool variants

  • {"type": "web_search"} — search the public web (GPT-5.x family).
  • {"type": "file_search"} — search uploaded files / vector stores.
  • {"type": "code_interpreter"} — sandbox Python.
  • {"type": "image_generation"} — call OpenAI's image model inline.
  • Custom function tools work too — cli2api passes everything through.

Reasoning tokens eat the budget

The GPT-5.x family burns most of max_output_tokens on hidden reasoning before any visible message. Set max_output_tokens to ≥500 for tool-using calls, otherwise the response can finish_reason: length with empty text. Check usage.completion_tokens_details.reasoning_tokens to see how much went to thinking.

POST /v1/messages

Anthropic shape. Accepts x-api-key or Authorization: Bearer. Claude's own web_search_20250305 tool flows through here unmodified.

curl http://localhost:51222/v1/messages \
  -H "x-api-key: $CLI2API_KEY" \
  -H "anthropic-version: 2023-06-01" \
  -d '{"model":"claude-sonnet-4-6","max_tokens":256,"messages":[{"role":"user","content":"hi"}]}'

SDK usage

from openai import OpenAI
c = OpenAI(api_key="local-key", base_url="http://localhost:51222/v1")

r = c.chat.completions.create(
    model="gpt-5",
    messages=[{"role": "user", "content": "hi"}],
)
print(r.choices[0].message.content)
from anthropic import Anthropic
a = Anthropic(api_key="local-key", base_url="http://localhost:51222")

r = a.messages.create(
    model="claude-sonnet-4-6",
    max_tokens=256,
    messages=[{"role": "user", "content": "hi"}],
)
print(r.content[0].text)
for chunk in c.chat.completions.create(
    model="gpt-5",
    messages=[{"role": "user", "content": "tell me a joke"}],
    stream=True,
):
    print(chunk.choices[0].delta.content or "", end="")

On this page