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

# Python SDK: errors and retries

> Handle SquareCloudAPIError in squarecloud-api: status, code and message, the SDK's own error codes, retries, timeouts and rate limits.

Every API and network failure raises one exception: `SquareCloudAPIError`.

Examples use the `client` from [Creating the client](/en/sdks/py/client#creating-the-client). `app_id` is the id of one of your apps: [`client.account.me()`](/en/sdks/py/client#account) lists them.

```python theme={"system"}
from squarecloud import SquareCloudAPIError

try:
    client.apps.start(app_id)
except SquareCloudAPIError as error:
    print(error.status, error.code, error.message)
    print(error.method, error.path)  # "POST" "/v2/apps/<id>/start"
    print(error)  # POST /v2/apps/<id>/start: HTTP 404 APP_NOT_FOUND
```

## `SquareCloudAPIError`

`SquareCloudAPIError` is a subclass of `Exception`.

| Attribute | Type | Description |
| - | - | - |
| `status` | `int` | HTTP status. `0` when no response arrived (network error, timeout, local check) |
| `code` | `str` | The API's error code, e.g. `APP_NOT_FOUND`, or one of the SDK's codes |
| `message` | `str` | The server's explanation. `''` when the server sent only a code |
| `method` | `str` | HTTP method of the failed call |
| `path` | `str` | URL path of the failed call, without the query string |
| `cause` | `BaseException \| None` | The original exception, for `NETWORK_ERROR`, `TIMEOUT` and invalid JSON (the same as `__cause__`) |

`str(error)` is `<METHOD> <path>: HTTP <status> <CODE>: <message>`, without `HTTP <status>` when the status is `0` and without `: <message>` when the message is empty.

Two failures are not wrapped:

* An empty or whitespace-only API key raises a `ValueError` in the client constructor.
* Local file problems raise an `OSError`: an upload path that cannot be opened, a file that becomes unreadable mid-upload, or a `download_snapshot` destination that cannot be written.

## SDK codes

| Status | Code | When |
| - | - | - |
| 0 | `NETWORK_ERROR` | No response (DNS, connection reset, body cut off). The original exception is in `cause` |
| 0 | `TIMEOUT` | A socket operation exceeded the [timeout](/en/sdks/py/client#timeouts) |
| 0 | `FILE_TOO_LARGE` | Local check: an upload over 100 MB or a `files.write` over 10 MB. Nothing was sent |
| 0 | `INVALID_ID` | Local check: an id that is empty, `.` or `..`. Nothing was sent |
| any | `UNKNOWN_ERROR` | A response without a code (such as a proxy error page), with the real `status` and the server's message, or `HTTP <status>` when there is none. A 2xx body that is not JSON has the message `Invalid JSON in HTTP <status> response` |

## Codes are strings

`code` is a plain `str`: the SDK has no enum of codes. The docstring of `SquareCloudAPIError` lists every known API code. `RATE_LIMIT` and `RATE_LIMIT_EXCEEDED` are still listed there, marked deprecated: the API now answers `RATE_LIMITED` for both.

The list of API codes grows. **Handle an unknown code by its HTTP status**:

```python theme={"system"}
def start(app_id: str):
    try:
        client.apps.start(app_id)
    except SquareCloudAPIError as error:
        match error.code:
            case "APP_NOT_FOUND":
                return None
            case "CONTAINER_ALREADY_STARTED":
                return  # fine
            case _:
                if error.status == 429:
                    return retry_later()
                if error.status >= 500:
                    return report_outage(error)
                raise
```

## API error codes

Every other code comes from the API. The [API error reference](/en/api-reference/errors) lists each one with its HTTP status, meaning and fix, and each SDK page lists the codes its methods return most often. Any call can also answer 401 `ACCESS_DENIED` (a bad key), 403 `MISSING_SCOPE` or `RESOURCE_NOT_ALLOWED` (the [limits of the key](/en/sdks/py/client#api-key-and-scopes)), 429 (see [Rate limits](#rate-limits)) or 503 `DATABASE_UNAVAILABLE`.

`ai.chat()` errors use lowercase OpenAI codes instead (`access_denied`, `rate_limit_exceeded`, `server_overloaded`, ...). See [AI](/en/sdks/py/ai#errors).

## Retries

The SDK only retries what is safe to repeat, up to `max_retries` times (default `2`, so up to 3 attempts):

| Retried | Methods |
| - | - |
| `NETWORK_ERROR` | `GET` only |
| 503 `UPLOAD_BUSY` | Any method |
| 503 `ANALYTICS_BUSY` | Any method |
| 503 `DATABASE_UNAVAILABLE` | `GET` only |

It **never** retries:

* `TIMEOUT`;
* any **429**: `RATE_LIMITED` can be a block of about 30 minutes, and `KEEP_CALM` is not retried either;
* other 5xx;
* AI errors.

503 `DATABASE_UNAVAILABLE` can arrive after a mutation was already applied, so the SDK does not retry it outside `GET`. Retry your own idempotent mutations if you need to.

The wait before retry `n` (starting at 0) is `min(8 s, 500 ms · 2^n) · U(0.5, 1)`: exponential backoff with 50% to 100% jitter. Set `max_retries=0` to turn retries off.

## Timeouts

`timeout` (30 s) applies **per socket operation** (the connect and each read or write), not to the whole request. See [Timeouts](/en/sdks/py/client#timeouts) for the calls with a 120 s floor and the calls with no timeout. A timeout raises `TIMEOUT` with status `0` and is never retried.

## Rate limits

Every account has a limit of requests per 60 seconds, set by its plan ([values](/en/api-reference/limitations-and-restrictions)), and some routes have their own:

* **429 `RATE_LIMITED`**: the account or API key went over its request budget and is blocked for about 30 minutes, or an IP that keeps sending invalid keys is blocked for a short period. Also the limit of the network endpoints and `account.snapshots`.
* **429 `KEEP_CALM`**: too many calls to one route in a short time.

The SDK never retries a 429. Slow down, and wait before trying again.

## Next steps

<CardGroup cols={2}>
  <Card title="API error reference" icon="book" href="/en/api-reference/errors">
    Every error code, with its meaning and fix.
  </Card>

  <Card title="Limits and restrictions" icon="gauge" href="/en/api-reference/limitations-and-restrictions">
    The request limits of each plan.
  </Card>
</CardGroup>
