> ## 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.

# Go SDK: errors and retries

> Handle *APIError in the Go SDK: status, code and message, errors.As and errors.Is, the SDK's own codes, the Code constants, retries, timeouts and rate limits.

Every API, network and local failure is one type: `*squarecloud.APIError`. Inspect it with `errors.As`.

The shorter snippets below run inside the program from [Running the examples](/en/sdks/go/client#running-the-examples): paste one at a time into `main` and run `goimports`.

```go theme={"system"}
package main

import (
	"context"
	"errors"
	"fmt"
	"os"

	"github.com/squarecloudofc/sdk-api-go/v3"
)

func main() {
	ctx := context.Background()
	c := squarecloud.New(os.Getenv("SQUARECLOUD_API_KEY"))
	appID := "abc123def456abc123def456"

	err := c.Apps.Start(ctx, appID)

	var apiErr *squarecloud.APIError
	if errors.As(err, &apiErr) {
		fmt.Println(apiErr.Status, apiErr.Code, apiErr.Message)
		fmt.Println(apiErr.Method, apiErr.Path) // "POST" "/v2/apps/<id>/start"
	}
}
```

## `APIError`

| Field | Type | Description |
| - | - | - |
| `Status` | `int` | HTTP status. `0` when no response arrived (network error, timeout, local check) |
| `Code` | `string` | The API's error code, e.g. `APP_NOT_FOUND`, or one of the SDK's codes. Compare it with the [`Code*` constants](#code-constants) |
| `Message` | `string` | The server's explanation. `""` when the server sent only a code |
| `Method` | `string` | HTTP method of the failed call |
| `Path` | `string` | URL path of the failed call, without the query string |

`Unwrap()` returns the cause (the transport, decoding or context error) for `NETWORK_ERROR`, `TIMEOUT` and invalid JSON, or `nil`. `Error()` renders `squarecloud: <METHOD> <path>: HTTP <status> <CODE>: <message>`, without `HTTP <status>` when the status is `0` and without `: <message>` when it is empty. Match on the fields, not on this text.

### Canceled and expired contexts

A call whose `ctx` is canceled returns an `*APIError` with `NETWORK_ERROR`, and one whose deadline passes returns `TIMEOUT`, both with status `0`. They unwrap to the context's error:

```go theme={"system"}
_, err := c.Apps.Get(ctx, appID)

switch {
case errors.Is(err, context.Canceled):
	// you canceled ctx
case errors.Is(err, context.DeadlineExceeded):
	// the deadline of ctx, or the default one, passed
}
```

A few failures are not `*APIError`:

* [`Realtime.Next`](/en/sdks/go/realtime#ending-the-stream) returns the bare `ctx.Err()` when its `ctx` ends, and `io.EOF` when the stream ends normally.
* Caller-side problems are plain errors: a `nil` upload reader, a snapshot URL or base URL that cannot be parsed, an input `encoding/json` cannot encode, and an error of the `io.Writer` passed to `DownloadSnapshot`.

## SDK codes

| Status | Code | Constant | When |
| - | - | - | - |
| 0 | `NETWORK_ERROR` | `CodeNetworkError` | No response (DNS, connection reset, body cut off), or `ctx` canceled. The original error is in `Unwrap()` |
| 0 | `TIMEOUT` | `CodeTimeout` | No response before the deadline (see [Timeouts](/en/sdks/go/client#timeouts)) |
| 0 | `FILE_TOO_LARGE` | `CodeFileTooLarge` | Local check: an upload over 100 MB or a `Files.Write` over 10 MB. Nothing was sent |
| 0 | `INVALID_ID` | `CodeInvalidID` | Local check: an id that is empty, `.` or `..`. Nothing was sent |
| 0 | `INVALID_API_KEY` | `CodeInvalidAPIKey` | Local check: the client was built with an empty key. Every call except `Service.Status`. Only in the Go SDK |
| any | `UNKNOWN_ERROR` | `CodeUnknown` | A response without a code (such as a proxy error page), with the real `Status` and the message `HTTP <status>`. A 2xx body that is not JSON has the message `Invalid JSON in HTTP <status> response` |

## `Code*` constants

`Code` is a plain `string`. The package has one constant per public API code, named after it in Go style (`CodeAppNotFound` for `APP_NOT_FOUND`, `CodeInvalidID`, `CodeDNSFailed`, ...), plus the SDK's own codes above. `CodeRateLimit` (`RATE_LIMIT`) and `CodeRateLimitExceeded` (`RATE_LIMIT_EXCEEDED`) are still exported, marked deprecated: the API now answers `RATE_LIMITED` (`CodeRateLimited`) for both.

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

```go theme={"system"}
func handle(err error) error {
	var apiErr *squarecloud.APIError
	if !errors.As(err, &apiErr) {
		return err // not from the SDK
	}

	switch apiErr.Code {
	case squarecloud.CodeAppNotFound, squarecloud.CodeContainerAlreadyStarted:
		return nil // expected: nothing to do
	}
	switch {
	case apiErr.Status == 429:
		log.Printf("rate limited, try again later: %v", apiErr)
	case apiErr.Status >= 500:
		log.Printf("Square Cloud had a server error: %v", apiErr)
	}
	return err
}
```

## 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/go/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`, ...), which `Code` carries verbatim. See [AI](/en/sdks/go/ai#errors).

## Retries

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

| Retried | Methods |
| - | - |
| `NETWORK_ERROR` | `GET` only (realtime opens and `DownloadSnapshot` before the response included) |
| 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. An upload is retried only when its body can be replayed: an `io.ReaderAt` with a known size, such as an `*os.File` (see [Commit and upload](/en/sdks/go/commit_and_upload#accepted-inputs)).

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 `WithMaxRetries(0)` to turn retries off.

## Timeouts

`WithTimeout` (30 s) applies only when `ctx` has no deadline, and one deadline covers the whole call, retries and backoff waits included. See [Timeouts](/en/sdks/go/client#timeouts) for the calls with a 2-minute floor and the calls with no default deadline. A deadline that passes returns `TIMEOUT` with status `0`, is never retried, and unwraps to `context.DeadlineExceeded`.

## 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>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.