API reference
Mubit takes each provider's own API. Your app sends the requests it sends today to Mubit's base URL, under the provider's prefix, with a Mubit key in place of the provider key. Mubit records each call.
Base URL
https://api.mubit.aiUse the base URL your console's Connect page shows.
Call Mubit from your server, not from a browser. The API answers no CORS preflight request, so a browser refuses to send the call.
Keys
Send your Mubit key in the header your provider's SDK uses for its own key. A Mubit key starts with mbt_.
| Provider | Header |
|---|---|
| OpenAI and OpenAI-compatible services | Authorization: Bearer YOUR_MUBIT_KEY |
| Anthropic | x-api-key: YOUR_MUBIT_KEY, or Authorization: Bearer YOUR_MUBIT_KEY |
| Gemini | x-goog-api-key: YOUR_MUBIT_KEY |
Send one key, and only your Mubit key. Mubit refuses a call with a 401 when it carries:
- no key (
missing_key); - a key that is wrong, revoked or malformed, or two different Mubit keys (
invalid_key); - a provider's API key, a sign-in token, or another credential beside your Mubit key (see Key errors);
- your Mubit key somewhere else in the request, such as another header or the path (
middleware_key_refused).
For Gemini, send the key in the header, not in ?key=.
Your provider keys stay in the console. Add them under Provider keys, as the Provider keys page shows.
Paths
Each provider has a prefix. After it, use the provider's own path.
| Provider | Prefix | Examples |
|---|---|---|
| OpenAI | /openai/v1 | POST /openai/v1/chat/completions, POST /openai/v1/responses, POST /openai/v1/embeddings, GET /openai/v1/models |
| Anthropic | /anthropic | POST /anthropic/v1/messages, POST /anthropic/v1/messages/count_tokens, GET /anthropic/v1/models |
| Gemini | /gemini | POST /gemini/v1beta/models/{model}:generateContent, POST /gemini/v1beta/models/{model}:streamGenerateContent?alt=sse, GET /gemini/v1beta/models |
| OpenAI-compatible services | /compat/{service}/v1 | POST /compat/openrouter/v1/chat/completions |
So the base URL for an SDK is:
| SDK | Base URL |
|---|---|
| OpenAI | https://api.mubit.ai/openai/v1 |
| Anthropic | https://api.mubit.ai/anthropic |
| Gemini (google-genai) | https://api.mubit.ai/gemini |
| OpenAI SDK on Gemini | https://api.mubit.ai/gemini/v1beta/openai |
| OpenAI SDK on an OpenAI-compatible service | https://api.mubit.ai/compat/{service}/v1 |
OpenAI-compatible services
{service} is openrouter, groq, together or mistral. Under /compat/{service}/v1, Mubit serves POST /chat/completions, POST /embeddings, GET /models and GET /models/{id}. These services need your own key, unless the console lists the service under the starter credit. See OpenAI-compatible services.
Paths Mubit refuses
| Answer | When |
|---|---|
404 unknown_route | The path is outside the prefixes above, or a /compat path or method that isn't listed. |
404 unknown_provider | The {service} in a /compat path isn't one of the four. The message lists them. |
403 admin_path_refused | An organization or admin endpoint of the provider. Call the provider directly for those. |
400 invalid_path | The path has a . or .. segment, a ;, a control character, or an escape that decodes again. |
On the starter credit, fewer paths work. After the prefix: on Anthropic, POST /v1/messages, POST /v1/messages/count_tokens and GET /v1/models; on OpenAI, POST /chat/completions, POST /responses, POST /embeddings and GET /models; on Gemini, POST /{version}/models/{model}:generateContent and :streamGenerateContent, where {version} is a version such as v1beta or v1; on an OpenAI-compatible service, POST /chat/completions and GET /models. Any other path gets 403 starter_credit_route until you add your own key for that provider. A provider the starter credit doesn't cover always needs your own key; the console names the providers it covers. See the starter credit's errors.
Request headers Mubit reads
Mubit reads these optional headers, and doesn't send them to your provider:
| Header | Values | What it does |
|---|---|---|
x-middleware-store | false, 0, off or no | Mubit keeps a record of the call without its request and answer. |
x-middleware-record | full, metadata or off | How much of the call Mubit keeps. metadata is the same as x-middleware-store: false; off also drops the call's task fingerprint. |
A header can lower what Mubit keeps of a call, never raise it. When a call sends several values, the lowest one counts.
Mubit passes most other headers to the provider as you sent them, such as anthropic-version and anthropic-beta. In particular:
- it never sends a header that starts with
x-middleware-to your provider; - it takes out your Mubit key, and sends your provider key or the starter credit's key in its place;
- it doesn't pass on cookies, in either direction;
- it doesn't pass on forwarding headers, such as
forwarded,x-forwarded-for,x-real-ipandvia.
Response headers
| Header | On | What it says |
|---|---|---|
x-middleware-request-id | Every answer: a success, a stream, your provider's error, Mubit's own error | The call's ID in Mubit: mreq_ and 32 hex digits. IDs sort by time. |
x-should-retry | Mubit's errors that a retry won't fix, such as the key errors | false. |
retry-after | Mubit's 429 and 503 errors | Seconds to wait before you try again. |
Your provider's own headers, such as its request-id or x-request-id, pass unchanged beside them.
Quote the x-middleware-request-id when you ask for help with a call. Find a call's request ID shows how to read it in Python, TypeScript and curl.
Model IDs
Send the model IDs you send today. When Mubit sends a call to your provider, it sends your model ID as your app wrote it, and records it. On Gemini, the model ID is in the path.
There is no Mubit model ID. On the starter credit, a call must name a model the starter credit serves: another model gets 400 starter_credit_model, and the message lists the models. See Keep your model IDs.
Streaming
Stream as you do with your provider: set stream to true on OpenAI, OpenAI-compatible services and Anthropic, or call streamGenerateContent?alt=sse on Gemini. Mubit passes the stream to your app as it arrives.
Each provider page shows an example: OpenAI, Anthropic, Gemini.
Limits
Mubit's limits today:
| Limit | Value | What happens |
|---|---|---|
| Connect to the provider | 10 s | Mubit tries once more on a new connection. If that fails too, the call gets 502 upstream_unreachable, and nothing was sent. |
| First byte | 610 s | From the start of your request to the provider's status and headers. Past it, the call gets 504 upstream_timeout. |
| Silence | 15 minutes | The longest time no part of the answer goes to your app, because the provider is quiet or your app stopped reading. Past it, Mubit closes the connection. |
| Total | 60 minutes | The longest a call can run, from the start of the request to the last byte of the answer. Past it, Mubit closes the connection. |
When Mubit closes a stream at a limit, the connection breaks. Mubit adds nothing to the stream.
The OpenAI and Anthropic SDKs wait 600 s by default, so their own timeout usually ends a wait for the first byte before Mubit's does.
Retries
Mubit retries a call once, and only when the first try failed before any byte of the request was sent: the connection to the provider couldn't be made. It never retries a call once the request has started, because the provider may already have it, and a second send could be billed twice.
Your SDK owns retries. Mubit's errors tell it what to do: x-should-retry: false on errors a retry won't fix, and retry-after on 429 and 503.
Sizes
On the starter credit, Mubit checks each request body before it sends the call. The body must:
- be at most 8 MiB, or the call gets
413 starter_credit_body; - arrive whole within 60 s, or the call gets
408 starter_credit_body; - be sent as plain JSON, with no content encoding, and be one JSON object, or the call gets
400 starter_credit_body.
With your own provider key, Mubit doesn't make these checks.
Today, Mubit records up to 32 MiB of a request, and the first 4 MiB and the last 256 KiB of an answer. A bigger call still goes through whole. Only Mubit's record of it is cut.
Errors
Mubit passes your provider's answers back to your app, its errors included.
Mubit's own errors use your provider's error format, so your SDK reads them as it reads the provider's. Each one has the type middleware_gateway_error, a code, a message, and the call's request ID. The one exception is a call sent to the console's address: it gets 503 no_provider_configured, with the type middleware_error. Troubleshooting lists every code and what to do.
The format depends on the path:
| Path | Format |
|---|---|
/openai/…, /compat/…, /gemini/v1beta/openai/…, and every other path, such as one that gets 404 unknown_route | OpenAI's |
/anthropic/… | Anthropic's |
/gemini/…, except its OpenAI-compatible endpoint | Gemini's |
{
"error": {
"message": "No API key was provided. Send your Mubit key where the provider's SDK sends its API key.",
"type": "middleware_gateway_error",
"param": null,
"code": "missing_key",
"request_id": "mreq_…"
}
}In Gemini's format, the HTTP status is the error's code, and Mubit's code is the reason of its ErrorInfo detail.
An error whose type is middleware_gateway_error or middleware_error came from Mubit. An error without either, on an answer that carries x-middleware-request-id, came from your provider as it sent it. An answer without that header didn't come through Mubit's API: check the base URL and try again.