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

# 为授权用户创建消费账户

> 在您的账户上创建一个消费账户，并将授权用户指定为其所有者。

消费账户始终在**调用方的账户上**创建——无法在他人的账户上创建。您可以做的是先创建消费账户，然后将授权用户记录为其**所有者**，以便该账户有具名负责人。

不同于[`createVirtualCard`](/features/create-virtual-card-for-authorized-user)，`createUserCashBalance`并**不**接受`authUserId`。所有权是一个单独的第二次调用：使用[`assignObjectOwner`](/api-reference/mutations/assign-object-owner)并传入`objectType: SPEND_ACCOUNTS`。

<Note>
  **所有权是元数据，不是访问权限。**

  分配所有者会记录谁对某个消费账户负责。它本身并不会授予该人从账户中消费的能力。实际的访问权限仍然取决于用户的账户角色（[`UACRoleType`](/api-reference/types/uacrole-type)）与在该消费账户上授予的条目级访问权限两者中较高者。

  分配所有者**并且**确保该用户的角色赋予了您实际期望的访问权限。
</Note>

***

## 前置条件

<Steps>
  <Step title="授权用户已存在且为 ACTIVE">
    使用[`addAuthorizedUser`](/features/create-authorized-users)添加，并确认返回的`status`为`ACTIVE`。`PENDING`的指派不能用作所有者——用户必须先接受邀请。
  </Step>

  <Step title="您的令牌同时携带两个 scope">
    `createUserCashBalance`需要`MANAGE_PAYMENT`。`assignObjectOwner`需要`MANAGE_SUBUSERS`。单个 Bearer 令牌需要同时具备二者才能端到端完成此流程。
  </Step>

  <Step title="您已获得授权用户的 userId">
    `assignObjectOwner`以`userId`为键，而非`authUserId`。参见下文[解析 userId](#resolving-the-userid)。
  </Step>
</Steps>

***

## 步骤 1 — 创建消费账户

像平常一样创建消费账户。它会在调用方的账户上创建，且不附带所有者。

```graphql theme={null}
mutation CreateUserCashBalance($input: CreateUserCashBalanceInput!) {
  createUserCashBalance(input: $input) {
    userCashBalanceId
    nickname
    availableCashBalance
    status
    createdAt
  }
}
```

```json theme={null}
{
  "input": {
    "nickname": "Ada — Field Ops"
  }
}
```

保存返回的`userCashBalanceId`。该值将在步骤 3 中作为`objectId`使用。

<Info>
  为账户设置能标识所有者的昵称。所有权元数据不会在所有列表视图中展示，因此使用类似于`"Ada — Field Ops"`的昵称，可以在[`getUserCashBalances`](/features/get-spend-accounts)中无需额外查询就让账户一目了然。
</Info>

***

## 解析 userId

`assignObjectOwner`接收授权用户的\*\*`userId`**——即底层用户记录。这与`addAuthorizedUser`和[`authorizedUsers`](/features/query-authorized-user)返回的**`authUserId`\*\*不同，后者标识的是 UAC 的角色指派。

`AuthorizedUser`类型目前尚未暴露`userId`。当前文档化的获取方式如下：

| 来源                                                                       | 获取方式                                            |
| :----------------------------------------------------------------------- | :---------------------------------------------- |
| [`createVirtualCard`](/features/create-virtual-card-for-authorized-user) | 当以`authUserId`调用时，响应中的`userId`即为授权用户的用户 ID。     |
| [`registerUser`](/user-registration)                                     | 如果您的平台完成了该用户的注册，请在注册时持久化用户 ID，并将其存储到您自有的该人员记录上。 |

<Warning>
  切勿在需要`userId`的地方传入`authUserId`。二者同为`UUID`，该变更不会被类型错误捕获——您将得到失败或错向的所有权分配。
</Warning>

***

## 步骤 2 — 将授权用户指派为所有者

<Card title="受限访问" icon="lock">
  此变更需要携带`MANAGE_SUBUSERS` scope 的 Bearer 令牌。
</Card>

```graphql theme={null}
mutation AssignObjectOwner(
  $objectType: ObjectOwnerObjectType!
  $objectId: UUID!
  $userId: UUID!
) {
  assignObjectOwner(
    objectType: $objectType
    objectId: $objectId
    userId: $userId
  ) {
    success
    objectOwnerId
    accountId
    objectType
    objectId
    userId
    createdAt
    error {
      code
      message
    }
  }
}
```

### 参数

| 参数           | 类型                       | 必填 | 说明                                                                       |
| :----------- | :----------------------- | :- | :----------------------------------------------------------------------- |
| `objectType` | `ObjectOwnerObjectType!` | 是  | 使用`SPEND_ACCOUNTS`。其他可选值为`VIRTUAL_CARDS`、`GIFT_CARDS`和`FUNDING_SOURCES`。 |
| `objectId`   | `UUID!`                  | 是  | 步骤 1 返回的`userCashBalanceId`。                                             |
| `userId`     | `UUID!`                  | 是  | 授权用户的用户 ID。必须是调用方账户上的用户。不是`authUserId`。                                  |

<Note>
  `assignObjectOwner`仅能为**尚无所有者**的对象指派所有者。如果该消费账户已存在所有者，调用不会覆盖——请改用[`transferObjectOwner`](#reassigning-ownership)。
</Note>

### 示例响应

```json theme={null}
{
  "data": {
    "assignObjectOwner": {
      "success": true,
      "objectOwnerId": "3c7a1b52-9e4d-4f88-a2c1-5d6e7f8a9b01",
      "accountId": "b41e2d90-6a77-4c35-9f12-8e0d3a4b5c6d",
      "objectType": "SPEND_ACCOUNTS",
      "objectId": "f8a3c9e1-7b2d-4f5e-9c8a-1d2e3f4a5b6c",
      "userId": "f1320ac4-52dc-4c67-9e80-24e506b18450",
      "createdAt": "2026-08-10T14:22:00.000Z",
      "error": null
    }
  }
}
```

### 响应字段

| 字段              | 类型                 | 说明                                           |
| :-------------- | :----------------- | :------------------------------------------- |
| `success`       | `Boolean!`         | 是否记录了该指派。                                    |
| `objectOwnerId` | `UUID`             | 所有权记录 ID。**请保存**——`transferObjectOwner`以其为键。 |
| `accountId`     | `UUID`             | 该对象及所有者所属的账户。                                |
| `objectType`    | `String`           | 回显对象域，`SPEND_ACCOUNTS`。                      |
| `objectId`      | `UUID`             | 被指派所有者的消费账户 ID。                              |
| `userId`        | `UUID`             | 现被记录为所有者的用户。                                 |
| `createdAt`     | `DateTime`         | 创建所有权记录的时间。                                  |
| `error`         | `ObjectOwnerError` | 当`success`为`false`时的错误详情。                    |

***

## 重新分配所有权

通过[`transferObjectOwner`](/api-reference/mutations/transfer-object-owner)迁移所有权，该接口接收原始指派产生的`objectOwnerId`，而非消费账户 ID。

```graphql theme={null}
mutation TransferObjectOwner($objectOwnerId: UUID!, $userId: UUID!) {
  transferObjectOwner(objectOwnerId: $objectOwnerId, userId: $userId) {
    success
    objectOwnerId
    objectId
    userId
    updatedAt
    error {
      code
      message
    }
  }
}
```

新所有者必须是同一账户上的用户。当某位授权用户离开团队而其消费账户需要移交他人时，请调用此接口——移除授权用户并不会重新分配其所拥有的对象。

***

## 全流程

添加授权用户，为其创建消费账户，并将其记录为账户所有者。

**步骤 1 — 添加授权用户。**

```curl theme={null}
curl -X POST https://transactional-graph.staging.fluzapp.com/api/v1/graphql \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer <account_owner_access_token>" \
  -d '{
  "query": "mutation AddAuthorizedUser($email: String, $roles: [UACRoleType!]!) { addAuthorizedUser(email: $email, roles: $roles) { success authUserId roles status error { code message } } }",
  "variables": {
    "email": "ada.lovelace@example.com",
    "roles": ["SPENDER"]
  }
}'
```

仅在`status`为`ACTIVE`后继续。

**步骤 2 — 创建消费账户。**

```curl theme={null}
curl -X POST https://transactional-graph.staging.fluzapp.com/api/v1/graphql \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer <account_owner_access_token>" \
  -d '{
  "query": "mutation CreateUserCashBalance($input: CreateUserCashBalanceInput!) { createUserCashBalance(input: $input) { userCashBalanceId nickname availableCashBalance status createdAt } }",
  "variables": {
    "input": {
      "nickname": "Ada — Field Ops"
    }
  }
}'
```

**步骤 3 — 指派授权用户为所有者。**

```curl theme={null}
curl -X POST https://transactional-graph.staging.fluzapp.com/api/v1/graphql \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer <account_owner_access_token>" \
  -d '{
  "query": "mutation AssignObjectOwner($objectType: ObjectOwnerObjectType!, $objectId: UUID!, $userId: UUID!) { assignObjectOwner(objectType: $objectType, objectId: $objectId, userId: $userId) { success objectOwnerId objectId userId createdAt error { code message } } }",
  "variables": {
    "objectType": "SPEND_ACCOUNTS",
    "objectId": "f8a3c9e1-7b2d-4f5e-9c8a-1d2e3f4a5b6c",
    "userId": "f1320ac4-52dc-4c67-9e80-24e506b18450"
  }
}'
```

**步骤 4 — 充值。** 消费账户初始余额为 0。可通过[`depositCashBalance`](/features/deposit-from-external-accounts)向其存入资金，或使用[`transferInternalBalance`](/features/transfer-between-spend-accounts)从其他消费账户转入资金，目标为新的`userCashBalanceId`。

### TypeScript

```typescript theme={null}
const graphql = async (query: string, variables: Record<string, unknown>) => {
  const response = await fetch(
    'https://transactional-graph.staging.fluzapp.com/api/v1/graphql',
    {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        Authorization: `Bearer ${accessToken}`,
      },
      body: JSON.stringify({ query, variables }),
    },
  );
  return response.json();
};

// 1. Create the spend account.
const created = await graphql(
  `mutation CreateUserCashBalance($input: CreateUserCashBalanceInput!) {
     createUserCashBalance(input: $input) {
       userCashBalanceId
       nickname
       status
     }
   }`,
  { input: { nickname: 'Ada — Field Ops' } },
);

const { userCashBalanceId } = created.data.createUserCashBalance;

// 2. Record the authorized user as its owner.
const assigned = await graphql(
  `mutation AssignObjectOwner(
     $objectType: ObjectOwnerObjectType!
     $objectId: UUID!
     $userId: UUID!
   ) {
     assignObjectOwner(
       objectType: $objectType
       objectId: $objectId
       userId: $userId
     ) {
       success
       objectOwnerId
       error { code message }
     }
   }`,
  {
    objectType: 'SPEND_ACCOUNTS',
    objectId: userCashBalanceId,
    userId: authorizedUserUserId,
  },
);

// Persist objectOwnerId — transferObjectOwner is keyed on it, not on the
// spend account ID.
const { objectOwnerId } = assigned.data.assignObjectOwner;
```

***

## 错误码

| 代码          | 说明                                                     |
| :---------- | :----------------------------------------------------- |
| `ARG-0001`  | 必填输入缺失或无效——`objectId`不是调用方账户上的消费账户，或`userId`不是该账户上的用户。 |
| `AUTH-0008` | 无法将 Bearer 令牌解析为调用方。请验证令牌有效性。                          |
| `AUTH-0031` | 令牌缺少分配或转移对象所有权所需的`MANAGE_SUBUSERS` scope。              |

***

<CardGroup cols={2}>
  <Card title="授权用户概览" icon="users" href="/features/authorized-user-overview">
    角色、状态，以及授权用户的完整生命周期。
  </Card>

  <Card title="为授权用户创建虚拟卡" icon="credit-card" href="/features/create-virtual-card-for-authorized-user">
    使用`authUserId`代表授权用户发行一张卡。
  </Card>

  <Card title="消费账户概览" icon="wallet" href="/features/spend-accounts">
    创建、读取、编辑与关闭消费账户。
  </Card>

  <Card title="移除授权用户" icon="user-minus" href="/features/remove-authorized-user">
    当访问被撤销时，已拥有对象会发生什么。
  </Card>
</CardGroup>
