# 接口约定与错误处理

本文介绍平台开放接口遵循的统一规范，包括统一错误信封、错误码处理、游标分页、幂等控制、限流规则及数据格式。

## 处理接口错误信封与错误码

当 HTTP 状态码 ≥ 400 时，所有 `/open/v1` 与 `/app/v1` 接口均返回统一的嵌套错误信封（开发者平台接口 `/api/dev` 也用它，见 [错误码与请求 ID](/developers/guide/errors)）：

```json
{
  "error": {
    "code": "scope_required",
    "message": "this call needs the scope platform:ping",
    "requestId": "9f2c7a3b4e1d5f60",
    "docUrl": "https://dev.${DOMAIN}/docs/errors#scope_required",
    "details": {}
  }
}
```

- `code`：稳定的错误类型标识，必须以此字段编写错误分支逻辑。
- `message`：人类可读的错误提示，语言根据请求头 `Accept-Language` 适配。
- `requestId`：该次调用的唯一标识，与响应头 `X-Request-Id` 一致，用于报障排查。
- `docUrl`：指向该错误码文档的链接。
- `details`：包含结构化错误详情的对象；无额外信息时固定为 `{}`。

每个稳定错误码的含义与处理方式、开发者平台接口（`/api/dev`）的错误，以及怎样用 `requestId` 查到一次调用，见 [错误码与请求 ID](/developers/guide/errors)。`docUrl` 指向那一页上对应的错误码。

## 使用游标进行数据分页

列表接口通过游标（Cursor）进行分页控制。

支持的查询参数：
- `limit`：每页返回记录数，默认为 20，最大为 100。
- `cursor`：上一页返回的游标标识。第一页无需传递。

响应格式：

```json
{
  "data": [
    { "id": "row_1" },
    { "id": "row_2" }
  ],
  "nextCursor": "cur_9a8b7c6d5e"
}
```

当 `nextCursor` 不为 `null` 时，将其作为参数请求下一页；当为 `null` 时表示数据已遍历完毕：

```bash
curl -sS "https://api.${DOMAIN}/open/v1/tables/${TABLE_ID}/views/${VIEW_ID}/rows?limit=20&cursor=cur_9a8b7c6d5e" \
  -H "Authorization: Bearer ${ACCESS_TOKEN}"
```

## 使用幂等键防止重复操作

除 `/oauth/token` 外的所有 `POST` 创建与动作接口均支持幂等控制。

在请求头中传入 `Idempotency-Key`，长度为 1 至 255 个字符：

```bash
curl -sS -X POST "https://api.${DOMAIN}/open/v1/tables/${TABLE_ID}/views/${VIEW_ID}/rows" \
  -H "Authorization: Bearer ${ACCESS_TOKEN}" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: ${IDEMPOTENCY_KEY}" \
  -d '{"name": "New Item"}'
```

- 平台在 24 小时内记录同一应用与租户下的幂等键。
- 相同键且请求方法、路径、正文完全一致时，直接重放首次处理结果，并带有响应头 `Idempotent-Replayed: true`。
- 相同键但请求正文不一致时，返回 HTTP 409 与错误码 `idempotency_conflict`。
- 首个请求仍在处理时，返回 HTTP 409 与错误码 `idempotency_in_progress`。
- 状态码 ≥ 500 的失败请求不会被记录为幂等成功结果。

## 读取限流响应头

所有经过认证的调用都会返回限流状态响应头：

| 响应头 | 含义 |
|---|---|
| `X-Request-Id` | 每次调用的全局追踪 ID。 |
| `X-RateLimit-Limit` | 当前周期允许的最大请求次数。 |
| `X-RateLimit-Remaining` | 当前周期内剩余的请求次数。 |
| `X-RateLimit-Reset` | 限流配额重置倒计时（秒）或重置时间戳。 |
| `Raise-Environment` | 令牌所属环境，取值为 `test` 或 `live`。 |

触发限流时接口返回 HTTP 429 与错误码 `rate_limited`，同时附带 `Retry-After` 头表示需等待的秒数。

## 处理时间与金额字段

接口正文字段遵循统一的数据表现规范：

- **字段命名**：所有业务 JSON 字段命名均采用小驼峰（camelCase）。OAuth 规范端点除外（遵循 RFC 6749 下划线规范）。
- **时间格式**：时间字符串采用 RFC 3339 格式的 UTC 时间，例如 `"2026-10-28T08:30:00Z"`。
- **金额表示**：金额统一用最小货币单位的整数（如分）配合货币代码表示，禁止使用浮点数：

```json
{
  "amount": 5000,
  "currency": "CNY"
}
```

## 版本与已删除的地址

现在公开的地址只有两组，接口描述的版本号（`info.version`）都是 `1.2.0`：

- 开放接口 `https://api.${DOMAIN}/open/v1/…`；
- 应用接口 `/app/v1/…`，见 [App API](/developers/guide/app-api)。

应用在清单的 `platform` 里写它需要的平台 API 版本范围，见 [App API](/developers/guide/app-api) 的「兼容矩阵」。

`/app/v1` 与 `/open/v1` 的 1.x 目前是测试版（beta）：在第一个外部 ISV 的应用上架之前，平台可以不经弃用期修改或删除地址，改动写进变更日志。从那一天起，一个主版本之内不做不兼容的改动，弃用至少提前 12 个月通知。

目前没有处于弃用期的地址：平台不再给任何地址发 `Deprecation` 或 `Sunset` 头。已经删除的地址有两批：

- `/k3/app/…` 下的 12 个别名，2026-11-02 删除，见 [App API](/developers/guide/app-api) 的「旧路径」。
- 下表里的旧地址。它们在 2026-11-03 开始带 `Sunset` 头，写的停用日期是 2027-05-01；实际在 2026-11-04 就删除了，没有等到那一天。

## 旧地址已删除

下面这些旧地址已于 **2026-11-04** 删除，请求返回 404（`not_found`），不再带 `Deprecation`、`Sunset` 或 `Link` 头：

| 旧地址 | 改用 |
|---|---|
| `/k3/open/v1/…`（任何路由，包括 `api.${DOMAIN}` 上的） | `https://api.${DOMAIN}/open/v1/…`，路径的其余部分不变 |
| `POST /open/v1/token`（也包括 `POST /k3/open/v1/token`，HTTP Basic 换接入令牌） | `POST https://api.${DOMAIN}/open/v1/oauth/token`，`grant_type=client_credentials`，见[认证与凭证](/developers/guide/auth)。应答是 `access_token`、`token_type`、`expires_in`、`scope`、`tenant`、`environment` |
| 用请求体 `{"customer":{"phone":…}}` 换客户令牌 | `POST /open/v1/oauth/token`，`grant_type=urn:raise:grant-type:customer`，带 `phone`（有多个客户匹配时再带 `customer_id`） |
| `https://api.${DOMAIN}/open/v1/me/*`（客户接口） | `https://membership.apps.${DOMAIN}/open/v1/me/*`，只在会员应用的域名上应答，`api.` 上是 404 |

换令牌时客户端凭证有误的错误码是 `invalid_client`。
