Are you an LLM? Read llms.txt for a summary of the docs, or llms-full.txt for the full context.
Skip to content
SDKs and tools

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 Claude

openai 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.

ArgumentWhat it sets
api_keyThe inference key, for model calls.
management_keyThe management key, for the management methods.
base_urlThe Mubit API address. The default is https://api.mubit.ai.
max_retriesHow many times a failed management call is retried. The default is 2.
timeoutHow 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.

OpenAI
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:

ProviderBase URL
OpenAIhttps://api.mubit.ai/openai/v1
Anthropichttps://api.mubit.ai/anthropic
Google Geminihttps://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 version

The 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 data and a next_cursor.
  • Busy or failing calls are retried. On a 429 or 5xx answer, or a network error, the client waits, honouring the server's Retry-After when it sends one, and retries up to max_retries times (2 by default).
  • A retried write isn't applied twice. Every POST carries an Idempotency-Key header. The client makes one for you, or you can pass your own as idempotency_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 wrongError
No key, or a bad optionConfigurationError
Mubit couldn't be reached (DNS, connection refused, TLS)APIConnectionError
The call took too longAPITimeoutError
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 retriesRateLimitError
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.

ℹ️Note

The package also has helpers for runtime calibration. The guide shows that feature with plain HTTP calls.

Related