คู่มือสำหรับนักพัฒนา · REST และ MCP

ซิงค์ฐานความรู้ของคุณ

เขียนเนื้อหาในระบบเดิมของคุณต่อไป แล้วส่งการเปลี่ยนแปลงมายัง Sonny คู่มือนี้จะสร้างหมวดหมู่ Billing ซิงค์บทความฉบับร่าง แล้วเผยแพร่ให้กลุ่มผู้ชมที่เป็นลูกค้าซึ่งยืนยันตัวตนแล้ว

1. เลือกช่องทางและสร้างคีย์

  1. เปิดช่องทาง → ช่องทางของคุณ แล้วคัดลอก ID ที่อยู่หลัง /app/sources/ ใน URL ของแดชบอร์ด API เรียก ID ช่องทางนี้ว่า sourceId ซึ่งต่างจาก siteId ของวิดเจ็ตและ slug สาธารณะของศูนย์ช่วยเหลือ
  2. ในการตั้งค่า → นักพัฒนา → คีย์ API → สร้างคีย์ ให้สิทธิ์ kb:read และ kb:write จำกัดสิทธิ์เข้าถึงช่องทางให้เฉพาะช่องทางที่คุณจะซิงค์ แล้วบันทึกคีย์ซึ่งแสดงเพียงครั้งเดียวไว้ในตัวจัดการข้อมูลลับ (secret manager) ของเซิร์ฟเวอร์
  3. ตั้งค่าตัวแปรด้านล่างในสภาพแวดล้อมแบ็กเอนด์ของคุณ ตัวอย่างเหล่านี้ใช้ curl ให้แทนที่ ID ที่เป็นตัวพิมพ์ใหญ่ด้วยค่าที่ Sonny ส่งกลับมา และทดลองรันกับช่องทางทดสอบก่อนซิงค์กับศูนย์ช่วยเหลือที่ใช้งานจริง
bash
# Load SONNY_API_KEY from your server's secret manager first.
# SOURCE_ID is the ID in the channel dashboard URL: /app/sources/SOURCE_ID
export SOURCE_ID="YOUR_SOURCE_ID"
export BASE="https://www.usesonny.com/api/v1/sources/$SOURCE_ID"

การค้นหาช่องทาง (ไม่บังคับ): GET /api/v1/sources และเครื่องมือ MCP list_sources จะส่งคืนช่องทางที่เข้าถึงได้ และต้องใช้ขอบเขตสิทธิ์ conversations:read คุณไม่จำเป็นต้องมีขอบเขตสิทธิ์นี้หากมี ID ช่องทางอยู่แล้ว ส่วนการเชื่อมต่อโดเมนยังคงต้องทำในแดชบอร์ด

การตั้งค่าคีย์ API ฉบับเต็ม

2. สร้างหรืออัปเดตหมวดหมู่

ใช้ ID ที่คงที่จากระบบต้นทางของคุณใน URL การส่ง ID ภายนอกเดิมซ้ำจะอัปเดตหมวดหมู่ที่มีอยู่แทนการสร้างรายการซ้ำ การสร้างครั้งแรกจะเริ่มต้นศูนย์ช่วยเหลือของช่องทางนี้ให้โดยอัตโนมัติ

bash
curl --fail-with-body -X PUT "$BASE/categories/by-external-id/billing" \
  -H "Authorization: Bearer $SONNY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name":"Billing","position":0}'

ผลลัพธ์ที่คาดไว้คือ 201 เมื่อสร้าง หรือ 200 เมื่ออัปเดต โดยมีหมวดหมู่อยู่ใน data บันทึก data.id ไว้หากต้องการ ID หมวดหมู่ภายในของ Sonny เข้ารหัส URL ให้กับ ID ภายนอก และคงค่า ID ไว้แม้ชื่อจะเปลี่ยน

3. ซิงค์บทความเป็นฉบับร่าง

bash
curl --fail-with-body -X PUT "$BASE/articles/by-external-id/billing-guide" \
  -H "Authorization: Bearer $SONNY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"title":"Manage billing","body":"## Find your invoices\n\nOpen **Settings → Billing** in your account.","bodyFormat":"markdown","categoryExternalId":"billing","status":"draft"}'

ผลลัพธ์มี data.id และ HTML ที่ผ่านการกรองแล้วใน data.body เปิดบทความใน ช่องทาง → ช่องทางของคุณ → ศูนย์ช่วยเหลือ เพื่อตรวจสอบการจัดรูปแบบ Markdown จะถูกแปลงเป็น HTML และยังรองรับ HTML ดิบด้วย bodyFormat: "html" (ค่าเริ่มต้น) ชื่อบทความแยกจากหัวข้อภายในเนื้อหา

ใช้ categoryExternalId เพื่ออ้างอิงหมวดหมู่จากขั้นตอนที่ 2 หรือใช้ categoryId สำหรับ ID ของ Sonny โดยระบุเพียงอย่างเดียว ตั้งค่าใดค่าหนึ่งเป็น null เพื่อนำบทความออกจากหมวดหมู่ ทุกการ upsert ต้องมี title และ body ส่วน PATCH ใช้แก้ไขบางส่วนได้ บทความใหม่จะเป็นฉบับร่างโดยค่าเริ่มต้นเมื่อไม่ระบุ status

4. ตั้งค่าการเข้าถึง แล้วเผยแพร่

สำหรับตัวอย่างศูนย์ช่วยเหลือแบบส่วนตัวนี้ ให้เชื่อมต่อการเข้าสู่ระบบของลูกค้าที่ยืนยันตัวตนแล้วก่อน จากนั้นสร้างกลุ่มผู้ชมและบันทึก data.id ที่ส่งกลับมา:

bash
curl --fail-with-body -X POST "$BASE/audiences" \
  -H "Authorization: Bearer $SONNY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name":"Pro customers","rules":{"match":"all","conditions":[{"trait":"plan","op":"in","value":["pro","business"]}]}}'

แทนที่ AUDIENCE_ID, ARTICLE_ID และ URL สำหรับเข้าสู่ระบบด้านล่าง คำขอแรกจะเปิดให้ศูนย์ช่วยเหลือใช้งานจริง (Live) และจำกัดให้เฉพาะลูกค้า Pro คำขอที่สองจะเผยแพร่บทความ ขั้นตอนเข้าสู่ระบบของแอปคุณต้องแลกเปลี่ยน JWT ของลูกค้าที่ลงนามแล้วกับศูนย์ช่วยเหลือ การตั้งค่า signInUrl เพียงอย่างเดียวไม่ได้ทำให้ใครเข้าสู่ระบบ

bash
curl --fail-with-body -X PATCH "$BASE/help-center" \
  -H "Authorization: Bearer $SONNY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"enabled":true,"title":"Acme Help Center","audience":"verified","audienceId":"AUDIENCE_ID","signInUrl":"https://app.example.com/login"}'

# Replace ARTICLE_ID and AUDIENCE_ID with the returned data.id values.
curl --fail-with-body -X PATCH "$BASE/articles/ARTICLE_ID" \
  -H "Authorization: Bearer $SONNY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"audienceId":"AUDIENCE_ID","status":"published"}'

ใช้ URL ที่ได้จาก GET /help-center เพื่อทดสอบมุมมองของผู้อ่านที่เข้าสู่ระบบแล้ว และเบราว์เซอร์ที่ยังไม่ได้เข้าสู่ระบบ คีย์ API และ MCP ทำงานในฐานะทีมงานและอ่านเนื้อหาได้ตามขอบเขตสิทธิ์ ผลลัพธ์ที่สำเร็จจึงไม่ได้พิสูจน์ว่าลูกค้าจะมองเห็นบทความนั้น

สำหรับศูนย์ช่วยเหลือแบบสาธารณะ ให้ใช้ audience: "everyone" และ audienceId: null ทั้งในศูนย์ช่วยเหลือและบทความ และตรวจสอบการเข้าถึงของหมวดหมู่ด้วย หากต้องการนำออกเฉพาะกลุ่มที่ระบุชื่อ ให้ส่ง audienceId: null การเข้าถึงแบบเฉพาะผู้ที่ยืนยันตัวตนจะยังคงอยู่จนกว่าคุณจะเปลี่ยน audience อย่างชัดเจน หากไม่ระบุ audienceId ระบบจะคงการกำหนดเดิมไว้ ข้อจำกัดของศูนย์ช่วยเหลือ หมวดหมู่ และบทความต้องสอดคล้องกันทั้งหมด

กฎของกลุ่มที่ระบุชื่อใช้ match: "all" หรือ "any" มีเงื่อนไขได้สูงสุด 10 ข้อ และค่าต้องมีชนิดข้อมูลที่ถูกต้องอย่างเคร่งครัด ตัวดำเนินการ: eq, neq, in, not_in, exists, not_exists, gt, gte, lt, lte การเปรียบเทียบตัวเลขรับค่าเป็นตัวเลข in/not_in รับอาร์เรย์ของค่าสเกลาร์ 1–100 ค่า ส่วน exists/not_exists ไม่ต้องระบุ value แอตทริบิวต์ที่ไม่มีอยู่จะทำให้การเปรียบเทียบไม่ผ่าน ดูการทำงานของกฎ การสืบทอด และการแก้ไขปัญหาการเข้าถึง

5. ทำให้การซิงค์เป็นปัจจุบันอยู่เสมอ

เขียนซ้ำโดยไม่ทำให้เนื้อหาซ้ำ

ส่ง PUT ซ้ำด้วย ID ภายนอกเดิมทุกครั้งที่ต้นทางของคุณเปลี่ยน ID ภายนอกต้องไม่ซ้ำกันภายในศูนย์ช่วยเหลือเดียวกัน ฟิลด์ที่ไม่ได้ระบุในการอัปเดตจะคงค่าปัจจุบันไว้ ไม่ต้องระบุ status ในการ upsert ครั้งต่อ ๆ ไปเพื่อคงสถานะการเผยแพร่ไว้ การส่ง draft อย่างชัดเจนจะถอนบทความที่เผยแพร่อยู่ publishedAt จะถูกตั้งค่าเมื่อเผยแพร่ครั้งแรกและไม่เปลี่ยนแปลงอีก ฟิลด์นี้อ่านได้อย่างเดียว จึงนำเข้าวันที่เผยแพร่ในอดีตไม่ได้ เนื้อหาที่ไม่เปลี่ยนแปลงและการแก้ไขเฉพาะเมทาดาทาจะไม่ทำให้ต้องสร้างเอ็มเบดดิงใหม่โดยไม่จำเป็น

อ่านการเปลี่ยนแปลงและสถิติ

bash
curl --fail-with-body --get "$BASE/articles" \
  -H "Authorization: Bearer $SONNY_API_KEY" \
  --data-urlencode "page=1" --data-urlencode "limit=100" \
  --data-urlencode "status=published" \
  --data-urlencode "updatedSince=2026-09-01T00:00:00Z"

# Get the full sanitized HTML and read-only analytics for one article.
curl --fail-with-body "$BASE/articles/ARTICLE_ID" \
  -H "Authorization: Bearer $SONNY_API_KEY"

รายการบทความและหมวดหมู่จะส่งคืน data พร้อม pagination: page, limit, total หน้าเริ่มที่ 1 ส่วน limit มีค่าเริ่มต้นเป็น 50 และรับได้สูงสุด 100 ใช้ตัวกรองเดิมและขอหน้าถัดไปจนกว่า page × limit จะถึง total ทั้งสองรายการรองรับ externalId และ updatedSince ส่วนบทความยังรองรับ categoryId และ status (draft หรือ published) ใช้เวลาแบบ ISO ที่มีเขตเวลาสำหรับ updatedSince ซึ่งจะรวมรายการที่อัปเดต ณ เวลานั้นด้วย

อ่านรายละเอียดของแต่ละบทความเพื่อรับ body ฉบับเต็ม, viewCount, helpfulYes และ helpfulNo สถิติเหล่านี้อ่านได้อย่างเดียว updatedSince แสดงเฉพาะรายการปัจจุบันและไม่รายงานการลบ ให้ติดตามการนำออกในระบบต้นทางของคุณ แล้วลบรายการที่ตรงกันใน Sonny อย่างชัดเจนเมื่อต้องการ

อัปโหลดรูปภาพ

bash
curl --fail-with-body -X POST "$BASE/media" \
  -H "Authorization: Bearer $SONNY_API_KEY" \
  -F "file=@billing.png;type=image/png"

ใช้ data.url ที่ส่งกลับมาในไวยากรณ์รูปภาพของ Markdown หรือองค์ประกอบ img ของ HTML แล้วซิงค์เนื้อหาบทความ การอัปโหลดรองรับ JPEG, PNG, GIF และ WebP ขนาดไม่เกิน 25 MB และต้องเริ่มต้นศูนย์ช่วยเหลือไว้แล้ว URL ที่อัปโหลดเป็นแบบสาธารณะ ข้อจำกัดกลุ่มผู้ชมของบทความไม่ได้ปกป้อง URL ของรูปภาพ สำหรับ MCP ให้ใช้ upload_kb_media พร้อม sourceId, fileName, contentType และ dataBase64 ของไฟล์ แทนข้อมูลฟอร์มแบบ multipart

จัดเรียงบทความและหมวดหมู่

bash
curl --fail-with-body -X PUT "$BASE/articles/reorder" \
  -H "Authorization: Bearer $SONNY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"ids":["ARTICLE_ID_1","ARTICLE_ID_2"]}'

ก่อนอื่นให้ดึงข้อมูลทุกหน้าโดยไม่ใช้ตัวกรอง status หมวดหมู่ หรือวันที่ รวมถึงฉบับร่างด้วย แทนที่ ID ตัวอย่างด้วย ID ของบทความปัจจุบันทุกรายการ รายการละหนึ่งครั้งพอดีตามลำดับที่ต้องการ ใช้ /categories/reorder สำหรับรายการหมวดหมู่ทั้งหมด การจัดเรียงที่สำเร็จจะส่งคืน 204 โดยไม่มีเนื้อหา หากรายการเปลี่ยนไป หรือมี ID จากที่อื่น ID ซ้ำ หรือ ID ที่ขาดหาย ให้รีเฟรชรายการก่อนลองอีกครั้ง คุณยังตั้งค่า position ที่ไม่ติดลบในการเขียนแต่ละครั้งได้ด้วย

ใช้ขั้นตอนเดียวกันผ่าน MCP

  1. เชื่อมต่อไคลเอนต์ MCP ของคุณ และเลือกเวิร์กสเปซให้ถูกต้อง ตรวจสอบว่ามี kb:read และ kb:write พร้อมสิทธิ์เข้าถึงช่องทางของคุณ
  2. ใช้ ID ช่องทางจากแดชบอร์ด หรือเรียก list_sources หากข้อมูลรับรองของคุณมี conversations:read ด้วย ห้ามใช้ siteId ของวิดเจ็ตแทนเด็ดขาด
  3. รันตัวอย่าง 5 รายการแรกด้านล่างตามลำดับ แทนที่ SOURCE_ID ด้วย ID ช่องทางของคุณ และแทนที่ ARTICLE_ID/AUDIENCE_ID ด้วย data.id จากผลลัพธ์ก่อนหน้าแต่ละรายการ แทนที่ URL สำหรับเข้าสู่ระบบและเชื่อมต่อการเข้าสู่ระบบก่อนเผยแพร่
  4. ใช้ list_kb_articles เพื่อตรวจสอบการเปลี่ยนแปลง จัดเรียงลำดับหลังจากรวบรวมรายการทั้งหมดแบบไม่กรอง รวมถึงฉบับร่างแล้วเท่านั้น และรีเฟรชรายการหลังการเขียนที่สำเร็จ

แต่ละตัวอย่างแสดงชื่อเครื่องมือและอาร์กิวเมนต์ ฟิลด์ของ upsert ใส่ใน arguments โดยตรง ส่วนเครื่องมืออัปเดตจะใส่ฟิลด์ที่เปลี่ยนไว้ใน updates ข้อมูลเหล่านี้เป็นอินพุตของการเรียกใช้เครื่องมือ ไม่ใช่คำขอ HTTP ไปยังเอนด์พอยต์ MCP

1. upsert_kb_category
การเรียกใช้เครื่องมือ MCP
{
  "name": "upsert_kb_category",
  "arguments": {
    "sourceId": "SOURCE_ID",
    "externalId": "billing",
    "name": "Billing",
    "position": 0
  }
}
2. upsert_kb_article
การเรียกใช้เครื่องมือ MCP
{
  "name": "upsert_kb_article",
  "arguments": {
    "sourceId": "SOURCE_ID",
    "externalId": "billing-guide",
    "title": "Manage billing",
    "body": "## Find your invoices\n\nOpen **Settings → Billing** in your account.",
    "bodyFormat": "markdown",
    "categoryExternalId": "billing",
    "status": "draft"
  }
}
3. create_kb_audience
การเรียกใช้เครื่องมือ MCP
{
  "name": "create_kb_audience",
  "arguments": {
    "sourceId": "SOURCE_ID",
    "name": "Pro customers",
    "rules": {
      "match": "all",
      "conditions": [
        {
          "trait": "plan",
          "op": "in",
          "value": [
            "pro",
            "business"
          ]
        }
      ]
    }
  }
}
4. update_help_center
การเรียกใช้เครื่องมือ MCP
{
  "name": "update_help_center",
  "arguments": {
    "sourceId": "SOURCE_ID",
    "updates": {
      "enabled": true,
      "title": "Acme Help Center",
      "audience": "verified",
      "audienceId": "AUDIENCE_ID",
      "signInUrl": "https://app.example.com/login"
    }
  }
}
5. update_kb_article
การเรียกใช้เครื่องมือ MCP
{
  "name": "update_kb_article",
  "arguments": {
    "sourceId": "SOURCE_ID",
    "articleId": "ARTICLE_ID",
    "updates": {
      "audience": "verified",
      "audienceId": "AUDIENCE_ID",
      "status": "published"
    }
  }
}
6. list_kb_articles
การเรียกใช้เครื่องมือ MCP
{
  "name": "list_kb_articles",
  "arguments": {
    "sourceId": "SOURCE_ID",
    "page": 1,
    "limit": 100,
    "updatedSince": "2026-09-01T00:00:00Z"
  }
}
7. reorder_kb_articles
การเรียกใช้เครื่องมือ MCP
{
  "name": "reorder_kb_articles",
  "arguments": {
    "sourceId": "SOURCE_ID",
    "ids": [
      "ARTICLE_ID_1",
      "ARTICLE_ID_2"
    ]
  }
}
8. update_kb_audience
การเรียกใช้เครื่องมือ MCP
{
  "name": "update_kb_audience",
  "arguments": {
    "sourceId": "SOURCE_ID",
    "audienceId": "AUDIENCE_ID",
    "updates": {
      "name": "Paid customers"
    }
  }
}

การดำเนินการที่เกี่ยวข้อง

พาธ REST ด้านล่างเป็นแบบสัมพัทธ์กับ BASE อ่านเอกสารอ้างอิง APIสำหรับทุกฟิลด์และสคีมาของผลลัพธ์

การดำเนินการ REST และ MCP ที่เกี่ยวข้องกับฐานความรู้
งานRESTMCP
แสดงรายการ / ดึงบทความGET /articles · GET /articles/{articleId}list_kb_articles · get_kb_article
แสดงรายการ / ดึงหมวดหมู่GET /categories · GET /categories/{categoryId}list_kb_categories · get_kb_category
สร้างโดยไม่มี ID ภายนอกPOST /articles · POST /categoriescreate_kb_article · create_kb_category
แก้ไขเนื้อหาที่มีอยู่PATCH /articles/{articleId} · PATCH /categories/{categoryId}update_kb_article · update_kb_category
จัดเรียงหมวดหมู่PUT /categories/reorderreorder_kb_categories
อ่าน / แก้ไขการตั้งค่าศูนย์ช่วยเหลือGET /help-center · PATCH /help-centerget_help_center · update_help_center
แสดงรายการ / แก้ไขกลุ่มผู้ชมGET /audiences · PATCH /audiences/{audienceId}list_kb_audiences · update_kb_audience
ลบDELETE /articles/{articleId} · /categories/{categoryId} · /audiences/{audienceId}delete_kb_article · delete_kb_category · delete_kb_audience

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

แก้ไขปัญหาการซิงค์

400 · คำขอไม่ถูกต้อง
ตรวจสอบ title/body หรือ name ที่จำเป็น ชื่อฟิลด์ที่ถูกต้องตรงกัน ชนิดของกฎที่ถูกต้อง และ ID สำหรับจัดเรียงที่ครบถ้วน ส่ง categoryId หรือ categoryExternalId อย่างใดอย่างหนึ่ง ไม่ใช่ทั้งสองอย่าง
401 · ไม่ได้รับอนุญาต
ระบุคีย์ Bearer ที่ถูกต้อง และตรวจสอบว่าคีย์หมดอายุหรือถูกเพิกถอนแล้วหรือไม่
403 · ไม่มีสิทธิ์เข้าถึง
ตรวจสอบขอบเขตสิทธิ์ของคีย์ สิทธิ์ของเจ้าของคีย์ และสถานะการเรียกเก็บเงินของเวิร์กสเปซ หากเครื่องมือของไคลเอนต์ MCP เป็นแบบอ่านอย่างเดียว ให้เชื่อมต่อใหม่พร้อมสิทธิ์ที่จำเป็น
404 · ไม่พบ
ตรวจสอบว่าช่องทางอยู่ในเวิร์กสเปซที่เลือกและข้อมูลรับรองอนุญาตให้เข้าถึง หากเป็นศูนย์ช่วยเหลือใหม่ ให้เริ่มต้นด้วยการสร้างเนื้อหาก่อน เพราะการอ่านจะไม่สร้างศูนย์ช่วยเหลือให้
409 · ข้อมูลขัดแย้ง
ดึงเนื้อหาปัจจุบันอีกครั้ง แล้วแก้ไข ID ที่ขัดแย้งหรือลำดับที่ล้าสมัยก่อนลองอีกครั้ง
413 · ขนาดใหญ่เกินไป
ลดขนาดรูปภาพให้ต่ำกว่าขีดจำกัด 25 MB
429 · ถูกจำกัดอัตราการเรียกใช้
รอตามเวลาที่ระบุใน Retry-After ก่อนลองอีกครั้ง ใช้ RateLimit-Limit, RateLimit-Remaining และ RateLimit-Reset เพื่อควบคุมจังหวะการส่งคำขอ

เอกสารที่เกี่ยวข้อง