ΤΕΚΜΗΡΙΩΣΗ ΠΡΟΓΡΑΜΜΑΤΙΣΤΩΝ OXIRANKER

Τεκμηρίωση OxiRanker

Ένα καθαρό κέντρο τεκμηρίωσης για τη σύνδεση του ιστοτόπου σας με τις υπηρεσίες OxiRanker. Ξεκινήστε με την ενσωμάτωση WordPress ή την ενσωμάτωση API και ακολουθήστε τον οδηγό βήμα προς βήμα.

Επίσημη τεκμηρίωσηΠλήρης έκδοση V1Για ιδιοκτήτες ιστοτόπων, προγραμματιστές και ομάδες ενσωμάτωσης
Μενού τεκμηρίωσης
Επιλέξτε έναν οδηγό από το μενού. Αυτή η σελίδα είναι το κύριο κέντρο τεκμηρίωσης για το OxiRanker.
Υπηρεσία API

Υπηρεσία API

Χρησιμοποιήστε Export API και Webhook για προσαρμοσμένους ιστοτόπους, προσαρμοσμένες πλατφόρμες CMS, Next.js, Laravel, Node.js, PHP ή Python backends.

Domain
Επαληθευμένο
Περιεχόμενο
Έτοιμο
API
Webhook
Σημαντική ειδοποίηση αποθήκευσης

Το OxiRanker δεν είναι μόνιμη φιλοξενία για τον ιστότοπο προορισμού

Το OxiRanker δεν παρέχει εγγύηση μόνιμης φιλοξενίας για το περιεχόμενο, τις εικόνες ή τα εξαγόμενα αρχεία του ιστοτόπου προορισμού. Το OxiRanker δεν είναι υπηρεσία φιλοξενίας περιεχομένου χρηστών και το δημιουργημένο περιεχόμενο, οι εικόνες εξωφύλλου, οι εικόνες Open Graph ή τα αρχεία εξόδου ενδέχεται να μην παραμείνουν στην υποδομή του OxiRanker για περισσότερο από 90 ημέρες.

Επομένως, μετά τη λήψη της εξόδου, ο ιστότοπος προορισμού πρέπει να αποθηκεύσει το τελικό άρθρο, τις εικόνες, τα δεδομένα SEO, τα δεδομένα Open Graph και τα δεδομένα Schema στη δική του βάση δεδομένων, διακομιστή, αποθηκευτικό χώρο ή CDN και δεν πρέπει να βασίζεται σε URL του OxiRanker για δημόσια εμφάνιση.

Εκπαιδευτικό βίντεο

Εκπαιδευτικό βίντεο σύνδεσης

Χρησιμοποιήστε αυτό το βίντεο ως οπτική καθοδήγηση πριν συνδέσετε το WordPress ή υλοποιήσετε την ενσωμάτωση API.

Έξοδος API

Υλοποιήστε τη ροή εξόδου βήμα προς βήμα

Αποθηκεύστε πρώτα το Export Token και το Webhook Secret στο backend σας και έπειτα ολοκληρώστε τον Webhook receiver, τη βάση δεδομένων, τη λογική UPSERT, το Image Mirroring και το ημερήσιο sync.

ΒΗΜΑ 01

Εγγραφή και δημιουργία domain στο OxiRanker

Πρώτα, εγγραφείτε στο OxiRanker, συνδεθείτε στον λογαριασμό σας και κάντε κλικ στο Add Domain από την ενότητα Domains.

Σωστή μορφή domain

Στο πεδίο Domain name, εισαγάγετε μόνο το root domain. Η διεύθυνση δεν πρέπει να περιλαμβάνει http, https ή www.

example.com

Λανθασμένες μορφές

Μην εισαγάγετε αυτές τις μορφές στο πεδίο domain.

https://example.com
http://example.com
www.example.com
Μετά τη δημιουργία του domain
Αν η μορφή του domain είναι έγκυρη, το OxiRanker δημιουργεί το domain και σας μεταφέρει στο βήμα επαλήθευσης ιδιοκτησίας domain.
ΒΗΜΑ 02

Επαλήθευση ιδιοκτησίας domain

Σε αυτό το βήμα, πρέπει να αποδείξετε ότι το domain σας ανήκει ή ότι έχετε πρόσβαση για τη διαχείρισή του.

DNS TXT

Προτείνεται

Προσθέστε ένα TXT record στο DNS του domain. Για το Cloudflare, αυτή είναι συνήθως η πιο απλή μέθοδος.

DNS Name:
_yourdomain-verification.example.com

TXT Value:
acb1****29803

HTTP File

Τοποθετήστε ένα αρχείο κειμένου στη διαδρομή .well-known του ιστοτόπου σας ώστε το OxiRanker να μπορεί να το επαληθεύσει.

File URL:
https://example.com/.well-known/yourdomain-verification.txt

File Content:
220daaa37****42730f27

Meta Tag

Τοποθετήστε ένα meta tag μέσα στην ενότητα head της αρχικής σελίδας του ιστοτόπου σας.

<meta name="yourdomain-verification" content="190208d****d275b65" />
Σημαντική σημείωση DNS
Μετά την προσθήκη του DNS record, ειδικά στο Cloudflare, ίσως χρειαστεί να περιμένετε 2 έως 3 λεπτά. Έπειτα κάντε κλικ στο Confirm στην ίδια σελίδα του OxiRanker.
Διάρκεια ζωής token επαλήθευσης
Οι τιμές επαλήθευσης domain έχουν χρονικό όριο. Προς το παρόν, οι τιμές επαλήθευσης είναι συνήθως έγκυρες για 1440 λεπτά.
ΒΗΜΑ 03

Λήψη δωρεάν tokens μετά την επαλήθευση του πρώτου domain

Μετά την επαλήθευση του πρώτου domain, το OxiRanker προσθέτει 1000 δωρεάν tokens στον λογαριασμό σας για αρχικές δοκιμές.

Μόνο για το πρώτο domain
Αυτό το δώρο προστίθεται μόνο για το πρώτο επαληθευμένο domain και δεν προστίθεται ξανά για επόμενα domains.
ΒΗΜΑ 04

Ρύθμιση γλωσσών και Blog URL

Στην ενότητα Blog URLs, ορίστε αν ο ιστότοπός σας είναι μονόγλωσσος ή πολύγλωσσος και ποιες γλώσσες πρέπει να χρησιμοποιούνται για τα δημιουργημένα άρθρα.

Επιλογή γλωσσών

Το OxiRanker υποστηρίζει 24 γλώσσες. Μπορείτε να επιλέξετε μία ή περισσότερες γλώσσες ανάλογα με τη δομή του ιστοτόπου σας.

Εισαγωγή Blog URL

Για κάθε γλώσσα, εισαγάγετε το Blog URL της συγκεκριμένης γλώσσας. Αν ο ιστότοπός σας είναι πολύγλωσσος, εισαγάγετε ξεχωριστά τη διαδρομή κάθε γλώσσας.

https://example.com/fa/blog
https://example.com/en/blog
Δομή URL
Αν ο ιστότοπός σας έχει διαφορετική δομή, εισαγάγετε το πραγματικό Blog URL του ιστοτόπου σας. Το σημαντικό είναι ότι το τελικό URL πρέπει να είναι η διαδρομή όπου θα δημοσιεύονται τα άρθρα.
ΒΗΜΑ 05

Προσθήκη λέξεων-κλειδιών για κάθε γλώσσα

Στην καρτέλα Keywords, προσθέστε τουλάχιστον 3 και έως 50 λέξεις-κλειδιά για κάθε γλώσσα.

Βασικός κανόνας

Οι λέξεις-κλειδιά για κάθε γλώσσα πρέπει να εισάγονται στην ίδια γλώσσα. Αν η γλώσσα του άρθρου είναι αγγλικά, οι λέξεις-κλειδιά πρέπει επίσης να είναι στα αγγλικά.

Πώς να προσθέσετε λέξεις-κλειδιά

Αφού πληκτρολογήσετε κάθε λέξη-κλειδί, πατήστε Enter για να την προσθέσετε. Αφού ολοκληρώσετε όλες τις λέξεις-κλειδιά, κάντε κλικ στο Save.

Συνηθισμένο λάθος
Αν εισαγάγετε τουρκικές λέξεις-κλειδιά για την αγγλική γλώσσα, ενδέχεται να εμφανιστούν τουρκικές φράσεις στο αγγλικό περιεχόμενο. Η γλώσσα των λέξεων-κλειδιών πρέπει να ταιριάζει με τη γλώσσα του άρθρου.
ΒΗΜΑ 06

Ολοκλήρωση του Article Profile του domain

Το Article Profile λέει στο OxiRanker τι είναι το brand σας, για ποιον γράφει, ποιον τόνο πρέπει να χρησιμοποιεί και ποια θέματα πρέπει να αποφεύγει.

Κλάδος

Ορίζει την αγορά ή την επιχειρηματική κατηγορία. Παράδειγμα: συμβουλευτική ακινήτων και αγοραπωλησίες ακινήτων στο Ντουμπάι.

Περιγραφή επιχείρησης

Εξηγεί τι πουλά η επιχείρηση, ποιον εξυπηρετεί, πώς λειτουργεί και τι την κάνει διαφορετική.

Κοινό

Ορίζει σε ποιο κοινό πρέπει να απευθύνεται το περιεχόμενο. Κάθε κοινό μπορεί να αποθηκευτεί ξεχωριστά.

Τόνος

Ελέγχει το ύφος και τη φωνή του δημιουργημένου περιεχομένου, όπως επίσημο, εκπαιδευτικό, απλό, τεχνικό ή προσανατολισμένο στις πωλήσεις.

Στόχος περιεχομένου

Ορίζει τι πρέπει να πετυχαίνουν τα άρθρα, όπως προσέλκυση πελατών, αύξηση επισκεψιμότητας ή εκπαίδευση χρηστών.

Απαγορευμένα θέματα

Οτιδήποτε δεν πρέπει να συζητά το brand ή δεν πρέπει να συνδέεται με αυτό εισάγεται εδώ.

Προαιρετικό CTA

CTA σημαίνει την ενέργεια που θέλετε να κάνει ο αναγνώστης μετά την ανάγνωση του άρθρου. Αν δεν είστε σίγουροι ποιο κείμενο είναι κατάλληλο, είναι καλύτερο να αφήσετε αυτό το πεδίο κενό.

Πολύγλωσσο προφίλ
Αν ο ιστότοπός σας είναι πολύγλωσσος, χρειάζεται να ολοκληρώσετε το προφίλ μόνο σε μία γλώσσα. Το OxiRanker μπορεί να προετοιμάσει τις απαιτούμενες εκδόσεις για άλλες γλώσσες με βάση το ίδιο προφίλ.
ΒΗΜΑ 07

Εσωτερική και εξωτερική διασύνδεση

Το OxiRanker μπορεί να εφαρμόζει έξυπνα εσωτερικούς και εξωτερικούς συνδέσμους μέσα στα άρθρα.

Εσωτερικοί σύνδεσμοι

Αυτή η επιλογή είναι ενεργή από προεπιλογή και είναι καλύτερο να παραμείνει ενεργή. Η εσωτερική διασύνδεση βοηθά τις μηχανές αναζήτησης να κατανοήσουν καλύτερα τη δομή περιεχομένου του ιστοτόπου σας.

Στο πρώτο άρθρο, συνήθως δεν δημιουργούνται εσωτερικοί σύνδεσμοι επειδή δεν υπάρχει ακόμη προηγούμενο άρθρο. Από το δεύτερο άρθρο και μετά, το σύστημα μπορεί να εκτελεί εσωτερική διασύνδεση.

Εξωτερικοί σύνδεσμοι

Οι εξωτερικοί σύνδεσμοι προστίθενται σε επίσημες και αξιόπιστες πηγές και επισημαίνονται ως nofollow ώστε να μην μεταφέρεται ακούσια SEO authority.

ΒΗΜΑ 08

Έλεγχος ποιότητας προφίλ με AI

Μετά την ολοκλήρωση του Article Profile, το OxiRanker ελέγχει την ποιότητα του προφίλ.

85+
Ελάχιστη βαθμολογία που απαιτείται για αποθήκευση
95
Εξαιρετική βαθμολογία που συνήθως επιτυγχάνεται μετά από βελτιώσεις
AI
Επαγγελματικές προτάσεις για κάθε πεδίο
Αν η βαθμολογία είναι κάτω από 85
Το σύστημα δεν επιτρέπει την τελική αποθήκευση και εμφανίζει εξηγήσεις και προτάσεις βελτίωσης για κάθε προβληματικό πεδίο. Μπορείτε να εφαρμόσετε τις προτάσεις και να αποθηκεύσετε ξανά.
ΒΗΜΑ 09

Σύνδεση Telegram για περιλήψεις άρθρων

Μετά την επιτυχή αποθήκευση του προφίλ, μπορείτε να ενεργοποιήσετε την αποστολή περιλήψεων άρθρων σε ένα κανάλι Telegram.

1

Προσθήκη του bot

Προσθέστε το @Oxiranker_Bot στο κανάλι σας στο Telegram και κάντε το Admin.

2

Εισαγωγή ονόματος χρήστη καναλιού

Εισαγάγετε το όνομα του καναλιού με @. Παράδειγμα: @my_channel

3

Ενεργοποίηση αποστολής περίληψης

Μετά την καταχώριση ενός έγκυρου καναλιού, ενεργοποιήστε την αποστολή περίληψης άρθρων.

Όφελος Telegram
Αν είναι ενεργό, μετά τη δημιουργία περιεχομένου, η περίληψη του άρθρου αποστέλλεται στο κανάλι Telegram μαζί με την εικόνα και τον σύνδεσμο. Αυτό μπορεί να ενισχύσει τις διαδρομές επισκεψιμότητας προς τον ιστότοπό σας.
ΒΗΜΑ 10

Ρύθμιση της αυτόματης μηχανής άρθρων

Στην καρτέλα Automatic article generation, μπορείτε να διαχειριστείτε τη μηχανή αυτόματης δημιουργίας άρθρων.

Έξυπνη επιλογή θέματος

Το σύστημα χρησιμοποιεί λέξεις-κλειδιά και Article Profile για να επιλέγει κατάλληλα, μοναδικά και δημοσιεύσιμα θέματα.

Δημιουργία με επίγνωση του brand

Ο τόνος, το κοινό, ο στόχος περιεχομένου και οι περιορισμοί του brand λαμβάνονται υπόψη κατά τη δημιουργία του άρθρου.

Προγραμματισμένη δημοσίευση

Η μηνιαία χωρητικότητα κατανέμεται μέσα στον μήνα ώστε η δημοσίευση περιεχομένου να φαίνεται πιο φυσική και συνεπής.

Μηνιαία χωρητικότητα

7 articles / month
10 articles / month
15 articles / month
30 articles / month

30 άρθρα σημαίνει σχεδόν ένα άρθρο την ημέρα. 15 άρθρα σημαίνει σχεδόν μέρα παρά μέρα. 10 άρθρα σημαίνει περίπου κάθε δύο έως τρεις ημέρες. 7 άρθρα σημαίνει περίπου κάθε τρεις έως τέσσερις ημέρες.

Μήκος άρθρου και εικόνα

Το μήκος άρθρου μπορεί να είναι Short, Standard ή Long. Το Standard είναι μια ισορροπημένη επιλογή για τους περισσότερους ιστοτόπους.

Οι εικόνες άρθρων μπορούν να ενεργοποιηθούν ή να απενεργοποιηθούν και το θέμα της εικόνας μπορεί να είναι Light ή Dark.

Σημείωση για ιστοτόπους API
Η αυτόματη μηχανή δημιουργεί άρθρα μέσα στο OxiRanker. Για να μεταφέρετε κάθε άρθρο στον προσαρμοσμένο ιστότοπό σας μετά τη δημιουργία, πρέπει να υλοποιήσετε Export API, Webhook, αποθήκευση βάσης δεδομένων και Image Mirroring.
ΒΗΜΑ 11

Δημιουργία του πρώτου άρθρου από το Generate

Μετά την προετοιμασία του domain, ανοίξτε το μενού Generate, επιλέξτε το domain και κάντε κλικ στο New Content ή New Article.

1

Γλώσσα

Επιλέγεται μόνο μία γλώσσα για κάθε αίτημα περιεχομένου.

2

Θέμα

Γράψτε ένα σαφές θέμα. Το Source URL είναι προαιρετικό.

3

Εικόνα

Ενεργοποιήστε ή απενεργοποιήστε την εικόνα και επιλέξτε θέμα Light ή Dark.

4

Πληρωμή και δημιουργία

Το κόστος αφαιρείται από το wallet και ξεκινά η δημιουργία του άρθρου.

Χρόνος δημιουργίας
Η δημιουργία άρθρου και εικόνας συνήθως διαρκεί περίπου 2 έως 3 λεπτά. Μετά την ολοκλήρωση, το άρθρο είναι ορατό μέσα στο OxiRanker.
Source URL
Αν εισαχθεί Source URL, το OxiRanker το χρησιμοποιεί για να κατανοήσει καλύτερα το θέμα, αλλά δεν δημιουργεί αντιγραμμένο περιεχόμενο. Το σύστημα έχει σχεδιαστεί για να δημιουργεί μοναδικό περιεχόμενο.
ΒΗΜΑ 12

Έλεγχος Preview, SEO Report και JSON Output

Μετά τη δημιουργία του άρθρου, διατίθενται αρκετές σημαντικές έξοδοι για έλεγχο ποιότητας και προετοιμασία δημοσίευσης.

Preview

Εμφανίζει το κείμενο του άρθρου και τη δημιουργημένη εικόνα. Αυτή η ενότητα είναι χρήσιμη για τον έλεγχο της τελικής ποιότητας πριν από τη δημοσίευση.

SEO Report

Εμφανίζει μια κατανοητή αναφορά SEO που περιλαμβάνει SEO Score, title, meta description, canonical, keywords, schema, headings, links, images και OpenGraph.

JSON Output

Εμφανίζει την πλήρη έξοδο του άρθρου για τεχνικούς χρήστες, συμπεριλαμβανομένων των slug, html_body, schema, URL εικόνων, tags, category, reading time και timestamps δημιουργίας/ενημέρωσης.

Σημασία των εξόδων
Αυτές οι έξοδοι δείχνουν ότι το άρθρο δεν είναι απλό κείμενο. Είναι δομημένο, έτοιμο για SEO περιεχόμενο, προετοιμασμένο για WordPress, CMS, API και συστήματα δημοσίευσης.
ΒΗΜΑ 13

Τι ακριβώς κάνουν το Export API και το Webhook;

Αφού δημιουργηθεί ένα άρθρο στο OxiRanker, ο ιστότοπος προορισμού μπορεί να το διαβάσει με Export API ή να ειδοποιηθεί μέσω Webhook όταν το άρθρο είναι έτοιμο.

Μέθοδος 1: Export API

Ο ιστότοπος προορισμού μπορεί να διαβάζει άρθρα από το OxiRanker με το Export Token όποτε χρειάζεται. Αυτή η μέθοδος χρησιμοποιείται για χειροκίνητο sync, ημερήσιο sync, σελίδες λίστας άρθρων και λήψη του πλήρους JSON άρθρου.

GET https://oxiranker.com/api/v1/users/domains/1/export/articles?limit=20&offset=0
Authorization: Bearer YOUR_EXPORT_TOKEN
Accept: application/json
GET https://oxiranker.com/api/v1/users/domains/1/articles/5/export/json
Authorization: Bearer YOUR_EXPORT_TOKEN
Accept: application/json

Μέθοδος 2: Webhook

Όταν δημιουργείται ένα νέο άρθρο ή ενημερώνεται ένα άρθρο, το OxiRanker ειδοποιεί το endpoint του ιστοτόπου προορισμού. Το Webhook δεν στέλνει το πλήρες άρθρο. Στέλνει μόνο το αναγνωριστικό του άρθρου ώστε ο ιστότοπος προορισμού να μπορεί να λάβει το πλήρες άρθρο από το Export JSON API.

https://example.com/api/oxiranker/webhook
{
  "event": "article.created",
  "domain_id": "1",
  "article_id": "5",
  "request_id": "5",
  "lang": "fa",
  "slug": "smart-internal-links",
  "occurred_at": "2026-05-20T19:35:33.000Z"
}
Πολύ σημαντική σημείωση
Το Webhook είναι μόνο ένα ελαφρύ μήνυμα. Ο ιστότοπος προορισμού δεν πρέπει να περιμένει το πλήρες άρθρο μέσα στο Webhook. Μετά τη λήψη των domain_id και article_id, πρέπει να λάβει το πλήρες άρθρο από το Export JSON API και να το αποθηκεύσει στη δική του βάση δεδομένων.
ΒΗΜΑ 14

Ευθύνη αποθήκευσης περιεχομένου και Image Mirroring

Ο ιστότοπος προορισμού πρέπει να αποθηκεύει το τελικό άρθρο και τις εικόνες στη δική του υποδομή.

Τι πρέπει να αποθηκεύει ο ιστότοπος προορισμού

Πλήρες κείμενο άρθρου στη βάση δεδομένων του ιστοτόπου προορισμού
Κύρια εικόνα άρθρου στον διακομιστή, αποθηκευτικό χώρο ή CDN του προορισμού
Εικόνα Open Graph στον διακομιστή, αποθηκευτικό χώρο ή CDN του προορισμού
Δεδομένα SEO, OpenGraph και Article Schema
raw_detail_payload και raw_list_item για διατήρηση της αρχικής εξόδου

Παράδειγμα σωστής διαδρομής εικόνας

Η άμεση και μόνιμη χρήση URL εικόνων του OxiRanker δεν είναι σωστή. Η εικόνα πρέπει να ληφθεί και τα URL του άρθρου πρέπει να αντικατασταθούν με εσωτερικά URL του ιστοτόπου προορισμού.

https://oxiranker.com/uploads/articles/3_5/cover.webp
https://example.com/uploads/site-articles/oxiranker/1/5/cover.webp
Το OxiRanker δεν είναι μόνιμη φιλοξενία για τον ιστότοπο προορισμού
Το OxiRanker δεν είναι υπεύθυνο για τη μόνιμη αποθήκευση περιεχομένου και εικόνων εξόδου για τον ιστότοπο προορισμού. Το OxiRanker δεν είναι υπηρεσία φιλοξενίας περιεχομένου χρηστών και το δημιουργημένο περιεχόμενο, οι εικόνες εξωφύλλου, οι εικόνες Open Graph ή τα αρχεία εξόδου ενδέχεται να μην παραμείνουν στην υποδομή του OxiRanker για περισσότερο από 90 ημέρες. Επομένως, ο ιστότοπος προορισμού πρέπει να αποθηκεύει το τελικό άρθρο, τις εικόνες, τα δεδομένα SEO, το OpenGraph και το Schema στη δική του βάση δεδομένων, διακομιστή, αποθηκευτικό χώρο ή CDN.
ΒΗΜΑ 15

Λήψη Export Token και έτοιμων endpoints

Το Export Token και τα έτοιμα προς χρήση endpoints είναι διαθέσιμα από τη σελίδα άρθρου, την καρτέλα Outputs και την ενότητα API Output.

Πού θα λάβετε το Export Token

Δημιουργήστε το πρώτο άρθρο στο OxiRanker.
Ανοίξτε τη σελίδα εκείνου του άρθρου.
Ανοίξτε την καρτέλα Outputs.
Ανοίξτε την ενότητα API Output.
Δημιουργήστε token από το Token management.
Το πλήρες token εμφανίζεται μόνο μία φορά.
Αποθηκεύστε το token στο backend ή στο .env του ιστοτόπου προορισμού.
https://oxiranker.com/en/pannel/generate/{domainId}/articles/{articleId}

Έτοιμα προς χρήση endpoints

Στην ίδια σελίδα άρθρου, μέσα στην ενότητα API Output, το Ready-to-use endpoints εμφανίζει προετοιμασμένα URL.

https://oxiranker.com/api/v1/users/domains/1/export/articles?limit=20&offset=0
https://oxiranker.com/api/v1/users/domains/1/articles/1/export/json
https://oxiranker.com/api/v1/users/domains/1/articles/1/export/html?canonical_mode=absolute&include_css=0
Περιστροφή του Token
Αν το Export Token περιστραφεί στον πίνακα OxiRanker, το προηγούμενο token λήγει. Η νέα τιμή πρέπει να αντικαταστήσει αμέσως την παλιά τιμή στο .env ή στο backend environment του ιστοτόπου προορισμού και το backend προορισμού πρέπει να επανεκκινηθεί ή να γίνει redeploy.
ΒΗΜΑ 16

Δημιουργία Webhook Secret και endpoint προορισμού

Το Webhook Secret δημιουργείται επίσης από τη σελίδα άρθρου, την καρτέλα Outputs και την ενότητα API Output.

Πού δημιουργείται το Webhook Secret;

Ανοίξτε τη σελίδα άρθρου.
Ανοίξτε την καρτέλα Outputs.
Ανοίξτε την ενότητα API Output.
Δημιουργήστε secret στην ενότητα Webhook Secret.
Το πλήρες secret εμφανίζεται μόνο μία φορά.
Αποθηκεύστε το secret στο .env του ιστοτόπου προορισμού.

Σταθερό endpoint προορισμού

Το OxiRanker αποδέχεται μόνο την ακόλουθη σταθερή διαδρομή για το Webhook προορισμού. Ο ιστότοπος προορισμού πρέπει να δημιουργήσει αυτό το endpoint στο backend του.

POST /api/oxiranker/webhook

https://example.com/api/oxiranker/webhook

Αυτό το endpoint πρέπει να αποδέχεται μόνο POST, να διατηρεί το raw body, να επαληθεύει timestamp και signature και να επιστρέφει επιτυχή απόκριση για webhook.test.

Ενεργά events
Στην τρέχουσα έκδοση, τα events είναι σταθερά: article.created και article.updated. Για δοκιμή της σύνδεσης, αποστέλλεται η τιμή event webhook.test.
ΒΗΜΑ 17

Απαιτούμενες τιμές .env για τον ιστότοπο προορισμού

Το OxiRanker δεν έχει πρόσβαση στα αρχεία του ιστοτόπου προορισμού ή στο .env. Ο προγραμματιστής του ιστοτόπου προορισμού πρέπει να αποθηκεύσει αυτές τις τιμές στο δικό του backend.

OXIRANKER_EXPORT_API_BASE_URL=https://oxiranker.com
OXIRANKER_EXPORT_API_TOKEN=YOUR_EXPORT_TOKEN
OXIRANKER_WEBHOOK_SECRET=YOUR_WEBHOOK_SECRET
OXIRANKER_WEBHOOK_MAX_SKEW_MS=300000
OXIRANKER_SOURCE_PROVIDER=oxiranker
OXIRANKER_SOURCE_DOMAIN_IDS=1

SITE_ARTICLE_IMAGE_UPLOAD_DIR=/var/www/example.com/uploads/site-articles
SITE_ARTICLE_IMAGE_MAX_BYTES=10485760

PUBLIC_BASE_URL=https://example.com

OXIRANKER_SYNC_ENABLED=true
OXIRANKER_SYNC_HOUR=3
OXIRANKER_SYNC_MINUTE=10
OXIRANKER_SYNC_PAGE_LIMIT=20
OXIRANKER_SYNC_MAX_PAGES=50

Τιμές ασφαλείας

OXIRANKER_EXPORT_API_TOKEN
Το Export API token πρέπει να αποθηκεύεται μόνο στο backend.
OXIRANKER_WEBHOOK_SECRET
Το Webhook Secret πρέπει να αποθηκεύεται μόνο στο backend.
OXIRANKER_WEBHOOK_MAX_SKEW_MS
Η μέγιστη επιτρεπόμενη χρονική απόκλιση για το Webhook. Το 300000 σημαίνει 5 λεπτά.

Τιμές αποθήκευσης

SITE_ARTICLE_IMAGE_UPLOAD_DIR
Η φυσική διαδρομή για την αποθήκευση των mirrored εικόνων στον διακομιστή προορισμού.
SITE_ARTICLE_IMAGE_MAX_BYTES
Το μέγιστο επιτρεπόμενο μέγεθος εικόνας για λήψη και αποθήκευση.
PUBLIC_BASE_URL
Το δημόσιο URL του ιστοτόπου προορισμού που χρησιμοποιείται για τη δημιουργία τελικών URL εικόνων και άρθρων.
Ασφάλεια Frontend
Το Export Token και το Webhook Secret δεν πρέπει να τοποθετούνται μέσα σε frontend code, JavaScript ορατό στον χρήστη, localStorage ή το window object.
ΒΗΜΑ 18

Export API: List, JSON, HTML και ZIP

Ο ιστότοπος προορισμού μπορεί να χρησιμοποιήσει το Export API για να λάβει τη λίστα άρθρων ή την πλήρη έξοδο κάθε άρθρου.

Λήψη της λίστας άρθρων για ένα domain

GET https://oxiranker.com/api/v1/users/domains/:domainId/export/articles?limit=20&offset=0
Authorization: Bearer YOUR_EXPORT_TOKEN
Accept: application/json

Το limit ορίζει τον αριθμό των άρθρων και προς το παρόν περιορίζεται μεταξύ 1 και 50. Το offset χρησιμοποιείται για pagination. Το lang είναι προαιρετικό και χρησιμοποιείται για λήψη άρθρων σε συγκεκριμένη γλώσσα όπως fa, en ή tr.

Λήψη του πλήρους άρθρου ως JSON

GET https://oxiranker.com/api/v1/users/domains/:domainId/articles/:articleId/export/json
Authorization: Bearer YOUR_EXPORT_TOKEN
Accept: application/json

Αυτή είναι η πιο σημαντική έξοδος για τον ιστότοπο προορισμού. Ο ιστότοπος προορισμού πρέπει να αποθηκεύει το πλήρες JSON του άρθρου στη δική του βάση δεδομένων και να διαβάζει από τη δική του βάση δεδομένων για δημόσια εμφάνιση.

Λήψη έτοιμου HTML

GET https://oxiranker.com/api/v1/users/domains/1/articles/5/export/html?canonical_mode=absolute&include_css=0
Authorization: Bearer YOUR_EXPORT_TOKEN

Το HTML είναι κατάλληλο για ιστοτόπους που θέλουν έτοιμη έξοδο, αλλά η πιο επαγγελματική προσέγγιση είναι η αποθήκευση JSON και η απόδοσή του με το δικό template του ιστοτόπου προορισμού.

Λήψη εξόδου ZIP

GET https://oxiranker.com/api/v1/users/domains/1/articles/5/export/zip
Authorization: Bearer YOUR_EXPORT_TOKEN

Η έξοδος ZIP περιλαμβάνει article.json, meta.json, assets.json, schema.jsonld, index.html, version.txt και README.txt.

ΒΗΜΑ 19

Υπογραφή Webhook και ασφαλής λήψη Webhook

Ο ιστότοπος προορισμού πρέπει να επαληθεύει την υπογραφή Webhook χρησιμοποιώντας το αρχικό raw body.

Webhook headers

content-type: application/json
user-agent: Oxiranker-Webhooks/1.0
x-oxiranker-event: article.created
x-oxiranker-domain-id: 1
x-oxiranker-timestamp: 2026-05-20T19:35:33.000Z
x-oxiranker-signature: sha256=...

Τύπος υπογραφής

payload_to_sign = timestamp + "." + rawBody
signature = "sha256=" + HMAC_SHA256(webhook_secret, payload_to_sign)

Αν ο ιστότοπος προορισμού κάνει πρώτα parse το JSON και έπειτα το ξανακάνει stringify, η σειρά ή τα κενά μπορεί να αλλάξουν και η υπογραφή να γίνει άκυρη. Το αρχικό raw body πρέπει να χρησιμοποιείται για την επαλήθευση της υπογραφής.

Timing-safe σύγκριση
Μην συγκρίνετε υπογραφές με expected === received. Στο Node.js, είναι καλύτερο να χρησιμοποιείτε crypto.timingSafeEqual.
ΒΗΜΑ 20

Απλό δείγμα κώδικα Node.js για λήψη Webhook

Αυτό το δείγμα Express χειρίζεται raw body, timestamp, signature, webhook.test και article events.

import express from "express";
import crypto from "crypto";

const app = express();

app.use(
  express.json({
    verify: (req, _res, buf) => {
      (req as any).rawBody = Buffer.from(buf);
    },
  })
);

function getHeader(req: express.Request, name: string): string {
  const value = req.headers[name.toLowerCase()];
  if (Array.isArray(value)) return String(value[0] || "").trim();
  return String(value || "").trim();
}

function getRawBody(req: express.Request): string {
  const raw = (req as any).rawBody;
  if (Buffer.isBuffer(raw)) return raw.toString("utf8");
  return JSON.stringify(req.body || {});
}

function createSignature(secret: string, timestamp: string, rawBody: string): string {
  const payload = `${timestamp}.${rawBody}`;
  const digest = crypto
    .createHmac("sha256", secret)
    .update(payload, "utf8")
    .digest("hex");

  return `sha256=${digest}`;
}

function timingSafeEqualText(a: string, b: string): boolean {
  const ab = Buffer.from(a, "utf8");
  const bb = Buffer.from(b, "utf8");

  if (ab.length !== bb.length) return false;
  return crypto.timingSafeEqual(ab, bb);
}

function isTimestampValid(timestamp: string): boolean {
  const maxSkewMs = Number(process.env.OXIRANKER_WEBHOOK_MAX_SKEW_MS || 300000);
  const date = new Date(timestamp);

  if (Number.isNaN(date.getTime())) return false;

  const diff = Math.abs(Date.now() - date.getTime());
  return diff <= maxSkewMs;
}

app.post("/api/oxiranker/webhook", async (req, res) => {
  const secret = String(process.env.OXIRANKER_WEBHOOK_SECRET || "").trim();

  if (!secret) {
    return res.status(500).json({
      error: "webhook_secret_missing",
      message: "Webhook secret is not configured.",
    });
  }

  const eventHeader = getHeader(req, "x-oxiranker-event");
  const domainIdHeader = getHeader(req, "x-oxiranker-domain-id");
  const timestamp = getHeader(req, "x-oxiranker-timestamp");
  const receivedSignature = getHeader(req, "x-oxiranker-signature");
  const rawBody = getRawBody(req);

  if (!eventHeader) {
    return res.status(400).json({
      error: "missing_webhook_event",
      message: "Webhook event header is required.",
    });
  }

  if (!domainIdHeader) {
    return res.status(400).json({
      error: "missing_webhook_domain_id",
      message: "Webhook domain id header is required.",
    });
  }

  if (!timestamp) {
    return res.status(400).json({
      error: "missing_webhook_timestamp",
      message: "Webhook timestamp header is required.",
    });
  }

  if (!receivedSignature) {
    return res.status(400).json({
      error: "missing_webhook_signature",
      message: "Webhook signature header is required.",
    });
  }

  if (!isTimestampValid(timestamp)) {
    return res.status(401).json({
      error: "invalid_webhook_timestamp",
      message: "Webhook timestamp is expired or invalid.",
    });
  }

  const expectedSignature = createSignature(secret, timestamp, rawBody);

  if (!timingSafeEqualText(expectedSignature, receivedSignature)) {
    return res.status(401).json({
      error: "invalid_webhook_signature",
      message: "Invalid webhook signature.",
    });
  }

  const payload = req.body || {};
  const event = String(payload.event || eventHeader || "").trim();

  if (event === "webhook.test") {
    const sourceDomainId = Number(payload.domain_id || domainIdHeader);

    return res.status(200).json({
      ok: true,
      message: "Webhook test received successfully.",
      event,
      source_domain_id:
        Number.isFinite(sourceDomainId) && sourceDomainId > 0 ? sourceDomainId : null,
      test: true,
    });
  }

  if (event !== "article.created" && event !== "article.updated") {
    return res.status(400).json({
      error: "unsupported_webhook_event",
      message: "Webhook event is not supported.",
    });
  }

  const sourceDomainId = Number(payload.domain_id || domainIdHeader);
  const sourceArticleId = Number(payload.article_id);

  if (!Number.isFinite(sourceDomainId) || sourceDomainId <= 0) {
    return res.status(400).json({
      error: "invalid_domain_id",
      message: "Webhook domain id is invalid.",
    });
  }

  if (!Number.isFinite(sourceArticleId) || sourceArticleId <= 0) {
    return res.status(400).json({
      error: "invalid_article_id",
      message: "Webhook article id is invalid.",
    });
  }

  // Fetch the full article from Export API and store it in the database here.
  // await syncArticleFromOxiranker(sourceDomainId, sourceArticleId);

  return res.status(200).json({
    ok: true,
    message: "Webhook processed successfully.",
    event,
    source_domain_id: sourceDomainId,
    source_article_id: sourceArticleId,
  });
});
ΒΗΜΑ 21

Προτεινόμενη βάση δεδομένων ιστοτόπου προορισμού

Ο ιστότοπος προορισμού πρέπει να αποθηκεύει άρθρα στη δική του βάση δεδομένων και να αποτρέπει διπλότυπα με unique constraint.

CREATE TABLE public.site_articles (
  id BIGSERIAL PRIMARY KEY,

  source_provider VARCHAR NOT NULL DEFAULT 'oxiranker',
  source_domain_id BIGINT NOT NULL,
  source_article_id BIGINT NOT NULL,
  source_request_id BIGINT NULL,

  lang VARCHAR NOT NULL,
  lang_label VARCHAR NULL,
  dir VARCHAR NOT NULL,
  is_rtl BOOLEAN NULL,

  length VARCHAR NULL,
  topic_text TEXT NULL,

  slug VARCHAR NOT NULL,
  canonical_path VARCHAR NULL,
  effective_canonical_url TEXT NULL,

  title TEXT NOT NULL,
  meta_description TEXT NULL,
  html_body TEXT NULL,

  focus_keywords JSONB NOT NULL DEFAULT '[]'::jsonb,
  category VARCHAR NULL,
  tags JSONB NOT NULL DEFAULT '[]'::jsonb,

  reading_time_minutes INTEGER NULL,

  og_title TEXT NULL,
  og_description TEXT NULL,

  cover_image_url TEXT NULL,
  og_image_url TEXT NULL,

  article_schema JSONB NOT NULL DEFAULT '{}'::jsonb,

  raw_list_item JSONB NOT NULL DEFAULT '{}'::jsonb,
  raw_detail_payload JSONB NOT NULL DEFAULT '{}'::jsonb,

  source_created_at TIMESTAMPTZ NULL,
  source_updated_at TIMESTAMPTZ NULL,

  sync_status VARCHAR NOT NULL DEFAULT 'synced',
  last_synced_at TIMESTAMPTZ NOT NULL DEFAULT now(),
  last_sync_error TEXT NULL,

  publish_status VARCHAR NOT NULL DEFAULT 'draft',
  published_at TIMESTAMPTZ NULL,
  deleted_at TIMESTAMPTZ NULL,

  created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
  updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),

  source_cover_image_url TEXT NULL,
  source_og_image_url TEXT NULL,

  image_mirror_status VARCHAR NOT NULL DEFAULT 'pending',
  image_mirror_error TEXT NULL,
  image_mirrored_at TIMESTAMPTZ NULL,

  CONSTRAINT site_articles_dir_check
    CHECK (dir IN ('ltr', 'rtl')),

  CONSTRAINT site_articles_reading_time_check
    CHECK (reading_time_minutes IS NULL OR reading_time_minutes >= 0),

  CONSTRAINT site_articles_sync_status_check
    CHECK (sync_status IN ('pending', 'syncing', 'synced', 'failed')),

  CONSTRAINT site_articles_publish_status_check
    CHECK (publish_status IN ('draft', 'published', 'archived')),

  CONSTRAINT site_articles_image_mirror_status_check
    CHECK (image_mirror_status IN ('pending', 'not_needed', 'mirrored', 'failed')),

  CONSTRAINT site_articles_source_unique
    UNIQUE (source_provider, source_domain_id, source_article_id)
);
CREATE INDEX site_articles_source_lookup_idx
ON public.site_articles (source_provider, source_domain_id, source_article_id);

CREATE INDEX site_articles_source_updated_at_idx
ON public.site_articles (source_updated_at DESC);

CREATE INDEX site_articles_slug_idx
ON public.site_articles (slug)
WHERE deleted_at IS NULL;

CREATE UNIQUE INDEX site_articles_lang_slug_active_unique
ON public.site_articles (lang, slug)
WHERE deleted_at IS NULL;

CREATE INDEX site_articles_lang_publish_idx
ON public.site_articles (lang, publish_status)
WHERE deleted_at IS NULL;

CREATE INDEX site_articles_published_at_idx
ON public.site_articles (published_at DESC)
WHERE deleted_at IS NULL;

CREATE INDEX site_articles_focus_keywords_gin_idx
ON public.site_articles USING gin (focus_keywords);

CREATE INDEX site_articles_tags_gin_idx
ON public.site_articles USING gin (tags);

CREATE INDEX site_articles_article_schema_gin_idx
ON public.site_articles USING gin (article_schema);
Σημείωση PostgreSQL
Στο PostgreSQL, χρησιμοποιούνται απλά εισαγωγικά για περσικό ή Unicode κείμενο. Κάτι όπως N'...' δεν χρειάζεται και δεν πρέπει να χρησιμοποιείται.
ΒΗΜΑ 22

Αποτροπή διπλοτύπων με UPSERT

Το άρθρο δεν πρέπει να εισάγεται ξανά για κάθε Webhook. Αν υπάρχει ήδη, πρέπει να ενημερώνεται.

Κύριο κλειδί για την αποτροπή διπλοτύπων
Το πιο σημαντικό constraint για την αποτροπή διπλοτύπων είναι: UNIQUE (source_provider, source_domain_id, source_article_id)
INSERT INTO public.site_articles (
  source_provider,
  source_domain_id,
  source_article_id,
  source_request_id,
  lang,
  dir,
  slug,
  title,
  meta_description,
  html_body,
  focus_keywords,
  category,
  tags,
  reading_time_minutes,
  cover_image_url,
  og_image_url,
  article_schema,
  source_created_at,
  source_updated_at,
  sync_status,
  last_synced_at,
  publish_status,
  published_at,
  created_at,
  updated_at
)
VALUES (
  'oxiranker',
  1,
  5,
  5,
  'fa',
  'rtl',
  'smart-internal-links',
  'Article title',
  'Meta description',
  '<p>HTML body</p>',
  '[]'::jsonb,
  'SEO',
  '[]'::jsonb,
  7,
  'https://example.com/uploads/site-articles/oxiranker/1/5/cover.webp',
  'https://example.com/uploads/site-articles/oxiranker/1/5/cover.webp',
  '{}'::jsonb,
  '2026-05-20T19:33:23.618Z',
  '2026-05-20T19:34:09.629Z',
  'synced',
  now(),
  'published',
  now(),
  now(),
  now()
)
ON CONFLICT (source_provider, source_domain_id, source_article_id)
DO UPDATE SET
  source_request_id = EXCLUDED.source_request_id,
  lang = EXCLUDED.lang,
  dir = EXCLUDED.dir,
  slug = EXCLUDED.slug,
  title = EXCLUDED.title,
  meta_description = EXCLUDED.meta_description,
  html_body = EXCLUDED.html_body,
  focus_keywords = EXCLUDED.focus_keywords,
  category = EXCLUDED.category,
  tags = EXCLUDED.tags,
  reading_time_minutes = EXCLUDED.reading_time_minutes,
  cover_image_url = EXCLUDED.cover_image_url,
  og_image_url = EXCLUDED.og_image_url,
  article_schema = EXCLUDED.article_schema,
  source_created_at = EXCLUDED.source_created_at,
  source_updated_at = EXCLUDED.source_updated_at,
  sync_status = 'synced',
  last_synced_at = now(),
  last_sync_error = NULL,
  publish_status = CASE
    WHEN public.site_articles.publish_status = 'archived'
    THEN public.site_articles.publish_status
    ELSE 'published'
  END,
  published_at = COALESCE(public.site_articles.published_at, EXCLUDED.published_at, now()),
  deleted_at = NULL,
  updated_at = now();
ΒΗΜΑ 23

Fetch του πλήρους άρθρου και sync μετά το Webhook

Όταν φτάσει ένα Webhook, ο ιστότοπος προορισμού πρέπει να κάνει fetch το πλήρες άρθρο από το Export API, να κάνει mirror τις εικόνες και να κάνει UPSERT το άρθρο.

async function fetchOxirankerArticleDetail(args: {
  baseUrl: string;
  token: string;
  domainId: number;
  articleId: number;
}) {
  const url = `${args.baseUrl.replace(/\/+$/, "")}/api/v1/users/domains/${args.domainId}/articles/${args.articleId}/export/json`;

  const response = await fetch(url, {
    method: "GET",
    headers: {
      Authorization: `Bearer ${args.token}`,
      Accept: "application/json",
      "User-Agent": "My-Site-Oxiranker-Sync/1.0",
    },
  });

  const bodyText = await response.text();

  if (response.status < 200 || response.status > 299) {
    throw new Error(
      `Export API request failed with HTTP status ${response.status}. Response: ${bodyText.slice(0, 1000)}`
    );
  }

  try {
    return JSON.parse(bodyText);
  } catch {
    throw new Error("Export API returned invalid JSON.");
  }
}
async function syncArticleFromWebhook(payload: any) {
  const sourceDomainId = Number(payload.domain_id);
  const sourceArticleId = Number(payload.article_id);

  if (!Number.isFinite(sourceDomainId) || sourceDomainId <= 0) {
    throw new Error("Webhook domain id is invalid.");
  }

  if (!Number.isFinite(sourceArticleId) || sourceArticleId <= 0) {
    throw new Error("Webhook article id is invalid.");
  }

  const article = await fetchOxirankerArticleDetail({
    baseUrl: process.env.OXIRANKER_EXPORT_API_BASE_URL!,
    token: process.env.OXIRANKER_EXPORT_API_TOKEN!,
    domainId: sourceDomainId,
    articleId: sourceArticleId,
  });

  const mirroredImages = await mirrorArticleImages(article);

  const finalHtmlBody = replaceImageUrls(article.html_body, mirroredImages);
  const finalArticleSchema = replaceImageUrlsInJson(article.article_schema, mirroredImages);

  await upsertSiteArticle({
    source_provider: process.env.OXIRANKER_SOURCE_PROVIDER || "oxiranker",
    source_domain_id: sourceDomainId,
    source_article_id: sourceArticleId,
    source_request_id: article.request_id,
    lang: article.lang,
    dir: article.dir,
    slug: article.slug,
    title: article.title,
    meta_description: article.meta_description,
    html_body: finalHtmlBody,
    focus_keywords: article.focus_keywords,
    category: article.category,
    tags: article.tags,
    reading_time_minutes: article.reading_time_minutes,
    cover_image_url: mirroredImages.cover_image_url,
    og_image_url: mirroredImages.og_image_url,
    article_schema: finalArticleSchema,
    raw_detail_payload: article,
    source_created_at: article.created_at,
    source_updated_at: article.updated_at,
  });
}
ΒΗΜΑ 24

Image Mirroring και κανόνες αποθήκευσης εικόνων

Ο ιστότοπος προορισμού πρέπει να κατεβάζει εικόνες, να τις αποθηκεύει στη δική του υποδομή και να αντικαθιστά τα URL του άρθρου.

Κανόνες Image Mirroring

Πρέπει να γίνονται αποδεκτά μόνο HTTPS URL.
Το αρχείο πρέπει πράγματι να είναι εικόνα.
Το content-type πρέπει να ξεκινά με image/.
Τα κενά αρχεία δεν πρέπει να αποθηκεύονται.
Το μέγεθος του αρχείου δεν πρέπει να υπερβαίνει το όριο.
Αν το cover και το og είναι ίδια, κατεβάστε μόνο μία φορά.
Μετά το mirroring, αντικαταστήστε τα URL μέσα στο html_body και στο article_schema.

Προτεινόμενη διαδρομή αποθήκευσης

/uploads/site-articles/oxiranker/1/5/cover.webp
/uploads/site-articles/oxiranker/1/5/og.webp
https://example.com/uploads/site-articles/oxiranker/1/5/cover.webp
ΒΗΜΑ 25

Ημερήσιο sync για αξιοπιστία

Το Webhook από μόνο του δεν είναι αρκετό. Ο ιστότοπος προορισμού πρέπει επίσης να έχει ημερήσιο sync για την ανάκτηση χαμένων άρθρων.

Γιατί χρειάζεται ημερήσιο sync;

Ο ιστότοπος προορισμού μπορεί να είναι προσωρινά εκτός λειτουργίας.
Το Webhook μπορεί να κάνει timeout.
Μπορεί να προκύψουν προβλήματα deployment ή δικτύου.
Ένα άρθρο μπορεί να δημιουργηθεί αλλά το Webhook του μπορεί να χαθεί.

Αλγόριθμος ημερήσιου sync

Ξεκινήστε το offset από 0.
Χρησιμοποιήστε limit 20 ή 50.
Κάντε fetch τη λίστα άρθρων από το Export List API.
Για κάθε στοιχείο, κάντε fetch το Export JSON εκείνου του άρθρου.
Κάντε mirror τις εικόνες.
Αποθηκεύστε το άρθρο με UPSERT.
Αυξήστε το offset και συνεχίστε μέχρι να ολοκληρωθεί το total.
ΒΗΜΑ 26

Εμφάνιση άρθρων στον ιστότοπο προορισμού

Μετά την αποθήκευση άρθρων στο site_articles, ο ιστότοπος προορισμού δεν πρέπει να συνδέεται με το OxiRanker για κάθε δημόσια εμφάνιση. Πρέπει να διαβάζει από τη δική του βάση δεδομένων.

Σελίδα λίστας blog

SELECT
  id,
  lang,
  lang_label,
  dir,
  is_rtl,
  slug,
  title,
  meta_description,
  category,
  tags,
  reading_time_minutes,
  cover_image_url,
  og_image_url,
  source_created_at,
  published_at,
  created_at
FROM public.site_articles
WHERE source_provider = 'oxiranker'
  AND deleted_at IS NULL
  AND sync_status = 'synced'
  AND publish_status = 'published'
ORDER BY COALESCE(source_created_at, published_at, created_at) DESC, id DESC
LIMIT 20 OFFSET 0;

Σελίδα λεπτομερειών άρθρου

SELECT *
FROM public.site_articles
WHERE source_provider = 'oxiranker'
  AND lang = 'fa'
  AND slug = 'smart-internal-links'
  AND deleted_at IS NULL
  AND sync_status = 'synced'
  AND publish_status = 'published'
LIMIT 1;
Προτεινόμενο URL άρθρου
Για πολύγλωσσους ιστοτόπους, μπορείτε να χρησιμοποιήσετε το μοτίβο /{lang}/blog/{slug}. Παράδειγμα: /fa/blog/smart-internal-links
ΒΗΜΑ 27

SEO, Schema, RTL και HTML rendering

Ο ιστότοπος προορισμού πρέπει να διαβάζει δεδομένα SEO από τη δική του βάση δεδομένων και να τα αποδίδει στη σελίδα άρθρου.

Πεδία SEO

title για τον τίτλο της σελίδας
meta_description για meta description
effective_canonical_url για canonical
og_title και og_description για Open Graph
og_image_url ή cover_image_url για εικόνα κοινωνικής προεπισκόπησης
article_schema για JSON-LD

HTML rendering

<div
  className="article-content"
  dangerouslySetInnerHTML={{ __html: article.html_body }}
/>

Αν ο ιστότοπος προορισμού έχει αυστηρή πολιτική ασφαλείας, μπορεί να κάνει sanitize το HTML πριν την αποθήκευση ή πριν την εμφάνιση.

RTL και LTR

Κάθε άρθρο έχει πεδία lang, dir και is_rtl. Αν το dir είναι rtl, η σελίδα άρθρου πρέπει να αποδίδεται από δεξιά προς τα αριστερά.

<article dir="rtl">
  ...
</article>
ΒΗΜΑ 28

Συνηθισμένα σφάλματα και η σημασία τους

Τα σφάλματα του ιστοτόπου προορισμού πρέπει να είναι στα αγγλικά, σαφή και κατανοητά ώστε η αντιμετώπιση προβλημάτων να γίνεται γρήγορα.

Σφάλματα Export API

Λείπει το domain export token.
Το Export API token δεν στάλθηκε. Πρέπει να σταλεί Authorization: Bearer YOUR_EXPORT_TOKEN.
Μη έγκυρο domain export token.
Το token είναι λάθος, έχει περιστραφεί, δεν σχετίζεται με αυτό το domain ή το domainId είναι λάθος.
Το token δεν έχει το απαιτούμενο scope.
Το token δεν έχει το απαιτούμενο scope. Τα νέα tokens πρέπει να έχουν articles:read και exports:read.
Μη έγκυρο lang.
Η τιμή lang στο query parameter στάλθηκε λανθασμένα.

Σφάλματα Webhook

Το Webhook URL πρέπει να χρησιμοποιεί HTTPS.
Το Webhook URL πρέπει να ξεκινά με https://.
Μη έγκυρη υπογραφή Webhook.
Το Webhook Secret είναι λάθος, το secret έχει περιστραφεί, το raw body άλλαξε ή η υπογραφή υπολογίστηκε λανθασμένα.
Το timestamp του Webhook έχει λήξει ή είναι μη έγκυρο.
Το timestamp είναι παλιό, βρίσκεται στο μέλλον ή είναι μη έγκυρο. Ελέγξτε το ρολόι του διακομιστή και το NTP.
Η αποστολή δοκιμής Webhook απέτυχε.
Ο ιστότοπος προορισμού δεν επέστρεψε επιτυχή κατάσταση από 200 έως 299, έκανε timeout ή δεν χειρίστηκε σωστά το webhook.test.
ΒΗΜΑ 29

Τελική λίστα ελέγχου API και Webhook

Μετά την υλοποίηση, αυτές οι δοκιμές επαληθεύουν τη σύνδεση από την αρχή μέχρι το τέλος.

Δοκιμή Export Token και JSON

GET https://oxiranker.com/api/v1/users/domains/1/export/articles?limit=2&offset=0
Authorization: Bearer YOUR_EXPORT_TOKEN
Accept: application/json
GET https://oxiranker.com/api/v1/users/domains/1/articles/5/export/json
Authorization: Bearer YOUR_EXPORT_TOKEN
Accept: application/json

Δοκιμή Webhook

Αν καλέσετε το Webhook προορισμού με Postman χωρίς υπογραφή, πρέπει να λάβετε σφάλμα ασφαλείας. Αυτό σημαίνει ότι το endpoint είναι ενεργό και ελέγχει την ασφάλεια.

{
  "error": "missing_webhook_signature",
  "message": "Webhook signature header is required."
}

Έπειτα κάντε κλικ στο Test Webhook μέσα στο OxiRanker. Αν ο receiver υποστηρίζει webhook.test, το response_status πρέπει να είναι 200.

Μετά την περιστροφή
Αν γίνει περιστροφή του Webhook Secret ή του Export Token, αντιγράψτε αμέσως τη νέα τιμή, αντικαταστήστε τη στο .env του ιστοτόπου προορισμού και επανεκκινήστε ή κάντε redeploy το backend προορισμού.
ΒΗΜΑ 30

Σημαντικοί κανόνες για σωστή υλοποίηση

Αυτοί οι κανόνες πρέπει να ακολουθούνται στην ενσωμάτωση API ώστε το σύστημα να παραμένει ασφαλές, σταθερό και συντηρήσιμο.

Το Export Token πρέπει να αποθηκεύεται μόνο στο backend.
Το Webhook Secret πρέπει να αποθηκεύεται μόνο στο backend.
Το endpoint του Webhook προορισμού πρέπει να είναι ακριβώς /api/oxiranker/webhook.
Το Webhook URL πρέπει να χρησιμοποιεί HTTPS.
Η υπογραφή Webhook και το timestamp πρέπει πάντα να επαληθεύονται.
Τα άρθρα πρέπει να αποθηκεύονται με UPSERT, όχι με απλό insert.
Απαιτείται unique constraint για την αποτροπή διπλοτύπων.
Οι εικόνες πρέπει να κατεβαίνουν από το OxiRanker και να αποθηκεύονται στον ιστότοπο προορισμού.
Ο ιστότοπος προορισμού δεν πρέπει να εξαρτάται μόνιμα από URL εικόνων του OxiRanker.
Τα URL εικόνων πρέπει να αντικαθίστανται μέσα στο html_body και στο article_schema.
Αν αποτύχει το Webhook, το ημερήσιο sync πρέπει να ανακτήσει το χαμένο άρθρο.
Τα σφάλματα που εμφανίζονται στους χρήστες ή στα logs πρέπει να είναι στα αγγλικά και κατανοητά.
Αν ένα άρθρο είναι αρχειοθετημένο, το αυτόματο sync δεν πρέπει να το δημοσιεύσει ξανά.
Τα raw_detail_payload και raw_list_item πρέπει να διατηρούνται ώστε να μη χαθούν δεδομένα της αρχικής εξόδου.
Αν γίνει περιστροφή του Export Token ή του Webhook Secret, η νέα τιμή πρέπει να αντικατασταθεί στο .env και το backend πρέπει να επανεκκινηθεί ή να γίνει redeploy.
ΒΗΜΑ 31

Πλήρης σύνοψη ροής API

Μετά την ολοκλήρωση αυτών των βημάτων, ο προσαρμοσμένος ιστότοπός σας μπορεί να λαμβάνει, να αποθηκεύει, να κάνει mirror και να δημοσιεύει με ασφάλεια άρθρα που δημιουργούνται από το OxiRanker.

Εγγραφή στο OxiRanker
Προσθήκη domain
Επαλήθευση ιδιοκτησίας domain
Επιλογή γλωσσών και προσθήκη Blog URL
Προσθήκη λέξεων-κλειδιών για κάθε γλώσσα
Ολοκλήρωση Article Profile
Ρύθμιση Telegram αν χρειάζεται
Ρύθμιση της αυτόματης μηχανής άρθρων
Δημιουργία του πρώτου άρθρου από το Generate
Έλεγχος Preview και SEO Report
Λήψη Export Token από το API Output
Λήψη έτοιμων Export API endpoints
Λήψη Webhook Secret
Δημιουργία του σταθερού endpoint /api/oxiranker/webhook στο backend προορισμού
Αποθήκευση Token και Secret στο .env του ιστοτόπου προορισμού
Δημιουργία του πίνακα site_articles και unique constraint
Υλοποίηση λήψης πλήρους άρθρου από Export JSON
Υλοποίηση Image Mirroring
Υλοποίηση UPSERT
Υλοποίηση ημερήσιου sync
Render άρθρου, SEO, OG και Schema από τη βάση δεδομένων προορισμού
Εκτέλεση τελικών δοκιμών Export API και Webhook