OROVA.VN — BIZ AI AGENT

/ Panduan untuk developer

Menerbitkan lewat API ke situs mana pun.

Cara membangun endpoint Anda sendiri agar Orova bisa menerbitkan artikel ke situs yang tidak memakai WordPress.

Orova sudah bisa menerbitkan artikel ke WordPress sejak awal. Jika situs Anda bukan WordPress — CMS buatan sendiri, sistem headless, situs statis, atau backend sendiri — Anda tetap bisa membiarkan Orova menerbitkan otomatis dengan menerima artikel lewat API kecil. Anda menulis satu endpoint HTTP; setiap kali selesai menulis artikel, Orova memanggilnya. Halaman ini ditujukan untuk developer dan menjelaskan kontrak request secara lengkap.

/ Ringkasan

Cara kerja

Setelah selesai menulis artikel untuk sebuah kata kunci, Orova mengirim tepat satu request HTTP POST ke URL endpoint yang Anda daftarkan. Body request berupa JSON dan berisi artikel secara utuh. Endpoint Anda membuat artikel di sisi Anda, lalu mengembalikan URL publik final artikel tersebut. Orova menyimpan URL itu sebagai tautan terbit, sama seperti artikel WordPress. Gunakan cara ini jika Anda ingin penerbitan otomatis di situs yang bukan WordPress, atau ingin mengatur sendiri cara artikel disimpan.

/ Pengaturan

Pengaturan — enam langkah

  • 1

    Aktifkan “Hubungkan melalui API” di proyek. Buka proyek Anda (Proyek → proyek Anda → Koneksi). Di samping kartu WordPress ada kartu Hubungkan melalui API. Masukkan URL endpoint Anda di sana. Kolom kunci rahasia bersifat opsional: biarkan kosong agar Orova membuat kunci, atau tempel kunci yang sudah dipakai situs Anda. Setelah disimpan, kunci langsung tampil di kartu beserta tombol Salin. Setiap proyek hanya menerbitkan ke satu tujuan — terhubung melalui API akan mengunci tombol WordPress, dan sebaliknya.

  • 2

    Bangun endpoint yang menerima POST. Di situs atau server Anda, buat route yang mendengarkan HTTP POST dengan body JSON. Inilah URL yang Anda tempel kembali di layar Koneksi proyek. Setiap artikel selesai, Orova memanggilnya tepat satu kali.

  • 3

    Periksa Bearer token di setiap request. Setiap request dari Orova membawa header Authorization berbentuk “Bearer <secret>”, sesuai kunci rahasia yang tampil di proyek. Bandingkan dengan salinan yang Anda simpan dan tolak setiap request yang tidak cocok — inilah satu-satunya hal yang mencegah orang lain menerbitkan artikel ke situs Anda.

  • 4

    Buat artikel dari payload. Baca body JSON lalu buat artikel di CMS atau database Anda: gunakan title, slug dan content_html untuk isi artikel, excerpt sebagai meta description, featured_image_url sebagai gambar sampul, sedangkan keyword / lang / published_at adalah metadata.

  • 5

    Kembalikan URL asli artikel. Balas dengan HTTP 200 atau 201 beserta body JSON { "url": "https://situs-anda.com/artikel-baru" }. Orova menyimpan URL itu sebagai tautan terbit artikel. Jika Anda mengembalikan status di luar 2xx, atau tidak menyertakan field url, Orova menganggap penerbitan itu gagal.

  • 6

    Tangani perintah pembaruan dari mesin Optimalkan. Jika body request berisi action = "update", cari artikel lama berdasarkan target_url (atau slug) lalu timpa title, content_html dan excerpt di tempat — URL tetap sama. Balas 200 dengan { "url": ... } yang mengarah ke artikel yang sama. Endpoint yang belum menangani bagian ini hanya membuat Orova melaporkan optimasi gagal; penerbitan artikel baru tetap berjalan seperti biasa.

/ Kontrak

Request yang dikirim Orova

Setiap artikel yang selesai akan tiba sebagai satu request 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.

/ Referensi

Field dalam payload

Field dari request penerbitan artikel baru. Perintah hapus dan perintah pembaruan memakai endpoint yang sama dengan body yang lebih ringkas — lihat bagian masing-masing di bawah.

FieldTipeDeskripsi
titlestringJudul artikel.
slugstringURL-friendly identifier suggested for the post.
content_htmlstringSeluruh isi artikel dalam bentuk HTML yang siap terbit.
excerptstringRingkasan singkat, dipakai sebagai meta description.
featured_image_urlstring | nullURL gambar sampul, atau null jika artikel tidak punya gambar.
keywordstringKata kunci SEO yang menjadi target artikel.
langstringLanguage as an ISO code, e.g. "en" or "vi".
published_atstringWaktu Orova mengirim artikel, format ISO 8601 dalam UTC, tanpa akhiran zona waktu.
statusstring | undefinedOpsional. "draft" menyimpan artikel sebagai draf; "publish" atau tanpa nilai akan langsung menerbitkannya.
actionstring | undefinedTidak ada pada artikel baru. "delete" meminta Anda menghapus artikel; "update" (dikirim mesin Optimalkan) meminta Anda menimpa artikel yang sudah ada di tempat.
target_urlstringHanya bersama action = "update": URL asli artikel yang akan ditimpa. Pertahankan URL ini.
idnumber | undefinedOnly sent when Orova knows your post id (from `list` or `get`): match on it before `target_url`.

Field opsional yang tidak berisi apa pun akan dihilangkan sepenuhnya dari body — Orova tidak pernah mengirim null, jadi baca setiap field opsional secara defensif. status juga opsional: "draft" membuat artikel sebagai draf, sedangkan "publish" atau tanpa status langsung menerbitkannya.

/ Kontrak

Response yang harus Anda kembalikan

Setelah artikel dibuat, balas dengan status HTTP 200 atau 201 dan body JSON ini:

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

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

Orova menyimpan url tersebut sebagai tautan terbit artikel. Jika endpoint Anda mengembalikan status di luar 2xx, atau body response bukan objek JSON dengan field url, Orova menganggap penerbitan gagal dan menandai artikel agar Anda bisa mengulanginya. Balas dalam 60 detik — setelah itu Orova memutus request (berlaku untuk terbit, pembaruan maupun hapus).

/ Contoh

Contoh kode

Endpoint penerima minimal dengan Node.js dan Express. Langkahnya sama di bahasa atau framework apa pun: periksa token, bercabang berdasarkan action (update / delete), buat atau ubah artikel, lalu kembalikan 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. 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 — pembaruan

Memperbarui artikel (Optimalkan)

Mesin Optimalkan Orova menulis ulang artikel yang sudah tayang — memperbarui fakta, memperbaiki judul, menggabungkan konten — lalu menimpa artikel lama di tempat, URL tetap sama. Request dikirim ke URL endpoint yang sama, juga berupa POST dengan Bearer token 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 artikel berdasarkan target_url (jika tidak ketemu, cari berdasarkan slug), ganti judul, isi artikel dan meta description, pertahankan URL, lalu balas 200 dengan { "url": "<same post URL>" }. Status di luar 2xx, atau body response tanpa url, membuat Orova menandai optimasi itu gagal (Orova tidak pernah membuat artikel duplikat). Endpoint yang belum menerapkan bagian ini tetap menerbitkan artikel baru seperti biasa — Anda baru perlu menambahkannya saat memakai layar Optimalkan.

Sejak 29 Agustus, body perintah pembaruan bisa menyertakan id (dicocokkan lebih dulu sebelum target_url), featured_image_url untuk mengganti gambar sampul, dan status berisi "publish" atau "draft". Ketiganya opsional; jika kosong, kuncinya dihilangkan dari body, tidak pernah dikirim sebagai null.

/ Kontrak — Visual Editor

Membaca artikel (wajib)

Orova perlu membaca artikel yang sudah ada di situs Anda — agar tidak menulis topik ganda, menyisipkan tautan internal dan mengoptimalkan artikel lama. Tambahkan dua action di URL endpoint yang sama, dengan Bearer token yang sama: list mengembalikan daftar artikel, get mengembalikan satu artikel secara utuh.

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 maksimal 500, artikel terbaru di urutan pertama. Untuk get, Orova mengirim id, target_url dan slug sekaligus — cocokkan dengan yang Anda kenali, sesuai urutan itu. Jika artikel tidak ditemukan, balas 404 dengan { "ok": false, "err": "not_found" }. excerpt dalam list bersifat opsional (maksimal 300 karakter). Wajib: Orova memanggil list untuk mengetahui artikel yang sudah ada (menghindari duplikat, menyisipkan tautan internal, menganalisis seluruh situs) dan memanggil get untuk membaca isi artikel saat mengoptimalkannya. Jika situs Anda tidak menjawab kedua action ini, artikel baru tidak akan punya tautan internal dan artikel lama tidak bisa dioptimalkan.

/ Kontrak — Visual Editor

Mengunggah gambar (Visual Editor)

Saat Anda meletakkan gambar ke Visual Editor, Orova mengirimnya ke URL endpoint yang sama sebagai base64 di dalam body JSON — tanpa multipart, tanpa URL kedua yang perlu diamankan.

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
  }

Hanya terima image/* dan tolak file di atas 15 MB. Simpan file, lalu balas dengan URL absolut gambar: path relatif akan rusak begitu artikel ditampilkan di tempat lain. Action ini hanya diperlukan untuk Visual Editor.

/ Kontrak — hapus

Menghapus artikel

Saat pengguna menghapus artikel yang sudah terbit langsung di Orova (dari layar Tulis atau Laporan), Orova meminta situs Anda menghapus artikel itu juga. Request dikirim ke URL endpoint yang sama, juga berupa POST dengan Bearer token yang sama — hanya body-nya yang berbeda:

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 artikel berdasarkan url (jika tidak ketemu, cari berdasarkan slug), hapus atau batalkan terbitnya, lalu balas 200 dengan { "deleted": true } — balasan kosong 204 juga boleh. Jika artikel sudah tidak ada, balas 404 dan Orova menganggapnya sudah terhapus. Membalas { "deleted": false } berarti Anda melaporkan artikel belum dihapus. Request penerbitan biasa tidak pernah memiliki field action, jadi endpoint lama tetap berjalan seperti biasa — menerapkan bagian hapus bersifat opsional, tetapi disarankan.

/ Keamanan

Catatan keamanan

  • Selalu periksa Bearer token. Endpoint Anda adalah URL publik — siapa pun bisa memanggilnya. Header Authorization adalah satu-satunya bukti bahwa request benar-benar berasal dari Orova, jadi tolak setiap request yang token-nya tidak sama persis dengan kunci yang Anda simpan.
  • Jalankan endpoint di HTTPS. Kunci rahasia dikirim di header. HTTPS menjaganya agar tidak terbaca selama transmisi.
  • Jangan simpan kunci di dalam kode. Simpan di variabel lingkungan atau secrets manager, jangan ditulis langsung di file lalu di-commit. Jika sampai bocor, buat kunci baru di Proyek → Koneksi.
  • Perlakukan content_html sebagai konten, bukan markup tepercaya. Simpan dan tampilkan dengan hati-hati, sama seperti isi artikel lain di CMS Anda.

/ Penutup

Itulah seluruh kontraknya

Begitu endpoint berjalan dan terdaftar di proyek, Orova akan menerbitkan artikel ke situs Anda secara otomatis — persis seperti untuk pengguna WordPress. Setiap artikel yang selesai masuk ke server Anda, menjadi postingan, dan URL publiknya mengalir kembali ke Laporan dan Analisis agar Anda bisa memantau performanya.

← Kembali ke pustaka panduan

/ Butuh bantuan?

Kesulitan menghubungkan endpoint? Buka bagian Dukungan di ruang kerja Anda, atau kirim pesan kepada kami.