/ Handleiding voor ontwikkelaars
Publiceren via API op elke website.
Hoe u uw eigen endpoint bouwt zodat Orova afgewerkte artikelen kan publiceren op een site die niet op WordPress draait.
Orova publiceert afgewerkte artikelen standaard naar WordPress. Als uw website geen WordPress is — een eigen CMS, een headless opzet, een statische site of uw eigen backend — kunt u Orova nog steeds automatisch laten publiceren door artikelen via een kleine API te ontvangen. U schrijft één HTTP-endpoint; Orova roept het aan telkens wanneer een artikel klaar is. Deze handleiding is bedoeld voor een ontwikkelaar en beschrijft het volledige requestcontract.
/ Overzicht
Hoe het werkt
Wanneer Orova een artikel voor een zoekwoord af heeft, stuurt het één HTTP-POST-request naar de endpoint-URL die u hebt geregistreerd. De body van het request is JSON en bevat het hele artikel. Uw endpoint maakt het bericht aan uw kant aan en antwoordt met de definitieve, openbare URL van het artikel. Orova slaat die URL op als gepubliceerde link, net zoals bij een WordPress-bericht. Gebruik dit wanneer u zonder omkijken wilt publiceren op een site die niet op WordPress draait, of wanneer u volledige controle wilt houden over hoe berichten worden opgeslagen.
/ Instellen
Instellen — zes stappen
- 1
Zet “Verbinden via API” aan in uw project. Open uw project (Projecten → uw project → Koppelingen). Naast de WordPress-kaart staat een kaart Verbinden via API. Vul daar de URL van uw endpoint in. Het veld voor de geheime sleutel is optioneel: laat het leeg en Orova genereert er een, of plak een sleutel die uw site al gebruikt. Na het opslaan wordt de sleutel op de kaart getoond met een knop Kopiëren. Een project publiceert naar slechts één bestemming — verbinden via API vergrendelt de WordPress-knop, en omgekeerd.
- 2
Bouw een endpoint dat POST accepteert. Maak op uw eigen site of server een route die luistert naar HTTP POST met een JSON-body. Dit is de URL die u terug plakt in het scherm Koppelingen van het project. Orova roept het eenmaal aan voor elk artikel dat klaar is.
- 3
Controleer het Bearer-token bij elk request. Elk request van Orova bevat een Authorization-header in de vorm “Bearer <secret>”, met precies de geheime sleutel die in uw project wordt getoond. Vergelijk die met uw opgeslagen kopie en weiger alles wat niet overeenkomt — zo voorkomt u dat iemand anders op uw site publiceert.
- 4
Maak het artikel aan uit de payload. Lees de JSON-body en maak een bericht aan in uw CMS of database: gebruik title, slug en content_html voor het artikel zelf, excerpt als metabeschrijving, featured_image_url als omslagafbeelding, en keyword / lang / published_at als metadata.
- 5
Geef de live URL van het artikel terug. Antwoord met HTTP 200 of 201 en een JSON-body { "url": "https://yoursite.com/the-new-article" }. Orova slaat die URL op als gepubliceerde link van het artikel. Als u een status buiten 2xx teruggeeft of het veld url weglaat, beschouwt Orova de publicatie als mislukt.
- 6
Verwerk updaterequests van de Optimize-engine. Wanneer de body action = "update" bevat, zoekt u het bestaande bericht op via target_url (of slug) en overschrijft u de title, content_html en excerpt op dezelfde plek — laat de URL ongewijzigd. Antwoord met 200 en { "url": ... } die naar hetzelfde bericht verwijst. Endpoints die dit nog niet verwerken, zorgen er alleen voor dat Orova de optimalisatie als mislukt meldt; het publiceren van nieuwe artikelen werkt gewoon door.
/ Contract
Het request dat Orova stuurt
Elk afgewerkt artikel komt binnen als één request in deze vorm:
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./ Referentie
Payloadvelden
Velden van een publicatierequest (nieuw artikel). Verwijder- en updaterequests gebruiken hetzelfde endpoint met de kleinere bodies die hieronder in hun eigen secties staan.
| Veld | Type | Beschrijving |
|---|---|---|
| title | string | De kop van het artikel. |
| slug | string | URL-friendly identifier suggested for the post. |
| content_html | string | De volledige artikeltekst als HTML, klaar om te publiceren. |
| excerpt | string | Een korte samenvatting, bedoeld als metabeschrijving. |
| featured_image_url | string | null | URL van de omslagafbeelding, of null als die er niet is. |
| keyword | string | Het SEO-zoekwoord waarvoor het artikel is geschreven. |
| lang | string | Language as an ISO code, e.g. "en" or "vi". |
| published_at | string | Het moment waarop Orova het artikel stuurde, ISO 8601 in UTC, zonder tijdzone-achtervoegsel. |
| status | string | undefined | Optioneel. "draft" houdt het artikel ongepubliceerd; "publish", of helemaal geen waarde, publiceert het. |
| action | string | undefined | Ontbreekt bij nieuwe artikelen. "delete" vraagt u een bericht te verwijderen; "update" (gestuurd door de Optimize-engine) vraagt u een bestaand bericht op dezelfde plek te overschrijven. |
| target_url | string | Alleen bij action = "update": de live URL van het bericht dat moet worden overschreven. Laat de URL ongewijzigd. |
| id | number | undefined | Only sent when Orova knows your post id (from `list` or `get`): match on it before `target_url`. |
Optionele velden worden helemaal uit de body weggelaten als er niets te sturen is — Orova stuurt nooit null, dus lees ze defensief. Ook status is optioneel: "draft" maakt het artikel aan als concept, terwijl "publish" of geen status het direct publiceert.
/ Contract
Het antwoord dat u moet teruggeven
Antwoord, nadat u het bericht hebt aangemaakt, met HTTP-status 200 of 201 en deze JSON-body:
HTTP 200 (or 201)
Content-Type: application/json
{
"url": "https://yoursite.com/published-article"
}Orova slaat die url op als gepubliceerde link van het artikel. Als uw endpoint een status buiten 2xx teruggeeft, of een body die geen JSON-object met een veld url is, beschouwt Orova de publicatie als mislukt en markeert het artikel zodat u het opnieuw kunt proberen. Antwoord binnen 60 seconden — daarna breekt Orova het request af (dit geldt voor publiceren, bijwerken en verwijderen).
/ Voorbeeld
Codevoorbeeld
Een minimaal ontvangend endpoint in Node.js met Express. Dezelfde stappen — token controleren, splitsen op action (update / delete), bericht aanmaken of wijzigen, URL teruggeven — gelden in elke taal of elk 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);/ Contract — bijwerken
Artikelen bijwerken (Optimize)
De Optimize-engine van Orova herschrijft artikelen die al live staan — bijgewerkte feiten, betere titels, samengevoegde inhoud — en overschrijft daarna het bestaande bericht op dezelfde plek, met behoud van de URL. Het request gaat naar dezelfde endpoint-URL, als POST met hetzelfde 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.Zoek het bericht op via target_url (anders via slug), vervang titel, tekst en metabeschrijving, laat de URL zoals hij is en antwoord dan met 200 en { "url": "<same post URL>" }. Elke status buiten 2xx, of een body zonder url, laat Orova die optimalisatie als mislukt markeren (er wordt nooit een dubbel bericht aangemaakt). Endpoints die dit nog niet hebben geïmplementeerd, blijven gewoon nieuwe artikelen publiceren — update implementeren is pas nodig zodra u het scherm Optimaliseren gebruikt.
Sinds 29 augustus kan de updatebody ook id bevatten (match daarop vóór target_url), featured_image_url voor een nieuwe omslagafbeelding, en status met "publish" of "draft". Alle drie zijn optioneel; als ze leeg zijn, worden ze uit de body weggelaten en nooit als null gestuurd.
/ Contract — Visual Editor
Artikelen lezen (verplicht)
Orova moet de artikelen lezen die al op uw site staan — om dubbele onderwerpen te voorkomen, interne links toe te voegen en oudere berichten te optimaliseren. Voeg nog twee actions toe op dezelfde endpoint-URL, met hetzelfde Bearer-token: list geeft uw artikelen terug, get geeft er één volledig terug.
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 is begrensd op 500 en het nieuwste artikel komt eerst. Voor get stuurt Orova id, target_url en slug samen — match op wat u herkent, in die volgorde. Als niets overeenkomt, antwoord dan met 404 en { "ok": false, "err": "not_found" }. excerpt in list is optioneel (maximaal 300 tekens). Verplicht: Orova roept list aan om te weten welke artikelen al bestaan (om dubbels te voorkomen, interne links toe te voegen en de hele site te analyseren) en roept get aan om een artikel te lezen bij het optimaliseren. Als uw site deze twee actions niet beantwoordt, krijgen nieuwe artikelen geen interne links en kunnen oudere artikelen niet worden geoptimaliseerd.
/ Contract — Visual Editor
Afbeeldingen uploaden (Visual Editor)
Wanneer u een afbeelding in de Visual Editor sleept, stuurt Orova die naar dezelfde endpoint-URL als base64 in de JSON-body — geen multipart, geen tweede URL om te beveiligen.
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
}Accepteer alleen image/* en weiger alles boven 15 MB. Sla het bestand op en antwoord met de absolute URL van de afbeelding: een relatief pad breekt het artikel zodra het ergens anders wordt weergegeven. Deze action is alleen nodig voor de Visual Editor.
/ Contract — verwijderen
Artikelen verwijderen
Wanneer een gebruiker een gepubliceerd artikel in Orova verwijdert (vanuit het scherm Schrijven of Rapporten), vraagt Orova uw site om het ook te verwijderen. Het request gaat naar dezelfde endpoint-URL, als POST met hetzelfde Bearer-token — alleen de body verschilt:
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
}Zoek het bericht op via url (anders via slug), verwijder het of zet het offline, en antwoord dan met 200 en { "deleted": true } — een kale 204 werkt ook. Als het bericht niet meer bestaat, antwoord dan met 404 en Orova beschouwt het als al verwijderd. Met { "deleted": false } laat u Orova weten dat het bericht niet is verwijderd. Gewone publicatierequests bevatten nooit een veld action, dus bestaande endpoints blijven ongewijzigd werken — delete implementeren is optioneel maar aanbevolen.
/ Beveiliging
Beveiligingstips
- Controleer altijd het Bearer-token. Uw endpoint is een openbare URL — iedereen kan het aanroepen. De Authorization-header is het enige bewijs dat een request echt van Orova komt, dus weiger elk request waarvan het token niet exact overeenkomt met uw opgeslagen geheime sleutel.
- Bied het endpoint aan via HTTPS. De geheime sleutel reist mee in een header. HTTPS voorkomt dat hij onderweg wordt gelezen.
- Houd de geheime sleutel buiten uw code. Bewaar hem in een omgevingsvariabele of een secrets manager, nooit hardcoded in een bestand dat u commit. Is hij ooit uitgelekt, genereer dan een nieuwe via Project → Koppelingen.
- Behandel content_html als inhoud, niet als vertrouwde opmaak. Sla het op en geef het weer met dezelfde zorg als elke andere artikeltekst in uw CMS.
/ Afronding
Dat is het hele contract
Zodra het endpoint live is en in uw project is geregistreerd, publiceert Orova automatisch naar uw site — precies zoals voor WordPress-gebruikers. Elk afgewerkt artikel komt op uw server terecht, wordt een bericht, en de openbare URL vloeit terug naar Rapporten en Analyse zodat u de prestaties kunt volgen.
/ Hulp nodig?
Loopt u vast bij het koppelen van uw endpoint? Open het onderdeel Ondersteuning in uw werkruimte, of stuur ons een bericht.
