> ## 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 中处理 *APIError：状态、代码和消息，errors.As 和 errors.Is，SDK 自身的代码，Code 常量，以及重试、超时和速率限制。

所有 API 失败、网络失败和本地失败都是同一个类型：`*squarecloud.APIError`。请使用 `errors.As` 检查它。

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

| 字段        | 类型       | 说明                                                                         |
| --------- | -------- | -------------------------------------------------------------------------- |
| `Status`  | `int`    | HTTP 状态。没有收到响应时为 `0`（网络错误、超时、本地检查）                                         |
| `Code`    | `string` | API 的错误代码，例如 `APP_NOT_FOUND`，或 SDK 自身的代码之一。请将其与 [`Code*` 常量](#code-常量)进行比较 |
| `Message` | `string` | 服务器给出的说明。当服务器只发送了代码时为 `""`                                                 |
| `Method`  | `string` | 失败调用的 HTTP 方法                                                              |
| `Path`    | `string` | 失败调用的 URL 路径，不含查询字符串                                                       |

对于 `NETWORK_ERROR`、`TIMEOUT` 和无效 JSON，`Unwrap()` 返回其原因（传输、解码或 context 错误），否则返回 `nil`。`Error()` 输出 `squarecloud: <METHOD> <path>: HTTP <status> <CODE>: <message>`，状态为 `0` 时省略 `HTTP <status>`，消息为空时省略 `: <message>`。请根据字段进行匹配，而不是依赖这段文本。

### 被取消和已到期的 context

`ctx` 被取消的调用会返回代码为 `NETWORK_ERROR` 的 `*APIError`，截止时间已过的调用会返回 `TIMEOUT`，两者的状态均为 `0`。它们可以解包为 context 的错误：

```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
}
```

少数失败不是 `*APIError`：

* 当 `ctx` 结束时，[`Realtime.Next`](/zh/sdks/go/realtime#结束流) 直接返回 `ctx.Err()`；当流正常结束时，返回 `io.EOF`。
* 调用方的问题是普通错误：`nil` 的上传 reader、无法解析的 snapshot URL 或基础 URL、`encoding/json` 无法编码的输入，以及传给 `DownloadSnapshot` 的 `io.Writer` 返回的错误。

## SDK 代码

| 状态 | 代码                | 常量                  | 触发条件                                                                                                                 |
| -- | ----------------- | ------------------- | -------------------------------------------------------------------------------------------------------------------- |
| 0  | `NETWORK_ERROR`   | `CodeNetworkError`  | 没有响应（DNS、连接重置、响应体被截断），或 `ctx` 被取消。原始错误可通过 `Unwrap()` 获取                                                              |
| 0  | `TIMEOUT`         | `CodeTimeout`       | 在截止时间之前没有响应（参见[超时](/zh/sdks/go/client#超时)）                                                                           |
| 0  | `FILE_TOO_LARGE`  | `CodeFileTooLarge`  | 本地检查：上传超过 100 MB 或 `Files.Write` 超过 10 MB。未发送任何内容                                                                    |
| 0  | `INVALID_ID`      | `CodeInvalidID`     | 本地检查：ID 为空、`.` 或 `..`。未发送任何内容                                                                                        |
| 0  | `INVALID_API_KEY` | `CodeInvalidAPIKey` | 本地检查：客户端是使用空密钥构建的。适用于除 `Service.Status` 外的每个调用。仅存在于 Go SDK 中                                                         |
| 任意 | `UNKNOWN_ERROR`   | `CodeUnknown`       | 没有代码的响应（例如代理的错误页面），带有真实的 `Status` 和消息 `HTTP <status>`。不是 JSON 的 2xx 响应体的消息为 `Invalid JSON in HTTP <status> response` |

## `Code*` 常量

`Code` 是一个普通的 `string`。该包为每个公开的 API 代码提供一个常量，以 Go 风格命名（`APP_NOT_FOUND` 对应 `CodeAppNotFound`，以及 `CodeInvalidID`、`CodeDNSFailed`……），外加上面列出的 SDK 自身的代码。

API 代码的列表会不断增加。**请根据 HTTP 状态处理未知代码**：

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

	switch apiErr.Code {
	case squarecloud.CodeAppNotFound:
		return nil
	case squarecloud.CodeContainerAlreadyStarted:
		return nil // fine
	}
	switch {
	case apiErr.Status == 429:
		return retryLater()
	case apiErr.Status >= 500:
		return reportOutage(apiErr)
	}
	return err
}
```

## 任何调用都可能出现的错误

| 状态  | 代码                      | 触发条件                                                               |
| --- | ----------------------- | ------------------------------------------------------------------ |
| 401 | `ACCESS_DENIED`         | API 密钥缺失、未知、已撤销或已过期                                                |
| 403 | `MISSING_SCOPE`         | 该密钥缺少此调用所需的作用域。`Message` 会指明是哪一个                                   |
| 403 | `RESOURCE_NOT_ALLOWED`  | 该密钥被限制为其他应用或数据库                                                    |
| 403 | `PERMISSION_DENIED`     | 你的 workspace 角色不允许该操作                                              |
| 404 | `ROUTE_NOT_FOUND`       | 未知路由                                                               |
| 429 | `RATE_LIMITED`          | 账户、密钥或 IP 被封锁（可能持续约 30 分钟），以及网络 endpoint 和 `Account.Snapshots` 的限制 |
| 429 | `KEEP_CALM`             | 对该路由的请求过快                                                          |
| 500 | `INTERNAL_SERVER_ERROR` | 服务器错误                                                              |
| 503 | `DATABASE_UNAVAILABLE`  | 平台数据库不可用。变更操作可能已经被应用                                               |

## 按分组列出的 API 代码

<AccordionGroup>
  <Accordion title="未找到">
    `APP_NOT_FOUND`, `DATABASE_NOT_FOUND`, `WORKSPACE_NOT_FOUND`, `MEMBER_NOT_FOUND`, `FILE_NOT_FOUND`, `SNAPSHOT_NOT_FOUND`, `REPOSITORY_NOT_FOUND`, `BRANCH_NOT_FOUND`, `ROUTE_NOT_FOUND`
  </Accordion>

  <Accordion title="验证">
    `INVALID_ACCESS_TOKEN`, `INVALID_AUTORESTART`, `INVALID_BRANCH_LENGTH`, `INVALID_CODE`, `INVALID_CONTENT`, `INVALID_CONTENT_TYPE`, `INVALID_DATABASE_TYPE`, `INVALID_DATABASE_VERSION`, `INVALID_DESCRIPTION`, `INVALID_DISPLAY_NAME`, `INVALID_DOMAIN`, `INVALID_ENCODING`, `INVALID_ENV_CONTENT`, `INVALID_FILE`, `INVALID_FILENAME`, `INVALID_FILTER`, `INVALID_GROUP`, `INVALID_ID`, `INVALID_INPUT`, `INVALID_JSON_BODY`, `INVALID_MEMORY`, `INVALID_NAME`, `INVALID_PARAMETERS`, `INVALID_PATH`, `INVALID_RESET_TYPE`, `INVALID_SCOPE`, `INVALID_SNAPSHOT_ID`, `INVALID_SUBDOMAIN`, `INVALID_TIME_RANGE`, `INVALID_VERSION_ID`, `MISSING_PARAMETERS`, `MISSING_REQUIRED_FIELDS`, `NO_UPDATE_DATA`, `VALIDATION_FAILED`, `VALIDATION_TIMEOUT`, `ENV_NAME_TOO_LONG`, `ENV_CONTENT_TOO_LONG`, `TOO_MANY_ENV_VARS`, `RESERVED_DOMAIN`, `CANNOT_SET_SUBDOMAIN`, `STATIC_APP_ENV_NOT_SUPPORTED`
  </Accordion>

  <Accordion title="身份验证和权限">
    `ACCESS_DENIED`, `MISSING_SCOPE`, `RESOURCE_NOT_ALLOWED`, `PERMISSION_DENIED`, `SCOPE_NOT_GRANTABLE`, `BLOCKED_PATH`, `UPGRADE_REQUIRED`
  </Accordion>

  <Accordion title="限制和速率限制">
    `RATE_LIMITED`, `KEEP_CALM`, `APPLICATIONS_LIMIT_REACHED`, `WORKSPACE_LIMIT_REACHED`, `MEMBERS_LIMIT_REACHED`, `LOAD_BALANCER_LIMIT_REACHED`, `DAILY_SNAPSHOTS_LIMIT_REACHED`, `INSUFFICIENT_MEMORY`, `FILE_TOO_LARGE`, `PAYLOAD_TOO_LARGE`, `REALTIME_MAX_CONNECTIONS`, `REALTIME_MAX_CONNECTIONS_APP`, `AI_DAILY_LIMIT_REACHED`, `AI_MAX_CONCURRENT_STREAMS`, `AI_NO_PLAN_LIMIT_REACHED`
  </Accordion>

  <Accordion title="容器（启动、停止、重启）">
    `CONTAINER_ALREADY_STARTED`, `CONTAINER_ALREADY_STOPPED`, `CONTAINER_TEMPORARILY_SUSPENDED`, `CONTAINER_NOT_FOUND`, `CONTAINER_INSUFFICIENT_DISK_SPACE`, `CONTAINER_NETWORK_CONFLICT`, `ACTION_FAILED`, `DATABASE_NOT_RUNNING`
  </Accordion>

  <Accordion title="上传、文件和 commit">
    `UPLOAD_BUSY`, `UPLOAD_FAILED`, `UPLOAD_ABORTED`, `STORAGE_UPLOAD_FAILED`, `COMMIT_FAILED`, `READ_FAILED`, `SAVE_FAILED`, `RENAME_FAILED`, `DELETE_FAILED`, `REQUEST_ABORTED`, `EMPTY_RESPONSE`
  </Accordion>

  <Accordion title="Snapshots">
    `SNAPSHOT_FAILED`, `SNAPSHOT_PROCESSING`, `SNAPSHOT_RESTORE_FAILED`, `SNAPSHOT_DATABASE_MISMATCH`, `RESTORE_IN_PROGRESS`
  </Accordion>

  <Accordion title="Deploy 和 GitHub">
    `GIT_ALREADY_CONFIGURED`, `GIT_NOT_CONFIGURED`, `GITHUB_NOT_CONNECTED`, `REPOSITORY_BRANCH_ALREADY_CONFIGURED`, `REPOSITORY_NOT_AVAILABLE`, `REPOSITORY_PERMISSION_REQUIRED`, `FAILED_TO_FETCH`
  </Accordion>

  <Accordion title="网络和域名">
    `ANALYTICS_BUSY`, `UNABLE_TO_FETCH_ANALYTICS`, `UNABLE_TO_FETCH_ERRORS`, `UNABLE_TO_FETCH_PERFORMANCE`, `DNS_FAILED`, `DOMAIN_ALREADY_EXISTS`, `NO_CUSTOM_DOMAIN`, `PURGE_CACHE_FAILED`, `LOGS_UNAVAILABLE`, `METRICS_NOT_SUPPORTED`
  </Accordion>

  <Accordion title="数据库和 workspace">
    `DATABASE_CREATION_FAILED`, `DATABASE_UNAVAILABLE`, `RESET_FAILED`, `WORKSPACE_CREATION_FAILED`, `APP_ALREADY_IN_WORKSPACE`, `MEMBER_ALREADY_ADDED`, `CANNOT_EDIT_OWNER`, `CANNOT_INVITE_OWNER`, `CANNOT_LEAVE_OWNER`, `CONFLICTING_RESOURCES`
  </Accordion>

  <Accordion title="平台">
    `INTERNAL_SERVER_ERROR`, `CLUSTER_MAINTENANCE_TRY_LATER`, `CLUSTER_SELECTION_FAILED`, `CLUSTER_TIMEOUT`, `CLUSTER_UNAVAILABLE`, `AI_UNAVAILABLE`
  </Accordion>

  <Accordion title="已弃用">
    `CodeRateLimit`（`RATE_LIMIT`）和 `CodeRateLimitExceeded`（`RATE_LIMIT_EXCEEDED`）仍然导出，并被标记为已弃用：API 现在对这两者都返回 `RATE_LIMITED`（`CodeRateLimited`）。
  </Accordion>
</AccordionGroup>

`AI.Chat` 的错误则使用小写的 OpenAI 代码（`access_denied`、`rate_limit_exceeded`、`server_overloaded`……），`Code` 会原样携带这些代码。参见 [AI](/zh/sdks/go/ai#错误)。

## 重试

SDK 只重试可以安全重复的操作，最多 `WithMaxRetries` 次（默认 `2`，即最多 3 次尝试）：

| 重试的情况                      | 方法                                                  |
| -------------------------- | --------------------------------------------------- |
| `NETWORK_ERROR`            | 仅 `GET`（包括实时连接的建立，以及 `DownloadSnapshot` 在收到响应之前的阶段） |
| 503 `UPLOAD_BUSY`          | 任意方法                                                |
| 503 `ANALYTICS_BUSY`       | 任意方法                                                |
| 503 `DATABASE_UNAVAILABLE` | 仅 `GET`                                             |

它**从不**重试：

* `TIMEOUT`；
* 任何 **429**：`RATE_LIMITED` 可能是持续约 30 分钟的封锁，`KEEP_CALM` 也不会重试；
* 其他 5xx；
* AI 错误。

503 `DATABASE_UNAVAILABLE` 可能在变更操作已被应用之后才到达，因此 SDK 不会在 `GET` 以外重试它。如有需要，请自行重试你的幂等变更操作。只有当上传的请求体可以重放时才会重试：即大小已知的 `io.ReaderAt`，例如 `*os.File`（参见 [Commit 与上传](/zh/sdks/go/commit_and_upload#接受的输入)）。

第 `n` 次重试（从 0 开始）之前的等待时间为 `min(8 s, 500 ms · 2^n) · U(0.5, 1)`：带有 50% 到 100% 抖动的指数退避。设置 `WithMaxRetries(0)` 可关闭重试。

## 超时

`WithTimeout`（30 秒）仅在 `ctx` 没有截止时间时生效，并且一个截止时间覆盖整个调用，包括重试和退避等待。关于具有 2 分钟下限的调用以及没有默认截止时间的调用，请参见[超时](/zh/sdks/go/client#超时)。截止时间已过会返回状态为 `0` 的 `TIMEOUT`，从不重试，并可解包为 `context.DeadlineExceeded`。

## 速率限制

每个账户都有每 60 秒的请求数限制，由其套餐决定（[具体数值](/zh/api-reference/limitations-and-restrictions)），部分路由还有各自的限制：

* **429 `RATE_LIMITED`**：账户、API 密钥或 IP 被封锁，可能持续约 30 分钟。也是网络 endpoint 和 `Account.Snapshots` 的限制。
* **429 `KEEP_CALM`**：短时间内对同一路由的调用过多。

SDK 从不重试 429。请放慢速度，并在再次尝试之前等待。
