mubit-sdk
The Python client for Mubit. Version 1.0.6.
Mubit learns from the model calls your agents already make. Send your OpenAI, Anthropic or Gemini calls through Mubit, and it:
- records each call and groups the calls into tasks;
- collects and rates training examples for each task;
- fine-tunes a smaller model for a task and tests it on examples it never trained on;
- serves a model that passes on a share of the task's traffic first, then on all of it, and rolls it back whenever you ask;
- forms lessons from the traffic.
You see and control all of this in the console at https://app.mubit.ai. This package lets your code and your agents do the same.
Install
pip install mubit-sdk openai # add anthropic if you call Claude
openai and anthropic aren't dependencies of this package: install the ones you use. Python 3.10 or newer.
Keys
Mubit uses two kinds of key. You create both in the console at https://app.mubit.ai.
| Key | Starts with | Environment variable | What it's for |
|---|---|---|---|
| Inference key | mw_ |
MUBIT_API_KEY |
Sending model calls through Mubit. Each key belongs to one project. |
| Management key | mwm_ |
MUBIT_MANAGEMENT_KEY |
Managing your organization from code: everything under "Manage Mubit from code". |
Your code needs the key for the job it does. To send model calls through Mubit, it needs an inference key. To manage Mubit from code, it needs a management key. Code that does both needs both.
Only an admin of your organization can create keys. If you aren't an admin, ask an admin of your organization to create the key for you. The console shows a new key once, so copy it when it appears. If you lose a key, create a new one.
Create an inference key
- Sign in at https://app.mubit.ai.
- Open the project the key is for. In the sidebar, select Mubit keys.
- Select Create key.
- Enter a Name, such as the name of your app. Leave Permission on Inference: calls this project.
- Select Create key, and copy the key.
The key works for that project only. For another project, create a key in that project.
You can also get an inference key when you create a project: in New project, keep Create an inference key selected. The Connect page has a Create key button too.
Create a management key
- Sign in at https://app.mubit.ai.
- Open All projects. In the sidebar, under Workspace, select Management keys.
- Select Create management key.
- Enter a Key name, and choose a Role. The table below shows what each role can do.
- Optional: choose a Project scope to limit the key to one project. The default is All projects (organization-wide).
- Select Create management key again. Select Copy key, save the key, and select Done.
| Role | What the key can do |
|---|---|
read |
Read everything except the list of management keys |
member |
Also rate calls, review training examples and import past calls |
admin |
Everything |
Set the keys
export MUBIT_API_KEY=mw_...
export MUBIT_MANAGEMENT_KEY=mwm_...
Mubit() reads both keys from the environment. You can also pass them in: Mubit(api_key="mw_...", management_key="mwm_..."). The base URL is https://api.mubit.ai.
Send model calls through Mubit
Your code keeps using the provider's own client. Mubit hands 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)
Anthropic
client.calls.anthropic() gives you a stock anthropic.Anthropic client. You call it exactly as you would call Anthropic directly:
from mubit import Mubit
client = Mubit()
claude = client.calls.anthropic() # a stock anthropic.Anthropic client
reply = 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)
Async
AsyncMubit returns the async clients, openai.AsyncOpenAI and anthropic.AsyncAnthropic:
from mubit import AsyncMubit
client = AsyncMubit()
claude = client.calls.anthropic() # anthropic.AsyncAnthropic
reply = await claude.messages.create(model="claude-sonnet-5-5", max_tokens=512, messages=[...])
Client options
calls.openai() and calls.anthropic() take the provider client's own options, such as timeout, max_retries and default_headers, and pass them through unchanged:
claude = client.calls.anthropic(timeout=30, max_retries=3)
Other providers, or your own client setup
Any provider client works when you point it at Mubit's base URL for that provider and give it 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"])
Neon AI Gateway
Each Neon branch has its own AI Gateway. If a project has a Neon AI Gateway key, Mubit sends the project's OpenAI, Anthropic and Gemini calls to that gateway whenever the project has no key of its own for the provider. Your code stays as it is: it keeps using the base URLs above.
Today, Neon AI Gateway keys are available on the EU shared workspace at https://app.mubit.ai only. Elsewhere the console does not list them.
Add the key in the console. Only an admin of your organization can.
- Sign in at https://app.mubit.ai.
- Open All projects. In the sidebar, under Workspace, select Provider keys.
- Select Add key, and choose Neon AI Gateway as the Provider.
- In Gateway URL, enter your branch's AI Gateway URL:
https://and the branch's host, with no path, port, user or query after it, such ashttps://br-example-123456-api.ai.c-2.us-east-2.aws.neon.tech. - In Token, enter a Neon token that starts
nt_live_…and has theai_gateway:invokescope. - Select Check and save. Mubit checks the token with your branch's gateway before it saves anything.
For a call on the OpenAI, Anthropic or Gemini base URL, Mubit uses the first of these that exists:
- the project's own key for that provider;
- the project's Neon AI Gateway key;
- Mubit's starter-credit key, if the project has the starter credit.
The OpenAI-compatible base URLs (https://api.mubit.ai/compat/<provider>/v1) never go to Neon. Mubit records a call that goes to Neon under the provider neon, and sends the token only to the gateway URL saved with it.
Manage Mubit from code
With a management key, your code can do everything you do in the console. The one exception is organization members, which you manage in the console.
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
What you can do, by area:
Organization and projects
| You can | Python |
|---|---|
| See your organization | client.org.get() |
| List and open projects | client.projects.list(), client.projects.get(id) |
| Create, rename and delete a project | client.projects.create(name=...), client.projects.rename(id, name=...), client.projects.delete(id) |
Keys
| You can | Python |
|---|---|
| List, create, rotate and revoke a project's inference keys | client.keys.list(project=...), client.keys.create(project=..., name=...), client.keys.rotate(id), client.keys.revoke(id) |
| List, add, rotate and delete the provider keys Mubit calls providers with | client.providers.list(), client.providers.add(...), client.providers.rotate(id, ...), client.providers.delete(id) |
| List, create, rotate and revoke management keys | client.management_keys.list(), client.management_keys.create(name=..., role=...), client.management_keys.rotate(id), client.management_keys.revoke(id) |
Calls
| You can | Python |
|---|---|
| List calls, filtered by project, task, model, status and time | client.calls.list(...) |
| Open a call | client.calls.get(id) |
| Rate a reply up or down, with a comment or a corrected answer | client.calls.feedback(id, rating="up", comment=..., correction=...) |
| Delete everything stored for one session | client.calls.forget_session(session_id) |
Tasks
| You can | Python |
|---|---|
| List tasks, and open one: its stage, what it waits for and why | client.tasks.list(), client.tasks.get(id) |
| Read a task's timeline | client.tasks.timeline(id) |
| Find out why a task isn't training or serving yet | client.tasks.diagnose(id) |
| Review its training examples: keep or drop each one | client.tasks.examples(id), client.tasks.review_example(id, example_id, action=...) |
| Train a new version now | client.tasks.train(id) |
| Pause and resume a task's learning | client.tasks.pause(id), client.tasks.resume(id) |
| Turn automatic routing to the task's trained models off or on | client.tasks.set_serving(id, on=False) |
| Stop all of a task's trained models at once | client.tasks.stop(id, reason=...) |
Trained models (versions)
| You can | Python |
|---|---|
| List a task's versions, and open one with its test results | client.tasks.versions(id), client.versions.get(id) |
| Run a version silently beside the current model, or serve it on a share of the calls | client.versions.rollout(id, shadow_percent=..., canary_percent=...) |
| Promote, demote or roll back a version | client.versions.promote(id), client.versions.demote(id), client.versions.rollback(id) |
| Read a task's promotion history | client.tasks.history(id) |
Settings
Your plan sets the outer limits. Within them, you can make a setting stricter or cheaper, but not looser.
| You can | Python |
|---|---|
| Read a project's settings, and who set each value | client.settings.get(project=...) |
| Change the project settings you see in the console: approval mode, silent test, partial rollout, live checks, grading, when new versions train, the longest example, how long calls are kept and the pass bar | client.settings.update(project=..., ...) |
| Fine-tuning, per task: base model, retrain schedule, rows needed to train, the daily rating cap and the rating rubric | client.tasks.update_settings(id, ...) |
| Routing, per task: when a trained model takes traffic, the silent-test and partial-rollout shares, the audit rate, and the quality bar it must hold | client.tasks.update_settings(id, ...) |
| Lessons, per project: lesson injection on or off, its token budget, and the daily cap on reflections | client.settings.update(project=..., ...) |
| Spend: a daily cap on audit spend | client.settings.update(project=..., ...) |
| Read the audit of every settings change | client.settings.audit(project=...) |
| Read a task's settings | client.tasks.settings(id) |
Lessons, imports, usage and activity
| You can | Python |
|---|---|
| List and open what Mubit learned | client.lessons.list(), client.lessons.get(id) |
| Import past calls from Langfuse, Braintrust or Logfire, follow the import, or cancel it | client.imports.create(...), client.imports.list(), client.imports.cancel(id) |
| See usage: calls and tokens | client.usage.get() |
| See your balance and credit | client.billing.get() |
| Read a project's activity | client.activity.list() |
| Follow a long-running job, such as a training run or an import | client.jobs.get(id), client.jobs.wait(id, timeout=...) |
Answers are the API's JSON as Python dicts.
Pages, retries and safe repeats
- Lists come in pages. A list call returns its first page, as
page.dataandpage.next_cursor. Loop over the result and the client fetches the next pages for you. - Busy or failing calls are retried. On a 429 or 5xx answer, the client waits (honouring the server's
Retry-Afterwhen it sends one) and retries, up tomax_retriestimes (2 by default). - A retried write isn't applied twice. Every POST carries an
Idempotency-Keyheader. The client makes one for you, or you can pass your own withidempotency_key=.... - Async works the same way.
AsyncMubithas the same namespaces:page = await client.tasks.list(), orasync for task in client.tasks.list():.
Errors
Every error the client raises is a mubit.MubitError, so catching that one class catches everything. To handle a specific case, catch its subclass:
| What went wrong | Error |
|---|---|
| No key, or a bad option | mubit.ConfigurationError |
| Mubit couldn't be reached (DNS, connection refused, TLS) | mubit.APIConnectionError |
| The call took too long | mubit.APITimeoutError |
| The request was malformed (400) | mubit.BadRequestError |
| The key is missing or wrong (401) | mubit.AuthenticationError |
| The key isn't allowed to do this (403) | mubit.PermissionDeniedError |
| The thing doesn't exist (404) | mubit.NotFoundError |
| It conflicts with the current state (409), or is gone (410) | mubit.ConflictError, mubit.GoneError |
| The request is too large (413) | mubit.PayloadTooLargeError |
| Too many requests (429), even after the retries | mubit.RateLimitError |
| A server error (500 and other 5xx), or Mubit is unavailable (503) | mubit.InternalServerError, mubit.UnavailableError |
Errors from the API carry status_code, code (a short reason, such as not_found), request_id (quote it when you contact support), retry_after and body:
import mubit
client = mubit.Mubit()
try:
client.tasks.get("task_triage12")
except mubit.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.
Related
- Console: https://app.mubit.ai
- JavaScript client:
npm install @mubit-ai/sdk
Metadata
Release files for mubit-sdk 1.0.6
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| mubit_sdk-1.0.6.tar.gz | 374.6 kB | Without provenance | Details for mubit_sdk-1.0.6.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| mubit_sdk-1.0.6-py3-none-any.whl | Python 3 | none | any | Details for mubit_sdk-1.0.6-py3-none-any.whl |
Total release size: 762.4 kB
Release files / mubit_sdk-1.0.6.tar.gz
| Download URL | mubit_sdk-1.0.6.tar.gz |
|---|---|
| Size | 374.6 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
57e123dd268f8a888b913b03b90933a9781418d7161ab06d48ccc1ad984caa1c
|
|
BLAKE2b-256 checksum How to use checksums |
d9817dc4dbc483099640c1d205bbb05d17ce88ebcdbdf8eeaf5bcd397930c3b0
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Release files / mubit_sdk-1.0.6-py3-none-any.whl
| Download URL | mubit_sdk-1.0.6-py3-none-any.whl |
|---|---|
| Size | 387.8 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
35c0ac3313b6ead63ddc1799c9da1445767e84bb9d1120cc03af533c4ce1ac07
|
|
BLAKE2b-256 checksum How to use checksums |
86f789d90943845201b92908dfe540e8a29ac93032ebdf3c6781073fd6f97840
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Security for mubit-sdk 1.0.6
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.