ข้ามไปยังเนื้อหา
Dee Wan 1.0 ยังอยู่ระหว่างพัฒนาและยังไม่เปิดตัว (pre-MVP) — ดู ขอบเขตผลิตภัณฑ์ 1.0

API จัดการ CMS

หลัง 1.0 API ที่มีเวอร์ชันสำหรับสคริปต์ บรรทัดคำสั่ง และเอเจนต์ AI ที่จัดการเนื้อหา โมเดล และการตั้งค่าของไซต์หนึ่งไซต์ ใช้บริการ lifecycle การ apply โมเดล และ handler ชุดเดียวกับหน้าผู้ดูแล ไม่ใช่ CRUD API ของแบ็กเอนด์ที่สร้างขึ้น การเปลี่ยนโมเดลทำได้โดยตรงด้วย schema:write หรือเสนอให้ผู้ใช้นำไปใช้ใน ตัวสร้างเนื้อหา

  • สัญญาที่เครื่องอ่านได้: openapi-v1.json (OpenAPI 3.1)
  • ชนิดข้อมูล TypeScript: backend/src/lib/management/client-v1.generated.ts
  • บรรทัดคำสั่งและเซิร์ฟเวอร์ MCP: dee-wan-manage.mjs ดาวน์โหลดได้จาก การตั้งค่า → API
  • MCP ระยะไกล: POST /api/manage/mcp การดำเนินการชุดเดียวกันผ่าน HTTPS โดยไม่ต้องดาวน์โหลด
  • ทั้งสามสร้างจาก backend/src/lib/management/contract.ts ด้วย npm run docs:management-api และ npm run check:management-api จะล้มเหลวเมื่อไฟล์ใดไฟล์หนึ่งล้าสมัย

สร้างได้ที่ การตั้งค่า → API → ข้อมูลประจำตัวสำหรับจัดการ ต้องมีสิทธิ์ site:settings บนไซต์

  • ตั้งชื่อ เลือกขอบเขตที่ใช้ได้ และเลือกโมเดลที่แตะได้หากต้องการ
  • ให้ได้เฉพาะสิ่งที่คุณมีอยู่ตอนนี้ ขอบเขตหรือโมเดลที่คุณไม่มีจะถูกปฏิเสธด้วย 422 grant_not_held พร้อมรายชื่อ และฟอร์มจะปิดตัวเลือกเหล่านั้น
  • secret แสดงครั้งเดียว ระบบเก็บเฉพาะ digest SHA-256 ของมัน คัดลอกก่อนปิดแผง
  • รูปแบบ: dwm_ ตามด้วยอักขระ base64url 43 ตัว
  • เพิกถอนได้ที่หน้าจอเดียวกัน secret ที่ถูกเพิกถอนจะใช้ไม่ได้ตั้งแต่คำขอถัดไป
  • ไม่มีตัวนับการใช้งานหรือเวลาที่ใช้ล่าสุด

ข้อมูลรับรองทำงานในนามผู้สร้าง ทุกคำขอ API จะอ่านสิทธิ์ปัจจุบันของผู้นั้นบนไซต์อีกครั้ง แล้วจำกัดให้เหลือเฉพาะส่วนที่ข้อมูลรับรองอนุญาตด้วย:

  • ขอบเขต จำกัดว่าเรียกคำสั่งใดได้
  • โมเดล (ถ้าเลือก) จำกัดว่าอ่านหรือเขียนโมเดลเนื้อหาใดได้
  • บทบาทของผู้สร้าง จำกัดส่วนที่เหลือ รวมถึงกฎความเป็นเจ้าของ update:own สิทธิ์ที่แต่ละการเปลี่ยนสถานะ workflow ต้องการ และข้อจำกัดฟิลด์และภาษาของบทบาท (บุคคลและสิทธิ์เข้าถึง): การแสดงรายการและการอ่านรายการไม่รวมภาษาและฟิลด์ที่บทบาทอ่านไม่ได้ และการเขียนนอกบทบาทจะตอบ field_denied หรือ locale_denied

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

ขอบเขต อนุญาต ต้องมีจากผู้สร้าง
content:read อ่านไซต์ โมเดล รายการ เวอร์ชัน รายการที่อ้างอิง ข้อเสนอ หน้า และสื่อ รวมอยู่เสมอ read บนโมเดล
content:write สร้างและแก้ไขฉบับร่าง เพิ่มภาษา กู้คืนเวอร์ชันเก่า เปลี่ยนรายการที่ลิงก์ (relate, related from) create / update บนโมเดล และ content:restore สำหรับการกู้คืนเวอร์ชัน
content:propose เสนอการเปลี่ยนฟิลด์ให้บรรณาธิการตรวจ read บนโมเดล
pages:write บันทึกในตัวแก้ไขหน้า update บนโมเดลหน้า
media:write อัปโหลดสื่อ แก้ไขข้อความแทนภาพ จุดโฟกัส การโหลด และลำดับความสำคัญในการดึงของไฟล์ ลบไฟล์ที่ไม่ได้ใช้ และสร้าง เปลี่ยนชื่อ ย้าย และลบโฟลเดอร์ในคลังสื่อ รวมถึงย้ายไฟล์ระหว่างโฟลเดอร์ media:upload สำหรับอัปโหลด media:update สำหรับแก้ไขและโฟลเดอร์ media:delete สำหรับลบ
lifecycle:publish เผยแพร่และยกเลิกการเผยแพร่ โดยไม่ต้องมีคนยืนยันซ้ำ update และสิทธิ์ของการเปลี่ยนสถานะ
content:trash ย้ายรายการหรือภาษาเดียวไปถังขยะ และกู้คืน delete บนโมเดล
content:purge ลบรายการในถังขยะถาวร delete บนโมเดล
workflow:move ขั้นตอน workflow ที่ไม่เผยแพร่และไม่ยกเลิกการเผยแพร่ (ส่งตรวจทาน) update และสิทธิ์ของขั้นตอน
workflow:assign กำหนดผู้ดำเนินการถัดไป และดูรายชื่อผู้ที่กำหนดได้ update บนโมเดล
settings:write อ่านและเปลี่ยนการตั้งค่าไซต์: การเก็บเวอร์ชันและขั้นตอนตรวจทาน การใช้ภาษาสำรองเมื่อไม่มีคำแปล ไฟล์สื่อ การค้นหา ค่าเริ่มต้นของ Site Chrome, SEO, robots, การเปลี่ยนเส้นทาง และเมนู site:settings ยกเว้นการค้นหาที่ใช้ site:infra แทน
schema:propose เสนอโมเดลใหม่หรือการเปลี่ยนโมเดลให้ผู้ใช้ตรวจ แสดงรายการและอ่านข้อเสนอเหล่านั้น model:apply
schema:write สร้าง เปลี่ยน แทนที่ และลบโมเดลโดยตรง model:apply สำหรับการลบต้องมี model:delete ด้วย
data:import เรียกใช้การนำเข้าตั้งแต่ต้นจนจบ: แหล่งข้อมูล งาน การอัปโหลด การเดินหน้า การจับคู่ การหยุดชั่วคราว การทำต่อ การยกเลิก การย้อนกลับ บันทึกผลการนำเข้า และไฟล์ปัญหา ผลแต่ละอย่างยังถูกตรวจกับสิทธิ์ของผู้สร้างเอง data:import และผลแต่ละอย่างต้องมีสิ่งที่การนำเข้าในหน้าแอดมินต้องใช้ด้วย (การเขียนโมเดลและรายการ media:upload model:apply site:settings และการเผยแพร่)
data:export สร้างการส่งออก สร้างไฟล์ และอ่านไฟล์เป็นช่วงไบต์ data:export
roles:write สร้าง เปลี่ยน และลบบทบาทของไซต์ รวมถึงข้อจำกัดฟิลด์และภาษา ข้อมูลรับรองมอบได้เฉพาะสิ่งที่ตนมี role:manage
plugins:use แสดงปลั๊กอินที่ติดตั้ง สร้างลิงก์ใช้ครั้งเดียวที่เปิดหน้าของปลั๊กอินหรือการทำงานกับรายการ และเรียกใช้การดำเนินการที่ปลั๊กอินประกาศไว้ สิ่งเดียวกับที่การเปิดหรือการเรียกใช้แบบนี้ต้องใช้ในหน้าแอดมิน: read บนรายการ (update สำหรับการดำเนินการที่ทำลายข้อมูล) หรือโมเดลที่ให้สิทธิ์ปลั๊กอินซึ่งผู้สร้างอ่านได้ รวมถึงอ่านได้เฉพาะรายการของตัวเอง; การดำเนินการระดับไซต์ต้องมี site:plugins

/roles แสดงบทบาทของไซต์พร้อมชุดสิทธิ์ทั้งหมด: models, system และ limits ของฟิลด์และภาษาที่ไม่บังคับ (บุคคลและสิทธิ์เข้าถึง) การเขียนผ่านการตรวจเดียวกับ การตั้งค่า → บทบาท: สิทธิ์ที่ไม่รู้จักตอบ 400 invalid_permissions พร้อมทุกเหตุผล และชุดที่เกินข้อมูลรับรองตอบ 403 escalation_refused เพดานคือสิทธิ์ที่ถูกจำกัดของข้อมูลรับรองเอง ไม่ใช่บทบาททั้งหมดของบุคคลนั้น: ข้อมูลรับรองที่ไม่มี content:write ให้ update ไม่ได้ และที่ไม่มี lifecycle:publish ให้ content:publish ไม่ได้ wildcard ของโมเดล ("*") ไม่อยู่ในเพดานของข้อมูลรับรองเลย PUT /roles/{id} แทนที่เฉพาะฟิลด์ที่ระบุ ส่วน permissions แทนที่ทั้งชุด การลบปฏิเสธบทบาทตั้งต้น บทบาทที่มีคนถืออยู่ บทบาทที่กฎ Cloudflare Access ระบุ และบทบาทสุดท้ายที่ให้สิทธิ์เข้าถึงได้

  • เส้นทางฐาน: /api/manage/v1
  • ส่ง Authorization: Bearer <secret> ไซต์มาจากข้อมูลรับรอง คำขอที่ส่ง X-Site-Id หรือระบุฐานข้อมูลจะถูกปฏิเสธ
  • เนื้อหา JSON เป็นแบบปิด คีย์ที่ไม่รู้จักให้ผล invalid_request
  • ทุกการเขียนต้องมี Idempotency-Key: อักขระ ASCII ที่มองเห็นได้ 1–128 ตัว (0x21–0x7E ห้ามมีช่องว่าง) ข้อยกเว้นไม่รับคีย์ และช่อง Key ในตารางด้านล่างว่างไว้: การ launch ปลั๊กอิน การตรวจ redirect (ไม่เขียนอะไรเลย) และสี่ route การแก้ไขภายใต้ /entries/{id}/editing เมื่อลองใหม่ให้ใช้คีย์และเนื้อหาเดิม ผลลัพธ์แรกจะกลับมาพร้อม replayed: true (หน้า: created: false) คีย์ที่ใช้กับคำขออื่นให้ผล idempotency_key_reused คีย์ของการเผยแพร่ การยกเลิกการเผยแพร่ และการเลื่อนขั้นตอนเก็บไว้ 30 วัน คีย์อื่นเก็บไว้ 24 ชั่วโมง คีย์ของถังขยะ การกู้คืน การลบถาวร การมอบหมาย และภาษาใหม่เก็บคำตอบแรกไว้ตรงตัว การลองลบถาวรซ้ำจึงได้คำตอบเดิมแม้รายการจะไม่มีแล้ว
  • คำสั่งจองคีย์ก่อนเปลี่ยนแปลงสิ่งใด คำขอที่สองที่มีคีย์และ body เดียวกันขณะที่คำขอแรกยังทำงานอยู่จะไม่เปลี่ยนอะไรและได้ 409 idempotency_in_progress พร้อม Retry-After ให้ลองใหม่ด้วยคีย์และ body เดิมเพื่อรับคำตอบแรก คำสั่งที่ถูกปฏิเสธหรือล้มเหลวจะปล่อยคีย์ การแก้ metadata ของสื่อ การลบสื่อ การเขียนการตั้งค่า ข้อเสนอ และทุกขั้นของการนำเข้าและส่งออกทำตามกฎนี้ การเล่นซ้ำตอบด้วยคำตอบแรกทุกประการ ไม่ใช่การอ่านใหม่
  • เพียงเวลาที่ผ่านไปไม่ได้ทำให้เรียกคำสั่งที่จองคีย์ไว้อีกครั้งได้อย่างปลอดภัย เมื่อคำขอที่จองคีย์หยุดตอบ การลองใหม่ครั้งถัดไปด้วยคีย์นั้นจะตัดสินการจองด้วยสิ่งที่คำสั่งพิสูจน์ได้เอง:
    • เขียนข้อมูลและคำตอบไว้ในชุดคำสั่งเดียวกัน: การอัปโหลด แก้ไข และลบสื่อ โฟลเดอร์สื่อ การเขียนโมเดลและเมนู การตั้งค่า Site Chrome และ SEO ถังขยะ การกู้คืน การลบถาวร การมอบหมาย ภาษาใหม่ การสร้างการนำเข้า การส่งออก แหล่งนำเข้า หรือบทบาท การลบบทบาท และการนำเข้ารายการจากแหล่งสื่อ การจองที่ไม่มีคำตอบพิสูจน์ว่ายังไม่มีอะไรถูกเขียน การลองใหม่จึงทำคำสั่ง เมื่อการเขียนสำเร็จแต่คำตอบหายไป การลองใหม่จะตอบจากสิ่งที่เขียนไว้ พร้อม replayed: true ไฟล์ที่เก็บไว้ระหว่างการอัปโหลดที่หยุดกลางทางจะถูกลบ
    • เก็บไว้ในบันทึกของตัวเอง หรือทำซ้ำได้อย่างปลอดภัย: การบันทึกรายการ การเผยแพร่ การยกเลิกเผยแพร่ การย้าย ข้อเสนอ การบันทึกหน้า การตั้งค่า operation ของปลั๊กอิน ส่วนของการนำเข้า การอัปเดตบทบาท การตั้งค่าความคิดเห็น การกลั่นกรองและการลบ การลองใหม่จะทำคำสั่งอีกครั้ง การกลั่นกรองหรือการลบที่ทำซ้ำหลังจากครั้งแรกสำเร็จแล้วจะตอบ conflict (การลบ: not_found) พร้อมสถานะปัจจุบันของความคิดเห็น
    • ขั้นของงาน: ขั้นการส่งออกและนำเข้า ตัวเลือกในการตรวจทาน การหยุดชั่วคราว การยกเลิก และการปิดการอัปโหลด การจองบันทึกว่างานอยู่ที่ใดก่อนขั้นนั้น ถ้างานขยับไปแล้ว การลองใหม่จะตอบสถานะปัจจุบันพร้อม replayed: true และไม่ทำอะไร ถ้างานยังไม่ขยับ ขั้นการส่งออก ตัวเลือกในการตรวจทาน การหยุดชั่วคราว การยกเลิก และการปิดการอัปโหลดจะทำอีกครั้ง เพราะแต่ละอย่างเป็น compare-and-set บนตำแหน่งนั้นหรือกำหนดสถานะเป้าหมาย advanceImport, resumeImport, advanceImportUndo และ retryImportUndo จะทำอีกครั้งก็ต่อเมื่อคำขอแรกจบด้วยข้อผิดพลาด ถ้าคำขอแรกหยุดตอบ (หมดเวลา การเชื่อมต่อขาด) การจองจะค้างอยู่
  • การจองที่ค้างอยู่จะตอบ 409 idempotency_in_progress โดยไม่มี Retry-After การรอจะไม่ทำให้มันจบ ให้ตรวจผลก่อน (สำหรับการนำเข้าใช้ getImportJob) แล้วส่งคำสั่งอีกครั้งด้วยคีย์ใหม่ถ้ายังต้องการ การจองหมดอายุพร้อมคีย์หลัง 24 ชั่วโมง
  • การอัปโหลดสื่อรับไฟล์ที่มีชนิดและรูปแบบที่ไซต์รับ (การตั้งค่า → เว็บไซต์ → ไฟล์สื่อ): รูปภาพ เอกสาร เสียง วิดีโอ หรือไฟล์อื่นเป็นไฟล์ดาวน์โหลด การตรวจเดียวกันใช้กับแอดมิน API นี้ ปลั๊กอิน การนำเข้า และสำเนาไซต์ ชนิดถูกตรวจกับไบต์ ไฟล์ที่ไบต์ นามสกุล หรือชนิดที่ประกาศบอกว่าเป็นโปรแกรม สคริปต์ หรือมาร์กอัปจะถูกปฏิเสธ (executable_file หรือ unsupported_media_type สำหรับมาร์กอัป) สื่อแต่ละรายการตอบ kind ของตน: image, document, audio, video หรือ file width และ height ของรูปภาพมาจาก header ของรูปภาพเอง ฟิลด์ width และ height ใช้เฉพาะเมื่อ header ไม่ได้ระบุ
  • edited_from อัปโหลดไบต์เป็นสำเนาที่แก้ไขของไฟล์ในคลัง (เช่น การครอปที่ทำในเครื่องมืออื่น) ต้นฉบับถูกเก็บไว้ สำเนาตอบ edited_from รับแท็กของต้นฉบับ และรับ alt text และโฟลเดอร์ของต้นฉบับ เว้นแต่คำขอระบุไว้ id ที่ไม่รู้จักคือ 404 media_edit_source_not_found
  • การอัปโหลดสื่อผ่าน API นี้โหลดทั้งไฟล์ไว้ในหน่วยความจำ จึงรับได้ไม่เกิน 32 MiB ไม่ว่าเพดานของชนิดจะเป็นเท่าใด แอดมินสตรีมเอกสาร เสียง วิดีโอ และไฟล์อื่นที่ใหญ่กว่าได้ถึง limits.streamed_max_bytes: 100 MB หรือขีดจำกัดแพ็กเกจที่ใหญ่กว่าที่การ deploy ประกาศใน MEDIA_REQUEST_BODY_LIMIT_BYTES (สูงสุด 5 GB ดู Workers limits)
  • การอัปโหลดสื่อผูกคีย์กับไบต์ ชื่อ และชนิดของไฟล์ รวมถึงฟิลด์ alt_texts, width, height, aspectRatio, on_duplicate, folder_id และ edited_from ไม่ใช่ขอบเขต multipart คีย์บันทึกใน batch เดียวกับแถวสื่อ การลองใหม่ที่ตรงกันจึงคืนผลที่บันทึกไว้
  • เมื่อไซต์มีไบต์เดียวกันอยู่แล้ว on_duplicate เป็นตัวตัดสิน ask (ค่าเริ่มต้น) ตอบ 409 media_duplicate พร้อมไฟล์ที่มีอยู่ใน existing และจำนวนสำเนาใน copies โดยไม่เก็บและไม่บันทึกอะไร use_existing ตอบ 200 พร้อมไฟล์ที่มีอยู่และ reused: true การลองใหม่จะส่งซ้ำเป็น 200 copy อัปโหลดสำเนาแยก ไฟล์ของไซต์อื่นไม่เคยถูกจับคู่
  • คลังสื่อใช้ร่วมทั้งไซต์ ข้อมูลรับรองที่จำกัดเฉพาะบางโมเดลยังเห็นและอัปโหลดสื่อทั้งหมดได้
  • สื่อทุกรายการตอบ folder_id ของตัวเอง หรือ null สำหรับ Unfiled การอัปโหลดระบุ folder_id ได้ ไฟล์ซ้ำที่ถูกนำกลับมาใช้จะอยู่ที่เดิม โฟลเดอร์เป็นแบบตรรกะ: ไม่มีไฟล์ที่เก็บไว้หรือที่อยู่ใดถูกย้ายเลย
  • /media/folders ตอบทั้งต้นไม้เรียงแบบ depth first แต่ละโฟลเดอร์มี revision, depth, media_count ของตัวเอง, subtree_folder_count และ subtree_media_count พร้อม unfiled, total และขีดจำกัด: 5 ระดับ 500 โฟลเดอร์ต่อไซต์ ชื่อยาว 1–80 ตัวอักษรและไม่ซ้ำภายใต้โฟลเดอร์แม่โดยไม่สนตัวพิมพ์เล็กใหญ่ การเปลี่ยนชื่อและการย้ายส่ง expected_revision การลบส่ง expected_revision, expected_folders และ expected_media ตามที่อ่านมา ค่าที่ล้าสมัยได้ 409 media_folder_changed พร้อม folder ปัจจุบัน การปฏิเสธอื่น: media_folder_exists (พร้อม folder ที่ใช้ชื่อนั้นอยู่), media_folder_cycle, media_folder_too_deep, media_folder_limit, media_folder_not_found การลบโฟลเดอร์จะลบทุกโฟลเดอร์ที่อยู่ข้างใต้และไม่ลบไฟล์ใดเลย: ทุกไฟล์จะกลายเป็น Unfiled /media/move ย้ายไฟล์ 1–200 ไฟล์ไปไว้ในโฟลเดอร์เดียว หรือไปที่ Unfiled ด้วย folder_id: null
  • สื่อทุกรายการตอบ default_focal ({ x, y } เป็นสัดส่วน หรือ null), loading และ fetch_priority ด้วย GET /media/{id} ตอบสื่อหนึ่งรายการในรูปแบบเดียวกับในรายการ
  • GET /media/{id}/usage ตอบว่าไฟล์ถูกใช้ที่ไหน ตามที่คลังสื่อแสดง: used_by (รายการที่ข้อมูลรับรองนี้อ่านได้ ไม่เกิน 50 รายการ), hidden (การอ้างอิงในเนื้อหาที่ข้อมูลรับรองนี้อ่านไม่ได้: นับรวมแต่ไม่อธิบายเลย), at_least, total (null เมื่อการนับถูกจำกัดหรืออ่านไม่ได้), more และ unreadable
  • PUT /media/{id} เปลี่ยนสิ่งที่คลังสื่อแก้ไขได้: alt_texts (รหัสภาษาไปยังข้อความ ภาษาที่ไม่ได้ส่งจะคงข้อความเดิม และ "" ล้างข้อความ), default_focal (เฉพาะภาพ null ล้างค่า ถ้าไม่ใช่ภาพจะได้ 400 media_framing_not_image), loading (eager, lazy) และ fetch_priority (high, low, auto) ต้องส่งอย่างน้อยหนึ่งฟิลด์ ตัวไฟล์ ขนาดภาพ และที่อยู่ของไฟล์ไม่เคยเปลี่ยนที่นี่
  • POST /media/{id}/replace รับ multipart ที่มี file และคีย์ idempotency ไฟล์ใหม่ต้องเป็นชนิดเดียวกับไฟล์เดิม ระบบเก็บรหัสไฟล์ การอ้างอิงจากเนื้อหา โฟลเดอร์ แท็ก เครดิต และการจัดกรอบไว้ ค่า alt_texts, default_focal, loading และ fetchPriority ที่ไม่ได้ส่งมาจะคงเดิม ชื่อ ชนิด ขนาดไฟล์ และมิติจะอัปเดตตามไฟล์ที่ตรวจสอบแล้ว URL อาจเปลี่ยน ให้ใช้ url จากคำตอบ เวอร์ชันที่เผยแพร่และตรึงออบเจ็กต์เดิมไว้ยังเก็บไบต์เดิมจนกว่าจะไม่มีเวอร์ชันใดอ้างอิงออบเจ็กต์นั้น ไม่สามารถแทนที่ไฟล์ที่เชื่อมโยงจากต้นทางภายนอกได้ ใช้การตรวจสอบการอัปโหลดและการแทนที่แบบอะตอมมิกเดียวกับคลังสื่อ เมื่อลองอีกครั้งให้ใช้ Idempotency-Key ไฟล์ และฟิลด์เดิม การดำเนินการ TypeScript คือ replaceMedia คำสั่ง CLI คือ replace-media และเครื่องมือ MCP คือ replace_media สำหรับ MCP ระยะไกลให้ส่ง id, content_base64 และ file_name
  • GET /media/{id}/originals แสดงต้นฉบับปัจจุบันแยกตามภาษา (content:read) พร้อม id ของออบเจ็กต์, lang, url, MIME type, ขนาดไบต์ และขนาดภาพ ไฟล์ส่วนตัวมี url เป็น null
  • PUT /media/{id}/originals/{lang} รับ multipart ที่มี file และ expected_object_id ใช้สตริงว่างเพื่อสร้างต้นฉบับเฉพาะภาษาครั้งแรก มิฉะนั้นส่ง id ของออบเจ็กต์ปัจจุบัน DELETE ที่เส้นทางเดียวกันรับ JSON ที่มี expected_object_id ซึ่งไม่ใช่สตริงว่าง ทั้งสองคำขอต้องมี media:write และ Idempotency-Key; id ที่ล้าสมัยตอบ 409 media_changed เปลี่ยนเฉพาะภาษาที่ระบุ ส่วน id สื่อ การอ้างอิง และต้นฉบับอื่นคงเดิม หากไม่มีต้นฉบับเฉพาะภาษา ระบบใช้ลำดับภาษาสำรองของไซต์ แล้วจึงใช้ต้นฉบับเริ่มต้น เวอร์ชันที่เผยแพร่ยังเก็บไบต์ที่ตรึงไว้ตามเดิม POST /media/{id}/replace ยังคงแทนที่ต้นฉบับเริ่มต้น
  • TypeScript: listMediaOriginals, replaceMediaOriginal, removeMediaOriginal CLI: list-media-originals, replace-media-original, remove-media-original MCP: list_media_originals, replace_media_original, remove_media_original
  • POST /media/{id}/remove ลบไฟล์ที่ไม่มีอะไรอ้างอิง ระหว่างที่ยังมีการอ้างอิง จะตอบ 409 media_in_use พร้อมฟิลด์การใช้งานชุดเดียวกัน และตอบ media_usage_unreadable เมื่ออ่านการใช้งานไม่ได้ ในกรณีนั้นจะไม่มีอะไรถูกลบ เมื่อสำเร็จจะตอบ storage_cleanup: complete หรือ pending เมื่อระเบียนหายไปแล้วแต่ยังลบสำเนาที่เก็บไว้ไม่ได้ ไคลเอนต์ MCP เห็นการดำเนินการนี้เป็นแบบทำลายข้อมูล
  • GET /media/sources แสดงแหล่งสื่อภายนอกที่ปลั๊กอินที่ใช้งานอยู่เสนอให้ไซต์ (ปลั๊กอินที่ได้รับ media:source) GET /media/sources/{installationId}/{sourceId}/items ตอบหนึ่งหน้าของแหล่ง ไม่เกิน 24 รายการ ค้นหาด้วย q ส่ง next_cursor กลับเป็น cursor เพื่อรับหน้าถัดไป แต่ละรายการมีชื่อเรื่อง ชื่อไฟล์ ชนิด ขนาด thumbnail_url บน origin ของปลั๊กอิน (โหลดโดยไม่มี referrer) และ attribution แต่ละแหล่งแสดง modes: copy, reference หรือทั้งสอง การเรียกดูไม่เก็บอะไร
  • POST /media/sources/{installationId}/{sourceId}/import พร้อม item_id และ mode เพิ่มหนึ่งรายการเข้าคลัง mode จำเป็นและเป็นหนึ่งใน modes ของแหล่ง Dee Wan ไม่เดาเอง ด้วย copy Dee Wan ดึงไฟล์จากปลั๊กอินและตรวจเหมือนการอัปโหลดทั่วไป: ชนิด รูปแบบ และขีดจำกัดขนาดของไซต์ และไฟล์ใหม่ไม่ขึ้นกับแหล่งอีกเลย ด้วย reference Dee Wan ไม่ดึงอะไรและเก็บ URL การส่งมอบของปลั๊กอินกับตัวตนของรายการ: สื่อตอบ external: true, url ของมันคือ URL การส่งมอบ ไบต์ของมันแทนที่หรือแก้ไขไม่ได้ (media_external_bytes_unavailable, 409) และการลบมันเอาออกเฉพาะแถวในคลัง folder_id และ on_duplicate ทำงานเหมือนตอนอัปโหลด (สำหรับ reference ปลั๊กอิน แหล่ง และรายการเดียวกันคือรายการซ้ำ) use_existing ตอบไฟล์ที่มีอยู่ด้วย 200 ไฟล์ใหม่ตอบ attribution (ชื่อแหล่ง ผู้สร้าง สัญญาอนุญาต ลิงก์ และ id ของปลั๊กอิน แหล่ง และรายการ) สื่อทุกรายการตอบ attribution เป็น null สำหรับไฟล์ที่เพิ่มด้วยวิธีอื่น การปฏิเสธ: media_source_unavailable (404), media_source_mode_unsupported (422 แหล่งไม่ได้ประกาศ mode นั้น), plugin_refused (422 พร้อม refusal ของปลั๊กอิน), plugin_unreachable, plugin_response_invalid และ media_source_file_unreachable (502), plugin_timeout (504) และการปฏิเสธของการอัปโหลด เช่น media_format_not_enabled, media_kind_not_enabled และ media_too_large บันทึกการตรวจสอบระบุว่าเป็นการอัปโหลดสื่อพร้อมข้อมูลแหล่งที่มา
  • จำกัดอัตรา: 120 คำขอต่อนาทีต่อข้อมูลรับรอง หากเกินจะได้ 429 พร้อม Retry-After
เมธอด เส้นทาง ขอบเขต คีย์
GET /site content:read
GET /models content:read
GET /entries?model_id=… content:read
POST /entries content:write จำเป็น
GET /entries/{id} content:read
PUT /entries/{id} content:write จำเป็น
GET /entries/{id}/versions content:read
GET /entries/{id}/versions/{versionId} content:read
GET /entries/{id}/dependents content:read
POST /entries/{id}/publish lifecycle:publish จำเป็น
POST /entries/{id}/unpublish lifecycle:publish จำเป็น
POST /entries/{id}/proposals content:propose จำเป็น
GET /entries/{id}/proposals content:read
GET /entries/{id}/editing?locale=…&kind=… content:read
POST /entries/{id}/editing/takeover content:write
PUT /entries/{id}/editing/draft content:write
POST /entries/{id}/editing/draft/discard content:write
GET /pages/{id} content:read
POST /pages/{id}/save pages:write จำเป็น
POST /pages/{id}/translations pages:write จำเป็น
GET /media content:read
POST /media (multipart ส่วน file) media:write จำเป็น
GET /media/folders content:read
POST /media/folders media:write จำเป็น
PUT /media/folders/{id} media:write จำเป็น
POST /media/folders/{id}/remove media:write จำเป็น
POST /media/move media:write จำเป็น
GET /media/sources media:write
GET /media/sources/{installationId}/{sourceId}/items media:write
POST /media/sources/{installationId}/{sourceId}/import media:write จำเป็น
GET /media/{id} content:read
GET /media/{id}/usage content:read
PUT /media/{id} media:write จำเป็น
POST /media/{id}/replace (multipart file) media:write จำเป็น
GET /media/{id}/originals content:read
PUT /media/{id}/originals/{lang} (multipart file, expected_object_id) media:write จำเป็น
DELETE /media/{id}/originals/{lang} media:write จำเป็น
POST /media/{id}/remove media:write จำเป็น
GET /languages content:read
POST /entries/{id}/locales content:write จำเป็น
POST /entries/{id}/versions/{versionId}/restore content:write จำเป็น
POST /entries/{id}/transition workflow:move จำเป็น
GET /entries/{id}/assignees workflow:assign
PUT /entries/{id}/assignment workflow:assign จำเป็น
GET /trash?model_id=… content:read
GET /entries/{id}/trash content:read
POST /entries/{id}/trash content:trash จำเป็น
POST /entries/{id}/trash/restore content:trash จำเป็น
POST /entries/{id}/trash/purge content:purge จำเป็น
POST /entries/relate content:write จำเป็น
GET /entries/{id}/related-from content:read
PUT /entries/{id}/related-from content:write จำเป็น
GET /menus settings:write
GET /menus/{key} settings:write
PUT /menus/{key} settings:write จำเป็น
POST /menus/{key}/languages settings:write จำเป็น
GET /models/{id} content:read
GET /models/proposals?state=… schema:propose
POST /models/proposals schema:propose จำเป็น
GET /models/proposals/{id} schema:propose
POST /models schema:write จำเป็น
PUT /models/{id} schema:write จำเป็น
PUT /models/{id}/folder schema:write จำเป็น
PUT /models/{id}/definition schema:write จำเป็น
POST /models/{id}/remove schema:write จำเป็น
GET /settings/editorial settings:write
PUT /settings/editorial settings:write จำเป็น
GET /settings/language-fallback settings:write
PUT /settings/language-fallback settings:write จำเป็น
GET /settings/media-files settings:write
PUT /settings/media-files settings:write จำเป็น
GET /settings/search settings:write
PUT /settings/search settings:write จำเป็น
GET /settings/chrome settings:write
PUT /settings/chrome settings:write จำเป็น
GET /settings/seo settings:write
POST /settings/seo/redirect-review settings:write
PUT /settings/seo settings:write จำเป็น
GET /imports/sources data:import
POST /imports/sources data:import จำเป็น
GET /imports?limit=… data:import
POST /imports data:import จำเป็น
GET /imports/{id} data:import
PUT /imports/{id}/archive/parts/{part} (binary) data:import จำเป็น
POST /imports/{id}/archive/complete data:import จำเป็น
POST /imports/{id}/advance?recheck=… data:import จำเป็น
GET /imports/{id}/findings data:import
GET /imports/{id}/mapping data:import
PUT /imports/{id}/mapping data:import จำเป็น
GET /imports/{id}/wordpress data:import
PUT /imports/{id}/wordpress data:import จำเป็น
GET /imports/{id}/assignments data:import
PUT /imports/{id}/assignments data:import จำเป็น
GET /imports/{id}/principals data:import
PUT /imports/{id}/principals data:import จำเป็น
POST /imports/{id}/principals/accept-suggestions data:import จำเป็น
POST /imports/{id}/pause data:import จำเป็น
POST /imports/{id}/resume data:import จำเป็น
POST /imports/{id}/cancel data:import จำเป็น
POST /imports/{id}/undo data:import จำเป็น
POST /imports/{id}/undo/retry data:import จำเป็น
GET /imports/{id}/artifacts/{name} data:import
POST /exports data:export จำเป็น
GET /exports/{id} data:export
POST /exports/{id}/advance data:export จำเป็น
GET /exports/{id}/artifact?offset=…&length=… data:export
GET /roles content:read
GET /roles/{id} content:read
POST /roles roles:write จำเป็น
PUT /roles/{id} roles:write จำเป็น
POST /roles/{id}/remove roles:write จำเป็น
GET /plugins plugins:use
GET /plugins/manage-views plugins:use
GET /plugins/content-actions?instance_id=… plugins:use
POST /plugins/{id}/manage-views/{view}/launch plugins:use
POST /plugins/{id}/content-actions/{action}/entries/{instanceId}/launch plugins:use
GET /plugins/operations?instance_id=… plugins:use
POST /plugins/{id}/operations/{operation}/invoke plugins:use ต้องมี
GET /rich-blocks content:read

สี่ route การแก้ไขอ่านและเปลี่ยน lease การแก้ไขและ draft ร่วมของรายการและภาษาเดียว (ดู lease การแก้ไข และ draft ร่วม ใน ความขัดแย้ง) route เหล่านี้ไม่รับ Idempotency-Key และไม่ถูกบันทึกในประวัติ audit route เหล่านี้บังคับความเป็นเจ้าของและขอบเขตการอ่านฟิลด์ของผู้เรียก และตอบเฉพาะฟิลด์ที่ข้อมูลรับรองอ่านได้

สิทธิ์อ่านและสิทธิ์เขียนแยกกัน ผู้สร้างที่เปลี่ยนรายการได้แต่อ่านแถวหรือภาษานั้นไม่ได้ (อ่านได้เฉพาะของตนเองแต่แถวเป็นของคนอื่น หรือไม่มีสิทธิ์อ่านในภาษานั้น) ยัง takeover เขียน และทิ้ง draft ได้ แต่ไม่มีคำตอบใดมี draft มาด้วย: GET ถูกปฏิเสธ (not_owner หรือ locale_denied) takeover ตอบ draft: null 409 draft_conflict ตอบ current: null และ draft ที่เขียนแล้วจะส่งกลับเฉพาะสิ่งที่ผู้สร้างเขียนเอง ผู้สร้างที่มีสิทธิ์ลักษณะนี้จึงอ่าน revision ของฉบับร่างไม่ได้: ให้ทิ้งโดยไม่ระบุ revision แล้วเขียนด้วย expected_revision: 0 ผู้สร้างที่ไม่มีสิทธิ์อ่านโมเดลเลยจะถูกปฏิเสธด้วย 403 ในทุก route การแก้ไข และไม่มีอะไรเปลี่ยน

  • GET /entries/{id}/editing: query locale และ kind (content หรือ page)
  • POST /entries/{id}/editing/takeover: body { locale }
  • PUT /entries/{id}/editing/draft: body { locale, kind, baseline_version_id, expected_revision, data } baseline_version_id คือเวอร์ชันที่ draft เริ่มจาก หรือ null; data คือค่าฟิลด์ของ draft
  • POST /entries/{id}/editing/draft/discard: body { locale, kind } และ revision ที่ไม่บังคับ เพื่อทิ้งเฉพาะ revision นั้น

การปฏิเสธบน route เหล่านี้:

  • 403 not_owner: ผู้สร้างอ่านหรือเปลี่ยนได้เฉพาะรายการของตนเอง และไม่ได้เป็นเจ้าของแถวของรายการในภาษานั้น (หรือตัวรายการเอง เมื่อภาษานั้นยังไม่มีแถว)
  • 403 locale_denied พร้อม locale: การอ่านต้องมีสิทธิ์อ่านในภาษานั้น takeover การเขียน draft และการทิ้ง draft ต้องมีสิทธิ์แก้ไขในภาษานั้น หรือสิทธิ์สร้างเมื่อรายการยังไม่มีแถวในภาษานั้น
  • 403 field_denied พร้อม fields: ["slides"]: draft แบบ page สำหรับผู้สร้างที่อ่านส่วนต่าง ๆ ของหน้าไม่ได้ ผู้สร้างที่อ่านชื่อเรื่องของหน้าไม่ได้จะได้ title และ baseline.title เป็น "" และการเขียนของเขาจะคงชื่อเรื่องที่เก็บไว้
  • 423 locked พร้อม holder และ recovery: อีกคนหนึ่งถือภาษานั้นอยู่ lease ถูกตรวจอีกครั้งภายในการเขียนหรือการทิ้ง หากมีผู้อื่นรับช่วงแก้ไขระหว่างนั้น การเขียนหรือทิ้งฉบับร่างจะถูกปฏิเสธด้วย และไม่มีอะไรเปลี่ยน การเขียนที่ถูกปฏิเสธจะถูกเก็บเป็น draft กู้คืนของผู้สร้างเอง (recovery) การทิ้งที่ถูกปฏิเสธไม่เก็บอะไรไว้: recovery: null
  • 409 draft_conflict พร้อม current: revision ล้าสมัย current และคำตอบอื่นทุกคำตอบที่มี draft มีเฉพาะฟิลด์ที่ผู้สร้างอ่านได้ current เป็น null เมื่อไม่มี draft หรือเมื่อผู้สร้างอ่านแถวหรือภาษานั้นไม่ได้

/entries แสดงรายการหลักของกลุ่มคำแปลในโมเดลเดียว: model_id (จำเป็น), published, after, limit (1–50) ผู้สร้างที่อ่านได้เฉพาะรายการของตัวเองในโมเดลนั้นจะได้ listing_owner_restricted ให้อ่านรายการเหล่านั้นด้วย id แทน

POST /entries และ PUT /entries/{id} รับรายการความสัมพันธ์ใต้ relations (ฟิลด์ → รายการ) ฟิลด์ความสัมพันธ์ที่มีลิงก์ต่างกันในแต่ละภาษารับรายการเหล่านั้นใต้ locale_relations (ภาษา → ฟิลด์ → รายการ) แทน ดู ลิงก์ต่างกันในแต่ละภาษา ฟิลด์ความสัมพันธ์รับ { connect, disconnect, move } ด้วย โดยทำ disconnect ก่อน ตามด้วย connect แล้วจึง move รายการ connect เป็น id (เพิ่มที่ท้าย) หรือ { id, position } move คือ [{ id, position }] สำหรับรายการที่เกี่ยวข้องอยู่แล้ว position เป็นหนึ่งเดียวใน { before: id }, { after: id }, { start: true }, { end: true } จุดอ้างอิงคือ id ในรายการ ณ ขั้นนั้น การปฏิเสธ (422): relation_position_anchor_missing, relation_write_invalid, relation_write_duplicate (id อยู่ทั้งใน connect และ move ทั้งใน disconnect และ move หรือซ้ำสองครั้งใน move), relation_move_target_missing และ relation_position_not_ordered (ตำแหน่งหรือ move บนฟิลด์เป้าหมายเดียว) ตำแหน่งตีความกับรายการที่เก็บไว้ ณ ตอนบันทึก expected_current_version_id ยังปฏิเสธการบันทึกที่ล้าสมัย ทุกที่ที่การเขียนความสัมพันธ์รับ id จะรับ { model, id } ที่ระบุ slug ของ model เป้าหมายด้วย ดู ความสัมพันธ์กับหลายโมเดล เชื่อมโยง และ เชื่อมโยงจาก เปลี่ยนรายการลิงก์ของภาษาที่ id ของรายการนั้นระบุ

/trash แสดงถังขยะของโมเดลเดียวในแบบเดียวกัน (model_id, after, limit) แต่ละรายการคือทั้งรายการ (scope: entry ระบุด้วยรายการหลัก) หรือภาษาเดียวที่ย้ายไปถังขยะแยกเอง (scope: language) พร้อม deleted_at และ purge_after (อีก 30 วัน)

/menus อ่านเมนูของ Site Chrome เวอร์ชันที่ยอมรับแล้ว และ revision ซึ่งคือเวอร์ชันที่ยอมรับแล้วที่เมนูมาจาก (null เมื่อยังไม่มีเวอร์ชันที่ยอมรับ) เมนูประกอบด้วย key, name และ variants: รหัสภาษาไปยัง items ของภาษานั้นเอง placed_in จับคู่ภาษากับตำแหน่งที่เมนูแสดงในภาษานั้น /menus/{key} เพิ่ม languages, served (ต่อภาษาของไซต์ คือเวอร์ชันที่ให้บริการในวันนี้ภายใต้กฎ strict หรือ fallback ของไซต์ หรือ null) และ live (ต่อเวอร์ชัน แล้วต่อ id ของรายการ คือภาษาที่รายการนั้นแสดงอยู่ในวันนี้) รายการที่หน้า โพสต์ หรือหน้ารวม (archive) ยังไม่เผยแพร่จะถูกซ่อนบนไซต์ PUT /menus/{key} สร้างหรือแทนที่เมนูหนึ่งเมนูพร้อมทุกเวอร์ชัน: expected_revision, name, variants และ placements ที่ไม่บังคับ (รหัสภาษาไปยัง header/footer ภาษาที่ไม่ได้ส่งจะคงตำแหน่งเดิม) แต่ละรายการชี้ไปที่ target: entry (model, entry = translation_group_id), blog, archive (role category/tag/author, entry), url (https), email, phone หรือ path หรือ null สำหรับหัวข้อที่มีรายการอยู่ข้างใต้ ทุกรายการต้องมีป้ายชื่อ id ของรายการไม่ซ้ำกันภายในเวอร์ชันเดียว POST /menus/{key}/languages เพิ่มหนึ่งเวอร์ชัน: expected_revision, language (ภาษาของไซต์) และ copy_from (ภาษาที่จะคัดลอก โดยได้ id ของรายการใหม่ หรือ null สำหรับเวอร์ชันว่าง) คำตอบอาจเป็น menu_not_found 404, menu_language_exists 409, menu_language_missing หรือ menu_language_unknown 422 การบันทึกแต่ละครั้งคือ Site Chrome เวอร์ชันใหม่ที่ยอมรับทันที การเปลี่ยนแปลง Site Chrome ที่ยังไม่ยอมรับจะถูกเขียนใหม่ทับด้านบนโดยรวมเมนูไว้ (draft: carried) หรือปล่อยไว้ให้คนจัดการ (draft: needs_rebase) expected_revision ที่ล้าสมัยได้ 409 stale_document พร้อม revision

การนำเข้าและส่งออกชุดเดียวกับใน การตั้งค่า → การย้ายข้อมูล แต่ไม่ต้องใช้หน้าแอดมิน ทุกขั้นตอนเรียกใช้โค้ดนำเข้าหรือส่งออกของหน้าแอดมินเองพร้อมการปฏิเสธแบบเดียวกัน ที่นี่ไม่ใช่เครื่องมือนำเข้าตัวที่สอง

การนำเข้า POST /imports พร้อม format (tar, tar.gz, json, transfer, wxr), source ใหม่ (label, system) หรือ source_id ที่มีอยู่ และ destination ที่ไม่บังคับ แต่ละไซต์นำเข้าได้ครั้งละหนึ่งงาน (409 import_already_active) จากนั้น:

  1. อัปโหลดไฟล์เป็นส่วน: PUT /imports/{id}/archive/parts/{n} พร้อม Content-Type: application/octet-stream ส่วนละ 5 MiB ทุกส่วนยกเว้นส่วนสุดท้ายต้องมีขนาด 5 MiB พอดี และเลขส่วนเริ่มที่ 1 ขีดจำกัดขนาดของรูปแบบไฟล์ยังมีผล หมายเลขส่วนถูกผูกกับ SHA-256 ของไบต์ก่อนเก็บไบต์ใด ทั้งที่นี่และในการอัปโหลดผ่านแอดมิน: การส่งไบต์เดิมซ้ำจะได้คำตอบเหมือนเดิม ไบต์ที่ต่างไปสำหรับส่วนที่ผูกแล้วจะได้ 409 import_part_conflict ไม่ว่าคีย์จะเป็นอะไร และไม่มีไบต์ใดถูกเก็บ ระบบไม่ดึงข้อมูลจาก URL ใด ๆ เลย
  2. POST /imports/{id}/archive/complete โดยจะส่ง client_sha256 ของทั้งไฟล์ด้วยก็ได้ สำหรับ .deewan แต่ละคำขอตรวจสอบไม่เกิน 32 MiB ขณะที่ outcome.more เป็น true ให้เรียก advance หรือเรียกคำสั่งจบการอัปโหลดอีกครั้งด้วยคีย์ idempotency ใหม่ ระบบทำต่อจากจุดตรวจสอบที่บันทึกไว้โดยใช้ ID งาน และจะระบุ client_sha256_matches หลังตรวจสอบทั้งไฟล์แล้วเท่านั้น
  3. POST /imports/{id}/advance ไปเรื่อย ๆ จนคำตอบไม่ขออะไรเพิ่ม แต่ละครั้งเรียกใช้หนึ่งขั้นที่มีขอบเขตจำกัดบนเซิร์ฟเวอร์: เตรียมไฟล์ อ่านไฟล์ สร้างรายการงาน ตรวจสอบหรือนำเข้าหนึ่งชุด ขั้นที่ Worker ทำไม่เสร็จในคำขอเดียวจะทำต่อในการเรียกครั้งถัดไป ไม่มีการเรียกครั้งเดียวสำหรับการนำเข้าทั้งหมด
  4. เมื่อ decision ระบุบางอย่าง ให้ตอบสิ่งนั้นแล้วเดินหน้าอีกครั้ง: เลือกการจับคู่ของ Strapi (PUT /imports/{id}/mapping) ตัวเลือกของ WordPress (PUT /imports/{id}/wordpress) การกำหนดของประวัติไซต์ (PUT /imports/{id}/assignments) หรือแก้สิ่งที่ GET /imports/{id}/findings แสดง ?recheck=true ตรวจสอบซ้ำหลังการตรวจสอบเสร็จแล้ว การโอนประวัติหรือการนำเข้า WordPress ยังจับคู่บุคคลที่ให้เครดิตกับสมาชิกที่นี่ได้: GET /imports/{id}/principals แสดงแต่ละบุคคล (ref, label ต้นทาง, email, uses, suggestion, choice, mapped, problem) และ members ที่ใช้งานอยู่ PUT /imports/{id}/principals พร้อม { "ref", "user_id" } จับคู่หนึ่งคน (user_id: null ให้เป็นป้ายต่อไป) ไฟล์ .deewan แบ่งรายชื่อบุคคลและการมอบหมายเป็นหน้า ให้ส่ง next_cursor ที่ได้รับโดยไม่แก้ไข พร้อม limit (1–100 ค่าเริ่มต้น 50) แต่ละหน้าของรายชื่อบุคคลมีรายชื่อสมาชิกที่ใช้งานอยู่ให้เลือกด้วย POST /imports/{id}/principals/accept-suggestions ยอมรับคำแนะนำที่อีเมลตรงกันเฉพาะหน้าที่เลือก: ใช้ cursor และ limit เดียวกับ GET แล้วอ่านหน้าถัดไปด้วย next_cursor จาก GET สำหรับ JSON แบบเดิมและ WordPress คำสั่งนี้ยอมรับคำแนะนำทั้งหมดที่ยังไม่ได้ตัดสินใจ ตัวเลือกที่บันทึกไว้แล้วจะคงเดิม สมาชิกต้องใช้งานอยู่บนไซต์นี้ (409 principal_user_ineligible) ref ที่ไม่รู้จักคือ 404 principal_not_in_transfer ไม่มีอะไรสร้างหรือเปลี่ยนบัญชี การตรวจสอบจะบล็อกด้วย principal_mapping_invalid เมื่อสมาชิกที่จับคู่เสียสิทธิ์เข้าถึง และการรันจะตรวจอีกครั้งก่อนเขียน ดู การนำเข้าและการย้ายข้อมูล

การนำเข้าครั้งหลังจากต้นทางเดียวกันจะสร้างสิ่งที่ใหม่และข้ามสิ่งที่นำเข้าแล้ว ไม่เปลี่ยนเนื้อหาที่การนำเข้าครั้งก่อนเขียนเลย และการบันทึกการจับคู่ใหม่ไม่เปลี่ยนสิ่งที่นำเข้าแล้ว ข้อยกเว้นเดียวคือเมนู WordPress ที่ยังเหมือนที่การนำเข้าครั้งล่าสุดเขียนไว้ทุกประการ ซึ่งจะได้รายการที่ตอนนี้จับคู่ได้เพิ่ม (การนำเข้าและการย้ายข้อมูล) 5. การตรวจสอบที่ผ่านและเป็นปัจจุบันจะนำเข้าในการเดินหน้าครั้งถัดไป: การเดินหน้างานที่ตรวจสอบแล้วคือการอนุมัติให้เริ่ม

คำตอบของการนำเข้าทุกแบบ ยกเว้นไฟล์ผลลัพธ์ คือ { "data": state }:

  • job: id, status, stage (upload, prepare, review, check, import, undo, done), format, source, plan_revision, upload (state, parts_received, bytes_received, part_size), progress, dry_run_state, undo_state, final_status, error และเวลาต่าง ๆ
  • findings: จำนวน blocking, warning และ info
  • decision: null หรือ { kind, message } โดย kind เป็นหนึ่งใน upload_needed, mapping_required, wordpress_mapping_required, findings_block, paused, blocked, cancel_pending, undo_refused, undo_failed
  • more: การเดินหน้าอีกครั้งจะเรียกใช้ขั้นได้ทันที
  • next_actions: การดำเนินการที่ทำได้ตอนนี้
  • step และ outcome: สิ่งที่คำขอนี้เรียกใช้ หรือ null

pause หยุดหลังชุดที่กำลังทำงานอยู่ resume ทำต่องานที่หยุดชั่วคราว cancel เก็บสิ่งที่นำเข้าแล้วไว้และเขียนบันทึกผลการนำเข้าที่ระบุว่ายกเลิกแล้ว (decision.kind: cancel_pending ระหว่างที่การล้างไฟล์ยังไม่เสร็จ) POST /imports/{id}/undo ย้อนกลับหนึ่งชุดต่อการเรียกและเป็นการดำเนินการที่ทำลายข้อมูล undo/retry นำหน่วยที่ถูกปฏิเสธกลับเข้าคิวในกรณีที่หน้าแอดมินอนุญาต GET /imports/{id}/findings?limit=&cursor= อ่านผลการตรวจได้ไม่เกิน 500 รายการ (ค่าเริ่มต้น 100) เรียงจากร้ายแรงที่สุด แล้วตามลำดับที่พบ ส่ง next_cursor เป็น cursor เพื่ออ่านต่อ (null หลังรายการสุดท้าย) GET /imports/{id}/mapping, GET /imports/{id}/wordpress, GET /imports/{id}/assignments และ GET /imports/{id}/principals รับ limit (ไม่เกิน 500 ค่าเริ่มต้น 200) และ cursor แบบเดียวกัน รายการยาวของ endpoint เหล่านี้มีหนึ่งหน้าตามลำดับของแอดมิน: types ของ Strapi, normalized.skipped_fields, แต่ละรายการของ normalized.group_keys และ review.unresolved; posts, pages และ entries ของ WordPress; assignments ของประวัติ; principals และ members ส่วน totals นับแต่ละรายการทั้งหมด ณ ตำแหน่งของรายการ และฟิลด์อื่นทั้งหมด รวมถึง mapping มาครบทุกหน้า GET /imports/{id}/artifacts/{name}?offset=&length= ให้ช่วงไบต์ของ receipt, problems (problem-rows.csv), entries, media (แมนิเฟสต์), partition หรือ plugin_data (ไฟล์ข้อมูลปลั๊กอินของ WordPress ดู การนำเข้าและการย้ายข้อมูล) ซึ่งเป็นไบต์เดียวกับที่หน้าแอดมินดาวน์โหลด ไม่เกิน 4 MiB ต่อคำขอ เป็น application/octet-stream พร้อม header X-Artifact-* ด้านล่าง X-Artifact-Content-Type ระบุชนิดของไฟล์เอง งานที่ไม่มีอยู่และงานของไซต์อื่นตอบ 404 job_not_found เหมือนกัน

การส่งออก เรียก POST /exports พร้อม product (bundle, content, csv, xlsx, history) และ models, locale หากต้องการ จากนั้นเรียก POST /exports/{id}/advance จน more เป็น false สำเนาไซต์และการย้ายพร้อมประวัติใช้รูปแบบ .deewan ที่ทำต่อได้ ส่วนข้อมูลรายการใช้ JSON Lines แต่ละคำขอเก็บส่วนถัดไปที่แก้ไขไม่ได้ หรือทำขั้นตอนประกอบไฟล์ต่อ การประกอบอาจต้องเรียกหลายครั้ง เมื่อเสร็จแล้ว artifact จะระบุขนาดไฟล์และ SHA-256 ของทั้งไฟล์ แล้วจึงลบส่วนย่อย pages แสดง checksum, ขนาด, cursor และสถานะ readable ของแต่ละส่วน CSV และ XLSX ยังคงมีข้อจำกัดแยกต่างหาก

GET /exports/{id}/artifact?offset=&length= อ่านไฟล์ที่เสร็จแล้ว ไม่เกิน 4 MiB ต่อคำขอ ก่อนเสร็จจะได้ 409 export_not_final เพิ่ม page=<n> เพื่ออ่านหน้าที่เก็บไว้หนึ่งหน้าขณะที่การส่งออกยังทำงาน หน้าที่ถูกลบหลังเสร็จจะได้ 409 export_page_gone การอ่านทุกครั้งตอบ application/octet-stream พร้อม X-Artifact-Offset, X-Artifact-Length, X-Artifact-Total, X-Artifact-Sha256 และ X-Artifact-Content-Type ของทั้งไฟล์, X-Artifact-Next-Offset (ไม่มีเมื่อถึงท้ายไฟล์) และสำหรับหน้า X-Artifact-Page และ X-Artifact-Next-Page (ที่ท้ายหน้า เมื่อสร้างหน้าถัดไปแล้ว) ไฟล์ที่เก็บซึ่งไม่ใช่ไฟล์ที่บันทึกไว้ได้ 409 export_artifact_changed การอ่านเลยท้ายไฟล์ได้ 400 artifact_range_invalid การเดินหน้าสองครั้งพร้อมกันไม่ทำให้หน้าหายหรือซ้ำ: ครั้งที่สองตอบการส่งออกตามที่ครั้งแรกทิ้งไว้ การส่งออกที่หมดอายุและที่ล้มเหลวจะถูกปฏิเสธเหมือนในหน้าแอดมิน

ทุกการเขียนต้องมี Idempotency-Key ประวัติบันทึกการดำเนินการ งาน รูปแบบ ช่วง และผลลัพธ์ แต่ไม่บันทึกเนื้อหาไฟล์ ค่าเนื้อหา การจับคู่ ข้อความของสิ่งที่พบ หรือไบต์ที่ดาวน์โหลด

settings:write อ่านและเปลี่ยนการตั้งค่าผ่านข้อมูลที่มีชนิดกำกับ แยกตามส่วน ทุกการเขียนระบุ revision ที่อ่านมา หาก revision ล้าสมัยจะไม่เขียนอะไรเลย ทุกการเขียนใช้ตัวตรวจและค่าที่เก็บชุดเดียวกับหน้าการตั้งค่าของส่วนนั้น และบันทึก management.settings.update ไว้คู่กับบันทึกของหน้านั้นเอง ผู้สร้างต้องมี site:settings ในขณะนั้น ส่วนการค้นหาต้องมี site:infra แทน

ส่วน อ่าน เขียน Revision
การเก็บเวอร์ชันและการตรวจ versions_to_keep, review_flow อย่างใดอย่างหนึ่งหรือทั้งสอง expected_revisions.versions_to_keep, .review_flow
เมื่อไม่มีภาษา policy (strict หรือ fallback_to_default), chains, languages policy, chains (แทนที่ทั้งหมด) expected_revisions.policy, .chains
ไฟล์สื่อ kinds (แต่ละชนิดมี enabled, max_bytes, formats), limits (min_bytes, buffered_max_bytes, streamed_max_bytes, kind_max_bytes) kinds (แทนที่ทั้งหมด รูปภาพเปิดอยู่เสมอ ชนิดที่ไม่มี formats ใช้ค่าเริ่มต้น) expected_revision
การค้นหา enabled, models, weights, modes และสิ่งที่เพิ่มหรือปรับได้ enabled, models, weights ที่ไม่บังคับ และ modes ที่ไม่บังคับ (word, english_stem หรือ substring ต่อ model ถ้าไม่ระบุคือ word และ english_stem ในภาษาที่ไม่ใช่ภาษาอังกฤษจับคู่ทั้งคำ) expected_revision
ค่าเริ่มต้นของ Site Chrome footer.note ต่อภาษา, routes footer, routes expected_revision (เวอร์ชัน Site Chrome ที่ยอมรับแล้ว หรือ null)
SEO ค่าเริ่มต้น seo, sitemap, robots, redirects ส่วนใดก็ได้ expected_revision (เวอร์ชัน Site Chrome ที่ยอมรับแล้ว หรือ null)
  • revision ของการตั้งค่าที่ล้าสมัยได้ 409 settings_changed_concurrently พร้อม field การเปลี่ยนที่ระบุการตั้งค่าโดยไม่มี revision ของมันได้ 400 invalid_request
  • การเก็บเวอร์ชันและขั้นตอนตรวจทาน การใช้ภาษาสำรองเมื่อไม่มีคำแปล ไฟล์สื่อ และการค้นหา เก็บคีย์ไว้กับการตั้งค่าในการเขียนครั้งเดียว การลองใหม่ด้วยคีย์และ body เดิมได้คำตอบแรก แม้ revision จะเปลี่ยนไปแล้ว การเขียนที่บันทึกแล้วแต่ยังไม่เสร็จได้ 503 temporary_failure การลองใหม่จะทำให้เสร็จและส่งคำตอบเดิมกลับมา
  • ตัวตรวจตอบด้วยชื่อ เช่น invalid_versions_to_keep, review_flow_items_in_progress (มีรายการรอตรวจ และแสดงรายการไว้), fallback_language_unknown, fallback_cycle, invalid_media_files, site_search_model_invalid, site_search_weights_invalid และ site_search_mode_invalid
  • ค่าเริ่มต้นของ Site Chrome และ SEO บันทึกเป็น Site Chrome เวอร์ชันใหม่ที่ยอมรับทันที เหมือนเมนู การเปลี่ยนแปลงที่ยังไม่ยอมรับจะถูกนำไปด้วย (draft) และ revision ที่ล้าสมัยได้ 409 stale_document
  • รูปสำหรับการแชร์หรือโลโก้ระบุด้วย og_image_media_id / logo_media_id จากคลังสื่อของไซต์นี้ เซิร์ฟเวอร์บันทึกที่อยู่และขนาดของรูปไว้
  • การเปลี่ยนเส้นทางแทนที่ทั้งหมด ต้นทางแบบตรงตัวที่เป็นที่อยู่ที่เผยแพร่อยู่ ที่อยู่สงวนของบล็อก หรือ route ที่ตั้งชื่อไว้ จะถูกปฏิเสธ การเปลี่ยนกฎแบบ pattern ต้องมี redirect_review_digest: ส่งกฎไปที่ /settings/seo/redirect-review อ่านว่าแต่ละ pattern ครอบคลุมหน้าที่เผยแพร่อยู่หน้าใด แล้วส่ง digest ของมัน หากไม่มีจะได้ 409 redirect_review_stale พร้อมผลการตรวจ
  • ไม่มีที่นี่: ความลับ รหัสผ่าน วิธีเข้าสู่ระบบ ผู้ให้บริการตัวตน ความลับและโทเค็นของปลั๊กอิน การ deploy ผู้ให้บริการซอร์ส โดเมน และโครงสร้างพื้นฐานอื่น

การดำเนินการกับความคิดเห็นรัน route ความคิดเห็นของแอดมินด้วยสิทธิ์ที่ถูกจำกัดของข้อมูลรับรอง กฎ การปฏิเสธ และคำตอบเรื่องความขัดแย้งจึงเหมือนของแอดมิน (ดู ความคิดเห็นของผู้เยี่ยมชม) การเขียนทุกครั้งต้องมี Idempotency-Key และบันทึกในประวัติเป็น management.comment.moderate, management.comment.delete หรือ management.comment.settings: จำนวนและสถานะเป้าหมาย ไม่ใช่ข้อความหรืออีเมลของความคิดเห็น

การดำเนินการ Scope ทำอะไร
GET /comments/settings (getCommentSettings) settings:write ทุกโมเดลและว่ารายการของโมเดลรับความคิดเห็นหรือไม่
PUT /comments/settings (updateCommentSettings) settings:write { model, enabled }: เปิดหรือปิดหนึ่งโมเดล การปิดซ่อนความคิดเห็น ไม่มีอะไรถูกลบ
GET /comments (listComments) lifecycle:publish ใหม่สุดก่อน หน้าละ 50 รายการ state (ค่าเริ่มต้น pending), model, entry, lang, cursor ไม่มีอีเมล
GET /comments/{id} (getComment) lifecycle:publish ความคิดเห็นหนึ่งรายการพร้อมอีเมลส่วนตัว parent และรายการ
POST /comments/moderate (moderateComments) lifecycle:publish { to, comments: [{ id, expected }] } ไม่เกิน 25 รายการ แต่ละรายการเฉพาะเมื่อยังอยู่ในสถานะที่อ่านไว้
POST /comments/publish-imported (publishImportedComment) lifecycle:publish เผยแพร่ความคิดเห็นที่นำเข้าหนึ่งรายการโดยไม่มีความยินยอม ความยินยอมยังเป็น false
POST /comments/remove (deleteComments) content:purge ลบความคิดเห็นไม่เกิน 25 รายการพร้อมคำตอบและ reaction ต้องมี lifecycle:publish และสิทธิ์ลบในโมเดลของแต่ละความคิดเห็นด้วย
  • ความคิดเห็นที่เปลี่ยนสถานะหลังจากอ่านข้อมูลจะตอบ conflict พร้อมสถานะปัจจุบัน ความคิดเห็นที่นำเข้าโดยไม่มีความยินยอมจะตอบ consent_missing เมื่อถูกอนุมัติ
  • reaction เป็นของฝั่งผู้เยี่ยมชมและไม่มีการเขียนผ่าน management ความคิดเห็นในรายการมีจำนวน reaction ของตน

ทุกการเขียนระบุสถานะที่ใช้เป็นฐาน ค่าที่ล้าสมัยจะถูกปฏิเสธและไม่มีการเขียนใด ๆ

  • แก้ไขรายการ: expected_current_version_id หากล้าสมัย: 409 version_conflict พร้อม current_version_id การสร้างหรือแก้ไขระบุที่อยู่ใน slugs หนึ่งค่าต่อหนึ่งภาษา ({ "fr": "bonjour" }) ภาษาที่ไม่ได้ระบุจะคงที่อยู่เดิม ที่อยู่ที่ถูกปฏิเสธจะระบุ field: "slug" และ locale ของมัน การสร้างในภาษาที่เว็บไซต์ไม่มีจะถูกปฏิเสธ 400 unknown_locale พร้อม lang ภาษาที่ถูกนำออกระหว่างการสร้างให้ผล 409 language_changed และไม่มีสิ่งใดถูกเขียน
  • เผยแพร่: to, expected_workflow_revision, expected_current_version_id ฉบับร่างที่ใหม่กว่าจะไม่ถูกเผยแพร่โดยไม่มีใครรู้: 409 scheduled_target_stale workflow ที่ขยับไปแล้ว: 409 workflow_conflict พร้อม revision
  • ยกเลิกการเผยแพร่: to, expected_workflow_revision, expected_published_version_id และ acknowledged_dependents ที่เท่ากับ ids จาก /entries/{id}/dependents เมื่อมีเนื้อหาอื่นอ้างอิงรายการนี้
  • ภาษาเดียว: เพิ่ม locale ในการเผยแพร่หรือยกเลิกการเผยแพร่เพื่อเคลื่อนย้ายเฉพาะภาษานั้น to ถูกตรวจกับ state ของภาษานั้นเองใน workflow.locales และการยกเลิกการเผยแพร่จะ pin เวอร์ชันที่เผยแพร่ของภาษานั้นเอง หากไม่มี locale ทุกภาษาจะถูกเคลื่อนย้าย
  • บันทึกหน้า: expected_current_version_id และ expected_facts_digest จาก GET /pages/{id} การบันทึกหนึ่งครั้งมีหนึ่งภาษา (locale) และเปลี่ยนเฉพาะภาษานั้น แต่ละภาษามี section ของตัวเอง
  • เริ่มภาษาของหน้า: locale (ภาษาที่ยังไม่มีในหน้านี้ หรือภาษาของไซต์ที่ยังไม่มีเวอร์ชันของหน้านี้), source (อีกภาษาหนึ่งของหน้าที่จะคัดลอก หรือ null สำหรับหน้าว่าง) และ expected_current_version_id การคัดลอกคงลำดับ รูปลักษณ์ ค่า และลิงก์ไปยัง shared section ไว้ภายใต้ id ของ section ใหม่ 409 translation_exists, 422 translation_source_unavailable
  • ข้อเสนอ: base_version_id ต้องเป็นเวอร์ชันปัจจุบันของรายการ
  • lease การแก้ไข: ขณะที่บุคคลอื่นเปิดรายการและภาษาไว้ในตัวแก้ไข การบันทึก การย้าย การมอบหมาย การกู้คืน หรือถังขยะของรายการและภาษานั้นจะถูกปฏิเสธ 423 locked พร้อม holder GET /entries/{id}/editing?locale=&kind= อ่านว่าใครถืออยู่และ draft ร่วม (kind: content สำหรับตัวแก้ไขรายการ page สำหรับ page composer) POST /entries/{id}/editing/takeover จบ lease นั้นทันที (ตัวแก้ไขเก็บงานไว้เป็น draft กู้คืน) และหลังจากนั้นไม่มีใครถือ ตัวแก้ไขที่คุณเปิดเองไม่เคยบล็อกข้อมูลรับรองของคุณ lease ที่หมดอายุไม่บล็อกเลย
  • draft ร่วม: PUT /entries/{id}/editing/draft ระบุ kind และ expected_revision (0 เมื่อยังไม่มี) ค่าที่ล้าสมัย: 409 draft_conflict พร้อม current POST /entries/{id}/editing/draft/discard ลบ draft นั้น draft ไม่ใช่เวอร์ชัน การอัปเดตรายการล้าง draft
  • เลื่อนขั้นตอน: to และ expected_workflow_revision พร้อม note และ locale ที่ไม่บังคับ ขั้นตอนที่จะเผยแพร่หรือยกเลิกการเผยแพร่ให้ผล 422 transition_kind_mismatch ให้ใช้การเผยแพร่หรือยกเลิกการเผยแพร่แทน การเลื่อนขั้นตอนโดยไม่มี locale ระหว่างสองขั้นที่ไม่ได้เผยแพร่จะคงภาษาที่เผยแพร่อยู่แล้วไว้ที่เดิม ให้ยกเลิกการเผยแพร่ภาษานั้นแยกต่างหาก การเลื่อนขั้นตอนล้างการมอบหมาย เหมือนในหน้าผู้ดูแล
  • การมอบหมาย: expected_workflow_revision, assignee_id (คนจาก /assignees หรือ null เพื่อล้าง) และ due_date ที่ไม่บังคับ (YYYY-MM-DD วันนี้หรือหลังจากนั้น) ค่าเก่า: 409 workflow_conflict พร้อม revision คนที่ดำเนินการกับรายการไม่ได้: 422 assignee_cannot_act
  • กู้คืนเวอร์ชัน: expected_current_version_id เวอร์ชันเก่ากลายเป็นฉบับร่างปัจจุบันใหม่ สิ่งที่เผยแพร่อยู่ไม่เปลี่ยน ค่าเก่า: 409 version_conflict locale ที่ไม่บังคับกู้คืนเฉพาะภาษานั้น: ฟิลด์ รายการความสัมพันธ์ของภาษานั้นเอง และที่อยู่มาจากเวอร์ชัน ฟิลด์ร่วม ความสัมพันธ์ร่วม สื่อ และทุกภาษาอื่นคงเดิม ภาษาที่เวอร์ชันหรือรายการไม่มี: 422 restore_locale_missing ต้องมี restore สำหรับภาษานั้น
  • ภาษาใหม่: lang จาก /languages และ slug ที่ไม่บังคับ (null: ไม่มีที่อยู่ ถ้าไม่ส่ง: ใช้ที่อยู่ของภาษาหลัก และเติมส่วนท้ายถ้าภาษานี้มีที่อยู่นั้นอยู่แล้ว) ภาษาเริ่มว่าง การแก้ไขรายการครั้งถัดไปจะเขียนลงไป มีอยู่แล้ว: 409 locale_exists พร้อม instance_id ของภาษานั้น
  • ถังขยะ: scope: entry ย้ายทุกภาษา scope: language ย้ายภาษาเดียวที่ไม่ใช่ภาษาหลัก (owner_language สำหรับภาษาหลัก) เนื้อหาที่เผยแพร่อยู่ถูกปฏิเสธด้วย 409 content_live และ live_languages ให้ยกเลิกการเผยแพร่ก่อน รายการที่กู้คืนเป็นฉบับร่าง
  • กู้คืนจากถังขยะ: GET /entries/{id}/trash ระบุ conflicts (ขวางการกู้คืน) และ references (ลิงก์ที่จะใช้ไม่ได้) การกู้คืนถูกปฏิเสธด้วย 409 restore_conflict ขณะที่รายการอื่นใช้ที่อยู่นั้น
  • ลบถาวร: confirm ซ้ำ id ของรายการจากพาธ ลบรายการ ประวัติ และความคิดเห็นของผู้เยี่ยมชม ย้อนกลับไม่ได้ GET /entries/{id}/trash บอกจำนวนความคิดเห็นที่จะลบใน comments และคำตอบบอกจำนวนที่ลบแล้วใน comments_deleted
  • เชื่อมโยง (relate): ids (1–25 รายการ), field (ฟิลด์ความสัมพันธ์ many-to-many ที่ทุกรายการมี), connect และ/หรือ disconnect (รายการที่ลิงก์รวม 1–10 รายการ ไม่มีรายการใดอยู่ทั้งสองฝั่ง) และ expected_versions ที่ระบุเวอร์ชันปัจจุบันของทุกรายการ แต่ละรายการเป็นการบันทึกฉบับร่างของตัวเองและได้ผลของตัวเอง: done, unchanged (เป็นอย่างที่ขออยู่แล้ว ไม่มีการเขียน), covered (ภาษาอื่นของรายการเดียวกันทำไปแล้ว สำหรับฟิลด์ที่มีลิงก์ต่างกันในแต่ละภาษา ภาษาแรกที่เลือกจะเปลี่ยนรายการของทุกภาษาที่เลือกในการบันทึกเดียว) หรือ refused พร้อม reason เช่น version_conflict หรือ batch_relation_exclusive สำหรับฟิลด์ one-to-many การปฏิเสธไม่หยุดรายการอื่น ไม่มีการเผยแพร่
  • เชื่อมโยงจาก: GET /entries/{id}/related-from แสดงทุกฟิลด์ความสัมพันธ์ทุกชนิด (one-to-one, many-to-one, one-to-many, many-to-many) ที่มีเป้าหมายเป็นโมเดลของรายการนี้ เป็น groups แยกตามโมเดลและฟิลด์ต้นทาง พร้อม relation_type ต้นทางแต่ละรายการมี label ที่ใช้แสดง, draft / live, version_id, can_change และ readable ต้นทางที่ข้อมูลรับรองอ่านไม่ได้จะไม่มี slug และ label PUT รับ field_id, source_id, action (add หรือ remove) และ expected_source_version_id ทั้งสองต้องมี read บนรายการนี้ การเปลี่ยนต้องมี update บนต้นทางด้วย เป็นการบันทึกฉบับร่างของต้นทางเอง: ใช้สิทธิ์ กฎของฟิลด์ จำนวนความสัมพันธ์ กฎเจ้าของรายเดียว กฎลำดับชั้น และการตรวจเวอร์ชันของต้นทาง และไม่เผยแพร่เลย ค่าเก่า: 409 version_conflict ต้นทางแบบ to-one ที่ชี้ไปรายการอื่นอยู่แล้วจะถูกปฏิเสธด้วย 409 related_from_replace_unconfirmed พร้อมระบุ current และ next (instance_id, slug, label ชื่อที่ข้อมูลรับรองอ่านไม่ได้เป็น null) ส่ง replace_target_id เป็น current.instance_id เพื่อยืนยันการแทนที่นั้นโดยเฉพาะ คำตอบคือ outcome (saved หรือ unchanged), version_id ใหม่ของต้นทาง และ related_from ที่อัปเดตแล้ว

GET /entries/{id} คืน state ของ workflow, revision และ transitions ที่ข้อมูลรับรองนี้ใช้ได้: kind: publish | unpublish เมื่อมี lifecycle:publish และ kind: move เมื่อมี workflow:move ค่า to ต้องเป็นหนึ่งในนั้น การเผยแพร่และการยกเลิกการเผยแพร่ปฏิเสธขั้นตอนชนิดอื่นด้วย transition_kind_mismatch และการเลื่อนขั้นตอนก็เช่นกัน

การเชื่อมต่อที่ไม่ควรเขียนโดยตรงสามารถเสนอการเปลี่ยนฟิลด์แทนได้:

{
"base_version_id": "<current version id>",
"fields": { "title": "A clearer title" },
"note": "Why"
}
  • ฟิลด์ค่าของโมเดลรายการได้สูงสุด 50 ฟิลด์ ค่ารวมไม่เกิน 64 KiB และหมายเหตุไม่เกิน 2,000 อักขระ ฟิลด์ความสัมพันธ์และฟิลด์สื่อเสนอไม่ได้ (proposal_field_unsupported)
  • ไม่มีอะไรเปลี่ยนจนกว่าจะมีคนนำไปใช้ ตัวแก้ไขรายการแสดงข้อเสนอที่เปิดอยู่เหนือฟิลด์ และแสดงค่าที่เสนอข้างฟิลด์ที่ระบุ พร้อมปุ่ม ใช้ และ ปฏิเสธ
  • ใช้ คือการบันทึกปกติในนามผู้ที่กดใช้ และผูกกับ base_version_id การตรวจสอบ สิทธิ์ กฎความเป็นเจ้าของ และ workflow ของผู้นั้นมีผลทั้งหมด หากรายการเปลี่ยนไปหลังจากมีข้อเสนอ ใช้ จะปฏิเสธด้วย proposal_stale และไม่เขียนอะไร
  • ตัวแก้ไขจะปิด ใช้ ขณะมีการแก้ไขที่ยังไม่บันทึก ปฏิเสธ จะปล่อยรายการไว้ตามเดิม
  • source ของข้อเสนอระบุผู้สร้าง: { "kind": "management", "id", "label" } สำหรับ credential หรือ { "kind": "plugin", "id", "label", "requested_by" } สำหรับการติดตั้ง plugin ที่ทำแทน บุคคล (docs/plugins/protocol-v1.md) ตัวแก้ไขแสดงข้อเสนอจาก plugin เป็น Proposed by <plugin> · requested by <person> รายการข้อเสนอยังรวม { "kind": "ai", "id", "label" } โดย id ระบุงาน AI แบบกลุ่ม ข้อเสนอแต่ละรายการมี revision ส่วน original_fields เป็นฟิลด์เสริมที่มีค่าเดิมของฟิลด์ที่เสนอ เฉพาะค่าที่ผู้ใช้มีสิทธิ์อ่าน

schema:write เปลี่ยนโมเดลโดยไม่มีขั้นตอนตรวจ ทุกการเขียนใช้การ apply หรือการลบของ ตัวสร้างเนื้อหา เอง: การตรวจ การปฏิเสธการเปลี่ยนแปลงที่ไม่เข้ากัน กฎความสัมพันธ์ การย้ายสิทธิ์ และบันทึกตรวจสอบชุดเดียวกัน

  1. GET /models/{id} ตอบ definition ทั้งหมดและ revision
  2. เขียนโดยอ้าง revision นั้น:
  • POST /models พร้อม { "definition": { "model": …, "fields": [ … ] } } สร้างโมเดล ไม่มี id ของฟิลด์

  • PUT /models/{id} พร้อม expected_revision และ model (name, slug, description, settings, enableI18n) เปลี่ยนโมเดลและคงฟิลด์ไว้

  • PUT /models/{id}/folder พร้อม { "folder": "Blog" } (1 ถึง 40 ตัวอักษร หรือ null) ใส่โมเดลในโฟลเดอร์นำทางเนื้อหาหรือเอาออก เป็นการแสดงผลเท่านั้น: ไม่มี expected_revision, revision ของโมเดลไม่เปลี่ยน และ listModels แสดงเป็น folder คำตอบคือ model_id, slug และ folder

  • PUT /models/{id}/definition พร้อม expected_revision และ definition ที่ครบถ้วน แทนที่นิยามเดิม: คงฟิลด์ไว้ด้วย id เพิ่มฟิลด์โดยไม่มี id และเอาฟิลด์ออกเพื่อลบ

  • POST /models/{id}/remove พร้อม expected_revision และ confirm (id ของโมเดลอีกครั้ง) ลบโมเดลที่ไม่มีเนื้อหา

  • คำตอบคือ model_id, slug, created_field_ids และ renamed_from หลังการเปลี่ยนชื่อหรือการลบ dependents.complete บอกว่าสิทธิ์ของบทบาทและขอบเขตโทเค็นทุกอันที่ระบุ slug เดิมถูกย้ายหรือลบแล้วหรือไม่ เมื่อเป็นการเล่นซ้ำจะไม่มีฟิลด์นี้

  • revision เปลี่ยนเมื่อโมเดลหรือฟิลด์ใดเปลี่ยน revision ที่ล้าสมัยได้ 409 model_changed และไม่เขียนอะไรเลย

  • การเปลี่ยนที่ไม่ปลอดภัยต่อเนื้อหาที่มีอยู่จะถูกปฏิเสธด้วย 409 model_change_blocked และ changes (kind, field, affected_count, message, suggestion) ไม่มีการข้าม ให้ย้ายหรือล้างเนื้อหาก่อน

  • การปฏิเสธอื่นใช้เหตุผลเดียวกับ ตัวสร้างเนื้อหา เช่น duplicate_field_key, field_not_found, unique_unsupported, model_name_collision, stale_role_grant, stale_token_scope, model_not_empty และ model_has_bound_queries

  • ผู้สร้างต้องมี model:apply ในขณะนั้น และสำหรับการลบต้องมี model:delete ด้วย ถ้าไม่มีจะได้ 403 missing_permission

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

  1. GET /models/{id} ตอบ definition ทั้งหมดของโมเดล (model และ fields โดยแต่ละฟิลด์มี id), updated_at และ revision
  2. POST /models/proposals พร้อมนิยามที่แก้ไขแล้ว:
{
"mode": "update",
"model_id": "<model id>",
"base_updated_at": "<updated_at from getModel>",
"definition": {
"model": { "name": "Article", "slug": "article", "enableI18n": true },
"fields": ["…"]
},
"note": "Why"
}
  • definition ต้องครบถ้วน: คงแต่ละฟิลด์ไว้ด้วย id ของมัน เพิ่มฟิลด์โดยไม่มี id และไม่ใส่ฟิลด์ที่ต้องการลบ ลำดับฟิลด์คือลำดับในอาร์เรย์ config, derived, ui และ validation เป็นข้อความ JSON ตามที่ ตัวสร้างเนื้อหา เก็บไว้
  • โมเดลใหม่คือ { "mode": "create", "definition": { … }, "note": … } โดยไม่มี id ของฟิลด์
  • ไม่เกิน 200 ฟิลด์ และ note ไม่เกิน 500 ตัวอักษร
  • นิยามถูกอ่านและตรวจแบบเดียวกับที่การ apply อ่าน สิ่งที่การ apply ปฏิเสธจะถูกปฏิเสธที่นี่ด้วยเหตุผลเดียวกัน เช่น duplicate_field_key, field_not_found, unique_unsupported, model_slug_taken และ model_name_collision หากโมเดลเปลี่ยนไปหลัง getModel คำตอบคือ 409 model_changed พร้อม current_updated_at
  • การเปลี่ยนที่จะทำให้เนื้อหาเดิมเสียหายทำได้ และจะแสดงใน changes (kind, field, affected_count, message, suggestion) ผู้ใช้จะเห็นการตรวจเดียวกันอีกครั้งก่อน apply
  • ผู้สร้างต้องมี model:apply ในขณะนั้น หากไม่มี คำตอบคือ 403 missing_permission
  • ตัวสร้างเนื้อหา → โมเดล แสดงข้อเสนอที่เปิดอยู่พร้อม ตรวจทาน และ ปัดตก ตรวจทาน เปิดเอดิเตอร์พร้อมนิยามที่เสนอ บันทึก จะนำไปใช้ เป็นการเปลี่ยนโมเดลตามปกติในนามผู้ที่บันทึก และทำเครื่องหมายข้อเสนอเป็น applied ในการเขียนเดียวกัน ผู้ใช้แก้ไขก่อนได้
  • ข้อเสนอผูกกับ base_updated_at เมื่อโมเดลเปลี่ยน ข้อเสนอจะเป็น stale: true การ apply จะถูกปฏิเสธด้วย 409 schema_proposal_stale และทำได้เพียงปัดตกข้อเสนอ
  • GET /models/proposals แสดงข้อเสนอของไซต์ ใหม่สุดก่อน (state กรอง open, applied, dismissed) GET /models/proposals/{id} อ่านข้อเสนอหนึ่งรายการ

เอเจนต์เปิดปลั๊กอินที่ติดตั้งไว้ให้ผู้ใช้ได้ การเปิดไม่เคยสั่งให้ปลั๊กอินทำงาน การดำเนินการ ที่ประกาศไว้เป็นสิ่งที่สั่งได้

  • GET /plugins แสดงปลั๊กอินที่ติดตั้ง: state (active หรือ disabled), launchable และสำหรับปลั๊กอินที่ใช้งานอยู่คือ manage_views และ content_actions ข้อมูลมาจาก manifest ที่บันทึกไว้ตอนติดตั้งหรืออัปเกรด GET /plugins/manage-views และ GET /plugins/content-actions แสดงรายการเดียวกันแบบรายการเรียบ เมื่อระบุ instance_id จะแสดงเฉพาะการทำงานที่ใช้ได้กับรายการนั้น
  • การทำงานกับรายการแสดง model_ids: โมเดลที่ให้สิทธิ์ปลั๊กอินและข้อมูลรับรองนี้อ่านได้
  • GET /rich-blocks (content:read) แสดงนิยามบล็อก rich text ของแต่ละปลั๊กอินที่ติดตั้ง: ฟิลด์ กฎ ค่าตัวอย่าง render tree และ migration ในรูป <plugin id>/<name> เขียนบล็อกลงในค่า rich เป็น figure แบบ canonical ของบล็อก (ดู Blog → บล็อกของปลั๊กอิน) เพิ่มหรือเปลี่ยนได้เฉพาะบล็อกของปลั๊กอินที่ใช้งานอยู่
  • แสดงเฉพาะการเปิดที่ข้อมูลรับรองเปิดได้ตอนนี้ ทั้งในรายการและในเครื่องมือ หน้าของปลั๊กอินต้องมีโมเดลที่ให้สิทธิ์ปลั๊กอินซึ่งผู้สร้างอ่านได้ รวมถึงอ่านได้เฉพาะรายการของตัวเอง และอยู่ในโมเดลของข้อมูลรับรอง การทำงานกับรายการต้องมีโมเดลอย่างน้อยหนึ่งโมเดลใน model_ids การเปลี่ยนสิทธิ์มีผลตั้งแต่คำขอถัดไป การเปิดที่ส่งมาทั้งที่ไม่แสดงก็ยังถูกตรวจครบทุกข้อ
  • POST /plugins/{id}/manage-views/{view}/launch และ POST /plugins/{id}/content-actions/{action}/entries/{instanceId}/launch สร้างลิงก์เปิดหนึ่งลิงก์ คำตอบคือ 201 พร้อม outcome: launch_created, url, expires_at และ single_use: true
  • ลิงก์มีรหัสเปิดหนึ่งรหัส ใช้ได้ครั้งเดียวและหมดอายุ 60 วินาทีหลังสร้าง ผู้ใช้เปิดลิงก์ในเบราว์เซอร์ แล้วปลั๊กอินทำงานในนามผู้สร้างข้อมูลรับรอง เหมือนตอนที่ผู้ใช้คนนั้นเปิดปลั๊กอินจากหน้าแอดมิน ก่อนหน้านั้นปลั๊กอินไม่ได้รับอะไรเลย และคำตอบไม่เคยบอกว่าปลั๊กอินทำอะไรแล้ว
  • เมื่อเปิดแล้ว ปลั๊กอินทำงานภายในสิทธิ์ของตัวเองและสิทธิ์ของผู้สร้าง จำกัดด้วยโมเดลของข้อมูลรับรองและถูกตัดด้วย scope delegation มีเฉพาะ capability ของปลั๊กอินที่ scope อนุญาต: content:read ให้ content:read และ workflow:read; lifecycle:publish ให้ workflow:publish, workflow:unpublish และ workflow:locale; content:write ให้ content:write, content:create และ draft:write; content:trash ให้ content:delete; media:write ให้ media:write, media:read, media:update และ media:source; content:propose ให้ชื่อเดียวกัน คำสั่งของปลั๊กอินที่อยู่นอกนั้นคือ capability_refused
  • ทุกครั้งที่เปิดจะตรวจสิทธิ์ปัจจุบันของผู้สร้าง โมเดลของข้อมูลรับรอง และสิทธิ์ของปลั๊กอินอีกครั้ง เป็นการตรวจเดียวกับในหน้าแอดมิน ปลั๊กอินที่ปิดอยู่ได้ 409 installation_not_active ปลั๊กอินที่ถอนการติดตั้งแล้วหรือของไซต์อื่นได้ 404 not_found
  • ไม่มี Idempotency-Key: ทุกการเรียกสร้างลิงก์ใหม่ ลิงก์ที่ไม่ได้ใช้จะหมดอายุเอง

manifest ของปลั๊กอินประกาศการดำเนินการได้: ฟอร์มเล็ก ๆ และผลลัพธ์ที่ Dee Wan วาดเอง เอเจนต์เรียกใช้แล้วได้ผลลัพธ์กลับมา

  • GET /plugins/operations แสดงการดำเนินการที่ข้อมูลรับรองนี้เรียกใช้ได้ตอนนี้: scope (site หรือ entry), destructive, input (ช่องที่ประกาศไว้), input_schema (JSON Schema ของ input), result (ส่วนประกอบของผลลัพธ์) และ model_ids (สำหรับการดำเนินการกับรายการ คือโมเดลที่ใช้ได้) เมื่อระบุ instance_id จะแสดงเฉพาะการทำงานที่ใช้ได้กับรายการนั้น GET /plugins มีรายการเดียวกันแยกตามปลั๊กอิน
  • การดำเนินการจะแสดงเฉพาะตอนที่ปลั๊กอินใช้งานอยู่และสิทธิ์ของปลั๊กอินมี capability ทุกอย่างที่การดำเนินการต้องใช้ ภายในสิ่งที่ scope ของข้อมูลรับรองอนุญาต การดำเนินการกับรายการยังต้องมีโมเดลที่ให้สิทธิ์ซึ่งผู้สร้างอ่านได้ภายในโมเดลของข้อมูลรับรอง ส่วนที่ทำลายข้อมูลต้องมี update การดำเนินการระดับไซต์ต้องมี site:plugins และปลั๊กอินจะกระทำแทนผู้สร้างบนโมเดลที่ได้รับสิทธิ์ซึ่งผู้สร้างอ่านได้ ภายในโมเดลของข้อมูลรับรอง และจำกัดตาม scope releases, release-edit และ release-run ของ Scheduling เป็นการดำเนินการแบบนี้ การเปลี่ยนสิทธิ์มีผลตั้งแต่คำขอถัดไป
  • POST /plugins/{id}/operations/{operation}/invoke เรียกใช้ body คือ { "instance_id"?: "…", "input"?: { … } } โดย instance_id จำเป็นสำหรับการดำเนินการกับรายการและถูกปฏิเสธสำหรับระดับไซต์ input เป็นแบบปิด: คีย์ที่ไม่รู้จักหรือค่านอกขอบเขตของช่องได้ 400 input_invalid พร้อม issues
  • คำตอบคือ 200 พร้อม outcome: completed และ result.blocks: text, rows, links (https เท่านั้น) และ json Idempotency-Key และ body เดิมจะคืนผลลัพธ์แรกพร้อม replayed: true ส่วนคีย์เดิมกับ body อื่นได้ 409 idempotency_key_reused การเล่นซ้ำจะถูกตรวจเหมือนการเรียกใหม่ก่อน ถ้าข้อมูลรับรองนี้เรียกการดำเนินการนั้นไม่ได้แล้ว จะได้ 404 operation_unavailable ไม่ใช่ผลลัพธ์เดิม
  • การปฏิเสธ: 404 operation_unavailable (ไม่รู้จัก หรือข้อมูลรับรองนี้ใช้ไม่ได้ตอนนี้), 422 plugin_refused (refusal: { code, message } ของปลั๊กอินเอง), 504 plugin_timeout, 502 plugin_unreachable, 502 plugin_response_invalid, 409 plugin_unavailable (ปลั๊กอินถูกปิด ลดสิทธิ์ หรือเปลี่ยนระหว่างทำงาน), 409 invocation_in_progress
  • ปลั๊กอินทำงานในนามผู้สร้างข้อมูลรับรอง และไม่เคยได้รับข้อมูลรับรอง: core ส่งรหัสใช้ครั้งเดียว แล้วปลั๊กอินแลกรหัสนั้นด้วยโทเค็นการติดตั้งของตัวเอง การเรียกใช้ถูกบันทึกในการตรวจสอบเป็น plugin.operation.run พร้อม digest ของค่าและผลลัพธ์ ไม่ใช่ตัวค่าเอง

ปลั๊กอินอาจประกาศนโยบายเนื้อหา: การตรวจที่ Dee Wan ถามก่อนการบันทึก การกู้คืน การลบ การเผยแพร่ การยกเลิกการเผยแพร่ หรือการย้าย workflow การอัปเดตรายการ การบันทึกหน้า การกู้คืนเวอร์ชัน คำสั่งถังขยะ และการเปลี่ยนสถานะผ่าน API นี้จะเรียกการตรวจแบบเดียวกับในตัวแก้ไข โดยมี caller.via: management_credential GET /plugins แสดง policies ของแต่ละปลั๊กอินพร้อม kind และ mode

  • คำตอบมี policy_results: นโยบายที่เตือน นโยบายในโหมด warn ที่ไม่ตอบ (outcome: unavailable พร้อม failure) หรือนโยบายที่แปลงเนื้อหาที่บันทึก (outcome: transformed โดย fields ระบุสิ่งที่เปลี่ยน) แต่ละรายการระบุ installation_id, plugin_name, policy_id, policy_label, locale, message และ fields การเขียนเสร็จแล้ว อย่าส่งซ้ำ หลังการแปลง ให้อ่านรายการอีกครั้งเพื่อดูค่าที่เก็บ
  • 422 plugin_policy_transform_invalid: เนื้อหาที่นโยบายแปลงละเมิดกฎ cause มี code, message และ detail ของกฎ ไม่มีการเขียน
  • 422 plugin_policy_transform_conflict: สองนโยบายเปลี่ยนฟิลด์เดียวกันต่างกัน ไม่มีการเขียน
  • 422 plugin_policy_refused: นโยบายปฏิเสธ ระบุ plugin, policy, policy_label, locale, code และ message ของปลั๊กอินเอง และ fields ไม่มีอะไรถูกเขียน
  • 503 plugin_policy_unavailable: นโยบายในโหมดบล็อกไม่ตอบ ระบุนโยบายและ failure ไม่มีอะไรถูกเขียน ลองใหม่ภายหลัง
  • 429 plugin_policy_budget_exhausted: คำขอเดียวถามนโยบายบ่อยเกินกว่าที่อนุญาต ส่งรายการให้น้อยลงต่อครั้ง

dee-wan-manage.mjs เป็นไฟล์เดียวที่ไม่มี dependency ต้องใช้ Node 18 ขึ้นไป ดาวน์โหลดได้จาก การตั้งค่า → API ข้อมูลรับรองคือ DEE_WAN_MANAGEMENT_TOKEN เมื่อตั้งค่าไว้ มิฉะนั้นจะใช้การเข้าสู่ระบบที่บันทึกไว้สำหรับ origin นั้น (ดู ลงชื่อเข้าใช้จากเทอร์มินัล) origin ของ CMS คือ --url หรือ DEE_WAN_URL origin ต้องใช้ https (ใช้ http ได้เฉพาะบน localhost, 127.0.0.1 หรือ [::1])

Terminal window
export DEE_WAN_MANAGEMENT_TOKEN=<credential>
node dee-wan-manage.mjs help
node dee-wan-manage.mjs list-entries --url https://cms.example --model-id <model id>
node dee-wan-manage.mjs create-entry --url https://cms.example --body-file entry.json
node dee-wan-manage.mjs upload-media --url https://cms.example --file photo.webp
node dee-wan-manage.mjs launch-plugin-manage-view --url https://cms.example --id <installation id> --view calendar
  • แต่ละคำสั่งคือหนึ่งการดำเนินการในตารางด้านบน เขียนแบบ kebab case (publishEntry → publish-entry) ไม่มีโหมดส่งคำขอดิบ
  • การเขียนส่ง Idempotency-Key เสมอ คีย์จะพิมพ์ออกทาง stderr ระบุคีย์เดิมด้วย --key <key> เพื่อลองใหม่อย่างปลอดภัย
  • รหัสออก 0 เมื่อสำเร็จ 1 เมื่อ Dee Wan ปฏิเสธ (จะพิมพ์คำตอบออกมา) และ 2 สำหรับปัญหาการใช้งาน เครือข่าย หรือข้อมูลรับรอง
Terminal window
node dee-wan-manage.mjs login --url https://cms.example
node dee-wan-manage.mjs status --url https://cms.example
node dee-wan-manage.mjs logout --url https://cms.example

login ลงชื่อเข้าใช้ด้วยบัญชีของคุณเอง เหมือน เชื่อมต่อแอป:

  1. อ่าน /.well-known/oauth-authorization-server issuer ต้องเป็น origin นั้น และ endpoint ของ token, registration และ revocation ต้องอยู่บน origin นั้น ต้องรองรับ PKCE S256
  2. เริ่ม listener บน 127.0.0.1 เท่านั้น บนพอร์ตที่ระบบเลือก และลงทะเบียน client ที่มี redirect URI เดียวคือ http://127.0.0.1:<port>/callback
  3. เปิด /connect ในเบราว์เซอร์พร้อม PKCE S256 และ state แบบสุ่ม --no-open จะพิมพ์ที่อยู่ออกมาแทน --scope "content:read content:write" ขอ scope น้อยลง หากไม่ระบุจะขอทุก scope คุณเลือกไซต์ในหน้ายินยอม
  4. callback ต้องเป็น GET /callback ที่มี state เดียวกัน มี issuer เป็น iss และมี code หนึ่งค่าพอดี path อื่นตอบ 404 ยกเลิก จบการลงชื่อเข้าใช้ด้วยรหัสออก 1 หลังจาก 5 นาทีจะเลิกรอด้วยรหัสออก 2
  5. แลกโค้ดและบันทึกการเข้าสู่ระบบ

การลงชื่อเข้าใช้ที่ล้มเหลวหรือถูกยกเลิกจะไม่เขียนอะไร การเข้าสู่ระบบที่บันทึกไว้จะคงเดิม

การเข้าสู่ระบบที่บันทึกไว้:

  • หนึ่งรายการต่อ origin ในไดเรกทอรีการตั้งค่าของระบบปฏิบัติการ: ~/Library/Application Support/dee-wan บน macOS, $XDG_CONFIG_HOME/dee-wan หรือ ~/.config/dee-wan บน Linux, %APPDATA%\dee-wan บน Windows ไดเรกทอรีเป็น 0700 และไฟล์เป็น 0600 (บน Windows ตั้งสิทธิ์ได้เท่าที่ระบบรองรับ)
  • เก็บ origin, client id, ไซต์, scope และ refresh token ปัจจุบัน ไม่เก็บ access token เลย ไม่มีคำสั่งใดพิมพ์ token
  • ทุกคำสั่งต่ออายุมัน: ล็อก origin นั้น ใช้ refresh token เขียนตัวใหม่ลงไฟล์ชั่วคราวในไดเรกทอรีเดียวกัน flush แล้ว rename ทับไฟล์เดิม สองคำสั่งที่รันพร้อมกันจะรอกัน refresh token จึงไม่ถูกใช้สองครั้ง ล็อกที่ โพรเซสที่สิ้นสุดแล้วทิ้งไว้ หรือที่เก่ากว่า 2 นาที จะถูกล้าง
  • เมื่อ Dee Wan ไม่รับมันอีกต่อไป (ถูกเพิกถอน หมดอายุหลังไม่ได้ใช้ 30 วัน หรือผู้ใช้เสียสิทธิ์ในไซต์) คำสั่งจะออกด้วยรหัส 2 และขอให้รัน login อีกครั้ง ไฟล์จะถูกเก็บไว้
  • login ใหม่สำหรับ origin เดียวกันจะแทนที่มันและเพิกถอนการเชื่อมต่อเดิม
  • DEE_WAN_MANAGEMENT_TOKEN มีลำดับเหนือกว่าเสมอ และไม่เคยถูกบันทึก status บอกเมื่อมันแทนที่การเข้าสู่ระบบที่บันทึกไว้

status แสดง origin, ไซต์และ scope ที่บันทึกไว้ มี client id บันทึกไว้หรือไม่ และตั้ง DEE_WAN_MANAGEMENT_TOKEN ไว้หรือไม่ หากไม่มีทั้งสองอย่างจะออกด้วยรหัส 2 logout เพิกถอนการเชื่อมต่อที่ revocation endpoint ก่อน แล้วจึงลบไฟล์ หากการเพิกถอนล้มเหลว ไฟล์จะถูกเก็บไว้และออกด้วยรหัสที่ไม่ใช่ศูนย์ ให้รันอีกครั้ง

node dee-wan-manage.mjs mcp --url <origin> รันเซิร์ฟเวอร์ MCP ผ่าน stdio เครื่องมือของมันคือการดำเนินการชุดเดียวกัน เขียนแบบ snake case (publish_entry) พร้อม input schema จากสัญญา เหมือนเซิร์ฟเวอร์ระยะไกล มันแสดงเฉพาะเครื่องมือที่ขอบเขตของข้อมูลรับรองอนุญาต (อ่านจาก GET /site) และเครื่องมือของปลั๊กอินด้านล่าง เครื่องมือเขียนรับ idempotency_key แบบไม่บังคับและคืนคีย์ที่ใช้ การเรียกตอบเป็น structuredContent { status, idempotency_key?, body } และวัตถุเดียวกันเป็นข้อความ JSON การตั้งค่า → API แสดงการตั้งค่าไคลเอนต์ให้คัดลอก:

{
"mcpServers": {
"dee-wan": {
"command": "node",
"args": [
"/path/to/dee-wan-manage.mjs",
"mcp",
"--url",
"https://cms.example"
],
"env": { "DEE_WAN_MANAGEMENT_TOKEN": "<your credential>" }
}
}
}

หากไม่มี env เซิร์ฟเวอร์ MCP จะใช้การเข้าสู่ระบบที่บันทึกไว้สำหรับ --url และต่ออายุภายใต้ล็อกเดียวกันขณะที่ session ทำงาน

เครื่องมืออัปโหลดอ่านพาธไฟล์ในเครื่องที่รันเซิร์ฟเวอร์

การนำเข้าและส่งออกจากบรรทัดคำสั่ง:

Terminal window
node dee-wan-manage.mjs create-import --body '{"format":"wxr","source":{"label":"Old blog","system":"wordpress"}}'
node dee-wan-manage.mjs upload-import-part --id <job id> --file export.xml
node dee-wan-manage.mjs advance-import --id <job id> --follow
node dee-wan-manage.mjs create-export --body '{"product":"bundle"}'
node dee-wan-manage.mjs advance-export --id <export id>
node dee-wan-manage.mjs read-export-chunk --id <export id> --out site.json
  • upload-import-part --file ส่งทั้งไฟล์เป็นส่วนละ 5 MiB แล้วจบการอัปโหลดด้วย SHA-256 ของไฟล์ คีย์ของแต่ละส่วนมาจากไบต์ของส่วนนั้น การรันซ้ำจึงได้ผลเดิม ส่วนไบต์ที่เปลี่ยนไปจะถูกปฏิเสธ --part <n> ส่งเพียงส่วนเดียว เครื่องมือ MCP แบบ stdio รับ file_path และ part ที่ไม่บังคับ
  • advance-import --follow เดินหน้าทีละขั้น พิมพ์หนึ่งบรรทัดต่อขั้นใน stderr และหยุดเมื่อต้องตัดสินใจ เมื่อจบ หรือเมื่อถูกปฏิเสธ สถานะสุดท้ายพิมพ์เป็น JSON ผ่าน MCP การเรียกแต่ละครั้งคือหนึ่งขั้น
  • read-export-chunk --out <file> อ่านการส่งออกที่เสร็จแล้วทีละส่วน ตรวจ SHA-256 แล้วจึงเขียนไฟล์ เมื่อใช้ --page <n> จะบันทึกหน้าที่เก็บไว้หนึ่งหน้า --finish เดินหน้าการส่งออกจนเสร็จก่อน แต่ละขั้นใช้คีย์ที่ได้จากการส่งออกและขั้นนั้น การรันอีกครั้งหลังคำตอบหายจึงเล่นซ้ำแทนการสร้างสองครั้ง
  • read-import-artifact --out <file> บันทึก artifact การนำเข้าทั้งไฟล์ด้วยวิธีเดียวกัน
  • get-import-findings, get-import-mapping, get-word-press-review, get-history-assignments และ get-import-principals --all อ่านทุกหน้ารวมเป็นคำตอบเดียว
  • เมื่อได้ 429 และ 409 idempotency_in_progress พร้อม Retry-After คำสั่งจะรอตาม Retry-After แล้วลองใหม่ด้วยคีย์เดิม ถ้าไม่มี Retry-After คำสั่งจะหยุดและพิมพ์คำตอบ

เอเจนต์ที่เชื่อมต่อผ่าน HTTPS ไม่ต้องดาวน์โหลด ให้เอเจนต์เพียงที่อยู่ แล้วเอเจนต์จะพาคุณลงชื่อเข้าใช้ (ดู เชื่อมต่อแอป):

https://cms.example/api/manage/mcp

สำหรับงานอัตโนมัติ ให้ส่งข้อมูลรับรองการจัดการแทน การตั้งค่า → API แสดงทั้งสองแบบ:

{
"mcpServers": {
"dee-wan": {
"type": "http",
"url": "https://cms.example/api/manage/mcp",
"headers": { "Authorization": "Bearer <your credential>" }
}
}
}
  • ข้อความ JSON-RPC หนึ่งข้อความต่อ POST และคำตอบ JSON หนึ่งคำตอบ ไม่มีเซสชันและไม่มีสตรีม: GET และ DELETE ให้ 405 การแจ้งเตือนได้ 202 แบบกลุ่ม (batch) ถูกปฏิเสธ
  • เวอร์ชันโปรโตคอล 2025-06-18 และ 2025-03-26 ส่วนหัว MCP-Protocol-Version อื่นให้ 400
  • ข้อมูลรับรองถูกอ่านใหม่ทุกข้อความ ข้อมูลรับรองที่ถูกเพิกถอนได้ 401 ในข้อความถัดไป แต่ละ POST นับในขีดจำกัดอัตราหนึ่งครั้ง
  • tools/list แสดงเฉพาะเครื่องมือที่ขอบเขตของข้อมูลรับรองอนุญาต การเรียกผ่านการตรวจขอบเขต บันทึกตรวจสอบ ความขัดแย้ง และ idempotency เดียวกับ HTTP API ในนามผู้สร้างข้อมูลรับรอง
  • คำขอจากหน้าเบราว์เซอร์ของ origin อื่นได้ 403
  • เครื่องมืออัปโหลดระยะไกลรับไฟล์เป็น content_base64 และ file_name ไม่เกิน 8 MiB หลังถอดรหัส
  • upload_import_part รับหนึ่งส่วนเป็น content_base64 พร้อม id และ part ส่วน read_export_chunk และ read_import_artifact ตอบหนึ่งช่วงไบต์เป็น { content_base64, content_type, offset, length, total, next_offset, sha256, page, next_page } ไม่เคยตอบทั้งไฟล์ขนาดใหญ่ content_type และ sha256 อธิบายทั้งไฟล์ และ page และ next_page เป็น null เมื่อไม่ใช่หน้าของการส่งออก get_import_findings อ่านผลการตรวจหนึ่งหน้า
  • เมื่อมี plugins:use หน้าและการทำงานกับรายการของปลั๊กอินที่ใช้งานอยู่แต่ละอย่างจะเป็นเครื่องมือด้วย เช่น plugin_com_example_scheduler_view_calendar (ไม่มีอาร์กิวเมนต์) หรือ plugin_com_example_scheduler_action_schedule (instance_id) ชื่อคือ plugin_ ตามด้วย id ของปลั๊กอิน view หรือ action และ id ของมัน เป็นตัวพิมพ์เล็ก อักขระอื่นแทนด้วย _ เครื่องมือถูกอ่านใหม่ทุกข้อความ ปลั๊กอินที่ปิดหรือถอนการติดตั้งจะไม่มีเครื่องมือตั้งแต่ข้อความถัดไป ชื่อที่การเปิดสองอย่างใช้ร่วมกัน ชื่อที่ตรงกับเครื่องมือหลัก หรือชื่อยาวเกิน 64 อักขระ ระบบจะไม่แสดงเครื่องมือทั้งหมดที่มีชื่อชนกันหรือชื่อยาวเกินกำหนด GET /plugins บอกเหตุผลใน mcp_tool.omitted_because การเรียกหน้าหรือการทำงานเพียงสร้างลิงก์เปิดตามข้างต้น อะแดปเตอร์ stdio แสดงเครื่องมือชุดเดียวกัน
  • การดำเนินการแต่ละอย่างที่ข้อมูลรับรองเรียกใช้ได้ก็เป็นเครื่องมือด้วย เช่น plugin_com_example_scheduler_op_status อาร์กิวเมนต์คือ instance_id สำหรับการดำเนินการกับรายการ input (input_schema ของการดำเนินการ) และ idempotency_key ที่ไม่บังคับ การเรียกจะเรียกใช้การดำเนินการทันทีและคืนผลลัพธ์เป็น structuredContent และข้อความ annotation ยังคง destructive ไว้ เครื่องมือที่ข้อมูลรับรองใช้ไม่ได้แล้วจะหายไปตั้งแต่ข้อความถัดไป การเรียกมันได้ Unknown tool การดำเนินการแบบ automation ไม่เคยเป็นเครื่องมือ
  • ทั้งสองช่องทางให้ชื่อเครื่องมือ schema annotation ผลลัพธ์ และการปฏิเสธแบบเดียวกัน ต่างกันเพียงเครื่องมืออัปโหลด: ที่นี่ใช้ base64 ส่วนอะแดปเตอร์ stdio ใช้ path ของไฟล์ในเครื่อง
  • ข้อมูลรับรองสร้างโดยผู้ที่ลงชื่อเข้าใช้ใน การตั้งค่า → API หรือโดยการเชื่อมต่อแอป

ไคลเอนต์ MCP ที่รองรับ OAuth ให้ผู้ใช้ลงชื่อเข้าใช้ด้วยบัญชีของตัวเอง ไม่ต้องติดตั้งอะไร

  1. ไคลเอนต์เรียกที่อยู่ MCP โดยไม่มีโทเค็นและได้ 401 พร้อม WWW-Authenticate: Bearer error="invalid_token", resource_metadata="…"
  2. ไคลเอนต์อ่าน /.well-known/oauth-protected-resource/api/manage/mcp และ /.well-known/oauth-authorization-server ลงทะเบียนตัวเองที่ POST /api/oauth/register และเปิดหน้า /connect ของหน้าผู้ดูแล
  3. ผู้ใช้ลงชื่อเข้าใช้หากจำเป็น เลือกไซต์ คงหรือเอาเครื่องหมายออกจากสิ่งที่แอปขอ แล้วกด เชื่อมต่อ ส่วน ยกเลิก จะส่ง access_denied กลับไปยังแอป
  4. ไคลเอนต์แลกโค้ดที่ POST /api/oauth/token และเรียกที่อยู่ MCP ด้วย access token

กฎ:

  • ใช้ OAuth 2.1 authorization code กับ PKCE S256 เท่านั้น เฉพาะ public client: ไม่มี client secret
  • Redirect URI ต้องตรงทุกตัวอักษร: https; http บน 127.0.0.1 หรือ localhost พอร์ตใดก็ได้; หรือ private-use scheme ของแอปเดสก์ท็อป (RFC 8252 7.1) เช่น cursor://… หรือ com.example.app:/cb private scheme ต้องเป็นตัวพิมพ์เล็ก ยาว 2 ถึง 64 ตัวอักษร และไม่ใช่ javascript, data, file, vbscript, blob, about, filesystem, ftp, ws, wss, mailto, tel, sms, intent, chrome, chrome-extension หรือ view-source ไม่มี fragment ไม่มี user info และไม่เกิน 5 รายการ หน้ายินยอมแสดงว่าแอปจะกลับไปที่ใด ลิงก์ที่ระบุแอปที่ไม่รู้จักหรือ redirect URI ที่ไม่ได้ลงทะเบียนจะได้คำตอบบนหน้านั้นและไม่ถูก redirect เลย
  • โค้ดมีอายุ 60 วินาทีและใช้ได้ครั้งเดียว การใช้ครั้งที่สองจะเพิกถอนสิ่งที่ครั้งแรกออกให้
  • Access token (dwo_) มีอายุ 10 นาที Refresh token (dwr_) มีอายุ 30 วันและหมุนเวียนทุกครั้งที่ใช้ refresh token แต่ละตัวใช้ได้ครั้งเดียว การส่ง refresh token ใดก็ตามที่ถูกแทนที่แล้ว ไม่ว่าจะหมุนเวียนไปกี่ครั้ง จะเพิกถอนการเชื่อมต่อ เก็บเฉพาะ digest แบบ SHA-256
  • แอปที่ขอขอบเขตที่เซิร์ฟเวอร์ไม่รู้จักจะได้ invalid_scope แอปที่ไม่ขอขอบเขตใดจะได้รับการเสนอทุกขอบเขต
  • POST /api/oauth/revoke (RFC 7009) เพิกถอนการเชื่อมต่อทั้งหมดจากโทเค็นใดก็ได้
  • การเชื่อมต่อทำงานเหมือนข้อมูลรับรองที่ผู้ใช้คนนั้นสร้างด้วยขอบเขตที่อนุมัติ สำหรับทุกโมเดล content:read รวมอยู่เสมอ สิทธิ์ปัจจุบันของผู้ใช้ถูกอ่านใหม่ทุกคำขอ: ผู้ใช้ที่ถูกปิดใช้งาน สมาชิกภาพที่ถูกนำออก หรือไซต์ที่ไม่ active จะหยุดการเชื่อมต่อทันที
  • สมาชิกทุกคนของไซต์เชื่อมต่อแอปได้ในนามตัวเอง และให้สิทธิ์ได้เฉพาะที่ตัวเองมี
  • การตั้งค่า → API → แอปที่เชื่อมต่อ แสดงการเชื่อมต่อ: ของทุกคนเมื่อมี site:settings มิฉะนั้นเฉพาะของคุณ เพิกถอน หยุดการเชื่อมต่อทันที «ใช้ล่าสุดเมื่อ…» คือการแลกโทเค็นครั้งล่าสุด
  • การลงทะเบียนแบบ dynamic, token และ revocation ถูกจำกัดอัตราต่อ IP การเชื่อมต่อ โค้ด และไคลเอนต์ที่ไม่มีใครใช้จะถูกลบโดยการล้างรายวันหลัง 30 วัน
  • ใน production Worker ตอบ <host>/api/* และ <host>/.well-known/oauth-*

ความสำเร็จคือ { "data": … } (รายการเวอร์ชัน: { "data": [...], "next_cursor": … }) การปฏิเสธคือ { "error": "…", "reason": "…" } พร้อมรายละเอียด ให้แยกกรณีด้วย reason ส่วน error มีไว้ให้คนอ่าน

สถานะ เหตุผล ความหมาย
400 invalid_request เนื้อหา query หรือ header ไม่ตรงกับ v1
400 idempotency_key_required คำสั่งนี้ต้องมี Idempotency-Key
401 credential_invalid ไม่มี secret ไม่รู้จัก หรือถูกเพิกถอนแล้ว
403 creator_unauthorized ผู้สร้างไม่ได้ใช้งานหรือเสียสิทธิ์ในไซต์ หรือไซต์ไม่ได้ใช้งาน
403 scope_refused ข้อมูลรับรองไม่มีขอบเขตนั้น (required ระบุไว้)
403 listing_owner_restricted ผู้สร้างอ่านได้เฉพาะรายการของตัวเองในโมเดลนั้น
409 idempotency_in_progress คีย์เดียวกันยังทำงานอยู่ ลองใหม่หลัง Retry-After ถ้าไม่มี การจองจะค้างอยู่ ให้ตรวจผลแล้วใช้คีย์ใหม่
429 rate_limited รอตาม Retry-After
503 audit_unavailable บันทึกความตั้งใจก่อนดำเนินการลงประวัติการตรวจสอบไม่ได้ จึงยังไม่เรียกใช้คำสั่ง
503 temporary_failure คำขอทำไม่เสร็จ error บอกสิ่งที่รู้: การเขียนการตั้งค่าหรือการเผยแพร่อาจถูกบันทึกแล้วแต่ยังไม่ได้บันทึกผล คำสั่งที่มี key ให้ลองใหม่ด้วย key และ body เดิม

เหตุผลของ lifecycle และหน้าส่งผ่านโดยไม่เปลี่ยน เช่น not_found, model_denied, not_owner, field_denied, locale_denied, export_limited_role, forbidden, missing_permission, version_conflict, workflow_conflict, idempotency_key_reused, scheduled_target_stale, transition_kind_mismatch, illegal_transition, dependents_not_acknowledged, invalid_save_request, facts_conflict, no_changes, content_live, plugin_policy_refused, plugin_policy_unavailable, plugin_policy_budget_exhausted, plugin_policy_transform_invalid, plugin_policy_transform_conflict, media_in_use, media_usage_unreadable, media_framing_not_image, in_trash, not_in_trash, entry_in_trash, owner_language, restore_conflict, locale_exists, unknown_locale, language_changed, locked, lock_lost, draft_conflict, assignee_cannot_act, due_date_past, batch_relation_terms_invalid, batch_relation_field_invalid, batch_expected_versions_invalid, related_from_field_invalid, related_from_source_invalid, related_from_replace_invalid และ related_from_replace_unconfirmed

field_validation_failed (422) และ field_value_taken (409) มี locale และ issues: [{ field, message, key?, params? }] ด้วย field คือ path เต็ม (faqs[2].question) message เป็นภาษาอังกฤษ key และ params ระบุข้อความของ admin ที่ใช้สร้างประโยคนั้น เพื่อให้ admin แสดงเป็นภาษาของตัวเอง ผู้เรียกอื่นไม่ต้องสนใจค่าเหล่านี้

ทุกการเขียนบันทึกเจตนาก่อนทำงานและผลลัพธ์หลังทำงานในประวัติการดูแลระบบของไซต์ ข้อยกเว้นคือการ launch ปลั๊กอิน และ route ของ lease การแก้ไขและ draft ร่วม; draft ร่วมคืองานที่ยังไม่บันทึก ไม่ใช่เวอร์ชัน ผู้กระทำคือผู้สร้าง และ change.management_access ระบุข้อมูลรับรอง การดำเนินการ: management.content.save, management.content.transition, management.content.move, management.content.assign, management.content.locale.add, management.content.version.restore, management.content.trash, management.content.trash.restore, management.content.purge, management.content.relate, management.content.related_from, management.menu.save, management.menu.language_add, management.page.save, management.page.translation.start, management.media.upload, management.role.create, management.role.update, management.role.delete, management.media.folder.create, management.media.folder.update, management.media.folder.delete, management.media.move, management.media.update, management.media.delete, management.import.source.create, management.import.create, management.import.upload, management.import.advance, management.import.mapping, management.import.assignment, management.import.pause, management.import.resume, management.import.cancel, management.import.undo, management.export.create, management.export.advance, management.proposal.create, management.schema_proposal.create, management.model.apply, management.model.delete, management.settings.update และ management.credential.issue / management.credential.revoke สำหรับตัวข้อมูลรับรองเอง และ management.oauth.connect / management.oauth.revoke สำหรับแอปที่เชื่อมต่อ การเขียนของแอปที่เชื่อมต่อยังมี change.via ซึ่งเป็นชื่อแอป ประวัติจึงอ่านว่า “ผู้ใช้ผ่านแอป” เวอร์ชันและเหตุการณ์ workflow ระบุผู้สร้าง เหมือนการแก้ไขในหน้าผู้ดูแล เวอร์ชันของข้อเสนอที่ถูกนำไปใช้ระบุผู้ที่นำไปใช้ ข้อเสนอ schema ที่ถูกนำไปใช้คือ model.apply ตามปกติโดยผู้ที่บันทึก และ change.proposal ระบุข้อเสนอนั้น

  • การเปลี่ยน workflow และการเผยแพร่แบบข้ามขั้นตอน
  • ความลับ วิธีเข้าสู่ระบบ ผู้ให้บริการตัวตน การ deploy ผู้ให้บริการซอร์ส และโดเมน
  • การดูแลปลั๊กอินและคำสั่งของปลั๊กอิน การติดตั้ง การให้สิทธิ์ การปิด และงานของปลั๊กอินเองใช้หน้าแอดมินและโปรโตคอลปลั๊กอินแยกต่างหาก API การจัดการเพียงแสดงปลั๊กอิน สร้างลิงก์เปิด และเรียกใช้การดำเนินการที่ประกาศไว้
  • CRUD ของแบ็กเอนด์ที่สร้างขึ้น ซึ่งไม่ใช่ช่องทางจัดการ CMS เด็ดขาด
Terminal window
curl -s https://cms.example/api/manage/v1/entries \
-H "Authorization: Bearer $DEE_WAN_MANAGEMENT_TOKEN" \
-H "Idempotency-Key: create-hello-1" \
-H "Content-Type: application/json" \
-d '{"model_id":"<model id>","locales":{"en":{"title":"Hello"}}}'

เว็บไซต์ของคุณเชื่อมต่อแล้ว เพื่อให้ทำงานต่อเนื่อง ให้ตั้งค่า การสำรองและกู้คืน เพื่อขยาย Dee Wan เริ่มที่ การเขียนปลั๊กอิน แบ็กเอนด์อิสระ ยังเป็นทางเลือก


ยังไม่เปิดตัว (pre-MVP) และยังไม่มีเวอร์ชันเสถียร
คอมมิตซอร์สโค้ด: fe23db481c422b40e317b92fc98116b4c0c26a87 (มีการแก้ไขที่ยังไม่คอมมิต)
วันที่สร้างเอกสาร (UTC): 2026-10-18T09:22:34.630Z
คอมมิตสำหรับอัปเกรด: ระบุไม่ได้: มีการแก้ไขที่ยังไม่คอมมิต. บันทึกการเปลี่ยนแปลง