Skip to content

First integration

What you’ll build: one Python file that sends a chat request with the OpenAI SDK, then streams the same request token by token. Along the way you’ll see the two other protocols MirAPI speaks, how to read errors, and how to check what a call cost.

  • Python 3.10+
  • pip install openai — the official OpenAI SDK, used against MirAPI’s OpenAI-compatible endpoint
  • A MIRAPI_API_KEY created in the console: https://console.mirapi.ai → API Keys. The full key is shown only once, so copy it into your environment or a secret manager immediately.
Terminal window
pip install openai
export MIRAPI_API_KEY=sk-...

Create chat.py and point the SDK at MirAPI’s OpenAI-compatible base URL, https://api.mirapi.ai/v1:

import os
from openai import OpenAI
client = OpenAI(
base_url="https://api.mirapi.ai/v1",
api_key=os.environ["MIRAPI_API_KEY"],
)
response = client.chat.completions.create(
model="deepseek-chat",
messages=[{"role": "user", "content": "Hello! Introduce yourself in one sentence."}],
)
print(response.choices[0].message.content)

Run it:

Terminal window
python chat.py

You get a plain text response. Its usage object reports token counts only — the dollar amount is never included; you reconcile it in the console or via the usage endpoints below.

The fields you’ll touch most often:

Parameter What it does
model The exact catalogue id to call — here deepseek-chat
messages The conversation as role / content pairs
stream Set true to receive SSE deltas instead of one response
max_tokens / max_completion_tokens Cap on generated tokens
temperature Sampling randomness, 02
response_format Structured output (JSON mode / json_schema) — JSON-capable models only
reasoning_effort Reasoning strength — reasoning-capable models only
tools / tool_choice Tool calling — tool-capable models only

Capability-gated parameters (tools, response_format, reasoning_effort) only work on models whose catalogue entry lists that capability under supported_parameters.

Change only the call — add stream=True and iterate the deltas:

import os
from openai import OpenAI
client = OpenAI(
base_url="https://api.mirapi.ai/v1",
api_key=os.environ["MIRAPI_API_KEY"],
)
stream = client.chat.completions.create(
model="deepseek-chat",
messages=[{"role": "user", "content": "Hello! Introduce yourself in one sentence."}],
stream=True,
)
for chunk in stream:
delta = chunk.choices[0].delta.content
if delta:
print(delta, end="", flush=True)
print()

Text now prints as it is generated. Streaming is standard SSE — data: lines ending with the data: [DONE] sentinel — and the SDK parses it for you: no special flags or custom parsing. On reasoning models, thinking tokens arrive before the final answer.

MirAPI exposes three protocol front-ends to the same catalogue. Use whichever your client already speaks; the model name is the same catalogue id.

Protocol Base URL How the key is sent
OpenAI-compatible https://api.mirapi.ai/v1 Authorization: Bearer sk-...
Anthropic https://api.mirapi.ai (no /v1) x-api-key: sk-... (or Authorization: Bearer)
Gemini https://api.mirapi.ai or https://api.mirapi.ai/v1beta x-goog-api-key: sk-... (or ?key=sk-...)

The OpenAI-compatible base URL already ends in /v1, so the SDK appends /chat/completions. The Anthropic client points at the bare origin and appends /v1/messages itself. Gemini clients accept either the bare origin or /v1beta.

Terminal window
curl 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":"Hi"}]}'
Terminal window
curl https://api.mirapi.ai/v1/messages \
-H "x-api-key: $MIRAPI_API_KEY" \
-H "Content-Type: application/json" \
-d '{"model":"deepseek-chat","max_tokens":256,"messages":[{"role":"user","content":"Hi"}]}'
Terminal window
curl "https://api.mirapi.ai/v1beta/models/deepseek-chat:generateContent" \
-H "x-goog-api-key: $MIRAPI_API_KEY" \
-H "Content-Type: application/json" \
-d '{"contents":[{"parts":[{"text":"Hi"}]}]}'

Full header details and key constraints live in Authentication.

Status Meaning What to do
401 Key missing, invalid, or unknown Fix the credential
403 Key valid, request rejected — balance, whitelist, quota, or IP Read the message, fix the cause, retry
413 Request body too large Reduce the size
429 Rate limited Retry with backoff — there is no Retry-After header
500 Gateway or upstream failure Safe to retry idempotent requests

An empty balance returns 403, not 401. OpenAI-protocol errors use the envelope {"error": {"message", "type", "param", "code"}}; type is new_api_error (gateway-side) or upstream_error (sanitized upstream failure).

{"error": {"message": "…", "type": "new_api_error", "param": null, "code": null}}

Every error message ends with a request ID — include it in any support or billing ticket. MirAPI does not retry on your behalf; for 429s use jittered exponential backoff (1s → 2s → 4s, capped around 30s).

The usage object in a non-streaming response counts tokens only — it carries no dollar amount. Reconcile spend in three places:

  • the console billing log;
  • GET /api/usage/token — account-level summary;
  • GET /api/log/token — per-request detail.
Terminal window
curl https://api.mirapi.ai/api/usage/token -H "Authorization: Bearer $MIRAPI_API_KEY"

A short hello costs well under a cent — a few hundred tokens at the model’s per-1M-token rate. Text is billed per 1M tokens in three tiers (input / output / cache read); images are per image and video per second of output. Billing runs on a prepaid USD balance that never expires; when it empties, requests return 403 and the same key works again immediately after a top-up. See Billing & top-ups.

  • 401 on a fresh key — the environment variable isn’t set, or the key was copied incompletely. The Bearer scheme is case-insensitive, and a bare key without the scheme also works.
  • 403 with a valid key — most often an empty balance, a model outside the key’s whitelist, a quota cap, or an IP restriction. Top up or adjust the key.
  • A “coming soon” model errors — catalogue entries marked Coming soon can’t be called yet; pick one marked Available.
  • Model not found — send the exact id the catalogue returns. The vendor/ prefix is for browsing only and is not part of a request.
  • Streaming prints nothing — you’re iterating delta.content, which is None until text arrives (and reasoning arrives first on reasoning models).
  • 429 without Retry-After — that’s expected; back off manually as described above.