# API แท็กอ็อบเจกต์ (เบต้า)

แท็กอ็อบเจกต์เก็บข้อมูลแบบมีโครงสร้างเกี่ยวกับผู้ใช้ไว้ในแท็กเดียว แท็กประเภท `Object` เก็บอ็อบเจกต์หนึ่งรายการที่มีฟิลด์ตั้งชื่อไว้ เช่น `order` ล่าสุดพร้อม `amount` และ `status` แท็กประเภท `ObjectList` เก็บอาร์เรย์ของอ็อบเจกต์ดังกล่าว เช่น `orders` ทั้งหมด คุณกำหนดฟิลด์เพียงครั้งเดียว จากนั้นเขียนและอ่านค่าผ่าน API ของแท็ก และสร้างเซกเมนต์ตามค่าของฟิลด์ได้

<Aside type="caution" title="เบต้า">
แท็กอ็อบเจกต์อยู่ในช่วงเบต้าและเปิดใช้งานได้ตามคำขอ ติดต่อ [Pushwoosh Support](https://help.pushwoosh.com/hc/en-us/requests/new) เพื่อเปิดใช้งานสำหรับบัญชีของคุณ ในช่วงเบต้า แท็กอ็อบเจกต์ใช้งานได้ผ่าน API เท่านั้น:

- Control Panel ไม่มีหน้าจอสำหรับแท็กอ็อบเจกต์
- ไม่สามารถแทรกฟิลด์ของแท็กอ็อบเจกต์ลงในเนื้อหาข้อความได้
- Customer Journey ไม่รองรับแท็กอ็อบเจกต์
</Aside>

## วิธีการทำงานของแท็กอ็อบเจกต์

แท็กอ็อบเจกต์มีสคีมาและค่า สคีมาคือชื่อแท็กและรายการฟิลด์ โดยแต่ละฟิลด์มีประเภทสเกลาร์ของตัวเอง ค่าจะถูกเก็บไว้ในฟิลด์ แยกกันสำหรับผู้ใช้แต่ละคน

- **ฟิลด์** แต่ละฟิลด์คือแท็กชื่อ `<object>.<field>` เช่น `order.amount` คุณสร้างเซกเมนต์ตามฟิลด์ และเขียนฟิลด์ของแท็ก `Object` ด้วยชื่อนี้ได้
- **ค่า `Object`** อ็อบเจกต์หนึ่งรายการต่อผู้ใช้ เช่น `"order": {"amount": 120, "status": "paid"}`
- **ค่า `ObjectList`** อาร์เรย์ของอ็อบเจกต์ที่มีฟิลด์เดียวกัน เช่น `"orders": [{"amount": 120, "status": "paid"}, {"amount": 50}]`

แท็กอ็อบเจกต์เป็นแท็ก User-specific เสมอ หมายความว่าค่าเป็นของผู้ใช้ ไม่ใช่ของอุปกรณ์เครื่องใดเครื่องหนึ่ง และจะถูกคัดลอกไปยังอุปกรณ์ทั้งหมดของผู้ใช้ ดู [Tags API](/th/developer/api-reference/tags/) สำหรับแท็กทั่วไป

## กฎของสคีมา

Pushwoosh ตรวจสอบกฎเหล่านี้เมื่อคุณสร้างแท็กอ็อบเจกต์หรือเพิ่มฟิลด์:

- ชื่ออ็อบเจกต์และชื่อฟิลด์มีได้เฉพาะตัวอักษรละติน ตัวเลข และ `_`
- ชื่อฟิลด์แบบเต็ม `<object>.<field>` ยาวไม่เกิน 64 อักขระ
- แท็กอ็อบเจกต์มีฟิลด์ 1 ถึง 20 ฟิลด์ ชื่อฟิลด์ไม่คำนึงถึงตัวพิมพ์เล็กใหญ่ ดังนั้น `Amount` และ `amount` จึงเป็นฟิลด์เดียวกัน
- ฟิลด์มีประเภทใดประเภทหนึ่งต่อไปนี้: `INTEGER`, `STRING`, `LIST`, `DATE`, `BOOLEAN`, `PRICE`, `VERSION` ไม่รองรับฟิลด์เขตเวลาและอ็อบเจกต์ซ้อน
- ฟิลด์ไม่สามารถใช้ชื่อที่แท็กอื่นใช้อยู่แล้ว หากมีแท็ก `order.note` อยู่ การเพิ่มฟิลด์ `note` ให้กับ `order` จะล้มเหลวพร้อม `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` เฉพาะกับแท็กทั่วไป เนื่องจากเมธอดนี้กำหนดฟิลด์ไม่ได้

### Base URL

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

### การรับรองความถูกต้อง

ทุกคำขอต้องมี `Authorization` header พร้อมกับ [Server API token](/th/developer/api-reference/api-access-token/#server-api-token) ของคุณ หากใช้ Device API token จะได้รับ HTTP 403 `a device API token cannot call this method, use an account API token`

```
Authorization: Api YOUR_API_TOKEN
```

### Endpoints

เมธอดเหล่านี้ใช้จัดการสคีมาของแท็กอ็อบเจกต์:

| เมธอด | พาธ | คำอธิบาย |
| :---- | :---- | :---- |
| `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` | คำขอใช้ Device API token |
| 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`](/th/developer/api-reference/device-api/#settags) และ [`/bulkSetTags`](/th/developer/api-reference/audience-api/#bulksettags) เมธอดเหล่านี้ใช้การรับรองความถูกต้องแบบเดิมของตัวเอง: `/setTags` รับ Device API token ใน header `Authorization: Token` และ `/bulkSetTags` รับ API access token ใน `auth` ส่งค่า `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 timestamp เป็นวินาที |
| `BOOLEAN` | `true` หรือ `false` |
| `PRICE` | `19.95` |
| `VERSION` | `"1.0.0"` |

### การเขียนจะแทนที่ค่าทั้งหมด

การเขียนแต่ละครั้งจะแทนที่ค่าที่เก็บไว้ของแท็กอ็อบเจกต์:

- ฟิลด์ที่ไม่มีในค่าใหม่จะถูกลบ ฟิลด์ที่ส่งเป็น `null` ก็จะถูกลบเช่นกัน
- `null`, `[]` หรือ `""` จะลบค่าทั้งหมด
- `{}` จะลบฟิลด์ทั้งหมดของแท็ก `Object`
- คีย์ที่ไม่ใช่ฟิลด์ของอ็อบเจกต์จะถูกข้าม
- หากค่าของฟิลด์ไม่ตรงกับประเภทฟิลด์ ค่าทั้งหมดของแท็กจะถูกปฏิเสธ และค่าที่เก็บไว้จะยังคงเหมือนเดิม

`/setTags` จะไม่แจ้งค่าที่ถูกปฏิเสธในการตอบกลับ ตรวจสอบผลลัพธ์ด้วย [`/getTags`](#read-values) หรือใช้ `/bulkSetTags` ซึ่งจะแจ้งผลลัพธ์สำหรับแต่ละแท็ก

### เขียนฟิลด์เดียวของ Object

เขียนฟิลด์เดียวของแท็ก `Object` ด้วยชื่อเต็มของฟิลด์ `increment` และการดำเนินการอื่นๆ ของ [`/setTags`](/th/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` [สถานะ `/bulkSetTags`](/th/developer/api-reference/audience-api/#bulksettags-status) จะส่งคืนผลลัพธ์อย่างใดอย่างหนึ่งต่อไปนี้สำหรับแท็กอ็อบเจกต์ นอกเหนือจาก `ok`, `tag not found` และ `wrong value. expect :<type>`:

| ผลลัพธ์ | สาเหตุ |
| :---- | :---- |
| `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`](/th/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 timestamp เป็นวินาที, Price เป็นสตริง เช่น `"120.00"` และ Boolean เป็น `1` หรือ `0`

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

## สร้างเซกเมนต์ตามฟิลด์

ตั้งเงื่อนไขของเซกเมนต์บนฟิลด์ ไม่ใช่บนตัวแท็กอ็อบเจกต์เอง ใน [ภาษาสำหรับการแบ่งส่วน](/th/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"`
- **`notin` บนฟิลด์ List** ไม่มีองค์ประกอบใดที่มีค่าใดๆ ในค่าที่กำหนด
- **`notin` บนฟิลด์ประเภทอื่น** มีองค์ประกอบอย่างน้อยหนึ่งรายการที่มีค่าในฟิลด์นี้ซึ่งไม่อยู่ในค่าที่กำหนด

`notset` และ `notin` บนฟิลด์ List จะไม่ผูกกับองค์ประกอบเดียวกันกับเงื่อนไขอื่นที่เชื่อมด้วย AND

## ส่งออกเซกเมนต์ที่มีแท็กอ็อบเจกต์

[`/exportSegment`](/th/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` ระบุแท็กอ็อบเจกต์ ฟิลด์ทั้งหมดของแท็กนั้นจะถูกส่งออก