/ Посібник для 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 із меншими тілами, показаними у відповідних розділах нижче.
| Поле | Тип | Опис |
|---|---|---|
| title | string | Заголовок статті. |
| slug | string | URL-friendly identifier suggested for the post. |
| content_html | string | Повний текст статті у вигляді HTML, готового до публікації. |
| excerpt | string | Короткий підсумок, призначений для meta description. |
| featured_image_url | string | null | URL-адреса обкладинки або null, якщо її немає. |
| keyword | string | Цільове SEO-ключове слово, для якого написано статтю. |
| lang | string | Language as an ISO code, e.g. "en" or "vi". |
| published_at | string | Момент, коли Orova надіслала статтю, у форматі ISO 8601 в UTC, без суфікса часового поясу. |
| status | string | undefined | Необов’язкове. "draft" залишає статтю неопублікованою; "publish" або відсутність значення — публікує її. |
| action | string | undefined | Відсутнє для нових статей. "delete" просить видалити публікацію; "update" (надсилає рушій Optimize) просить перезаписати наявну публікацію на місці. |
| target_url | string | Лише з action = "update": робоча URL-адреса публікації, яку треба перезаписати. URL-адресу не змінюйте. |
| id | number | undefined | Only 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? Відкрийте розділ «Підтримка» у своєму робочому просторі або надішліть нам повідомлення.
