/ คู่มือสำหรับนักพัฒนา
เผยแพร่ผ่าน 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 สั้นกว่า — ดูได้ในหัวข้อของแต่ละอย่างด้านล่าง
| ฟิลด์ | ชนิด | คำอธิบาย |
|---|---|---|
| title | string | หัวข้อของบทความ |
| slug | string | ตัวระบุที่เหมาะกับ URL ซึ่ง Orova แนะนำไว้ให้บทความนี้ |
| content_html | string | เนื้อบทความทั้งหมดในรูป HTML ที่พร้อมเผยแพร่ทันที |
| excerpt | string | บทสรุปสั้น ๆ สำหรับใช้เป็น meta description |
| featured_image_url | string | null | URL ของภาพหน้าปก หรือ null เมื่อไม่มีภาพ |
| keyword | string | คีย์เวิร์ด SEO เป้าหมายที่บทความนี้เขียนขึ้นเพื่อมัน |
| lang | string | ภาษาในรูปรหัส ISO เช่น "en" หรือ "vi" |
| published_at | string | เวลาที่ Orova ส่งบทความออกมา รูปแบบ ISO 8601 ตามเวลา UTC ไม่มีส่วนต่อท้ายบอกเขตเวลา |
| status | string | undefined | ไม่บังคับ "draft" คงบทความไว้เป็นฉบับร่าง ส่วน "publish" หรือไม่ส่งค่าจะเผยแพร่ทันที |
| action | string | undefined | ไม่มีในบทความใหม่ ค่า "delete" คือขอให้คุณลบบทความ ส่วน "update" (ส่งมาจากเอนจินปรับให้ดีขึ้น) คือขอให้คุณเขียนทับบทความเดิมที่เดิม |
| target_url | string | มีเฉพาะเมื่อ action = "update": URL จริงของบทความที่ต้องเขียนทับ ให้คง URL ไว้เหมือนเดิม |
| id | number | 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 ใช่ไหม เปิดส่วนช่วยเหลือในพื้นที่ทำงานของคุณ หรือ ส่งข้อความถึงเรา
