OROVA.VN — BIZ AI AGENT

/ Guide for udviklere

Udgivelse via API på ethvert website.

Sådan bygger du dit eget endpoint, så Orova kan udgive færdige artikler på et website, der ikke kører WordPress.

Orova udgiver færdige artikler på WordPress uden ekstra opsætning. Hvis dit website ikke er WordPress — et skræddersyet CMS, en headless-opsætning, et statisk website eller din egen backend — kan du stadig lade Orova udgive automatisk ved at modtage artikler via et lille API. Du skriver ét HTTP-endpoint; Orova kalder det, hver gang en artikel er færdig. Denne guide er til en udvikler og beskriver hele anmodningskontrakten.

/ Overblik

Sådan fungerer det

Når Orova er færdig med at skrive en artikel til et søgeord, sender den én HTTP POST-anmodning til den endpoint-URL, du har registreret. Anmodningens body er JSON og indeholder hele artiklen. Dit endpoint opretter indlægget hos dig og svarer med artiklens endelige, offentlige URL. Orova gemmer den URL som det udgivne link, præcis som for et WordPress-indlæg. Brug dette, når du vil have automatisk udgivelse på et website, der ikke er WordPress, eller når du foretrækker at have fuld kontrol over, hvordan indlæg gemmes.

/ Opsætning

Opsætning — seks trin

  • 1

    Slå “Connect via API” til i dit projekt. Åbn dit projekt (Projekter → dit projekt → Forbindelser). Ved siden af WordPress-kortet er der et Connect via API-kort. Indtast URL'en til dit endpoint der. Feltet til den hemmelige nøgle er valgfrit: lad det stå tomt, så genererer Orova en, eller indsæt en nøgle, dit website allerede bruger. Når det er gemt, vises nøglen på kortet med en Kopiér-knap. Et projekt udgiver kun til ét mål — forbindelse via API låser WordPress-knappen og omvendt.

  • 2

    Byg et endpoint, der modtager POST. Opret på dit eget website eller din egen server en route, der lytter efter HTTP POST med en JSON-body. Det er den URL, du indsætter på projektets Forbindelser-skærm. Orova kalder den én gang for hver artikel, der bliver færdig.

  • 3

    Verificér Bearer-tokenet ved hver anmodning. Hver anmodning fra Orova har en Authorization-header i formen “Bearer <secret>” med præcis den hemmelige nøgle, der vises i dit projekt. Sammenlign den med din gemte kopi, og afvis alt, der ikke matcher — det er det, der forhindrer andre i at udgive på dit website.

  • 4

    Opret artiklen ud fra payloaden. Læs JSON-bodyen, og opret et indlæg i dit CMS eller din database: brug title, slug og content_html til selve artiklen, excerpt som metabeskrivelse, featured_image_url som forsidebillede og keyword / lang / published_at som metadata.

  • 5

    Returnér artiklens live-URL. Svar med HTTP 200 eller 201 og en JSON-body på { "url": "https://yoursite.com/the-new-article" }. Orova gemmer den URL som artiklens udgivne link. Hvis du returnerer en status, der ikke er 2xx, eller udelader url-feltet, betragter Orova udgivelsen som mislykket.

  • 6

    Håndtér opdateringsanmodninger fra Optimize-motoren. Når bodyen indeholder action = "update", så find det eksisterende indlæg via target_url (eller slug), og overskriv dets title, content_html og excerpt på stedet — behold URL'en uændret. Svar 200 med { "url": ... }, der peger på samme indlæg. Endpoints, der endnu ikke håndterer dette, får blot Orova til at melde optimeringen som mislykket; udgivelse af nye artikler fungerer som før.

/ Kontrakt

Anmodningen, Orova sender

Hver færdig artikel ankommer som én anmodning, der ser sådan ud:

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

Payload-felter

Felter i en udgivelsesanmodning (ny artikel). Anmodninger om sletning og opdatering bruger samme endpoint med de mindre bodies, der vises i deres egne afsnit nedenfor.

FeltTypeBeskrivelse
titlestringArtiklens overskrift.
slugstringURL-friendly identifier suggested for the post.
content_htmlstringHele artiklens brødtekst som HTML klar til udgivelse.
excerptstringEt kort resumé, beregnet til metabeskrivelsen.
featured_image_urlstring | nullURL til forsidebilledet, eller null, når der ikke er noget.
keywordstringDet SEO-søgeord, artiklen er skrevet til.
langstringLanguage as an ISO code, e.g. "en" or "vi".
published_atstringTidspunktet, hvor Orova sendte artiklen, ISO 8601 i UTC uden tidszonesuffiks.
statusstring | undefinedValgfrit. "draft" holder artiklen ikke-udgivet; "publish" eller ingen værdi udgiver den.
actionstring | undefinedMangler for nye artikler. "delete" beder dig fjerne et indlæg; "update" (sendt af Optimize-motoren) beder dig overskrive et eksisterende indlæg på stedet.
target_urlstringKun med action = "update": live-URL'en for det indlæg, der skal overskrives. Behold URL'en uændret.
idnumber | undefinedOnly sent when Orova knows your post id (from `list` or `get`): match on it before `target_url`.

Valgfrie felter udelades helt fra bodyen, når der ikke er noget at sende — Orova sender aldrig null, så læs dem defensivt. status er også valgfrit: "draft" opretter artiklen som kladde, mens "publish" eller ingen status udgiver den med det samme.

/ Kontrakt

Svaret, du skal returnere

Når du har oprettet indlægget, så svar med HTTP-status 200 eller 201 og denne JSON-body:

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

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

Orova gemmer den url som artiklens udgivne link. Hvis dit endpoint returnerer en status, der ikke er 2xx, eller en body, der ikke er et JSON-objekt med et url-felt, betragter Orova udgivelsen som mislykket og markerer artiklen derefter, så du kan prøve igen. Svar inden for 60 sekunder — Orova afbryder anmodningen derefter (det gælder både udgivelse, opdatering og sletning).

/ Eksempel

Kodeeksempel

Et minimalt modtagende endpoint i Node.js med Express. De samme trin — verificér tokenet, forgren på action (update / delete), opret eller ændr indlægget, returnér URL'en — gælder i ethvert sprog eller 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);

/ Kontrakt — opdatering

Opdatering af artikler (Optimize)

Orovas Optimize-motor omskriver artikler, der allerede er live — opdaterede fakta, bedre titler, sammenlagt indhold — og overskriver derefter det eksisterende indlæg på stedet med uændret URL. Anmodningen går til samme endpoint-URL som en POST med samme Bearer-token:

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.

Find indlægget via target_url (ellers slug), erstat dets titel, brødtekst og metabeskrivelse, behold URL'en som den er, og svar derefter 200 med { "url": "<same post URL>" }. Enhver status, der ikke er 2xx, eller en body uden url, får Orova til at markere optimeringen som mislykket (den opretter aldrig et dubleret indlæg). Endpoints, der endnu ikke har implementeret dette, fortsætter uændret med at udgive nye artikler — opdatering skal kun implementeres, når du bruger Optimize-skærmen.

Siden 29. august kan opdateringsbodyen også indeholde id (match på det før target_url), featured_image_url til et nyt forsidebillede og status med "publish" eller "draft". Alle tre er valgfrie; når de er tomme, udelades de fra bodyen og sendes aldrig som null.

/ Kontrakt — Visual Editor

Læsning af artikler (påkrævet)

Orova skal kunne læse de artikler, der allerede ligger på dit website — for at undgå dublerede emner, tilføje interne links og optimere ældre indlæg. Tilføj to handlinger mere på samme endpoint-URL med samme Bearer-token: list returnerer dine artikler, get returnerer én af dem i fuld længde.

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 er maksimalt 500, og den nyeste artikel kommer først. Til get sender Orova id, target_url og slug samlet — match på det, du genkender, i den rækkefølge. Når intet matcher, så svar 404 med { "ok": false, "err": "not_found" }. excerpt i list er valgfrit (højst 300 tegn). Påkrævet: Orova kalder list for at finde ud af, hvilke artikler der allerede findes (for at undgå dubletter, tilføje interne links og analysere hele websitet), og kalder get for at læse en artikel, når den optimeres. Hvis dit website ikke besvarer disse to handlinger, får nye artikler ingen interne links, og ældre artikler kan ikke optimeres.

/ Kontrakt — Visual Editor

Upload af billeder (Visual Editor)

Når du slipper et billede i Visual Editor, sender Orova det til samme endpoint-URL som base64 i JSON-bodyen — ingen multipart, ingen ekstra URL at sikre.

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
  }

Acceptér kun image/*, og afvis alt over 15 MB. Gem filen, og svar derefter med billedets absolutte URL: en relativ sti ødelægger artiklen, så snart den vises et andet sted. Denne handling er kun nødvendig for Visual Editor.

/ Kontrakt — sletning

Sletning af artikler

Når en bruger fjerner en udgivet artikel i Orova (fra skærmen Skriv eller Rapporter), beder Orova dit website om også at slette den. Anmodningen går til samme endpoint-URL som en POST med samme Bearer-token — den eneste forskel er bodyen:

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
  }

Slå indlægget op via url (ellers slug), fjern det eller afpublicér det, og svar derefter med 200 og { "deleted": true } — en ren 204 virker også. Hvis indlægget ikke længere findes, så svar 404, og Orova betragter det som allerede slettet. Returnerer du { "deleted": false }, ved Orova, at indlægget ikke blev fjernet. Almindelige udgivelsesanmodninger indeholder aldrig et action-felt, så eksisterende endpoints fungerer uændret — sletning er valgfri, men anbefales.

/ Sikkerhed

Sikkerhedsnoter

  • Verificér altid Bearer-tokenet. Dit endpoint er en offentlig URL — alle kan kalde det. Authorization-headeren er det eneste, der beviser, at en anmodning virkelig kom fra Orova, så afvis enhver anmodning, hvis token ikke præcist matcher din gemte hemmelighed.
  • Kør endpointet over HTTPS. Hemmeligheden sendes i en header. HTTPS forhindrer, at den bliver læst undervejs.
  • Hold hemmeligheden ude af din kode. Gem den i en miljøvariabel eller en secrets manager, aldrig hardkodet i en fil, du committer. Hvis den nogensinde bliver afsløret, så generér en ny under Projekt → Forbindelser.
  • Behandl content_html som indhold, ikke som betroet markup. Gem og vis den med samme omhu, som du ville gøre med enhver artikeltekst i dit CMS.

/ Afrunding

Det er hele kontrakten

Når endpointet er live og registreret i dit projekt, udgiver Orova automatisk på dit website — præcis som for WordPress-brugere. Hver færdig artikel lander på din server, bliver til et indlæg, og dens offentlige URL føres tilbage til Rapporter og Analyse, så du kan følge, hvordan den klarer sig.

← Tilbage til guidebiblioteket

/ Brug for hjælp?

Sidder du fast med at koble dit endpoint op? Åbn Support i dit arbejdsområde, eller send os en besked.