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

Python Client SDK

The minima_client package is the official thin, typed Python client for the hosted Minima API, plus an optional zero-code intake helper.

ℹ️Note

This page targets the hosted service (https://api.minima.sh, authenticated with your Mubit key). The same package works against a self-hosted Minima — point base_url at your deployment (see Self-hosting); the repo's docs/client-sdk.md documents that framing.

Install

pip install minima-cli

The SDK is published on PyPI as part of minima-cli — the Python package that ships the minima_client SDK and the Minima server tooling. It does not include the minima terminal agent: the CLI is a native binary installed with Homebrew (brew install mubit-ai/minima/minima). Import the SDK as minima_client:

from minima_client import MinimaClient, AsyncMinimaClient, MinimaError, autocapture

Clients

Both MinimaClient (sync) and AsyncMinimaClient (async) share the same surface.

from minima_client import MinimaClient
 
with MinimaClient("https://api.minima.sh", api_key="mbt_…", timeout=10.0) as minima:
    rec = minima.recommend("Summarize this incident report into 3 bullets.",
                           cost_quality_tradeoff=3)
    print(rec.recommended_model.model_id)
  • base_url — the Minima API base URL (https://api.minima.sh).
  • api_key — your Mubit API key (mbt_…), sent as Authorization: Bearer <key> and passed through to Mubit. Required.
  • timeout — HTTP timeout in seconds.

The async client mirrors every method with await; use it inside FastAPI/async apps:

async with AsyncMinimaClient("https://api.minima.sh", api_key="mbt_…") as minima:
    rec = await minima.recommend(task)

recommend(task, *, ...)

Returns a RecommendResponse.

rec = minima.recommend(
    task,                              # str | TaskInput | dict
    cost_quality_tradeoff=5.0,         # 0..10
    constraints=None,                  # Constraints | None
    user_id=None,
    namespace=None,
    allow_llm_escalation=True,
    explain=True,
    baseline_model_id=None,            # the model you'd use without Minima -> savings()
)

task is flexible:

minima.recommend("plain prompt text")                       # str
minima.recommend({"task": "…", "task_type": "code"})        # dict
 
from minima_client import TaskInput, Constraints
minima.recommend(
    TaskInput(task="…", difficulty="hard"),
    constraints=Constraints(min_quality=0.85, max_cost_per_call=0.02),
)

recommend_workflow(req)

Takes a WorkflowRequest, returns a WorkflowResponse.

from minima_client import WorkflowRequest, WorkflowStep, TaskInput
 
req = WorkflowRequest(
    steps=[
        WorkflowStep(step_id="extract",
                     task=TaskInput(task="Extract entities from …",
                                    task_type="extraction")),
        WorkflowStep(step_id="reason",
                     task=TaskInput(task="Decide next action given …",
                                    task_type="reasoning", difficulty="hard")),
    ],
    cost_quality_tradeoff=4,
)
wf = minima.recommend_workflow(req)
print(wf.total_est_cost_usd, "vs", wf.total_est_cost_if_all_premium)

feedback(recommendation_id, chosen_model_id, outcome, **kwargs)

Returns a FeedbackResponse. outcome is "success" | "partial" | "failure". Pass realized numbers to power the observed/rescaled cost tiers, and declare where the label came from with evidence_source:

⚠️Warning

The typed Usage parameter is the loop's single biggest accuracy lever — report what the provider actually billed (never echo Minima's own est_cost_usd back as cost_usd). Realized input_tokens / output_tokens / cost_usd / latency_ms are what let the cost basis climb estimate → observed → rescaled for your account.

from minima_client import Usage
 
minima.feedback(
    rec.recommendation_id,
    rec.recommended_model.model_id,
    "success",
    usage=Usage(input_tokens=180, output_tokens=640, cost_usd=0.0034, latency_ms=2100),
    quality_score=0.95,          # your judge/eval score, if you have one
    evidence_source="gate",      # gate | judge | human | none — label provenance
    idempotency_key="…",         # optional
)

Label honesty matters: evidence_source="none" (no real label) makes the outcome cost/latency telemetry only — it never teaches the success posterior. An outcome you asserted yourself is "human"; a deterministic check that passed is "gate" — the only origin that may claim verified-in-production. For provider/infra faults pass error_cause="infra" so a rate limit never reads as model quality. Per-step results for multi-step work ride as step_outcomes=[...].

The feedback call retries transient faults automatically (3 attempts, exponential backoff); the server dedupes via the idempotency key.

models(...), strategies(...), capabilities(), health()

catalog = minima.models(provider="anthropic", max_cost=10.0)      # ModelsResponse
strat   = minima.strategies(namespace="team-payments", max_strategies=5)
caps    = minima.capabilities()   # feature handshake — gate optional features on this
status  = minima.health()         # dict

diagnose(...) and memory_health(...)

The memory insight layer: failure lessons matching an error, and your namespace's memory hygiene. Both degrade gracefully — a Mubit outage returns an empty result plus a memory_unavailable warning, never an exception:

diag = minima.diagnose("TypeError: cannot read properties of undefined", limit=3)
for lesson in diag.failure_lessons:
    print(lesson.content, lesson.confidence)
 
mh = minima.memory_health(namespace="team-payments", stale_threshold_days=30)
print(mh.entry_counts, mh.stale_entries, mh.contradictions, mh.promotion_candidates)

savings(...), calibration(...), and policy_value(...)

The measurement layer: what Minima saved you, whether predicted_success is calibrated against your reported outcomes, and what alternative routing policies would have been worth (policy_value(...) — doubly-robust regret-vs-oracle). All report on your account's decision ledger (see the API reference for every field).

report = minima.savings(namespace="team-payments", days=30, group_by="task_type")
print(report.summary.estimated.savings_vs_premium_usd,   # generous baseline
      report.summary.estimated.savings_vs_declared_usd,  # your declared default
      report.health["feedback_coverage"])                # how much to trust "realized"
 
cal = minima.calibration(days=30)
for r in cal.reports:
    print(r.slice_key, r.n, r.ece, r.ece_shrunk)
for flag in cal.drift_flags:
    print(flag.cluster, flag.model_id, flag.direction)   # sustained prediction drift

Pass baseline_model_id on recommend() and realized actual_cost_usd on feedback() to turn the savings report from an estimate into a measurement.

Errors

Non-2xx responses raise MinimaError (which carries the problem+json detail):

from minima_client import MinimaError
 
try:
    rec = minima.recommend(task)
except MinimaError as exc:
    # fall back to a default model
    ...

Zero-code intake: autocapture

minima_client.autocapture is a thin wrapper over mubit.learn. Calling enable() pins a learn session to the same memory lane Minima recalls from (minima:<namespace>) and monkeypatches your OpenAI/Anthropic/LiteLLM/Google-GenAI clients, so every LLM call auto-ingests its trace — no code changes at the call site. Requires mubit-sdk.

from minima_client import autocapture
 
autocapture.enable(
    api_key="<mubit-key>",
    endpoint="https://api.mubit.ai",
    namespace="team-payments",
    user_id="svc-router",
)
 
# ... your normal OpenAI/Anthropic/LiteLLM calls happen here, auto-captured ...
 
# mubit.learn does NOT fabricate a success signal — close the loop explicitly:
autocapture.feedback(good=True)   # or score in [-1, 1]
autocapture.disable()             # restore original client behavior
⚠️Warning

What autocapture does and doesn't do. Autocapture lands traces + lessons in Minima's lane (enriching the reasoner's memory block and Mubit's reflection), but it does not by itself produce the kind="outcome" records the deterministic k-NN aggregator scores. To fully close the loop, either call autocapture.feedback(...) or send a quality score to POST /v1/feedback.

Other helpers:

autocapture.wrap(client)                           # enrich one client instead of global patching
autocapture.capture(messages, response)            # manual ingest for raw HTTP / unsupported libs

See the zero-code intake example.