Errors
Error types the New Theory inference API emits, grouped by WebSocket close code.
Abnormal WebSocket closes carry a JSON payload (the error envelope) with six fields, except for the teardown described under Closed with no envelope.
{
"code": 4001,
"type": "auth.invalid_key",
"message": "API key rejected. The key was revoked, never issued, or malformed. Generate a new key in the New Theory console.",
"context": { "key_prefix": "nt_a1b2c3d4" },
"docs": "https://docs.newtheory.ai/api/errors",
"trace_id": "tr_a1b2c3d4"
}| Field | Type | Notes |
|---|---|---|
code | integer | WebSocket close code. One of 4001, 4403, 4400, 4404, 4422, 4500, 4503. |
type | string | Dot-namespaced subtype (domain.specific). Stable identifier for programmatic handling. |
message | string | Human-readable description. Do not parse; use type for branching. |
context | object | Structured diagnostics. Fields vary by type — see each entry below. |
docs | string (URI) | Link to this page's anchor for the error type. |
trace_id | string | Opaque support token (tr_xxxxxxxx). Include in support requests. |
In the Python SDK, every close triggers an exception that inherits from newt.NewTheoryError. Catch by class for domain-level handling, branch on exc.type for specific cases.
import newt
try:
robot.run("pick up the cup")
except newt.AuthError as e:
print(e.type, e.trace_id) # "auth.invalid_key", "tr_a1b2c3d4"
except newt.NewTheoryError as e:
print(e.code, e.type, e.message)Every error the server can emit is registered in a single-source catalog, and server startup validates coverage — a new error type can't ship without an entry.
Raised before connection
These conditions are detected by the SDK during Robot() construction, before any WebSocket is opened.
RegistryUnavailable
The registry service (nt-registry-production.up.railway.app) was unreachable when Robot() tried to resolve the model and contract. This is the only condition that raises RegistryUnavailable — it means the registry itself is down or the host cannot reach it, not a model or auth problem.
Next step: check your network, verify NT_BOOTSTRAP_URL is not set to a bad value (leave it unset to use the production registry), and retry. If the registry is down, wait and retry — it is an always-on service and outages are brief.
AuthError at construction
Your API key failed validation at the registry during Robot() construction — the registry returned a 401 with a hint that valid keys start with nt_. The connection closes before any WebSocket is opened.
Next step: re-export NT_API_KEY with the correct key (nt_ prefix + 40 hex chars), or generate a new key in the console.
ColdStartRetry warning
Not an error — a logging.WARNING the SDK emits when the GPU container did not respond within the initial connection timeout and the SDK is opening an extended retry (180s). This is the normal cold-start signal. Wait it out; a cold container can take a few minutes to come up. If the extended retry also times out, a TimeoutError is raised to your code.
Closed with no envelope
This failure closes the connection without a close frame or an error envelope.
Oversized observation frames
A single obs frame has a hard inbound size limit of 2,097,152 bytes (2 MiB) — state, every camera image, the prompt, and msgpack overhead counted together. Sending a frame above that size tears the connection down in transit; the frame never reaches the server, so nothing on our side records it.
With the newt SDK: the frame never leaves your machine. The SDK measures every packed frame and raises newt.FrameTooLargeError instead of sending it, at all three send sites — infer(), run(), and run(stream=True). The check runs on each frame of a run, not only the first, so a stream whose frames grow past the limit partway through raises on the frame that crosses. The message names the measured size, the limit, the frame's index in the run, and the per-camera image shapes that account for the bulk. exc.type is frame_too_large.exceeds_limit and exc.code is 4413, which is not a WebSocket close code — nothing was sent, so there is no close and trace_id is empty. exc.context carries frame_bytes, limit_bytes, frame_index, image_shapes, and model.
import newt
try:
robot.run("pick up the cup")
except newt.FrameTooLargeError as e:
print(e.context["frame_bytes"], e.context["limit_bytes"])
print(e.context["frame_index"], e.context["image_shapes"])With a client you write yourself: nothing measures the frame before it goes out, so the connection ends mid-run with no close frame. Python's websockets reports that as no close frame received or sent, with close code 1006. No envelope arrives, so there is no type and no trace_id to look up. Close code 1005 is a different signature — a close frame that arrived carrying no status.
What fits: the sizes below are measured msgpack frames, not pixel counts. Prompt text and camera key names move them by a few hundred bytes.
| Observation | Frame size |
|---|---|
| 2 cameras at 224×224 | about 301,000 bytes |
| 3 cameras at 224×224 | about 452,000 bytes |
| 14 cameras at 224×224 | about 2,108,000 bytes — over |
| 3 cameras at 640×480, not resized | about 2,765,000 bytes — over |
The models New Theory publishes declare image_shape: [3, 224, 224], where one camera image is 150,528 bytes. A run at that shape stays under the limit until the fourteenth camera.
The one real-hardware configuration on record is a three-camera RealSense rig at its resized client target, from a 2026-05-28 session. Packed as an observation frame, that configuration measures about 1.6 MB — under the limit, with roughly 23% to spare. This is a single measurement from one rig on that date; other hardware will differ.
Where you hit it: the SDK validates the first observation of a run against the model's contract before it opens the connection, so a wrong-sized image raises ContractMismatchError locally, naming the expected and received shapes. That preflight runs once per run. From there the server takes over: it checks every frame's image shape against the shape the stream opened with, and a mid-run change closes 4422 with a contract_mismatch.image_shape_changed envelope. Three paths get past both:
- A frame that grows past the size limit. The server's per-frame check only fires on a frame that arrives. If
read_state()starts returning larger arrays partway through a run — a camera that reconnects at its native resolution, a resize that runs only on the first read — you get the4422envelope while the larger frame still fits under the limit; once it doesn't, the SDK refuses the frame that crosses and a custom client gets the teardown described above. - Growth that isn't a shape change, after the first observation. The preflight catches an unknown camera key or a wrong state shape on the first frame. Nothing checks for them after that: the server reads only the cameras the model declares, and pads or truncates state to fit. An extra camera key, a longer prompt, or a longer state array adds bytes without changing a declared image shape, and surfaces only as the frame crossing the limit.
- Clients you write yourself. A custom client has no client-side validation. An oversized frame that would have come back as a
4422contract mismatch instead ends the connection with no envelope, because the frame never arrives to be validated.
Ruling it out: a teardown you get from the SDK is not an oversized frame — the size check runs before every send, so an over-limit frame raises FrameTooLargeError and never reaches the connection. For a client you write yourself, at least one other layer in the path tears down a connection the same way, so the symptom alone does not identify the cause. Start by measuring what you are sending:
sum(a.nbytes for a in obs["images"].values()) + obs["state"].nbytesThat undercounts the packed frame by a few hundred bytes. Above 2,097,152 bytes, this section is your answer.
Under it, size is not the cause. A 2026-08-06 check sent frames from 900,000 bytes up to the limit against the live API; every one reached the server and drew a response, and the first refusal came one byte over. A frame that comes in under the limit and still tears down is failing for a reason this page does not cover.
4001 — Authentication
Sent when the API key fails verification at the WebSocket handshake. The connection closes before any inference runs.
auth.invalid_key
SDK exception: newt.AuthError
The submitted key was revoked, never issued, or malformed.
{
"code": 4001,
"type": "auth.invalid_key",
"message": "API key rejected. The key was revoked, never issued, or malformed. Generate a new key in the New Theory console.",
"context": {
"key_prefix": "nt_a1b2c3d4"
},
"trace_id": "tr_a1b2c3d4"
}context.key_prefix is the first eight characters of the submitted key — enough for diagnostics without leaking the full value.
Next step: generate a new key in the console. If the key was just created, confirm it has not been revoked.
4403 — Authorization
Sent when the API key is valid but does not own the requested model. A fine-tune belongs to the team that made it — another team's key can't serve it. The connection closes before any inference runs.
auth.forbidden
SDK exception: newt.ForbiddenError
{
"code": 4403,
"type": "auth.forbidden",
"message": "That model belongs to another team. Your key can only serve models your team owns.",
"context": {
"model": "ft_6341c5_d13da9"
},
"trace_id": "tr_a1b2c3d4"
}context.model is the requested model identifier, as submitted.
Next step: use one of your own team's models, or ask the owning team to share. A model's owner is the team that fine-tuned it.
4400 — Protocol
Sent when a received frame cannot be decoded or is structurally invalid. The connection closes on the first bad frame. The Python SDK raises newt.ProtocolError for all 4400 subtypes.
protocol.malformed_msgpack
SDK exception: newt.ProtocolError
A binary WebSocket frame could not be decoded as msgpack.
{
"code": 4400,
"type": "protocol.malformed_msgpack",
"message": "The server received a WebSocket frame that could not be decoded as msgpack. Ensure every frame is msgpack-encoded binary.",
"context": {
"frame_bytes": 42
},
"trace_id": "tr_a1b2c3d4"
}context.frame_bytes is the size of the frame that failed to decode.
Next step: use msgpack.packb(obs, use_bin_type=True) to encode each frame. Text WebSocket frames are not accepted.
protocol.missing_type
SDK exception: newt.ProtocolError
A decoded msgpack frame did not contain the required type key.
{
"code": 4400,
"type": "protocol.missing_type",
"message": "The server received a msgpack frame with no 'type' key. Every frame must include a 'type' key set to 'obs' or 'stop'.",
"context": {
"keys_present": ["state", "images"]
},
"trace_id": "tr_a1b2c3d4"
}context.keys_present lists the keys the server found in the frame.
Next step: ensure every frame dict includes a "type" key before packing.
protocol.unknown_type
SDK exception: newt.ProtocolError
A decoded msgpack frame had a type value the server does not recognize.
{
"code": 4400,
"type": "protocol.unknown_type",
"message": "The server received a frame with an unrecognized type. Accepted frame types are: 'obs', 'stop'.",
"context": {
"received_type": "observation"
},
"trace_id": "tr_a1b2c3d4"
}context.received_type is the value the server received.
Next step: type must be exactly "obs" (to send an observation) or "stop" (to end the session).
4404 — Model not found
Sent when the model identifier in the first obs frame does not match any UID or tag in the server's registry.
model_not_found.unknown_identifier
SDK exception: newt.ModelNotFoundError
{
"code": 4404,
"type": "model_not_found.unknown_identifier",
"message": "Model not found. Check spelling or call newt.list_models() to see available models.",
"context": {
"requested": "so101-v2",
"known_uids": ["ft_base_molmoact2", "ft_6341c5_d13da9"],
"known_tags": ["so101"]
},
"trace_id": "tr_a1b2c3d4"
}context.requested is the identifier from the obs frame. context.known_uids and context.known_tags list what the registry contained at the time.
Next step: model identifiers are case-sensitive. Run newt.list_models() to see current UIDs and tags.
4422 — Contract mismatch
Sent when an obs frame's data does not match the model's trained input contract. Each subtype names the specific field that failed validation. The Python SDK raises newt.ContractMismatchError for all 4422 subtypes.
contract_mismatch.state_shape
SDK exception: newt.ContractMismatchError
The state array shape does not match the shape the model was trained on.
{
"code": 4422,
"type": "contract_mismatch.state_shape",
"message": "State shape mismatch. Adjust read_state() to return the expected shape, or switch to a model that matches your robot.",
"context": {
"model": "so101",
"expected_shape": [6],
"got_shape": [14]
},
"trace_id": "tr_a1b2c3d4"
}Next step: check the model's expected state shape via robot.contract. The contract is fixed at training time.
contract_mismatch.state_dtype
SDK exception: newt.ContractMismatchError
The state array dtype does not match the dtype the model was trained on.
{
"code": 4422,
"type": "contract_mismatch.state_dtype",
"message": "State dtype mismatch. Cast your state array to the expected dtype in read_state().",
"context": {
"model": "so101",
"expected_dtype": "float32",
"got_dtype": "float64"
},
"trace_id": "tr_a1b2c3d4"
}Next step: most models expect float32. Use state.astype(np.float32) in read_state().
contract_mismatch.camera_missing
SDK exception: newt.ContractMismatchError
A camera the model requires was absent from the obs frame's images dict.
{
"code": 4422,
"type": "contract_mismatch.camera_missing",
"message": "Required camera missing. Add the missing camera to the images dict in read_state(), or switch to a model that matches your hardware.",
"context": {
"model": "so101",
"missing_required_camera": "side",
"cameras_required": ["top", "side"],
"got_cameras": ["top"]
},
"trace_id": "tr_a1b2c3d4"
}context.missing_required_camera names the absent key. context.cameras_required lists all keys the model requires.
Next step: check cameras.required via robot.contract. Each required camera key must appear in the images dict.
contract_mismatch.image_shape
SDK exception: newt.ContractMismatchError
A camera image's shape does not match the shape the model expects.
{
"code": 4422,
"type": "contract_mismatch.image_shape",
"message": "Image shape mismatch. Resize the camera frame to the expected shape (CHW) in read_state().",
"context": {
"model": "so101",
"camera": "top",
"expected_shape": [3, 224, 224],
"got_shape": [3, 480, 640]
},
"trace_id": "tr_a1b2c3d4"
}Next step: images must be in CHW layout. Check the expected shape via robot.contract and resize on the client.
contract_mismatch.image_shape_changed
SDK exception: newt.ContractMismatchError
An image arrived whose shape differs from the shape this stream declared when it opened — a mid-run resolution change, a stacked (N, 3, H, W) buffer, or a collapsed 2-D frame. Distinct from contract_mismatch.image_shape, which gates the first frame against the model's contract; this one fires when a stream that already opened changes shape underneath itself.
{
"code": 4422,
"type": "contract_mismatch.image_shape_changed",
"message": "Image shape changed mid-stream. Every frame must match the shape this stream opened with — keep sending that shape, or close this stream and open a new one for the new shape.",
"context": {
"model": "so101",
"camera": "top",
"declared_shape": [3, 224, 224],
"got_shape": [3, 480, 640],
"obs_index": 47
},
"trace_id": "tr_a1b2c3d4"
}context.declared_shape is the shape this stream opened with; context.got_shape is what arrived. context.obs_index is the 1-based obs frame number the change arrived on.
Next step: keep sending the shape this stream opened with, or close this stream and open a new one for the new shape. A stacked buffer of N frames arriving as (N, 3, H, W) is rejected the same way — this API takes one frame per obs.
contract_mismatch.image_unusable
SDK exception: newt.ContractMismatchError
An image arrived that can't be read as uint8 pixel data at all. context.reason names which of three causes fired: not_array_like (not array-like), dtype (float pixel data, whose 0.0–1.0-vs-0–255 scale the dtype alone can't tell us), or value_range (an integer array with values outside 0-255, where the cast would wrap). An integer array already inside 0-255 is accepted and cast losslessly.
{
"code": 4422,
"type": "contract_mismatch.image_unusable",
"message": "Camera frame is not usable pixel data. Send a uint8 numpy array in CHW layout from read_state().",
"context": {
"model": "so101",
"camera": "top",
"reason": "dtype",
"got_dtype": "float32",
"declared_shape": [3, 224, 224],
"obs_index": 12
},
"trace_id": "tr_a1b2c3d4"
}context.reason discriminates the three causes. context.got_dtype is present for dtype and value_range; context.got_type is present for not_array_like.
Next step: cast with img.astype(np.uint8) after scaling to 0-255. Float images are rejected outright — a 0.0-1.0 frame and a 0.0-255.0 frame share the same dtype, and casting the first straight to uint8 produces a black picture.
contract_mismatch.camera_dropped
SDK exception: newt.ContractMismatchError
A camera that carried a frame when the stream opened is absent from a later obs frame. Distinct from contract_mismatch.camera_missing, which gates the first frame against the model's required cameras: opening a stream without a camera can be a supported, warned degraded mode, but dropping one after the stream is already relying on it is not — there is no declared substitution mid-stream.
{
"code": 4422,
"type": "contract_mismatch.camera_dropped",
"message": "A camera stopped mid-stream. Keep sending every camera this stream opened with, or close it and open a new one with the camera set you can supply.",
"context": {
"model": "so101",
"dropped_camera": "side",
"cameras_at_open": ["top", "side"],
"got_cameras": ["top"],
"obs_index": 83
},
"trace_id": "tr_a1b2c3d4"
}context.dropped_camera names the camera that went missing. context.cameras_at_open lists every camera the stream opened with; context.got_cameras lists what arrived in this frame.
Next step: check that your capture loop for that camera didn't error out. A camera you never intend to send should be absent from the first obs frame, where its absence is a declared, warned degraded mode — not dropped after the stream already opened with it.
4500 — Server error
Sent when the server encounters an unhandled exception, either inside the model call or in the WebSocket handler itself. The Python SDK raises newt.ServerError for all 4500 subtypes. Include exc.trace_id in any support request.
server.inference_error
SDK exception: newt.ServerError
The model raised an unhandled exception during policy.infer().
{
"code": 4500,
"type": "server.inference_error",
"message": "The model raised an error during inference. Retry the request. If this persists, contact support with the trace_id.",
"context": {
"error_type": "RuntimeError"
},
"trace_id": "tr_a1b2c3d4"
}context.error_type is the Python exception class name from the inference call.
Next step: retry the request. If this persists, contact support with the trace_id. This error may indicate an edge case in the input data.
In an earlier protocol revision, in-model errors emitted a terminal frame with stop_reason="error" and closed with code 1000. They now close 4500 with this envelope, so ServerError is raised rather than returning a RunResult with stop_reason="error".
server.internal
SDK exception: newt.ServerError
An unhandled exception occurred in the WebSocket handler outside the inference path.
{
"code": 4500,
"type": "server.internal",
"message": "The server encountered an internal error and closed the session. Retry the request. If this persists, contact support with the trace_id.",
"context": {
"error_type": "ValueError"
},
"trace_id": "tr_a1b2c3d4"
}Next step: retry the request. If this persists, contact support with the trace_id.
Embodiment
Raised by the SDK during Robot() construction when the value passed to embodiment= is invalid. This is a client-side check — no WebSocket is opened, and no server is involved. The SDK raises newt.EmbodimentError for all three subtypes; the code is 4422 by HTTP convention. See the SDK reference for the protocol an embodiment object must satisfy.
embodiment.string_not_object
SDK exception: newt.EmbodimentError
Raised when embodiment= receives a string. embodiment= takes your embodiment object, not a name string — the SDK never resolves names. This commonly happens when a developer expects a name-based API such as Robot(embodiment="so101").
{
"code": 4422,
"type": "embodiment.string_not_object",
"message": "Robot(embodiment=) takes your embodiment object, not a name string (got 'so101'). Generate one with a starter kit, or implement read_state() and execute() on any class.",
"context": { "got": "so101" }
}context.got is the string the SDK received.
Next step: pass an object that implements read_state() and execute(). Generate one with a starter kit, or write your own class — see Set up your embodiment.
embodiment.conflict
SDK exception: newt.EmbodimentError
Raised when embodiment= is combined with read_state= or execute=. The two paths are equivalent and mutually exclusive: embodiment= is convenience shorthand for passing the two callbacks separately.
{
"code": 4422,
"type": "embodiment.conflict",
"message": "Robot() received both embodiment= and read_state=. Pick one path: pass embodiment= (an object with read_state() and execute()), or pass read_state= and execute= as separate callbacks. The two paths are equivalent; embodiment= is convenience shorthand.",
"context": { "conflict_kwargs": ["read_state="] }
}context.conflict_kwargs lists the conflicting callback arguments the SDK saw.
Next step: pass embodiment= alone, or pass read_state= and execute= alone. Not both.
embodiment.missing_method
SDK exception: newt.EmbodimentError
Raised when the object passed as embodiment= is missing read_state(), execute(), or both. The message names every missing method.
{
"code": 4422,
"type": "embodiment.missing_method",
"message": "The object passed as embodiment= is missing: execute(). An embodiment must implement both read_state() -> dict and execute(action_chunk) -> None.",
"context": { "missing": ["execute()"], "got_type": "PartialRig" }
}context.missing lists the absent methods; context.got_type is the class name of the object that was passed.
Next step: implement both read_state() -> dict and execute(action_chunk) -> None on the class. Any object with those two methods is an embodiment — no inheritance or registration required.
4503 — Verifier unavailable
Sent when the key verification service is unreachable or returns an error during the WebSocket handshake. The connection closes before any inference runs.
verifier.unavailable
SDK exception: newt.VerifierError
{
"code": 4503,
"type": "verifier.unavailable",
"message": "The API key verification service is temporarily unavailable. Retry the request in a few seconds.",
"context": {
"error_type": "httpx.ConnectTimeout"
},
"trace_id": "tr_a1b2c3d4"
}context.error_type is the Python exception class from the verification call.
Next step: this is a server-side infrastructure issue, not a problem with your API key. Retry with exponential backoff.