> See the full index of documentation pages at https://docs.zettabyte.com/llms.txt.

# zSTUDIO API errors

> If a zSTUDIO API call failed, match the HTTP status or the text in the error message to its row to see what went wrong, whether a retry will help, and what to change; [Should I retry?](https://docs.zettabyte.com/docs/maas/errors#should-i-retry) answers the retry question for every error here.

Source: https://docs.zettabyte.com/docs/maas/errors

Last updated: 2026-10-10

zSTUDIO's model API gives you a choice of models through one API, and you pay per token. A call goes to zSTUDIO's API, which returns the reply from the model named in the request. Find an error by its HTTP status or by the literal text in its message: `Invalid token`, `insufficient_user_quota`, `Invalid URL` or `model_not_found`.

## Find your error

Look up the status or the text in the error, then follow the link to its fix.

The errors, their causes and what to do:

| Status and code | Message contains | Likely cause | SDK class | What to do | Details |
| --- | --- | --- | --- | --- | --- |
| `401`, `code` empty | `Invalid token` | No API key, or an invalid one | `AuthenticationError` | Change the request: send a valid key | [401 Invalid token](https://docs.zettabyte.com/docs/maas/errors#401-invalid-token) |
| `403`, `insufficient_user_quota` | The remaining balance, in Simplified Chinese | The account has no credit | `PermissionDeniedError` | Add credit: see [403 insufficient_user_quota](https://docs.zettabyte.com/docs/maas/errors#403-insufficient-user-quota) | [403 insufficient_user_quota](https://docs.zettabyte.com/docs/maas/errors#403-insufficient-user-quota) |
| `404`, `code` empty | `Invalid URL`, then the path called | A wrong path, or a base URL ending in `/chat/completions` | `NotFoundError` | Change the request: fix the path or the base URL | [404 Invalid URL](https://docs.zettabyte.com/docs/maas/errors#404-invalid-url) |
| `503`, `model_not_found` | See the `code` | The model ID isn't known | `InternalServerError`, after the SDK's retries | Change the request: change the model ID | [503 model_not_found](https://docs.zettabyte.com/docs/maas/errors#503-model-not-found) |
| `200`, no JSON error | An HTML page | A base URL missing `/v1` | None raised | Change the request: fix the base URL | [HTTP 200 with an HTML page](https://docs.zettabyte.com/docs/maas/errors#http-200-with-an-html-page) |

None of these errors clears on retry: each needs a change to the key, the credit, the model ID or the path.

## 401 Invalid token

**What it means:** the call reached the API without a valid key.

**What you'll see:** HTTP `401`, with the same body whether the key is missing or invalid. The `type` is `new_api_error`, the `code` is empty, and the message ends with the call's request ID. In the following body, and in each error body shown here, `REQUEST_ID` stands in for that ID:

```json
{"error":{"code":"","message":"Invalid token (request id: REQUEST_ID)","type":"new_api_error"}}
```

The OpenAI SDKs raise `AuthenticationError`.

**To fix it:** send a valid API key. [Quickstart: call zSTUDIO with the OpenAI SDK](https://docs.zettabyte.com/docs/maas/quickstart) shows how to set the key, and [API keys](https://docs.zettabyte.com/docs/inference-api/api-keys) shows how to create one.

## 403 insufficient_user_quota

**What it means:** the account has no credit. The key is valid, so its permissions aren't the problem.

**What you'll see:** HTTP `403` with the `code` `insufficient_user_quota`. The message is in Simplified Chinese and states the remaining balance, ending with the call's request ID:

```json
{"error":{"message":"用户额度不足, 剩余额度: ＄0.000000 (request id: REQUEST_ID)","type":"new_api_error","param":"","code":"insufficient_user_quota"}}
```

Translation of the message: "Insufficient user quota, remaining quota: $0.000000".

The OpenAI SDKs raise `PermissionDeniedError`.

**Why the names mislead:** the SDKs name every `403` as `PermissionDeniedError`, which reads as a problem with the key. And the `quota` in the `code` means the account's credit, not a rate limit.

**Before you act on it:** test the `code` as well as the status, since the status alone doesn't say it's credit.

**If your code was copied or migrated from OpenAI:** OpenAI returns a `429` when credit runs out, so error handling written for that `429` won't catch this error. See [Errors and limits, in the API overview](https://docs.zettabyte.com/docs/maas/overview#errors-and-limits).

**To fix it:**

- If you manage the account, open [Payment and Bill](https://app.zettabytecloud.com/revenue-bill-overview/list) in the console, where the credit is shown as **Wallet**, and add credit there.
- If you don't manage the account, calls fail until the account has credit.

## 404 Invalid URL

**What it means:** the request went to a path the API doesn't have. The message shows the path you called.

**What you'll see:** HTTP `404` with `type` `invalid_request_error` and an empty `code`, for a wrong path such as `/v1/chat/completion` (singular). The message names the path after `/inference` and carries no request ID:

```json
{"error":{"message":"Invalid URL (POST /v1/chat/completion)","type":"invalid_request_error","param":"","code":""}}
```

Both OpenAI SDKs raise `NotFoundError`.

**In SDK code:** SDK code can return this 404 too. Set `base_url` (Python) or `baseURL` (Node) to the base URL, not to a full endpoint: the SDK adds `/chat/completions` to it. A base URL that already ends in `/chat/completions` sends `/v1/chat/completions/chat/completions` and gets the same 404, with that path in the message.

**To fix it:**

- In SDK code, use a base URL that ends in `/v1`, as shown in [Base URL, in the API overview](https://docs.zettabyte.com/docs/maas/overview#base-url).
- For a path built by hand, replace it with the chat URL written in full in [Base URL, in the API overview](https://docs.zettabyte.com/docs/maas/overview#base-url).
- If an app or tool adds the path to an address that already ends in `/chat/completions`, the same 404 follows. See [Connect an app or tool to zSTUDIO](https://docs.zettabyte.com/docs/maas/connect-an-app-or-tool).

## 503 model_not_found

**What it means:** the model ID in the request isn't one the API knows. The request must change; the service isn't down.

**What you'll see:** HTTP `503` with the `code` `model_not_found` and `type` `new_api_error`, and a message that ends with the call's request ID. Recognise this error by its `code` rather than its status.

The OpenAI SDKs retry a 503 before raising `InternalServerError`, so the error reaches your code after those retries. A further retry won't clear it.

**To fix it:** change the model ID to one in [zSTUDIO models and model IDs](https://docs.zettabyte.com/docs/maas/models). See [Quickstart: call zSTUDIO with the OpenAI SDK](https://docs.zettabyte.com/docs/maas/quickstart) for finding the current list.

## HTTP 200 with an HTML page

**What it means:** a web page rather than JSON means the base URL is missing `/v1`.

**What you'll see:** an HTML page with HTTP 200, not a JSON error. The SDKs raise no error, so `except` and `catch` branches don't run. The code fails later, on the line that reads `choices`:

- Python: `AttributeError: 'str' object has no attribute 'choices'`
- Node: `TypeError: Cannot read properties of undefined (reading '0')`

**To fix it:** use the base URL in [Base URL, in the API overview](https://docs.zettabyte.com/docs/maas/overview#base-url).

## Request IDs

Each failed call carries a request ID you can log and quote.

The responses to the errors listed here, and the HTML page, carry a request ID in the `X-Oneapi-Request-Id` header. The handlers in the following section read it from there. The 401, 403 and 503 bodies also carry it inside the message, as `(request id: ...)`, so you can find it in a printed error. The 404 body doesn't.

A request ID your code sends in an `X-Client-Request-Id` or `X-Request-Id` header isn't returned in the 503 response, in a header or the body. The ID to quote is the one in `X-Oneapi-Request-Id`.

Include the request ID when you report a problem with a call.

## Should I retry?

Not for `401`, `403`, `404`, `503` `model_not_found`, or an HTML page with a `200`. Each needs a change to the key, the credit, the model ID or the path.

Both OpenAI SDKs retry connection errors, 408, 409, 429 and 5xx responses, twice by default. `max_retries` (Python) and `maxRetries` (Node) set this for the client or for one request, and `0` turns the retries off. Keep the default in production, where it also covers 429s and connection errors. For a one-off check that fails fast, set it to `0` on that one request.

## Handle these errors in code

Catch each error by its SDK class, then check the status and the `code` before acting.

The message text can change, so branch on the status and the `code`, never on the message. Test the `code` as well as the status before treating a 403 as no credit: the handlers treat a 403 with any other `code` as unexpected. In Python, `APIConnectionError` is raised when the client can't connect, separately from `APIStatusError`. In Node a connection error is also an `APIError`, so test for `APIConnectionError` before a general `APIError`. If `ZSTUDIO_API_KEY` is unset, the Node SDK sends `OPENAI_API_KEY` instead, so the Node handler checks the variable before creating the client.

Before you run a handler, install the OpenAI SDK and set `ZSTUDIO_API_KEY`, as shown in [Quickstart: call zSTUDIO with the OpenAI SDK](https://docs.zettabyte.com/docs/maas/quickstart). The Node handler uses top-level `await`, so save it as a `.mjs` file.

Replace `MODEL_ID` with the ID of the model you want to call.

The two handlers are the same handler in Python and in Node. Use the one for your language:

**Python**

```python
# handle_errors.py
import os

from openai import (
    APIConnectionError,
    APIStatusError,
    AuthenticationError,
    InternalServerError,
    NotFoundError,
    OpenAI,
    PermissionDeniedError,
    RateLimitError,
)

client = OpenAI(
    base_url="https://app.zettabytecloud.com/inference/v1",
    api_key=os.environ["ZSTUDIO_API_KEY"],
)


def details(err):
    request_id = err.response.headers.get("x-oneapi-request-id")
    print(f"Status: {err.status_code}, code: {err.code!r}, "
          f"request ID: {request_id}")


# The SDK retries connection errors, 408, 409, 429 and 5xx twice
# by default. Keep that default in production. For a one-off
# fail-fast check, set it to 0 on that one call:
# client.with_options(max_retries=0).chat.completions.create(...)
try:
    response = client.chat.completions.create(
        model="MODEL_ID",
        messages=[{"role": "user", "content": "Say hello."}],
    )
except AuthenticationError as err:
    print("401: send a valid API key.")
    details(err)
except PermissionDeniedError as err:
    if err.code == "insufficient_user_quota":
        print("403: the account has no credit. Add credit.")
    else:
        print("Unexpected 403.")
    details(err)
except NotFoundError as err:
    print("404: check that the base URL ends in /v1, "
          "not in /chat/completions.")
    details(err)
except InternalServerError as err:
    if err.code == "model_not_found":
        print("503: the model ID isn't known. Change MODEL_ID.")
    else:
        print("Unexpected 5xx.")
    details(err)
except APIConnectionError:
    print("Could not connect to the API.")
except RateLimitError as err:
    print("Rate limited (429) after the SDK's retries.")
    details(err)
except APIStatusError as err:
    print("Unexpected error.")
    details(err)
else:
    choices = getattr(response, "choices", None)
    if isinstance(choices, list) and choices:
        print(choices[0].message.content)
    else:
        print("The response was not a chat completion. "
              "Check that the base URL ends in /v1.")
```

**Node**

```javascript
// handle_errors.mjs
import OpenAI from "openai";

// Without this check, the SDK sends OPENAI_API_KEY instead.
const apiKey = process.env.ZSTUDIO_API_KEY;
if (!apiKey) throw new Error("ZSTUDIO_API_KEY is not set.");

const client = new OpenAI({
  baseURL: "https://app.zettabytecloud.com/inference/v1",
  apiKey,
});

function details(err) {
  const h = err.headers;
  const requestId =
    typeof h?.get === "function"
      ? h.get("x-oneapi-request-id")
      : h?.["x-oneapi-request-id"];
  console.log(
    `Status: ${err.status}, code: ${JSON.stringify(err.code)}, ` +
      `request ID: ${requestId}`,
  );
}

// The SDK retries connection errors, 408, 409, 429 and 5xx twice
// by default. Keep that default in production. For a one-off
// fail-fast check, set it to 0 on that one call:
// client.chat.completions.create({ ... }, { maxRetries: 0 })
try {
  const response = await client.chat.completions.create({
    model: "MODEL_ID",
    messages: [{ role: "user", content: "Say hello." }],
  });
  const choices = response?.choices;
  if (Array.isArray(choices) && choices.length > 0) {
    console.log(choices[0].message.content);
  } else {
    console.log(
      "The response was not a chat completion. " +
        "Check that the base URL ends in /v1.",
    );
  }
} catch (err) {
  if (err instanceof OpenAI.AuthenticationError) {
    console.log("401: send a valid API key.");
    details(err);
  } else if (err instanceof OpenAI.PermissionDeniedError) {
    if (err.code === "insufficient_user_quota") {
      console.log("403: the account has no credit. Add credit.");
    } else {
      console.log("Unexpected 403.");
    }
    details(err);
  } else if (err instanceof OpenAI.NotFoundError) {
    console.log(
      "404: check that the base URL ends in /v1, " +
        "not in /chat/completions.",
    );
    details(err);
  } else if (err instanceof OpenAI.InternalServerError) {
    if (err.code === "model_not_found") {
      console.log("503: the model ID isn't known. Change MODEL_ID.");
    } else {
      console.log("Unexpected 5xx.");
    }
    details(err);
  } else if (err instanceof OpenAI.APIConnectionError) {
    console.log("Could not connect to the API.");
  } else if (err instanceof OpenAI.RateLimitError) {
    console.log("Rate limited (429) after the SDK's retries.");
    details(err);
  } else if (err instanceof OpenAI.APIError) {
    console.log("Unexpected error.");
    details(err);
  } else {
    throw err;
  }
}
```

## Next steps

Fixed your error? Go back to [Quickstart: call zSTUDIO with the OpenAI SDK](https://docs.zettabyte.com/docs/maas/quickstart) and try the call again.
