OxiRanker փաստաթղթեր
Մաքուր փաստաթղթերի կենտրոն՝ ձեր կայքը OxiRanker ծառայություններին միացնելու համար։ Սկսեք WordPress ինտեգրումից կամ API ինտեգրումից, ապա քայլ առ քայլ հետևեք ուղեցույցին։
API ծառայություն
Օգտագործեք Export API և Webhook անհատական կայքերի, անհատական CMS հարթակների, Next.js, Laravel, Node.js, PHP կամ Python backend-ների համար։
OxiRanker-ը նպատակակետ կայքի համար մշտական hosting չէ
OxiRanker-ը նպատակակետ կայքի բովանդակության, պատկերների կամ արտահանված ֆայլերի համար մշտական hosting-ի երաշխիք չի տրամադրում։ OxiRanker-ը user-content hosting ծառայություն չէ, և ստեղծված բովանդակությունը, cover պատկերները, Open Graph պատկերները կամ output ֆայլերը կարող են OxiRanker ենթակառուցվածքում չմնալ 90 օրից ավելի։
Հետևաբար, output-ը ստանալուց հետո նպատակակետ կայքը պետք է վերջնական հոդվածը, պատկերները, SEO տվյալները, Open Graph տվյալները և Schema տվյալները պահի իր սեփական database-ում, server-ում, storage-ում կամ CDN-ում և չպետք է public display-ի համար կախված լինի OxiRanker URL-ներից։
Միացման ուսուցողական տեսանյութ
Օգտագործեք այս տեսանյութը որպես տեսողական ուղեցույց՝ նախքան WordPress-ը միացնելը կամ API ինտեգրումը իրականացնելը։
Իրականացրեք output flow-ը քայլ առ քայլ
Նախ պահեք Export Token-ը և Webhook Secret-ը ձեր backend-ում, ապա ավարտեք Webhook receiver-ը, database-ը, UPSERT logic-ը, Image Mirroring-ը և օրական sync-ը։
Գրանցվել և ստեղծել domain OxiRanker-ում
Նախ գրանցվեք OxiRanker-ում, մուտք գործեք ձեր account և Domains բաժնից սեղմեք Add Domain։
Domain-ի ճիշտ ձևաչափ
Domain name դաշտում մուտքագրեք միայն root domain-ը։ Հասցեն չպետք է ներառի http, https կամ www։
example.comՍխալ ձևաչափեր
Այս ձևաչափերը մի մուտքագրեք domain դաշտում։
https://example.com
http://example.com
www.example.comՀաստատել domain-ի սեփականությունը
Այս քայլում դուք պետք է ապացուցեք, որ domain-ը պատկանում է ձեզ կամ ունեք այն կառավարելու հասանելիություն։
DNS TXT
Առաջարկվում էԱվելացրեք TXT record domain DNS-ում։ Cloudflare-ի համար սա սովորաբար ամենապարզ մեթոդն է։
DNS Name:
_yourdomain-verification.example.com
TXT Value:
acb1****29803HTTP File
Տեղադրեք text file ձեր կայքի .well-known path-ում, որպեսզի OxiRanker-ը կարողանա այն verify անել։
File URL:
https://example.com/.well-known/yourdomain-verification.txt
File Content:
220daaa37****42730f27Meta Tag
Տեղադրեք meta tag ձեր կայքի homepage-ի head section-ում։
<meta name="yourdomain-verification" content="190208d****d275b65" />Ստանալ անվճար token-ներ առաջին domain-ը verify անելուց հետո
Առաջին domain-ը verify անելուց հետո OxiRanker-ը ձեր account-ին ավելացնում է 1000 անվճար token initial testing-ի համար։
Կարգավորել լեզուները և Blog URL-ը
Blog URLs բաժնում սահմանեք՝ ձեր կայքը մեկ լեզվո՞վ է, թե՞ բազմալեզու, և որ լեզուները պետք է օգտագործվեն generated articles-ի համար։
Ընտրել լեզուներ
OxiRanker-ը աջակցում է 24 լեզու։ Կարող եք ընտրել մեկ կամ մի քանի լեզու՝ ըստ ձեր կայքի կառուցվածքի։
Մուտքագրել Blog URL
Յուրաքանչյուր լեզվի համար մուտքագրեք այդ լեզվի blog URL-ը։ Եթե ձեր կայքը multilingual է, յուրաքանչյուր language path-ը մուտքագրեք առանձին։
https://example.com/fa/blog
https://example.com/en/blogԱվելացնել keywords յուրաքանչյուր լեզվի համար
Keywords tab-ում յուրաքանչյուր լեզվի համար ավելացրեք առնվազն 3 և առավելագույնը 50 keywords։
Հիմնական կանոն
Յուրաքանչյուր լեզվի keywords-ը պետք է մուտքագրվեն նույն լեզվով։ Եթե article language-ը English է, keywords-ը նույնպես պետք է English լինեն։
Ինչպես ավելացնել keywords
Յուրաքանչյուր keyword մուտքագրելուց հետո սեղմեք Enter՝ այն ավելացնելու համար։ Բոլոր keywords-ը լրացնելուց հետո սեղմեք Save։
Լրացնել domain Article Profile-ը
Article Profile-ը OxiRanker-ին ասում է՝ ինչ է ձեր brand-ը, ում համար է գրում, ինչ tone պետք է օգտագործի և որ topics-ից պետք է խուսափի։
Industry
Սահմանում է market-ը կամ business category-ն։ Օրինակ՝ real estate consulting և property buying and selling Dubai-ում։
Business description
Բացատրում է՝ business-ը ինչ է վաճառում, ում է ծառայում, ինչպես է աշխատում և ինչն է այն տարբեր դարձնում։
Audience
Սահմանում է՝ content-ը ում հետ պետք է խոսի։ Յուրաքանչյուր audience կարող է պահվել առանձին։
Tone
Կառավարում է generated content-ի style-ը և voice-ը, օրինակ՝ formal, educational, simple, technical կամ sales-oriented։
Content goal
Սահմանում է՝ articles-ը ինչ պետք է հասնեն, օրինակ՝ customers attract անել, traffic ավելացնել կամ users-ին educate անել։
Արգելված թեմաներ
Այն ամենը, ինչի մասին brand-ը չպետք է խոսի կամ ինչի հետ չպետք է ասոցացվի, մուտքագրվում է այստեղ։
Optional CTA
CTA նշանակում է այն գործողությունը, որը ցանկանում եք, որ reader-ը կատարի article-ը կարդալուց հետո։ Եթե վստահ չեք, թե ինչ text է suitable, ավելի լավ է այս field-ը թողնել empty։
Ներքին և արտաքին հղումներ
OxiRanker-ը կարող է articles-ի ներսում խելացիորեն կիրառել internal և external links։
Internal links
Այս option-ը default enabled է, և ավելի լավ է այն պահել enabled։ Internal linking-ը search engines-ին օգնում է ավելի լավ հասկանալ ձեր կայքի content structure-ը։
Առաջին article-ում internal links սովորաբար չեն ստեղծվում, քանի որ դեռ previous article չկա։ Երկրորդ article-ից սկսած՝ system-ը կարող է perform անել internal linking։
External links
External links-ը ավելացվում են official և trusted sources-ին և նշվում են որպես nofollow, որպեսզի SEO authority-ն unintentionally չփոխանցվի։
Ստուգել profile quality-ն AI-ով
Article Profile-ը լրացնելուց հետո OxiRanker-ը review է անում profile quality-ն։
Միացնել Telegram-ը article summaries-ի համար
Profile-ը հաջողությամբ save անելուց հետո կարող եք enable անել article summaries-ի ուղարկումը Telegram channel։
Ավելացնել bot-ը
Ավելացրեք @Oxiranker_Bot-ը ձեր Telegram channel-ին և դարձրեք այն Admin։
Մուտքագրել channel username-ը
Մուտքագրեք channel name-ը @-ով։ Օրինակ՝ @my_channel
Enable անել summary delivery-ն
Valid channel գրանցելուց հետո enable արեք article summary delivery-ն։
Կարգավորել automatic article engine-ը
Automatic article generation tab-ում կարող եք manage անել automatic article generation engine-ը։
Smart topic selection
System-ը օգտագործում է keywords և Article Profile՝ suitable, unique և publishable topics ընտրելու համար։
Brand-aware generation
Tone-ը, audience-ը, content goal-ը և brand restrictions-ը պահպանվում են article generation-ի ընթացքում։
Scheduled publishing
Monthly capacity-ն բաշխվում է ամբողջ ամսվա ընթացքում, որպեսզի content publishing-ը ավելի natural և consistent երևա։
Monthly capacity
7 articles / month
10 articles / month
15 articles / month
30 articles / month30 articles նշանակում է գրեթե մեկ article օրական։ 15 articles նշանակում է գրեթե մեկ օրումեջ։ 10 articles նշանակում է մոտ երկու-երեք օրը մեկ։ 7 articles նշանակում է մոտ երեք-չորս օրը մեկ։
Article length և image
Article length-ը կարող է լինել Short, Standard կամ Long։ Standard-ը balanced choice է կայքերի մեծ մասի համար։
Article images-ը կարող են enabled կամ disabled լինել, և image theme-ը կարող է լինել Light կամ Dark։
Ստեղծել առաջին article-ը Generate-ից
Domain-ը պատրաստելուց հետո բացեք Generate menu-ն, ընտրեք domain-ը և սեղմեք New Content կամ New Article։
Language
Յուրաքանչյուր content request-ի համար ընտրվում է միայն մեկ language։
Topic
Գրեք հստակ topic։ Source URL-ը optional է։
Image
Enable կամ disable արեք image-ը և ընտրեք Light կամ Dark theme։
Payment և generation
Cost-ը deducted է լինում wallet-ից և article generation-ը սկսվում է։
Review անել Preview, SEO Report և JSON Output
Article-ը generate անելուց հետո quality review-ի և publishing preparation-ի համար հասանելի են մի քանի կարևոր outputs։
Preview
Ցուցադրում է article text-ը և generated image-ը։ Այս բաժինը useful է final quality-ն publishing-ից առաջ review անելու համար։
SEO Report
Ցուցադրում է հասկանալի SEO report՝ ներառյալ SEO Score, title, meta description, canonical, keywords, schema, headings, links, images և OpenGraph։
JSON Output
Ցուցադրում է full article output-ը technical users-ի համար՝ ներառյալ slug, html_body, schema, image URLs, tags, category, reading time և created/updated timestamps։
Ի՞նչ են իրականում անում Export API-ն և Webhook-ը
OxiRanker-ում article generate լինելուց հետո նպատակակետ կայքը կարող է այն կարդալ Export API-ով կամ ստանալ ծանուցում Webhook-ով, երբ article-ը պատրաստ է։
Մեթոդ 1: Export API
Նպատակակետ կայքը կարող է անհրաժեշտության դեպքում Export Token-ով կարդալ articles-ը OxiRanker-ից։ Այս method-ը օգտագործվում է manual sync-ի, daily sync-ի, article list pages-ի և full article JSON fetch անելու համար։
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/jsonՄեթոդ 2: Webhook
Երբ new article է ստեղծվում կամ article է updated լինում, OxiRanker-ը notify է անում destination website endpoint-ին։ Webhook-ը full article չի ուղարկում։ Այն ուղարկում է միայն article identifier, որպեսզի destination website-ը կարողանա fetch անել full article-ը 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"
}Content storage responsibility և Image Mirroring
Destination website-ը պետք է final article-ը և images-ը store անի իր սեփական infrastructure-ում։
Ինչ պետք է store անի destination website-ը
Correct image path example
OxiRanker image URLs-ը directly և permanently օգտագործելը ճիշտ չէ։ Image-ը պետք է download արվի, և article URLs-ը պետք է replace արվեն destination website-ի internal URLs-ով։
https://oxiranker.com/uploads/articles/3_5/cover.webphttps://example.com/uploads/site-articles/oxiranker/1/5/cover.webpՍտանալ Export Token և ready endpoints
Export Token-ը և ready-to-use endpoints-ը հասանելի են article page-ից, Outputs tab-ից և API Output section-ից։
Որտեղից ստանալ Export Token-ը
https://oxiranker.com/en/pannel/generate/{domainId}/articles/{articleId}Ready-to-use endpoints
Նույն article page-ում, API Output section-ի ներսում, Ready-to-use endpoints-ը ցուցադրում է prepared URLs։
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=0Ստեղծել Webhook Secret և destination endpoint
Webhook Secret-ը նույնպես ստեղծվում է article page-ից, Outputs tab-ից և API Output section-ից։
Որտե՞ղ է ստեղծվում Webhook Secret-ը
Fixed destination endpoint
OxiRanker-ը destination Webhook-ի համար ընդունում է միայն հետևյալ fixed path-ը։ Destination website-ը պետք է այս endpoint-ը ստեղծի իր backend-ում։
POST /api/oxiranker/webhook
https://example.com/api/oxiranker/webhookԱյս endpoint-ը պետք է ընդունի միայն POST, preserve անի raw body-ն, verify անի timestamp-ը և signature-ը, և վերադարձնի successful response webhook.test-ի համար։
Destination website-ի required .env values
OxiRanker-ը չունի access destination website files-ին կամ .env-ին։ Destination website developer-ը պետք է այս values-ը store անի իր 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=50Security values
Storage values
Export API: List, JSON, HTML և ZIP
Destination website-ը կարող է օգտագործել Export API article list-ը կամ յուրաքանչյուր article-ի full output-ը ստանալու համար։
Ստանալ articles list-ը domain-ի համար
GET https://oxiranker.com/api/v1/users/domains/:domainId/export/articles?limit=20&offset=0
Authorization: Bearer YOUR_EXPORT_TOKEN
Accept: application/jsonlimit-ը սահմանում է articles-ի քանակը և ներկայումս սահմանափակված է 1-ից 50-ի միջև։ offset-ը օգտագործվում է pagination-ի համար։ lang-ը optional է և օգտագործվում է specific language-ի articles-ը fetch անելու համար, օրինակ՝ fa, en կամ tr։
Ստանալ full article-ը որպես JSON
GET https://oxiranker.com/api/v1/users/domains/:domainId/articles/:articleId/export/json
Authorization: Bearer YOUR_EXPORT_TOKEN
Accept: application/jsonՍա destination website-ի համար ամենակարևոր output-ն է։ Destination website-ը պետք է full article JSON-ը store անի իր սեփական database-ում և public display-ի համար read անի իր սեփական database-ից։
Ստանալ ready HTML
GET https://oxiranker.com/api/v1/users/domains/1/articles/5/export/html?canonical_mode=absolute&include_css=0
Authorization: Bearer YOUR_EXPORT_TOKENHTML-ը suitable է այն websites-ի համար, որոնք ցանկանում են ready output, բայց ավելի professional approach-ը JSON store անելն է և այն destination website-ի սեփական template-ով render անելը։
Ստանալ ZIP output
GET https://oxiranker.com/api/v1/users/domains/1/articles/5/export/zip
Authorization: Bearer YOUR_EXPORT_TOKENZIP output-ը ներառում է article.json, meta.json, assets.json, schema.jsonld, index.html, version.txt և README.txt։
Webhook Signature և secure Webhook receiving
Destination website-ը պետք է verify անի Webhook signature-ը՝ օգտագործելով original 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=...Signature formula
payload_to_sign = timestamp + "." + rawBody
signature = "sha256=" + HMAC_SHA256(webhook_secret, payload_to_sign)Եթե destination website-ը նախ parse է անում JSON-ը և հետո նորից stringify անում, order-ը կամ spacing-ը կարող են փոխվել, և signature-ը կարող է դառնալ invalid։ Signature verification-ի համար պետք է օգտագործել original raw body-ն։
Պարզ Node.js code sample Webhook ստանալու համար
Այս Express sample-ը handle է անում 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,
});
});Recommended destination website database
Destination website-ը պետք է store անի articles-ը իր սեփական database-ում և prevent անի duplicates-ը 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);Prevent duplicates UPSERT-ով
Article-ը չպետք է կրկին inserted լինի յուրաքանչյուր Webhook-ի համար։ Եթե այն արդեն exists է, այն պետք է updated լինի։
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 անել full article-ը և sync անել Webhook-ից հետո
Երբ Webhook է գալիս, destination website-ը պետք է Export API-ից fetch անի full article-ը, mirror անի images-ը և UPSERT անի article-ը։
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 և image storage rules
Destination website-ը պետք է download անի images-ը, store անի իր own infrastructure-ում և replace անի article URLs-ը։
Image Mirroring rules
Recommended storage path
/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.webpDaily sync reliability-ի համար
Webhook-ը alone բավարար չէ։ Destination website-ը պետք է ունենա նաև daily sync missed articles-ը recover անելու համար։
Ինչու՞ է daily sync-ը պետք
Daily sync algorithm
Ցուցադրել articles-ը destination website-ում
Articles-ը site_articles-ում store անելուց հետո destination website-ը չպետք է յուրաքանչյուր public display-ի համար connect լինի OxiRanker-ին։ Այն պետք է read անի իր սեփական database-ից։
Blog list page
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;Article detail page
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 և HTML rendering
Destination website-ը պետք է read անի SEO data-ն իր սեփական database-ից և render անի այն article page-ում։
SEO fields
HTML rendering
<div
className="article-content"
dangerouslySetInnerHTML={{ __html: article.html_body }}
/>Եթե destination website-ն ունի strict security policy, կարող է sanitize անել HTML-ը save-ից առաջ կամ display-ից առաջ։
RTL և LTR
Յուրաքանչյուր article ունի lang, dir և is_rtl fields։ Եթե dir-ը rtl է, article page-ը պետք է render լինի right-to-left։
<article dir="rtl">
...
</article>Հաճախ հանդիպող errors և դրանց իմաստները
Destination website errors-ը պետք է լինեն English, clear և understandable, որպեսզի troubleshooting-ը արագ լինի։
Export API errors
Webhook errors
Final API և Webhook testing checklist
Implementation-ից հետո այս tests-ը verify են անում connection-ը beginning-ից end։
Export Token և JSON test
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/jsonWebhook test
Եթե destination Webhook-ը Postman-ով call եք անում առանց signature-ի, պետք է ստանաք security error։ Սա նշանակում է, որ endpoint-ը active է և checks security։
{
"error": "missing_webhook_signature",
"message": "Webhook signature header is required."
}Այնուհետև սեղմեք Test Webhook OxiRanker-ի ներսում։ Եթե receiver-ը supports webhook.test, response_status-ը պետք է լինի 200։
Important rules correct implementation-ի համար
Այս rules-ը պետք է follow արվեն API integration-ում, որպեսզի system-ը մնա secure, stable և maintainable։
Complete API flow summary
Այս steps-ը ավարտելուց հետո ձեր custom website-ը կարող է securely receive, store, mirror և publish անել OxiRanker-ում generated articles-ը։