OROVA.VN — BIZ AI AGENT

/ Kehittäjän opas

Julkaisu API:n kautta mille tahansa sivustolle.

Näin rakennat oman päätepisteen, jotta Orova voi julkaista valmiit artikkelit sivustolle, joka ei käytä WordPressiä.

Orova julkaisee valmiit artikkelit WordPressiin suoraan. Jos sivustosi ei ole WordPress — oma CMS, headless-ratkaisu, staattinen sivusto tai oma taustajärjestelmä — voit silti antaa Orovan julkaista automaattisesti vastaanottamalla artikkelit pienen API:n kautta. Kirjoitat yhden HTTP-päätepisteen; Orova kutsuu sitä aina, kun artikkeli valmistuu. Tämä opas on tarkoitettu kehittäjälle ja kuvaa koko pyyntösopimuksen.

/ Yleiskatsaus

Miten se toimii

Kun Orova on kirjoittanut artikkelin avainsanalle, se lähettää yhden HTTP POST -pyynnön rekisteröimääsi päätepisteen URL-osoitteeseen. Pyynnön runko on JSONia ja sisältää koko artikkelin. Päätepisteesi luo julkaisun omalla puolellasi ja vastaa artikkelin lopullisella, julkisella URL-osoitteella. Orova tallentaa tämän URL-osoitteen julkaistuksi linkiksi, aivan kuten WordPress-julkaisussa. Käytä tätä, kun haluat automaattisen julkaisun sivustolle, joka ei ole WordPress, tai kun haluat pitää täyden hallinnan siitä, miten julkaisut tallennetaan.

/ Käyttöönotto

Käyttöönotto — kuusi vaihetta

  • 1

    Ota ”Yhdistä API:n kautta” käyttöön projektissasi. Avaa projektisi (Projektit → projektisi → Yhteydet). WordPress-kortin vieressä on Yhdistä API:n kautta -kortti. Syötä siihen päätepisteesi URL-osoite. Salaisen avaimen kenttä on valinnainen: jätä se tyhjäksi, niin Orova luo avaimen, tai liitä sivustosi jo käyttämä avain. Tallennuksen jälkeen avain näkyy kortilla Kopioi-painikkeen kanssa. Projekti julkaisee vain yhteen kohteeseen — API-yhteys lukitsee WordPress-painikkeen ja päinvastoin.

  • 2

    Rakenna päätepiste, joka hyväksyy POSTin. Luo omalle sivustollesi tai palvelimellesi reitti, joka kuuntelee HTTP POST -pyyntöjä JSON-rungolla. Tämä on URL-osoite, jonka liität takaisin projektin Yhteydet-näkymään. Orova kutsuu sitä kerran jokaista valmistunutta artikkelia kohden.

  • 3

    Tarkista Bearer-tunnus jokaisessa pyynnössä. Jokainen Orovan pyyntö sisältää Authorization-otsakkeen muodossa ”Bearer <secret>”, ja se käyttää täsmälleen projektissasi näkyvää salaista avainta. Vertaa sitä tallentamaasi kopioon ja hylkää kaikki, mikä ei täsmää — näin estät muita julkaisemasta sivustollesi.

  • 4

    Luo artikkeli payloadista. Lue JSON-runko ja luo julkaisu CMS:ään tai tietokantaasi: käytä kenttiä title, slug ja content_html itse artikkeliin, excerpt meta-kuvauksena, featured_image_url kansikuvana ja keyword / lang / published_at metatietoina.

  • 5

    Palauta artikkelin julkinen URL. Vastaa HTTP 200 tai 201 ja JSON-rungolla { "url": "https://yoursite.com/the-new-article" }. Orova tallentaa tämän URL-osoitteen artikkelin julkaistuksi linkiksi. Jos palautat muun kuin 2xx-tilan tai jätät url-kentän pois, Orova katsoo julkaisun epäonnistuneen.

  • 6

    Käsittele Optimize-moottorin päivityspyynnöt. Kun runko sisältää action = "update", etsi olemassa oleva julkaisu target_url-kentän (tai slugin) perusteella ja korvaa sen title, content_html ja excerpt paikallaan — pidä URL ennallaan. Vastaa 200 ja { "url": ... }, joka osoittaa samaan julkaisuun. Jos päätepiste ei vielä käsittele tätä, Orova vain raportoi optimoinnin epäonnistuneen; uusien artikkelien julkaisu toimii kuten ennenkin.

/ Sopimus

Orovan lähettämä pyyntö

Jokainen valmis artikkeli saapuu yhtenä tämän muotoisena pyyntönä:

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.

/ Viite

Payload-kentät

Julkaisupyynnön (uusi artikkeli) kentät. Poisto- ja päivityspyynnöt käyttävät samaa päätepistettä pienemmillä rungoilla, jotka on esitetty omissa osioissaan alempana.

KenttäTyyppiKuvaus
titlestringArtikkelin otsikko.
slugstringURL-friendly identifier suggested for the post.
content_htmlstringKoko artikkelin runko julkaisuvalmiina HTML:nä.
excerptstringLyhyt tiivistelmä, tarkoitettu meta-kuvaukseksi.
featured_image_urlstring | nullKansikuvan URL tai null, jos kuvaa ei ole.
keywordstringSEO-avainsana, jolle artikkeli kirjoitettiin.
langstringLanguage as an ISO code, e.g. "en" or "vi".
published_atstringHetki, jolloin Orova lähetti artikkelin, ISO 8601 -muodossa UTC-ajassa ilman aikavyöhykepäätettä.
statusstring | undefinedValinnainen. "draft" pitää artikkelin julkaisemattomana; "publish" tai tyhjä arvo julkaisee sen.
actionstring | undefinedPuuttuu uusista artikkeleista. "delete" pyytää poistamaan julkaisun; "update" (Optimize-moottorin lähettämä) pyytää korvaamaan olemassa olevan julkaisun paikallaan.
target_urlstringVain kun action = "update": korvattavan julkaisun julkinen URL. Pidä URL ennallaan.
idnumber | undefinedOnly sent when Orova knows your post id (from `list` or `get`): match on it before `target_url`.

Valinnaiset kentät jätetään rungosta kokonaan pois, kun lähetettävää ei ole — Orova ei koskaan lähetä null-arvoa, joten lue ne varovasti. Myös status on valinnainen: "draft" luo artikkelin luonnoksena, kun taas "publish" tai puuttuva status julkaisee sen heti.

/ Sopimus

Vastaus, joka sinun on palautettava

Kun olet luonut julkaisun, vastaa HTTP-tilalla 200 tai 201 ja tällä JSON-rungolla:

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

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

Orova tallentaa tämän url-arvon artikkelin julkaistuksi linkiksi. Jos päätepisteesi palauttaa minkä tahansa muun kuin 2xx-tilan tai rungon, joka ei ole JSON-objekti url-kentällä, Orova katsoo julkaisun epäonnistuneen ja merkitsee artikkelin sen mukaisesti, jotta voit yrittää uudelleen. Vastaa 60 sekunnin kuluessa — sen jälkeen Orova katkaisee pyynnön aikakatkaisulla (koskee yhtä lailla julkaisua, päivitystä ja poistoa).

/ Esimerkki

Koodiesimerkki

Minimaalinen vastaanottava päätepiste Node.js:llä ja Expressillä. Samat vaiheet — tarkista tunnus, haaraudu actionin mukaan (update / delete), luo tai muuta julkaisu, palauta URL — pätevät millä tahansa kielellä tai kehyksellä.

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

/ Sopimus — päivitys

Artikkelien päivitys (Optimize)

Orovan Optimize-moottori kirjoittaa uudelleen jo julkaistuja artikkeleita — päivitetyt faktat, paremmat otsikot, yhdistetty sisältö — ja korvaa sitten olemassa olevan julkaisun paikallaan, URL ennallaan. Pyyntö menee samaan päätepisteen URL-osoitteeseen POST-pyyntönä samalla Bearer-tunnuksella:

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.

Etsi julkaisu target_url-kentän perusteella (varalla slug), korvaa sen otsikko, runko ja meta-kuvaus, pidä URL ennallaan ja vastaa sitten 200 ja { "url": "<same post URL>" }. Mikä tahansa muu kuin 2xx-tila tai runko ilman url-kenttää saa Orovan merkitsemään optimoinnin epäonnistuneeksi (se ei koskaan luo päällekkäistä julkaisua). Päätepisteet, jotka eivät vielä toteuta tätä, julkaisevat uusia artikkeleita ennallaan — päivityksen toteuttaminen tarvitaan vasta, kun käytät Optimize-näkymää.

29. elokuuta alkaen päivitysrunko voi sisältää myös kentät id (täsmää siihen ennen target_url-kenttää), featured_image_url uutta kansikuvaa varten sekä status, jonka arvo on "publish" tai "draft". Kaikki kolme ovat valinnaisia; tyhjinä ne jätetään rungosta pois, eikä niitä koskaan lähetetä null-arvona.

/ Sopimus — Visual Editor

Artikkelien lukeminen (pakollinen)

Orovan on luettava sivustollasi jo olevat artikkelit — päällekkäisten aiheiden välttämiseksi, sisäisten linkkien lisäämiseksi ja vanhempien julkaisujen optimoimiseksi. Lisää kaksi toimintoa samaan päätepisteen URL-osoitteeseen samalla Bearer-tunnuksella: list palauttaa artikkelisi, get palauttaa yhden niistä kokonaisuudessaan.

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 on enintään 500, ja uusin artikkeli tulee ensin. get-pyynnössä Orova lähettää yhdessä kentät id, target_url ja slug — täsmää siihen, minkä tunnistat, tässä järjestyksessä. Kun mikään ei täsmää, vastaa 404 ja { "ok": false, "err": "not_found" }. excerpt on list-vastauksessa valinnainen (enintään 300 merkkiä). Pakollinen: Orova kutsuu list-toimintoa selvittääkseen, mitkä artikkelit ovat jo olemassa (päällekkäisyyksien välttämiseksi, sisäisten linkkien lisäämiseksi ja koko sivuston analysoimiseksi), ja get-toimintoa lukeakseen artikkelin sitä optimoidessaan. Jos sivustosi ei vastaa näihin kahteen toimintoon, uudet artikkelit eivät saa sisäisiä linkkejä eikä vanhempia artikkeleita voi optimoida.

/ Sopimus — Visual Editor

Kuvien lataus (Visual Editor)

Kun pudotat kuvan Visual Editoriin, Orova lähettää sen samaan päätepisteen URL-osoitteeseen base64-muodossa JSON-rungon sisällä — ei multipartia, ei toista suojattavaa URL-osoitetta.

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
  }

Hyväksy vain image/* ja hylkää kaikki yli 15 MB. Tallenna tiedosto ja vastaa kuvan absoluuttisella URL-osoitteella: suhteellinen polku rikkoo artikkelin heti, kun se näytetään muualla. Tätä toimintoa tarvitaan vain Visual Editoria varten.

/ Sopimus — poisto

Artikkelien poistaminen

Kun käyttäjä poistaa julkaistun artikkelin Orovassa (Kirjoita- tai Raportit-näkymästä), Orova pyytää sivustoasi poistamaan sen myös. Pyyntö menee samaan päätepisteen URL-osoitteeseen POST-pyyntönä samalla Bearer-tunnuksella — ainoa ero on runko:

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
  }

Etsi julkaisu url-kentän perusteella (varalla slug), poista se tai peru sen julkaisu ja vastaa sitten 200 ja { "deleted": true } — pelkkä 204 toimii myös. Jos julkaisua ei enää ole, vastaa 404, niin Orova käsittelee sen jo poistettuna. Vastaus { "deleted": false } kertoo Orovalle, ettei julkaisua poistettu. Tavalliset julkaisupyynnöt eivät koskaan sisällä action-kenttää, joten nykyiset päätepisteet toimivat ennallaan — poiston toteuttaminen on valinnaista mutta suositeltavaa.

/ Tietoturva

Tietoturvahuomiot

  • Tarkista aina Bearer-tunnus. Päätepisteesi on julkinen URL — kuka tahansa voi kutsua sitä. Authorization-otsake on ainoa asia, joka todistaa pyynnön todella tulleen Orovalta, joten hylkää jokainen pyyntö, jonka tunnus ei täsmää tarkalleen tallentamaasi salaisuuteen.
  • Tarjoa päätepiste HTTPS:n kautta. Salaisuus kulkee otsakkeessa. HTTPS estää sen lukemisen siirron aikana.
  • Pidä salaisuus poissa koodistasi. Säilytä se ympäristömuuttujassa tai salaisuuksien hallinnassa, älä koskaan kovakoodattuna tiedostoon, jonka committaat. Jos se joskus paljastuu, luo uusi kohdasta Projekti → Yhteydet.
  • Käsittele content_html sisältönä, älä luotettuna merkintäkoodina. Tallenna ja näytä se yhtä huolellisesti kuin minkä tahansa artikkelin rungon CMS:ssäsi.

/ Lopuksi

Siinä koko sopimus

Kun päätepiste on käytössä ja rekisteröity projektiisi, Orova julkaisee sivustollesi automaattisesti — täsmälleen kuten WordPress-käyttäjille. Jokainen valmistunut artikkeli saapuu palvelimellesi, muuttuu julkaisuksi, ja sen julkinen URL palautuu Raportteihin ja Analyysiin, jotta voit seurata sen menestystä.

← Takaisin oppaiden kirjastoon

/ Tarvitsetko apua?

Jumissa päätepisteen kytkemisessä? Avaa Tuki-osio työtilassasi tai lähetä meille viesti.