OXIRANKER DEVELOPER DOCS

OxiRanker Dokümantasyonu

Web sitenizi OxiRanker servislerine bağlamak için düzenli bir dokümantasyon merkezi. WordPress entegrasyonu veya API entegrasyonu ile başlayın, ardından kılavuzu adım adım takip edin.

Resmi dokümantasyonFull-feature V1Site sahipleri, geliştiriciler ve entegrasyon ekipleri için
Dokümantasyon menüsü
Menüden bir kılavuz seçin. Bu sayfa OxiRanker için ana dokümantasyon merkezidir.
API servisi

API servisi

Custom websites, custom CMS platformları, Next.js, Laravel, Node.js, PHP veya Python backendleri için Export API ve Webhook kullanın.

Domain
Doğrulandı
Content
Hazır
API
Webhook
Önemli saklama bildirimi

OxiRanker destination website için permanent hosting değildir

OxiRanker, destination website content, images veya exported files için permanent hosting garantisi vermez. OxiRanker user-content hosting service değildir ve generated content, cover images, Open Graph images veya output files OxiRanker infrastructure üzerinde 90 günden fazla kalmayabilir.

Bu nedenle output alındıktan sonra destination website, final article, images, SEO data, Open Graph data ve Schema data değerlerini kendi database, server, storage veya CDN içinde store etmeli ve public display için OxiRanker URLs değerlerine bağlı kalmamalıdır.

Eğitim videosu

Bağlantı eğitim videosu

WordPress bağlamadan veya API entegrasyonunu implement etmeden önce bu videoyu görsel bir walkthrough olarak kullanın.

API Output

Output flow adım adım implement edin

Önce Export Token ve Webhook Secret değerlerini backend içinde store edin, ardından Webhook receiver, database, UPSERT logic, Image Mirroring ve daily sync işlemlerini tamamlayın.

ADIM 01

OxiRanker içinde kayıt olun ve domain oluşturun

Önce OxiRanker içinde kayıt olun, account içine sign in yapın ve Domains bölümünden Add Domain üzerine tıklayın.

Doğru domain formatı

Domain name alanına yalnızca root domain girin. Adres http, https veya www içermemelidir.

example.com

Yanlış formatlar

Domain alanına bu formatları girmeyin.

https://example.com
http://example.com
www.example.com
Domain oluşturduktan sonra
Domain formatı valid ise OxiRanker domain oluşturur ve sizi domain ownership verification step bölümüne yönlendirir.
ADIM 02

Domain sahipliğini doğrulayın

Bu step içinde domain size ait olduğunu veya onu yönetmek için access sahibi olduğunuzu kanıtlamanız gerekir.

DNS TXT

Önerilir

Domain DNS içine TXT record ekleyin. Cloudflare için bu genellikle en kolay method olur.

DNS Name:
_yourdomain-verification.example.com

TXT Value:
acb1****29803

HTTP File

OxiRanker doğrulayabilsin diye web sitenizin .well-known path içine bir text file yerleştirin.

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

File Content:
220daaa37****42730f27

Meta Tag

Web sitenizin homepage head section içine bir meta tag yerleştirin.

<meta name="yourdomain-verification" content="190208d****d275b65" />
Önemli DNS notu
DNS record ekledikten sonra, özellikle Cloudflare içinde, 2 ila 3 dakika beklemeniz gerekebilir. Ardından aynı OxiRanker page üzerinde Confirm tıklayın.
Verification token geçerlilik süresi
Domain verification values için time limit vardır. Şu anda verification values genellikle 1440 minutes boyunca valid olur.
ADIM 03

İlk domain doğrulandıktan sonra ücretsiz token alın

İlk domain verify edildikten sonra OxiRanker initial testing için account içine 1000 free tokens ekler.

Yalnızca ilk domain için
Bu hediye yalnızca ilk verified domain için eklenir ve sonraki domains için tekrar eklenmez.
ADIM 04

Dilleri ve Blog URL değerini yapılandırın

Blog URLs bölümünde web sitenizin single-language mi multilingual mi olduğunu ve generated articles için hangi languages kullanılacağını tanımlayın.

Dilleri seçin

OxiRanker 24 languages destekler. Web sitesi yapınıza göre bir veya birden fazla languages seçebilirsiniz.

Blog URL girin

Her language için o language blog URL değerini girin. Web siteniz multilingual ise her language path değerini ayrı ayrı girin.

https://example.com/fa/blog
https://example.com/en/blog
URL yapısı
Web sitenizin yapısı farklıysa web sitenizin gerçek blog URL değerini girin. Önemli olan final URL değerinin articles publish edilmesi gereken path olmasıdır.
ADIM 05

Her language için keywords ekleyin

Keywords tab içinde her language için en az 3 ve en fazla 50 keywords ekleyin.

Ana kural

Her language için keywords aynı language içinde girilmelidir. Article language English ise keywords de English olmalıdır.

Keywords nasıl eklenir

Her keyword yazdıktan sonra eklemek için Enter tuşuna basın. Tüm keywords tamamlandıktan sonra Save tıklayın.

Yaygın hata
English language için Turkish keywords girerseniz English content içinde Turkish phrases görünebilir. Keyword language, article language ile eşleşmelidir.
ADIM 06

Domain Article Profile tamamlayın

Article Profile, OxiRanker'e brand değerinizin ne olduğunu, kim için yazdığını, hangi tone kullanması gerektiğini ve hangi topics değerlerinden kaçınması gerektiğini söyler.

Industry

Market veya business category tanımlar. Örnek: Dubai içinde real estate consulting ve property buying and selling.

Business description

Business ne sattığını, kime hizmet ettiğini, nasıl çalıştığını ve onu farklı yapan şeyleri açıklar.

Audience

Content kime hitap etmelidir bunu tanımlar. Her audience ayrı olarak save edilebilir.

Tone

Generated content için style ve voice kontrol eder; örneğin formal, educational, simple, technical veya sales-oriented.

Content goal

Articles neyi başarmalı tanımlar; örneğin customers çekmek, traffic artırmak veya users eğitmek.

Yasaklı konular

Brand konuşmamalı veya ilişkilendirilmemeli olan her şey buraya girilir.

Optional CTA

CTA, reader article okuduktan sonra yapmasını istediğiniz action anlamına gelir. Hangi text suitable emin değilseniz bu field empty bırakmak daha iyidir.

Multilingual profile
Web siteniz multilingual ise profile yalnızca bir language içinde tamamlanmalıdır. OxiRanker aynı profile temelinde diğer languages için gerekli versions hazırlayabilir.
ADIM 07

İç ve dış bağlantılar

OxiRanker articles içinde internal links ve external links değerlerini intelligent şekilde uygulayabilir.

Internal links

Bu option default olarak enabled gelir ve enabled kalması daha iyidir. Internal linking, search engines için web sitenizin content structure yapısını daha iyi anlamaya yardımcı olur.

İlk article içinde internal links genellikle oluşturulmaz çünkü henüz previous article yoktur. İkinci article itibarıyla system internal linking yapabilir.

External links

External links official ve trusted sources üzerine eklenir ve SEO authority unintentionally aktarılmasın diye nofollow olarak marked edilir.

ADIM 08

AI ile profile quality kontrol edin

Article Profile tamamlandıktan sonra OxiRanker profile quality review yapar.

85+
Save için required minimum score
95
Improvements sonrası genellikle ulaşılan excellent score
AI
Her field için professional suggestions
Score 85 altındaysa
System final saving işlemine izin vermez ve her problematic field için explanations ve improvement suggestions gösterir. Suggestions apply edip tekrar save edebilirsiniz.
ADIM 09

Article summaries için Telegram bağlayın

Profile başarılı şekilde save edildikten sonra article summaries değerlerini Telegram channel üzerine göndermeyi enable edebilirsiniz.

1

Bot ekleyin

@Oxiranker_Bot botunu Telegram channel içine ekleyin ve Admin yapın.

2

Channel username girin

Channel name değerini @ ile girin. Örnek: @my_channel

3

Summary delivery enable edin

Valid channel kaydedildikten sonra article summary delivery enable edin.

Telegram faydası
Enabled ise content generation sonrasında article summary image ve link ile Telegram channel üzerine gönderilir. Bu, web sitenize giden traffic paths değerlerini güçlendirebilir.
ADIM 10

Automatic article engine yapılandırın

Automatic article generation tab içinde automatic article generation engine yönetebilirsiniz.

Smart topic selection

System suitable, unique ve publishable topics seçmek için keywords ve Article Profile kullanır.

Brand-aware generation

Article generation sırasında tone, audience, content goal ve brand restrictions dikkate alınır.

Scheduled publishing

Monthly capacity ay boyunca dağıtılır, böylece content publishing daha natural ve consistent görünür.

Monthly capacity

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

30 articles neredeyse her gün bir article anlamına gelir. 15 articles neredeyse iki günde bir anlamına gelir. 10 articles yaklaşık iki-üç günde bir anlamına gelir. 7 articles yaklaşık üç-dört günde bir anlamına gelir.

Article length ve image

Article length Short, Standard veya Long olabilir. Standard çoğu web sitesi için balanced choice olur.

Article images enabled veya disabled yapılabilir ve image theme Light veya Dark seçilebilir.

API websites için not
Automatic engine articles değerlerini OxiRanker içinde generate eder. Generation sonrası her article değerini custom website üzerine taşımak için Export API, Webhook, database storage ve Image Mirroring implement edilmelidir.
ADIM 11

Generate üzerinden ilk article oluşturun

Domain hazırlandıktan sonra Generate menu açın, domain seçin ve New Content veya New Article tıklayın.

1

Language

Her content request için yalnızca bir language seçilir.

2

Topic

Net bir topic yazın. Source URL optional olur.

3

Image

Image enable veya disable edin ve Light veya Dark theme seçin.

4

Payment ve generation

Cost wallet içinden deducted edilir ve article generation başlar.

Generation time
Article ve image generation genellikle yaklaşık 2 ila 3 dakika sürer. Tamamlandıktan sonra article OxiRanker içinde visible olur.
Source URL
Source URL girilirse OxiRanker topic değerini daha iyi anlamak için bunu kullanır, fakat copied content generate etmez. System unique content oluşturmak için designed.
ADIM 12

Preview, SEO Report ve JSON Output review edin

Article generated olduktan sonra quality review ve publishing preparation için birkaç important outputs kullanılabilir.

Preview

Article text ve generated image gösterir. Bu section publishing öncesi final quality review için kullanışlıdır.

SEO Report

SEO Score, title, meta description, canonical, keywords, schema, headings, links, images ve OpenGraph dahil understandable SEO report gösterir.

JSON Output

Technical users için full article output gösterir; slug, html_body, schema, image URLs, tags, category, reading time ve created/updated timestamps içerir.

Outputs anlamı
Bu outputs, article değerinin sadece plain text olmadığını gösterir. WordPress, CMS, API ve publishing systems için hazırlanmış structured, SEO-ready content değeridir.
ADIM 13

Export API ve Webhook tam olarak ne yapar?

OxiRanker içinde article generated olduktan sonra destination website bunu Export API ile read edebilir veya article ready olduğunda Webhook ile notified olabilir.

Method 1: Export API

Destination website gerektiğinde Export Token ile OxiRanker üzerinden articles read edebilir. Bu method manual sync, daily sync, article list pages ve full article JSON fetching için kullanılır.

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

Method 2: Webhook

New article created olduğunda veya article updated olduğunda OxiRanker destination website endpoint değerini notify eder. Webhook full article göndermez. Destination website Export JSON API üzerinden full article fetch edebilsin diye yalnızca article identifier gönderir.

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"
}
Çok önemli not
Webhook yalnızca lightweight message değeridir. Destination website Webhook içinde full article expect etmemelidir. domain_id ve article_id alındıktan sonra complete article Export JSON API üzerinden fetch edilmeli ve kendi database içine store edilmelidir.
ADIM 14

Content storage responsibility ve Image Mirroring

Destination website final article ve images değerlerini kendi infrastructure içinde store etmelidir.

Destination website ne store etmelidir

Destination website database içindeki full article text
Destination server, storage veya CDN üzerindeki main article image
Destination server, storage veya CDN üzerindeki Open Graph image
SEO, OpenGraph ve Article Schema data
Original output preserve etmek için raw_detail_payload ve raw_list_item

Doğru image path örneği

OxiRanker image URLs değerlerini directly ve permanently kullanmak doğru değildir. Image download edilmeli ve article URLs destination website internal URLs ile replace edilmelidir.

https://oxiranker.com/uploads/articles/3_5/cover.webp
https://example.com/uploads/site-articles/oxiranker/1/5/cover.webp
OxiRanker destination website için permanent hosting değildir
OxiRanker, destination website için output content ve images değerlerini permanently storing etmekten sorumlu değildir. OxiRanker user-content hosting service değildir ve generated content, cover images, Open Graph images veya output files OxiRanker infrastructure üzerinde 90 days üzerinde kalmayabilir. Bu nedenle destination website final article, images, SEO data, OpenGraph ve Schema değerlerini kendi database, server, storage veya CDN içinde store etmelidir.
ADIM 15

Export Token ve ready endpoints alın

Export Token ve ready-to-use endpoints article page, Outputs tab ve API Output section üzerinden kullanılabilir.

Export Token nereden alınır

OxiRanker içinde ilk article create edin.
O article page değerini open edin.
Outputs tab open edin.
API Output section open edin.
Token management üzerinden token create edin.
Full token yalnızca bir kez shown edilir.
Token değerini destination website backend veya .env içinde store edin.
https://oxiranker.com/en/pannel/generate/{domainId}/articles/{articleId}

Ready-to-use endpoints

Aynı article page üzerinde, API Output section içinde, Ready-to-use endpoints prepared URLs gösterir.

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 rotate etmek
Export Token OxiRanker panel içinde rotated edilirse previous token expires olur. New value destination website .env veya backend environment içindeki old value değerini immediately replace etmeli ve destination backend restarted veya redeployed edilmelidir.
ADIM 16

Webhook Secret ve destination endpoint oluşturun

Webhook Secret de article page, Outputs tab ve API Output section üzerinden oluşturulur.

Webhook Secret nerede oluşturulur?

Article page open edin.
Outputs tab open edin.
API Output section open edin.
Webhook Secret section içinde secret create edin.
Full secret yalnızca bir kez shown edilir.
Secret değerini destination website .env içinde store edin.

Fixed destination endpoint

OxiRanker destination Webhook için yalnızca aşağıdaki fixed path kabul eder. Destination website bu endpoint değerini backend içinde create etmelidir.

POST /api/oxiranker/webhook

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

Bu endpoint yalnızca POST accept etmeli, raw body preserve etmeli, timestamp ve signature verify etmeli ve webhook.test için successful response return etmelidir.

Active events
Current version içinde events fixed olur: article.created ve article.updated. Connection testing için event value webhook.test gönderilir.
ADIM 17

Destination website için required .env values

OxiRanker destination website files veya .env üzerine access sahibi değildir. Destination website developer bu values değerlerini kendi backend içinde store etmelidir.

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

Security values

OXIRANKER_EXPORT_API_TOKEN
Export API token yalnızca backend içinde stored olmalıdır.
OXIRANKER_WEBHOOK_SECRET
Webhook Secret yalnızca backend içinde stored olmalıdır.
OXIRANKER_WEBHOOK_MAX_SKEW_MS
Webhook için maximum allowed time difference. 300000, 5 minutes anlamına gelir.

Storage values

SITE_ARTICLE_IMAGE_UPLOAD_DIR
Destination server üzerinde mirrored images storing için physical path.
SITE_ARTICLE_IMAGE_MAX_BYTES
Download ve storage için maximum allowed image size.
PUBLIC_BASE_URL
Final image ve article URLs create etmek için kullanılan public destination website URL.
Frontend security
Export Token ve Webhook Secret frontend code, user-visible JavaScript, localStorage veya window object içine yerleştirilmemelidir.
ADIM 18

Export API: List, JSON, HTML ve ZIP

Destination website Export API kullanarak article list veya her article için full output receive edebilir.

Domain için list of articles alın

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

limit number of articles değerini tanımlar ve şu anda 1 ile 50 arasında limited olur. offset pagination için kullanılır. lang optional olur ve fa, en veya tr gibi specific language articles fetch etmek için kullanılır.

Full article değerini JSON olarak alın

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

Bu destination website için en important output değeridir. Destination website full article JSON değerini kendi database içinde store etmeli ve public display için kendi database üzerinden read etmelidir.

Ready HTML alın

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 ready output isteyen websites için suitable olur, ancak daha professional approach JSON store etmek ve destination website kendi template ile render etmektir.

ZIP output alın

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

ZIP output article.json, meta.json, assets.json, schema.jsonld, index.html, version.txt ve README.txt içerir.

ADIM 19

Webhook Signature ve secure Webhook receiving

Destination website original raw body kullanarak Webhook signature verify etmelidir.

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 önce JSON parse edip sonra tekrar stringify ederse order veya spacing değişebilir ve signature invalid olabilir. Signature verification için original raw body kullanılmalıdır.

Timing-safe comparison
Signatures değerlerini expected === received ile karşılaştırmayın. Node.js içinde crypto.timingSafeEqual kullanmak daha iyidir.
ADIM 20

Webhook receiving için simple Node.js code sample

Bu Express sample raw body, timestamp, signature, webhook.test ve article events handle eder.

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,
  });
});
ADIM 21

Recommended destination website database

Destination website articles değerlerini kendi database içinde store etmeli ve unique constraint ile duplicates prevent etmelidir.

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 note
PostgreSQL içinde Persian veya Unicode text için simple quotes kullanılır. N'...' gibi bir şeye gerek yoktur ve kullanılmamalıdır.
ADIM 22

UPSERT ile duplicates prevent edin

Article her Webhook için yeniden inserted edilmemelidir. Zaten exists ise updated edilmelidir.

Duplicates preventing için main key
Duplicates preventing için en important constraint şudur: 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();
ADIM 23

Webhook sonrası full article fetch ve sync yapın

Webhook geldiğinde destination website Export API üzerinden full article fetch etmeli, images mirror etmeli ve article UPSERT etmelidir.

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,
  });
}
ADIM 24

Image Mirroring ve image storage rules

Destination website images download etmeli, kendi infrastructure üzerinde store etmeli ve article URLs replace etmelidir.

Image Mirroring rules

Yalnızca HTTPS URLs accepted edilmelidir.
File gerçekten image olmalıdır.
content-type image/ ile başlamalıdır.
Empty files stored edilmemelidir.
File size limit değerini aşmamalıdır.
cover ve og aynıysa yalnızca bir kez download edin.
Mirroring sonrasında html_body ve article_schema içindeki URLs replace edin.

Recommended storage path

/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
ADIM 25

Reliability için daily sync

Webhook alone yeterli değildir. Destination website missed articles recover etmek için daily sync de bulundurmalıdır.

Daily sync neden gerekli?

Destination website temporarily down olabilir.
Webhook timeout olabilir.
Deployment veya network issues olabilir.
Article generated olabilir fakat Webhook missed olabilir.

Daily sync algorithm

offset değerini 0 üzerinden start edin.
limit 20 veya 50 kullanın.
Export List API üzerinden article list fetch edin.
Her item için o article Export JSON değerini fetch edin.
Images mirror edin.
Article değerini UPSERT ile store edin.
offset increase edin ve total completed olana kadar continue edin.
ADIM 26

Destination website üzerinde articles gösterin

Articles site_articles içine storing edildikten sonra destination website her public display için OxiRanker'e connect etmemelidir. Kendi database üzerinden read etmelidir.

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;
Recommended article URL
Multilingual websites için /{lang}/blog/{slug} pattern kullanabilirsiniz. Örnek: /fa/blog/smart-internal-links
ADIM 27

SEO, Schema, RTL ve HTML rendering

Destination website SEO data değerlerini kendi database üzerinden read edip article page üzerinde render etmelidir.

SEO fields

page title için title
meta description için meta_description
canonical için effective_canonical_url
Open Graph için og_title ve og_description
social preview image için og_image_url veya cover_image_url
JSON-LD için article_schema

HTML rendering

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

Destination website strict security policy içeriyorsa HTML değerini saving öncesinde veya displaying öncesinde sanitize edebilir.

RTL ve LTR

Her article lang, dir ve is_rtl fields içerir. dir rtl ise article page right-to-left rendered edilmelidir.

<article dir="rtl">
  ...
</article>
ADIM 28

Common errors ve anlamları

Destination website errors English, clear ve understandable olmalıdır ki troubleshooting hızlı yapılabilsin.

Export API errors

Domain export token eksik.
Export API token gönderilmedi. Authorization: Bearer YOUR_EXPORT_TOKEN gönderilmelidir.
Domain export token invalid.
Token wrong, rotated, bu domain ile ilişkili değil veya domainId wrong.
Token required scope içermiyor.
Token required scope içermiyor. New tokens articles:read ve exports:read içermelidir.
lang invalid.
Query parameter içindeki lang value incorrectly gönderildi.

Webhook errors

Webhook URL HTTPS kullanmalıdır.
Webhook URL https:// ile başlamalıdır.
Webhook signature invalid.
Webhook Secret wrong, secret rotated, raw body changed veya signature incorrectly calculated.
Webhook timestamp expired veya invalid.
Timestamp old, future içinde veya invalid. Server clock ve NTP kontrol edin.
Webhook test delivery başarısız oldu.
Destination website successful status 200 ile 299 arasında return etmedi, timed out oldu veya webhook.test correctly handle etmedi.
ADIM 29

Final API ve Webhook testing checklist

Implementation sonrasında bu tests connection değerini beginning ile end arasında verify eder.

Export Token ve JSON test

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 test

Destination Webhook değerini Postman ile signature olmadan call ederseniz security error receive etmelisiniz. Bu, endpoint active ve checks security demektir.

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

Sonra OxiRanker içinde Test Webhook tıklayın. Receiver webhook.test supports ediyorsa response_status 200 olmalıdır.

Rotation sonrası
Webhook Secret veya Export Token rotated edilirse new value hemen copy edilmeli, destination website .env içinde replace edilmeli ve destination backend restart veya redeploy edilmelidir.
ADIM 30

Correct implementation için important rules

System secure, stable ve maintainable kalması için API integration içinde bu rules takip edilmelidir.

Export Token yalnızca backend içinde stored edilmelidir.
Webhook Secret yalnızca backend içinde stored edilmelidir.
Destination Webhook endpoint tam olarak /api/oxiranker/webhook olmalıdır.
Webhook URL HTTPS kullanmalıdır.
Webhook signature ve timestamp her zaman verified edilmelidir.
Articles simple insert ile değil, UPSERT ile stored edilmelidir.
Duplicates prevent etmek için unique constraint required olur.
Images OxiRanker üzerinden downloaded edilmeli ve destination website üzerinde stored edilmelidir.
Destination website OxiRanker image URLs değerlerine permanently depend etmemelidir.
Image URLs html_body ve article_schema içinde replaced edilmelidir.
Webhook fails olursa daily sync missed article recover etmelidir.
Users veya logs için shown edilen errors English ve understandable olmalıdır.
Article archived ise automatic sync onu tekrar publish etmemelidir.
Original output data kaybolmasın diye raw_detail_payload ve raw_list_item preserved edilmelidir.
Export Token veya Webhook Secret rotated edilirse new value .env içinde replaced edilmeli ve backend restarted veya redeployed edilmelidir.
ADIM 31

Complete API flow summary

Bu steps tamamlandıktan sonra custom website, OxiRanker içinde generated articles değerlerini securely receive, store, mirror ve publish edebilir.

OxiRanker içinde kayıt olun
Domain ekleyin
Domain ownership verify edin
Languages seçin ve Blog URL ekleyin
Her language için keywords ekleyin
Article Profile tamamlayın
Gerekiyorsa Telegram yapılandırın
Automatic article engine yapılandırın
Generate üzerinden ilk article create edin
Preview ve SEO Report review edin
API Output üzerinden Export Token alın
Ready Export API endpoints alın
Webhook Secret alın
Destination backend içinde fixed /api/oxiranker/webhook endpoint create edin
Token ve Secret değerlerini destination website .env içinde store edin
site_articles table ve unique constraint create edin
Export JSON üzerinden full article fetching implement edin
Image Mirroring implement edin
UPSERT implement edin
Daily sync implement edin
Destination database üzerinden article, SEO, OG ve Schema render edin
Final Export API ve Webhook tests run edin