OROVA.VN — BIZ AI AGENT

/ คู่มือสำหรับนักพัฒนา

เผยแพร่ผ่าน API ไปยังเว็บไซต์ใดก็ได้

วิธีสร้าง endpoint ของคุณเอง เพื่อให้ Orova เผยแพร่บทความที่เขียนเสร็จแล้วไปยังเว็บไซต์ที่ไม่ได้ใช้ WordPress

Orova เผยแพร่บทความที่เขียนเสร็จแล้วขึ้น WordPress ได้ตั้งแต่แรกโดยไม่ต้องตั้งค่าอะไรเพิ่ม หากเว็บไซต์ของคุณไม่ใช่ WordPress — CMS ที่เขียนเอง ระบบ headless เว็บไซต์แบบสแตติก หรือ backend ของคุณเอง — คุณก็ยังให้ Orova เผยแพร่อัตโนมัติได้ ด้วยการรับบทความผ่าน API เล็ก ๆ ตัวหนึ่ง คุณเขียน HTTP endpoint เพียงเส้นเดียว แล้ว Orova จะเรียกมันทุกครั้งที่เขียนบทความเสร็จ หน้านี้เขียนสำหรับนักพัฒนา และอธิบายข้อตกลงของ request ไว้ครบถ้วน

/ ภาพรวม

ทำงานอย่างไร

เมื่อเขียนบทความสำหรับคีย์เวิร์ดหนึ่งเสร็จ Orova จะส่ง request HTTP POST เพียงครั้งเดียวไปยัง endpoint URL ที่คุณลงทะเบียนไว้ เนื้อ request เป็น JSON และบรรจุบทความทั้งบทความ endpoint ของคุณสร้างบทความขึ้นที่ฝั่งคุณ แล้วตอบกลับด้วย URL สาธารณะสุดท้ายของบทความนั้น Orova เก็บ URL นั้นไว้เป็นลิงก์ที่เผยแพร่แล้ว เหมือนกับกรณีบทความบน WordPress ทุกประการ ใช้วิธีนี้เมื่อคุณอยากให้การเผยแพร่เดินเองบนเว็บไซต์ที่ไม่ใช่ WordPress หรือเมื่อคุณอยากคุมวิธีจัดเก็บบทความทั้งหมดด้วยตัวเอง

/ การตั้งค่า

การตั้งค่า — หกขั้นตอน

  • 1

    เปิด “เชื่อมต่อผ่าน API” ในโปรเจกต์ของคุณ. เปิดโปรเจกต์ของคุณ (โปรเจกต์ → โปรเจกต์ของคุณ → การเชื่อมต่อ) ข้าง ๆ การ์ด WordPress จะมีการ์ด เชื่อมต่อผ่าน API ใส่ URL ของ endpoint ของคุณลงไปตรงนั้น ช่องคีย์ลับไม่บังคับ: เว้นว่างไว้แล้ว Orova จะสร้างให้ หรือวางคีย์ที่เว็บไซต์ของคุณใช้อยู่ก็ได้ เมื่อบันทึกแล้ว คีย์จะแสดงบนการ์ดพร้อมปุ่มคัดลอก โปรเจกต์หนึ่งเผยแพร่ไปที่เดียวเท่านั้น — เชื่อมผ่าน API แล้วปุ่ม WordPress จะถูกล็อก และในทางกลับกันก็เช่นกัน

  • 2

    สร้าง endpoint ที่รับ POST. บนเว็บไซต์หรือเซิร์ฟเวอร์ของคุณเอง สร้าง route ที่คอยรับ HTTP POST พร้อมเนื้อแบบ JSON นี่คือ URL ที่คุณนำกลับไปวางในหน้า การเชื่อมต่อ ของโปรเจกต์ ทุกบทความที่เขียนเสร็จ Orova จะเรียกมันหนึ่งครั้ง

  • 3

    ตรวจ Bearer token ทุก request. ทุก request จาก Orova จะมี header Authorization รูปแบบ “Bearer <secret>” ซึ่งเป็นคีย์ลับตัวเดียวกับที่แสดงในโปรเจกต์ของคุณ เทียบกับค่าที่คุณเก็บไว้ และปฏิเสธทุกอย่างที่ไม่ตรงกัน — นี่คือสิ่งเดียวที่กันไม่ให้คนอื่นส่งบทความขึ้นเว็บไซต์ของคุณ

  • 4

    สร้างบทความจาก payload. อ่านเนื้อ JSON แล้วสร้างบทความใน CMS หรือฐานข้อมูลของคุณ: ใช้ title, slug และ content_html สำหรับตัวบทความ, excerpt เป็น meta description, featured_image_url เป็นภาพหน้าปก ส่วน keyword / lang / published_at เป็นข้อมูลกำกับ

  • 5

    ตอบกลับด้วย URL จริงของบทความ. ตอบด้วย HTTP 200 หรือ 201 พร้อมเนื้อ JSON { "url": "https://yoursite.com/the-new-article" } Orova จะเก็บ URL นั้นไว้เป็นลิงก์ที่เผยแพร่แล้วของบทความ หากคุณตอบด้วยสถานะที่ไม่ใช่ 2xx หรือไม่มีฟิลด์ url, Orova จะถือว่าการเผยแพร่ครั้งนั้นล้มเหลว

  • 6

    รองรับ request อัปเดตจากเอนจินปรับให้ดีขึ้น. เมื่อเนื้อ request มี action = "update" ให้ค้นบทความเดิมด้วย target_url (หรือ slug) แล้วเขียนทับ title, content_html และ excerpt ที่เดิม — คง URL ไว้เหมือนเดิม ตอบ 200 พร้อม { "url": ... } ที่ชี้ไปยังบทความเดียวกัน endpoint ที่ยังไม่รองรับส่วนนี้จะทำให้ Orova รายงานว่าการปรับครั้งนั้นล้มเหลวเท่านั้น การเผยแพร่บทความใหม่ยังทำงานเหมือนเดิม

/ ข้อตกลง

request ที่ Orova ส่งมา

บทความที่เขียนเสร็จแต่ละบทความจะมาถึงเป็น request หนึ่งชุด หน้าตาแบบนี้:

POST <your endpoint URL>

Headers:
  Content-Type: application/json
  Authorization: Bearer <secret>     # the secret key shown in Project -> Connections
  User-Agent: Orova-SEO

Body (JSON):
  {
    "title": "...",                  # article headline
    "slug": "...",                   # URL-friendly identifier
    "content_html": "...",           # full article HTML
    "excerpt": "...",                # meta description
    "featured_image_url": "..." | null,
    "keyword": "...",                # target keyword
    "lang": "en",                    # ISO language code
    "published_at": "2026-05-17T09:00:00.482913",  # ISO 8601, UTC, no offset suffix
    "status": "draft"                # optional: "draft" keeps it unpublished
  }
  # Optional fields are left out of the body when empty — never sent as null.

/ อ้างอิง

ฟิลด์ใน payload

ฟิลด์ของ request เผยแพร่ (บทความใหม่) ส่วน request ลบและ request อัปเดตใช้ endpoint เดียวกันนี้ แต่เนื้อ request สั้นกว่า — ดูได้ในหัวข้อของแต่ละอย่างด้านล่าง

ฟิลด์ชนิดคำอธิบาย
titlestringหัวข้อของบทความ
slugstringตัวระบุที่เหมาะกับ URL ซึ่ง Orova แนะนำไว้ให้บทความนี้
content_htmlstringเนื้อบทความทั้งหมดในรูป HTML ที่พร้อมเผยแพร่ทันที
excerptstringบทสรุปสั้น ๆ สำหรับใช้เป็น meta description
featured_image_urlstring | nullURL ของภาพหน้าปก หรือ null เมื่อไม่มีภาพ
keywordstringคีย์เวิร์ด SEO เป้าหมายที่บทความนี้เขียนขึ้นเพื่อมัน
langstringภาษาในรูปรหัส ISO เช่น "en" หรือ "vi"
published_atstringเวลาที่ Orova ส่งบทความออกมา รูปแบบ ISO 8601 ตามเวลา UTC ไม่มีส่วนต่อท้ายบอกเขตเวลา
statusstring | undefinedไม่บังคับ "draft" คงบทความไว้เป็นฉบับร่าง ส่วน "publish" หรือไม่ส่งค่าจะเผยแพร่ทันที
actionstring | undefinedไม่มีในบทความใหม่ ค่า "delete" คือขอให้คุณลบบทความ ส่วน "update" (ส่งมาจากเอนจินปรับให้ดีขึ้น) คือขอให้คุณเขียนทับบทความเดิมที่เดิม
target_urlstringมีเฉพาะเมื่อ action = "update": URL จริงของบทความที่ต้องเขียนทับ ให้คง URL ไว้เหมือนเดิม
idnumber | undefinedส่งมาเฉพาะเมื่อ Orova รู้รหัสบทความฝั่งคุณ (ได้จาก `list` หรือ `get`) ให้ค้นด้วยรหัสนี้ก่อน `target_url`

ฟิลด์ที่ไม่บังคับจะหายไปทั้งคีย์เมื่อไม่มีค่าให้ส่ง Orova ไม่เคยส่ง null ดังนั้นให้อ่านฟิลด์เหล่านี้แบบกันพลาดไว้ก่อน status ก็ไม่บังคับเช่นกัน: "draft" จะสร้างบทความเป็นฉบับร่าง ส่วน "publish" หรือไม่ส่ง status เลยจะเผยแพร่ทันที

/ ข้อตกลง

response ที่คุณต้องตอบกลับ

เมื่อสร้างบทความเสร็จแล้ว ให้ตอบด้วยสถานะ HTTP 200 หรือ 201 พร้อมเนื้อ JSON แบบนี้:

HTTP 200 (or 201)
Content-Type: application/json

{
  "url": "https://yoursite.com/published-article"
}

Orova เก็บ url นั้นไว้เป็นลิงก์ที่เผยแพร่แล้วของบทความ หาก endpoint ของคุณตอบด้วยสถานะใดก็ตามที่ไม่ใช่ 2xx หรือตอบด้วยเนื้อที่ไม่ใช่ออบเจ็กต์ JSON ที่มีฟิลด์ url, Orova จะถือว่าการเผยแพร่ล้มเหลว และทำเครื่องหมายบทความไว้ให้คุณลองใหม่ได้ ตอบกลับภายใน 60 วินาที — เกินกว่านั้น Orova จะตัด request ทิ้ง (ใช้กับทั้งการเผยแพร่ การอัปเดต และการลบเหมือนกัน)

/ ตัวอย่าง

ตัวอย่างโค้ด

endpoint สำหรับรับบทความแบบย่อที่สุดด้วย Node.js กับ Express ขั้นตอนเดียวกันนี้ — ตรวจ token, แยกทางตาม action (update / delete), สร้างหรือแก้บทความ, ตอบ URL กลับ — ใช้ได้กับทุกภาษาและทุกเฟรมเวิร์ก

// Node.js / Express — a minimal receiving endpoint
import express from "express";

const app = express();
app.use(express.json({ limit: "5mb" }));

// The secret Orova generated for this project.
const OROVA_SECRET = process.env.OROVA_SECRET;

app.post("/orova/publish", async (req, res) => {
  // 1. Verify the Bearer token.
  const auth = req.get("authorization") || "";
  if (auth !== "Bearer " + OROVA_SECRET) {
    return res.status(401).json({ error: "unauthorized" });
  }

  // 2. Visual Editor: list your articles. action = "list".
  if (req.body.action === "list") {
    const posts = await listPosts(req.body.limit || 500); // newest first, 500 max
    return res.status(200).json({ ok: true, posts });
  }

  // 3. Visual Editor: read one article in full. action = "get".
  if (req.body.action === "get") {
    const post = await findPost(req.body.id, req.body.target_url, req.body.slug);
    if (!post) return res.status(404).json({ ok: false, err: "not_found" });
    return res.status(200).json({ ok: true, post });
  }

  // 4. Visual Editor: store an image, answer with its absolute URL.
  if (req.body.action === "upload_image") {
    const { filename, mime, data_base64 } = req.body;
    if (!String(mime || "").startsWith("image/")) {
      return res.status(422).json({ error: "image files only" });
    }
    const bytes = Buffer.from(data_base64, "base64");
    if (bytes.length > 15 * 1024 * 1024) {
      return res.status(413).json({ error: "image too large" });
    }
    return res.status(200).json({ ok: true, url: await saveImage(filename, bytes) });
  }

  // 5. Delete requests: body carries action = "delete".
  if (req.body.action === "delete") {
    const removed = await deletePostByUrl(req.body.url, req.body.slug);
    if (!removed) return res.status(404).json({ deleted: true }); // already gone
    return res.status(200).json({ deleted: true });
  }

  // 6. Update requests: action = "update". Optional fields may be absent.
  if (req.body.action === "update") {
    const updated = await updatePost(req.body.id, req.body.target_url, {
      title: req.body.title,
      html: req.body.content_html,
      metaDescription: req.body.excerpt,
      coverImage: req.body.featured_image_url,  // optional
      status: req.body.status,                  // optional: "publish" | "draft"
    });
    if (!updated) return res.status(404).json({ error: "post not found" });
    return res.status(200).json({ url: updated.url });
  }

  // 7. New article: read the payload Orova sent.
  const {
    title, slug, content_html, excerpt,
    featured_image_url, keyword, lang, published_at, status,
  } = req.body;

  // 8. Create the article in your own CMS or database.
  const post = await createPost({
    title,
    slug,
    html: content_html,
    metaDescription: excerpt,
    coverImage: featured_image_url,
    keyword,
    lang,
    publishedAt: published_at,
    draft: status === "draft",   // status is optional; absent means publish now
  });

  // 9. Return 200/201 with the live URL.
  return res.status(201).json({
    url: "https://yoursite.com/" + post.slug,
  });
});

app.listen(3000);

/ ข้อตกลง — อัปเดต

การอัปเดตบทความ (ปรับให้ดีขึ้น)

เอนจินปรับให้ดีขึ้น ของ Orova เขียนบทความที่เผยแพร่ไปแล้วใหม่ — ข้อมูลที่สดกว่า หัวข้อที่ดีกว่า เนื้อหาที่รวมเข้าด้วยกัน — แล้วเขียนทับบทความเดิม ที่เดิม โดยคง URL ไว้เหมือนเดิม request จะไปที่ endpoint URL เดียวกัน เป็น POST พร้อม Bearer token ตัวเดิม:

POST <your endpoint URL>

Headers:
  Content-Type: application/json
  Authorization: Bearer <secret>
  User-Agent: Orova-SEO

Body (JSON):
  {
    "action": "update",          # always the literal string "update"
    "id": 123,                   # optional: post id, match on this first
    "target_url": "...",         # public URL of the post to overwrite (match next)
    "title": "...",              # new headline
    "slug": "...",               # slug fallback for matching, may be empty
    "content_html": "...",       # full new article body as HTML
    "excerpt": "...",            # new meta description
    "featured_image_url": "...", # optional: new cover image
    "status": "publish"          # optional: "publish" or "draft"
  }
  # Optional fields are left out of the body when empty — never sent as null.

ค้นบทความด้วย target_url (หากไม่พบให้ใช้ slug) แทนที่หัวข้อ เนื้อบทความ และ meta description คง URL ไว้อย่างเดิม แล้วตอบ 200 พร้อม { "url": "<URL ของบทความเดิม>" } สถานะใดก็ตามที่ไม่ใช่ 2xx หรือเนื้อที่ไม่มี url จะทำให้ Orova บันทึกว่าการปรับครั้งนั้นล้มเหลว (Orova ไม่เคยสร้างบทความซ้ำ) endpoint ที่ยังไม่ได้ทำส่วนนี้ยังเผยแพร่บทความใหม่ได้เหมือนเดิม — การรองรับ update จำเป็นก็ต่อเมื่อคุณเริ่มใช้หน้าปรับให้ดีขึ้น

ตั้งแต่ 29 ส.ค. เนื้อคำสั่งอัปเดตอาจมี id มาด้วย (ใช้ค้นก่อน target_url), featured_image_url สำหรับเปลี่ยนรูปปก และ status ที่รับค่า "publish" หรือ "draft" ทั้งสามเป็นตัวเลือก ถ้าไม่มีค่าจะถูกตัดออกทั้งคีย์ ไม่ถูกส่งเป็น null

/ ข้อตกลง — Visual Editor

อ่านบทความ (Visual Editor)

Visual Editor เปิดบทความที่อยู่บนเว็บของคุณอยู่แล้ว จึงต้องอ่านบทความให้ได้ก่อน นี่คืออีกสอง action บน endpoint URL เดิม และ Bearer token เดิม: list คืนรายการบทความ ส่วน get คืนบทความเดียวแบบเต็ม

POST <your endpoint URL>     # same endpoint, same Bearer secret

Body (JSON):
  {
    "action": "list",            # always the literal string "list"
    "limit": 500                 # newest article first, 500 maximum
  }

Response — HTTP 200:
  {
    "ok": true,
    "posts": [
      {
        "id": 123,               # your own post id, reused by "get" and "update"
        "title": "...",
        "slug": "...",
        "link": "https://yoursite.com/the-article",
        "date": "2026-08-29",    # published date, YYYY-MM-DD
        "modified": "2026-08-29",
        "status": "publish",     # "publish" or "draft"
        "author": "..."
      }
    ]
  }
POST <your endpoint URL>

Body (JSON):
  {
    "action": "get",             # always the literal string "get"
    "id": 123,                   # match on this first
    "target_url": "...",         # then the public URL
    "slug": "..."                # then the slug
  }

Response — HTTP 200:
  {
    "ok": true,
    "post": {
      "id": 123,
      "title": "...",
      "slug": "...",
      "link": "https://yoursite.com/the-article",
      "content_html": "...",     # full article body as HTML
      "excerpt": "...",
      "featured_image_url": "...",
      "status": "publish"        # "publish" or "draft"
    }
  }

No such post — HTTP 404:
  { "ok": false, "err": "not_found" }

limit สูงสุด 500 และบทความใหม่สุดมาก่อน สำหรับ get Orova ส่ง id, target_url และ slug มาพร้อมกัน — ให้ค้นด้วยตัวที่คุณรู้จัก ตามลำดับนี้ ถ้าไม่พบให้ตอบ 404 พร้อม { "ok": false, "err": "not_found" } สอง action นี้จำเป็นเฉพาะเมื่อคุณอยากแก้บทความบนเว็บด้วย Visual Editor การเผยแพร่และการปรับแต่งอัตโนมัติไม่ต้องใช้

/ ข้อตกลง — Visual Editor

อัปโหลดรูป (Visual Editor)

เมื่อคุณวางรูปลงใน Visual Editor Orova จะส่งรูปนั้นไปที่ endpoint URL เดิม เป็น base64 ในเนื้อ JSON — ไม่ใช้ multipart และไม่มี URL เพิ่มให้ต้องดูแล

POST <your endpoint URL>

Body (JSON):
  {
    "action": "upload_image",    # always the literal string "upload_image"
    "filename": "cover.png",     # original file name, extension included
    "mime": "image/png",         # image/* only
    "data_base64": "iVBORw0KGgo..."   # file bytes, base64, 15 MB maximum
  }

Response — HTTP 200 (or 201):
  {
    "ok": true,
    "url": "https://yoursite.com/uploads/cover.png"   # absolute URL
  }

รับเฉพาะ image/* และปฏิเสธไฟล์ที่เกิน 15 MB เก็บไฟล์เสร็จแล้วให้ตอบกลับเป็น URL แบบเต็ม ของรูป เพราะพาธแบบสัมพัทธ์จะพังเมื่อบทความไปแสดงที่อื่น action นี้ใช้กับ Visual Editor เท่านั้น

/ ข้อตกลง — ลบ

การลบบทความ

เมื่อผู้ใช้ลบบทความที่เผยแพร่แล้วภายใน Orova (จากหน้าเขียนบทความหรือหน้ารายงาน) Orova จะขอให้เว็บไซต์ของคุณลบบทความนั้นด้วย request จะไปที่ endpoint URL เดียวกัน เป็น POST พร้อม Bearer token ตัวเดิม — ต่างกันแค่เนื้อ request:

POST <your endpoint URL>

Headers:
  Content-Type: application/json
  Authorization: Bearer <secret>
  User-Agent: Orova-SEO

Body (JSON):
  {
    "action": "delete",          # always the literal string "delete"
    "url": "...",                # public URL of the article to remove (match on this first)
    "slug": "...",               # slug fallback, may be empty
    "keyword": "..."             # the article's target keyword, for your logs
  }

ค้นบทความด้วย url (หากไม่พบให้ใช้ slug) ลบหรือถอนการเผยแพร่ แล้วตอบ 200 พร้อม { "deleted": true } — ตอบ 204 เปล่า ๆ ก็ได้ ถ้าบทความไม่มีอยู่แล้ว ให้ตอบ 404 แล้ว Orova จะถือว่าถูกลบไปแล้ว การตอบ { "deleted": false } เป็นการบอก Orova ว่าบทความยังไม่ถูกลบ request เผยแพร่ตามปกติจะไม่มีฟิลด์ action เลย endpoint เดิมจึงทำงานได้เหมือนเดิม — การรองรับการลบไม่บังคับ แต่แนะนำให้ทำ

/ ความปลอดภัย

ข้อควรระวังด้านความปลอดภัย

  • ตรวจ Bearer token เสมอ. endpoint ของคุณเป็น URL สาธารณะ — ใครก็เรียกได้ header Authorization คือสิ่งเดียวที่พิสูจน์ว่า request นั้นมาจาก Orova จริง ดังนั้นให้ปฏิเสธทุก request ที่ token ไม่ตรงกับคีย์ลับที่คุณเก็บไว้ทุกตัวอักษร
  • ให้บริการ endpoint ผ่าน HTTPS. คีย์ลับเดินทางอยู่ใน header ของ request HTTPS ช่วยกันไม่ให้ใครอ่านมันได้ระหว่างทาง
  • อย่าเก็บคีย์ลับไว้ในโค้ด. เก็บไว้ในตัวแปรสภาพแวดล้อมหรือระบบจัดการความลับ อย่าเขียนตายลงในไฟล์ที่คุณคอมมิต หากคีย์หลุดออกไปเมื่อใด ให้สร้างคีย์ใหม่จาก โปรเจกต์ → การเชื่อมต่อ
  • ปฏิบัติกับ content_html แบบเนื้อหา ไม่ใช่ markup ที่เชื่อถือได้. จัดเก็บและแสดงผลมันอย่างระมัดระวังเหมือนที่คุณทำกับเนื้อบทความอื่นใน CMS ของคุณ

/ สรุป

ข้อตกลงทั้งหมดมีเท่านี้

เมื่อ endpoint พร้อมใช้งานและลงทะเบียนในโปรเจกต์แล้ว Orova จะเผยแพร่ขึ้นเว็บไซต์ของคุณโดยอัตโนมัติ — เหมือนกับที่ทำให้ผู้ใช้ WordPress ทุกประการ บทความที่เขียนเสร็จแต่ละบทความจะไปถึงเซิร์ฟเวอร์ของคุณ กลายเป็นบทความหนึ่งบท และ URL สาธารณะของมันจะไหลกลับเข้าสู่รายงานและการวิเคราะห์ เพื่อให้คุณติดตามผลได้

← กลับไปคลังคู่มือ

/ ต้องการความช่วยเหลือ?

ติดขัดตอนต่อ endpoint ใช่ไหม เปิดส่วนช่วยเหลือในพื้นที่ทำงานของคุณ หรือ ส่งข้อความถึงเรา