> ## 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">
  Resolver 錯誤會以 HTTP 200 傳回
</h2>

這是撰寫錯誤處理前最需要了解的一件事。

依照 GraphQL over HTTP 規範，抵達我們 resolver 的請求**即使操作失敗也會傳回 HTTP 200**。失敗會在回應 body 中回報，而不是透過狀態碼。

因此，以下這種檢查方式是無效的：

```javascript theme={"system"}
// 錯誤：會將失敗的請求視為成功
if (response.ok) {
  return "everything worked";
}
```

請改為檢查 body。少數傳輸錯誤（例如不支援的 `Content-Type` 所造成的 415）的 body 是空的，因此在解析之前，請先確認回應是 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) {
    // 在執行前就被攔截的錯誤沒有 extensions，因此請安全地讀取。
    console.log(error.message, error.extensions?.status);
  }
}
```

有些失敗會在任何東西執行之前就被攔截。這些失敗都不會抵達 resolver，也不會使用 API 呼叫：

<ul class="custom-bullets">
  <li>格式錯誤的請求會傳回錯誤狀態：無效的 JSON 或缺少 <code>query</code> 時為 <strong>HTTP 400</strong>，body 超過 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>。無論哪種情況，body 都會包含 <code>errors</code> 陣列，且沒有 <code>data</code>。</li>
</ul>

一旦開始執行，resolver 層級的輸入驗證與 API 失敗（包括驗證、授權、上游速率限制與上游 5xx 回應）都會傳回 HTTP 200 以及 `errors` 陣列。

<h2 id="the-error-shape">
  錯誤結構
</h2>

每個 resolver 錯誤都會在 `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>：人類可讀的說明。我們會從 resolver 錯誤訊息中移除 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 從不自動重試。** 如果請求失敗，系統不會在背後代替你悄悄再次嘗試。

這一點對 mutation 最為重要。`createPost` 提供 `idempotencyKey`：請在第一次嘗試前產生一個唯一的 key，之後每次重試都重複使用相同的 key 與完全相同的 payload。已完成的重試會重播原始結果，而不會重複寫入。收到不明確的回應後，請不要改用新的 key。

`boostFacebookPost` 與 `instagramBoostPost` 也接受 `idempotencyKey`，而它保護的是真金白銀。使用相同 key 與相同引數的重試會傳回原始結果，並標示 `idempotentReplayed: true`，而不會建立第二則廣告並再次付費。一個 key 涵蓋你整個帳號在兩個 Meta 平台上的使用，因此絕對不要將 Facebook 推廣的 key 重複用於 Instagram 推廣。

當 key 被拒絕時，`extensions.status` 會告訴你原因。當第一次嘗試仍在處理中時，`createPost` 會傳回 409，此時請等待並使用相同的 key 重試；當 payload 與第一次嘗試不同時，則會傳回 400。推廣 mutation 在這兩種情況下都會傳回 409，當該 key 已用於另一個平台的推廣時也是如此。

其他寫入操作（例如 `addComment`）沒有冪等 key。如果它們的回應遺失，請先檢查社群網路或 API 狀態，再決定是否可以安全地再次嘗試。

有兩種不同的限制都會回報 429，且需要不同的處理方式。分派預算只會在同一個請求中已開始五次 API 呼叫後才拒絕根欄位，其訊息開頭為 `Query exceeds the per-request dispatch budget`：請將這些欄位拆分成每次最多五個的請求，不需要等待。其他任何 429 都是速率限制或配額：如果有 `extensions.retryAfter`，請等待該秒數，否則請先退避再重試，且只重試失敗的欄位。對於**讀取**操作的 5xx，重試是安全的，但有一個例外：`generatePost` 雖然是 query，但重試會再次呼叫 AI 產生器、再算一次 API 呼叫，並傳回不同的文字。對於沒有冪等性的寫入操作的 5xx，請在重試前先確認它是否已生效。

<h2 id="common-statuses">
  常見狀態
</h2>

| `extensions.status` | 意義 | 處理方式 |
| - | - | - |
| 400 | 我們的 API 拒絕的無效輸入 | 修正請求；重試無濟於事 |
| 401 | 缺少 API Key 或 API Key 無效 | 檢查 `Authorization` header |
| 402 | 方案不包含此功能 | 檢查你的方案 |
| 403 | Key 有效，但不允許執行此操作 | 檢查 profile key 與方案 |
| 404 | 你要求的項目不存在 | 確認 id |
| 409 | 冪等 key 正在使用中：第一個請求仍在執行，或推廣 key 被用於不同的引數 | 如果第一個請求仍在執行，請等待並以不變的內容重試；否則請為新操作使用新的 key |
| 429 | 速率限制、配額或分派預算 | 如果訊息提到分派預算，請拆分請求。否則請等待 `retryAfter`，若沒有此值則請退避 |
| 500 | 我們這端發生問題 | 重試讀取操作（`generatePost` 除外，因為會再次計費）；在支援的情況下重複使用相同的冪等 key，否則請在重試前確認寫入結果 |

GraphQL 本身在執行前引發的錯誤（例如未知的欄位、錯誤的引數型別或無效的列舉值）沒有 `extensions.status`。它們會依你的 `Accept` header 傳回 HTTP 400 或 200，如上所述。這表示查詢本身有誤，且未進行任何 API 呼叫，也不會計費。
