> For clean Markdown of any page, append `.md` to the page URL.
> For a complete documentation index, see https://docs.sarvam.ai/llms.txt.
> For full documentation content in one file, see https://docs.sarvam.ai/llms-full.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.sarvam.ai/_mcp/server.

# Error handling on SageMaker

> How every self-hosted Sarvam endpoint reports errors on SageMaker: the 424 collapse through InvokeEndpoint, the shared error envelope, and the overload back-off rule for Saaras v3, Bulbul v3, and Sarvam Vision.

Every Sarvam model container on SageMaker uses the same error contract. Read this once, then see the per-model references — [Speech-to-Text](/api/self-hosted/sagemaker/api-saaras#errors), [Text-to-Speech](/api/self-hosted/sagemaker/api-bulbul#errors), and [Sarvam Vision](/api/self-hosted/sagemaker/api-vision#errors) — for the model-specific bodies.

## Every non-2xx is delivered as HTTP 424

Through `sagemaker-runtime:InvokeEndpoint`, any error the container returns is collapsed by boto3 into a **`ModelError` (HTTP 424)**. The container's real status is in **`OriginalStatusCode`**, its JSON body in **`OriginalMessage`**, and its `Retry-After` **header is dropped** — so retry hints ride in the **body** as `retry_after`.

Branch on `OriginalStatusCode` + `error.code`, **never** on the HTTP status (it is always `424`).

```python
from botocore.exceptions import ClientError
import json

try:
    r = client.invoke_endpoint(EndpointName=EP, ContentType=ct, Body=body)
except ClientError as e:
    status = e.response["OriginalStatusCode"]           # 400 / 429 / 503 / …
    err    = json.loads(e.response["OriginalMessage"])["error"]
    if status == 400:
        raise                                            # bad request — fix it, never retry
    elif err["code"] == "service_overloaded":
        backoff(err.get("retry_after", 1))               # 429 or 503 — back off with jitter
    else:
        retry_once()                                     # 5xx — retry once, then report err["request_id"]
```

## Error envelope

Every error body is JSON with the same shape:

```json
{ "error": { "code": "…", "message": "…", "request_id": "…" } }
```

Optional fields extend it: `detail_code` (a specific reason), `retryable` (bool), and `retry_after` (seconds). **Sarvam Vision** always carries `detail_code` and `retryable`.

## Overload (backpressure) sheds immediately — it does not queue

At capacity, the container rejects excess work at once rather than queueing it, using `error.code = "service_overloaded"` with a body `retry_after`:

| Model                      | `OriginalStatusCode` | `error.code`         | Retry                                |
| -------------------------- | :------------------: | -------------------- | ------------------------------------ |
| Speech-to-Text (Saaras v3) |         `429`        | `service_overloaded` | back off; body `retry_after` seconds |
| Text-to-Speech (Bulbul v3) |         `503`        | `service_overloaded` | back off; body `retry_after` seconds |
| Sarvam Vision (OCR)        |         `503`        | `service_overloaded` | back off; body `retry_after` seconds |

**Client rule:** treat **`429` or `503` with `code: "service_overloaded"`** as "retry after `retry_after` seconds, with jitter." Never retry a `400` — fix the request instead.