# 对象标签 API (Beta)

对象标签可在一个标签中存储有关用户的结构化数据。`Object` 类型的标签保存一个带有命名字段的对象，例如最近一笔 `order` 及其 `amount` 和 `status`。`ObjectList` 类型的标签保存此类对象的数组，例如所有 `orders`。您只需定义一次字段，即可通过标签 API 写入和读取值，并按字段值构建细分。

<Aside type="caution" title="Beta">
对象标签目前处于 Beta 阶段，需申请开通。请联系 [Pushwoosh Support](https://help.pushwoosh.com/hc/en-us/requests/new) 为您的账户启用该功能。在 Beta 期间，对象标签只能通过 API 使用：

- 控制面板中没有对象标签的界面。
- 对象标签的字段无法插入消息内容。
- Customer Journey 不支持对象标签。
</Aside>

## 对象标签的工作原理

对象标签包含架构和值。架构是标签名称和字段列表，每个字段都有自己的标量类型。值存储在字段中，每个用户单独存储。

- **字段。** 每个字段都是一个名为 `<object>.<field>` 的标签，例如 `order.amount`。您可以按字段进行细分，也可以通过此名称写入 `Object` 标签的字段。
- **`Object` 值。** 每个用户一个对象，例如 `"order": {"amount": 120, "status": "paid"}`。
- **`ObjectList` 值。** 具有相同字段的对象数组，例如 `"orders": [{"amount": 120, "status": "paid"}, {"amount": 50}]`。

对象标签始终是 User-specific 标签，即值属于用户而非单个设备，并会复制到该用户的所有设备上。有关常规标签，请参阅 [标签 API](/zh/developer/api-reference/tags/)。

## 架构规则

在您创建对象标签或添加字段时，Pushwoosh 会检查以下规则：

- 对象名称和字段名称只能包含拉丁字母、数字和 `_`。
- 完整字段名称 `<object>.<field>` 不超过 64 个字符。
- 一个对象标签有 1 到 20 个字段。字段名称不区分大小写，因此 `Amount` 和 `amount` 是同一个字段。
- 字段的类型为以下之一：`INTEGER`、`STRING`、`LIST`、`DATE`、`BOOLEAN`、`PRICE`、`VERSION`。不支持时区字段和嵌套对象。
- 字段不能使用其他标签已有的名称。如果标签 `order.note` 已存在，向 `order` 添加字段 `note` 会失败并返回 `tag order.note already exists`。
- 对象标签不能删除其最后一个字段。请改为删除该对象标签。
- 正在使用的字段或对象标签无法删除。错误信息会列出其使用位置，例如 `field is in use: segment Big buyers, in-app Spring sale`。引用该标签的细分、In-App、正在运行的 Customer Journey 活动、预设、电子邮件模板及其他项目均计为使用。

## 管理架构

使用以下 REST 方法创建和更改对象标签。`/json/1.3/addTag` 仅用于常规标签，因为它无法定义字段。

### 基础 URL

```
https://rpc-api.svc-nue.pushwoosh.com
```

### 身份验证

每个请求都必须包含一个 `Authorization` 标头，其中包含您的 [服务器 API 令牌](/zh/developer/api-reference/api-access-token/#server-api-token)。使用设备 API 令牌会返回 HTTP 403 `a device API token cannot call this method, use an account API token`。

```
Authorization: Api YOUR_API_TOKEN
```

### 端点

以下方法用于管理对象标签的架构：

| 方法 | 路径 | 描述 |
| :---- | :---- | :---- |
| `POST` | `/api/tags` | 创建对象标签及其字段 |
| `GET` | `/api/tags/{name}` | 获取对象标签及其字段 |
| `GET` | `/api/tags` | 列出标签。对象标签会附带其 `fields` 返回，字段不会作为单独的标签列出 |
| `POST` | `/api/tags/{name}/fields` | 添加字段 |
| `DELETE` | `/api/tags/{name}/fields/{field}` | 删除字段 |
| `DELETE` | `/api/tags/{name}` | 删除对象标签及其所有字段 |

`POST /api/tags` 和两个字段方法会返回更改后的对象标签，包装在 `{"tag": {...}}` 中。`DELETE /api/tags/{name}` 返回 `{}`。

如果您的账户未启用对象标签，使用对象类型调用 `POST /api/tags` 以及两个字段方法都会失败并返回 HTTP 403 `object tags are not enabled for the account`。`GET /api/tags/{name}` 和 `DELETE /api/tags/{name}` 返回 HTTP 404 `tag not found`，`GET /api/tags` 不会列出对象标签。未启用对象标签时，您无法创建对象标签，因此也就没有可写入值的对象。

<span id="create-an-object-tag" />

### 创建对象标签

`POST` `/api/tags`

##### 请求正文

| 名称 | 类型 | 描述 |
| :---- | :---- | :---- |
| name\* | string | 对象标签的名称。 |
| type\* | string | `OBJECT` 表示单个对象，`OBJECT_LIST` 表示对象列表。 |
| fields\* | array | 对象的字段，1 到 20 个。 |
| fields[].name\* | string | 字段名称。该字段会成为标签 `<name>.<fields[].name>`。 |
| fields[].type\* | string | 字段类型：`INTEGER`、`STRING`、`LIST`、`DATE`、`BOOLEAN`、`PRICE` 或 `VERSION`。 |

```json title="示例"
{
  "name": "orders",
  "type": "OBJECT_LIST",
  "fields": [
    { "name": "amount", "type": "INTEGER" },
    { "name": "status", "type": "STRING" }
  ]
}
```

```json title="响应"
{
  "tag": {
    "name": "orders",
    "type": "OBJECT_LIST",
    "system": false,
    "user_specific": true,
    "has_right_ace_modify": true,
    "fields": [
      { "name": "amount", "type": "INTEGER" },
      { "name": "status", "type": "STRING" }
    ]
  }
}
```

无论 `user_specific` 的值是什么，该标签都会创建为 User-specific 标签。`GET /api/tags/{name}` 返回相同的对象，但不带 `tag` 包装。字段按名称排序。

### 添加字段

`POST` `/api/tags/{name}/fields`

```json title="示例：POST /api/tags/orders/fields"
{
  "field": { "name": "note", "type": "STRING" }
}
```

您可以立即将新字段作为整个标签值的一部分写入。如果对象标签已有 20 个字段，或已存在具有该完整字段名称的标签，添加字段会失败，请参阅 [错误](#errors)。

### 删除字段

`DELETE` `/api/tags/{name}/fields/{field}`

例如，`DELETE /api/tags/orders/fields/note` 会删除字段 `orders.note`。您无法删除最后一个字段或正在使用的字段，请参阅 [错误](#errors)。

<span id="errors" />

### 错误

错误的格式为 `{"code": <n>, "message": "...", "details": [...]}`。创建对象标签时，400 错误消息以 `validate: ` 开头。

| HTTP 状态 | 消息 | 原因 |
| :---- | :---- | :---- |
| 400 | `object tag and field names must contain only letters, digits and underscores` | 名称包含其他字符。 |
| 400 | `an object tag needs 1 to 20 fields` | 创建时没有字段或字段超过 20 个。 |
| 400 | `an object tag has at most 20 fields` | 对象标签已有 20 个字段。 |
| 400 | `field <name> is given twice` | 两个字段名称相同（不区分大小写）。 |
| 400 | `<object>.<field>: object and field names together cannot be longer than 64 characters` | 完整字段名称过长。 |
| 400 | `<object>.<field>: field type must be INTEGER, STRING, LIST, DATE, BOOLEAN, PRICE or VERSION` | 不支持的字段类型。 |
| 400 | `an object tag needs at least one field; delete the object tag instead` | 您尝试删除最后一个字段。 |
| 400 | `field is in use: ...`, `tag is in use: ...` | 该字段或对象标签正在被细分、In-App、Customer Journey 活动或其他项目使用。 |
| 403 | `object tags are not enabled for the account` | 该账户未启用对象标签。 |
| 403 | `a device API token cannot call this method, use an account API token` | 请求使用了设备 API 令牌。 |
| 404 | `tag not found` | `GET` 或 `DELETE /api/tags/{name}`：不存在该标签、该名称是字段，或未启用对象标签。 |
| 404 | `object tag not found`, `field not found` | 字段方法：不存在该对象标签或字段。 |
| 409 | `tag already exists` | 已存在与对象标签同名的标签。 |
| 409 | `tag <object>.<field> already exists` | 已存在与该字段完整名称同名的标签。 |

## 写入值

使用 [`/setTags`](/zh/developer/api-reference/device-api/#settags) 和 [`/bulkSetTags`](/zh/developer/api-reference/audience-api/#bulksettags) 写入对象标签值。这些方法保留各自的身份验证方式：`/setTags` 在 `Authorization: Token` 标头中使用设备 API 令牌，`/bulkSetTags` 在 `auth` 中使用 API 访问令牌。将 `Object` 值作为 JSON 对象发送，将 `ObjectList` 值作为对象数组发送：

```json title="示例：/json/1.3/setTags"
{
  "request": {
    "application": "XXXXX-XXXXX",
    "userId": "user_42",
    "tags": {
      "order": { "amount": 120, "status": "paid" },
      "orders": [
        { "amount": 120, "status": "paid" },
        { "amount": 50 }
      ]
    }
  }
}
```

如果按 HWID 设置值，该值会存储在该设备所属的用户上。

每个字段接受其类型格式的值：

| 字段类型 | 示例值 |
| :---- | :---- |
| `INTEGER` | `120` |
| `STRING` | `"paid"` |
| `LIST` | `["gift", "express"]` |
| `DATE` | `"2026-10-02 22:11"`（UTC）、`"2026-10-02"` 或以秒为单位的 Unix 时间戳 |
| `BOOLEAN` | `true` 或 `false` |
| `PRICE` | `19.95` |
| `VERSION` | `"1.0.0"` |

### 写入会替换整个值

每次写入都会替换对象标签的已存储值：

- 新值中缺少的字段会被删除。以 `null` 发送的字段也会被删除。
- `null`、`[]` 或 `""` 会删除整个值。
- `{}` 会删除 `Object` 标签的所有字段。
- 不属于该对象字段的键会被跳过。
- 如果某个字段值与字段类型不匹配，则该标签的整个值都会被拒绝，已存储的值保持不变。

`/setTags` 不会在响应中报告被拒绝的值。请使用 [`/getTags`](#read-values) 检查结果，或使用 `/bulkSetTags`，它会为每个标签报告结果。

### 写入 Object 的单个字段

按完整名称写入 `Object` 标签的单个字段。`increment` 以及 [`/setTags`](/zh/developer/api-reference/device-api/#settags) 的其他操作都适用于字段：

```json
"tags": {
  "order.status": "shipped",
  "order.amount": { "operation": "increment", "value": 5 }
}
```

`ObjectList` 标签的字段无法单独写入，因为无法确定要更改哪个元素。`/setTags` 会忽略此类值。请改为发送整个列表。

### 对象标签不会自动创建

如果某个标签不存在，为其传入对象或对象数组的值会被跳过。标签自动创建不会创建对象标签，因此请先使用 [`POST /api/tags`](#create-an-object-tag) 创建架构。

### bulkSetTags 结果

使用 `detailed=true` 时，除 `ok`、`tag not found` 和 `wrong value. expect :<type>` 外，[`/bulkSetTags` 状态](/zh/developer/api-reference/audience-api/#bulksettags-status) 还会为对象标签返回以下结果之一：

| 结果 | 原因 |
| :---- | :---- |
| `object tags are stored on the user only` | 对象标签不是 User-specific 标签。 |
| `cannot load the fields of the object` | Pushwoosh 无法读取对象标签的字段。 |
| `wrong value. expect an object (a map) or, for a list of objects, an array of maps` | 值不适用于该对象标签，或某个字段值与其类型不匹配。 |
| `a field of a list of objects cannot be set on its own, set the whole list` | 值被设置到 `ObjectList` 标签的字段上。 |

<span id="read-values" />

## 读取值

[`/getTags`](/zh/developer/api-reference/device-api/#gettags) 将 `Object` 值作为 JSON 对象返回，将 `ObjectList` 值作为对象数组返回：

```json title="响应"
{
  "status_code": 200,
  "status_message": "OK",
  "response": {
    "result": {
      "order": { "amount": 120, "status": "paid" },
      "orders": [
        { "amount": 120, "status": "paid" },
        { "amount": 50 }
      ]
    }
  }
}
```

字段值的返回方式与 `/getTags` 中常规标签值相同：Date 以秒为单位的 Unix 时间戳返回，Price 以字符串形式返回（例如 `"120.00"`），Boolean 以 `1` 或 `0` 返回。

<span id="segment-by-fields" />

## 按字段细分

请对字段设置细分条件，而不是对对象标签本身设置。在 [细分语言](/zh/developer/api-reference/segmentation-filters-api/segmentation-language/) 中，字段是名称中带点的标签，并使用其自身类型的运算符：

```
AT("XXXXX-XXXXX", "order.amount", gte, 100)
```

对对象标签本身设置的条件会失败并返回 `object tag has no value of its own, use one of its fields`。

### ObjectList 上的条件

当列表中至少有一个元素符合条件时，`ObjectList` 标签字段上的条件即匹配该用户。用 AND 连接的同一列表字段上的条件必须匹配同一个元素：

```
AT("XXXXX-XXXXX", "orders.amount", gte, 100) * AT("XXXXX-XXXXX", "orders.status", eq, "paid")
```

此细分包含至少有一笔订单同时满足金额为 100 或以上且已支付的用户。如果用户有一笔未支付的大额订单和一笔已支付的小额订单，则不匹配。用 OR 连接的条件不必匹配同一个元素。两个不同列表上的条件会在各自的列表上分别检查。

某些运算符在 `ObjectList` 标签的字段上的工作方式不同：

- **`notset`。** 列表中没有任何元素在此字段中有值。没有该列表的用户也匹配。
- **`noteq`。** 至少有一个元素在此字段中有值，且该值与给定值不同。没有该字段的元素不计入。订单状态为 `paid` 和 `new` 的用户匹配 `noteq "paid"`。
- **List 字段上的 `notin`。** 没有任何元素包含任一给定值。
- **其他类型字段上的 `notin`。** 至少有一个元素在此字段中的值不在给定值之中。

`notset` 和 List 字段上的 `notin` 不会与用 AND 连接的其他条件绑定到同一个元素。

## 导出包含对象标签的细分

[`/exportSegment`](/zh/developer/api-reference/segmentation-filters-api/#exportsegment) 会导出对象标签的字段：

- **CSV。** 每个字段是一个名为 `<object>.<field>` 的单独列，对象标签本身没有列。ObjectList 的字段以 JSON 数组形式保存所有元素中的不同值，与 List 标签相同：`["120","50"]`。
- **`json` 和 `json_each_line`。** `Object` 标签的字段以名为 `<object>.<field>` 的单独标签形式出现。`ObjectList` 标签作为 `tags` 中的一个条目出现，其元素位于 `object_list.items` 中。每个元素以短名称列出其字段，每个字段的值位于以其类型命名的键下：`integer`、`string`、`list`、`date`、`boolean`、`price` 或 `version`。整数值以字符串形式返回：

```json
{
  "name": "orders",
  "object_list": {
    "items": [
      { "fields": [{ "name": "amount", "integer": "120" }, { "name": "status", "string": "paid" }] },
      { "fields": [{ "name": "amount", "integer": "50" }] }
    ]
  }
}
```

如果 `tagsList` 指定了对象标签，则会导出其所有字段。