/ Vodič za developere
Objavljivanje putem API-ja na bilo kojoj web-stranici.
Kako izraditi vlastitu krajnju točku kako bi Orova mogla objavljivati gotove članke na stranici koja ne koristi WordPress.
Orova odmah objavljuje gotove članke na WordPress. Ako vaša web-stranica nije WordPress — vlastiti CMS, headless postava, statična stranica ili vlastiti backend — i dalje možete dopustiti Orovi automatsko objavljivanje primanjem članaka putem malog API-ja. Napišete jednu HTTP krajnju točku; Orova je poziva svaki put kad dovrši članak. Ovaj je vodič namijenjen developeru i opisuje cijeli ugovor zahtjeva.
/ Pregled
Kako funkcionira
Kad Orova dovrši članak za ključnu riječ, šalje jedan HTTP POST zahtjev na URL krajnje točke koji ste registrirali. Tijelo zahtjeva je JSON i sadrži cijeli članak. Vaša krajnja točka stvara objavu na vašoj strani i odgovara konačnim, javnim URL-om članka. Orova sprema taj URL kao objavljenu poveznicu, kao i za WordPress objavu. Koristite ovo kad želite objavljivanje bez ručnog rada na stranici koja nije WordPress ili kad želite potpunu kontrolu nad pohranom objava.
/ Postavljanje
Postavljanje — šest koraka
- 1
Uključite „Connect via API” u svom projektu. Otvorite projekt (Projekti → vaš projekt → Veze). Pokraj kartice WordPress nalazi se kartica Connect via API. Ondje unesite URL svoje krajnje točke. Polje tajnog ključa nije obavezno: ostavite ga praznim i Orova će ga generirati ili zalijepite ključ koji vaša stranica već koristi. Nakon spremanja ključ se prikazuje na kartici s gumbom Kopiraj. Projekt objavljuje samo na jedno odredište — povezivanje putem API-ja zaključava gumb WordPress, i obrnuto.
- 2
Izradite krajnju točku koja prima POST. Na svojoj stranici ili poslužitelju izradite rutu koja sluša HTTP POST s JSON tijelom. To je URL koji zalijepite natrag na zaslon Veze projekta. Orova je poziva jednom za svaki dovršeni članak.
- 3
Provjerite Bearer token u svakom zahtjevu. Svaki zahtjev od Orove nosi zaglavlje Authorization u obliku „Bearer <secret>”, s točno onim tajnim ključem prikazanim u vašem projektu. Usporedite ga sa spremljenom kopijom i odbijte sve što se ne podudara — to sprječava druge da objavljuju na vašoj stranici.
- 4
Izradite članak iz payloada. Pročitajte JSON tijelo i izradite objavu u svom CMS-u ili bazi podataka: koristite title, slug i content_html za sam članak, excerpt kao meta opis, featured_image_url kao naslovnu sliku te keyword / lang / published_at kao metapodatke.
- 5
Vratite živi URL članka. Odgovorite s HTTP 200 ili 201 i JSON tijelom { "url": "https://yoursite.com/the-new-article" }. Orova sprema taj URL kao objavljenu poveznicu članka. Ako vratite status koji nije 2xx ili izostavite polje url, Orova smatra objavu neuspjelom.
- 6
Obradite zahtjeve za ažuriranje iz mehanizma Optimize. Kad tijelo sadrži action = „update”, pronađite postojeću objavu prema target_url (ili slug) i na mjestu prepišite njezine title, content_html i excerpt — URL ostavite nepromijenjen. Odgovorite s 200 i { "url": ... } koji upućuje na istu objavu. Krajnje točke koje to još ne podržavaju jednostavno će dovesti do toga da Orova prijavi optimizaciju kao neuspjelu; objavljivanje novih članaka radi kao i prije.
/ Ugovor
Zahtjev koji Orova šalje
Svaki dovršeni članak stiže kao jedan zahtjev ovakvog oblika:
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./ Referenca
Polja payloada
Polja zahtjeva za objavu (novi članak). Zahtjevi za brisanje i ažuriranje koriste istu krajnju točku s manjim tijelima prikazanima u zasebnim odjeljcima u nastavku.
| Polje | Vrsta | Opis |
|---|---|---|
| title | string | Naslov članka. |
| slug | string | URL-friendly identifier suggested for the post. |
| content_html | string | Cijelo tijelo članka kao HTML spreman za objavu. |
| excerpt | string | Kratak sažetak, namijenjen za meta opis. |
| featured_image_url | string | null | URL naslovne slike ili null ako je nema. |
| keyword | string | Ciljna SEO ključna riječ za koju je članak napisan. |
| lang | string | Language as an ISO code, e.g. "en" or "vi". |
| published_at | string | Trenutak kad je Orova poslala članak, ISO 8601 u UTC-u, bez oznake vremenske zone. |
| status | string | undefined | Nije obavezno. „draft” ostavlja članak neobjavljenim; „publish” ili izostanak vrijednosti ga objavljuje. |
| action | string | undefined | Izostaje za nove članke. „delete” traži da uklonite objavu; „update” (šalje ga mehanizam Optimize) traži da na mjestu prepišete postojeću objavu. |
| target_url | string | Samo uz action = "update": živi URL objave koju treba prepisati. URL ostavite nepromijenjen. |
| id | number | undefined | Only sent when Orova knows your post id (from `list` or `get`): match on it before `target_url`. |
Neobavezna polja potpuno se izostavljaju iz tijela kad nema što poslati — Orova nikad ne šalje null, pa ih čitajte oprezno. I status nije obavezan: "draft" stvara članak kao skicu, a "publish" ili izostanak polja status ga odmah objavljuje.
/ Ugovor
Odgovor koji morate vratiti
Nakon što izradite objavu, odgovorite statusom HTTP 200 ili 201 i ovim JSON tijelom:
HTTP 200 (or 201)
Content-Type: application/json
{
"url": "https://yoursite.com/published-article"
}Orova sprema taj url kao objavljenu poveznicu članka. Ako vaša krajnja točka vrati bilo koji status koji nije 2xx ili tijelo koje nije JSON objekt s poljem url, Orova smatra objavu neuspjelom i u skladu s tim označava članak kako biste mogli pokušati ponovno. Odgovorite unutar 60 sekundi — nakon toga Orova prekida zahtjev (vrijedi jednako za objavu, ažuriranje i brisanje).
/ Primjer
Primjer kôda
Minimalna krajnja točka za primanje u Node.js-u s Expressom. Isti koraci — provjera tokena, grananje prema action (update / delete), izrada ili izmjena objave, vraćanje URL-a — vrijede u bilo kojem jeziku ili okviru.
// 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);/ Ugovor — ažuriranje
Ažuriranje članaka (Optimize)
Orovin mehanizam Optimize prepisuje članke koji su već objavljeni — osvježene činjenice, bolji naslovi, spojeni sadržaj — a zatim prepisuje postojeću objavu na mjestu, uz nepromijenjen URL. Zahtjev ide na isti URL krajnje točke, kao POST s istim Bearer tokenom:
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.Pronađite objavu prema target_url (zamjenski slug), zamijenite njezin naslov, tijelo i meta opis, URL ostavite kakav jest, a zatim odgovorite s 200 i { "url": "<same post URL>" }. Bilo koji status koji nije 2xx ili tijelo bez url dovodi do toga da Orova označi tu optimizaciju kao neuspjelu (nikad ne stvara duplikat objave). Krajnje točke koje to još nisu implementirale nastavljaju objavljivati nove članke bez promjena — ažuriranje je potrebno implementirati tek kad koristite zaslon Optimize.
Od 29. kolovoza tijelo ažuriranja može sadržavati i id (podudarajte prema njemu prije target_url), featured_image_url za novu naslovnu sliku i status s vrijednošću "publish" ili "draft". Sva tri polja nisu obavezna; kad su prazna, izostavljaju se iz tijela i nikad se ne šalju kao null.
/ Ugovor — Visual Editor
Čitanje članaka (obavezno)
Orova mora čitati članke koji su već na vašoj stranici — kako bi izbjegla duplicirane teme, dodala interne poveznice i optimizirala starije objave. Dodajte još dvije radnje na isti URL krajnje točke, s istim Bearer tokenom: list vraća vaše članke, get vraća jedan od njih u cijelosti.
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 ograničen na 500, a najnoviji članak dolazi prvi. Za get Orova šalje id, target_url i slug zajedno — podudarajte prema onome što prepoznajete, tim redoslijedom. Kad se ništa ne podudara, odgovorite s 404 i { "ok": false, "err": "not_found" }. excerpt u list nije obavezan (najviše 300 znakova). Obavezno: Orova poziva list da sazna koji članci već postoje (radi izbjegavanja duplikata, dodavanja internih poveznica i analize cijele stranice) i poziva get da pročita članak pri optimizaciji. Ako vaša stranica ne odgovara na ove dvije radnje, novi članci neće dobiti interne poveznice, a stariji se članci ne mogu optimizirati.
/ Ugovor — Visual Editor
Prijenos slika (Visual Editor)
Kad ispustite sliku u Visual Editor, Orova je šalje na isti URL krajnje točke kao base64 unutar JSON tijela — bez multiparta, bez drugog URL-a koji treba zaštititi.
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
}Prihvaćajte samo image/* i odbijte sve veće od 15 MB. Spremite datoteku, a zatim odgovorite apsolutnim URL-om slike: relativna putanja kvari članak čim se prikaže negdje drugdje. Ova je radnja potrebna samo za Visual Editor.
/ Ugovor — brisanje
Brisanje članaka
Kad korisnik ukloni objavljeni članak u Orovi (na zaslonu Pisanje ili Izvješća), Orova traži od vaše stranice da ga također izbriše. Zahtjev ide na isti URL krajnje točke, kao POST s istim Bearer tokenom — jedina je razlika tijelo:
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
}Pronađite objavu prema url (zamjenski slug), uklonite je ili poništite objavu, a zatim odgovorite s 200 i { "deleted": true } — radi i sam 204. Ako objava više ne postoji, odgovorite s 404 i Orova će je smatrati već izbrisanom. Vraćanje { "deleted": false } govori Orovi da objava nije uklonjena. Uobičajeni zahtjevi za objavu nikad ne sadrže polje action, pa postojeće krajnje točke rade bez promjena — implementacija brisanja nije obavezna, ali se preporučuje.
/ Sigurnost
Sigurnosne napomene
- Uvijek provjeravajte Bearer token. Vaša krajnja točka je javni URL — svatko je može pozvati. Zaglavlje Authorization jedino dokazuje da je zahtjev stvarno došao od Orove, pa odbijte svaki zahtjev čiji se token ne podudara točno sa spremljenim tajnim ključem.
- Poslužujte krajnju točku preko HTTPS-a. Tajni ključ putuje u zaglavlju. HTTPS sprječava da ga se pročita tijekom prijenosa.
- Držite tajni ključ izvan kôda. Spremite ga u varijablu okruženja ili upravitelj tajni, nikad izravno u datoteku koju commitate. Ako ikad procuri, generirajte novi u Projekt → Veze.
- Tretirajte content_html kao sadržaj, ne kao pouzdan markup. Spremajte ga i prikazujte jednako pažljivo kao i svako tijelo članka u svom CMS-u.
/ Zaključak
To je cijeli ugovor
Kad je krajnja točka aktivna i registrirana u projektu, Orova automatski objavljuje na vašoj stranici — točno kao za korisnike WordPressa. Svaki dovršeni članak stiže na vaš poslužitelj, postaje objava, a njegov javni URL vraća se u Izvješća i Analizu kako biste mogli pratiti uspješnost.
/ Trebate pomoć?
Zapeli ste s povezivanjem krajnje točke? Otvorite odjeljak Podrška u svom radnom prostoru ili nam pošaljite poruku.
