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

التقسيم إلى صفحات في واجهة مُجيب بالمؤشر nextCursor

التقسيم إلى صفحات في واجهة مُجيب البرمجية يتم بمؤشر (cursor): كل قائمة تعيد صفحة من النتائج ومعها nextCursor، فتمرّره في الطلب التالي باسم cursor حتى تصبح قيمته null. حجم الصفحة 50 عنصرًا، ويمكنك رفعه إلى 100.

يعمل هذا في القائمتين: GET /api/v1/contacts وGET /api/v1/bookings. أما طلبات العنصر الواحد فلا صفحات فيها.

كيف تعمل الصفحات في الواجهة؟

رابط القسم «كيف تعمل الصفحات في الواجهة؟»

ترسل الطلب الأول دون cursor، فيعود الرد بالعناصر في data وبمؤشر الصفحة التالية في nextCursor:

{
"data": [ { "id": "5d8f2a61-0c4e-4b7a-9f13-6e2b8c0d4a57" }, { "id": "9b1e3c74-2a5d-4f08-8e6b-1c7d0a3f5e92" } ],
"nextCursor": "MjAyNi0xMC0yOFQxNDowNToxMi40ODFafDliMWUzYzc0LTJhNWQtNGYwOC04ZTZiLTFjN2QwYTNmNWU5Mg"
}

(العناصر هنا مختصرة إلى id للتوضيح. شكلها الكامل في صفحة كل طلب.)

المعاملمعناه
limitعدد العناصر في الصفحة، من واحد إلى 100. إذا لم ترسله فهو 50
cursorقيمة nextCursor من الصفحة السابقة، كما هي حرفًا بحرف
  • عندما تكون nextCursor قيمتها null فقد وصلت إلى آخر صفحة.
  • قد تأتي الصفحة الأخيرة بعناصر أقل من limit، وقد تأتي data فارغة إذا لم يطابق الطلب شيئًا.
  • المؤشر نص مغلق: لا تحاول قراءته أو تركيبه بنفسك. المؤشر الذي لا يُقرأ يعود بالخطأ 400 ورسالة Invalid cursor.

كيف أقرأ كل الصفحات حتى النهاية؟

رابط القسم «كيف أقرأ كل الصفحات حتى النهاية؟»

اطلب الصفحة، واجمع عناصرها، ثم اطلب التالية بمؤشرها، وتوقّف عندما يصبح المؤشر null. هذه دالة تفعل ذلك وتنتظر إذا وصلت إلى حدود الطلبات:

const BASE = 'https://api.mujib.ai/api/v1';
const headers = { Authorization: `Bearer ${process.env.MUJIB_API_KEY}` };
async function fetchAll(path, params = {}) {
const items = [];
let cursor = null;
for (;;) {
const url = new URL(BASE + path);
for (const [k, v] of Object.entries({ ...params, limit: 100 })) url.searchParams.set(k, String(v));
if (cursor) url.searchParams.set('cursor', cursor);
const res = await fetch(url, { headers });
if (res.status === 429) {
const wait = Number(res.headers.get('retry-after') ?? '60');
await new Promise((r) => setTimeout(r, wait * 1000));
continue;
}
const body = await res.json();
if (!res.ok) throw new Error(`${res.status} ${body.error.code}: ${body.error.message}`);
items.push(...body.data);
if (body.nextCursor === null) return items;
cursor = body.nextCursor;
}
}
const bookings = await fetchAll('/bookings', { from: '2026-11-01', to: '2026-11-30' });
console.log(bookings.length);
import os
import time
import requests
BASE = "https://api.mujib.ai/api/v1"
HEADERS = {"Authorization": f"Bearer {os.environ['MUJIB_API_KEY']}"}
def fetch_all(path, params=None):
items, cursor = [], None
while True:
query = {**(params or {}), "limit": 100}
if cursor:
query["cursor"] = cursor
res = requests.get(BASE + path, params=query, headers=HEADERS, timeout=30)
if res.status_code == 429:
time.sleep(int(res.headers.get("Retry-After", "60")))
continue
body = res.json()
if not res.ok:
raise RuntimeError(f"{res.status_code} {body['error']['code']}: {body['error']['message']}")
items.extend(body["data"])
if body["nextCursor"] is None:
return items
cursor = body["nextCursor"]
contacts = fetch_all("/contacts", {"since": "2026-10-01T00:00:00Z"})
print(len(contacts))

لكل قائمة ترتيب ثابت لا يتغيّر بين الصفحات:

القائمةالترتيب
العملاءالأحدث أولًا، بحسب createdAt (أول ظهور للعميل)، ثم بالمعرّف id عند التساوي
الحجوزاتالأقدم أولًا، بحسب الوقت المحلي للموعد في فرعه (localTime)، ثم بالمعرّف id عند التساوي

إذا كانت فروعك في مناطق زمنية مختلفة، فترتيب الحجوزات يتبع الوقت المحلي لكل فرع، لا اللحظة بتوقيت UTC.

هل يتغيّر شيء إذا أُضيفت بيانات أثناء القراءة؟

رابط القسم «هل يتغيّر شيء إذا أُضيفت بيانات أثناء القراءة؟»

لا يتكرر عنصر قرأته ولا يُتخطّى عنصر بسبب إضافة جديدة، لأن المؤشر يحفظ موضعك بقيمة آخر عنصر قرأته لا برقم صفحة.

  • عميل جديد أثناء القراءة يظهر في أول القائمة، فلا تراه في الصفحات التالية. تلتقطه في المزامنة التالية بالمعامل since.
  • حجز جديد يقع وقته بعد موضعك يظهر في صفحة تالية، والذي يقع قبله تراه في القراءة التالية.
  • حجز نُقل موعده أثناء القراءة ينتقل إلى موضعه الجديد في الترتيب، فقد تراه مرتين أو لا تراه. اعتمد على id لتمنع التكرار.

كيف أجلب الجديد فقط بدل القائمة كلها؟

رابط القسم «كيف أجلب الجديد فقط بدل القائمة كلها؟»

احفظ موضعك من المزامنة السابقة، واطلب ما بعده فقط.

  • العملاء: احفظ أحدث قيمة createdAt قرأتها، وأرسلها في المزامنة التالية باسم since. المعامل يشمل اللحظة نفسها، فقد يعود آخر عميل قرأته مرة أخرى؛ تجاهله بمعرّفه.
  • الحجوزات: اطلب نافذة أيام بـ from وto، مثل الأيام السبعة القادمة. لا يوجد معامل لما تغيّر حديثًا، فإذا أردت أن تعرف بكل حجز جديد أو ملغى لحظة حدوثه فاستخدم Webhooks.
  • أبقِ معاملات الاختيار نفسها في كل صفحات القراءة الواحدة: المؤشر يحفظ الموضع فقط، ولا يحفظ since أو from أو to أو status.

الأسئلة الشائعة

هل أستطيع طلب الصفحة الثالثة مباشرة؟

لا. لا توجد أرقام صفحات. تصل إلى كل صفحة بالمؤشر الذي أعادته الصفحة التي قبلها.

كيف أعرف أنني وصلت إلى آخر صفحة؟

عندما تكون قيمة nextCursor في الرد null. قد تأتي الصفحة الأخيرة بعناصر أقل من limit.

هل أستطيع تغيير limit بين صفحة وأخرى؟

نعم. المؤشر يحفظ موضعك لا حجم الصفحة. أما المعاملات التي تختار النتائج، مثل since وfrom وto وstatus، فأبقِها كما هي.

ما أكبر حجم للصفحة؟

100 عنصر. وإذا لم ترسل limit فحجم الصفحة 50.