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

# 提交批量操作

> 通过一次异步调用，对许多已连接用户执行写入或大型导出。你提交一个作业，轮询其状态，并（对于导出和写入）下载结果文件。

在一次调用中对你已连接的用户运行批量操作。与同步读取（[获取批量余额](/get-bulk-balances)、[获取批量交易](/get-bulk-transactions)）不同，`submitBulkOperation` 是**异步**的：它会验证请求，为每个目标创建一个作业和一条项目，并立即返回一个 `jobId`。工作在后台运行——[跟踪该作业](/track-bulk-job)以了解进度并获取结果。

一个变更覆盖所有五种操作，由 `operation` 字段选择：

| `operation`                   | 它的作用                  | 所需授权范围（每个用户）                            |
| ----------------------------- | --------------------- | --------------------------------------- |
| `GET_BALANCES_EXPORT`         | 将现金余额导出到文件            | `LIST_PAYMENT`                          |
| `GET_TRANSACTIONS_EXPORT`     | 将交易导出到文件              | `LIST_PAYMENT`, `LIST_PURCHASES`        |
| `CREATE_TRANSFER`             | 在运营方和/或已连接用户之间转移资金    | `MAKE_PAYOUT_TRANSFER_SEND`（在**来源**用户上） |
| `DEPOSIT_CASH_BALANCE`        | 从已连接用户自己的支付方式为其消费账户充值 | `MAKE_DEPOSIT`                          |
| `UPDATE_TRANSACTION_METADATA` | 编辑交易的备注/类别            | `LIST_PAYMENT`, `LIST_PURCHASES`        |

## 要求

* `Authorization: Basic <API_KEY>`
* 你的应用已启用批量 API 能力。
* 所选操作所需的 scope，且由每个目标用户授予（见上表）。缺少 scope 的目标会成为逐项失败——永远不会导致整个作业失败。

<Note>
  每次提交**必须**携带一个幂等性键——可以是 `idempotencyKey` 输入，或 `Idempotency-Key` 头（如果两者都发送则必须匹配）。在请求逐字节完全相同的情况下，使用相同键重新提交会返回原始作业，而不是创建重复项；在相同键下的不同请求将被拒绝。对于扇出式写入，没有安全的默认行为，因此该键为必填。
</Note>

## 变更

```graphql theme={null}
mutation SubmitBulkOperation($input: SubmitBulkOperationInput!) {
  submitBulkOperation(input: $input) {
    jobId
    status
    operation
    requestedTargetCount
    acceptedItemCount
    succeededItemCount
    failedItemCount
    skippedItemCount
    createdAt
  }
}
```

成功提交会返回处于 `QUEUED` 状态的作业。`acceptedItemCount` 表示将被处理的目标数量；`skippedItemCount` 表示在前置阶段被拒绝的数量（例如缺少所需的 scope）。轮询该作业以观察 `succeededItemCount` / `failedItemCount` 的变化——参见[跟踪批量作业](/track-bulk-job)。

## 变量 — 导出余额 / 交易

`exportOptions` 限定窗口（默认为最近 90 天；`GET_BALANCES_EXPORT` 为时点快照，忽略该窗口）。与读取相同，使用[目标选择](/bulk-api#selecting-target-users)。

```json theme={null}
{
  "input": {
    "operation": "GET_TRANSACTIONS_EXPORT",
    "idempotencyKey": "export-2024-06-01-a",
    "targetSpec": { "mode": "ALL_CONNECTED" },
    "exportOptions": {
      "createdGte": "2024-05-01T00:00:00Z",
      "createdLte": "2024-06-01T00:00:00Z",
      "includeMetadata": false
    }
  }
}
```

## 变量 — 创建转账

每个项目指定其各自的 `from` 和 `to` 端点，因此一个作业可以混合多种方向：运营方→用户、用户→运营方，以及用户→用户。一个端点**要么**是 `{ "operator": true }`（你的应用的付款账户）**要么**是 `{ "externalReferenceId": "…" }`（一个已连接用户）。`from` 和 `to` 必须不同。**来源**用户的授权必须允许 `MAKE_PAYOUT_TRANSFER_SEND`；目的方只需已连接。转账在同一赞助银行内进行。对于转账不需要 `targetSpec`——参与方来自各条项目。

```json theme={null}
{
  "input": {
    "operation": "CREATE_TRANSFER",
    "idempotencyKey": "payouts-2024-06-01-a",
    "transferOptions": {
      "items": [
        { "from": { "operator": true }, "to": { "externalReferenceId": "user-123" }, "amount": 10.00, "memo": "Reward" },
        { "from": { "externalReferenceId": "user-123" }, "to": { "operator": true }, "amount": 2.50 },
        { "from": { "externalReferenceId": "user-123" }, "to": { "externalReferenceId": "user-456" }, "amount": 5.00 }
      ]
    }
  }
}
```

## 变量 — 充值现金余额

从已连接用户**本人**的支付方式为其消费账户充值——每条项目在 `bankCardId` 或 `bankAccountId` 中恰好选择一个。使用 `SELECTED` 目标方式，每个目标一条项目。`userCashBalanceId` 可选（默认为用户的许可/默认消费账户）。

```json theme={null}
{
  "input": {
    "operation": "DEPOSIT_CASH_BALANCE",
    "idempotencyKey": "deposits-2024-06-01-a",
    "targetSpec": { "mode": "SELECTED", "targets": ["user-123", "user-456"] },
    "depositOptions": {
      "items": [
        { "externalReferenceId": "user-123", "amount": 10.00, "bankCardId": "b1a2…", "memo": "Top-up" },
        { "externalReferenceId": "user-456", "amount": 25.00, "bankAccountId": "c3d4…" }
      ]
    }
  }
}
```

## 变量 — 更新交易元数据

编辑用户交易上的 `memo` 和/或 `transactionCategory`。使用 `SELECTED` 目标方式，每个目标一条项目，**每次提交最多 100 次编辑**。将某字段设为 `null` 会清空它；省略的字段保持不变。元数据编辑是**同步**执行的——返回的作业已处于终态，因此你可以立即读取每次编辑的结果而无需轮询。

```json theme={null}
{
  "input": {
    "operation": "UPDATE_TRANSACTION_METADATA",
    "idempotencyKey": "metadata-2024-06-01-a",
    "targetSpec": { "mode": "SELECTED", "targets": ["user-123"] },
    "metadataOptions": {
      "items": [
        {
          "externalReferenceId": "user-123",
          "edits": [
            { "recordId": "3f2a…", "memo": "Team lunch", "transactionCategory": "Meals" },
            { "recordId": "9b7c…", "memo": null }
          ]
        }
      ]
    }
  }
}
```

## 响应

```json theme={null}
{
  "data": {
    "submitBulkOperation": {
      "jobId": "9c1e6f2a-1d4b-4a2e-8f0c-2b7e5a9d1234",
      "status": "QUEUED",
      "operation": "CREATE_TRANSFER",
      "requestedTargetCount": 3,
      "acceptedItemCount": 3,
      "succeededItemCount": 0,
      "failedItemCount": 0,
      "skippedItemCount": 0,
      "createdAt": "2024-06-01T15:04:05Z"
    }
  }
}
```

## 参数

| 参数                            | 类型                         | 描述                                                                                                                             |
| ----------------------------- | -------------------------- | ------------------------------------------------------------------------------------------------------------------------------ |
| `input.operation`             | `BulkOperationType!`       | 取值为 `GET_BALANCES_EXPORT`、`GET_TRANSACTIONS_EXPORT`、`CREATE_TRANSFER`、`DEPOSIT_CASH_BALANCE`、`UPDATE_TRANSACTION_METADATA` 之一。 |
| `input.idempotencyKey`        | `String`                   | 你为本次提交设置的唯一键。必填（通过此字段或 `Idempotency-Key` 头）。                                                                                   |
| `input.targetSpec`            | `BulkTargetSpecInput`      | 操作用于哪些已连接用户。对导出、充值和元数据为必填；对 `CREATE_TRANSFER` 忽略（由各条项目自描述）。                                                                    |
| `input.exportOptions`         | `BulkExportOptionsInput`   | `createdGte` / `createdLte` 窗口和 `includeMetadata`。仅导出操作使用。                                                                     |
| `input.transferOptions.items` | `[BulkTransferItemInput!]` | 每笔转账的 `from` / `to` 端点、`amount`，可选 `memo`。仅用于 `CREATE_TRANSFER`。                                                               |
| `input.depositOptions.items`  | `[BulkDepositItemInput!]`  | 每个目标的 `amount`，`bankCardId` / `bankAccountId` 二选一，可选 `userCashBalanceId` / `memo`。仅用于 `DEPOSIT_CASH_BALANCE`。                  |
| `input.metadataOptions.items` | `[BulkMetadataItemInput!]` | 每个目标的 `edits`（`recordId`、`memo?`、`transactionCategory?`）。仅用于 `UPDATE_TRANSACTION_METADATA`；总编辑数 ≤100。                          |

## 响应字段

| 字段                                       | 描述                                                                                    |
| ---------------------------------------- | ------------------------------------------------------------------------------------- |
| `jobId`                                  | 作业的 ID。使用它来[跟踪状态与结果](/track-bulk-job)。                                                |
| `status`                                 | 生命周期状态：`QUEUED`、`RUNNING`、`COMPLETED`、`COMPLETED_WITH_ERRORS`、`FAILED` 或 `CANCELLED`。 |
| `operation`                              | 提交的操作。                                                                                |
| `requestedTargetCount`                   | 请求所涉及的目标数。                                                                            |
| `acceptedItemCount`                      | 将被处理的项目数（每个被接受的目标一条项目）。                                                               |
| `succeededItemCount` / `failedItemCount` | 逐项结果；随着作业运行而填充。                                                                       |
| `skippedItemCount`                       | 在提交阶段被拒绝的目标（例如缺少所需 scope）。                                                            |
| `createdAt`                              | 作业的创建时间。                                                                              |

<Note>
  失败是**逐项**的——某个目标的失败不会使作业失败（参见[按目标的失败约定](/bulk-api#per-target-failure-contract)）。仅当发生认证失败、无效操作、超出上限，或当\_所有\_目标均不可解析时，请求本身才会被拒绝并失败。达到终态后，通过[跟踪批量作业](/track-bulk-job)读取逐项详情——包括每次写入产生的资源。
</Note>

## 错误

仅在以下情况会拒绝整个提交（不创建作业）。其他一切将成为逐项结果，可通过[跟踪批量作业](/track-bulk-job#per-item-failure-codes)读取。

| 错误                           | HTTP | 何时发生                                                                                         |
| ---------------------------- | ---- | -------------------------------------------------------------------------------------------- |
| `BulkApiAccessDenied`        | 403  | 你的应用未启用批量 API 能力。                                                                            |
| `IdempotencyKeyConflict`     | 409  | 同时发送了 `Idempotency-Key` 头与 `idempotencyKey` 输入，且两者不匹配。                                       |
| `IdempotencyKeyReused`       | 409  | 该幂等性键已用于**不同**的请求。（对相同请求的再次提交会返回原始作业。）                                                       |
| `TargetLimitExceeded`        | 422  | 请求引用了超过 10,000 个不同用户。请拆分批次并重新提交。                                                             |
| `ArgumentsInvalid`           | 400  | 负载校验失败（例如端点不是严格二选一的运营方/用户、`from` == `to`、小于分的金额、过长的备注、>100 次元数据编辑）**或**所有目标均不可解析/缺少所需 scope。 |
| `AmbiguousSourceCashBalance` | 422  | *（仅 `CREATE_TRANSFER`）* 源自运营方的转账，但你的付款路由配置了多个现金余额且没有默认项。请联系 Fluz 配置默认付款余额。                   |
