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

# 追蹤批次作業

> 輪詢批次作業的狀態與計數器、讀取每個目標的結果，並下載結果檔案。

在你[提交批次操作](/submit-bulk-operation)之後，接著執行兩個查詢：用 `getBulkOperationJob` 取得作業的狀態與彙總計數器，並用 `getBulkOperationItems` 取得每個目標的詳細資訊。完成狀態僅支援**輪詢** — 不提供批次用的 webhook。

## 需求條件

* `Authorization: Basic <API_KEY>`
* 你的應用程式已啟用批次 API 能力。
* 作業必須屬於你的應用程式 — 不明或屬於其他應用的作業 id 會回傳找不到。

## 查詢 — 作業狀態

持續輪詢直到 `status` 進入終止狀態（`COMPLETED`、`COMPLETED_WITH_ERRORS`、`FAILED` 或 `CANCELLED`）。

```graphql theme={null}
query BulkJob($bulkJobId: UUID!) {
  getBulkOperationJob(bulkJobId: $bulkJobId) {
    jobId
    status
    operation
    requestedTargetCount
    acceptedItemCount
    succeededItemCount
    failedItemCount
    skippedItemCount
    resultUrl
    resultUrlExpiresAt
  }
}
```

```json theme={null}
{
  "data": {
    "getBulkOperationJob": {
      "jobId": "9c1e6f2a-1d4b-4a2e-8f0c-2b7e5a9d1234",
      "status": "COMPLETED_WITH_ERRORS",
      "operation": "CREATE_TRANSFER",
      "requestedTargetCount": 3,
      "acceptedItemCount": 3,
      "succeededItemCount": 2,
      "failedItemCount": 1,
      "skippedItemCount": 0,
      "resultUrl": "https://storage.googleapis.com/…",
      "resultUrlExpiresAt": "2024-06-01T15:20:00Z"
    }
  }
}
```

## 查詢 — 逐目標結果

使用游標分頁（每頁 100 筆），並可依項目狀態篩選 — 篩到 `FAILED` 以僅對失敗項目對帳，或讀取 `resultResourceId` 以將成功寫入對應回其產生的 Fluz 資源。

```graphql theme={null}
query BulkItems($bulkJobId: UUID!, $status: BulkItemStatus, $after: String) {
  getBulkOperationItems(bulkJobId: $bulkJobId, status: $status, limit: 100, after: $after) {
    totalCount
    hasNextPage
    nextCursor
    items {
      externalReferenceId
      accountId
      itemIndex
      status
      resultResourceId
      errorCode
      errorMessage
      metadataResults { recordId success errorCode errorMessage }
    }
  }
}
```

```json theme={null}
{
  "data": {
    "getBulkOperationItems": {
      "totalCount": 3,
      "hasNextPage": false,
      "nextCursor": null,
      "items": [
        {
          "externalReferenceId": "user-123",
          "accountId": "a1b2…",
          "itemIndex": 0,
          "status": "SUCCEEDED",
          "resultResourceId": "7453414c-e290-4bb5-9516-79b212bd84cc",
          "errorCode": null,
          "errorMessage": null,
          "metadataResults": null
        },
        {
          "externalReferenceId": "user-456",
          "accountId": "c3d4…",
          "itemIndex": 1,
          "status": "FAILED",
          "resultResourceId": null,
          "errorCode": "PT-0007",
          "errorMessage": "The sender does not have sufficient balance to complete this transfer.",
          "metadataResults": null
        }
      ]
    }
  }
}
```

## 下載結果檔案

選取 `resultUrl` 會即時產生一個短效（約 15 分鐘）的簽章下載 URL — 單純輪詢狀態而不選取它的成本很低。開啟它（或使用 `curl -L`）可下載一個 **NDJSON** 檔案，每個目標一行。

* **匯出**（`GET_*_EXPORT`）— 匯出的餘額／交易資料。
* **寫入**（`CREATE_TRANSFER`、`DEPOSIT_CASH_BALANCE`）— 結果分錄：每個目標一筆 `{ externalReferenceId, accountId, status, resultResourceId, resourceType, errorCode, errorMessage }`。

`resultUrl` 在作業達到終止狀態前，以及對於未產生檔案的作業，皆為 `null`。當連結過期（`resultUrlExpiresAt`）後，再次請求該欄位即可取得新的 URL。

## 參數

| Parameter   | Type             | Description                                                         |
| ----------- | ---------------- | ------------------------------------------------------------------- |
| `bulkJobId` | `UUID!`          | `submitBulkOperation` 回傳的作業 id。                                     |
| `status`    | `BulkItemStatus` | *(僅 items)* 篩選 `QUEUED`、`RUNNING`、`SUCCEEDED`、`FAILED` 或 `SKIPPED`。 |
| `limit`     | `Int`            | *(僅 items)* 分頁大小，最大值與預設皆為 100。                                      |
| `after`     | `String`         | *(僅 items)* 先前頁面的 `nextCursor` 所回傳的不透明游標。                           |

## 回應欄位

| Field                                                         | Description                                                                            |
| ------------------------------------------------------------- | -------------------------------------------------------------------------------------- |
| `status` (job)                                                | `QUEUED` → `RUNNING` → `COMPLETED` / `COMPLETED_WITH_ERRORS`，或 `FAILED` / `CANCELLED`。 |
| `succeededItemCount` / `failedItemCount` / `skippedItemCount` | 逐項目的彙總計數器。                                                                             |
| `resultUrl` / `resultUrlExpiresAt`                            | 結果檔案的簽章下載 URL 與其到期時間。                                                                  |
| `items[].status`                                              | 終止狀態為 `SUCCEEDED`、`FAILED` 或 `SKIPPED`，或進行中為 `QUEUED` / `RUNNING`。                     |
| `items[].resultResourceId`                                    | 項目成功時所產生的 Fluz 資源（例如轉帳 id、`cash_balance_deposit_id`）。                                  |
| `items[].errorCode` / `errorMessage`                          | 當為 `FAILED` 或 `SKIPPED` 時的機器可讀原因與訊息。                                                   |
| `items[].metadataResults`                                     | `UPDATE_TRANSACTION_METADATA` 項目的逐筆編輯結果；其他操作為 `null`。                                  |
| `totalCount`                                                  | 作業在所有狀態下的總項目數 — 不是篩選後頁面的數量。                                                            |

<Note>
  `getBulkOperationItems` 與結果檔案是相同逐項目資料的兩種檢視。使用 items 查詢可程式化對帳（篩到 `FAILED`，對應 `resultResourceId`）；對於大型作業，使用結果檔案一次拉取完整分錄。
</Note>

## 錯誤

這些查詢僅會在請求本身失敗時整體失敗 — 錯誤的目標絕非請求錯誤，而是逐項目的 `FAILED`/`SKIPPED` 結果（見下方）。

| Error                 | HTTP | When                                                       |
| --------------------- | ---- | ---------------------------------------------------------- |
| `BulkApiAccessDenied` | 403  | 你的應用程式未啟用批次 API 能力。                                        |
| `BulkJobNotFound`     | 404  | 作業 id 不明，**或** 屬於另一個應用程式。外部與不存在刻意不可區分 — 回應不會洩漏任何其他應用的作業資訊。 |
| `ArgumentsInvalid`    | 400  | `getBulkOperationItems` 收到了格式錯誤的 `after` 游標。               |

## 逐項目失敗代碼

`FAILED` 或 `SKIPPED` 項目會包含機器可讀的 `errorCode` 與給人看的 `errorMessage`。`SKIPPED` 代碼在提交時指派（該目標從未進入佇列）；`FAILED` 代碼可能來自提交時驗證，或對於已被接受的項目，來自執行時下游服務的錯誤。

**任何操作：**

| `errorCode`                 | 意義                      |
| --------------------------- | ----------------------- |
| `TARGET_NOT_CONNECTED`      | 該 id 無法解析為連結到你應用程式的使用者。 |
| `INVALID_TARGET_IDENTIFIER` | 該 id 不是有效的目標識別子。        |
| `INSUFFICIENT_SCOPE`        | 使用者授權缺少此操作所需的 scope。    |
| `ACCOUNT_NOT_PERMITTED`     | 使用者未允許在該請求的帳戶上執行此操作。    |

**僅限 `CREATE_TRANSFER`：**

| `errorCode`                          | 意義                                    |
| ------------------------------------ | ------------------------------------- |
| `SOURCE_MATCHES_DESTINATION`         | `from` 與 `to` 解析到相同帳戶。                |
| `AMBIGUOUS_DESTINATION_CASH_BALANCE` | 目的地使用者允許多個消費帳戶且未設預設值 — 目的地不明確。        |
| *downstream*                         | 執行時，付款失敗會直接回傳支付服務本身的代碼／訊息（例如匯款人餘額不足）。 |

**僅限 `DEPOSIT_CASH_BALANCE`：**

| `errorCode`                          | 意義                                                                     |
| ------------------------------------ | ---------------------------------------------------------------------- |
| `PAYMENT_METHOD_NOT_PERMITTED`       | `bankCardId` / `bankAccountId` 不屬於目標使用者（缺少與非本人擁有使用相同代碼 — 不洩漏支付工具是否存在）。 |
| `ACCOUNT_NOT_PERMITTED`              | 無法為目標找到任何消費帳戶席次，或目的地餘額不被允許。                                            |
| `AMBIGUOUS_DESTINATION_CASH_BALANCE` | 目標允許多個消費帳戶且未設預設值 — 請在項目上提供 `userCashBalanceId`。                        |
| *downstream*                         | 執行時，充值失敗會直接回傳購買服務本身的代碼／訊息（例如金額低於最低限度）。                                 |

<Note>
  帶有類別前綴的代碼為 Fluz 指派；標示為 *downstream* 的列表示該項目攜帶執行服務本身的錯誤代碼，故這些代碼是開放集合。請針對你能處理的代碼撰寫程式，並將未知代碼視為一般性失敗 — 可讀取 `errorMessage` 取得人類可讀的原因。
</Note>
