> ## 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>
