OROVA.VN — BIZ AI AGENT

/ Водич за програмере

Објављивање преко API-ја на било ком сајту.

Како да направите сопствени endpoint како би Orova објављивала готове чланке на сајт који не ради на WordPress-у.

Orova готове чланке објављује на WordPress одмах по повезивању. Ако ваш сајт није WordPress — прилагођени CMS, headless решење, статички сајт или ваш сопствени backend — и даље можете омогућити да Orova аутоматски објављује тако што ћете примати чланке преко малог API-ја. Ви пишете један HTTP endpoint; Orova га позива сваки пут када заврши чланак. Овај водич је намењен програмеру и описује комплетан уговор захтева.

/ Преглед

Како функционише

Када Orova заврши писање чланка за кључну реч, шаље један HTTP POST захтев на URL endpoint-а који сте регистровали. Тело захтева је JSON и садржи цео чланак. Ваш endpoint прави објаву код вас и одговара коначним, јавним URL-ом чланка. Orova чува тај URL као објављени линк, исто као за WordPress објаву. Користите ово када желите објављивање без ручног рада на сајту који није WordPress или када желите пуну контролу над начином чувања објава.

/ Подешавање

Подешавање — шест корака

  • 1

    Укључите „Повезивање преко API-ја“ у свом пројекту. Отворите пројекат (Пројекти → ваш пројекат → Везе). Поред WordPress картице налази се картица Повезивање преко API-ја. Ту унесите URL свог endpoint-а. Поље тајног кључа није обавезно: оставите га празним и Orova ће га генерисати, или налепите кључ који ваш сајт већ користи. Након чувања кључ се приказује на картици са дугметом Копирај. Пројекат објављује само на једно одредиште — повезивање преко API-ја закључава WordPress дугме, и обрнуто.

  • 2

    Направите endpoint који прихвата POST. На свом сајту или серверу направите руту која слуша HTTP POST са JSON телом. То је URL који налепите на екран Везе пројекта. Orova га позива једном за сваки завршени чланак.

  • 3

    Проверите Bearer токен на сваком захтеву. Сваки захтев из Orova носи заглавље Authorization у облику „Bearer <secret>“, са тачно оним тајним кључем приказаним у пројекту. Упоредите га са својом сачуваном копијом и одбијте све што се не поклапа — то спречава било кога другог да објављује на вашем сајту.

  • 4

    Направите чланак из payload-а. Прочитајте JSON тело и направите објаву у свом CMS-у или бази: користите title, slug и content_html за сам чланак, excerpt као meta опис, 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 опис.
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-а — важе у било ком језику или framework-у.

// 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);

/ Уговор — ажурирање

Ажурирање чланака (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 опис, оставите URL какав јесте, па одговорите 200 са { "url": "<same post URL>" }. Сваки статус који није 2xx или тело без url доводи до тога да Orova означи ту оптимизацију као неуспелу (никада не прави дуплу објаву). Endpoint-и који ово још нису имплементирали настављају да објављују нове чланке без промене — имплементација ажурирања потребна је тек када користите екран Optimize.

Од 29. августа тело ажурирања може садржати и id (поклапајте по њему пре target_url), featured_image_url за нову насловну слику и status са вредношћу "publish" или "draft". Сва три су опциона; када су празна, изостављају се из тела и никада се не шаљу као null.

/ Уговор — Визуелни уређивач

Читање чланака (обавезно)

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)

Када убаците слику у Визуелни уређивач, 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-ом слике: релативна путања квари чланак чим се прикаже негде другде. Ова акција је потребна само за Визуелни уређивач.

/ Уговор — брисање

Брисање чланака

Када корисник уклони објављени чланак у Orova (са екрана Пиши или Извештаји), 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 спречава да буде прочитан током преноса.
  • Држите тајни кључ ван кода. Чувајте га у променљивој окружења или менаџеру тајни, никада уписаног у фајл који комитујете. Ако икада процури, генеришите нови у Пројекат → Везе.
  • Третирајте content_html као садржај, не као поуздан код. Чувајте га и приказујте једнако пажљиво као и свако тело чланка у свом CMS-у.

/ Закључак

То је цео уговор

Када је endpoint активан и регистрован у пројекту, Orova аутоматски објављује на ваш сајт — баш као за кориснике WordPress-а. Сваки чланак који заврши стиже на ваш сервер, постаје објава, а његов јавни URL се враћа у Извештаје и Анализу да бисте пратили како пролази.

← Назад на библиотеку водича

/ Треба вам помоћ?

Запели сте при повезивању endpoint-а? Отворите одељак Подршка у свом радном простору или пошаљите нам поруку.