OROVA.VN — BIZ AI AGENT

/ Návod pro vývojáře

Publikování přes API na jakýkoli web.

Jak vytvořit vlastní endpoint, aby Orova mohla publikovat hotové články na web, který neběží na WordPress.

Orova publikuje hotové články do WordPress hned po vybalení. Pokud váš web není na WordPress — vlastní CMS, headless řešení, statický web nebo vlastní backend — můžete Orova přesto nechat publikovat automaticky tím, že budete články přijímat přes malé API. Napíšete jeden HTTP endpoint; Orova ho zavolá pokaždé, když dokončí článek. Tento návod je určen vývojáři a popisuje celý kontrakt požadavku.

/ Přehled

Jak to funguje

Když Orova dopíše článek pro klíčové slovo, pošle jeden HTTP požadavek POST na URL endpointu, kterou jste zaregistrovali. Tělo požadavku je JSON a obsahuje celý článek. Váš endpoint vytvoří příspěvek u vás a odpoví finální veřejnou URL článku. Orova tuto URL uloží jako publikovaný odkaz, stejně jako u příspěvku ve WordPress. Použijte to, když chcete bezobslužné publikování na webu, který není na WordPress, nebo když chcete mít plnou kontrolu nad tím, jak se příspěvky ukládají.

/ Nastavení

Nastavení — šest kroků

  • 1

    Zapněte ve svém projektu „Připojit přes API“. Otevřete svůj projekt (Projekty → váš projekt → Připojení). Vedle karty WordPress je karta Připojit přes API. Zadejte do ní URL svého endpointu. Pole tajného klíče je nepovinné: nechte ho prázdné a Orova klíč vygeneruje, nebo vložte klíč, který už váš web používá. Po uložení se klíč zobrazí na kartě s tlačítkem Kopírovat. Projekt publikuje jen do jednoho cíle — připojení přes API uzamkne tlačítko WordPress a naopak.

  • 2

    Vytvořte endpoint, který přijímá POST. Na svém webu nebo serveru vytvořte route, která naslouchá HTTP POST s tělem JSON. Tuto URL pak vložíte zpět na obrazovku Připojení projektu. Orova ji zavolá jednou pro každý dokončený článek.

  • 3

    Ověřujte Bearer token u každého požadavku. Každý požadavek od Orova nese hlavičku Authorization ve tvaru „Bearer <secret>“ s přesně tím tajným klíčem, který je zobrazen ve vašem projektu. Porovnejte ho s uloženou kopií a vše, co nesouhlasí, odmítněte — právě to brání komukoli jinému publikovat na váš web.

  • 4

    Vytvořte článek z payloadu. Přečtěte tělo JSON a vytvořte příspěvek ve svém CMS nebo databázi: title, slug a content_html použijte pro samotný článek, excerpt jako meta description, featured_image_url jako titulní obrázek a keyword / lang / published_at jako metadata.

  • 5

    Vraťte živou URL článku. Odpovězte HTTP 200 nebo 201 a tělem JSON { "url": "https://yoursite.com/the-new-article" }. Orova tuto URL uloží jako publikovaný odkaz článku. Pokud vrátíte jiný stav než 2xx nebo vynecháte pole url, Orova publikování považuje za neúspěšné.

  • 6

    Zpracujte požadavky na aktualizaci z enginu Optimize. Když tělo nese action = "update", najděte existující příspěvek podle target_url (nebo slug) a přepište na místě jeho title, content_html a excerpt — URL ponechte beze změny. Odpovězte 200 s { "url": ... } odkazujícím na stejný příspěvek. Endpointy, které to zatím neumí, jen způsobí, že Orova ohlásí optimalizaci jako neúspěšnou; publikování nových článků funguje dál jako dřív.

/ Kontrakt

Požadavek, který Orova posílá

Každý dokončený článek dorazí jako jeden požadavek v tomto tvaru:

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.

/ Reference

Pole payloadu

Pole požadavku na publikování (nový článek). Požadavky na smazání a aktualizaci používají stejný endpoint s menšími těly, popsanými v jejich vlastních částech níže.

PoleTypPopis
titlestringTitulek článku.
slugstringURL-friendly identifier suggested for the post.
content_htmlstringCelé tělo článku jako HTML připravené k publikování.
excerptstringKrátké shrnutí, určené pro meta description.
featured_image_urlstring | nullURL titulního obrázku, nebo null, pokud žádný není.
keywordstringCílové klíčové slovo SEO, pro které byl článek napsán.
langstringLanguage as an ISO code, e.g. "en" or "vi".
published_atstringOkamžik, kdy Orova článek odeslala, ISO 8601 v UTC, bez přípony časového pásma.
statusstring | undefinedNepovinné. "draft" ponechá článek nepublikovaný; "publish" nebo žádná hodnota ho publikuje.
actionstring | undefinedU nových článků chybí. "delete" žádá o odstranění příspěvku; "update" (posílá engine Optimize) žádá o přepsání existujícího příspěvku na místě.
target_urlstringJen s action = "update": živá URL příspěvku k přepsání. URL ponechte beze změny.
idnumber | undefinedOnly sent when Orova knows your post id (from `list` or `get`): match on it before `target_url`.

Nepovinná pole se z těla zcela vynechají, když není co poslat — Orova nikdy neposílá null, čtěte je proto obezřetně. status je také nepovinný: "draft" vytvoří článek jako koncept, zatímco "publish" nebo žádný status ho publikuje hned.

/ Kontrakt

Odpověď, kterou musíte vrátit

Jakmile příspěvek vytvoříte, odpovězte stavem HTTP 200 nebo 201 a tímto tělem JSON:

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

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

Orova tuto url uloží jako publikovaný odkaz článku. Pokud váš endpoint vrátí jakýkoli stav mimo 2xx nebo tělo, které není objektem JSON s polem url, Orova publikování považuje za neúspěšné a článek podle toho označí, abyste ho mohli zkusit znovu. Odpovězte do 60 sekund — poté Orova požadavek ukončí (platí pro publikování, aktualizaci i mazání).

/ Ukázka

Ukázka kódu

Minimální přijímací endpoint v Node.js s Express. Stejné kroky — ověřit token, rozvětvit podle action (update / delete), vytvořit nebo změnit příspěvek, vrátit URL — platí v jakémkoli jazyce či frameworku.

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

/ Kontrakt — aktualizace

Aktualizace článků (Optimize)

Engine Optimize v Orova přepisuje články, které už jsou živé — aktualizovaná fakta, lepší titulky, sloučený obsah — a pak existující příspěvek přepíše na místě, s nezměněnou URL. Požadavek jde na stejnou URL endpointu jako POST se stejným Bearer tokenem:

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.

Najděte příspěvek podle target_url (záložně podle slug), nahraďte jeho titulek, tělo a meta description, URL ponechte, jak je, a odpovězte 200 s { "url": "<same post URL>" }. Jakýkoli stav mimo 2xx nebo tělo bez url způsobí, že Orova optimalizaci označí jako neúspěšnou (nikdy nevytvoří duplicitní příspěvek). Endpointy, které to zatím neimplementovaly, dál publikují nové články beze změny — aktualizaci stačí implementovat, až začnete používat obrazovku Optimize.

Od 29. srpna může tělo aktualizace nést také id (párujte podle něj před target_url), featured_image_url pro nový titulní obrázek a status s hodnotou "publish" nebo "draft". Všechna tři jsou nepovinná; když jsou prázdná, z těla se vynechají a nikdy se neposílají jako null.

/ Kontrakt — Visual Editor

Čtení článků (povinné)

Orova potřebuje číst články, které už na vašem webu jsou — aby se vyhnula duplicitním tématům, přidala interní odkazy a optimalizovala starší příspěvky. Přidejte na stejnou URL endpointu se stejným Bearer tokenem další dvě akce: list vrací vaše články, get vrací jeden z nich celý.

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 je omezen na 500 a nejnovější článek je první. U get posílá Orova id, target_url a slug společně — párujte podle toho, co rozpoznáte, v tomto pořadí. Když nic nesedí, odpovězte 404 s { "ok": false, "err": "not_found" }. excerpt v list je nepovinný (max. 300 znaků). Povinné: Orova volá list, aby zjistila, které články už existují (kvůli duplicitám, interním odkazům a analýze celého webu), a get, aby článek přečetla při optimalizaci. Pokud váš web na tyto dvě akce neodpovídá, nové články nedostanou interní odkazy a starší články nelze optimalizovat.

/ Kontrakt — Visual Editor

Nahrávání obrázků (Visual Editor)

Když do Visual Editor přetáhnete obrázek, Orova ho pošle na stejnou URL endpointu jako base64 uvnitř těla JSON — žádný multipart, žádná druhá URL k zabezpečení.

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
  }

Přijímejte jen image/* a odmítněte vše nad 15 MB. Soubor uložte a odpovězte absolutní URL obrázku: relativní cesta článek rozbije, jakmile se zobrazí jinde. Tato akce je potřeba jen pro Visual Editor.

/ Kontrakt — mazání

Mazání článků

Když uživatel v Orova odstraní publikovaný článek (z obrazovky Psát nebo Přehledy), Orova požádá váš web, aby ho smazal také. Požadavek jde na stejnou URL endpointu jako POST se stejným Bearer tokenem — jediný rozdíl je tělo:

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
  }

Vyhledejte příspěvek podle url (záložně podle slug), odstraňte ho nebo zrušte jeho publikování a odpovězte 200 s { "deleted": true } — funguje i samotné 204. Pokud příspěvek už neexistuje, odpovězte 404 a Orova ho bude považovat za již smazaný. Vrácení { "deleted": false } Orova sdělí, že příspěvek odstraněn nebyl. Běžné požadavky na publikování nikdy neobsahují pole action, takže stávající endpointy fungují beze změny — implementace mazání je nepovinná, ale doporučená.

/ Zabezpečení

Bezpečnostní poznámky

  • Vždy ověřujte Bearer token. Váš endpoint je veřejná URL — zavolat ho může kdokoli. Hlavička Authorization je jediné, co dokazuje, že požadavek skutečně přišel od Orova, proto odmítněte každý požadavek, jehož token přesně neodpovídá uloženému tajnému klíči.
  • Provozujte endpoint přes HTTPS. Tajný klíč putuje v hlavičce. HTTPS zabrání jeho přečtení při přenosu.
  • Nemějte tajný klíč v kódu. Uložte ho do proměnné prostředí nebo správce tajných klíčů, nikdy natvrdo do souboru, který commitujete. Pokud by někdy unikl, vygenerujte nový v Projekt → Připojení.
  • Berte content_html jako obsah, ne jako důvěryhodné značkování. Ukládejte a vykreslujte ho se stejnou opatrností jako jakékoli tělo článku ve svém CMS.

/ Závěrem

To je celý kontrakt

Jakmile je endpoint živý a zaregistrovaný ve vašem projektu, Orova publikuje na váš web automaticky — přesně jako pro uživatele WordPress. Každý dokončený článek dorazí na váš server, stane se příspěvkem a jeho veřejná URL se vrátí do Přehledů a Analýzy, abyste mohli sledovat jeho výkon.

← Zpět do knihovny návodů

/ Potřebujete pomoc?

Nedaří se vám endpoint zapojit? Otevřete sekci Podpora ve svém pracovním prostoru, nebo nám pošlete zprávu.