OROVA.VN — BIZ AI AGENT

/ Panduan pembangun

Penerbitan melalui API di mana-mana laman web.

Cara membina endpoint anda sendiri supaya Orova boleh menerbitkan artikel siap ke laman yang tidak menggunakan WordPress.

Orova menerbitkan artikel siap ke WordPress secara terus. Jika laman web anda bukan WordPress — CMS tersuai, persediaan headless, laman statik atau backend anda sendiri — anda masih boleh membenarkan Orova menerbitkan secara automatik dengan menerima artikel melalui API kecil. Anda menulis satu endpoint HTTP; Orova memanggilnya setiap kali ia menyiapkan artikel. Panduan ini untuk pembangun dan menerangkan kontrak permintaan sepenuhnya.

/ Gambaran keseluruhan

Cara ia berfungsi

Apabila Orova selesai menulis artikel untuk sesuatu kata kunci, ia menghantar satu permintaan HTTP POST ke URL endpoint yang anda daftarkan. Badan permintaan ialah JSON dan mengandungi keseluruhan artikel. Endpoint anda mencipta siaran di pihak anda dan membalas dengan URL awam akhir artikel tersebut. Orova menyimpan URL itu sebagai pautan yang diterbitkan, sama seperti siaran WordPress. Gunakan ini apabila anda mahukan penerbitan tanpa campur tangan di laman yang bukan WordPress, atau apabila anda lebih suka mengawal sepenuhnya cara siaran disimpan.

/ Persediaan

Persediaan — enam langkah

  • 1

    Hidupkan “Sambung melalui API” dalam projek anda. Buka projek anda (Projek → projek anda → Sambungan). Di sebelah kad WordPress terdapat kad Sambung melalui API. Masukkan URL endpoint anda di situ. Medan kunci rahsia adalah pilihan: biarkan kosong dan Orova akan menjananya, atau tampal kunci yang sudah digunakan oleh laman anda. Setelah disimpan, kunci dipaparkan pada kad dengan butang Salin. Satu projek hanya menerbitkan ke satu sasaran — menyambung melalui API mengunci butang WordPress, dan sebaliknya.

  • 2

    Bina endpoint yang menerima POST. Di laman atau pelayan anda sendiri, cipta laluan yang mendengar HTTP POST dengan badan JSON. Inilah URL yang anda tampal semula ke skrin Sambungan projek. Orova memanggilnya sekali bagi setiap artikel yang disiapkan.

  • 3

    Sahkan token Bearer pada setiap permintaan. Setiap permintaan daripada Orova membawa pengepala Authorization dalam bentuk “Bearer <secret>”, menggunakan kunci rahsia tepat yang dipaparkan dalam projek anda. Bandingkan dengan salinan yang anda simpan dan tolak apa-apa yang tidak sepadan — inilah yang menghalang orang lain daripada menyiarkan ke laman anda.

  • 4

    Cipta artikel daripada payload. Baca badan JSON dan cipta siaran dalam CMS atau pangkalan data anda: gunakan title, slug dan content_html untuk artikel itu sendiri, excerpt sebagai meta description, featured_image_url sebagai imej muka depan, dan keyword / lang / published_at sebagai metadata.

  • 5

    Kembalikan URL langsung artikel. Balas dengan HTTP 200 atau 201 dan badan JSON { "url": "https://yoursite.com/the-new-article" }. Orova menyimpan URL itu sebagai pautan yang diterbitkan untuk artikel tersebut. Jika anda mengembalikan status bukan 2xx, atau meninggalkan medan url, Orova menganggap penerbitan gagal.

  • 6

    Kendalikan permintaan kemas kini daripada enjin Optimumkan. Apabila badan membawa action = "update", cari siaran sedia ada melalui target_url (atau slug) dan tulis ganti title, content_html dan excerpt di tempatnya — kekalkan URL tidak berubah. Balas 200 dengan { "url": ... } yang menunjuk ke siaran yang sama. Endpoint yang belum mengendalikan ini hanya akan menyebabkan Orova melaporkan pengoptimuman sebagai gagal; penerbitan artikel baharu terus berfungsi seperti biasa.

/ Kontrak

Permintaan yang dihantar Orova

Setiap artikel siap tiba sebagai satu permintaan berbentuk seperti ini:

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.

/ Rujukan

Medan payload

Medan bagi permintaan terbit (artikel baharu). Permintaan padam dan kemas kini menggunakan semula endpoint yang sama dengan badan lebih kecil yang ditunjukkan dalam bahagian masing-masing di bawah.

MedanJenisPenerangan
titlestringTajuk artikel.
slugstringURL-friendly identifier suggested for the post.
content_htmlstringBadan penuh artikel sebagai HTML sedia diterbitkan.
excerptstringRingkasan pendek, bertujuan untuk meta description.
featured_image_urlstring | nullURL imej muka depan, atau null jika tiada.
keywordstringKata kunci SEO sasaran yang menjadi asas penulisan artikel.
langstringLanguage as an ISO code, e.g. "en" or "vi".
published_atstringSaat Orova menghantar artikel, ISO 8601 dalam UTC, tanpa akhiran zon waktu.
statusstring | undefinedPilihan. "draft" mengekalkan artikel tidak diterbitkan; "publish", atau tiada nilai langsung, menerbitkannya.
actionstring | undefinedTiada untuk artikel baharu. "delete" meminta anda membuang siaran; "update" (dihantar oleh enjin Optimumkan) meminta anda menulis ganti siaran sedia ada di tempatnya.
target_urlstringHanya dengan action = "update": URL langsung siaran yang hendak ditulis ganti. Kekalkan URL tidak berubah.
idnumber | undefinedOnly sent when Orova knows your post id (from `list` or `get`): match on it before `target_url`.

Medan pilihan ditinggalkan sepenuhnya daripada badan apabila tiada apa-apa untuk dihantar — Orova tidak pernah menghantar null, jadi bacanya dengan berhati-hati. status juga pilihan: "draft" mencipta artikel sebagai draf, manakala "publish" atau tiada status langsung menerbitkannya serta-merta.

/ Kontrak

Respons yang mesti anda kembalikan

Setelah anda mencipta siaran, balas dengan status HTTP 200 atau 201 dan badan JSON ini:

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

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

Orova menyimpan url itu sebagai pautan yang diterbitkan untuk artikel. Jika endpoint anda mengembalikan sebarang status bukan 2xx, atau badan yang bukan objek JSON dengan medan url, Orova menganggap penerbitan gagal dan menandakan artikel sewajarnya supaya anda boleh mencuba semula. Balas dalam 60 saat — Orova menamatkan permintaan selepas itu (ini terpakai untuk terbit, kemas kini dan padam).

/ Contoh

Contoh kod

Endpoint penerima minimum dalam Node.js dengan Express. Langkah yang sama — sahkan token, bercabang mengikut action (update / delete), cipta atau ubah siaran, kembalikan URL — terpakai dalam mana-mana bahasa atau rangka kerja.

// 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. Required: list your articles (duplicate check, internal links). action = "list".
  if (req.body.action === "list") {
    const posts = await listPosts(req.body.limit || 500); // newest first, 500 max
    // each post: { id, title, slug, link, date, modified, status, author, excerpt? }
    return res.status(200).json({ ok: true, posts });
  }

  // 3. Required: read one article in full (used when optimizing it). 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);

/ Kontrak — kemas kini

Mengemas kini artikel (Optimumkan)

Enjin Optimumkan Orova menulis semula artikel yang sudah disiarkan — fakta dikemas kini, tajuk lebih baik, kandungan digabungkan — kemudian menulis ganti siaran sedia ada di tempatnya, mengekalkan URL tidak berubah. Permintaan dihantar ke URL endpoint yang sama, sebagai POST dengan token Bearer yang sama:

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.

Cari siaran melalui target_url (atau slug sebagai sandaran), gantikan tajuk, badan dan meta description, kekalkan URL seperti sedia ada, kemudian balas 200 dengan { "url": "<same post URL>" }. Sebarang status bukan 2xx, atau badan tanpa url, menyebabkan Orova menandakan pengoptimuman itu sebagai gagal (ia tidak pernah mencipta siaran pendua). Endpoint yang belum melaksanakan ini terus menerbitkan artikel baharu seperti biasa — pelaksanaan update hanya diperlukan apabila anda menggunakan skrin Optimumkan.

Sejak 29 Ogos, badan kemas kini juga boleh membawa id (padankan dengannya sebelum target_url), featured_image_url untuk imej muka depan baharu, dan status yang memegang "publish" atau "draft". Ketiga-tiganya pilihan; apabila kosong, ia digugurkan daripada badan, tidak pernah dihantar sebagai null.

/ Kontrak — Visual Editor

Membaca artikel (wajib)

Orova perlu membaca artikel yang sudah ada di laman anda — untuk mengelakkan topik pendua, menambah pautan dalaman dan mengoptimumkan siaran lama. Tambah dua tindakan lagi pada URL endpoint yang sama, dengan token Bearer yang sama: list mengembalikan artikel anda, get mengembalikan salah satu daripadanya secara penuh.

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": "...",
        "excerpt": "..."         # optional: short summary, 300 characters max
      }
    ]
  }
  # Required: Orova reads this list to avoid duplicate topics,
  # add internal links and analyze the whole site.
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 dihadkan pada 500 dan artikel terbaharu didahulukan. Untuk get, Orova menghantar id, target_url dan slug bersama — padankan dengan mana-mana yang anda kenali, mengikut urutan itu. Jika tiada yang sepadan, balas 404 dengan { "ok": false, "err": "not_found" }. excerpt dalam list adalah pilihan (maksimum 300 aksara). Wajib: Orova memanggil list untuk mengetahui artikel yang sudah wujud (untuk mengelakkan pendua, menambah pautan dalaman dan menganalisis seluruh laman) dan memanggil get untuk membaca artikel semasa mengoptimumkannya. Jika laman anda tidak menjawab dua tindakan ini, artikel baharu tidak mendapat pautan dalaman dan artikel lama tidak dapat dioptimumkan.

/ Kontrak — Visual Editor

Memuat naik imej (Visual Editor)

Apabila anda meletakkan imej ke dalam Visual Editor, Orova menghantarnya ke URL endpoint yang sama sebagai base64 di dalam badan JSON — tiada multipart, tiada URL kedua untuk dilindungi.

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
  }

Terima image/* sahaja dan tolak apa-apa melebihi 15 MB. Simpan fail, kemudian balas dengan URL mutlak imej: laluan relatif merosakkan artikel sebaik sahaja ia dipaparkan di tempat lain. Tindakan ini hanya diperlukan untuk Visual Editor.

/ Kontrak — padam

Memadam artikel

Apabila pengguna membuang artikel yang telah diterbitkan dalam Orova (dari skrin Tulis atau Laporan), Orova meminta laman anda memadamnya juga. Permintaan dihantar ke URL endpoint yang sama, sebagai POST dengan token Bearer yang sama — satu-satunya perbezaan ialah badannya:

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
  }

Cari siaran melalui url (atau slug sebagai sandaran), buang atau nyahterbitkannya, kemudian balas dengan 200 dan { "deleted": true } — 204 kosong juga berfungsi. Jika siaran sudah tidak wujud, balas 404 dan Orova menganggapnya sudah dipadam. Mengembalikan { "deleted": false } memberitahu Orova bahawa siaran tidak dibuang. Permintaan terbit biasa tidak pernah mengandungi medan action, jadi endpoint sedia ada terus berfungsi tanpa perubahan — pelaksanaan padam adalah pilihan tetapi disyorkan.

/ Keselamatan

Nota keselamatan

  • Sentiasa sahkan token Bearer. Endpoint anda ialah URL awam — sesiapa sahaja boleh memanggilnya. Pengepala Authorization ialah satu-satunya perkara yang membuktikan permintaan benar-benar datang daripada Orova, jadi tolak setiap permintaan yang tokennya tidak sepadan tepat dengan rahsia yang anda simpan.
  • Sediakan endpoint melalui HTTPS. Rahsia dihantar dalam pengepala. HTTPS menghalangnya daripada dibaca semasa dalam transit.
  • Simpan rahsia di luar kod anda. Simpannya dalam pemboleh ubah persekitaran atau pengurus rahsia, jangan sekali-kali dikodkan terus dalam fail yang anda commit. Jika ia terdedah, jana yang baharu daripada Projek → Sambungan.
  • Anggap content_html sebagai kandungan, bukan markup yang dipercayai. Simpan dan paparkannya dengan berhati-hati, sama seperti mana-mana badan artikel dalam CMS anda.

/ Penutup

Itulah keseluruhan kontraknya

Dengan endpoint yang aktif dan didaftarkan dalam projek anda, Orova menerbitkan ke laman anda secara automatik — sama seperti untuk pengguna WordPress. Setiap artikel yang disiapkan tiba di pelayan anda, menjadi siaran, dan URL awamnya dihantar kembali ke Laporan dan Analisis supaya anda boleh menjejak prestasinya.

← Kembali ke pustaka panduan

/ Perlukan bantuan?

Buntu menyambungkan endpoint anda? Buka bahagian Sokongan dalam ruang kerja anda, atau hantar mesej kepada kami.