Documentația OxiRanker
Un centru de documentație clar pentru conectarea site-ului tău la serviciile OxiRanker. Începe cu integrarea WordPress sau integrarea API, apoi urmează ghidul pas cu pas.
Serviciu API
Folosește Export API și Webhook pentru site-uri personalizate, platforme CMS personalizate, Next.js, Laravel, Node.js, PHP sau backenduri Python.
OxiRanker nu este hosting permanent pentru site-ul de destinație
OxiRanker nu oferă garanție de hosting permanent pentru conținutul, imaginile sau fișierele exportate ale site-ului de destinație. OxiRanker nu este un serviciu de hosting pentru conținutul utilizatorilor, iar conținutul generat, imaginile de copertă, imaginile Open Graph sau fișierele de output pot să nu rămână pe infrastructura OxiRanker mai mult de 90 de zile.
Prin urmare, după primirea outputului, site-ul de destinație trebuie să stocheze articolul final, imaginile, datele SEO, datele Open Graph și datele Schema în propria bază de date, pe propriul server, în propriul storage sau CDN și nu trebuie să se bazeze pe URL-urile OxiRanker pentru afișarea publică.
Video de instruire pentru conectare
Folosește acest video ca ghid vizual înainte de conectarea WordPress sau implementarea integrării API.
Implementează fluxul de output pas cu pas
Stochează mai întâi Export Token și Webhook Secret în backendul tău, apoi finalizează Webhook receiver, baza de date, logica UPSERT, Image Mirroring și syncul zilnic.
Înregistrează-te și creează un domeniu în OxiRanker
Mai întâi, înregistrează-te în OxiRanker, autentifică-te în cont și apasă Add Domain din secțiunea Domains.
Format corect al domeniului
În câmpul Domain name, introdu doar domeniul rădăcină. Adresa nu trebuie să includă http, https sau www.
example.comFormate greșite
Nu introduce aceste formate în câmpul domeniului.
https://example.com
http://example.com
www.example.comVerifică proprietatea domeniului
În acest pas, trebuie să dovedești că domeniul îți aparține sau că ai acces pentru administrarea lui.
DNS TXT
RecomandatAdaugă un TXT record în DNS-ul domeniului. Pentru Cloudflare, aceasta este de obicei cea mai simplă metodă.
DNS Name:
_yourdomain-verification.example.com
TXT Value:
acb1****29803HTTP File
Plasează un fișier text în pathul .well-known al site-ului tău, astfel încât OxiRanker să îl poată verifica.
File URL:
https://example.com/.well-known/yourdomain-verification.txt
File Content:
220daaa37****42730f27Meta Tag
Plasează un meta tag în secțiunea head a homepage-ului site-ului tău.
<meta name="yourdomain-verification" content="190208d****d275b65" />Primește tokenuri gratuite după verificarea primului domeniu
După verificarea primului domeniu, OxiRanker adaugă 1000 de tokenuri gratuite în contul tău pentru testare inițială.
Configurează limbile și Blog URL
În secțiunea Blog URLs, definește dacă site-ul tău este într-o singură limbă sau multilingv și ce limbi trebuie folosite pentru articolele generate.
Alege limbile
OxiRanker acceptă 24 de limbi. Poți alege una sau mai multe limbi în funcție de structura site-ului tău.
Introdu Blog URL
Pentru fiecare limbă, introdu blog URL-ul acelei limbi. Dacă site-ul tău este multilingv, introdu separat pathul fiecărei limbi.
https://example.com/fa/blog
https://example.com/en/blogAdaugă cuvinte-cheie pentru fiecare limbă
În tabul Keywords, adaugă cel puțin 3 și până la 50 de cuvinte-cheie pentru fiecare limbă.
Regula principală
Cuvintele-cheie pentru fiecare limbă trebuie introduse în aceeași limbă. Dacă limba articolului este engleza, cuvintele-cheie trebuie să fie tot în engleză.
Cum adaugi cuvinte-cheie
După ce tastezi fiecare cuvânt-cheie, apasă Enter pentru a-l adăuga. După completarea tuturor cuvintelor-cheie, apasă Save.
Completează Article Profile al domeniului
Article Profile îi spune OxiRanker ce este brandul tău, pentru cine scrie, ce ton trebuie să folosească și ce subiecte trebuie să evite.
Industrie
Definește piața sau categoria de business. Exemplu: consultanță imobiliară și cumpărarea și vânzarea proprietăților în Dubai.
Descrierea businessului
Explică ce vinde businessul, pe cine servește, cum funcționează și ce îl face diferit.
Audiență
Definește cui trebuie să se adreseze conținutul. Fiecare audiență poate fi salvată separat.
Ton
Controlează stilul și vocea conținutului generat, cum ar fi formal, educațional, simplu, tehnic sau orientat spre vânzări.
Obiectivul conținutului
Definește ce trebuie să obțină articolele, cum ar fi atragerea clienților, creșterea traficului sau educarea utilizatorilor.
Subiecte interzise
Orice lucru despre care brandul nu ar trebui să vorbească sau cu care nu ar trebui asociat se introduce aici.
CTA opțional
CTA înseamnă acțiunea pe care vrei ca cititorul să o facă după citirea articolului. Dacă nu ești sigur ce text este potrivit, este mai bine să lași acest câmp gol.
Linkuri interne și externe
OxiRanker poate aplica inteligent linkuri interne și externe în interiorul articolelor.
Linkuri interne
Această opțiune este activată implicit și este mai bine să rămână activată. Linkurile interne ajută motoarele de căutare să înțeleagă mai bine structura conținutului site-ului tău.
În primul articol, linkurile interne de obicei nu sunt create deoarece nu există încă un articol anterior. De la al doilea articol înainte, sistemul poate face linking intern.
Linkuri externe
Linkurile externe sunt adăugate către surse oficiale și de încredere și sunt marcate ca nofollow, astfel încât autoritatea SEO să nu fie transferată neintenționat.
Verifică calitatea profilului cu AI
După completarea Article Profile, OxiRanker verifică calitatea profilului.
Conectează Telegram pentru rezumatele articolelor
După salvarea cu succes a profilului, poți activa trimiterea rezumatelor articolelor către un canal Telegram.
Adaugă botul
Adaugă @Oxiranker_Bot în canalul tău Telegram și fă-l Admin.
Introdu username-ul canalului
Introdu numele canalului cu @. Exemplu: @my_channel
Activează livrarea rezumatului
După înregistrarea unui canal valid, activează livrarea rezumatului articolului.
Configurează motorul automat de articole
În tabul Automatic article generation, poți gestiona motorul de generare automată a articolelor.
Selecție inteligentă a subiectului
Sistemul folosește cuvinte-cheie și Article Profile pentru a selecta subiecte potrivite, unice și publicabile.
Generare conștientă de brand
Tonul, audiența, obiectivul conținutului și restricțiile brandului sunt respectate în timpul generării articolului.
Publicare programată
Capacitatea lunară este distribuită de-a lungul lunii, astfel încât publicarea conținutului să pară mai naturală și consistentă.
Capacitate lunară
7 articles / month
10 articles / month
15 articles / month
30 articles / month30 de articole înseamnă aproape un articol pe zi. 15 articole înseamnă aproape o dată la două zile. 10 articole înseamnă aproximativ la fiecare două-trei zile. 7 articole înseamnă aproximativ la fiecare trei-patru zile.
Lungimea articolului și imaginea
Lungimea articolului poate fi Short, Standard sau Long. Standard este o alegere echilibrată pentru majoritatea site-urilor.
Imaginile articolelor pot fi activate sau dezactivate, iar tema imaginii poate fi Light sau Dark.
Creează primul articol din Generate
După pregătirea domeniului, deschide meniul Generate, alege domeniul și apasă New Content sau New Article.
Limbă
Pentru fiecare cerere de conținut se selectează o singură limbă.
Subiect
Scrie un subiect clar. Source URL este opțional.
Imagine
Activează sau dezactivează imaginea și alege tema Light sau Dark.
Plată și generare
Costul este dedus din wallet și începe generarea articolului.
Verifică Preview, SEO Report și JSON Output
După generarea articolului, sunt disponibile mai multe outputuri importante pentru verificarea calității și pregătirea publicării.
Preview
Afișează textul articolului și imaginea generată. Această secțiune este utilă pentru verificarea calității finale înainte de publicare.
SEO Report
Afișează un raport SEO ușor de înțeles, inclusiv SEO Score, title, meta description, canonical, keywords, schema, headings, links, images și OpenGraph.
JSON Output
Afișează outputul complet al articolului pentru utilizatori tehnici, inclusiv slug, html_body, schema, URL-uri de imagini, tags, category, timp de citire și timestampuri created/updated.
Ce fac exact Export API și Webhook?
După ce un articol este generat în OxiRanker, site-ul de destinație îl poate citi cu Export API sau poate fi notificat prin Webhook atunci când articolul este gata.
Metoda 1: Export API
Site-ul de destinație poate citi articole din OxiRanker cu Export Token ori de câte ori este necesar. Această metodă este folosită pentru sync manual, sync zilnic, pagini de listă a articolelor și fetch pentru JSON-ul complet al articolului.
GET https://oxiranker.com/api/v1/users/domains/1/export/articles?limit=20&offset=0
Authorization: Bearer YOUR_EXPORT_TOKEN
Accept: application/jsonGET https://oxiranker.com/api/v1/users/domains/1/articles/5/export/json
Authorization: Bearer YOUR_EXPORT_TOKEN
Accept: application/jsonMetoda 2: Webhook
Când este creat un articol nou sau un articol este actualizat, OxiRanker notifică endpointul site-ului de destinație. Webhook nu trimite articolul complet. Trimite doar identificatorul articolului, astfel încât site-ul de destinație să poată face fetch pentru articolul complet din 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"
}Responsabilitatea stocării conținutului și Image Mirroring
Site-ul de destinație trebuie să stocheze articolul final și imaginile pe propria infrastructură.
Ce trebuie să stocheze site-ul de destinație
Exemplu corect de path imagine
Folosirea directă și permanentă a URL-urilor imaginilor OxiRanker nu este corectă. Imaginea trebuie descărcată, iar URL-urile articolului trebuie înlocuite cu URL-uri interne ale site-ului de destinație.
https://oxiranker.com/uploads/articles/3_5/cover.webphttps://example.com/uploads/site-articles/oxiranker/1/5/cover.webpObține Export Token și endpointuri gata
Export Token și endpointurile gata de utilizare sunt disponibile din pagina articolului, tabul Outputs și secțiunea API Output.
De unde se obține Export Token
https://oxiranker.com/en/pannel/generate/{domainId}/articles/{articleId}Endpointuri gata de utilizare
Pe aceeași pagină a articolului, în secțiunea API Output, Ready-to-use endpoints afișează URL-uri pregătite.
https://oxiranker.com/api/v1/users/domains/1/export/articles?limit=20&offset=0https://oxiranker.com/api/v1/users/domains/1/articles/1/export/jsonhttps://oxiranker.com/api/v1/users/domains/1/articles/1/export/html?canonical_mode=absolute&include_css=0Creează Webhook Secret și endpointul de destinație
Webhook Secret este creat tot din pagina articolului, tabul Outputs și secțiunea API Output.
Unde se creează Webhook Secret?
Endpoint fix de destinație
OxiRanker acceptă doar următorul path fix pentru Webhook de destinație. Site-ul de destinație trebuie să creeze acest endpoint în backendul său.
POST /api/oxiranker/webhook
https://example.com/api/oxiranker/webhookAcest endpoint trebuie să accepte doar POST, să păstreze raw body, să verifice timestamp și signature și să returneze un răspuns de succes pentru webhook.test.
Valori .env necesare pentru site-ul de destinație
OxiRanker nu are acces la fișierele site-ului de destinație sau la .env. Dezvoltatorul site-ului de destinație trebuie să stocheze aceste valori în propriul 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=50Valori de securitate
Valori de stocare
Export API: List, JSON, HTML și ZIP
Site-ul de destinație poate folosi Export API pentru a primi lista articolelor sau outputul complet al fiecărui articol.
Obține lista articolelor pentru un domeniu
GET https://oxiranker.com/api/v1/users/domains/:domainId/export/articles?limit=20&offset=0
Authorization: Bearer YOUR_EXPORT_TOKEN
Accept: application/jsonlimit definește numărul de articole și este în prezent limitat între 1 și 50. offset este folosit pentru paginație. lang este opțional și este folosit pentru a face fetch articolelor într-o limbă specifică precum fa, en sau tr.
Obține articolul complet ca JSON
GET https://oxiranker.com/api/v1/users/domains/:domainId/articles/:articleId/export/json
Authorization: Bearer YOUR_EXPORT_TOKEN
Accept: application/jsonAcesta este cel mai important output pentru site-ul de destinație. Site-ul de destinație trebuie să stocheze JSON-ul complet al articolului în propria bază de date și să citească din propria bază de date pentru afișarea publică.
Obține HTML gata
GET https://oxiranker.com/api/v1/users/domains/1/articles/5/export/html?canonical_mode=absolute&include_css=0
Authorization: Bearer YOUR_EXPORT_TOKENHTML este potrivit pentru site-uri care doresc output gata, dar abordarea mai profesională este stocarea JSON și renderizarea lui cu propriul template al site-ului de destinație.
Obține output ZIP
GET https://oxiranker.com/api/v1/users/domains/1/articles/5/export/zip
Authorization: Bearer YOUR_EXPORT_TOKENOutputul ZIP include article.json, meta.json, assets.json, schema.jsonld, index.html, version.txt și README.txt.
Webhook Signature și primirea sigură a Webhook
Site-ul de destinație trebuie să verifice semnătura Webhook folosind raw body original.
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=...Formula semnăturii
payload_to_sign = timestamp + "." + rawBody
signature = "sha256=" + HMAC_SHA256(webhook_secret, payload_to_sign)Dacă site-ul de destinație parsează mai întâi JSON și apoi îl stringify din nou, ordinea sau spațiile se pot schimba și semnătura poate deveni invalidă. Raw body original trebuie folosit pentru verificarea semnăturii.
Exemplu simplu de cod Node.js pentru primirea Webhook
Acest exemplu Express gestionează raw body, timestamp, signature, webhook.test și evenimentele articolului.
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,
});
});Bază de date recomandată pentru site-ul de destinație
Site-ul de destinație trebuie să stocheze articolele în propria bază de date și să prevină duplicatele cu un 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);Previne duplicatele cu UPSERT
Articolul nu trebuie inserat din nou pentru fiecare Webhook. Dacă există deja, trebuie actualizat.
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();Fetch articol complet și sync după Webhook
Când sosește un Webhook, site-ul de destinație trebuie să facă fetch pentru articolul complet din Export API, să facă mirror imaginilor și să facă UPSERT articolului.
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,
});
}Image Mirroring și reguli de stocare a imaginilor
Site-ul de destinație trebuie să descarce imaginile, să le stocheze pe propria infrastructură și să înlocuiască URL-urile articolului.
Reguli Image Mirroring
Path de stocare recomandat
/uploads/site-articles/oxiranker/1/5/cover.webp
/uploads/site-articles/oxiranker/1/5/og.webphttps://example.com/uploads/site-articles/oxiranker/1/5/cover.webpSync zilnic pentru fiabilitate
Webhook singur nu este suficient. Site-ul de destinație trebuie să aibă și un sync zilnic pentru recuperarea articolelor ratate.
De ce este necesar syncul zilnic?
Algoritm sync zilnic
Afișează articolele pe site-ul de destinație
După stocarea articolelor în site_articles, site-ul de destinație nu trebuie să se conecteze la OxiRanker pentru fiecare afișare publică. Trebuie să citească din propria bază de date.
Pagină listă 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;Pagină detaliu articol
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;SEO, Schema, RTL și HTML rendering
Site-ul de destinație trebuie să citească datele SEO din propria bază de date și să le renderizeze pe pagina articolului.
Câmpuri SEO
HTML rendering
<div
className="article-content"
dangerouslySetInnerHTML={{ __html: article.html_body }}
/>Dacă site-ul de destinație are o politică strictă de securitate, poate sanitiza HTML înainte de salvare sau înainte de afișare.
RTL și LTR
Fiecare articol are câmpurile lang, dir și is_rtl. Dacă dir este rtl, pagina articolului trebuie renderizată de la dreapta la stânga.
<article dir="rtl">
...
</article>Erori comune și semnificațiile lor
Erorile site-ului de destinație trebuie să fie în engleză, clare și ușor de înțeles, astfel încât depanarea să se facă rapid.
Erori Export API
Erori Webhook
Checklist final pentru testarea API și Webhook
După implementare, aceste teste verifică conexiunea de la început până la final.
Test Export Token și JSON
GET https://oxiranker.com/api/v1/users/domains/1/export/articles?limit=2&offset=0
Authorization: Bearer YOUR_EXPORT_TOKEN
Accept: application/jsonGET https://oxiranker.com/api/v1/users/domains/1/articles/5/export/json
Authorization: Bearer YOUR_EXPORT_TOKEN
Accept: application/jsonTest Webhook
Dacă apelezi Webhookul de destinație cu Postman fără semnătură, trebuie să primești o eroare de securitate. Asta înseamnă că endpointul este activ și verifică securitatea.
{
"error": "missing_webhook_signature",
"message": "Webhook signature header is required."
}Apoi apasă Test Webhook în OxiRanker. Dacă receiverul acceptă webhook.test, response_status trebuie să fie 200.
Reguli importante pentru implementare corectă
Aceste reguli trebuie urmate în integrarea API pentru ca sistemul să rămână sigur, stabil și ușor de întreținut.
Rezumat complet al fluxului API
După finalizarea acestor pași, site-ul tău personalizat poate primi, stoca, mirrora și publica în siguranță articole generate în OxiRanker.