错误
Sursa 用 HTTP 状态码表示请求是否成功,用稳定的错误码说明失败的原因。所有错误响应的结构都相同:
{
"detail": "the cursor is malformed or was issued for different filters",
"error": {
"code": "api.cursor_invalid",
"message": "the cursor is malformed or was issued for different filters",
"details": {"parameter": "cursor"},
"request_id": "01JB7Q2M4X9V3K8T5N6P0R1S2A",
"doc_url": "https://developers.sursa.com/errors/api.cursor_invalid"
}
}
| 字段 | 含义 |
|---|---|
error.code | 稳定的错误码,形式为 <area>.<reason>。代码应根据它判断分支。 |
error.message | 便于阅读的英文说明。措辞随时可能调整,请勿解析。 |
error.details | 有关错误的结构化信息,例如参数、限额或何时重试。字段取决于错误码。 |
error.request_id | 本次请求的 ID,与响应头 X-Request-Id 相同。 |
error.doc_url | 本门户上介绍该错误码的页面。 |
detail | 旧版的错误形式,为现有集成保留。新代码应读取 error。 |
每个错误码在错误码下都有单独的页面,列出 HTTP 状态、details 的字段和返回该错误码的接口。API 参考中每个接口的页面列出该接口可能返回的错误码。
HTTP 状态
| 状态 | 含义 |
|---|---|
200、201、202、204 | 请求成功。202 表示请求已被接受,仍在处理中。 |
400 | 请求格式有误:参数、游标、metadata 或 Idempotency-Key 不正确。请修正请求。 |
401 | 没有有效的密钥。参见身份验证。 |
402 | 已达到组织套餐的某项限额,例如卡片数量或存储空间。 |
403 | 密钥有效,但不允许执行此操作:缺少角色、权限范围或套餐功能。 |
404 | 对象不存在,或密钥无权查看。 |
409 | 请求与当前状态冲突:external_id 重复、同步尚未关闭、具有相同 Idempotency-Key 的请求仍在执行。 |
410 | 对象已过期,例如超过 24 小时的上传,或该路由已停用。 |
413 | 正文或文件太大。 |
415 | 不支持该文件类型,或文件内容与文件名不符。 |
422 | 请求格式正确但内容无效:缺少字段或值超出范围。 |
429 | 请求过多或配额已用完。参见速率限制与配额。 |
500、502、503、504 | Sursa 一方出现问题。请稍后重试。 |
校验错误
对于错误码为 validation.failed 的 422,detail 列出所有未通过校验的字段及其位置:
{
"detail": [
{"loc": ["body", "title"], "msg": "String should have at least 1 character", "type": "string_too_short"}
],
"error": {"code": "validation.failed", "message": "request validation failed", "request_id": "01JB7…"}
}
哪些错误可以重试
| 错误 | 是否重试 |
|---|---|
| 网络错误或超时,没有响应 | 是,使用相同的 Idempotency-Key。 |
429 | 是,等待 Retry-After 中的秒数后重试。 |
500、502、503、504 | 是,使用指数退避:1 秒、2 秒、4 秒……最长一分钟,并加入随机抖动。 |
409 api.idempotency_in_progress | 是,等待 Retry-After 后重试:使用该键的第一个请求仍在执行。 |
其他任何 4xx | 否。同样的请求还会失败,请先修正。 |
重试 POST 和 PATCH 请求时一定要带 Idempotency-Key,这样响应丢失后重试也不会创建第二个对象。GET、PUT 和 DELETE 可以直接安全重试。
// Retries a request on network errors, 429 and 5xx. The same Idempotency-Key is sent on every attempt.
async function withRetries(send, attempts = 5) {
for (let attempt = 1; ; attempt++) {
let response;
try {
response = await send();
} catch (error) {
if (attempt >= attempts) throw error;
}
if (response && response.status !== 429 && response.status < 500) return response;
if (attempt >= attempts) return response;
const retryAfter = Number(response?.headers.get("Retry-After"));
const backoff = Math.min(60, 2 ** (attempt - 1)) * (0.8 + Math.random() * 0.4);
await new Promise((resolve) => setTimeout(resolve, 1000 * (retryAfter || backoff)));
}
}
const key = crypto.randomUUID();
const response = await withRetries(() =>
fetch("https://api.sursa.com/v1/cards/text", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.SURSA_API_KEY}`,
"Content-Type": "application/json",
"Idempotency-Key": key,
},
body: JSON.stringify({ title: "Refund policy", body_markdown: "Refunds take 14 days." }),
}),
);报告问题
每个响应,无论成功与否,都带有 X-Request-Id 头;每个错误的 error.request_id 中也是同一个值。请把它和您自己的请求记录一起写入日志。就某个请求联系技术支持时,请提供它的请求 ID:凭它可以直接找到该请求的记录。
处理不认识的错误码
API 的任何版本都可能新增错误码。按 HTTP 状态处理不认识的错误码:不认识的 4xx 表示请求照原样不会成功,不认识的 5xx 表示暂时性问题。