/ دليل المطورين
النشر عبر API على أي موقع.
كيف تبني نقطة النهاية الخاصة بك لكي ينشر Orova المقالات الجاهزة على موقع لا يعمل بـ WordPress.
ينشر Orova المقالات الجاهزة على WordPress مباشرة. إذا لم يكن موقعك مبنيًا على WordPress — نظام CMS مخصص، أو إعداد headless، أو موقع ثابت، أو واجهة خلفية خاصة بك — فلا يزال بإمكانك جعل Orova ينشر تلقائيًا عبر استقبال المقالات من خلال API صغيرة. تكتب نقطة نهاية HTTP واحدة، ويستدعيها Orova في كل مرة ينتهي فيها من مقال. هذا الدليل موجّه للمطورين ويصف عقد الطلب كاملًا.
/ نظرة عامة
آلية العمل
عندما ينتهي Orova من كتابة مقال لكلمة رئيسية، يرسل طلب HTTP POST واحدًا إلى عنوان URL لنقطة النهاية التي سجّلتها. جسم الطلب بصيغة JSON ويحتوي على المقال كاملًا. تنشئ نقطة النهاية الخاصة بك المقال لديك وتردّ بعنوان URL العام النهائي للمقال. يحفظ Orova ذلك العنوان كرابط منشور، تمامًا كما يفعل مع مقال WordPress. استخدم ذلك عندما تريد نشرًا تلقائيًا بالكامل على موقع لا يعمل بـ WordPress، أو عندما تفضّل الاحتفاظ بالتحكم الكامل في طريقة تخزين المقالات.
/ الإعداد
الإعداد — ست خطوات
- 1
فعّل "الربط عبر API" في مشروعك. افتح مشروعك (المشاريع ← مشروعك ← عمليات الربط). بجوار بطاقة WordPress توجد بطاقة الربط عبر API. أدخل فيها عنوان URL لنقطة النهاية الخاصة بك. حقل المفتاح السري اختياري: اتركه فارغًا وسينشئ Orova مفتاحًا، أو الصق مفتاحًا يستخدمه موقعك بالفعل. بعد الحفظ، يظهر المفتاح على البطاقة مع زر نسخ. ينشر المشروع إلى وجهة واحدة فقط — الربط عبر API يقفل زر WordPress، والعكس صحيح.
- 2
أنشئ نقطة نهاية تقبل POST. على موقعك أو خادمك الخاص، أنشئ مسارًا يستمع لطلبات HTTP POST بجسم JSON. هذا هو عنوان URL الذي تلصقه في شاشة عمليات الربط في المشروع. يستدعيه Orova مرة واحدة لكل مقال ينتهي منه.
- 3
تحقّق من رمز Bearer في كل طلب. يحمل كل طلب من Orova ترويسة Authorization بالشكل "Bearer <secret>"، باستخدام المفتاح السري نفسه المعروض في مشروعك. قارنه بنسختك المخزّنة وارفض أي طلب لا يطابقه — فهذا ما يمنع أي شخص آخر من النشر على موقعك.
- 4
أنشئ المقال من الحمولة. اقرأ جسم JSON وأنشئ مقالًا في نظام CMS أو قاعدة البيانات لديك: استخدم title وslug وcontent_html للمقال نفسه، وexcerpt كوصف تعريفي، وfeatured_image_url كصورة غلاف، وkeyword / lang / published_at كبيانات وصفية.
- 5
أرجِع عنوان URL المباشر للمقال. استجب بـ HTTP 200 أو 201 وجسم JSON بالشكل { "url": "https://yoursite.com/the-new-article" }. يخزّن Orova ذلك العنوان كرابط منشور للمقال. إذا أرجعت حالة غير 2xx، أو أغفلت الحقل url، فسيعدّ Orova النشر فاشلًا.
- 6
تعامل مع طلبات التحديث من محرك التحسين. عندما يحمل الجسم action = "update"، ابحث عن المقال الموجود عبر target_url (أو slug) واستبدل title وcontent_html وexcerpt في مكانها — مع إبقاء عنوان URL دون تغيير. استجب بـ 200 مع { "url": ... } يشير إلى المقال نفسه. نقاط النهاية التي لا تتعامل مع ذلك بعد ستجعل Orova يبلّغ ببساطة عن فشل التحسين؛ ويستمر نشر المقالات الجديدة كما كان.
/ العقد
الطلب الذي يرسله Orova
يصل كل مقال جاهز كطلب واحد بهذا الشكل:
POST <your endpoint URL>
Headers:
Content-Type: application/json
Authorization: Bearer <secret> # the secret key shown in Project -> Connections
User-Agent: Orova-SEO
Body (JSON):
{
"title": "...", # article headline
"slug": "...", # URL-friendly identifier
"content_html": "...", # full article HTML
"excerpt": "...", # meta description
"featured_image_url": "..." | null,
"keyword": "...", # target keyword
"lang": "en", # ISO language code
"published_at": "2026-05-17T09:00:00.482913", # ISO 8601, UTC, no offset suffix
"status": "draft" # optional: "draft" keeps it unpublished
}
# Optional fields are left out of the body when empty — never sent as null./ مرجع
حقول الحمولة
حقول طلب النشر (مقال جديد). تعيد طلبات الحذف والتحديث استخدام نقطة النهاية نفسها بأجسام أصغر موضحة في أقسامها أدناه.
| الحقل | النوع | الوصف |
|---|---|---|
| title | string | عنوان المقال. |
| slug | string | URL-friendly identifier suggested for the post. |
| content_html | string | جسم المقال كاملًا بصيغة HTML جاهزة للنشر. |
| excerpt | string | ملخص قصير، مخصص للوصف التعريفي. |
| featured_image_url | string | null | عنوان URL لصورة الغلاف، أو null عند عدم وجودها. |
| keyword | string | الكلمة الرئيسية المستهدفة في SEO التي كُتب المقال من أجلها. |
| lang | string | Language as an ISO code, e.g. "en" or "vi". |
| published_at | string | لحظة إرسال Orova للمقال، بتنسيق ISO 8601 بتوقيت UTC، دون لاحقة للمنطقة الزمنية. |
| status | string | undefined | اختياري. "draft" يُبقي المقال غير منشور؛ و"publish"، أو عدم وجود قيمة إطلاقًا، ينشره. |
| action | string | undefined | غائب في المقالات الجديدة. "delete" يطلب منك إزالة مقال؛ و"update" (يرسله محرك التحسين) يطلب منك استبدال مقال موجود في مكانه. |
| target_url | string | فقط مع action = "update": عنوان URL المباشر للمقال المراد استبداله. أبقِ عنوان URL دون تغيير. |
| id | number | undefined | Only sent when Orova knows your post id (from `list` or `get`): match on it before `target_url`. |
تُستبعد الحقول الاختيارية من الجسم تمامًا عندما لا يوجد ما يُرسَل — لا يرسل Orova القيمة null أبدًا، لذا اقرأها بحذر. status اختياري أيضًا: "draft" ينشئ المقال كمسودة، بينما "publish" أو عدم وجود status إطلاقًا ينشره فورًا.
/ العقد
الاستجابة التي يجب إرجاعها
بعد إنشاء المقال، ردّ بحالة HTTP 200 أو 201 وجسم JSON هذا:
HTTP 200 (or 201)
Content-Type: application/json
{
"url": "https://yoursite.com/published-article"
}يخزّن Orova قيمة url تلك كرابط منشور للمقال. إذا أرجعت نقطة النهاية أي حالة غير 2xx، أو جسمًا ليس كائن JSON يحتوي على حقل url، فسيعدّ Orova النشر فاشلًا ويضع علامة على المقال وفقًا لذلك لتتمكن من إعادة المحاولة. ردّ خلال 60 ثانية — بعدها تنتهي مهلة الطلب لدى Orova (ينطبق ذلك على النشر والتحديث والحذف على حد سواء).
/ مثال
مثال برمجي
نقطة نهاية استقبال بسيطة في Node.js باستخدام Express. الخطوات نفسها — التحقق من الرمز، والتفرع حسب action (update / delete)، وإنشاء المقال أو تغييره، وإرجاع عنوان URL — تنطبق على أي لغة أو إطار عمل.
// Node.js / Express — a minimal receiving endpoint
import express from "express";
const app = express();
app.use(express.json({ limit: "5mb" }));
// The secret Orova generated for this project.
const OROVA_SECRET = process.env.OROVA_SECRET;
app.post("/orova/publish", async (req, res) => {
// 1. Verify the Bearer token.
const auth = req.get("authorization") || "";
if (auth !== "Bearer " + OROVA_SECRET) {
return res.status(401).json({ error: "unauthorized" });
}
// 2. Required: list your articles (duplicate check, internal links). action = "list".
if (req.body.action === "list") {
const posts = await listPosts(req.body.limit || 500); // newest first, 500 max
// each post: { id, title, slug, link, date, modified, status, author, excerpt? }
return res.status(200).json({ ok: true, posts });
}
// 3. Required: read one article in full (used when optimizing it). action = "get".
if (req.body.action === "get") {
const post = await findPost(req.body.id, req.body.target_url, req.body.slug);
if (!post) return res.status(404).json({ ok: false, err: "not_found" });
return res.status(200).json({ ok: true, post });
}
// 4. Visual Editor: store an image, answer with its absolute URL.
if (req.body.action === "upload_image") {
const { filename, mime, data_base64 } = req.body;
if (!String(mime || "").startsWith("image/")) {
return res.status(422).json({ error: "image files only" });
}
const bytes = Buffer.from(data_base64, "base64");
if (bytes.length > 15 * 1024 * 1024) {
return res.status(413).json({ error: "image too large" });
}
return res.status(200).json({ ok: true, url: await saveImage(filename, bytes) });
}
// 5. Delete requests: body carries action = "delete".
if (req.body.action === "delete") {
const removed = await deletePostByUrl(req.body.url, req.body.slug);
if (!removed) return res.status(404).json({ deleted: true }); // already gone
return res.status(200).json({ deleted: true });
}
// 6. Update requests: action = "update". Optional fields may be absent.
if (req.body.action === "update") {
const updated = await updatePost(req.body.id, req.body.target_url, {
title: req.body.title,
html: req.body.content_html,
metaDescription: req.body.excerpt,
coverImage: req.body.featured_image_url, // optional
status: req.body.status, // optional: "publish" | "draft"
});
if (!updated) return res.status(404).json({ error: "post not found" });
return res.status(200).json({ url: updated.url });
}
// 7. New article: read the payload Orova sent.
const {
title, slug, content_html, excerpt,
featured_image_url, keyword, lang, published_at, status,
} = req.body;
// 8. Create the article in your own CMS or database.
const post = await createPost({
title,
slug,
html: content_html,
metaDescription: excerpt,
coverImage: featured_image_url,
keyword,
lang,
publishedAt: published_at,
draft: status === "draft", // status is optional; absent means publish now
});
// 9. Return 200/201 with the live URL.
return res.status(201).json({
url: "https://yoursite.com/" + post.slug,
});
});
app.listen(3000);/ العقد — التحديث
تحديث المقالات (تحسين)
يعيد محرك التحسين في Orova كتابة المقالات المنشورة بالفعل — حقائق محدَّثة، وعناوين أفضل، ومحتوى مدمج — ثم يستبدل المقال الموجود في مكانه، مع إبقاء عنوان URL دون تغيير. يذهب الطلب إلى عنوان URL نفسه لنقطة النهاية، كطلب POST برمز Bearer نفسه:
POST <your endpoint URL>
Headers:
Content-Type: application/json
Authorization: Bearer <secret>
User-Agent: Orova-SEO
Body (JSON):
{
"action": "update", # always the literal string "update"
"id": 123, # optional: post id, match on this first
"target_url": "...", # public URL of the post to overwrite (match next)
"title": "...", # new headline
"slug": "...", # slug fallback for matching, may be empty
"content_html": "...", # full new article body as HTML
"excerpt": "...", # new meta description
"featured_image_url": "...", # optional: new cover image
"status": "publish" # optional: "publish" or "draft"
}
# Optional fields are left out of the body when empty — never sent as null.ابحث عن المقال عبر target_url (وإلا فعبر slug)، واستبدل عنوانه وجسمه ووصفه التعريفي، وأبقِ عنوان URL كما هو، ثم استجب بـ 200 مع { "url": "<same post URL>" }. أي حالة غير 2xx، أو جسم بلا url، تجعل Orova يضع علامة فشل على ذلك التحسين (ولا ينشئ مقالًا مكررًا أبدًا). نقاط النهاية التي لم تنفّذ ذلك بعد تواصل نشر المقالات الجديدة دون تغيير — ولا يلزم تنفيذ التحديث إلا عند استخدامك شاشة التحسين.
منذ 29 أغسطس يمكن لجسم التحديث أن يحمل أيضًا id (طابِق عليه قبل target_url)، وfeatured_image_url لصورة غلاف جديدة، وstatus بقيمة "publish" أو "draft". الثلاثة اختيارية؛ وعندما تكون فارغة تُحذف من الجسم، ولا تُرسَل أبدًا كـ null.
/ العقد — المحرر المرئي
قراءة المقالات (مطلوب)
يحتاج Orova إلى قراءة المقالات الموجودة بالفعل على موقعك — لتجنّب تكرار المواضيع، وإضافة روابط داخلية، وتحسين المقالات القديمة. أضِف إجراءين آخرين على عنوان URL نفسه لنقطة النهاية، برمز Bearer نفسه: list يُرجع مقالاتك، وget يُرجع أحدها كاملًا.
POST <your endpoint URL> # same endpoint, same Bearer secret
Body (JSON):
{
"action": "list", # always the literal string "list"
"limit": 500 # newest article first, 500 maximum
}
Response — HTTP 200:
{
"ok": true,
"posts": [
{
"id": 123, # your own post id, reused by "get" and "update"
"title": "...",
"slug": "...",
"link": "https://yoursite.com/the-article",
"date": "2026-08-29", # published date, YYYY-MM-DD
"modified": "2026-08-29",
"status": "publish", # "publish" or "draft"
"author": "...",
"excerpt": "..." # optional: short summary, 300 characters max
}
]
}
# Required: Orova reads this list to avoid duplicate topics,
# add internal links and analyze the whole site.POST <your endpoint URL>
Body (JSON):
{
"action": "get", # always the literal string "get"
"id": 123, # match on this first
"target_url": "...", # then the public URL
"slug": "..." # then the slug
}
Response — HTTP 200:
{
"ok": true,
"post": {
"id": 123,
"title": "...",
"slug": "...",
"link": "https://yoursite.com/the-article",
"content_html": "...", # full article body as HTML
"excerpt": "...",
"featured_image_url": "...",
"status": "publish" # "publish" or "draft"
}
}
No such post — HTTP 404:
{ "ok": false, "err": "not_found" }الحد الأقصى لـ limit هو 500، ويأتي أحدث مقال أولًا. في get، يرسل Orova id وtarget_url وslug معًا — طابِق على أيٍّ منها تتعرف عليه، بهذا الترتيب. عند عدم وجود تطابق، أجب بـ 404 مع { "ok": false, "err": "not_found" }. الحقل excerpt في list اختياري (300 حرف كحد أقصى). مطلوب: يستدعي Orova list لمعرفة المقالات الموجودة بالفعل (لتجنّب التكرار، وإضافة روابط داخلية، وتحليل الموقع بأكمله)، ويستدعي get لقراءة مقال عند تحسينه. إذا لم يُجب موقعك على هذين الإجراءين، فلن تحصل المقالات الجديدة على روابط داخلية ولن يمكن تحسين المقالات القديمة.
/ العقد — المحرر المرئي
رفع الصور (المحرر المرئي)
عندما تُفلت صورة في المحرر المرئي، يرسلها Orova إلى عنوان URL نفسه لنقطة النهاية بترميز base64 داخل جسم JSON — دون multipart، ودون عنوان URL ثانٍ يلزم تأمينه.
POST <your endpoint URL>
Body (JSON):
{
"action": "upload_image", # always the literal string "upload_image"
"filename": "cover.png", # original file name, extension included
"mime": "image/png", # image/* only
"data_base64": "iVBORw0KGgo..." # file bytes, base64, 15 MB maximum
}
Response — HTTP 200 (or 201):
{
"ok": true,
"url": "https://yoursite.com/uploads/cover.png" # absolute URL
}اقبل image/* فقط وارفض أي ملف يتجاوز 15 MB. خزّن الملف، ثم أجب بعنوان URL المطلق للصورة: المسار النسبي يُفسد المقال بمجرد عرضه في مكان آخر. هذا الإجراء مطلوب للمحرر المرئي فقط.
/ العقد — الحذف
حذف المقالات
عندما يزيل مستخدم مقالًا منشورًا داخل Orova (من شاشة كتابة المقالات أو التقارير)، يطلب Orova من موقعك حذفه أيضًا. يذهب الطلب إلى عنوان URL نفسه لنقطة النهاية، كطلب POST برمز Bearer نفسه — والفرق الوحيد هو الجسم:
POST <your endpoint URL>
Headers:
Content-Type: application/json
Authorization: Bearer <secret>
User-Agent: Orova-SEO
Body (JSON):
{
"action": "delete", # always the literal string "delete"
"url": "...", # public URL of the article to remove (match on this first)
"slug": "...", # slug fallback, may be empty
"keyword": "..." # the article's target keyword, for your logs
}ابحث عن المقال عبر url (وإلا فعبر slug)، وأزله أو ألغِ نشره، ثم استجب بـ 200 و{ "deleted": true } — كما يصلح 204 المجرد أيضًا. إذا لم يعد المقال موجودًا، فردّ بـ 404 وسيعدّه Orova محذوفًا بالفعل. إرجاع { "deleted": false } يخبر Orova بأن المقال لم يُزَل. لا تحتوي طلبات النشر العادية أبدًا على حقل action، لذا تستمر نقاط النهاية الحالية في العمل دون تغيير — تنفيذ الحذف اختياري لكنه موصى به.
/ الأمان
ملاحظات أمنية
- تحقّق دائمًا من رمز Bearer. نقطة النهاية الخاصة بك عنوان URL عام — يمكن لأي شخص استدعاؤها. ترويسة Authorization هي الشيء الوحيد الذي يثبت أن الطلب جاء فعلًا من Orova، لذا ارفض كل طلب لا يطابق رمزه السر المخزّن لديك تمامًا.
- قدّم نقطة النهاية عبر HTTPS. ينتقل السر في ترويسة. يمنع HTTPS قراءته أثناء النقل.
- أبقِ السر خارج الشيفرة البرمجية. خزّنه في متغير بيئة أو في مدير أسرار، ولا تكتبه مباشرة في ملف تقوم بإيداعه. إذا انكشف يومًا، فأنشئ سرًا جديدًا من المشروع ← عمليات الربط.
- تعامل مع content_html كمحتوى، لا كترميز موثوق. خزّنه واعرضه بالحرص نفسه الذي تتعامل به مع جسم أي مقال في نظام CMS لديك.
/ الختام
هذا هو العقد كاملًا
بعد تشغيل نقطة النهاية وتسجيلها في مشروعك، ينشر Orova على موقعك تلقائيًا — تمامًا كما يفعل مع مستخدمي WordPress. كل مقال ينتهي منه يصل إلى خادمك ويصبح منشورًا، ويعود عنوان URL العام الخاص به إلى التقارير والتحليل لتتمكن من متابعة أدائه.
/ هل تحتاج إلى مساعدة؟
هل تواجه صعوبة في ربط نقطة النهاية؟ افتح قسم الدعم داخل مساحة العمل الخاصة بك، أو أرسل إلينا رسالة.
