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

Errors

HTTP status codes, the SDK 0.14 error hierarchy, the 0.13 aliases that still catch, and the structured error payload returned by the Mubit control API.

Every error response carries a flat JSON body with a single human-readable error string:

{
  "error": "ingest request exceeds the 1000-item limit"
}

There is no nested type/code envelope. The request id travels in the x-request-id response header and a throttled response may carry Retry-After; the SDK copies both onto the exception (request_id, retry_after). Map programmatically on the HTTP status code (below) or, over gRPC, on the canonical status code. The HTTP API is a thin shell over the gRPC service, so every HTTP status is derived from a gRPC code.

Status codes

StatusgRPC codeMeaningSDK classRetried by the SDK?
400InvalidArgument, FailedPrecondition, OutOfRangeRequest shape rejected — missing/invalid field, or an input cap exceeded (e.g. >1000 items)BadRequestErrorNo
401UnauthenticatedMissing or invalid Authorization headerAuthenticationErrorNo
403PermissionDeniedKey is valid but not scoped to this run/resource, or the principal is too low for the writePermissionDeniedErrorNo
404NotFoundSession, agent, reference id, lesson, job or snapshot does not existNotFoundErrorNo
409AlreadyExistsA unique resource already exists, or a control-plane rule refuses the write (proposer cannot approve own proposal, kill switch is forced on)AlreadyExistsError (a ConflictError)No
410Endpoint was removed (legacy core SDM lanes); use insert/search insteadGoneErrorNo
429ResourceExhaustedAn upstream dependency (e.g. an LLM provider) is throttlingRateLimitErrorYes, with backoff
500 and any unlisted 5xxInternal (and unmapped)Unhandled server errorInternalServerErrorYes, with backoff
501UnimplementedThe operation does not exist on this server or transportUnsupportedFeatureErrorNo
503UnavailableBackend temporarily unavailableUnavailableErrorYes, with backoff
ℹ️Note

The runtime does not currently emit 422, 499, 502, or 504; the SDK maps 422 to BadRequestError and any other 5xx to InternalServerError should a proxy produce one. Business-rule rejections surface as 400; overload surfaces as 503. See Rate limits for what is and isn't throttled.

The SDK hierarchy

Every class is exported from the top-level package (import mubit / import { ... } from "@mubit-ai/sdk"); there is no mubit.errors submodule in Python (mubit.errors in JS is the same module re-exported).

MubitError
├── ConfigurationError            no key, no endpoint, unknown option
├── APIConnectionError            DNS, refused, TLS   (.endpoint, .refused, .code)
│   ├── APITimeoutError           request or connect deadline elapsed
│   └── TransportError            0.13 shape TransportError(code, message); also raised by the gRPC transport
├── APIStatusError                the server answered with an error status
│   │                             (.status_code, .request_id, .body, .retry_after, .grpc_code, .path)
│   ├── BadRequestError (400)     ├── AuthenticationError (401)   ├── PermissionDeniedError (403)
│   ├── NotFoundError (404)       ├── AlreadyExistsError (409)    ├── GoneError (410)
│   ├── RateLimitError (429)      ├── InternalServerError (5xx)   ├── UnavailableError (503)
│   ├── ControlError              /v2/loop control-plane rejection (.status is an alias of .status_code)
│   │   ├── PolicyError           policy write rejected (.valid_keys)
│   │   └── ControlUnavailable    control plane unreachable; also an APIConnectionError
│   └── LessonRejected            a lesson failed the policy schema or its condition (.reason)
└── UnsupportedFeatureError       501, an unknown raw.invoke operation, bidirectional streaming

str(error) is HTTP 404: referenced entry was not found; error.message is the server text alone. request_id is None when the response carried no x-request-id header; retry_after is seconds parsed from Retry-After, else None; grpc_code is set only on the gRPC transport (NOT_FOUND, UNAUTHENTICATED, ...); path is the route the control namespaces called.

Three classes belong to two branches so that the 0.13 handlers below keep catching what they caught:

ClassAlso an instance ofEffect
PermissionDeniedError (403)AuthenticationErrorexcept AuthError still catches a 403
NotFoundError (404)BadRequestErrorexcept ValidationError still catches a 404
AlreadyExistsError (409)ConflictError and BadRequestErrorexcept ValidationError still catches a 409; catch AlreadyExistsError first to fall back to a get-by-name
UnavailableError (503)InternalServerErrorexcept ServerError still catches a 503 and it is retried like any 5xx

0.13 names

The 0.13 exception names are kept for the whole 1.x line. In Python they are plain aliases or the thin subclasses above:

0.13 name0.14Catches
AuthError= AuthenticationError401, and 403 through PermissionDeniedError
ValidationError= BadRequestError400/422, and 404, 409 through the subclasses
AlreadyExistsErrorsubclass of ConflictError and BadRequestError409
ServerError= InternalServerError500, any unlisted 5xx, and 503 through UnavailableError; not 410 or 429
TransportErrorsubclass of APIConnectionErrorconnection failures and gRPC transport codes; .code is UNAVAILABLE | DEADLINE_EXCEEDED | CONNECTION_RESET | IO | UNIMPLEMENTED | CONNECTION_ERROR | TIMEOUT, .refused is True for a refused connection or an unresolvable host
UnsupportedFeatureErrorunchanged501 and unknown operations

GoneError (410) and RateLimitError (429) are new classes with no 0.13 parent: code that only caught ServerError does not see them. Add except mubit.RateLimitError (retry after retry_after) and except mubit.GoneError (the route is gone; change the call) where that matters.

gRPC transport

Over transport="grpc" the same classes are raised from the canonical codes: UNAUTHENTICATEDAuthenticationError, PERMISSION_DENIEDPermissionDeniedError, NOT_FOUNDNotFoundError, ALREADY_EXISTSAlreadyExistsError, INVALID_ARGUMENT / FAILED_PRECONDITION / OUT_OF_RANGEBadRequestError, RESOURCE_EXHAUSTEDRateLimitError, INTERNAL / UNKNOWNInternalServerError. Transport-level codes stay TransportError: UNAVAILABLE, UNIMPLEMENTED, CANCELLED (as IO), a reset connection (CONNECTION_RESET), and DEADLINE_EXCEEDED (also an APITimeoutError). With transport="auto" the client falls back to HTTP for UNAVAILABLE, DEADLINE_EXCEEDED, CONNECTION_RESET, IO and UNIMPLEMENTED before raising.

Python

import time
import mubit
 
client = mubit.Client()
try:
    client.memory.remember("…", kind="fact", run_id=run_id, agent_id="support-agent")
except mubit.AuthenticationError:
    raise                                   # fix the API key; a 403 also lands here
except mubit.RateLimitError as e:
    time.sleep(e.retry_after or 1.0)        # the SDK already retried MUBIT_MAX_RETRIES times
except mubit.BadRequestError as e:
    log.error("bad call %s: %s", e.request_id, e.message)   # 400, 404, 409: fix the call
except mubit.APIConnectionError as e:
    if e.refused:                            # nothing listening: never retried
        raise
    ...                                     # timeout or reset: already retried; retry the outer job
except mubit.APIStatusError as e:
    log.error("HTTP %s on %s: %s", e.status_code, e.path, e.body)

The control namespaces (client.policy, client.kill, client.jobs, client.proposals, client.snapshots, client.audit, client.export) raise ControlError for a server rejection; its status property is the 0.13 name of status_code, so except mubit.ControlError as e: if e.status == 403 keeps working. client.policy.set(...) raises PolicyError for an unknown key or a bad value, with valid_keys listing the accepted keys. ControlUnavailable is raised for an unreachable control plane only under on_error="raise"; with "warn" the namespaces log once and return their empty value.

client.memory.remember(..., kind="lesson", lesson_condition=...) raises LessonRejected (a BadRequestError and a ValueError; .reason is the server text) when the lesson does not match the policy's distill.schema or its condition does not parse. It is raised under every on_error setting because the caller asked for the write.

The global helpers (mubit.context, mubit.outcome, mubit.remember, ...) do not raise for server or connection problems under the default on_error="warn"; they return a typed empty value whose reason is one of ok, nothing_to_record, uninitialized, disabled, degraded, legacy, killed, unauthorized, forbidden, unreachable. See SDK configuration → Failure policy.

JavaScript

The same classes are exported from @mubit-ai/sdk, in camelCase: statusCode (with a status alias), requestId, body, retryAfter, path, endpoint, and cause (the underlying fetch or transport error). APIConnectionError carries endpoint, path and code (CONNECTION_ERROR | TIMEOUT).

import { Client, AuthenticationError, RateLimitError, BadRequestError, APIConnectionError, APIStatusError } from "@mubit-ai/sdk";
 
const client = new Client();
try {
  await client.memory.remember("…", { kind: "fact", runId, agentId: "support-agent" });
} catch (err) {
  if (err instanceof AuthenticationError) throw err;                 // 401, and 403
  else if (err instanceof RateLimitError) await sleep((err.retryAfter ?? 1) * 1000);
  else if (err instanceof BadRequestError) console.error(err.requestId, err.message);   // 400, 404, 409
  else if (err instanceof APIConnectionError) { if (err.code === "CONNECTION_ERROR") throw err; }
  else if (err instanceof APIStatusError) console.error(err.statusCode, err.path, err.body);
}

The 0.13 names are subclasses whose instanceof is widened with Symbol.hasInstance, so an existing handler keeps matching what it matched before:

0.13 nameExtendsinstanceof is true for
ValidationErrorBadRequestErrorevery BadRequestError, NotFoundError (404) and ConflictError (409)
AuthErrorAuthenticationErrorevery AuthenticationError and PermissionDeniedError (403)
ServerErrorInternalServerErrorevery APIStatusError with statusCode >= 500 (so UnavailableError too); not GoneError (410) or RateLimitError (429)
AlreadyExistsErrorConflictError409
TransportErrorAPIConnectionErrortransport failures; code and refused as in Python
LessonRejectedValidationErrora rejected explicit lesson (reason)

ControlError (an APIStatusError), PolicyError (validKeys) and ControlUnavailable (an APIConnectionError; only with onError: "raise") mirror the Python control-plane classes. A 501 becomes UnsupportedFeatureError, which is a MubitError but not an APIStatusError.

The SDK already retries the retryable rows of the status table; see Retries and idempotency for the exact policy, the environment variables and the manual pattern.