Skip to content

OpenAI compatibility

MirAPI implements the OpenAI protocol — any code written for the OpenAI SDK keeps working once you point it at MirAPI’s base URL. The Anthropic and Gemini protocols are also supported natively, each on its own base URL, so a single key covers all three.

Protocol Base URL Authentication
OpenAI https://api.mirapi.ai/v1 Authorization: Bearer
Anthropic https://api.mirapi.ai x-api-key or Authorization: Bearer
Gemini https://api.mirapi.ai or https://api.mirapi.ai/v1beta x-goog-api-key or ?key=

Rule of thumb: when a tool appends its own path (for example /chat/completions), set its base URL to https://api.mirapi.ai/v1. Anthropic’s /v1/messages endpoint is served from the bare root, so point the Anthropic SDK at https://api.mirapi.ai.

OpenAI-protocol requests authenticate with Authorization: Bearer sk-.... The scheme is case-insensitive, and a bare key without the scheme is also accepted. Anthropic endpoints also accept x-api-key; Gemini endpoints accept x-goog-api-key or a ?key= query parameter.

Keys are constrained by four rules — model whitelist, validity period, quota cap, and IP allowlist — and a key that violates one of them is rejected with 403. The OpenAI-Organization header is accepted but ignored: MirAPI has no organization scoping. See Authentication.

Terminal window
curl -X POST "https://api.mirapi.ai/v1/chat/completions" \
-H "Authorization: Bearer $MIRAPI_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "deepseek-chat",
"messages": [{"role": "user", "content": "Hello!"}]
}'
Endpoint Status
GET /v1/models · GET /v1beta/models Supported
POST /v1/chat/completions Supported
POST /v1/responses · POST /v1/responses/compact Supported
POST /v1/messages (Anthropic) Supported
POST /v1beta/models/{model}:generateContent (Gemini) Supported
POST /v1/embeddings · POST /v1/engines/{model}/embeddings Supported
POST /v1/images/generations · POST /v1/images/edits Supported
POST /v1/audio/transcriptions · POST /v1/audio/translations · POST /v1/audio/speech Supported
POST /v1/videos · GET /v1/videos/{task_id} · GET /v1/videos/{task_id}/content Supported (async)
POST /v1/video/generations · GET /v1/video/generations/{task_id} Supported (async)
Kling / Jimeng video compatibility routes Supported
POST /v1/rerank · POST /v1/moderations Supported
GET /v1/realtime (WebSocket) Supported
POST /v1/messages/count_tokens Not implemented (404)
/v1/files* · /v1/fine-tunes* Not implemented (404)
import os
from openai import OpenAI
client = OpenAI(base_url="https://api.mirapi.ai/v1", api_key=os.environ["MIRAPI_API_KEY"])
resp = client.chat.completions.create(
model="deepseek-chat",
messages=[{"role": "user", "content": "Explain prompt caching in one sentence."}],
)
print(resp.choices[0].message.content)

The core parameters are:

Parameter Purpose
model Model id, exactly as returned by GET /v1/models
messages Conversation history; image_url content parts enable vision input
stream Set true for SSE streaming
tools / tool_choice Function calling
response_format Structured JSON output (json_schema or JSON mode)
reasoning_effort Reasoning intensity, for models that support it

POST /v1/responses (and /v1/responses/compact) serve OpenAI’s newer Responses format as an alternative to chat completions.

Streaming is standard SSE, identical to OpenAI: pass "stream": true, read data: lines, and stop at data: [DONE]. It works for chat completions and responses; reasoning content arrives in the stream before the answer. Non-streaming usage reports token counts only — never a dollar amount.

stream = client.chat.completions.create(
model="deepseek-chat",
messages=[{"role": "user", "content": "Count from 1 to 5."}],
stream=True,
)
for chunk in stream:
if chunk.choices and chunk.choices[0].delta.content:
print(chunk.choices[0].delta.content, end="")

Function calling follows the OpenAI format — tools and tool_choice:

response = client.chat.completions.create(
model="deepseek-chat",
messages=[{"role": "user", "content": "What is the weather in Paris?"}],
tools=[{
"type": "function",
"function": {
"name": "get_weather",
"description": "Get the current weather in a city",
"parameters": {
"type": "object",
"properties": {"city": {"type": "string"}},
"required": ["city"],
},
},
}],
)
print(response.choices[0].message.tool_calls)

Tool calling works only for models that support it — check the capability flags in the catalogue. A full loop (define → call → execute → feed back → final answer) is in the tool-calling tutorial.

Ask for structured JSON with response_format. Both JSON mode and strict json_schema are supported; the model must have JSON output capability.

resp = client.chat.completions.create(
model="deepseek-chat",
messages=[{"role": "user", "content": "Name two planets."}],
response_format={
"type": "json_schema",
"json_schema": {
"name": "planets",
"schema": {
"type": "object",
"properties": {"planets": {"type": "array", "items": {"type": "string"}}},
"required": ["planets"],
},
},
},
)

See Structured outputs.

Status Meaning What to do
401 Key missing, invalid, or unknown Fix your credentials
403 Key valid but rejected (balance, whitelist, quota, IP) Resolve the cause, then retry
413 Payload too large Reduce the request size
429 Rate limited Back off; no Retry-After header is sent
500 Server error Safe to retry idempotent requests

OpenAI-style errors use the envelope {"error":{"message","type","param","code"}}; type is new_api_error (gateway side) or upstream_error (upstream failure, message sanitized). Every message ends with a request ID — include it in support tickets. The gateway does not retry for you: on 429, use jittered exponential backoff (1s → 2s → 4s, capped around 30s). See Errors.

Requests are billed per token (per-1M USD tiers for input, output, and cache reads), per image, or per second of video output. Reasoning tokens are billed as output, and cache reads cost less than normal input. The response usage object contains no amount: reconcile dollar cost in the console billing log, GET /api/usage/token, or GET /api/log/token. When the balance runs out, requests return 403 and recover immediately after you top up. See Billing.

  • Model names — use the exact id from GET /v1/models (for example deepseek-chat). vendor/-prefixed ids are for browsing and filtering in the console, not for API requests.
  • usage has no amount — it reports token counts only; the dollar cost lives in the console and /api/log/token.
  • 403 means “rejected”, not “unauthenticated” — a valid key rejected for balance, whitelist, quota, or IP returns 403.
  • No organization scopingOpenAI-Organization is accepted but ignored.
  • Prompt caching is automatic — OpenAI-compatible models cache prompts automatically; confirm hits via usage.prompt_tokens_details.cached_tokens. See Prompt caching.
  • Errors are sanitized — upstream error messages have vendor-internal details stripped and replaced with a stable message.
  • 429 has no Retry-After — the gateway never retries; back off client-side.
  • Realtime is WebSocket-onlyGET /v1/realtime upgrades to a wss:// connection; there is no HTTP response. See Realtime speech.
  • No files, fine-tuning, or count_tokens — these endpoints return 404.
  • 401? The key is missing, invalid, or unknown — verify it and retry.
  • 403 on a request that used to work? Check the balance and key constraints (whitelist, quota, IP). Topping up restores the key immediately.
  • 429? Back off with jitter (1s → 2s → 4s, capped ~30s).
  • Model not found? Use the exact id from GET /v1/models and drop any vendor/ prefix.
  • Double /v1 in the URL? If your SDK appends its own path, point it at https://api.mirapi.ai/v1, not a deeper path.