/ Hướng dẫn cho lập trình viên
Đăng bài qua API lên mọi website.
Cách dựng endpoint của riêng bạn để Orova đăng bài lên website không chạy WordPress.
Orova đăng bài lên WordPress sẵn từ đầu. Nếu website của bạn không phải WordPress — CMS tự viết, hệ thống headless, trang tĩnh, hay backend riêng — bạn vẫn để Orova đăng tự động được, bằng cách nhận bài qua một API nhỏ. Bạn viết một endpoint HTTP; mỗi lần viết xong một bài, Orova gọi vào đó. Trang này dành cho lập trình viên và mô tả đầy đủ hợp đồng request.
/ Tổng quan
Cách hoạt động
Viết xong một bài cho từ khoá, Orova gửi đúng một request HTTP POST tới endpoint URL bạn đã khai. Thân request là JSON, chứa trọn bài viết. Endpoint của bạn tạo bài ở phía bạn, rồi trả về URL công khai cuối cùng của bài. Orova lưu URL đó làm link đã đăng, y như với bài WordPress. Dùng cách này khi bạn muốn đăng tự động trên website không phải WordPress, hoặc muốn tự nắm cách lưu bài.
/ Cài đặt
Cài đặt — sáu bước
- 1
Bật “Kết nối qua API” trong dự án. Mở dự án của bạn (Dự án → dự án của bạn → Kết nối). Cạnh thẻ WordPress có thẻ Kết nối qua API. Nhập đường dẫn endpoint của bạn vào đó. Ô khoá bí mật là tuỳ chọn: để trống thì Orova tự sinh một khoá, hoặc bạn dán khoá website mình đang dùng. Lưu xong, khoá hiện ngay trên thẻ kèm nút Sao chép. Mỗi dự án chỉ đăng về một nơi — nối qua API thì nút nối WordPress khoá lại, và ngược lại.
- 2
Dựng một endpoint nhận POST. Trên website hoặc máy chủ của bạn, tạo một route lắng nghe HTTP POST với thân JSON. Đây chính là đường dẫn bạn dán lại vào màn hình Kết nối của dự án. Mỗi bài viết xong, Orova gọi vào đó đúng một lần.
- 3
Kiểm tra Bearer token ở mọi request. Mọi request từ Orova đều mang header Authorization dạng “Bearer <secret>”, đúng khoá bí mật hiện trong dự án. Hãy so với bản bạn lưu và chặn mọi request không khớp — đây là thứ duy nhất ngăn người lạ đăng bài lên website của bạn.
- 4
Tạo bài từ payload. Đọc thân JSON rồi tạo bài trong CMS hoặc cơ sở dữ liệu của bạn: dùng title, slug và content_html cho phần bài viết, excerpt làm meta description, featured_image_url làm ảnh bìa, còn keyword / lang / published_at là dữ liệu kèm theo.
- 5
Trả về URL thật của bài. Trả HTTP 200 hoặc 201 kèm thân JSON { "url": "https://website-cua-ban.com/bai-viet-moi" }. Orova lưu URL đó làm link đã đăng của bài. Nếu bạn trả mã không thuộc nhóm 2xx, hoặc thiếu trường url, Orova coi lần đăng đó là thất bại.
- 6
Xử lý lệnh cập nhật từ engine Tối ưu. Khi thân request có action = "update", hãy tìm bài cũ theo target_url (hoặc slug) rồi ghi đè title, content_html và excerpt tại chỗ — giữ nguyên URL. Trả 200 kèm { "url": ... } trỏ về chính bài đó. Endpoint chưa xử lý phần này chỉ khiến Orova báo lần tối ưu là thất bại; việc đăng bài mới vẫn chạy như cũ.
/ Hợp đồng
Request Orova gửi sang
Mỗi bài viết xong sẽ tới dưới dạng một request như sau:
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./ Tra cứu
Các trường trong payload
Các trường của một request đăng bài mới. Lệnh xoá và lệnh cập nhật dùng chung endpoint này, thân request gọn hơn — xem đúng mục của từng lệnh phía dưới.
| Trường | Kiểu | Mô tả |
|---|---|---|
| title | string | Tiêu đề bài viết. |
| slug | string | Định danh thân thiện URL mà Orova gợi ý cho bài. |
| content_html | string | Toàn bộ thân bài dưới dạng HTML, đăng được ngay. |
| excerpt | string | Đoạn tóm tắt ngắn, dùng làm meta description. |
| featured_image_url | string | null | Đường dẫn ảnh bìa, hoặc null khi bài không có ảnh. |
| keyword | string | Từ khoá SEO mà bài được viết để nhắm tới. |
| lang | string | Ngôn ngữ dạng mã ISO, ví dụ "en" hoặc "vi". |
| published_at | string | Thời điểm Orova gửi bài, chuẩn ISO 8601 theo giờ UTC, không có hậu tố múi giờ. |
| status | string | undefined | Tuỳ chọn. "draft" giữ bài ở dạng nháp; "publish" hoặc không gửi gì thì đăng bài ngay. |
| action | string | undefined | Không có ở bài mới. "delete" là yêu cầu bạn gỡ một bài; "update" (do engine Tối ưu gửi) là yêu cầu ghi đè một bài đã có, giữ nguyên chỗ cũ. |
| target_url | string | Chỉ đi kèm action = "update": URL thật của bài cần ghi đè. Giữ nguyên URL này. |
| id | number | undefined | Chỉ có khi Orova biết mã bài bên bạn (lấy từ `list` hoặc `get`): tra theo mã này trước `target_url`. |
Trường tuỳ chọn không có gì để gửi thì Orova bỏ hẳn khoá đó, không gửi null — hãy đọc mọi trường tuỳ chọn theo kiểu phòng thủ. status cũng là tuỳ chọn: "draft" tạo bài ở dạng nháp, còn "publish" hoặc không có status thì đăng ngay.
/ Hợp đồng
Response bạn phải trả về
Tạo bài xong, hãy trả về mã HTTP 200 hoặc 201 cùng thân JSON này:
HTTP 200 (or 201)
Content-Type: application/json
{
"url": "https://yoursite.com/published-article"
}Orova lưu url đó làm link đã đăng của bài. Nếu endpoint của bạn trả mã ngoài nhóm 2xx, hoặc thân response không phải object JSON có trường url, Orova coi lần đăng là thất bại và đánh dấu bài để bạn chạy lại. Hãy trả lời trong vòng 60 giây — quá hạn Orova ngắt request (áp dụng cho cả đăng, cập nhật lẫn xoá).
/ Ví dụ
Mã mẫu
Một endpoint nhận bài tối giản viết bằng Node.js với Express. Các bước vẫn thế ở mọi ngôn ngữ hay framework: kiểm token, rẽ nhánh theo action (update / delete), tạo hoặc sửa bài, rồi trả URL về.
// 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);/ Hợp đồng — cập nhật
Cập nhật bài (Tối ưu)
Engine Tối ưu của Orova viết lại những bài đã lên sóng — cập nhật dữ kiện, sửa tiêu đề, gộp nội dung — rồi ghi đè bài cũ tại chỗ, giữ nguyên URL. Request đi tới chính endpoint URL đó, cũng là POST với cùng 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.Tìm bài theo target_url (không thấy thì dò theo slug), thay tiêu đề, thân bài và meta description, giữ nguyên URL, rồi trả 200 kèm { "url": "<URL chính bài đó>" }. Mã ngoài nhóm 2xx, hoặc thân response thiếu url, sẽ khiến Orova đánh dấu lần tối ưu là thất bại (Orova không bao giờ tạo bài trùng). Endpoint chưa làm phần này vẫn đăng bài mới bình thường — chỉ khi bạn dùng màn hình Tối ưu mới cần bổ sung.
Từ 29/8, thân lệnh cập nhật có thể kèm id (tra theo id trước, rồi mới tới target_url), featured_image_url để đổi ảnh bìa, và status nhận "publish" hoặc "draft". Cả ba đều tuỳ chọn; không có giá trị thì Orova bỏ hẳn khoá, không gửi null.
/ Hợp đồng — Visual Editor
Đọc bài (Visual Editor)
Visual Editor mở đúng bài đang nằm trên website của bạn, nên trước hết nó phải đọc được bài. Thêm hai action trên cùng endpoint URL, cùng Bearer token: list trả danh sách bài, get trả trọn một bài.
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 tối đa 500, bài mới nhất đứng đầu. Với get, Orova gửi cùng lúc id, target_url và slug — bạn tra theo cái nào nhận ra, đúng thứ tự đó. Không thấy bài thì trả 404 kèm { "ok": false, "err": "not_found" }. Hai action này chỉ cần khi bạn muốn sửa bài trên site bằng Visual Editor — đăng bài và tối ưu tự động không cần tới chúng.
/ Hợp đồng — Visual Editor
Tải ảnh lên (Visual Editor)
Khi bạn thả một ảnh vào Visual Editor, Orova gửi ảnh tới cùng endpoint URL dưới dạng base64 trong thân JSON — không multipart, không thêm đường dẫn nào phải bảo vệ.
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
}Chỉ nhận image/*, từ chối file quá 15 MB. Lưu file xong, trả về URL tuyệt đối của ảnh: đường dẫn tương đối sẽ vỡ khi bài hiện ở nơi khác. Action này chỉ cần cho Visual Editor.
/ Hợp đồng — xoá
Xoá bài
Khi người dùng gỡ một bài đã đăng ngay trong Orova (ở màn hình Viết bài hoặc Báo cáo), Orova nhờ website của bạn xoá bài đó luôn. Request đi tới chính endpoint URL đó, cũng là POST với cùng Bearer token — chỉ khác phần thân:
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
}Tra bài theo url (không thấy thì dò theo slug), xoá hoặc gỡ đăng, rồi trả 200 kèm { "deleted": true } — trả trống mã 204 cũng được. Nếu bài không còn nữa, trả 404 và Orova hiểu là đã xoá rồi. Trả { "deleted": false } nghĩa là bạn báo bài chưa được gỡ. Request đăng bài thường không bao giờ có trường action, nên endpoint cũ vẫn chạy nguyên — làm phần xoá là tuỳ bạn, nhưng nên làm.
/ Bảo mật
Lưu ý bảo mật
- Luôn kiểm Bearer token. Endpoint của bạn là một URL công khai — ai cũng gọi được. Header Authorization là thứ duy nhất chứng minh request thật sự đến từ Orova, nên hãy chặn mọi request có token không khớp chính xác khoá bạn lưu.
- Chạy endpoint trên HTTPS. Khoá bí mật đi trong header. HTTPS giữ cho nó không bị đọc trên đường truyền.
- Đừng để khoá nằm trong code. Cất khoá ở biến môi trường hoặc kho bí mật, đừng viết cứng vào file rồi commit. Lỡ lộ thì vào Dự án → Kết nối sinh khoá mới.
- Coi content_html là nội dung, không phải markup đáng tin. Hãy lưu và hiển thị nó cẩn thận đúng như mọi thân bài khác trong CMS của bạn.
/ Khép lại
Hợp đồng chỉ có vậy
Endpoint chạy được và đã khai trong dự án, Orova sẽ đăng bài lên website của bạn tự động — y như với người dùng WordPress. Mỗi bài viết xong sẽ về máy chủ của bạn, thành một bài đăng, và URL công khai của nó chảy ngược vào Báo cáo cùng Phân tích để bạn theo dõi hiệu quả.
/ Cần hỗ trợ?
Nối endpoint mãi chưa được? Mở mục Hỗ trợ trong khu làm việc, hoặc gửi tin nhắn cho chúng tôi.
