OROVA.VN — BIZ AI AGENT

/ Οδηγός για developers

Δημοσίευση μέσω API σε οποιονδήποτε ιστότοπο.

Πώς να φτιάξετε το δικό σας endpoint ώστε το Orova να δημοσιεύει τα ολοκληρωμένα άρθρα σε ιστότοπο που δεν τρέχει WordPress.

Το Orova δημοσιεύει τα ολοκληρωμένα άρθρα στο WordPress χωρίς επιπλέον ρυθμίσεις. Αν ο ιστότοπός σας δεν είναι WordPress — δικό σας CMS, headless διάταξη, στατικός ιστότοπος ή δικό σας backend — μπορείτε και πάλι να αφήσετε το Orova να δημοσιεύει αυτόματα, δεχόμενοι άρθρα μέσω ενός μικρού API. Γράφετε ένα HTTP endpoint· το Orova το καλεί κάθε φορά που ολοκληρώνει ένα άρθρο. Ο οδηγός απευθύνεται σε developer και περιγράφει πλήρως το συμβόλαιο αιτήματος.

/ Επισκόπηση

Πώς λειτουργεί

Όταν το Orova ολοκληρώνει ένα άρθρο για μια λέξη-κλειδί, στέλνει ένα μόνο αίτημα HTTP POST στο URL endpoint που καταχωρίσατε. Το σώμα του αιτήματος είναι JSON και περιέχει ολόκληρο το άρθρο. Το endpoint σας δημιουργεί την ανάρτηση στη δική σας πλευρά και απαντά με το τελικό, δημόσιο URL του άρθρου. Το Orova αποθηκεύει αυτό το URL ως σύνδεσμο δημοσίευσης, όπως θα έκανε για ανάρτηση WordPress. Χρησιμοποιήστε το όταν θέλετε δημοσίευση χωρίς παρέμβαση σε ιστότοπο που δεν είναι WordPress, ή όταν προτιμάτε πλήρη έλεγχο στο πώς αποθηκεύονται οι αναρτήσεις.

/ Ρύθμιση

Ρύθμιση — έξι βήματα

  • 1

    Ενεργοποιήστε τη «Σύνδεση μέσω API» στο έργο σας. Ανοίξτε το έργο σας (Έργα → το έργο σας → Συνδέσεις). Δίπλα στην κάρτα WordPress υπάρχει η κάρτα Σύνδεση μέσω API. Εισαγάγετε εκεί το URL του endpoint σας. Το πεδίο μυστικού κλειδιού είναι προαιρετικό: αφήστε το κενό και το Orova δημιουργεί ένα, ή επικολλήστε ένα κλειδί που ήδη χρησιμοποιεί ο ιστότοπός σας. Μετά την αποθήκευση, το κλειδί εμφανίζεται στην κάρτα με κουμπί Αντιγραφή. Ένα έργο δημοσιεύει σε έναν μόνο προορισμό — η σύνδεση μέσω API κλειδώνει το κουμπί WordPress, και αντίστροφα.

  • 2

    Φτιάξτε endpoint που δέχεται POST. Στον ιστότοπο ή τον διακομιστή σας, δημιουργήστε μια διαδρομή που ακούει HTTP POST με σώμα JSON. Αυτό είναι το URL που επικολλάτε στην οθόνη Συνδέσεις του έργου. Το Orova το καλεί μία φορά για κάθε άρθρο που ολοκληρώνει.

  • 3

    Επαληθεύστε το Bearer token σε κάθε αίτημα. Κάθε αίτημα από το Orova φέρει κεφαλίδα Authorization της μορφής «Bearer <secret>», με ακριβώς το μυστικό κλειδί που φαίνεται στο έργο σας. Συγκρίνετέ το με το αποθηκευμένο αντίγραφο και απορρίψτε ό,τι δεν ταιριάζει — αυτό εμποδίζει οποιονδήποτε άλλο να δημοσιεύει στον ιστότοπό σας.

  • 4

    Δημιουργήστε το άρθρο από το payload. Διαβάστε το σώμα JSON και δημιουργήστε ανάρτηση στο CMS ή τη βάση δεδομένων σας: title, slug και content_html για το ίδιο το άρθρο, excerpt ως meta description, featured_image_url ως εικόνα εξωφύλλου, και keyword / lang / published_at ως μεταδεδομένα.

  • 5

    Επιστρέψτε το ζωντανό URL του άρθρου. Απαντήστε με HTTP 200 ή 201 και σώμα JSON { "url": "https://yoursite.com/the-new-article" }. Το Orova αποθηκεύει αυτό το URL ως σύνδεσμο δημοσίευσης του άρθρου. Αν επιστρέψετε κατάσταση εκτός 2xx ή παραλείψετε το πεδίο url, το Orova θεωρεί ότι η δημοσίευση απέτυχε.

  • 6

    Χειριστείτε αιτήματα ενημέρωσης από τη μηχανή Optimize. Όταν το σώμα έχει action = "update", βρείτε την υπάρχουσα ανάρτηση με το target_url (ή το slug) και αντικαταστήστε title, content_html και excerpt στη θέση τους — κρατήστε το URL αμετάβλητο. Απαντήστε 200 με { "url": ... } που δείχνει στην ίδια ανάρτηση. Τα endpoints που δεν το χειρίζονται ακόμη απλώς θα κάνουν το Orova να αναφέρει τη βελτιστοποίηση ως αποτυχημένη· η δημοσίευση νέων άρθρων συνεχίζει να λειτουργεί όπως πριν.

/ Συμβόλαιο

Το αίτημα που στέλνει το Orova

Κάθε ολοκληρωμένο άρθρο φτάνει ως ένα αίτημα αυτής της μορφής:

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.

/ Αναφορά

Πεδία payload

Πεδία ενός αιτήματος δημοσίευσης (νέο άρθρο). Τα αιτήματα διαγραφής και ενημέρωσης χρησιμοποιούν το ίδιο endpoint με τα μικρότερα σώματα που φαίνονται στις δικές τους ενότητες παρακάτω.

ΠεδίοΤύποςΠεριγραφή
titlestringΗ επικεφαλίδα του άρθρου.
slugstringURL-friendly identifier suggested for the post.
content_htmlstringΟλόκληρο το σώμα του άρθρου σε HTML έτοιμο για δημοσίευση.
excerptstringΣύντομη περίληψη, για το meta description.
featured_image_urlstring | nullURL της εικόνας εξωφύλλου, ή null όταν δεν υπάρχει.
keywordstringΗ στοχευμένη λέξη-κλειδί SEO για την οποία γράφτηκε το άρθρο.
langstringLanguage as an ISO code, e.g. "en" or "vi".
published_atstringΗ στιγμή που το Orova έστειλε το άρθρο, ISO 8601 σε UTC, χωρίς επίθημα ζώνης ώρας.
statusstring | undefinedΠροαιρετικό. Το "draft" κρατά το άρθρο αδημοσίευτο· το "publish" ή καμία τιμή το δημοσιεύει.
actionstring | undefinedΑπουσιάζει στα νέα άρθρα. Το "delete" ζητά να αφαιρέσετε μια ανάρτηση· το "update" (από τη μηχανή Optimize) ζητά να αντικαταστήσετε υπάρχουσα ανάρτηση στη θέση της.
target_urlstringΜόνο με action = "update": το ζωντανό URL της ανάρτησης προς αντικατάσταση. Κρατήστε το URL αμετάβλητο.
idnumber | undefinedOnly sent when Orova knows your post id (from `list` or `get`): match on it before `target_url`.

Τα προαιρετικά πεδία παραλείπονται εντελώς από το σώμα όταν δεν υπάρχει τίποτα να σταλεί — το Orova δεν στέλνει ποτέ null, οπότε διαβάστε τα αμυντικά. Και το status είναι προαιρετικό: το "draft" δημιουργεί το άρθρο ως πρόχειρο, ενώ το "publish" ή η απουσία status το δημοσιεύει αμέσως.

/ Συμβόλαιο

Η απόκριση που πρέπει να επιστρέψετε

Αφού δημιουργήσετε την ανάρτηση, απαντήστε με κατάσταση HTTP 200 ή 201 και αυτό το σώμα JSON:

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

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

Το Orova αποθηκεύει αυτό το url ως σύνδεσμο δημοσίευσης του άρθρου. Αν το endpoint σας επιστρέψει οποιαδήποτε κατάσταση εκτός 2xx, ή σώμα που δεν είναι αντικείμενο JSON με πεδίο url, το Orova θεωρεί ότι η δημοσίευση απέτυχε και σημειώνει το άρθρο ανάλογα ώστε να το ξαναδοκιμάσετε. Απαντήστε μέσα σε 60 δευτερόλεπτα — μετά το Orova τερματίζει το αίτημα (ισχύει εξίσου για δημοσίευση, ενημέρωση και διαγραφή).

/ Παράδειγμα

Παράδειγμα κώδικα

Ένα ελάχιστο endpoint λήψης σε Node.js με Express. Τα ίδια βήματα — επαλήθευση token, διακλάδωση με βάση το action (update / delete), δημιουργία ή αλλαγή ανάρτησης, επιστροφή του URL — ισχύουν σε οποιαδήποτε γλώσσα ή 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);

/ Συμβόλαιο — update

Ενημέρωση άρθρων (Optimize)

Η μηχανή Optimize του Orova ξαναγράφει άρθρα που είναι ήδη ζωντανά — ανανεωμένα στοιχεία, καλύτεροι τίτλοι, συγχωνευμένο περιεχόμενο — και μετά αντικαθιστά την υπάρχουσα ανάρτηση στη θέση της, κρατώντας το URL αμετάβλητο. Το αίτημα πηγαίνει στο ίδιο URL endpoint, ως POST με το ίδιο 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.

Βρείτε την ανάρτηση με το target_url (εναλλακτικά με το slug), αντικαταστήστε τίτλο, σώμα και meta description, κρατήστε το URL ως έχει και απαντήστε 200 με { "url": "<same post URL>" }. Οποιαδήποτε κατάσταση εκτός 2xx, ή σώμα χωρίς url, κάνει το Orova να σημειώσει τη βελτιστοποίηση ως αποτυχημένη (ποτέ δεν δημιουργεί διπλότυπη ανάρτηση). Τα endpoints που δεν το έχουν υλοποιήσει ακόμη συνεχίζουν να δημοσιεύουν νέα άρθρα κανονικά — η υλοποίηση του update χρειάζεται μόνο όταν χρησιμοποιείτε την οθόνη Optimize.

Από τις 29 Αυγούστου το σώμα ενημέρωσης μπορεί να περιέχει επίσης id (αντιστοιχίστε με αυτό πριν από το target_url), featured_image_url για νέα εικόνα εξωφύλλου, και status με τιμή "publish" ή "draft". Και τα τρία είναι προαιρετικά· όταν είναι κενά αφαιρούνται από το σώμα, δεν στέλνονται ποτέ ως null.

/ Συμβόλαιο — Visual Editor

Ανάγνωση άρθρων (υποχρεωτικό)

Το Orova χρειάζεται να διαβάζει τα άρθρα που ήδη υπάρχουν στον ιστότοπό σας — για να αποφεύγει διπλά θέματα, να προσθέτει εσωτερικούς συνδέσμους και να βελτιστοποιεί παλαιότερες αναρτήσεις. Προσθέστε δύο ακόμη actions στο ίδιο URL endpoint, με το ίδιο Bearer token: το list επιστρέφει τα άρθρα σας, το get επιστρέφει ένα από αυτά πλήρως.

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 έχει ανώτατο όριο 500 και το νεότερο άρθρο έρχεται πρώτο. Για το get, το Orova στέλνει μαζί id, target_url και slug — αντιστοιχίστε με όποιο αναγνωρίζετε, με αυτή τη σειρά. Όταν δεν ταιριάζει τίποτα, απαντήστε 404 με { "ok": false, "err": "not_found" }. Το excerpt στο list είναι προαιρετικό (έως 300 χαρακτήρες). Υποχρεωτικό: το Orova καλεί το list για να μάθει ποια άρθρα υπάρχουν ήδη (για αποφυγή διπλότυπων, εσωτερικούς συνδέσμους και ανάλυση όλου του ιστοτόπου) και καλεί το get για να διαβάσει ένα άρθρο όταν το βελτιστοποιεί. Αν ο ιστότοπός σας δεν απαντά σε αυτές τις δύο actions, τα νέα άρθρα δεν παίρνουν εσωτερικούς συνδέσμους και τα παλαιότερα δεν μπορούν να βελτιστοποιηθούν.

/ Συμβόλαιο — Visual Editor

Ανέβασμα εικόνων (Visual Editor)

Όταν αφήνετε μια εικόνα στο Visual Editor, το Orova τη στέλνει στο ίδιο URL endpoint ως base64 μέσα στο σώμα JSON — χωρίς multipart, χωρίς δεύτερο URL προς προστασία.

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
  }

Δεχτείτε μόνο image/* και απορρίψτε οτιδήποτε πάνω από 15 MB. Αποθηκεύστε το αρχείο και απαντήστε με το απόλυτο URL της εικόνας: μια σχετική διαδρομή χαλάει το άρθρο μόλις εμφανιστεί κάπου αλλού. Αυτή η action χρειάζεται μόνο για το Visual Editor.

/ Συμβόλαιο — delete

Διαγραφή άρθρων

Όταν ένας χρήστης αφαιρεί ένα δημοσιευμένο άρθρο μέσα στο Orova (από την οθόνη Γραφή ή Αναφορές), το Orova ζητά από τον ιστότοπό σας να το διαγράψει κι αυτός. Το αίτημα πηγαίνει στο ίδιο URL endpoint, ως POST με το ίδιο Bearer token — η μόνη διαφορά είναι το σώμα:

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
  }

Βρείτε την ανάρτηση με το url (εναλλακτικά με το slug), αφαιρέστε την ή αποσύρετέ την και απαντήστε 200 με { "deleted": true } — ένα σκέτο 204 λειτουργεί επίσης. Αν η ανάρτηση δεν υπάρχει πια, απαντήστε 404 και το Orova τη θεωρεί ήδη διαγραμμένη. Η επιστροφή { "deleted": false } λέει στο Orova ότι η ανάρτηση δεν αφαιρέθηκε. Τα κανονικά αιτήματα δημοσίευσης δεν περιέχουν ποτέ πεδίο action, οπότε τα υπάρχοντα endpoints συνεχίζουν να λειτουργούν αμετάβλητα — η υλοποίηση του delete είναι προαιρετική αλλά συνιστάται.

/ Ασφάλεια

Σημειώσεις ασφαλείας

  • Επαληθεύετε πάντα το Bearer token. Το endpoint σας είναι δημόσιο URL — οποιοσδήποτε μπορεί να το καλέσει. Η κεφαλίδα Authorization είναι το μόνο που αποδεικνύει ότι ένα αίτημα ήρθε πράγματι από το Orova, οπότε απορρίψτε κάθε αίτημα του οποίου το token δεν ταιριάζει ακριβώς με το αποθηκευμένο μυστικό.
  • Εξυπηρετήστε το endpoint μέσω HTTPS. Το μυστικό ταξιδεύει σε κεφαλίδα. Το HTTPS εμποδίζει την ανάγνωσή του κατά τη μεταφορά.
  • Κρατήστε το μυστικό εκτός κώδικα. Αποθηκεύστε το σε μεταβλητή περιβάλλοντος ή σε διαχειριστή μυστικών, ποτέ γραμμένο μέσα σε αρχείο που κάνετε commit. Αν διαρρεύσει, δημιουργήστε νέο από Έργο → Συνδέσεις.
  • Αντιμετωπίστε το content_html ως περιεχόμενο, όχι ως έμπιστη σήμανση. Αποθηκεύστε και εμφανίστε το με την ίδια προσοχή που θα δείχνατε σε οποιοδήποτε σώμα άρθρου στο CMS σας.

/ Κλείνοντας

Αυτό είναι όλο το συμβόλαιο

Με το endpoint ζωντανό και καταχωρισμένο στο έργο σας, το Orova δημοσιεύει αυτόματα στον ιστότοπό σας — ακριβώς όπως για τους χρήστες WordPress. Κάθε άρθρο που ολοκληρώνει φτάνει στον διακομιστή σας, γίνεται ανάρτηση και το δημόσιο URL του επιστρέφει στις Αναφορές και την Ανάλυση ώστε να παρακολουθείτε την απόδοσή του.

← Πίσω στη βιβλιοθήκη οδηγών

/ Χρειάζεστε βοήθεια;

Κολλήσατε στη σύνδεση του endpoint; Ανοίξτε την ενότητα Υποστήριξη μέσα στον χώρο εργασίας σας ή στείλτε μας μήνυμα.