Python SDK
mubit-sdk is for Python developers. It hands you the OpenAI or Anthropic client already pointed at Mubit, and it manages your Mubit organization from code. This page covers version 1.0.6.
Install
pip install mubit-sdk openai # add anthropic if you call Claudeopenai and anthropic aren't dependencies of the package: install the ones you use. The package needs Python 3.10 or newer. The package name is mubit-sdk; you import it as mubit.
Set your keys
The SDK uses two kinds of key: an inference key (mbt_ or mw_) to send model calls, and a management key (mwm_) to manage Mubit. SDKs and tools shows how to create each one.
export MUBIT_API_KEY=<your inference key>
export MUBIT_MANAGEMENT_KEY=<your management key>Mubit() reads both from the environment. You can also pass them in:
import os
from mubit import Mubit
client = Mubit(
api_key=os.environ["MUBIT_API_KEY"],
management_key=os.environ["MUBIT_MANAGEMENT_KEY"],
)Your code needs the key for the job it does. Code that only sends model calls needs only the inference key. Code that only manages Mubit needs only the management key. Without the key a method needs, the SDK raises an error before it sends anything.
| Argument | What it sets |
|---|---|
api_key | The inference key, for model calls. |
management_key | The management key, for the management methods. |
base_url | The Mubit API address. The default is https://api.mubit.ai. |
max_retries | How many times a failed management call is retried. The default is 2. |
timeout | How long a management call may take, in seconds. The default is 60. |
Send model calls through Mubit
Your code keeps using the provider's own client. client.calls.openai() and client.calls.anthropic() give you one that already points at Mubit and carries your inference key. Mubit records each call and forwards it to the provider with the provider key stored for your project. Your calls, the tasks they form and the models trained from them then show in the console.
from mubit import Mubit
client = Mubit()
openai = client.calls.openai() # a stock openai.OpenAI client
reply = openai.chat.completions.create(
model="gpt-5-mini",
messages=[{"role": "user", "content": "Classify this ticket: the invoice total is wrong."}],
)
print(reply.choices[0].message.content)Both methods take the provider client's own options, such as timeout, max_retries and default_headers, and pass them through unchanged:
from mubit import Mubit
client = Mubit()
claude = client.calls.anthropic(timeout=30, max_retries=3)Async
AsyncMubit gives you the async provider clients, openai.AsyncOpenAI and anthropic.AsyncAnthropic:
import asyncio
from mubit import AsyncMubit
async def main() -> None:
client = AsyncMubit()
claude = client.calls.anthropic() # an anthropic.AsyncAnthropic client
reply = await claude.messages.create(
model="claude-sonnet-5-5",
max_tokens=512,
messages=[{"role": "user", "content": "Classify this ticket: the invoice total is wrong."}],
)
print(reply.content[0].text)
asyncio.run(main())Other providers, or your own client setup
Any provider client works when you give it Mubit's base URL for that provider and your inference key as its API key:
| Provider | Base URL |
|---|---|
| OpenAI | https://api.mubit.ai/openai/v1 |
| Anthropic | https://api.mubit.ai/anthropic |
| Google Gemini | https://api.mubit.ai/gemini |
| OpenRouter, Groq, Together, Mistral (OpenAI-compatible) | https://api.mubit.ai/compat/<provider>/v1, for example https://api.mubit.ai/compat/groq/v1 |
import os
import anthropic
claude = anthropic.Anthropic(
base_url="https://api.mubit.ai/anthropic",
api_key=os.environ["MUBIT_API_KEY"],
)Connect covers each provider, and OpenAI-compatible the compatible services. A project can also use a Neon AI Gateway key: see Connect with the Neon AI Gateway.
Manage Mubit from code
With a management key, your code can do what you do in the console. Manage Mubit lists every method. This example reads the tasks, rates a call, trains a new version and rolls it out:
from mubit import Mubit
client = Mubit()
for task in client.tasks.list(): # every task, across all pages
print(task["id"], task["name"], task["stage"])
task_id = "task_triage12"
calls = client.calls.list(task=task_id, limit=20)
client.calls.feedback(calls.data[0]["id"], rating="down", comment="Missed the sales tax line.")
client.tasks.train(task_id) # train a new version now
version = client.tasks.versions(task_id).data[0]
client.versions.rollout(version["id"], canary_percent=10) # serve it on 10% of the task's calls
client.versions.promote(version["id"]) # then on all of them
client.versions.rollback(version["id"]) # or go back to the previous versionThe namespaces are org, projects, keys, providers, management_keys, calls, tasks, versions, lessons, imports, usage, billing, activity and jobs. Answers are the API's JSON as dictionaries.
On AsyncMubit, the same methods are awaited, and a list is walked with async for:
import asyncio
from mubit import AsyncMubit
async def main() -> None:
client = AsyncMubit()
async for task in client.tasks.list():
print(task["id"])
await client.tasks.train("task_triage12")
asyncio.run(main())Follow a job
A long-running operation runs as a job. jobs.wait polls the job until it ends. It raises an error if the job fails or is canceled, or if the wait passes timeout seconds (60 by default):
from mubit import Mubit
client = Mubit()
job = client.jobs.wait("job_example1", timeout=300)
print(job["status"])Pages, retries and safe repeats
- Lists come in pages. A list call returns a page you can loop over. The loop fetches the next pages for you. Each page has the items as
dataand anext_cursor. - Busy or failing calls are retried. On a
429or5xxanswer, or a network error, the client waits, honouring the server'sRetry-Afterwhen it sends one, and retries up tomax_retriestimes (2 by default). - A retried write isn't applied twice. Every
POSTcarries anIdempotency-Keyheader. The client makes one for you, or you can pass your own asidempotency_key="...".
Errors
Every error the client raises is a MubitError, so one except handles all of them. To handle a specific case, catch its subclass:
| What went wrong | Error |
|---|---|
| No key, or a bad option | ConfigurationError |
| Mubit couldn't be reached (DNS, connection refused, TLS) | APIConnectionError |
| The call took too long | APITimeoutError |
| The request was malformed (400) | BadRequestError |
| The key is missing or wrong (401) | AuthenticationError |
| The key isn't allowed to do this (403) | PermissionDeniedError |
| The thing doesn't exist (404) | NotFoundError |
| It conflicts with the current state (409), or is gone (410) | ConflictError, GoneError |
| The request is too large (413) | PayloadTooLargeError |
| Too many requests (429), even after the retries | RateLimitError |
| A server error (500 and other 5xx), or Mubit is unavailable (503) | InternalServerError, UnavailableError |
All of them are importable from mubit. An error from the API carries status_code, code (a short reason, such as not_found), request_id (quote it when you contact support) and retry_after:
from mubit import Mubit, NotFoundError
client = Mubit()
try:
client.tasks.get("task_triage12")
except NotFoundError as err:
print(err.code, err.request_id)Errors from model calls you send through Mubit are the provider client's own, for example openai.APIError.
The package also has helpers for runtime calibration. The guide shows that feature with plain HTTP calls.
Related
- SDKs and tools: the keys, and the other tools.
- Manage Mubit: every method, with its TypeScript method, command and MCP tool.
- Migrate to 1.0: moving code from an earlier version.
- Frameworks: LangChain and other frameworks.