OXIRANKER DEVELOPER DOCS

OxiRanker ドキュメント

あなたの Web サイトを OxiRanker サービスに接続するための整理されたドキュメントセンターです。WordPress 連携または API 連携から始め、その後ガイドを step by step で進めてください。

公式ドキュメントFull-feature V1サイト所有者、開発者、連携チーム向け
ドキュメントメニュー
メニューからガイドを選択してください。このページは OxiRanker のメインドキュメントセンターです。
API サービス

API サービス

カスタム Web サイト、カスタム CMS プラットフォーム、Next.js、Laravel、Node.js、PHP、Python backend 向けに Export API と Webhook を使用します。

Domain
確認済み
コンテンツ
準備完了
API
Webhook
重要な保存に関する通知

OxiRanker は destination website の permanent hosting ではありません

OxiRanker は、destination website の content、images、exported files に対して permanent hosting の保証を提供しません。OxiRanker は user-content hosting service ではなく、generated content、cover images、Open Graph images、output files は OxiRanker infrastructure 上に 90 日を超えて保持されない場合があります。

そのため、output を受け取った後、destination website は final article、images、SEO data、Open Graph data、Schema data を自分の database、server、storage、CDN に保存する必要があり、public display のために OxiRanker URLs に依存してはいけません。

トレーニング動画

接続トレーニング動画

WordPress を接続する前、または API 連携を実装する前に、この動画を視覚的な walkthrough として使用してください。

API Output

output flow を step by step で実装する

まず Export Token と Webhook Secret を backend に保存し、その後 Webhook receiver、database、UPSERT logic、Image Mirroring、daily sync を完成させます。

STEP 01

OxiRanker に登録して domain を作成する

まず OxiRanker に登録し、account に sign in して、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 形式が valid の場合、OxiRanker は domain を作成し、domain ownership verification step に進みます。
STEP 02

domain 所有権を確認する

この step では、domain があなたのものであること、または管理する access があることを証明する必要があります。

DNS TXT

推奨

domain DNS に TXT record を追加してください。Cloudflare の場合、通常これが最も簡単な方法です。

DNS Name:
_yourdomain-verification.example.com

TXT Value:
acb1****29803

HTTP File

OxiRanker が確認できるように、Web サイトの .well-known path に text file を配置してください。

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

File Content:
220daaa37****42730f27

Meta Tag

Web サイト homepage の head section 内に meta tag を配置してください。

<meta name="yourdomain-verification" content="190208d****d275b65" />
重要な DNS note
DNS record を追加した後、特に Cloudflare では 2〜3 分待つ必要がある場合があります。その後、同じ OxiRanker page で Confirm をクリックしてください。
Verification token lifetime
Domain verification values には time limit があります。現在、verification values は通常 1440 minutes 有効です。
STEP 03

最初の domain を verify した後に無料 token を受け取る

最初の domain を verify した後、OxiRanker は initial testing のために account に 1000 free tokens を追加します。

最初の domain のみ
この gift は最初の verified domain にのみ追加され、後続の domains には再度追加されません。
STEP 04

言語と Blog URL を設定する

Blog URLs セクションで、Web サイトが single-language か multilingual か、また generated articles に使用する languages を定義します。

言語を選択する

OxiRanker は 24 languages をサポートしています。Web サイト構造に応じて 1 つまたは複数の languages を選択できます。

Blog URL を入力する

各 language について、その language の blog URL を入力してください。Web サイトが multilingual の場合、各 language path を個別に入力してください。

https://example.com/fa/blog
https://example.com/en/blog
URL structure
Web サイトの構造が異なる場合は、Web サイトの実際の blog URL を入力してください。重要なのは、final URL が articles を publish する path であることです。
STEP 05

各 language に keywords を追加する

Keywords tab で、各 language に最低 3 個、最大 50 個の keywords を追加してください。

主なルール

各 language の keywords は同じ language で入力する必要があります。article language が English の場合、keywords も English である必要があります。

keywords の追加方法

各 keyword を入力した後、Enter を押して追加します。すべての keywords を完了したら、Save をクリックしてください。

よくある間違い
English language に Turkish keywords を入力すると、English content 内に Turkish phrases が表示される場合があります。Keyword language は article language と一致している必要があります。
STEP 06

domain Article Profile を完成させる

Article Profile は、あなたの brand が何であるか、誰に向けて書くか、どの tone を使うべきか、どの topics を避けるべきかを OxiRanker に伝えます。

Industry

market または business category を定義します。例: Dubai の real estate consulting と property buying and selling。

Business description

business が何を販売し、誰にサービスを提供し、どのように機能し、何が異なるのかを説明します。

Audience

content が誰に語りかけるべきかを定義します。各 audience は個別に保存できます。

Tone

formal、educational、simple、technical、sales-oriented など、generated content の style と voice を制御します。

Content goal

articles が達成すべき内容を定義します。例: customers を attract する、traffic を増やす、users を educate する。

禁止トピック

brand が話すべきでないもの、または関連付けられるべきでないものをここに入力します。

Optional CTA

CTA は、article を読んだ後に reader に取ってほしい action を意味します。適切な text がわからない場合は、この field を empty のままにする方がよいです。

Multilingual profile
Web サイトが multilingual の場合、profile は 1 つの language で完成させるだけで十分です。OxiRanker は同じ profile に基づいて他の languages に必要な versions を準備できます。
STEP 07

内部リンクと外部リンク

OxiRanker は articles 内に internal links と external links を intelligent に適用できます。

Internal links

この option は default で enabled になっており、enabled のままにしておくことをおすすめします。Internal linking は search engines が Web サイトの content structure をよりよく理解するのに役立ちます。

最初の記事では、まだ previous article がないため、通常 internal links は作成されません。2 本目の記事以降、system は internal linking を実行できます。

External links

External links は official で trusted な sources に追加され、SEO authority が意図せず移転しないように nofollow として marked されます。

STEP 08

AI で profile quality を確認する

Article Profile を完成させた後、OxiRanker は profile quality を review します。

85+
保存に必要な minimum score
95
改善後によく達成される excellent score
AI
各 field に対する professional suggestions
score が 85 未満の場合
system は final saving を許可せず、各 problematic field に対して explanations と improvement suggestions を表示します。suggestions を適用して再度 save できます。
STEP 09

article summaries のために Telegram を接続する

profile を正常に save した後、Telegram channel への article summaries 送信を enable できます。

1

bot を追加する

@Oxiranker_Bot を Telegram channel に追加し、Admin にしてください。

2

channel username を入力する

channel name を @ 付きで入力してください。例: @my_channel

3

summary delivery を enable する

valid channel を登録した後、article summary delivery を enable してください。

Telegram の利点
enabled の場合、content generation 後に article summary が image と link とともに Telegram channel に送信されます。これにより Web サイトへの traffic paths を強化できます。
STEP 10

automatic article engine を設定する

Automatic article generation tab で、automatic article generation engine を管理できます。

Smart topic selection

system は keywords と Article Profile を使用して、suitable、unique、publishable な topics を選択します。

Brand-aware generation

article generation 中、tone、audience、content goal、brand restrictions が尊重されます。

Scheduled publishing

Monthly capacity は月全体に分散されるため、content publishing がより natural で consistent に見えます。

Monthly capacity

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

30 articles はほぼ 1 日 1 article を意味します。15 articles はほぼ 2 日に 1 回を意味します。10 articles は約 2〜3 日に 1 回を意味します。7 articles は約 3〜4 日に 1 回を意味します。

Article length と image

Article length は Short、Standard、Long のいずれかです。Standard はほとんどの Web サイトにとって balanced choice です。

Article images は enabled または disabled にでき、image theme は Light または Dark を選択できます。

API websites への note
automatic engine は OxiRanker 内で articles を generate します。generation 後に各 article を custom website に移動するには、Export API、Webhook、database storage、Image Mirroring を実装する必要があります。
STEP 11

Generate から最初の記事を作成する

domain を準備した後、Generate menu を開き、domain を選択して New Content または New Article をクリックします。

1

Language

各 content request では 1 つの language のみが選択されます。

2

Topic

明確な topic を書いてください。Source URL は optional です。

3

Image

image を enable または disable にし、Light または Dark theme を選択します。

4

Payment と generation

cost が wallet から deducted され、article generation が開始されます。

Generation time
Article と image generation は通常約 2〜3 分かかります。完了後、article は OxiRanker 内に表示されます。
Source URL
Source URL が入力されている場合、OxiRanker は topic をよりよく理解するためにそれを使用しますが、copied content は生成しません。system は unique content を作成するように設計されています。
STEP 12

Preview、SEO Report、JSON Output を確認する

article が generated された後、quality review と publishing preparation のために複数の important outputs が利用できます。

Preview

article text と generated image を表示します。この section は publishing 前に final quality を確認するのに役立ちます。

SEO Report

SEO Score、title、meta description、canonical、keywords、schema、headings、links、images、OpenGraph を含む understandable SEO report を表示します。

JSON Output

technical users 向けに full article output を表示します。slug、html_body、schema、image URLs、tags、category、reading time、created/updated timestamps が含まれます。

outputs の意味
これらの outputs は、article が単なる plain text ではないことを示しています。これは WordPress、CMS、API、publishing systems 向けに準備された structured、SEO-ready content です。
STEP 13

Export API と Webhook は正確に何をするのか?

OxiRanker で article が generated された後、destination website は Export API でそれを read するか、article が ready になったとき Webhook で通知を受け取れます。

Method 1: Export API

destination website は、必要なときに Export Token を使って OxiRanker から articles を read できます。この 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/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 されたとき、または article が updated されたとき、OxiRanker は destination website endpoint に通知します。Webhook は full article を送信しません。destination website が Export JSON API から full article を fetch できるよう、article identifier のみを送信します。

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"
}
非常に重要な note
Webhook は lightweight message にすぎません。destination website は Webhook 内に full article が含まれることを期待してはいけません。domain_id と article_id を受け取った後、Export JSON API から complete article を fetch し、自分の database に store する必要があります。
STEP 14

Content storage responsibility と Image Mirroring

destination website は final article と images を自分の infrastructure に store する必要があります。

destination website が store すべきもの

destination website database 内の full article text
destination server、storage、CDN 上の main article image
destination server、storage、CDN 上の Open Graph image
SEO、OpenGraph、Article Schema data
original output を保持するための raw_detail_payload と raw_list_item

正しい image path の例

OxiRanker image URLs を直接かつ permanent に使用するのは正しくありません。image は download され、article URLs は destination website の internal URLs に置き換えられる必要があります。

https://oxiranker.com/uploads/articles/3_5/cover.webp
https://example.com/uploads/site-articles/oxiranker/1/5/cover.webp
OxiRanker は destination website の permanent hosting ではありません
OxiRanker は destination website の output content と images を permanent に store する責任を負いません。OxiRanker は user-content hosting service ではなく、generated content、cover images、Open Graph images、output files は OxiRanker infrastructure 上に 90 days を超えて保持されない場合があります。そのため、destination website は final article、images、SEO data、OpenGraph、Schema を自分の database、server、storage、CDN に store する必要があります。
STEP 15

Export Token と ready endpoints を取得する

Export Token と ready-to-use endpoints は article page、Outputs tab、API Output section から利用できます。

Export Token の取得場所

OxiRanker で最初の article を作成します。
その article page を開きます。
Outputs tab を開きます。
API Output section を開きます。
Token management から token を作成します。
full token は一度だけ表示されます。
token を destination website backend または .env に store します。
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=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 の rotation
OxiRanker panel で Export Token が rotated されると、previous token は expires します。new value は destination website .env または backend environment の old value を直ちに replace する必要があり、destination backend は restart または redeploy される必要があります。
STEP 16

Webhook Secret と destination endpoint を作成する

Webhook Secret も article page、Outputs tab、API Output section から作成されます。

Webhook Secret はどこで作成されますか?

article page を開きます。
Outputs tab を開きます。
API Output section を開きます。
Webhook Secret section で secret を作成します。
full secret は一度だけ表示されます。
secret を destination website .env に store します。

固定 destination endpoint

OxiRanker は destination Webhook に対して次の固定 path のみを受け付けます。destination website はこの endpoint を backend に作成する必要があります。

POST /api/oxiranker/webhook

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

この endpoint は POST のみを受け付け、raw body を preserve し、timestamp と signature を verify し、webhook.test に対して successful response を返す必要があります。

Active events
current version では events は固定です: article.created と article.updated。connection を test するため、event value webhook.test が送信されます。
STEP 17

destination website に必要な .env values

OxiRanker は destination website files または .env に access できません。destination website developer はこれらの values を自分の backend に store する必要があります。

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 は backend のみに store する必要があります。
OXIRANKER_WEBHOOK_SECRET
Webhook Secret は backend のみに store する必要があります。
OXIRANKER_WEBHOOK_MAX_SKEW_MS
Webhook に許可される最大 time difference。300000 は 5 minutes を意味します。

Storage values

SITE_ARTICLE_IMAGE_UPLOAD_DIR
destination server に mirrored images を store するための physical path。
SITE_ARTICLE_IMAGE_MAX_BYTES
download と storage に許可される最大 image size。
PUBLIC_BASE_URL
final image と article URLs を作成するために使用される public destination website URL。
Frontend security
Export Token と Webhook Secret を frontend code、user-visible JavaScript、localStorage、window object の中に配置してはいけません。
STEP 18

Export API: List、JSON、HTML、ZIP

destination website は Export API を使用して article list または各 article の full output を受け取れます。

domain の articles list を取得する

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

limit は articles の数を定義し、現在 1〜50 に制限されています。offset は pagination に使用されます。lang は optional で、fa、en、tr など specific language の articles を fetch するために使用されます。

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 にとって最も important な output です。destination website は full article JSON を自分の database に store し、public display のために自分の database から read する必要があります。

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_TOKEN

HTML は ready output を必要とする Web サイトに適していますが、より 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_TOKEN

ZIP output には article.json、meta.json、assets.json、schema.jsonld、index.html、version.txt、README.txt が含まれます。

STEP 19

Webhook Signature と安全な Webhook 受信

destination website は original raw body を使用して Webhook signature を verify する必要があります。

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 が最初に JSON を parse し、その後再度 stringify すると、order や spacing が変わり、signature が invalid になる可能性があります。signature verification には original raw body を使用する必要があります。

Timing-safe comparison
signatures を expected === received で比較しないでください。Node.js では crypto.timingSafeEqual を使用する方がよいです。
STEP 20

Webhook 受信用の simple Node.js code sample

この Express sample は 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,
  });
});
STEP 21

推奨 destination website database

destination website は articles を自分の database に store し、unique constraint で duplicates を防ぐ必要があります。

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 では Persian または Unicode text に simple quotes を使用します。N'...' のようなものは不要であり、使用してはいけません。
STEP 22

UPSERT で duplicates を防ぐ

article は各 Webhook ごとに再度 insert してはいけません。すでに存在する場合は update する必要があります。

duplicates 防止の main key
duplicates を防ぐための最も important な constraint は次の通りです: UNIQUE (source_provider, source_domain_id, source_article_id)
INSERT INTO public.site_articles (
  source_provider,
  source_domain_id,
  source_article_id,
  source_request_id,
  lang,
  dir,
  slug,
  title,
  meta_description,
  html_body,
  focus_keywords,
  category,
  tags,
  reading_time_minutes,
  cover_image_url,
  og_image_url,
  article_schema,
  source_created_at,
  source_updated_at,
  sync_status,
  last_synced_at,
  publish_status,
  published_at,
  created_at,
  updated_at
)
VALUES (
  'oxiranker',
  1,
  5,
  5,
  'fa',
  'rtl',
  'smart-internal-links',
  'Article title',
  'Meta description',
  '<p>HTML body</p>',
  '[]'::jsonb,
  'SEO',
  '[]'::jsonb,
  7,
  'https://example.com/uploads/site-articles/oxiranker/1/5/cover.webp',
  'https://example.com/uploads/site-articles/oxiranker/1/5/cover.webp',
  '{}'::jsonb,
  '2026-05-20T19:33:23.618Z',
  '2026-05-20T19:34:09.629Z',
  'synced',
  now(),
  'published',
  now(),
  now(),
  now()
)
ON CONFLICT (source_provider, source_domain_id, source_article_id)
DO UPDATE SET
  source_request_id = EXCLUDED.source_request_id,
  lang = EXCLUDED.lang,
  dir = EXCLUDED.dir,
  slug = EXCLUDED.slug,
  title = EXCLUDED.title,
  meta_description = EXCLUDED.meta_description,
  html_body = EXCLUDED.html_body,
  focus_keywords = EXCLUDED.focus_keywords,
  category = EXCLUDED.category,
  tags = EXCLUDED.tags,
  reading_time_minutes = EXCLUDED.reading_time_minutes,
  cover_image_url = EXCLUDED.cover_image_url,
  og_image_url = EXCLUDED.og_image_url,
  article_schema = EXCLUDED.article_schema,
  source_created_at = EXCLUDED.source_created_at,
  source_updated_at = EXCLUDED.source_updated_at,
  sync_status = 'synced',
  last_synced_at = now(),
  last_sync_error = NULL,
  publish_status = CASE
    WHEN public.site_articles.publish_status = 'archived'
    THEN public.site_articles.publish_status
    ELSE 'published'
  END,
  published_at = COALESCE(public.site_articles.published_at, EXCLUDED.published_at, now()),
  deleted_at = NULL,
  updated_at = now();
STEP 23

Webhook 後に full article を fetch して sync する

Webhook が到着したら、destination website は Export API から full article を fetch し、images を mirror し、article を UPSERT する必要があります。

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

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

  const bodyText = await response.text();

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

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

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

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

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

  const mirroredImages = await mirrorArticleImages(article);

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

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

Image Mirroring と image storage rules

destination website は images を download し、自分の infrastructure に store し、article URLs を replace する必要があります。

Image Mirroring rules

HTTPS URLs のみを accept する必要があります。
file は実際に image である必要があります。
content-type は image/ で始まる必要があります。
empty files は store してはいけません。
file size は limit を超えてはいけません。
cover と og が同じ場合、download は 1 回だけ行ってください。
mirroring 後、html_body と article_schema 内の URLs を replace してください。

推奨 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
STEP 25

信頼性のための daily sync

Webhook だけでは十分ではありません。destination website は missed articles を recover するために daily sync も持つべきです。

なぜ daily sync が必要ですか?

destination website が一時的に down する場合があります。
Webhook が timeout する場合があります。
deployment または network issues が発生する場合があります。
article は generated されたが、その Webhook が missed される場合があります。

Daily sync algorithm

offset を 0 から開始します。
limit 20 または 50 を使用します。
Export List API から article list を fetch します。
各 item について、その article の Export JSON を fetch します。
images を mirror します。
article を UPSERT で store します。
offset を increase し、total が completed になるまで continue します。
STEP 26

destination website に articles を表示する

articles を site_articles に store した後、destination website は各 public display のために OxiRanker に接続してはいけません。自分の database から read する必要があります。

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;
推奨 article URL
Multilingual websites の場合、/{lang}/blog/{slug} pattern を使用できます。例: /fa/blog/smart-internal-links
STEP 27

SEO、Schema、RTL、HTML rendering

destination website は SEO data を自分の database から read し、article page で render する必要があります。

SEO fields

page title 用の title
meta description 用の meta_description
canonical 用の effective_canonical_url
Open Graph 用の og_title と og_description
social preview image 用の og_image_url または cover_image_url
JSON-LD 用の article_schema

HTML rendering

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

destination website に strict security policy がある場合、HTML を save 前または display 前に sanitize できます。

RTL と LTR

各 article には lang、dir、is_rtl fields があります。dir が rtl の場合、article page は right-to-left で render される必要があります。

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

よくある errors とその意味

destination website errors は English で、clear かつ understandable である必要があります。そうすることで troubleshooting を迅速に行えます。

Export API errors

domain export token がありません。
Export API token が送信されていません。Authorization: Bearer YOUR_EXPORT_TOKEN を送信する必要があります。
domain export token が invalid です。
token が wrong、rotated 済み、この domain に関連していない、または domainId が wrong です。
token に required scope がありません。
token に required scope がありません。new tokens には articles:read と exports:read が必要です。
lang が invalid です。
query parameter の lang value が incorrectly 送信されました。

Webhook errors

Webhook URL は HTTPS を使用する必要があります。
Webhook URL は https:// で始まる必要があります。
Webhook signature が invalid です。
Webhook Secret が wrong、secret が rotated 済み、raw body が changed、または signature が incorrectly calculated されています。
Webhook timestamp が expired または invalid です。
timestamp が old、future、または invalid です。server clock と NTP を確認してください。
Webhook test delivery に失敗しました。
destination website が successful 200〜299 status を返さなかった、timed out した、または webhook.test を correctly handle しませんでした。
STEP 29

最終 API と Webhook testing checklist

implementation 後、これらの tests によって connection を beginning から end まで verify します。

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/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 を Postman で signature なしに call した場合、security error を受け取る必要があります。これは endpoint が active で security を check していることを意味します。

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

その後、OxiRanker 内で Test Webhook をクリックしてください。receiver が webhook.test を support している場合、response_status は 200 である必要があります。

Rotation 後
Webhook Secret または Export Token が rotated された場合、new value をすぐに copy し、destination website .env で replace し、destination backend を restart または redeploy してください。
STEP 30

正しい implementation のための重要な rules

API integration では、system を secure、stable、maintainable に保つためにこれらの rules に従う必要があります。

Export Token は backend のみに store する必要があります。
Webhook Secret は backend のみに store する必要があります。
destination Webhook endpoint は正確に /api/oxiranker/webhook である必要があります。
Webhook URL は HTTPS を使用する必要があります。
Webhook signature と timestamp は常に verify する必要があります。
Articles は simple insert ではなく UPSERT で store する必要があります。
duplicates を防ぐには unique constraint が required です。
Images は OxiRanker から download され、destination website に store される必要があります。
destination website は OxiRanker image URLs に permanent に依存してはいけません。
Image URLs は html_body と article_schema 内で replace する必要があります。
Webhook が fail した場合、daily sync は missed article を recover する必要があります。
users または logs に表示される errors は English で understandable である必要があります。
article が archived の場合、automatic sync はそれを再度 publish してはいけません。
original output data が失われないように、raw_detail_payload と raw_list_item を preserve する必要があります。
Export Token または Webhook Secret が rotated された場合、new value を .env で replace し、backend を restart または redeploy する必要があります。
STEP 31

Complete API flow summary

これらの steps を完了すると、custom website は OxiRanker で generated された articles を安全に receive、store、mirror、publish できます。

OxiRanker に登録する
domain を追加する
domain ownership を verify する
languages を選択して Blog URL を追加する
各 language に keywords を追加する
Article Profile を完成させる
必要に応じて Telegram を設定する
automatic article engine を設定する
Generate から最初の article を作成する
Preview と SEO Report を review する
API Output から Export Token を取得する
ready Export API endpoints を取得する
Webhook Secret を取得する
destination backend に固定 /api/oxiranker/webhook endpoint を作成する
Token と Secret を destination website .env に store する
site_articles table と unique constraint を作成する
Export JSON から full article fetching を実装する
Image Mirroring を実装する
UPSERT を実装する
daily sync を実装する
destination database から article、SEO、OG、Schema を render する
final Export API と Webhook tests を実行する