> ## Documentation Index
> Fetch the complete documentation index at: https://developers.meshapi.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Error Handling

> Catch typed API errors, read error codes, and configure automatic retries.

All SDKs raise or throw a typed exception class when the API returns an error. The exception carries the HTTP status, a machine-readable error code, the request ID, and provider-specific details where applicable.

## Catching errors

<Tabs>
  <Tab title="Python">
    ```python theme={null}
    from meshapi import MeshAPI, MeshAPIError, ChatCompletionParams, ChatMessage

    client = MeshAPI(base_url="https://api.meshapi.ai", token="rsk_...")

    try:
        resp = client.chat.completions.create(
            ChatCompletionParams(
                model="openai/gpt-4o-mini",
                messages=[ChatMessage(role="user", content="hello")],
            )
        )
    except MeshAPIError as e:
        print(f"[{e.status}] {e.error_code}: {e}")
        print("Request ID:", e.request_id)

        if e.error_code == "rate_limit_exceeded":
            print(f"Retry after {e.retry_after_seconds}s")
        elif e.error_code == "spend_limit_exceeded":
            print("Balance exhausted — top up to continue.")
        elif e.error_code == "unauthorized":
            print("Invalid API key.")
    ```

    Mid-stream errors raise the same `MeshAPIError` from inside the iterator.
  </Tab>

  <Tab title="Node.js">
    ```javascript theme={null}
    import { MeshAPI, MeshAPIApiError } from "meshapi-node-sdk";

    const client = new MeshAPI({ baseUrl: "https://api.meshapi.ai", token: "rsk_..." });

    try {
      const resp = await client.chat.completions.create({ ... });
    } catch (err) {
      if (err instanceof MeshAPIApiError) {
        console.error(`[${err.status}] ${err.errorCode}: ${err.message}`);
        console.error("Request ID:", err.requestId);

        switch (err.errorCode) {
          case "rate_limit_exceeded":
            console.log(`Retry after ${err.retryAfterSeconds}s`);
            break;
          case "spend_limit_exceeded":
            console.log("Spend cap reached — add credits.");
            break;
          case "unauthorized":
            console.log("Invalid API key.");
            break;
        }
      } else {
        throw err; // Network error, AbortError, etc.
      }
    }
    ```

    For streaming, mid-stream errors are also thrown as `MeshAPIApiError` inside the `for await` loop.
  </Tab>

  <Tab title="Go">
    ```go theme={null}
    import "errors"

    resp, err := client.Chat.Completions.Create(ctx, params)
    if err != nil {
        var svcErr *meshapi.MeshAPIError
        if errors.As(err, &svcErr) {
            fmt.Printf("[%d] %s: %s\n", svcErr.Status, svcErr.Code, svcErr.Message)
            fmt.Println("Request ID:", svcErr.RequestID)

            if svcErr.Code == "rate_limit_exceeded" {
                fmt.Printf("Retry after %v seconds\n", svcErr.RetryAfterSeconds)
            }
        } else {
            // Network or context error
            log.Fatal(err)
        }
    }
    ```
  </Tab>
</Tabs>

## Error field names by language

| Field       | Python                  | Node.js                 | Go                         |
| ----------- | ----------------------- | ----------------------- | -------------------------- |
| HTTP status | `e.status`              | `err.status`            | `svcErr.Status`            |
| Error code  | `e.error_code`          | `err.errorCode`         | `svcErr.Code`              |
| Message     | `str(e)`                | `err.message`           | `svcErr.Message`           |
| Request ID  | `e.request_id`          | `err.requestId`         | `svcErr.RequestID`         |
| Retry after | `e.retry_after_seconds` | `err.retryAfterSeconds` | `svcErr.RetryAfterSeconds` |

## Error codes

| Code                            | HTTP    | Meaning                                |
| ------------------------------- | ------- | -------------------------------------- |
| `unauthorized`                  | 401     | Invalid or missing API key             |
| `forbidden`                     | 403     | Key suspended or access denied         |
| `not_found` / `model_not_found` | 404     | Resource or model not found            |
| `spend_limit_exceeded`          | 402     | Per-key spend cap reached              |
| `validation_error`              | 422     | Request body failed validation         |
| `rate_limit_exceeded`           | 429     | RPM or RPD limit hit                   |
| `upstream_error`                | 502/503 | Provider or server error               |
| `stream_interrupted`            | —       | Connection dropped mid-stream (Python) |

## Automatic retries

Non-streaming `create()` requests are retried automatically on `429`, `502`, `503`, and `504` with exponential backoff. The `Retry-After` header is respected on 429 responses.

**Streams never retry automatically.** If a stream drops mid-response, the client raises an error and you need to restart the request manually. See [Streaming — Stream recovery](/sdk/streaming).

<Info>
  `max_retries` is not the only knob. The full retry policy — status codes, backoff
  bounds, `Retry-After` handling, network-error retry — plus a client-side **model
  fallback chain** and retry/fallback logging are covered in
  [Resilience](/sdk/resilience).
</Info>

### Configuring retries (Python)

```python theme={null}
client = MeshAPI(
    base_url="https://api.meshapi.ai",
    token="rsk_...",
    max_retries=5,   # default: 3 — set to 0 to disable
    timeout=30.0,    # seconds
)
```
