# API keys {#api-keys}

::: info หน้านี้สอนอะไร
การให้สคริปต์ แอปอื่น และผู้ช่วย AI มี key ของตัวเองสำหรับเรียก REST API โดยจำกัดเฉพาะ collection
และสิ่งที่ต้องทำ

**ควรอ่านก่อน:** [ผู้ใช้และการยืนยันตัวตน](./auth) และ [REST API](./rest-api)
:::

<Screenshot name="api-keys" alt="API key ใหม่พร้อมสิทธิ์ที่ติ๊กไว้" />

คน login ด้วยรหัสผ่านแล้วได้ session แต่สคริปต์ไม่ควรใช้รหัสผ่านของใคร ให้ใช้ API key แทน key เป็นของผู้ใช้คนหนึ่ง
ทำได้ไม่เกินสิทธิ์ของผู้ใช้คนนั้น และจำกัดให้แคบลงได้อีก เช่น "อ่านและสร้างบทความได้ อย่างอื่นไม่ได้"

## เปิดใช้ {#turn-them-on}

```ts
export default defineConfig({
  // …
  apiKeys: true,
})
```

จะมีหน้า **API keys** ใต้ **ตั้งค่า** ในหน้า admin และตารางสำหรับเก็บ key บน production ให้รัน
`easy-cms migrate:create api-keys` แล้ว deploy migration

## สร้าง key {#create-a-key}

1. ในหน้า admin เปิด **ตั้งค่า → API keys** แล้วกด **สร้างใหม่**
2. ตั้งชื่อที่บอกว่าใครใช้ ("ตัวนำเข้าจดหมายข่าว", "Claude")
3. ติ๊กสิ่งที่ key ทำได้ ทีละ collection และ global การติ๊กสร้าง แก้ไข ลบ หรือเผยแพร่ จะติ๊กอ่านให้ด้วย
4. ตั้งวันหมดอายุได้ (ไม่บังคับ) แล้วกด **บันทึก**

key จะแสดง**ครั้งเดียว**ในกล่องข้อความ ให้คัดลอกไปเก็บในค่าลับของแอปหรือโปรแกรมจัดการรหัสผ่าน Easy CMS เก็บไว้แค่ค่า
hash ถ้า key หายจะแสดงอีกไม่ได้ ให้ลบแล้วสร้างใหม่

key มีรูปแบบ `ecms_1a2b3c4d_…` ส่วนหลัง `ecms_` จะแสดงในรายการเป็น **ขึ้นต้นด้วย** เพื่อแยกว่าเป็น key ไหน

## ใช้งาน {#use-it}

ส่ง key เป็น Bearer token ไม่ต้องใช้ cookie และ CSRF token:

```bash
curl https://example.com/api/cms/posts?draft=true \
  -H "Authorization: Bearer $EASY_CMS_KEY"

curl -X POST https://example.com/api/cms/posts \
  -H "Authorization: Bearer $EASY_CMS_KEY" \
  -H 'content-type: application/json' \
  -d '{"title":"Imported"}'
```

| คำตอบ | เมื่อ |
|---|---|
| `401 Invalid or expired API key` | key ผิด ถูกลบ หมดอายุ หรือเจ้าของถูกปิดใช้งาน |
| `403 This API key may not create "posts"` | key ไม่ได้รับอนุญาตให้ทำสิ่งนั้น |
| `403 Forbidden` | key อนุญาต แต่กฎสิทธิ์ของเจ้าของไม่อนุญาต |

## key ทำอะไรได้บ้าง {#what-a-key-may-do}

แต่ละแถวในตารางคือ collection หรือ global แต่ละคอลัมน์คือการกระทำ:

| การกระทำ | Collection | Global |
|---|---|---|
| **อ่าน** | ดูรายการและอ่านเอกสาร รวมฉบับร่าง | อ่าน |
| **สร้าง** | สร้างเอกสาร สำหรับ **คลังสื่อ** คือการอัปโหลดไฟล์ | — |
| **แก้ไข** | แก้เอกสาร ดูประวัติ ย้อนกลับเวอร์ชัน | แก้ |
| **ลบ** | ลบเอกสาร | — |
| **เผยแพร่** | เผยแพร่ ยกเลิกการเผยแพร่ และตั้งเวลา (collection ที่มี drafts) | เหมือนกัน |

- **key และเจ้าของตัดสินร่วมกัน** key ทำให้สิทธิ์ของเจ้าของแคบลง แต่[กฎสิทธิ์](./access-control)ของเจ้าของยังใช้อยู่
  key ของ editor ที่ติ๊ก "ลบ" จะลบสิ่งที่ editor ลบไม่ได้ก็ไม่ได้
- **ไม่ได้ติ๊กคือไม่อนุญาต** รวมถึง collection ที่เพิ่มมาทีหลัง
- **key เข้าถึงผู้ใช้และ key อื่นไม่ได้** collection เหล่านั้นไม่อยู่ในตาราง และ key สร้าง key ไม่ได้
- **บันทึกเป็นฉบับร่างไม่ต้องมีสิทธิ์เผยแพร่** key ที่สร้างและแก้ได้แต่เผยแพร่ไม่ได้ จะเขียนฉบับร่างให้คนตรวจแล้วเผยแพร่
- **โฟลเดอร์สื่อ** ([`upload.folders`](./uploads#folders)): key ที่ใช้ Media ได้ ใช้ได้ทุกโฟลเดอร์ หรือ **เฉพาะบางโฟลเดอร์**
  รวมโฟลเดอร์ย่อย key ที่จำกัดไว้จะเห็นและแก้เฉพาะไฟล์ในโฟลเดอร์เหล่านั้น อัปโหลดได้เฉพาะที่นั่น และอ่าน
  [ไฟล์ส่วนตัว](./uploads#private-folders)ได้เฉพาะในโฟลเดอร์เหล่านั้น

## จัดการ key {#manage-keys}

- **ใช้ล่าสุด** บอกเวลาที่ key ถูกใช้ครั้งล่าสุด (อัปเดตอย่างมากนาทีละครั้ง)
- **หมดอายุ** หลังเวลานี้ key จะได้ `401`
- **เพิกถอน** key ด้วยการลบ ใช้ไม่ได้ทันที
- การปิดใช้งาน (หรือลบ) ผู้ใช้จะทำให้ key ของคนนั้นใช้ไม่ได้ด้วย
- admin เห็นทุก key ผู้ใช้อื่นเห็นและจัดการได้เฉพาะของตัวเอง

## ในโค้ด {#in-code}

สร้าง key จากสคริปต์หรือ seed ด้วย Local API ได้ key จะถูกคืนมาครั้งเดียว:

```ts
const { key } = await cms.createApiKey({
  name: 'Newsletter importer',
  user: admin.id, // เจ้าของ ถ้าเรียกด้วย { user, overrideAccess: false } จะเป็นผู้ใช้คนนั้น
  permissions: { collections: { posts: ['read', 'create'] } },
  expiresAt: '2027-01-01T00:00:00Z',
})
```

request ที่ใช้ key จะมี `apiKey` อยู่ใน user กฎสิทธิ์และ hook จึงแยกได้:

```ts
access: {
  // คนลบบทความได้ แต่ key ลบไม่ได้ ไม่ว่าจะติ๊กอะไรไว้
  delete: ({ user }) => !!user && !user.apiKey,
}
```

## ความปลอดภัย {#security}

- ส่วนที่เป็นความลับคือ byte สุ่ม 32 ตัว Easy CMS เก็บค่า hash แบบ SHA-256 และเทียบค่าแบบใช้เวลาคงที่
- ดูแล key เหมือนรหัสผ่าน: เก็บใน environment variable หรือระบบจัดการความลับ อย่าใส่ในโค้ดหรือใน browser
- ให้แต่ละแอปมี key ของตัวเองที่มีสิทธิ์น้อยที่สุด และตั้งวันหมดอายุเมื่อทำได้ การเปลี่ยน key ทำโดยสร้าง key ใหม่
  เปลี่ยนแอปไปใช้ แล้วลบ key เก่า

## ขั้นต่อไป {#next-steps}

- [REST API](./rest-api): endpoint ทั้งหมดที่ key เรียกได้
- [Plugins](./plugins): plugin MCP ใช้ API key สำหรับผู้ช่วย AI
