> ## Documentation Index
> Fetch the complete documentation index at: https://www.ayrshare.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# GraphQL 错误

> Ayrshare GraphQL API 如何报告错误，涵盖格式错误请求的 HTTP 状态码、errors 数组、部分成功、幂等重试以及速率限制。

<h2 id="resolver-errors-arrive-with-http-200">
  解析器错误以 HTTP 200 返回
</h2>

这是你在编写错误处理之前需要了解的最重要的一点。

根据 GraphQL over HTTP 规范，到达我们解析器的请求**即使操作失败也会返回 HTTP 200**。失败信息在响应体中报告，而不是通过状态码体现。

因此，下面这种检查是无效的：

```javascript theme={"system"}
// WRONG - will treat a failed request as a success
if (response.ok) {
  return "everything worked";
}
```

请改为检查响应体。少数传输错误（例如不支持的 `Content-Type` 导致的 415）的响应体为空，因此在解析之前请先检查响应是否为 JSON：

```javascript theme={"system"}
if (!response.headers.get("content-type")?.includes("json")) {
  throw new Error(`GraphQL request failed with HTTP ${response.status}`);
}

const result = await response.json();

if (result.errors) {
  for (const error of result.errors) {
    // Errors caught before execution have no extensions, so read it safely.
    console.log(error.message, error.extensions?.status);
  }
}
```

有些失败会在任何操作运行之前就被捕获。这些失败都不会到达解析器，也不会消耗 API 调用：

<ul class="custom-bullets">
  <li>格式错误的请求会返回错误状态码：JSON 无效或缺少 <code>query</code> 时返回 <strong>HTTP 400</strong>，请求体超过 64 KB 时返回 <strong>413</strong>，使用 <code>POST</code> 以外的方法时返回 <strong>405</strong>，<code>Content-Type</code> 不受支持时返回 <strong>415</strong>。</li>
  <li>对 schema 无效的查询只有在你的请求发送了 <code>Accept: application/graphql-response+json</code> 时才会返回 <strong>HTTP 400</strong>。这包括语法错误、未知字段、参数类型错误、无效的枚举值，以及超出<a href="/docs/apis/graphql/limits#query-limits">查询限制</a>的查询。如果你发送的是 <code>Accept: application/json</code>，或未发送 <code>Accept</code> header，同样的错误会返回 <strong>HTTP 200</strong>。无论哪种情况，响应体中都会有 <code>errors</code> 数组，且没有 <code>data</code>。</li>
</ul>

一旦开始执行，解析器级别的输入验证错误和 API 失败（包括身份验证、授权、上游速率限制以及上游 5xx 响应）都会返回 HTTP 200 以及 `errors` 数组。

<h2 id="the-error-shape">
  错误结构
</h2>

每个解析器错误都会在 `extensions` 中携带类似 HTTP 的 API 状态码：

```json theme={"system"}
{
  "data": { "postHistory": null },
  "errors": [
    {
      "message": "Rate limit exceeded",
      "path": ["postHistory"],
      "extensions": {
        "status": 429,
        "retryAfter": 7
      }
    }
  ]
}
```

<ul class="custom-bullets">
  <li><code>message</code>：人类可读的描述。我们会从解析器错误消息中移除 API Key 和其他凭据，但与你发送的值相关的错误（例如类型错误的变量）可能会重复该值。在记录 <code>message</code> 之前，请将其视为可能包含敏感信息。</li>
  <li><code>path</code>：查询中失败的字段。当一个请求中有多个字段时，可以据此判断是哪一个失败了。</li>
  <li><code>extensions.status</code>：等效 REST 调用会返回的 HTTP 状态码。请根据它进行分支处理。</li>
  <li><code>extensions.code</code>：Ayrshare 数字错误代码（当底层端点提供时）。</li>
  <li><code>extensions.retryAfter</code>：在上游配额或速率限制导致的 429 中，如果有可用的重试延迟，则会出现该字段。每请求分派预算导致的 429 中不会出现该字段。</li>
</ul>

请结合[错误代码参考](/docs/errors/overview)使用 `extensions.code`，或将其传给 `explainError`。

<h2 id="partial-success">
  部分成功
</h2>

如果一个请求需要获取多项内容，而只有部分成功，你会**同时**收到 `data` 和 `errors`：

```graphql theme={"system"}
{
  history: postHistory(lastDays: 7)
  analytics: socialAnalytics(platforms: [INSTAGRAM])
}
```

```json theme={"system"}
{
  "data": {
    "history": { "history": [{ "id": "RkQ8uXg2jT7bNd1Wq0Ya", "status": "success", "post": "Hello world" }], "refId": "9d2a7c41f0b8e6d35a1c", "count": 1, "lastUpdated": "2026-09-24T12:00:00.000Z", "nextUpdate": "2026-09-24T12:00:00.000Z" },
    "analytics": null
  },
  "errors": [
    {
      "message": "Rate limit exceeded",
      "path": ["analytics"],
      "extensions": { "status": 429 }
    }
  ]
}
```

`history` 已成功，其数据可以使用。只有 `analytics` 需要重试。不要因为存在 `errors` 就丢弃整个响应，否则你会丢掉已经计费的数据。

<h2 id="partial-success-on-multi-network-posts">
  多社交网络帖子的部分成功
</h2>

发布到多个社交网络的帖子可能在部分社交网络上成功，而在其他社交网络上失败。这种情况**不会**作为 GraphQL 错误报告，因为操作本身是成功的：它完全按照你的要求执行，并告诉你发生了什么。

结果会区分三种结果：

```graphql theme={"system"}
mutation {
  createPost(input: {
    post: "Hi"
    platforms: [FACEBOOK, INSTAGRAM]
    idempotencyKey: "multi-network-hi-001"
  }) {
    status
    postIds { platform postUrl }
    errors { platform message code }
    blocked { platform message }
  }
}
```

<ul class="custom-bullets">
  <li><code>postIds</code>：已发布的社交网络及其帖子 URL。仅包含成功项。</li>
  <li><code>errors</code>：失败的社交网络及失败原因。</li>
  <li><code>blocked</code>：在尝试之前就被阻止的社交网络，例如在多次确定性失败后被按社交网络划分的熔断器阻止。这些与向服务提供方尝试后返回错误的情况是分开的。</li>
</ul>

只要**任何**一个社交网络失败，`status` 就为 `"error"`，因此它并不能可靠地表明没有任何内容被发布。请读取 `postIds` 以了解哪些已发布。

<h2 id="retries">
  重试
</h2>

**GraphQL API 从不自动重试。** 如果请求失败，系统不会在你不知情的情况下代你再次尝试。

这一点对变更最为重要。`createPost` 提供了 `idempotencyKey`：请在第一次尝试之前生成一个唯一的键，然后在每次重试时复用相同的键和完全相同的负载。已完成处理的重试会重放原始结果，而不是重复写入。在收到不明确的响应后，不要换用新的键。

`boostFacebookPost` 和 `instagramBoostPost` 也接受 `idempotencyKey`，它保护的是真金白银。使用相同的键和相同参数进行重试时，会返回原始结果并标记 `idempotentReplayed: true`，而不会创建并支付第二个广告。一个键覆盖你的整个账户在两个 Meta 平台上的使用，因此切勿将 Facebook 推广的键复用于 Instagram 推广。

当键被拒绝时，`extensions.status` 会告诉你原因。`createPost` 在第一次尝试仍在处理时返回 409，此时请等待并使用相同的键重试；当负载与第一次尝试不同时返回 400。推广类变更在这两种情况下都返回 409，当该键已被用于另一个平台上的推广时也会返回 409。

其他写入操作（例如 `addComment`）没有幂等键。如果它们的响应丢失，请先检查社交网络或 API 状态，再决定是否可以安全地再次尝试。

有两种不同的限制都会报告 429，需要分别处理。分派预算只会在同一请求中已经启动五次 API 调用后拒绝根字段，其消息以 `Query exceeds the per-request dispatch budget` 开头：请将这些字段拆分到每个最多包含五个字段的请求中，无需等待。其他任何 429 都是速率限制或配额：如果存在 `extensions.retryAfter`，请等待相应秒数，否则请在重试前进行退避，并且只重试失败的字段。对于**读取**操作的 5xx，重试是安全的，但有一个例外：`generatePost` 虽然是查询，但重试会再次调用 AI 生成器，计为另一次 API 调用，并返回不同的文本。对于没有幂等保护的写入操作的 5xx，请在重试前核实该操作是否已生效。

<h2 id="common-statuses">
  常见状态码
</h2>

| `extensions.status` | 含义 | 处理方式 |
| - | - | - |
| 400 | 我们的 API 拒绝了无效输入 | 修正请求；重试无济于事 |
| 401 | API Key 缺失或无效 | 检查 `Authorization` header |
| 402 | 你的计划不包含此功能 | 检查你的计划 |
| 403 | 密钥有效，但无权执行此操作 | 检查 Profile Key 和计划 |
| 404 | 你请求的对象不存在 | 核实 ID |
| 409 | 幂等键正在使用中：第一个请求仍在运行，或推广键被用于不同的参数 | 如果第一个请求仍在运行，请等待并原样重试；否则请为新操作使用新的键 |
| 429 | 速率限制、配额或分派预算 | 如果消息中提到分派预算，请拆分请求。否则等待 `retryAfter`，如果没有该字段则进行退避 |
| 500 | 我们这边出现了问题 | 重试读取操作（`generatePost` 除外，它会再次计费）；在支持的情况下复用相同的幂等键，否则请在重试前核实写入操作 |

GraphQL 本身在执行前抛出的错误（例如未知字段、参数类型错误或无效的枚举值）没有 `extensions.status`。如上所述，根据你的 `Accept` header，它们会返回 HTTP 400 或 200。这些错误表示查询本身有误，不会发起 API 调用，也不会计费。
