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 Platform: https://platform.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, never a dollar amount. Reconcile spend in the Platform or through the usage endpoints below.

The fields you’ll touch most often:

Parameter What it does
model The exact catalog 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, 0–2
response_format Structured output (JSON mode / json_schema) on JSON-capable models only
reasoning_effort Reasoning strength on reasoning-capable models only
tools / tool_choice Tool calling on tool-capable models only

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

Only the call changes. Add stream=True and iterate over 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, so you don’t need special flags or custom parsing. On reasoning models, thinking tokens arrive before the final answer.

MirAPI exposes three protocol front-ends to the same catalog. Use whichever your client already speaks; the model name is the same catalog 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"}]}]}'

Authentication covers the full header details and key constraints.

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, so for 429s use jittered exponential backoff (1s → 2s → 4s, capped around 30s).

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

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

A short hello is a few hundred tokens at the model’s per-1M-token rate, which comes to well under a cent. Text is billed per 1M tokens in three tiers (input / output / cache read); images are billed 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: catalog entries marked Coming soon can’t be called yet; pick one marked Available.
  • Model not found: send the exact id the catalog 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: this is expected. Back off manually as described above.