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.
Base URLs
Section titled “Base URLs”| 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.
Authentication
Section titled “Authentication”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.
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 support
Section titled “Endpoint support”| 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) |
Requests
Section titled “Requests”import osfrom 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
Section titled “Streaming”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="")Tool calling
Section titled “Tool calling”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.
Structured outputs
Section titled “Structured outputs”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.
Errors
Section titled “Errors”| 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.
Billing
Section titled “Billing”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.
Known differences from OpenAI
Section titled “Known differences from OpenAI”- Model names — use the exact id from
GET /v1/models(for exampledeepseek-chat).vendor/-prefixed ids are for browsing and filtering in the console, not for API requests. usagehas 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 scoping —
OpenAI-Organizationis 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-only —
GET /v1/realtimeupgrades to awss://connection; there is no HTTP response. See Realtime speech. - No files, fine-tuning, or
count_tokens— these endpoints return 404.
Troubleshooting
Section titled “Troubleshooting”- 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/modelsand drop anyvendor/prefix. - Double
/v1in the URL? If your SDK appends its own path, point it athttps://api.mirapi.ai/v1, not a deeper path.