ข้ามไปยังเนื้อหา
Pre-MVP โค้ดเบส 1.0 ยังไม่ใช่ผลิตภัณฑ์ที่เผยแพร่ — ดู ขอบเขตผลิตภัณฑ์ 1.0

โปรโตคอล v1

ทุก route, header, body, รหัส outcome และกฎการ retry ของ remote plugin protocol ปลั๊กอินคือ service ที่ deploy แยกต่างหาก: มันสื่อสารกับ Dee Wan ผ่าน HTTP และไม่รันโค้ดใดภายใน Dee Wan ดูเพิ่มเติมที่ การเขียนปลั๊กอิน สำหรับปลั๊กอินแรกตั้งแต่ต้นจนจบ, ความปลอดภัยของปลั๊กอิน สำหรับขอบเขตความเชื่อถือ และ การทดสอบปลั๊กอิน สำหรับวิธีทดสอบปลั๊กอิน

เวอร์ชันของ protocol ที่รองรับ: 1 เวอร์ชันจะถูกตกลงกันครั้งเดียวตอนติดตั้ง และถูก pin ไว้กับ installation (การติดตั้ง Dee Wan หนึ่งชุด) การเปลี่ยนแปลงที่ breaking ต่อ route, ความหมาย หรือรหัส error ต้องใช้ protocol v2; ฟิลด์ใน response ที่เพิ่มเข้ามาได้รับอนุญาตเพียงเพราะ client ถูกกำหนดให้ต้องเพิกเฉยต่อ key ใน response ที่ไม่รู้จัก shape ของ request เป็นแบบปิด — key ที่ไม่รู้จักจะถูกปฏิเสธ ไม่ใช่ถูกเพิกเฉย

ทุก route ของปลั๊กอินรับ Authorization: Bearer <token> โดย token คือ dwp_ ตามด้วยอักขระ URL-safe 43 ตัวจากความสุ่ม 256 บิต core เก็บเพียง hash SHA-256 ของมันและไม่สามารถแสดงมันได้อีก ปลั๊กอินได้รับมันเพียงครั้งเดียวตอน activation

route เหล่านี้อยู่ใต้ /api/plugin/v1 และถูก mount ก่อนการตรวจ origin และ session ฝั่งเบราว์เซอร์ เพราะ server ไม่ได้ส่งทั้ง header Origin และ cookie middleware requirePlugin ของพวกมันเอง resolve installation และไซต์ ก่อน ที่จะอ่าน body ของ request ใด ๆ ตามลำดับนี้: ปฏิเสธไซต์หรือ ฐานข้อมูลที่ผู้เรียกระบุชื่อมา, resolve token ไปยัง installation ที่ active หนึ่งตัว, ใช้ rate limit, resolve ไซต์, route ไปยังฐานข้อมูลของ tenant

ผลที่ควรระบุให้ชัด:

  • ไซต์ได้มาจาก token ปลั๊กอินระบุไซต์เองไม่ได้ และการส่ง X-Site-Id มาเลยคือ invalid_request
  • cookie ของผู้ใช้ไม่ใช่การยืนยันตัวตนของปลั๊กอิน และ token ของปลั๊กอินจะไม่ถูกยอมรับบน route ด้านการดูแลระบบใดเลย
  • installation ที่เป็น pending, disabled และ revoked จะตอบ plugin_unauthorized ก่อนที่จะมี การเข้าถึง tenant ใด ๆ
Header ที่ไหน ความหมาย
Authorization: Bearer <token> ทุก request ของปลั๊กอิน token ของ installation บังคับ
Idempotency-Key ทุก mutation บังคับ อักขระ ASCII ที่พิมพ์ได้ไม่เกิน 128 ตัว bearer token คือการยืนยันตัวตน ไม่ใช่การควบคุม replay
Content-Type: application/json request ที่มี body รูปแบบ body เดียวที่รองรับ
X-Site-Id ไม่เคย ถูกปฏิเสธ ไซต์มาจาก token
Retry-After บน response 429 วินาที ให้ปฏิบัติตาม ภายในค่าสูงสุดของคุณเอง
Referrer-Policy: no-referrer บน response ของ launch core ตั้งค่านี้เพื่อไม่ให้ launch code รั่วผ่าน referrer
ขีดจำกัด ค่า
Request body 16384 ไบต์
Manifest body 32768 ไบต์
Idempotency key 128 อักขระ
หน้าของเนื้อหา 50 รายการ, ค่าเริ่มต้น 20
Request ต่อ installation 120 ต่อหน้าต่าง 60s, ใช้ installation เป็น key
อายุของ launch code 60s, ใช้ครั้งเดียว, เก็บแบบ hash
ช่วงซ้อนทับตอน rotate token ไม่เกิน 10 นาที
Capability สิ่งที่อนุญาต
content:read route สำหรับอ่านเนื้อหา ภายในโมเดลที่ได้รับ grant
workflow:read บล็อก workflow ในการอ่าน: state, revision และ transition ที่ข้ามขอบเขตการเผยแพร่
workflow:publish คำสั่ง kind: publish
workflow:unpublish คำสั่ง kind: unpublish

grant (สิทธิ์ที่มอบให้) คือส่วนตัดกันของ capability เหล่านี้กับ list ของ id ของโมเดลเนื้อหาต่อไซต์ list โมเดลที่ว่างหมายถึงไม่มีสิทธิ์เข้าถึงเนื้อหา — ไม่ใช่ทุกโมเดล capability ที่อยู่นอกคำขอใน manifest ที่เก็บไว้ ไม่สามารถ grant ได้ ดังนั้นการอัปเดตปลั๊กอินไม่มีทางขยาย grant ที่มีอยู่ได้

pending, active, disabled, revoked มีเพียง active ที่เรียก plugin API ได้ revoked เป็นสถานะสุดท้าย: token ของมันจะไม่กลับมาใช้ได้อีก และการถอนการติดตั้งจะ revoke ก่อนสิ่งอื่นใด

installation นี้คืออะไร id ของ installation, ไซต์ที่มันผูกอยู่, เวอร์ชัน protocol ที่ยอมรับ และ grant ให้อ่านตอนเริ่มต้นเพื่อรู้ว่า installation นี้ขออะไรได้บ้าง; มันไม่ระบุผู้ใช้, ไม่มี hash ของ token, ไม่มีฐานข้อมูล และไม่มีโครงสร้างพื้นฐานใด

  • ผู้เรียก: ปลั๊กอิน พร้อม token ของ installation
  • ต้องการ: ไม่มี
  • ไม่มี query ไม่มี body
  • คำตอบ: { "data": <context> }$defs/context

Outcome:

  • plugin_unauthorized
  • rate_limited
  • temporary_failure

installation นี้ทำอะไรได้บ้าง

Terminal window
curl "https://cms.example/api/plugin/v1/context" \
-H "Authorization: Bearer dwp_ExampleTokenNotRealExampleTokenNotRealExamp"
{
"data": {
"installation_id": "plg_0000000000000000000000000000d1ee",
"site": {
"id": "cdocssite00000000000000001",
"name": "docs-site",
"default_lang": "en"
},
"protocol_version": 1,
"capabilities": [
"content:read",
"workflow:read",
"workflow:publish",
"workflow:unpublish"
],
"model_ids": [
"cdocsmodel0000000000000001"
]
}
}

token ที่ไม่รู้จัก ถูก disable หรือถูก revoke

Terminal window
curl "https://cms.example/api/plugin/v1/context" \
-H "Authorization: Bearer dwp_UnknownTokenNotRealUnknownTokenNotRealUnkno"
{
"outcome": {
"code": "plugin_unauthorized",
"message": "The installation token is missing, unknown, disabled or revoked.",
"retry": false
}
}

คำตอบเดียวสำหรับทั้งสี่กรณี ไม่มีสิ่งใดบอกผู้เรียกว่าเป็นกรณีใด

ปลั๊กอินระบุไซต์ไม่ได้

Terminal window
curl "https://cms.example/api/plugin/v1/context" \
-H "Authorization: Bearer dwp_ExampleTokenNotRealExampleTokenNotRealExamp" \
-H "X-Site-Id: cotherdocsite00000000001"
{
"outcome": {
"code": "invalid_request",
"message": "A plugin request cannot name a site.",
"retry": false
}
}

ไซต์มาจาก token การส่ง header นี้มาเลยจะถูกปฏิเสธ แม้จะเป็นค่าที่ถูกต้องก็ตาม

route ของปลั๊กอินที่ไม่รู้จัก

Terminal window
curl "https://cms.example/api/plugin/v1/schedules" \
-H "Authorization: Bearer dwp_ExampleTokenNotRealExampleTokenNotRealExamp"
{
"outcome": {
"code": "content_not_found",
"message": "No such plugin route.",
"retry": false
}
}

Protocol v1 มี route ข้างต้นและไม่มีอะไรอื่น 404 ตรงนี้ไม่ใช่ความล้มเหลวชั่วคราว

แสดงรายการเนื้อหาในโมเดลที่ได้รับ grant หนึ่งโมเดล instance หลักหนึ่งหน้าในโมเดลเดียว model_id เป็นค่าบังคับ: ไม่มีการอ่านแบบ “ทุกโมเดล” ชุด filter เป็นแบบปิด — query parameter อื่นใดคือ invalid_request ไม่ใช่ถูกเพิกเฉย — และหน้านี้เป็นสรุปแบบแคบ ไม่ใช่ snapshot ของเนื้อหา มีเพียง primary ของ translation group ที่ปรากฏ; group คือหน่วยที่ transition เคลื่อนย้าย

  • ผู้เรียก: ปลั๊กอิน, content:read
  • ต้องการ: content:read
  • model_id (บังคับ) — id ของโมเดลจาก grant
  • publishedtrue หรือ false
  • afterinstance_id ตัวสุดท้ายของหน้าก่อนหน้า
  • limit — 1..50, ค่าเริ่มต้น 20
  • คำตอบ: { "data": [<summary>], "next": <instance_id|null> }$defs/content_page

Outcome:

  • capability_refused — ไม่มี content:read หรือโมเดลอยู่นอก grant
  • invalid_request — ไม่มี model_id, filter ที่ไม่รู้จัก, limit อยู่นอกช่วง
  • plugin_unauthorized
  • rate_limited
  • temporary_failure

หนึ่งหน้าของหนึ่งโมเดล

Terminal window
curl "https://cms.example/api/plugin/v1/content?model_id=cdocsmodel0000000000000001&published=false&limit=1" \
-H "Authorization: Bearer dwp_ExampleTokenNotRealExampleTokenNotRealExamp"
{
"data": [
{
"instance_id": "cdocsinstance00000000000001",
"primary_instance_id": "cdocsinstance00000000000001",
"translation_group_id": "cdocsgroup0000000000000001",
"model_id": "cdocsmodel0000000000000001",
"model_slug": "article",
"lang": "en",
"label": "First draft",
"slug": "hello",
"current_version_id": "cdocsversion00000000000001",
"published_version_id": null
}
],
"next": null
}

filter ที่อยู่นอก model_id, published, after, limit คือ invalid_request ไม่ใช่ถูกเพิกเฉย

ชุด filter เป็นแบบปิด

Terminal window
curl "https://cms.example/api/plugin/v1/content?model_id=cdocsmodel0000000000000001&sort=title" \
-H "Authorization: Bearer dwp_ExampleTokenNotRealExampleTokenNotRealExamp"
{
"outcome": {
"code": "invalid_request",
"message": "model_id is required; filters are model_id, published, after, limit.",
"retry": false
}
}

อ่านรายการเดียว และสิ่งที่มันทำต่อได้ id, label, slug, id ของ version ปัจจุบันและ version ที่เผยแพร่ และ — เมื่อมี workflow:read — state ของ workflow, revision ของมัน และ transition ที่ข้ามขอบเขตการเผยแพร่ transition ถูกจำกัดให้แคบลงตาม capability ที่ installation นี้ถืออยู่ ส่ง delegation_id มา แล้วมันจะถูกจำกัดให้แคบลงอีกตามสิ่งที่บุคคลนั้นทำได้ ในตอนนี้ โดยอ่านซ้ำจาก control; delegation ที่บุคคลของมันสูญเสียอำนาจไปแล้วจะตอบ requester_unauthorized แทนที่จะเป็น list ที่สั้นลงอย่างเงียบ ๆ รายการที่อยู่นอกไซต์หรือนอก grant ของโมเดลคือ content_not_found ซึ่งเป็นคำตอบเดียวกับ id ข้ามไซต์ด้วย

  • ผู้เรียก: ปลั๊กอิน, content:read
  • ต้องการ: content:read และ workflow:read สำหรับบล็อก workflow
  • delegation_id — launch delegation ที่ถูกใช้แล้ว ไม่รับ query parameter อื่น
  • คำตอบ: { "data": <summary> }$defs/content_summary

Outcome:

  • capability_refused
  • content_not_found
  • requester_unauthorized — delegation ที่ระบุไม่เป็นที่รู้จัก หรือบุคคลของมันไม่มีคุณสมบัติอีกต่อไป
  • invalid_request
  • plugin_unauthorized
  • rate_limited
  • temporary_failure

หนึ่งรายการ พร้อม transition ที่ผู้ร้องขอทำได้ในตอนนี้

Terminal window
curl "https://cms.example/api/plugin/v1/content/cdocsinstance00000000000001?delegation_id=pld_0000000000000000000000000000d1ee" \
-H "Authorization: Bearer dwp_ExampleTokenNotRealExampleTokenNotRealExamp"
{
"data": {
"instance_id": "cdocsinstance00000000000001",
"primary_instance_id": "cdocsinstance00000000000001",
"translation_group_id": "cdocsgroup0000000000000001",
"model_id": "cdocsmodel0000000000000001",
"model_slug": "article",
"lang": "en",
"label": "First draft",
"slug": "hello",
"current_version_id": "cdocsversion00000000000001",
"published_version_id": null,
"workflow": {
"state": "draft",
"revision": 0,
"transitions": [
{
"to": "published",
"kind": "publish"
}
]
}
}
}

ให้ pin สิ่งที่คำตอบนี้ให้มา: current_version_id สำหรับการเผยแพร่, published_version_id สำหรับการยกเลิกการเผยแพร่ และ workflow.revision สำหรับทั้งสองแบบ

เผยแพร่หรือยกเลิกการเผยแพร่แบบมีเงื่อนไข mutation เดียวที่ protocol v1 มี มันมีเงื่อนไขสี่ทางพร้อมกัน ซึ่งทั้งหมดถูกตรวจภายใน lifecycle mutation แบบ atomic เดียวกัน: version ที่ถูก pin ต้องยังเป็นตัวที่ transition จะเคลื่อนย้าย, revision ของ workflow ต้องยังเป็นตัวที่ใช้ตอนสร้างรายการตั้งเวลา, targetState ต้องเป็น transition ที่ใช้ได้จาก state ปัจจุบัน และความหมายด้านการเผยแพร่ของ transition นั้นต้องตรงกับ kind อำนาจของบุคคลที่ delegate จะถูกอ่านซ้ำจาก control ก่อนการเขียน draft ที่ใหม่กว่าจะไม่ถูกเผยแพร่อย่างเงียบ ๆ เลย; workflow ที่เปลี่ยนไปจะไม่ถูกเดินผ่านอย่างเงียบ ๆ เลย การเผยแพร่มีผลกับทั้ง translation group ซึ่งเป็นเหตุผลที่ผลลัพธ์ระบุ instance หลัก

  • ผู้เรียก: ปลั๊กอิน, workflow:publish หรือ workflow:unpublish
  • ต้องการ: workflow:publish สำหรับ kind: publish, workflow:unpublish สำหรับ kind: unpublish
  • Idempotency-Key (บังคับ) — อักขระ ASCII ที่พิมพ์ได้ไม่เกิน 128 ตัว ให้สร้างจากรายการตั้งเวลา ไม่ใช่จากนาฬิกา: replay ที่ตรงกันทุกประการจะคืนผลลัพธ์แรก และ key เดิมกับอินพุตที่ต่างกันคือ idempotency_key_reused
  • Body — $defs/publish_command หรือ $defs/unpublish_command shape เป็นแบบ ปิด: key ที่ไม่รู้จัก หรือ pin ของอีก kind หนึ่ง คือ invalid_request
  • คำตอบ: { "outcome": {...}, "data": <transition_result> }$defs/transition_result

Outcome:

  • transition_applied / idempotent_replay
  • invalid_request
  • capability_refused
  • requester_unauthorized
  • content_not_found
  • scheduled_target_stale
  • workflow_conflict
  • dependent_content
  • idempotency_key_reused
  • transition_unavailable / transition_refused
  • rate_limited
  • temporary_failure

เผยแพร่ draft ที่ถูก pin

Terminal window
curl -X POST "https://cms.example/api/plugin/v1/content/cdocsinstance00000000000001/transitions/published" \
-H "Authorization: Bearer dwp_ExampleTokenNotRealExampleTokenNotRealExamp" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: sched:plg_0000000000000000000000000000d1ee:sch_0001:r1" \
--data '{
"kind": "publish",
"expected_current_version_id": "cdocsversion00000000000001",
"expected_workflow_revision": 0,
"delegation_id": "pld_0000000000000000000000000000d1ee"
}'
{
"outcome": {
"code": "transition_applied",
"message": "The transition was applied.",
"retry": false
},
"data": {
"instance_id": "cdocsinstance00000000000001",
"primary_instance_id": "cdocsinstance00000000000001",
"from": "draft",
"to": "published",
"published": true,
"published_changed": true,
"published_version_id": "cdocsversion00000000000001",
"revision": 1
}
}

คำสั่งเดิมอีกครั้ง

Terminal window
curl -X POST "https://cms.example/api/plugin/v1/content/cdocsinstance00000000000001/transitions/published" \
-H "Authorization: Bearer dwp_ExampleTokenNotRealExampleTokenNotRealExamp" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: sched:plg_0000000000000000000000000000d1ee:sch_0001:r1" \
--data '{
"kind": "publish",
"expected_current_version_id": "cdocsversion00000000000001",
"expected_workflow_revision": 0,
"delegation_id": "pld_0000000000000000000000000000d1ee"
}'
{
"outcome": {
"code": "idempotent_replay",
"message": "This command was already applied. The first result is returned.",
"retry": false
},
"data": {
"instance_id": "cdocsinstance00000000000001",
"primary_instance_id": "cdocsinstance00000000000001",
"from": "draft",
"to": "published",
"published": true,
"published_changed": true,
"published_version_id": "cdocsversion00000000000001",
"revision": 1
}
}

การ retry หลัง network ล้มเหลวปลอดภัย: ผลลัพธ์แรกจะกลับมา และไม่มีอะไรรันซ้ำสองครั้ง

key เดิม คำสั่งต่างกัน

Terminal window
curl -X POST "https://cms.example/api/plugin/v1/content/cdocsinstance00000000000001/transitions/published" \
-H "Authorization: Bearer dwp_ExampleTokenNotRealExampleTokenNotRealExamp" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: sched:plg_0000000000000000000000000000d1ee:sch_0001:r1" \
--data '{
"kind": "publish",
"expected_current_version_id": "cdocsversion00000000000001",
"expected_workflow_revision": 7,
"delegation_id": "pld_0000000000000000000000000000d1ee"
}'
{
"outcome": {
"code": "idempotency_key_reused",
"message": "This idempotency key was already used for a different command.",
"retry": false
}
}

ให้สร้าง key จากรายการตั้งเวลาและ revision ของมัน การตั้งเวลาใหม่คือ revision ใหม่ จึงเป็น key ใหม่

ยกเลิกการเผยแพร่ version ที่ live และถูก pin

Terminal window
curl -X POST "https://cms.example/api/plugin/v1/content/cdocsinstance00000000000001/transitions/draft" \
-H "Authorization: Bearer dwp_ExampleTokenNotRealExampleTokenNotRealExamp" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: sched:plg_0000000000000000000000000000d1ee:sch_0002:r1" \
--data '{
"kind": "unpublish",
"expected_published_version_id": "cdocsversion00000000000001",
"expected_workflow_revision": 1,
"delegation_id": "pld_0000000000000000000000000000d1ee"
}'
{
"outcome": {
"code": "transition_applied",
"message": "The transition was applied.",
"retry": false
},
"data": {
"instance_id": "cdocsinstance00000000000001",
"primary_instance_id": "cdocsinstance00000000000001",
"from": "published",
"to": "draft",
"published": false,
"published_changed": true,
"published_version_id": null,
"revision": 2
}
}

draft ที่ใหม่กว่าจะไม่ถูกเผยแพร่อย่างเงียบ ๆ เลย

Terminal window
curl -X POST "https://cms.example/api/plugin/v1/content/cdocsinstance00000000000001/transitions/published" \
-H "Authorization: Bearer dwp_ExampleTokenNotRealExampleTokenNotRealExamp" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: sched:plg_0000000000000000000000000000d1ee:sch_0003:r1" \
--data '{
"kind": "publish",
"expected_current_version_id": "cdocsversion00000000000001",
"expected_workflow_revision": 2,
"delegation_id": "pld_0000000000000000000000000000d1ee"
}'
{
"outcome": {
"code": "scheduled_target_stale",
"message": "The pinned version is no longer the one this transition would move.",
"retry": false
}
}

มีคนบันทึก draft ที่ใหม่กว่าหลังจากสร้างรายการตั้งเวลาแล้ว เป็นสถานะสุดท้าย: บุคคลเป็นผู้ตัดสินใจว่าจะทำอย่างไร

workflow ที่เปลี่ยนไปจะไม่ถูกเดินผ่านอย่างเงียบ ๆ เลย

Terminal window
curl -X POST "https://cms.example/api/plugin/v1/content/cdocsinstance00000000000001/transitions/published" \
-H "Authorization: Bearer dwp_ExampleTokenNotRealExampleTokenNotRealExamp" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: sched:plg_0000000000000000000000000000d1ee:sch_0004:r1" \
--data '{
"kind": "publish",
"expected_current_version_id": "cdocsversion00000000000002",
"expected_workflow_revision": 0,
"delegation_id": "pld_0000000000000000000000000000d1ee"
}'
{
"outcome": {
"code": "workflow_conflict",
"message": "The content moved through its workflow after this was requested.",
"retry": false
}
}

shape ของ request เป็นแบบปิด

Terminal window
curl -X POST "https://cms.example/api/plugin/v1/content/cdocsinstance00000000000001/transitions/published" \
-H "Authorization: Bearer dwp_ExampleTokenNotRealExampleTokenNotRealExamp" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: sched:plg_0000000000000000000000000000d1ee:sch_0005:r1" \
--data '{
"kind": "publish",
"expected_current_version_id": "cdocsversion00000000000002",
"expected_workflow_revision": 2,
"delegation_id": "pld_0000000000000000000000000000d1ee",
"override_dependents": true
}'
{
"outcome": {
"code": "invalid_request",
"message": "The request is not valid for protocol v1.",
"retry": false
}
}

ไม่มีการ override ใน protocol v1 key ที่ไม่รู้จักจะถูกปฏิเสธ ไม่ใช่ถูกทิ้ง

mutation ที่ไม่มี idempotency key

Terminal window
curl -X POST "https://cms.example/api/plugin/v1/content/cdocsinstance00000000000001/transitions/published" \
-H "Authorization: Bearer dwp_ExampleTokenNotRealExampleTokenNotRealExamp" \
-H "Content-Type: application/json" \
--data '{
"kind": "publish",
"expected_current_version_id": "cdocsversion00000000000002",
"expected_workflow_revision": 2,
"delegation_id": "pld_0000000000000000000000000000d1ee"
}'
{
"outcome": {
"code": "invalid_request",
"message": "Idempotency-Key is required.",
"retry": false
}
}

แลก launch code เป็น delegation เบราว์เซอร์มาถึง management_url พร้อม dee_wan_launch BACKEND ของปลั๊กอินส่งรหัสนั้นมาที่นี่ พร้อม bearer token ของตัวเอง ภายใน 60 วินาที รหัสถูกใช้ได้ครั้งเดียว สิ่งที่ได้กลับมาคือการผูก — installation, ไซต์, action, content instance (ไม่บังคับ) — และตัวตนที่ปลอดภัยต่อการแสดงผลสำหรับ session ของปลั๊กอินเอง มันไม่ได้มอบ capability ใดของ Dee Wan ที่ token ของ installation ยังไม่มีอยู่แล้ว และรหัสที่ออกให้ installation อื่นจะถูกปฏิเสธ

  • ผู้เรียก: ปลั๊กอิน พร้อม token ของ installation
  • ต้องการ: ไม่มี
  • Body — $defs/launch_exchange_request
  • คำตอบ: { "data": <delegation> }$defs/launch_delegation

Outcome:

  • invalid_request — ไม่รู้จัก, ถูกใช้แล้ว, หมดอายุ หรือออกให้ installation อื่น
  • plugin_unauthorized
  • rate_limited
  • temporary_failure

เปลี่ยน launch code เป็น delegation

Terminal window
curl -X POST "https://cms.example/api/plugin/v1/launch/exchange" \
-H "Authorization: Bearer dwp_ExampleTokenNotRealExampleTokenNotRealExamp" \
-H "Content-Type: application/json" \
--data '{
"code": "dwl_ExampleLaunchCodeExampleLaunchCodeExampleLa"
}'
{
"data": {
"delegation_id": "pld_0000000000000000000000000000d1ee",
"installation_id": "plg_0000000000000000000000000000d1ee",
"site_id": "cdocssite00000000000000001",
"action": "schedule",
"instance_id": "cdocsinstance00000000000001",
"user": {
"id": "cdocsuser00000000000000001",
"email": "admin@example.com"
}
}
}

เก็บ delegation_id ไว้ ทุกคำสั่งหลังจากนี้จะแนบมันไป และ core จะอ่านอำนาจของบุคคลนั้นซ้ำ

launch code ใช้ได้ครั้งเดียว

Terminal window
curl -X POST "https://cms.example/api/plugin/v1/launch/exchange" \
-H "Authorization: Bearer dwp_ExampleTokenNotRealExampleTokenNotRealExamp" \
-H "Content-Type: application/json" \
--data '{
"code": "dwl_ExampleLaunchCodeExampleLaunchCodeExampleLa"
}'
{
"outcome": {
"code": "invalid_request",
"message": "The launch code is unknown, used or expired.",
"retry": false
}
}

ทุก route ของปลั๊กอินตอบด้วยรหัสสำหรับเครื่องและข้อความสำหรับมนุษย์:

{
"outcome": {
"code": "transition_applied",
"message": "…",
"retry": false
},
"data": {}
}

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

HTTP รหัส Retry ความหมาย
200 transition_applied ไม่ transition ถูกนำไปใช้แล้ว
200 idempotent_replay ไม่ คำสั่งนี้ถูกนำไปใช้แล้ว ผลลัพธ์แรกถูกส่งกลับ
400 invalid_request ไม่ request ไม่ถูกต้องสำหรับ protocol v1
401 plugin_unauthorized ไม่ token ของ installation หายไป, ไม่รู้จัก, ถูก disable หรือถูก revoke
403 capability_refused ไม่ installation นี้ไม่ได้รับ grant capability หรือโมเดลนั้น
403 requester_unauthorized ไม่ บุคคลที่ร้องขอสิ่งนี้ไม่มีอำนาจที่ต้องใช้อีกต่อไป
404 content_not_found ไม่ ไม่มีเนื้อหานี้ในขอบเขตของ installation นี้
409 scheduled_target_stale ไม่ version ที่ถูก pin ไม่ใช่ตัวที่ transition นี้จะเคลื่อนย้ายอีกต่อไป
409 workflow_conflict ไม่ เนื้อหาเคลื่อนผ่าน workflow ของมันไปแล้วหลังจากที่มีการร้องขอนี้
409 dependent_content ไม่ มีเนื้อหาอื่นที่พึ่งพารายการนี้ บุคคลต้องตรวจทานการยกเลิกการเผยแพร่ใน Dee Wan
409 idempotency_key_reused ไม่ idempotency key นี้ถูกใช้ไปแล้วกับคำสั่งอื่น
422 transition_unavailable ไม่ transition นั้นใช้ไม่ได้จาก state ปัจจุบันของเนื้อหา
422 transition_refused ไม่ lifecycle ปฏิเสธ transition นี้ ไม่มีอะไรถูกเปลี่ยน
429 rate_limited ใช่ request มากเกินไปสำหรับ installation นี้
503 temporary_failure ใช่ Dee Wan ดำเนินการ request ไม่สำเร็จ ไม่มีสิ่งใดที่ทราบว่าถูกเปลี่ยน
  • rate_limited — retry และปฏิบัติตาม Retry-After ให้ปฏิเสธรายการตั้งเวลาแทนการรอ หากเวลารอ เกินค่าสูงสุดของคุณเอง
  • temporary_failure และความล้มเหลวของ transport (timeout, connection reset, ไม่มี response) — retry ด้วย exponential backoff ที่มีขอบเขตพร้อม jitter จนถึงเพดานจำนวนครั้งและอายุที่ระบุไว้ในเอกสาร
  • ทุกอย่างที่เหลือเป็นสถานะสุดท้าย (TERMINAL) อย่า retry และอย่าเปลี่ยนมันให้เป็นความสำเร็จ
  • คำตอบที่ HTTP status และ outcome.code ไม่ตรงกัน หรือ body ไม่ใช่ envelope ถือว่าไม่น่าเชื่อถือ: ให้ถือเป็นการปฏิเสธขั้นสุดท้ายแทนการเดา
  • การ retry หลัง outcome ที่ไม่ทราบผลปลอดภัยเพราะ Idempotency-Key: replay ที่ตรงกันทุกประการจะคืน ผลลัพธ์แรกและไม่นำสิ่งใดไปใช้ซ้ำสองครั้ง มันไม่ใช่สิ่งทดแทน lease ของคุณเอง — worker สองตัวของคุณ ยังคงต้องไม่ดำเนินการรายการตั้งเวลาเดียวกันพร้อมกัน
  • ไม่มีสิ่งใดในนี้ที่เป็นการ retry ไม่รู้จบ และไม่มีความล้มเหลวที่ไม่ทราบผลใดถูกรายงานว่านำไปใช้แล้ว

นี่คือ route ฝั่งเบราว์เซอร์ที่อยู่หลัง session ของผู้ใช้, header ของไซต์ และสิทธิ์ site:plugins — surface ที่ติดตั้ง, grant, rotate และลบ พวกมันไม่ใช่ส่วนที่ปลั๊กอินเรียก; มีไว้ที่นี่ เพื่อให้ผู้เขียนปลั๊กอินรู้ว่าผู้ดูแลทำอะไร route ที่ทำ mutation แต่ละตัวจะเขียน audit entry และจะปฏิเสธ (503 audit_unavailable) แทนที่จะ mutate โดยไม่มี audit

แสดงรายการ installation ทุก installation ที่ยังไม่ถูก revoke บนไซต์ แบบปกปิดข้อมูล: ไม่มี token ไม่มี hash

  • ผู้เรียก: ผู้ใช้ที่มี site:plugins
  • ต้องการ: site:plugins
  • ไม่มี body
  • คำตอบ: { "data": [<installation>] }$defs/installation

Outcome:

  • 200
  • 403 missing_permission

โมเดลที่ grant ได้ id, ชื่อ และ slug ของทุกโมเดลเนื้อหาบนไซต์ สำหรับหน้าจอ grant GRANT เก็บ id ดังนั้นการเปลี่ยนชื่อยังคงรักษามันไว้ และโมเดลที่ถูกลบจะทำให้ส่วนนั้นของมันไม่มีผล

  • ผู้เรียก: ผู้ใช้ที่มี site:plugins
  • ต้องการ: site:plugins
  • ไม่มี body
  • คำตอบ: { "data": [{ "id", "name", "slug" }] }

Outcome:

  • 200
  • 403 missing_permission

action ของเนื้อหาที่จะ render คู่ { id, label } ที่มีขอบเขตซึ่ง installation ที่ active ประกาศไว้ เพื่อให้หน้าจอเนื้อหา render ปุ่มที่ core เป็นเจ้าของได้ ไม่มี markup, ไม่มี script, ไม่มี CSS, ไม่มี URL ของไอคอนใดมาจากปลั๊กอินเลย

  • ผู้เรียก: สมาชิกใดก็ได้ของไซต์
  • ต้องการ: ไม่มีอะไรนอกจากสิทธิ์เข้าถึงไซต์
  • ไม่มี body
  • คำตอบ: { "data": [{ "installation_id", "plugin_name", "actions": [{ "id", "label" }] }] }

Outcome:

  • 200

ดึงและตรวจสอบ manifest ดึง URL ของ manifest ตรวจสอบมัน และคืนสิ่งที่มันขอ ไม่มีอะไรถูกเก็บและไม่มี installation เกิดขึ้นหลังจากนั้น; นี่คือขั้นตอนตรวจทานที่ทำให้การอนุมัติเป็นไปอย่างมีข้อมูล

  • ผู้เรียก: ผู้ใช้ที่มี site:plugins
  • ต้องการ: site:plugins
  • Body — { "manifest_url": "https://…" }
  • คำตอบ: { "data": { "manifest": <manifest>, "protocol_version": 1 } }

Outcome:

  • 200
  • 400 พร้อม reasonmanifest_invalid, protocol_unsupported, manifest_origin_mismatch, url_not_https, url_credentials, url_fragment, url_private_destination, remote_redirect_refused, remote_body_too_large, remote_unreachable, manifest_unavailable

ติดตั้ง ดึงและตรวจสอบ manifest อีกครั้ง บันทึก installation สถานะ pending พร้อม capability และ id ของโมเดลที่อนุมัติ ออก token และส่ง token ดิบพร้อม activation code แบบใช้ครั้งเดียวไปยัง activation_url ของ manifest installation จะกลายเป็น active ก็ต่อเมื่อได้ 2xx จากปลั๊กอินเท่านั้น; core เก็บ hash และไม่เคยเก็บ token activation ที่ล้มเหลวจะไม่ทิ้งอะไรไว้ และการติดตั้งปลั๊กอินเดียวกันเป็นครั้งที่สองบนไซต์เดียวกันคือ conflict ไม่ใช่ installation ที่สอง

  • ผู้เรียก: ผู้ใช้ที่มี site:plugins
  • ต้องการ: site:plugins
  • Body — { "manifest_url", "activation_code", "capabilities": […], "model_ids": […] } capability ต้องเป็น subset ของคำขอใน manifest; model_ids ที่ว่างหมายถึงไม่มีสิทธิ์เข้าถึงเนื้อหา ไม่ใช่ทุกโมเดล
  • คำตอบ: { "data": <installation> }, 201

Outcome:

  • 201
  • 400 invalid_activation_code / invalid_capabilities / invalid_model_ids / reason ของ manifest
  • 409 installation_exists / backend_url_unset / activation_superseded
  • 502 พร้อม reason จากฝั่ง remote — ปลั๊กอินปฏิเสธหรือไม่สามารถติดต่อได้
  • 503 audit_unavailable

เปลี่ยน grant แทนที่ capability และ id ของโมเดล มันทำได้เพียงจำกัดให้แคบลงภายในสิ่งที่ manifest ที่ เก็บไว้ ขอ — capability ที่อยู่นอกนั้นจะถูกปฏิเสธ ดังนั้น manifest ที่เปลี่ยนไปไม่สามารถขยาย grant ได้ การลดสิทธิ์มีผลใน request ถัดไปของปลั๊กอิน

  • ผู้เรียก: ผู้ใช้ที่มี site:plugins
  • ต้องการ: site:plugins
  • Body — { "capabilities": […], "model_ids": […] }
  • คำตอบ: { "data": <installation> }

Outcome:

  • 200
  • 400 invalid_capabilities / invalid_model_ids
  • 404
  • 409 installation_state_changed

ยอมรับ manifest เวอร์ชันใหม่ ดึง URL ของ manifest ที่เก็บไว้อีกครั้งและยอมรับ release ที่ผู้ดูแลเพิ่งตรวจทาน — expected_version ต้องตรงกับสิ่งที่ถูก serve มิฉะนั้นการตรวจทานถือว่าเก่าแล้ว id ของปลั๊กอิน, origin และเวอร์ชัน protocol ที่ยอมรับต้องไม่เปลี่ยน capability จะถูก ตัดกัน (INTERSECTED) กับคำขอใหม่; การอัปเกรดไม่เคยขยาย grant

  • ผู้เรียก: ผู้ใช้ที่มี site:plugins
  • ต้องการ: site:plugins
  • Body — { "expected_version": "0.2.0" }
  • คำตอบ: { "data": <installation> }

Outcome:

  • 200
  • 400 reason ของ manifest
  • 409 manifest_identity_changed / protocol_changed / manifest_changed_since_review

Rotate token ต้องใช้รหัสใช้ครั้งเดียวใหม่จากปลั๊กอิน เพราะการ rotate คือการส่งมอบ ไม่ใช่การรีเซ็ต core ออก token ส่งไปยัง activation origin ที่ถูก pin และหลังจากนั้นจึงสลับ hash หากปลั๊กอินปฏิเสธ token เก่ายังใช้งานได้ hash ก่อนหน้ายังใช้ได้ไม่เกิน 10 นาที และ request แรกที่แนบ token ใหม่ จะยุติช่วงซ้อนทับนั้นทันที

  • ผู้เรียก: ผู้ใช้ที่มี site:plugins
  • ต้องการ: site:plugins
  • Body — { "activation_code": "…" }
  • คำตอบ: { "data": { "rotated": true } }

Outcome:

  • 200
  • 400 invalid_activation_code
  • 409 installation_not_active / backend_url_unset / installation_state_changed
  • 502 reason จากฝั่ง remote — และ token เก่า ยังใช้งานได้

Disable เก็บการตั้งค่าและ audit trail ไว้ และหยุด plugin API ทันที: ทุก request ตอบ plugin_unauthorized ก่อนการเข้าถึง tenant ใด ๆ

  • ผู้เรียก: ผู้ใช้ที่มี site:plugins
  • ต้องการ: site:plugins
  • ไม่มี body
  • คำตอบ: { "data": { "id", "state": "disabled" } }

Outcome:

  • 200
  • 404
  • 409 installation_state_changed

Enable คืน installation ที่ถูก disable กลับเป็น active ด้วย token เดิมและ grant เดิม

  • ผู้เรียก: ผู้ใช้ที่มี site:plugins
  • ต้องการ: site:plugins
  • ไม่มี body
  • คำตอบ: { "data": { "id", "state": "active" } }

Outcome:

  • 200
  • 404
  • 409 installation_state_changed

ถอนการติดตั้ง revoke ก่อน: state กลายเป็น revoked, hash ทั้งสองถูกทิ้ง และ token จะไม่กลับมาใช้ได้อีก การระบุที่มาใน audit ยังคงอยู่ เพราะ workflow event เก็บ snapshot ของ installation แทนที่จะเป็น foreign key Dee Wan ไม่ อ้างว่าการนี้ลบสิ่งใดที่ปลั๊กอินถืออยู่ — การลบฝั่ง remote เป็น action ของปลั๊กอินเอง

  • ผู้เรียก: ผู้ใช้ที่มี site:plugins
  • ต้องการ: site:plugins
  • ไม่มี body
  • คำตอบ: { "data": { "id", "state": "revoked" } }

Outcome:

  • 200
  • 404
  • 409 installation_state_changed

เปิดปลั๊กอินแบบ delegate ออกรหัสใช้ครั้งเดียว มีอายุ 60 วินาที เก็บแบบ hash ผูกกับ installation, ไซต์, ผู้ใช้, action และ instance (ไม่บังคับ) และคืน management_url พร้อมรหัสนั้น response มี Referrer-Policy: no-referrer และ Cache-Control: no-store action ต้องเป็น manage โดยไม่มี instance หรือเป็น id ของ content_actions จาก manifest ที่เก็บไว้พร้อม instance หนึ่งตัว cookie ของ session Dee Wan ไม่เคยออกจาก Dee Wan

  • ผู้เรียก: สมาชิกของไซต์สำหรับ content action; site:plugins สำหรับ manage
  • ต้องการ: สิทธิ์อ่านรายการที่ระบุ สำหรับ content action
  • Body — { "action": "manage" } หรือ { "action": "<action id>", "instance_id": "…" }
  • คำตอบ: { "data": { "url": "https://…?dee_wan_launch=…" } }$defs/launch

Outcome:

  • 200
  • 400 invalid_launch_action
  • 403 missing_permission (สำหรับ manage) / model_denied
  • 404 not_found
  • 409 installation_not_active
  • schemas/manifest-v1.schema.json — manifest ที่สร้างจากออบเจกต์ ที่ core ใช้ตรวจสอบทุก manifest ที่ดึงมา
  • schemas/protocol-v1.schema.json — request, response, outcome, ขีดจำกัด และรูปแบบของ secret ที่สร้างจากนิยามชุดเดียวกัน

ทั้งสองถูกเขียนโดย npm run docs:plugins และถูก commit ไว้ มีการทดสอบที่เทียบสำเนาที่ commit ไว้กับ สิ่งที่โค้ด render ออกมา ดังนั้นพวกมันจึงไม่สามารถอธิบาย protocol ที่ core นี้ไม่ได้ใช้