Skip to content

错误处理

错误响应格式

所有 API 错误遵循一致的 JSON 结构,并附带相应的 HTTP 状态码:

json
{
  "error": {
    "code": "ERROR_CODE",
    "message": "Human-readable description of what went wrong"
  }
}

code 字段是稳定的、机器可读的标识符,可在错误处理逻辑中使用。message 字段提供额外上下文,在同一错误码的不同出现场景中可能有所不同。

错误类别

认证错误 (401)

请求无法通过认证时返回。

错误码描述
UNAUTHORIZEDAuthorization 头缺失、格式错误或包含无效令牌。
KEY_EXPIREDAPI 密钥有效但已超过过期日期。请创建新密钥继续使用。
json
{
  "error": {
    "code": "UNAUTHORIZED",
    "message": "Missing or invalid Authorization header"
  }
}

授权错误 (403)

已认证的用户没有权限访问请求的资源时返回。

错误码描述
FORBIDDEN您正在尝试访问或修改属于其他用户的资源(例如吊销他人的 API 密钥)。

未找到错误 (404)

请求的资源不存在时返回。

错误码描述
NOT_FOUNDURL 中标识的资源(提取、API 密钥等)不存在或已被删除。

验证错误 (400)

请求体或参数无效时返回。

错误码描述
VALIDATION_ERROR请求体未通过模式验证。message 字段描述了哪些字段无效。
MAX_KEYS_REACHED您已达到 10 个有效 API 密钥的上限。请先吊销现有密钥再创建新密钥。
json
{
  "error": {
    "code": "MAX_KEYS_REACHED",
    "message": "Maximum of 10 active API keys allowed"
  }
}

速率限制错误 (429)

用量限额已超出时返回。

错误码描述
USAGE_LIMIT_EXCEEDED您的月度提取限额已用完。请升级计划或等待下一个计费周期。
json
{
  "error": {
    "code": "USAGE_LIMIT_EXCEEDED",
    "message": "Monthly extraction limit reached (25/25). Upgrade your plan for more extractions."
  }
}

服务器错误 (500)

服务端发生意外情况时返回。

错误码描述
INTERNAL_ERROR发生意外的服务器错误。这些错误会被自动记录和调查。
EXTRACTION_FAILEDAI 处理管道在从文档提取数据时遇到错误。文档可能已损坏、不可读或格式不受支持。

可重试的错误

并非所有错误都应该重试。以下是详细分类:

错误码可重试操作
INTERNAL_ERROR使用指数退避重试
EXTRACTION_FAILED重试 -- AI 可能产生不同的结果。如果持续失败,请检查文档质量
USAGE_LIMIT_EXCEEDED升级计划或等待下一个计费周期
UNAUTHORIZED修复认证凭据
KEY_EXPIRED创建新的 API 密钥
FORBIDDEN您正在访问不属于自己的资源
NOT_FOUND资源不存在 -- 请验证 ID
VALIDATION_ERROR修复请求体以匹配预期模式
MAX_KEYS_REACHED先吊销现有密钥再创建新密钥

重试策略

对于可重试的错误,使用指数退避以避免服务器过载。以下是一个可复用的实现:

typescript
interface ApiResponse<T> {
  data?: T;
  error?: { code: string; message: string };
}

async function fetchWithRetry<T>(
  url: string,
  options: RequestInit,
  maxRetries = 3,
): Promise<ApiResponse<T>> {
  let lastError: Error | null = null;

  for (let attempt = 0; attempt <= maxRetries; attempt++) {
    try {
      const response = await fetch(url, options);
      const body = await response.json();

      if (response.ok) {
        return body as ApiResponse<T>;
      }

      const errorCode = body?.error?.code;

      // Only retry server errors
      if (
        (errorCode === "INTERNAL_ERROR" || errorCode === "EXTRACTION_FAILED") &&
        attempt < maxRetries
      ) {
        const delayMs = 1000 * Math.pow(2, attempt); // 1s, 2s, 4s
        console.log(`Retrying in ${delayMs}ms (attempt ${attempt + 1}/${maxRetries})...`);
        await new Promise((resolve) => setTimeout(resolve, delayMs));
        continue;
      }

      // Non-retryable error -- return immediately
      return body as ApiResponse<T>;
    } catch (err) {
      lastError = err as Error;

      if (attempt < maxRetries) {
        const delayMs = 1000 * Math.pow(2, attempt);
        await new Promise((resolve) => setTimeout(resolve, delayMs));
        continue;
      }
    }
  }

  throw lastError ?? new Error("Max retries exceeded");
}

用法:

typescript
const result = await fetchWithRetry<ExtractionResult>(
  "https://api.docmap.io/v1/extractions/run",
  {
    method: "POST",
    headers: {
      Authorization: `Bearer ${apiKey}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      templateId: "tmpl_abc123",
      fileName: "invoice.pdf",
      pdfBase64: encodedPdf,
      mimeType: "application/pdf",
    }),
  },
);

if (result.error) {
  console.error(`Extraction failed: [${result.error.code}] ${result.error.message}`);
} else {
  console.log("Extracted data:", result.data);
}

最佳实践

  • 始终先检查 HTTP 状态码。 2xx 状态码表示成功;其他均为错误。不要在未检查的情况下假设响应体包含 data 字段。

  • 解析错误体中的 code 字段。 在控制流中使用 code(而非 message)。错误消息可能随时间变化,但错误码是稳定的。

  • 处理特定错误码。 对已知的错误码如 USAGE_LIMIT_EXCEEDEDKEY_EXPIRED 进行分支处理,向用户显示针对性消息或触发特定工作流。

  • 记录未知错误。 如果遇到未明确处理的错误码,请记录完整响应(状态码、错误码和消息)以便调试。

  • 不要重试 4xx 错误。 认证、授权、验证和未找到错误不会自行解决。在重试前修复根本问题。

  • 设置重试限制。 切勿无限重试。对于服务器错误,三次重试加指数退避(1 秒、2 秒、4 秒)是合理的默认值。

DocMap API 文档