انتقل إلى المحتوى
Sursa Developers

استيراد المستندات

يبيّن هذا الدليل كيف تضع مستنداتك في Sursa باستخدام واجهة البطاقات: بضعة مستندات من نص برمجي، أو كل مستندات نظام ما عبر مهمة دورية. يصبح كل مستند بطاقة في مجموعة. يجدها الناس في البحث، ويجيب المساعد منها، ولها مسؤول ووسوم وتوثيق وصلاحيات مثل أي بطاقة أخرى.

إذا أردت أن يستنتج Sursa أي المستندات حُذفت في نظامك، أو كنت ترسل المستندات من منصة تكامل، فاستخدم مصدرًا مخصصًا بدلًا من ذلك. فهو مصمم للمزامنة الكاملة والتزايدية. أما هذا الدليل فيغطي الكتابة المباشرة في مجموعة.

تحتاج إلى مفتاح API له النطاق cards:write ودور يستطيع الكتابة في المجموعة المستهدفة (editor أو admin). وتستخدم الأمثلة الدالة المساعدة من البدء السريع.

اختيار نقطة النهاية المناسبة

ما لديكما تستخدمه
سجل بمعرّف ثابت في نظامكPUT /v1/cards-by-external-id/{external_id}: إنشاء أو استبدال بمعرّفك.
نص تضيفه مرة واحدة، بلا معرّف خاص بكPOST /v1/cards/text
ملف تضيفه مرة واحدةPOST /v1/cards
أرشيف ZIP لملفات كثيرةPOST /v1/cards/archive
ملف أكبر من حد الطلب الواحدالرفع على أجزاء، ثم أي من نقاط النهاية أعلاه.
محتوى جديد لبطاقة موجودة لديكPUT /v1/cards/{card_id}/content

لكل ما تستورده أكثر من مرة، استخدم external_id. فهو يجعل كل استيراد قابلًا للتكرار: تشغيله مرة أخرى يحدّث البطاقات التي أنشأها بدل إضافة نسخ.

إنشاء بطاقة أو استبدالها بمعرّفك

تأخذ العملية PUT /v1/cards-by-external-id/{external_id} معرّف السجل في نظامك. فإن لم تكن هناك بطاقة لها قيمة external_id هذه، تنشئ واحدة (201). وإن وُجدت، تستبدل محتوى البطاقة وتفاصيلها (200).

curl -X PUT https://api.sursa.com/v1/cards-by-external-id/kb-article-1042 \
  -H "Authorization: Bearer $SURSA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "collection_id": 3,
    "folder_id": "0f8e2d7c-4b6a-4e1f-9c3d-2a5b8e7f1c40",
    "title": "Returning a damaged item",
    "content_type": "html",
    "body": "<h2>Damaged items</h2><p>Send a photo within 7 days and we ship a replacement.</p>",
    "tags": ["returns", "shipping"],
    "metadata": {"source": "helpdesk", "article_id": "1042"}
  }'

ما يحدث عند التكرار:

  • لم يتغيّر المحتوى: لا يُنشأ إصدار جديد. الاستجابة 200 مع "status": "skipped" و"reason": "unchanged". وتكرار الطلب آمن دائمًا، حتى بلا Idempotency-Key.
  • تغيّر المحتوى: تحصل البطاقة على إصدار جديد وتُفهرَس من جديد. ويأخذ عنوانها ووسومها والمسؤول عنها وفئتها وmetadata القيم التي أرسلتها؛ ويُستبدل metadata كاملًا.
  • المجموعة والمجلد لا يُطبَّقان إلا عند إنشاء البطاقة. ولنقل البطاقة لاحقًا، غيّرها بالعملية PATCH /v1/cards/{card_id}.

الإصدار الجديد يزيل توثيق البطاقة، كما يفعل أي تغيير في المحتوى، فيعيد موثِّق توثيقها.

صيغ النص

content_typeنص الطلب
markdown (افتراضي)بصيغة Markdown.
htmlبصيغة HTML. يُنظَّف حتى لا يبقى إلا ترميز بسيط: العناوين والفقرات والقوائم والجداول وروابط http وhttps وmailto. وتُزال النصوص البرمجية والأنماط والإطارات والاستمارات والصور مع محتواها.
textنص عادي. تُفصل الفقرات بأسطر فارغة.

انتظار البطاقة أو عدمه

افتراضيًا، يعود الطلب حين تصبح البطاقة جاهزة للعثور عليها، مع "status": "ready" والبطاقة في card. ويستغرق ذلك من جزء من الثانية للنص القصير إلى دقيقة أو أكثر للملف الطويل.

في الاستيراد الجماعي، أرسل "async": true. فتصل الاستجابة فورًا مع "status": "queued" ومهمة الفهرسة في job (مع job_id). تحقّق من المهمة بالعملية GET /v1/jobs/{job_id}، أو تابع كل المهام في GET /v1/jobs/stream. وتنتهي المهمة بقيمة status هي done أو error أو canceled.

curl https://api.sursa.com/v1/jobs/48213 \
  -H "Authorization: Bearer $SURSA_API_KEY"

رفع ملف

تُرسَل الملفات في طلب multipart/form-data، في الحقل file. يقرأ Sursa ملفات PDF وWord وExcel وPowerPoint وجداول OpenDocument وCSV وMarkdown وHTML وAsciiDoc والبريد الإلكتروني (.eml) والنص العادي وصيغ الصور الشائعة. ويتحقق من تطابق اسم الملف ونوعه المعلن ومحتواه، ويرفض عدم التطابق بالحالة 415.

لإنشاء بطاقة ملف أو استبدالها بمعرّفك، أرسل الملف إلى العملية نفسها PUT /v1/cards-by-external-id/{external_id}:

curl -X PUT https://api.sursa.com/v1/cards-by-external-id/handbook-2026 \
  -H "Authorization: Bearer $SURSA_API_KEY" \
  -F "file=@employee-handbook.pdf" \
  -F "collection_id=3" \
  -F "tags=hr,policies" \
  -F 'metadata={"owner_team":"people"}'

في الاستمارة، الحقل tags سلسلة نصية مفصولة بفواصل، وmetadata كائن JSON مكتوب سلسلةً نصية. والبطاقة الجديدة بلا title تأخذ اسم الملف عنوانًا لها.

لإضافة ملف مرة واحدة بلا معرّف خاص بك، استخدم POST /v1/cards بالاستمارة نفسها مع Idempotency-Key. ورفع ملف موجود من قبل في المجموعة يعيد "status": "skipped" مع "duplicate": true وcard_id الموجود، ما لم تمرّر external_id.

حجم الملف الواحد محدود بإعدادات التثبيت (200 ميغابايت افتراضيًا). وللملفات الأكبر، أو للشبكات غير الموثوقة، ارفع الملف على أجزاء أولًا.

استبدال محتوى بطاقة

حين يكون لديك معرّف البطاقة في Sursa، تستبدل العملية PUT /v1/cards/{card_id}/content نصها أو ملفها وتُبقي كل ما عداه: المسؤول، والوسوم، والموثِّقين، وفترة التوثيق، والفئة المعيّنة يدويًا.

curl -X PUT https://api.sursa.com/v1/cards/3f2c9a7e-5b1d-4c8e-9f0a-1b2c3d4e5f60/content \
  -H "Authorization: Bearer $SURSA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"content_type": "markdown", "body": "# Refund policy\n\nRefunds are processed within 10 days."}'
  • إرسال المحتوى نفسه مرة أخرى يعيد "status": "skipped" مع "reason": "unchanged".
  • البطاقة التي تجري فهرستها الآن تعيد الحالة 409 مع الرمز cards.indexing_in_progress. أعد المحاولة بعد بضع ثوانٍ.
  • البطاقة الآتية من مصدر، مثل موصّل أو مصدر مخصص، لا تُغيَّر إلا في مصدرها: cards.managed_by_source.
  • يجب أن يُسمح للمفتاح برؤية البطاقة كلها؛ والبطاقة المرئية جزئيًا تعيد cards.content_whole_access.

استيراد مستندات كثيرة

حلقة تمر على سجلاتك مع PUT /v1/cards-by-external-id/… هي أبسط استيراد جماعي، ويمكن إيقافها وتشغيلها من جديد بأمان في أي لحظة. ولجعلها سريعة ومراعية لحد المعدل:

  1. أرسل "async": true، كي يعود كل طلب بمجرد وضع البطاقة في قائمة الانتظار.
  2. شغّل بضعة طلبات بالتوازي (من 4 إلى 8)، وأبطئ حين تُظهر الترويسة RateLimit أن الطلبات المتبقية قليلة. انظر حدود معدل الطلبات والحصص.
  3. عند الحالة 429، انتظر Retry-After؛ وعند خطأ في الشبكة أو 5xx، أعد الطلب نفسه. فالتكرار لا ضرر منه.
  4. دوّن external_id لكل سجل ينتهي بخطأ، وأعد محاولته في النهاية.
import { api } from "./sursa.mjs";

async function importAll(records, concurrency = 6) {
  const failed = [];
  const queue = [...records];
  async function worker() {
    for (let record = queue.shift(); record; record = queue.shift()) {
      try {
        await api("PUT", `/v1/cards-by-external-id/${encodeURIComponent(record.id)}`, {
          collection_id: 3,
          title: record.title,
          content_type: "html",
          body: record.html,
          metadata: { source: "helpdesk" },
          async: true,
        });
      } catch (error) {
        failed.push({ id: record.id, error: String(error) });
      }
    }
  }
  await Promise.all(Array.from({ length: concurrency }, worker));
  return failed;
}

لأرشيف ZIP من الملفات، تحوّل العملية POST /v1/cards/archive كل ملف مدعوم فيه إلى بطاقة بطلب واحد. وتجيب فورًا بخطة الاستيراد (أي الملفات تصبح بطاقات وأيها يُتخطى، ولماذا) وبمهمة معالجة؛ وتعرض العملية GET /v1/archives/{job_id} النتيجة.

العثور على البطاقات المستوردة وحذفها

الأخطاء الشائعة

الرمزالسببما العمل
collections.not_foundلا مجموعة بهذا المعرّف أو الاسم مرئية للمفتاح.تحقّق من collection_id بالعملية GET /v1/collections.
collections.no_write_accessلا يحق للمفتاح الكتابة في هذه المجموعة.استخدم مفتاحًا بالدور editor وله وصول إلى المجموعة.
cards.external_id_takenطلب POST مع external_id تحمله بطاقة أخرى.استخدم PUT /v1/cards-by-external-id/… لاستبدالها.
cards.managed_by_sourceالبطاقة تتبع مصدرًا.غيّر المستند في مصدره.
files.type_mismatchاسم الملف ونوعه ومحتواه غير متطابقة.أرسل الملف بامتداده الحقيقي.
files.too_largeالملف فوق حد الرفع أو فوق مساحة التخزين للمؤسسة.استخدم الرفع على أجزاء، أو حرّر مساحة تخزين.
limits.cardsبلغت المؤسسة حد البطاقات في خطتها.احذف البطاقات التي لم تعد لازمة، أو غيّر الخطة.
api.metadata_invalidالحقل metadata يتجاوز أحد الحدود.انظر البيانات الوصفية.