OROVA.VN — BIZ AI AGENT

/ Посібник для Developers

Publishing via API на будь-якому сайті.

Як створити власний endpoint, щоб Orova могла публікувати готові статті на сайті, який не працює на WordPress.

Orova з коробки публікує готові статті у WordPress. Якщо ваш сайт не на WordPress — власна CMS, headless-рішення, статичний сайт чи ваш власний бекенд, — ви все одно можете дозволити Orova публікувати автоматично, приймаючи статті через невеликий API. Ви пишете один HTTP endpoint; Orova викликає його щоразу, коли завершує статтю. Цей посібник призначено для розробника, і він описує повний контракт запиту.

/ Огляд

Як це працює

Коли Orova завершує статтю для ключового слова, вона надсилає один HTTP-запит POST на зареєстровану вами URL-адресу endpoint. Тіло запиту — JSON, і воно містить усю статтю. Ваш endpoint створює публікацію на вашому боці й відповідає остаточною публічною URL-адресою статті. Orova зберігає цю URL-адресу як посилання на публікацію — так само, як для публікації у WordPress. Використовуйте це, коли хочете публікувати без ручної роботи на сайті не на WordPress або коли волієте повністю контролювати, як зберігаються публікації.

/ Налаштування

Налаштування — шість кроків

  • 1

    Увімкніть «Connect via API» у своєму проєкті. Відкрийте свій проєкт (Projects → ваш проєкт → Connections). Поруч із карткою WordPress є картка Connect via API. Введіть там URL-адресу свого endpoint. Поле секретного ключа необов’язкове: залиште його порожнім, і Orova згенерує ключ, або вставте ключ, який ваш сайт уже використовує. Після збереження ключ показується на картці з кнопкою копіювання. Проєкт публікує лише в одне місце: підключення через API блокує кнопку WordPress, і навпаки.

  • 2

    Створіть endpoint, що приймає POST. На своєму сайті чи сервері створіть маршрут, який слухає HTTP POST із тілом JSON. Саме цю URL-адресу ви вставляєте на екрані Connections проєкту. Orova викликає її один раз для кожної завершеної статті.

  • 3

    Перевіряйте Bearer-токен у кожному запиті. Кожен запит від Orova містить заголовок 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

    Обробляйте запити на оновлення від рушія Optimize. Коли тіло містить action = "update", знайдіть наявну публікацію за target_url (або slug) і перезапишіть її title, content_html та excerpt на місці — URL-адресу не змінюйте. Відповідайте 200 із { "url": ... }, що вказує на ту саму публікацію. Якщо endpoint ще цього не обробляє, Orova просто повідомить про невдалу оптимізацію; публікація нових статей працюватиме, як і раніше.

/ Контракт

Запит, який надсилає Orova

Кожна завершена стаття надходить одним запитом такого вигляду:

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

Поля запиту на публікацію (нова стаття). Запити на видалення та оновлення використовують той самий endpoint із меншими тілами, показаними у відповідних розділах нижче.

ПолеТипОпис
titlestringЗаголовок статті.
slugstringURL-friendly identifier suggested for the post.
content_htmlstringПовний текст статті у вигляді HTML, готового до публікації.
excerptstringКороткий підсумок, призначений для meta description.
featured_image_urlstring | nullURL-адреса обкладинки або null, якщо її немає.
keywordstringЦільове SEO-ключове слово, для якого написано статтю.
langstringLanguage as an ISO code, e.g. "en" or "vi".
published_atstringМомент, коли Orova надіслала статтю, у форматі ISO 8601 в UTC, без суфікса часового поясу.
statusstring | undefinedНеобов’язкове. "draft" залишає статтю неопублікованою; "publish" або відсутність значення — публікує її.
actionstring | undefinedВідсутнє для нових статей. "delete" просить видалити публікацію; "update" (надсилає рушій Optimize) просить перезаписати наявну публікацію на місці.
target_urlstringЛише з action = "update": робоча URL-адреса публікації, яку треба перезаписати. URL-адресу не змінюйте.
idnumber | undefinedOnly sent when Orova knows your post id (from `list` or `get`): match on it before `target_url`.

Необов’язкові поля повністю пропускаються в тілі, коли надсилати нічого, — Orova ніколи не надсилає null, тож читайте їх обережно. status теж необов’язковий: "draft" створює статтю як чернетку, а "publish" або відсутність status публікує її одразу.

/ Контракт

Відповідь, яку ви маєте повернути

Створивши публікацію, відповідайте статусом 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 перериває запит за тайм-аутом (це стосується публікації, оновлення й видалення однаково).

/ Приклад

Приклад коду

Мінімальний endpoint для приймання на Node.js з Express. Ті самі кроки — перевірити токен, розгалузити за 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. 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);

/ Контракт — update

Оновлення статей (Optimize)

Рушій Optimize в Orova переписує вже опубліковані статті — оновлює факти, покращує заголовки, об’єднує контент — і потім перезаписує наявну публікацію на місці, не змінюючи URL-адресу. Запит надходить на ту саму URL-адресу endpoint як POST із тим самим Bearer-токеном:

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": "<same post URL>" }. Будь-який статус, відмінний від 2xx, або тіло без url змушує Orova позначити цю оптимізацію як невдалу (дубльована публікація ніколи не створюється). Endpoint, які ще цього не реалізували, і далі публікують нові статті без змін — реалізувати update потрібно лише тоді, коли ви користуєтеся екраном Optimize.

З 29 серпня тіло оновлення може також містити id (зіставляйте за ним перед target_url), featured_image_url для нової обкладинки та status зі значенням "publish" або "draft". Усі три поля необов’язкові; якщо вони порожні, їх вилучають із тіла й ніколи не надсилають як null.

/ Контракт — Visual Editor

Читання статей (обов’язково)

Orova потрібно читати статті, які вже є на вашому сайті, — щоб уникати дубльованих тем, додавати внутрішні посилання й оптимізувати старіші публікації. Додайте ще дві дії на тій самій URL-адресі endpoint із тим самим Bearer-токеном: 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": "...",
        "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 обмежено 500, найновіша стаття йде першою. Для get Orova надсилає id, target_url і slug разом — зіставляйте за тим, яке розпізнаєте, саме в такому порядку. Якщо нічого не збігається, відповідайте 404 із { "ok": false, "err": "not_found" }. excerpt у list необов’язковий (до 300 символів). Обов’язково: Orova викликає list, щоб дізнатися, які статті вже існують (щоб уникати дублів, додавати внутрішні посилання й аналізувати весь сайт), і викликає get, щоб прочитати статтю під час її оптимізації. Якщо ваш сайт не відповідає на ці дві дії, нові статті не отримають внутрішніх посилань, а старіші не можна буде оптимізувати.

/ Контракт — Visual Editor

Завантаження зображень (Visual Editor)

Коли ви перетягуєте зображення у Visual Editor, Orova надсилає його на ту саму URL-адресу endpoint у форматі 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-адресою зображення: відносний шлях зламає статтю, щойно її буде показано деінде. Ця дія потрібна лише для Visual Editor.

/ Контракт — delete

Видалення статей

Коли користувач видаляє опубліковану статтю в Orova (з екрана Write або Reports), Orova просить ваш сайт також її видалити. Запит надходить на ту саму URL-адресу endpoint як POST із тим самим Bearer-токеном — відрізняється лише тіло:

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, що публікацію не видалено. Звичайні запити на публікацію ніколи не містять поля action, тож наявні endpoint і далі працюють без змін — реалізація видалення необов’язкова, але рекомендована.

/ Безпека

Поради з безпеки

  • Завжди перевіряйте Bearer-токен. Ваш endpoint — публічна URL-адреса, будь-хто може його викликати. Заголовок Authorization — єдине, що доводить, що запит справді надійшов від Orova, тож відхиляйте кожен запит, токен якого не збігається точно з вашим збереженим секретом.
  • Обслуговуйте endpoint через HTTPS. Секрет передається в заголовку. HTTPS не дає прочитати його під час передавання.
  • Не зберігайте секрет у коді. Зберігайте його у змінній середовища або менеджері секретів, ніколи не прописуйте у файлі, який комітите. Якщо він колись стане відомим стороннім, згенеруйте новий у Project → Connections.
  • Сприймайте content_html як контент, а не як довірену розмітку. Зберігайте та відображайте його так само обережно, як будь-який текст статті у своїй CMS.

/ Підсумок

Ось і весь контракт

Щойно endpoint запрацює й буде зареєстрований у вашому проєкті, Orova публікуватиме на ваш сайт автоматично — так само, як для користувачів WordPress. Кожна завершена стаття потрапляє на ваш сервер, стає публікацією, а її публічна URL-адреса повертається у Reports та Analysis, щоб ви могли відстежувати її результати.

← Назад до бібліотеки посібників

/ Потрібна допомога?

Не вдається підключити endpoint? Відкрийте розділ «Підтримка» у своєму робочому просторі або надішліть нам повідомлення.