پنل آیتی‌لند مستندات توسعه‌دهندگان
نسخهٔ ۱ فقط‌خواندنی JSON با کدگذاری UTF-8 احراز هویت با کلید API

راهنمای استفاده از API حضور و غیاب

این سند نحوهٔ دریافت اطلاعات حضور و غیاب کلاس‌های برگزارشده روی سرورهای BigBlueButton موسسه را، از طریق رابط برنامه‌نویسی پنل آیتی‌لند، شرح می‌دهد. مخاطب آن توسعه‌دهندگان سامانه‌های بیرونی (مانند سامانهٔ مدیریت یادگیری) هستند که با کلید اختصاصی موسسه به این اطلاعات دسترسی پیدا می‌کنند.

۱ مقدمه و دامنهٔ کاربرد

API حضور و غیاب اطلاعات حضور شرکت‌کنندگان را برای سامانه‌های مجاز فراهم می‌کند: چه کسی، در کدام جلسه، در چه ساعتی وارد و خارج شده، مجموع مدت حضور واقعی او چقدر بوده و این مقدار نسبت به پرحضورترین فرد همان جلسه (معمولاً مدرس) چند درصد است. این سرویس صرفاً برای خواندن اطلاعات طراحی شده و هیچ امکانی برای ایجاد، ویرایش یا حذف داده ارائه نمی‌دهد.

آدرس پایهhttps://panel.itland.org/api/v1
پروتکلHTTPS (الزامی). درخواست‌های ساده HTTP به HTTPS هدایت می‌شوند و کلید در مسیر ناامن ارسال نشود.
قالب دادهJSON با کدگذاری UTF-8؛ نام فیلدها به‌صورت camelCase است.
روش‌های HTTPفقط GET
احراز هویتکلید API موسسه در هدر X-Api-Key (بخش ۳)
نسخه۱ (بخش ۱۳)
پیش‌نیاز

برای استفاده از API، ابتدا باید مدیر پنل موسسهٔ شما در بخش «کلیدهای API» یک کلید برای سامانهٔ شما صادر کند.

۲ مفاهیم پایه

درک دقیق مفاهیم زیر برای تفسیر صحیح پاسخ‌ها ضروری است.

جلسه (Session)

هر بار تشکیل یک کلاس در BigBlueButton یک جلسه است. هر جلسه شناسهٔ یکتا (sessionId) و زمان شروع و پایان مستقل خود را دارد.

کلاس (Class)

مجموعهٔ جلسه‌هایی که externalMeetingId یکسان دارند؛ همان meetingID که سامانهٔ شما هنگام ساخت کلاس به BigBlueButton می‌دهد.

شرکت‌کننده (Participant)

هر کاربر با externalUserId شناخته می‌شود؛ همان userID که سامانهٔ شما هنگام ورود کاربر به کلاس ارسال کرده است.

بازهٔ حضور (Interval)

یک دورهٔ پیوسته از ورود تا خروج. کاربر ممکن است در یک جلسه چند بار وارد و خارج شود و هر بار یک بازه ثبت می‌شود.

درصد حضور

مدت واقعی حضور هر فرد نسبت به پرحضورترین فرد همان جلسه؛ در کلاس‌های معمول، مدرس مبنای ۱۰۰ درصد است.

آستانهٔ حضور (Threshold)

مرزی (پیش‌فرض ۷۰ درصد) که حضور کمتر از آن «ناکافی» علامت‌گذاری می‌شود (belowThreshold).

کلاسی که چند بار در روز برگزار می‌شود

هر بار تشکیل کلاس یک جلسهٔ مستقل است، حتی اگر externalMeetingId یکسان باشد. بنابراین دو جلسهٔ صبح و عصر یک روز در پاسخ API دو رکورد جدا با sessionId متفاوت هستند و هرگز با یکدیگر ادغام نمی‌شوند. فیلدهای sequenceInDay و sessionsInDay مشخص می‌کنند جلسه «چندم از چند جلسهٔ آن روز» است.

محدودیت شناخته‌شده

اگر اتاق کلاس میان دو بخش هرگز بسته نشود (مثلاً مدرس چند دقیقه خارج شود و اتاق باز بماند)، BigBlueButton آن را یک جلسه می‌بیند و API نیز یک جلسه گزارش می‌کند. برای جداسازی قطعی، externalMeetingId را برای هر جلسه یکتا انتخاب کنید (مثلاً class42-2026-09-20-am) و با پارامتر externalMeetingIdPrefix=class42- همهٔ جلسه‌ها را دریافت کنید.

۳ احراز هویت و امنیت

دریافت کلید

کلید توسط مدیر پنل موسسه در بخش «کلیدهای API» ساخته می‌شود. کلید کامل فقط یک‌بار در لحظهٔ ساخت نمایش داده می‌شود و پس از آن قابل بازیابی نیست؛ در صورت گم‌شدن باید کلید جدید صادر و کلید قبلی لغو شود.

ارسال کلید در درخواست

روشنمونهتوضیح
هدر اختصاصی پیشنهادیX-Api-Key: itl_xxxxxxxxساده‌ترین و رایج‌ترین روش.
هدر AuthorizationAuthorization: Bearer itl_xxxxxxxxبرای کلاینت‌هایی که فقط Bearer را پشتیبانی می‌کنند.
پارامتر query پذیرفته نمی‌شود?api_key=...آدرس‌ها در لاگ سرورها و پراکسی‌ها ذخیره می‌شوند؛ به همین دلیل کلید در آدرس رد می‌شود.

ویژگی‌های امنیتی کلید

  • مختص یک موسسه: کلید فقط به داده‌های همان موسسه دسترسی دارد. جلسه یا کلاس موسسهٔ دیگر برای آن اصلاً وجود ندارد و پاسخ «یافت نشد» (۴۰۴) می‌گیرد.
  • فقط‌خواندنی: هیچ endpoint نوشتاری وجود ندارد.
  • ذخیرهٔ ایمن: در پنل فقط هش برگشت‌ناپذیر کلید نگهداری می‌شود، نه خود کلید.
  • لغو فوری و انقضا: مدیر پنل می‌تواند کلید را در هر لحظه لغو کند یا هنگام ساخت، تاریخ انقضا تعیین کند.
  • پایش استفاده: آخرین زمان و آی‌پی استفاده از هر کلید در پنل ثبت می‌شود.
  • محدودیت نرخ: تعداد درخواست هر کلید در دقیقه محدود است (بخش ۹).

مجوزهای اضافی (اطلاعات شخصی)

آی‌پی/مرورگر شرکت‌کنندگان و متن پیام‌های چت اطلاعات شخصی محسوب می‌شوند و به‌صورت پیش‌فرض بازگردانده نمی‌شوند. دریافت آن‌ها مستلزم دو شرط هم‌زمان است: مدیر پنل هنگام ساخت کلید مجوز مربوطه را فعال کرده باشد و درخواست، صراحتاً پارامتر include را داشته باشد.

پارامترمجوز لازم روی کلیدخروجی اضافه
include=ipدریافت آی‌پی و مرورگرipAddress و userAgent در هر بازه، و ipAddresses در هر شرکت‌کننده
include=chatدریافت متن چتآرایهٔ chat در جلسه

در صورت نبودن مجوز، پاسخ ۴۰۳ با کد forbidden_include برمی‌گردد (پارامتر نادیده گرفته نمی‌شود تا مصرف‌کننده متوجه خطا شود).

مسئولیت نگهداری کلید

کلید را در مخزن کد، کد سمت مرورگر یا برنامهٔ موبایل قرار ندهید و آن را از طریق متغیر محیطی یا مخزن رمزهای سرور نگه دارید. فراخوانی API باید همواره از سرور شما انجام شود. در صورت افشای احتمالی کلید، بلافاصله از پنل آن را لغو کنید.

۴ شروع سریع

  1. کلید API را از مدیر پنل موسسهٔ خود دریافت کنید.
  2. با فراخوانی whoami از صحت کلید و مجوزهای آن اطمینان حاصل کنید.
  3. جلسه‌های کلاس مورد نظر را با externalMeetingId و تاریخ دریافت کنید.
cURL
# کلید را در متغیر محیطی نگه دارید، نه داخل اسکریپت
export ITLAND_API_KEY="itl_xxxxxxxxxxxxxxxx"

# ۱) بررسی اتصال
curl -s "https://panel.itland.org/api/v1/whoami" -H "X-Api-Key: $ITLAND_API_KEY"

# ۲) همهٔ جلسه‌های کلاس class42 در یک روز (هر جلسه جدا)
curl -s "https://panel.itland.org/api/v1/classes/class42/sessions?date=2026-09-20" \
  -H "X-Api-Key: $ITLAND_API_KEY"

ابزارهای آزمایش

۵ مرجع Endpointها

همهٔ مسیرها با پیشوند /api/v1 و روش GET هستند. پارامترهای مشترک (date، from، to، threshold، include، page) در هر مسیر یک‌سان عمل می‌کنند. این بخش مستقیماً از همان سند OpenAPI ساخته می‌شود که Swagger استفاده می‌کند، بنابراین همیشه با آن یکی است.

GET /api/v1/whoami بررسی کلید و مجوزها

برای اطمینان از صحت اتصال و اعتبار کلید استفاده می‌شود؛ نام موسسه، نام کلید، مجوزهای include و زمان سرور را بازمی‌گرداند.

توصیه می‌شود نخستین فراخوانی پس از دریافت کلید، همین مسیر باشد.

نمونهٔ درخواست

cURL
curl -s "https://panel.itland.org/api/v1/whoami" \
  -H "X-Api-Key: $ITLAND_API_KEY"

نمونهٔ پاسخ موفق

پاسخ موفق
200 OK — application/json
{
  "institute": "دانشگاه نمونه",
  "keyName": "سامانهٔ آموزش دانشگاه",
  "keyPrefix": "itl_AbC12x",
  "canViewIpAddresses": false,
  "canViewChat": false,
  "expiresAt": null,
  "serverTime": "2026-09-20T14:30:00\u002B00:00"
}

کدهای پاسخ

200 کلید معتبر است.
401 احراز هویت ناموفق: کلید ارسال نشده، ناشناخته، لغوشده یا منقضی است.
403 دسترسی مجاز نیست: کلید مجوز include درخواستی را ندارد، یا موسسه غیرفعال است.
429 تعداد درخواست‌ها از سقف مجاز (پیش‌فرض ۱۲۰ در دقیقه برای هر کلید) بیشتر شده است. پس از مدت هدر Retry-After دوباره تلاش کنید.
GET /api/v1/meetings فهرست جلسه‌ها

جلسه‌های موسسه را از تازه‌ترین به قدیمی‌ترین برمی‌گرداند.

  • هر بار تشکیل کلاس یک جلسهٔ مستقل است؛ کلاسی که چند بار در روز برگزار شده باشد چند ردیف جدا دارد.
  • پاسخ فقط خلاصهٔ جلسه‌هاست (بدون شرکت‌کنندگان). برای دریافت حاضرین از «جزئیات جلسه» یا «جلسه‌های یک کلاس» استفاده کنید.
  • فیلترها با هم ترکیب می‌شوند (AND). بازهٔ زمانی بر اساس زمان شروع جلسه اعمال می‌شود.

پارامترها

پارامتر محل نوع توضیح
externalMeetingId query string

فقط جلسه‌های این کلاس (مطابقت دقیق با meetingID سامانهٔ شما). مثال: class42

externalMeetingIdPrefix query string

جلسه‌هایی که شناسهٔ کلاسشان با این مقدار شروع می‌شود؛ برای زمانی که شناسهٔ هر جلسه یکتا است. مثال: class42-

courseCode query string

فقط جلسه‌های درسی که در پنل با این کد تعریف و تطبیق داده شده است. مثال: MATH101

status query string

live: جلسه‌های در حال برگزاری — ended: جلسه‌های پایان‌یافته. بدون این پارامتر هر دو برگردانده می‌شوند.

مقادیر مجاز: live، ended
date query string

یک روز مشخص به وقت تهران؛ تاریخ میلادی (2026-09-20) یا شمسی (1405-06-29). همزمان با from/to قابل ارسال نیست.

from query string

ابتدای بازه (بر اساس زمان شروع جلسه). تاریخ تنها (روز تهرانی) یا زمان کامل ISO-8601 همراه offset، مانند 2026-09-20T08:00:00+03:30 یا 2026-09-20T04:30:00Z. علامت + در آدرس باید به‌صورت %2B کدگذاری شود؛ زمان بدون offset پذیرفته نمی‌شود.

to query string

انتهای بازه. اگر تاریخ تنها باشد کل همان روز را شامل می‌شود؛ اگر زمان کامل باشد، همان لحظه جزو بازه نیست. فاصلهٔ from تا to حداکثر ۳۶۶ روز است.

page query integer

شمارهٔ صفحه (از ۱).

حداقل: 1 — پیش‌فرض: 1
pageSize query integer

تعداد ردیف در هر صفحه (۱ تا 100).

بازه: 1 تا 100 — پیش‌فرض: 20

نمونهٔ درخواست

cURL
curl -s "https://panel.itland.org/api/v1/meetings?externalMeetingId=class42&date=2026-09-20" \
  -H "X-Api-Key: $ITLAND_API_KEY"

نمونهٔ پاسخ موفق

دو جلسهٔ یک کلاس در یک روز
200 OK — application/json
{
  "items": [
    {
      "sessionId": "c3d81f52-7e4b-4a19-8f60-1b2a9d5e7c44",
      "internalMeetingId": "c3d81f527e4b4a198f601b2a9d5e7c44c3d81f52-1789909200000",
      "externalMeetingId": "class42",
      "name": "ریاضی ۱ - جلسهٔ عصر",
      "course": {
        "code": "MATH101",
        "name": "ریاضی ۱"
      },
      "origin": {
        "system": "Moodle",
        "version": "4.5.5",
        "serverName": "lms.example.edu",
        "courseId": "42",
        "courseName": "ریاضی ۱",
        "courseLabel": "math-101"
      },
      "status": "ended",
      "startedAt": "2026-09-20T13:00:00\u002B00:00",
      "endedAt": "2026-09-20T14:00:00\u002B00:00",
      "durationSeconds": 3600,
      "localDate": "2026-09-20",
      "participantCount": 3,
      "chatMessageCount": 0
    },
    {
      "sessionId": "5b1e0f3c-0f7a-4b53-9c1e-2d8a4d6f9a10",
      "internalMeetingId": "5b1e0f3c0f7a4b539c1e2d8a4d6f9a105b1e0f3c-1789880400000",
      "externalMeetingId": "class42",
      "name": "ریاضی ۱ - جلسهٔ صبح",
      "course": {
        "code": "MATH101",
        "name": "ریاضی ۱"
      },
      "origin": {
        "system": "Moodle",
        "version": "4.5.5",
        "serverName": "lms.example.edu",
        "courseId": "42",
        "courseName": "ریاضی ۱",
        "courseLabel": "math-101"
      },
      "status": "ended",
      "startedAt": "2026-09-20T05:00:00\u002B00:00",
      "endedAt": "2026-09-20T06:30:00\u002B00:00",
      "durationSeconds": 5400,
      "localDate": "2026-09-20",
      "participantCount": 3,
      "chatMessageCount": 3
    }
  ],
  "page": 1,
  "pageSize": 20,
  "totalCount": 2,
  "totalPages": 1
}

کدهای پاسخ

200 فهرست جلسه‌ها (صفحه‌بندی‌شده).
400 درخواست نامعتبر: پارامتر ناقص یا نادرست است. پیام فارسی علت را دقیق می‌گوید.
401 احراز هویت ناموفق: کلید ارسال نشده، ناشناخته، لغوشده یا منقضی است.
429 تعداد درخواست‌ها از سقف مجاز (پیش‌فرض ۱۲۰ در دقیقه برای هر کلید) بیشتر شده است. پس از مدت هدر Retry-After دوباره تلاش کنید.
GET /api/v1/meetings/{id} جزئیات یک جلسه

اطلاعات کامل یک جلسه شامل حاضرین، تمام بازه‌های ورود و خروج، مدت واقعی حضور و درصد حضور هر فرد.

  • شناسه می‌تواند sessionId (UUID) یا internalMeetingId بیگ‌بلو باشد.
  • اگر جلسه متعلق به موسسهٔ دیگری باشد، به‌جای ۴۰۳ پاسخ ۴۰۴ برمی‌گردد.
  • آی‌پی و متن چت فقط با include و در صورت داشتن مجوز کلید بازگردانده می‌شوند.

پارامترها

پارامتر محل نوع توضیح
id الزامی مسیر string

شناسهٔ جلسه: sessionId (UUID) یا internalMeetingId بیگ‌بلو.

threshold query integer

آستانهٔ درصد حضور (۰ تا ۱۰۰). belowThreshold برای کسانی true می‌شود که درصد حضورشان کمتر از این عدد باشد.

بازه: 0 تا 100 — پیش‌فرض: 70
include query array‹string›

فیلدهای اضافه، با ویرگول جدا شده (مثلاً ip,chat).

  • ip: آی‌پی و مرورگر هر بازهٔ حضور
  • chat: متن پیام‌های چت

هر مورد نیازمند مجوز همان کلید است، وگرنه پاسخ ۴۰۳ با کد forbidden_include برمی‌گردد.

مقادیر مجاز: ip، chat

نمونهٔ درخواست

cURL
curl -s "https://panel.itland.org/api/v1/meetings/5b1e0f3c-0f7a-4b53-9c1e-2d8a4d6f9a10?threshold=70" \
  -H "X-Api-Key: $ITLAND_API_KEY"

نمونهٔ پاسخ موفق

پاسخ پیش‌فرض (بدون آی‌پی و چت)
200 OK — application/json
{
  "sessionId": "5b1e0f3c-0f7a-4b53-9c1e-2d8a4d6f9a10",
  "internalMeetingId": "5b1e0f3c0f7a4b539c1e2d8a4d6f9a105b1e0f3c-1789880400000",
  "externalMeetingId": "class42",
  "name": "ریاضی ۱ - جلسهٔ صبح",
  "course": {
    "code": "MATH101",
    "name": "ریاضی ۱"
  },
  "origin": {
    "system": "Moodle",
    "version": "4.5.5",
    "serverName": "lms.example.edu",
    "courseId": "42",
    "courseName": "ریاضی ۱",
    "courseLabel": "math-101"
  },
  "status": "ended",
  "startedAt": "2026-09-20T05:00:00\u002B00:00",
  "endedAt": "2026-09-20T06:30:00\u002B00:00",
  "durationSeconds": 5400,
  "localDate": "2026-09-20",
  "localDateJalali": "1405/06/29",
  "sequenceInDay": 1,
  "sessionsInDay": 2,
  "thresholdPercent": 70,
  "baseline": {
    "externalUserId": "t-100",
    "name": "استاد احمدی",
    "presenceSeconds": 5400
  },
  "participantCount": 3,
  "belowThresholdCount": 1,
  "peakConcurrentParticipants": 3,
  "isShortSession": false,
  "deviceSummary": {
    "mobile": 1,
    "desktop": 2,
    "unknown": 0
  },
  "recording": {
    "enabled": true,
    "recordedSeconds": 5280,
    "isRecording": false
  },
  "screenshareCount": 1,
  "presentationChangeCount": 3,
  "maxUsers": 0,
  "plannedDurationMinutes": 0,
  "participants": [
    {
      "externalUserId": "t-100",
      "name": "استاد احمدی",
      "role": "moderator",
      "firstJoinedAt": "2026-09-20T05:00:00\u002B00:00",
      "lastLeftAt": "2026-09-20T06:30:00\u002B00:00",
      "isPresent": false,
      "totalPresenceSeconds": 5400,
      "attendancePercent": 100,
      "belowThreshold": false,
      "joinCount": 1,
      "chatMessageCount": 1,
      "lateSeconds": 0,
      "earlyLeaveSeconds": 0,
      "activity": {
        "audioSeconds": 5400,
        "audioDrops": 0,
        "unmuteCount": 1,
        "cameraSeconds": 0,
        "cameraStarts": 0,
        "handRaises": 0,
        "screenshares": 1,
        "wasPresenter": true
      },
      "intervals": [
        {
          "joinedAt": "2026-09-20T05:00:00\u002B00:00",
          "leftAt": "2026-09-20T06:30:00\u002B00:00",
          "durationSeconds": 5400
        }
      ]
    },
    {
      "externalUserId": "s-2002",
      "name": "علی محمدی",
      "role": "viewer",
      "firstJoinedAt": "2026-09-20T05:00:00\u002B00:00",
      "lastLeftAt": "2026-09-20T06:20:00\u002B00:00",
      "isPresent": false,
      "totalPresenceSeconds": 4800,
      "attendancePercent": 88.9,
      "belowThreshold": false,
      "joinCount": 1,
      "chatMessageCount": 1,
      "lateSeconds": 0,
      "earlyLeaveSeconds": 600,
      "activity": {
        "audioSeconds": 0,
        "audioDrops": 0,
        "unmuteCount": 0,
        "cameraSeconds": 0,
        "cameraStarts": 0,
        "handRaises": 0,
        "screenshares": 0,
        "wasPresenter": false
      },
      "intervals": [
        {
          "joinedAt": "2026-09-20T05:00:00\u002B00:00",
          "leftAt": "2026-09-20T06:20:00\u002B00:00",
          "durationSeconds": 4800
        }
      ]
    },
    {
      "externalUserId": "s-2001",
      "name": "سارا رضایی",
      "role": "viewer",
      "firstJoinedAt": "2026-09-20T05:02:00\u002B00:00",
      "lastLeftAt": "2026-09-20T05:40:00\u002B00:00",
      "isPresent": false,
      "totalPresenceSeconds": 2160,
      "attendancePercent": 40,
      "belowThreshold": true,
      "joinCount": 2,
      "chatMessageCount": 1,
      "lateSeconds": 120,
      "earlyLeaveSeconds": 3000,
      "activity": {
        "audioSeconds": 3360,
        "audioDrops": 1,
        "unmuteCount": 1,
        "cameraSeconds": 2100,
        "cameraStarts": 1,
        "handRaises": 1,
        "screenshares": 0,
        "wasPresenter": false
      },
      "intervals": [
        {
          "joinedAt": "2026-09-20T05:02:00\u002B00:00",
          "leftAt": "2026-09-20T05:20:00\u002B00:00",
          "durationSeconds": 1080
        },
        {
          "joinedAt": "2026-09-20T05:22:00\u002B00:00",
          "leftAt": "2026-09-20T05:40:00\u002B00:00",
          "durationSeconds": 1080
        }
      ]
    }
  ]
}
با include=ip,chat (نیازمند مجوز کلید)
200 OK — application/json
{
  "sessionId": "5b1e0f3c-0f7a-4b53-9c1e-2d8a4d6f9a10",
  "internalMeetingId": "5b1e0f3c0f7a4b539c1e2d8a4d6f9a105b1e0f3c-1789880400000",
  "externalMeetingId": "class42",
  "name": "ریاضی ۱ - جلسهٔ صبح",
  "course": {
    "code": "MATH101",
    "name": "ریاضی ۱"
  },
  "origin": {
    "system": "Moodle",
    "version": "4.5.5",
    "serverName": "lms.example.edu",
    "courseId": "42",
    "courseName": "ریاضی ۱",
    "courseLabel": "math-101"
  },
  "status": "ended",
  "startedAt": "2026-09-20T05:00:00\u002B00:00",
  "endedAt": "2026-09-20T06:30:00\u002B00:00",
  "durationSeconds": 5400,
  "localDate": "2026-09-20",
  "localDateJalali": "1405/06/29",
  "sequenceInDay": 1,
  "sessionsInDay": 2,
  "thresholdPercent": 70,
  "baseline": {
    "externalUserId": "t-100",
    "name": "استاد احمدی",
    "presenceSeconds": 5400
  },
  "participantCount": 3,
  "belowThresholdCount": 1,
  "peakConcurrentParticipants": 3,
  "isShortSession": false,
  "deviceSummary": {
    "mobile": 1,
    "desktop": 2,
    "unknown": 0
  },
  "recording": {
    "enabled": true,
    "recordedSeconds": 5280,
    "isRecording": false
  },
  "screenshareCount": 1,
  "presentationChangeCount": 3,
  "maxUsers": 0,
  "plannedDurationMinutes": 0,
  "participants": [
    {
      "externalUserId": "t-100",
      "name": "استاد احمدی",
      "role": "moderator",
      "firstJoinedAt": "2026-09-20T05:00:00\u002B00:00",
      "lastLeftAt": "2026-09-20T06:30:00\u002B00:00",
      "isPresent": false,
      "totalPresenceSeconds": 5400,
      "attendancePercent": 100,
      "belowThreshold": false,
      "joinCount": 1,
      "chatMessageCount": 1,
      "lateSeconds": 0,
      "earlyLeaveSeconds": 0,
      "activity": {
        "audioSeconds": 5400,
        "audioDrops": 0,
        "unmuteCount": 1,
        "cameraSeconds": 0,
        "cameraStarts": 0,
        "handRaises": 0,
        "screenshares": 1,
        "wasPresenter": true
      },
      "intervals": [
        {
          "joinedAt": "2026-09-20T05:00:00\u002B00:00",
          "leftAt": "2026-09-20T06:30:00\u002B00:00",
          "durationSeconds": 5400,
          "ipAddress": "185.141.104.10",
          "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36"
        }
      ],
      "ipAddresses": [
        "185.141.104.10"
      ],
      "device": {
        "browser": "Chrome 131",
        "os": "Windows",
        "kind": "desktop"
      }
    },
    {
      "externalUserId": "s-2002",
      "name": "علی محمدی",
      "role": "viewer",
      "firstJoinedAt": "2026-09-20T05:00:00\u002B00:00",
      "lastLeftAt": "2026-09-20T06:20:00\u002B00:00",
      "isPresent": false,
      "totalPresenceSeconds": 4800,
      "attendancePercent": 88.9,
      "belowThreshold": false,
      "joinCount": 1,
      "chatMessageCount": 1,
      "lateSeconds": 0,
      "earlyLeaveSeconds": 600,
      "activity": {
        "audioSeconds": 0,
        "audioDrops": 0,
        "unmuteCount": 0,
        "cameraSeconds": 0,
        "cameraStarts": 0,
        "handRaises": 0,
        "screenshares": 0,
        "wasPresenter": false
      },
      "intervals": [
        {
          "joinedAt": "2026-09-20T05:00:00\u002B00:00",
          "leftAt": "2026-09-20T06:20:00\u002B00:00",
          "durationSeconds": 4800,
          "ipAddress": "37.98.4.4",
          "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:130.0) Gecko/20100101 Firefox/130.0"
        }
      ],
      "ipAddresses": [
        "37.98.4.4"
      ],
      "device": {
        "browser": "Firefox 130",
        "os": "Windows",
        "kind": "desktop"
      }
    },
    {
      "externalUserId": "s-2001",
      "name": "سارا رضایی",
      "role": "viewer",
      "firstJoinedAt": "2026-09-20T05:02:00\u002B00:00",
      "lastLeftAt": "2026-09-20T05:40:00\u002B00:00",
      "isPresent": false,
      "totalPresenceSeconds": 2160,
      "attendancePercent": 40,
      "belowThreshold": true,
      "joinCount": 2,
      "chatMessageCount": 1,
      "lateSeconds": 120,
      "earlyLeaveSeconds": 3000,
      "activity": {
        "audioSeconds": 3360,
        "audioDrops": 1,
        "unmuteCount": 1,
        "cameraSeconds": 2100,
        "cameraStarts": 1,
        "handRaises": 1,
        "screenshares": 0,
        "wasPresenter": false
      },
      "intervals": [
        {
          "joinedAt": "2026-09-20T05:02:00\u002B00:00",
          "leftAt": "2026-09-20T05:20:00\u002B00:00",
          "durationSeconds": 1080,
          "ipAddress": "5.22.10.7",
          "userAgent": "Mozilla/5.0 (Linux; Android 14; SM-S911B) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Mobile Safari/537.36"
        },
        {
          "joinedAt": "2026-09-20T05:22:00\u002B00:00",
          "leftAt": "2026-09-20T05:40:00\u002B00:00",
          "durationSeconds": 1080,
          "ipAddress": "5.22.10.8",
          "userAgent": "Mozilla/5.0 (Linux; Android 14; SM-S911B) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Mobile Safari/537.36"
        }
      ],
      "ipAddresses": [
        "5.22.10.7",
        "5.22.10.8"
      ],
      "device": {
        "browser": "Chrome 131",
        "os": "Android",
        "kind": "mobile"
      }
    }
  ],
  "chat": [
    {
      "senderName": "سارا رضایی",
      "externalUserId": "s-2001",
      "message": "سلام استاد، صدا میاد؟",
      "chatId": "MAIN-PUBLIC-GROUP-CHAT",
      "isPrivate": false,
      "sentAt": "2026-09-20T05:03:00\u002B00:00"
    },
    {
      "senderName": "استاد احمدی",
      "externalUserId": "t-100",
      "message": "بله، شروع می‌کنیم.",
      "chatId": "MAIN-PUBLIC-GROUP-CHAT",
      "isPrivate": false,
      "sentAt": "2026-09-20T05:04:00\u002B00:00"
    },
    {
      "senderName": "علی محمدی",
      "externalUserId": "s-2002",
      "message": "یک سؤال خصوصی دارم.",
      "chatId": "b7e1c0-private-chat",
      "isPrivate": true,
      "sentAt": "2026-09-20T05:30:00\u002B00:00"
    }
  ]
}

کدهای پاسخ

200 جزئیات جلسه.
400 درخواست نامعتبر: پارامتر ناقص یا نادرست است. پیام فارسی علت را دقیق می‌گوید.
401 احراز هویت ناموفق: کلید ارسال نشده، ناشناخته، لغوشده یا منقضی است.
403 دسترسی مجاز نیست: کلید مجوز include درخواستی را ندارد، یا موسسه غیرفعال است.
404 یافت نشد: جلسه یا کلاس برای موسسهٔ صاحب کلید وجود ندارد (داده‌های موسسه‌های دیگر هرگز آشکار نمی‌شوند). پیام، نام موسسهٔ کلید و شناسهٔ دریافت‌شده را نشان می‌دهد تا علت (شناسهٔ اشتباه یا کلیدِ موسسهٔ دیگر) را بتوانید پیدا کنید.
429 تعداد درخواست‌ها از سقف مجاز (پیش‌فرض ۱۲۰ در دقیقه برای هر کلید) بیشتر شده است. پس از مدت هدر Retry-After دوباره تلاش کنید.
GET /api/v1/classes/{externalMeetingId}/sessions جلسه‌های یک کلاس

همهٔ جلسه‌های یک کلاس (بر اساس externalMeetingId) را با جزئیات کامل و به ترتیب زمان برمی‌گرداند.

  • کلاسی که در یک روز دو بار تشکیل شده باشد، دو جلسهٔ جدا با شناسه‌های متفاوت است؛ فیلدهای sequenceInDay و sessionsInDay شمارهٔ جلسه در آن روز را مشخص می‌کنند.
  • ارسال date یا from الزامی است.
  • اگر هیچ جلسه‌ای در بازه نباشد، پاسخ ۲۰۰ با فهرست خالی است؛ ۴۰۴ فقط وقتی برمی‌گردد که این شناسهٔ کلاس اصلاً برای موسسه ثبت نشده باشد.

پارامترها

پارامتر محل نوع توضیح
externalMeetingId الزامی مسیر string

شناسهٔ کلاس در سامانهٔ شما (همان meetingID که هنگام ساخت کلاس به BigBlueButton داده شده است).

date query string

یک روز مشخص به وقت تهران؛ تاریخ میلادی (2026-09-20) یا شمسی (1405-06-29). همزمان با from/to قابل ارسال نیست.

from query string

ابتدای بازه (بر اساس زمان شروع جلسه). تاریخ تنها (روز تهرانی) یا زمان کامل ISO-8601 همراه offset، مانند 2026-09-20T08:00:00+03:30 یا 2026-09-20T04:30:00Z. علامت + در آدرس باید به‌صورت %2B کدگذاری شود؛ زمان بدون offset پذیرفته نمی‌شود.

to query string

انتهای بازه. اگر تاریخ تنها باشد کل همان روز را شامل می‌شود؛ اگر زمان کامل باشد، همان لحظه جزو بازه نیست. فاصلهٔ from تا to حداکثر ۳۶۶ روز است.

threshold query integer

آستانهٔ درصد حضور (۰ تا ۱۰۰). belowThreshold برای کسانی true می‌شود که درصد حضورشان کمتر از این عدد باشد.

بازه: 0 تا 100 — پیش‌فرض: 70
include query array‹string›

فیلدهای اضافه، با ویرگول جدا شده (مثلاً ip,chat).

  • ip: آی‌پی و مرورگر هر بازهٔ حضور
  • chat: متن پیام‌های چت

هر مورد نیازمند مجوز همان کلید است، وگرنه پاسخ ۴۰۳ با کد forbidden_include برمی‌گردد.

مقادیر مجاز: ip، chat
page query integer

شمارهٔ صفحه (از ۱).

حداقل: 1 — پیش‌فرض: 1
pageSize query integer

تعداد ردیف در هر صفحه (۱ تا 50).

بازه: 1 تا 50 — پیش‌فرض: 20

نمونهٔ درخواست

cURL
curl -s "https://panel.itland.org/api/v1/classes/class42/sessions?date=2026-09-20" \
  -H "X-Api-Key: $ITLAND_API_KEY"

نمونهٔ پاسخ موفق

دو جلسهٔ یک روز
200 OK — application/json
{
  "items": [
    {
      "sessionId": "5b1e0f3c-0f7a-4b53-9c1e-2d8a4d6f9a10",
      "internalMeetingId": "5b1e0f3c0f7a4b539c1e2d8a4d6f9a105b1e0f3c-1789880400000",
      "externalMeetingId": "class42",
      "name": "ریاضی ۱ - جلسهٔ صبح",
      "course": {
        "code": "MATH101",
        "name": "ریاضی ۱"
      },
      "origin": {
        "system": "Moodle",
        "version": "4.5.5",
        "serverName": "lms.example.edu",
        "courseId": "42",
        "courseName": "ریاضی ۱",
        "courseLabel": "math-101"
      },
      "status": "ended",
      "startedAt": "2026-09-20T05:00:00\u002B00:00",
      "endedAt": "2026-09-20T06:30:00\u002B00:00",
      "durationSeconds": 5400,
      "localDate": "2026-09-20",
      "localDateJalali": "1405/06/29",
      "sequenceInDay": 1,
      "sessionsInDay": 2,
      "thresholdPercent": 70,
      "baseline": {
        "externalUserId": "t-100",
        "name": "استاد احمدی",
        "presenceSeconds": 5400
      },
      "participantCount": 3,
      "belowThresholdCount": 1,
      "peakConcurrentParticipants": 3,
      "isShortSession": false,
      "deviceSummary": {
        "mobile": 1,
        "desktop": 2,
        "unknown": 0
      },
      "recording": {
        "enabled": true,
        "recordedSeconds": 5280,
        "isRecording": false
      },
      "screenshareCount": 1,
      "presentationChangeCount": 3,
      "maxUsers": 0,
      "plannedDurationMinutes": 0,
      "participants": [
        {
          "externalUserId": "t-100",
          "name": "استاد احمدی",
          "role": "moderator",
          "firstJoinedAt": "2026-09-20T05:00:00\u002B00:00",
          "lastLeftAt": "2026-09-20T06:30:00\u002B00:00",
          "isPresent": false,
          "totalPresenceSeconds": 5400,
          "attendancePercent": 100,
          "belowThreshold": false,
          "joinCount": 1,
          "chatMessageCount": 1,
          "lateSeconds": 0,
          "earlyLeaveSeconds": 0,
          "activity": {
            "audioSeconds": 5400,
            "audioDrops": 0,
            "unmuteCount": 1,
            "cameraSeconds": 0,
            "cameraStarts": 0,
            "handRaises": 0,
            "screenshares": 1,
            "wasPresenter": true
          },
          "intervals": [
            {
              "joinedAt": "2026-09-20T05:00:00\u002B00:00",
              "leftAt": "2026-09-20T06:30:00\u002B00:00",
              "durationSeconds": 5400
            }
          ]
        },
        {
          "externalUserId": "s-2002",
          "name": "علی محمدی",
          "role": "viewer",
          "firstJoinedAt": "2026-09-20T05:00:00\u002B00:00",
          "lastLeftAt": "2026-09-20T06:20:00\u002B00:00",
          "isPresent": false,
          "totalPresenceSeconds": 4800,
          "attendancePercent": 88.9,
          "belowThreshold": false,
          "joinCount": 1,
          "chatMessageCount": 1,
          "lateSeconds": 0,
          "earlyLeaveSeconds": 600,
          "activity": {
            "audioSeconds": 0,
            "audioDrops": 0,
            "unmuteCount": 0,
            "cameraSeconds": 0,
            "cameraStarts": 0,
            "handRaises": 0,
            "screenshares": 0,
            "wasPresenter": false
          },
          "intervals": [
            {
              "joinedAt": "2026-09-20T05:00:00\u002B00:00",
              "leftAt": "2026-09-20T06:20:00\u002B00:00",
              "durationSeconds": 4800
            }
          ]
        },
        {
          "externalUserId": "s-2001",
          "name": "سارا رضایی",
          "role": "viewer",
          "firstJoinedAt": "2026-09-20T05:02:00\u002B00:00",
          "lastLeftAt": "2026-09-20T05:40:00\u002B00:00",
          "isPresent": false,
          "totalPresenceSeconds": 2160,
          "attendancePercent": 40,
          "belowThreshold": true,
          "joinCount": 2,
          "chatMessageCount": 1,
          "lateSeconds": 120,
          "earlyLeaveSeconds": 3000,
          "activity": {
            "audioSeconds": 3360,
            "audioDrops": 1,
            "unmuteCount": 1,
            "cameraSeconds": 2100,
            "cameraStarts": 1,
            "handRaises": 1,
            "screenshares": 0,
            "wasPresenter": false
          },
          "intervals": [
            {
              "joinedAt": "2026-09-20T05:02:00\u002B00:00",
              "leftAt": "2026-09-20T05:20:00\u002B00:00",
              "durationSeconds": 1080
            },
            {
              "joinedAt": "2026-09-20T05:22:00\u002B00:00",
              "leftAt": "2026-09-20T05:40:00\u002B00:00",
              "durationSeconds": 1080
            }
          ]
        }
      ]
    },
    {
      "sessionId": "c3d81f52-7e4b-4a19-8f60-1b2a9d5e7c44",
      "internalMeetingId": "c3d81f527e4b4a198f601b2a9d5e7c44c3d81f52-1789909200000",
      "externalMeetingId": "class42",
      "name": "ریاضی ۱ - جلسهٔ عصر",
      "course": {
        "code": "MATH101",
        "name": "ریاضی ۱"
      },
      "origin": {
        "system": "Moodle",
        "version": "4.5.5",
        "serverName": "lms.example.edu",
        "courseId": "42",
        "courseName": "ریاضی ۱",
        "courseLabel": "math-101"
      },
      "status": "ended",
      "startedAt": "2026-09-20T13:00:00\u002B00:00",
      "endedAt": "2026-09-20T14:00:00\u002B00:00",
      "durationSeconds": 3600,
      "localDate": "2026-09-20",
      "localDateJalali": "1405/06/29",
      "sequenceInDay": 2,
      "sessionsInDay": 2,
      "thresholdPercent": 70,
      "baseline": {
        "externalUserId": "t-100",
        "name": "استاد احمدی",
        "presenceSeconds": 3600
      },
      "participantCount": 3,
      "belowThresholdCount": 1,
      "peakConcurrentParticipants": 3,
      "isShortSession": false,
      "deviceSummary": {
        "mobile": 1,
        "desktop": 2,
        "unknown": 0
      },
      "recording": {
        "enabled": true,
        "recordedSeconds": 3480,
        "isRecording": false
      },
      "screenshareCount": 1,
      "presentationChangeCount": 3,
      "maxUsers": 0,
      "plannedDurationMinutes": 0,
      "participants": [
        {
          "externalUserId": "t-100",
          "name": "استاد احمدی",
          "role": "moderator",
          "firstJoinedAt": "2026-09-20T13:00:00\u002B00:00",
          "lastLeftAt": "2026-09-20T14:00:00\u002B00:00",
          "isPresent": false,
          "totalPresenceSeconds": 3600,
          "attendancePercent": 100,
          "belowThreshold": false,
          "joinCount": 1,
          "chatMessageCount": 0,
          "lateSeconds": 0,
          "earlyLeaveSeconds": 0,
          "activity": {
            "audioSeconds": 0,
            "audioDrops": 0,
            "unmuteCount": 0,
            "cameraSeconds": 0,
            "cameraStarts": 0,
            "handRaises": 0,
            "screenshares": 0,
            "wasPresenter": false
          },
          "intervals": [
            {
              "joinedAt": "2026-09-20T13:00:00\u002B00:00",
              "leftAt": "2026-09-20T14:00:00\u002B00:00",
              "durationSeconds": 3600
            }
          ]
        },
        {
          "externalUserId": "s-2001",
          "name": "سارا رضایی",
          "role": "viewer",
          "firstJoinedAt": "2026-09-20T13:00:00\u002B00:00",
          "lastLeftAt": "2026-09-20T13:30:00\u002B00:00",
          "isPresent": false,
          "totalPresenceSeconds": 1800,
          "attendancePercent": 50,
          "belowThreshold": true,
          "joinCount": 1,
          "chatMessageCount": 0,
          "lateSeconds": 0,
          "earlyLeaveSeconds": 1800,
          "activity": {
            "audioSeconds": 0,
            "audioDrops": 0,
            "unmuteCount": 0,
            "cameraSeconds": 0,
            "cameraStarts": 0,
            "handRaises": 0,
            "screenshares": 0,
            "wasPresenter": false
          },
          "intervals": [
            {
              "joinedAt": "2026-09-20T13:00:00\u002B00:00",
              "leftAt": "2026-09-20T13:30:00\u002B00:00",
              "durationSeconds": 1800
            }
          ]
        },
        {
          "externalUserId": "s-2003",
          "name": "نیما کاظمی",
          "role": "viewer",
          "firstJoinedAt": "2026-09-20T13:05:00\u002B00:00",
          "lastLeftAt": "2026-09-20T13:58:00\u002B00:00",
          "isPresent": false,
          "totalPresenceSeconds": 3180,
          "attendancePercent": 88.3,
          "belowThreshold": false,
          "joinCount": 1,
          "chatMessageCount": 0,
          "lateSeconds": 300,
          "earlyLeaveSeconds": 120,
          "activity": {
            "audioSeconds": 0,
            "audioDrops": 0,
            "unmuteCount": 0,
            "cameraSeconds": 0,
            "cameraStarts": 0,
            "handRaises": 0,
            "screenshares": 0,
            "wasPresenter": false
          },
          "intervals": [
            {
              "joinedAt": "2026-09-20T13:05:00\u002B00:00",
              "leftAt": "2026-09-20T13:58:00\u002B00:00",
              "durationSeconds": 3180
            }
          ]
        }
      ]
    }
  ],
  "page": 1,
  "pageSize": 20,
  "totalCount": 2,
  "totalPages": 1
}

کدهای پاسخ

200 جلسه‌های کلاس با جزئیات.
400 درخواست نامعتبر: پارامتر ناقص یا نادرست است. پیام فارسی علت را دقیق می‌گوید.
401 احراز هویت ناموفق: کلید ارسال نشده، ناشناخته، لغوشده یا منقضی است.
403 دسترسی مجاز نیست: کلید مجوز include درخواستی را ندارد، یا موسسه غیرفعال است.
404 یافت نشد: جلسه یا کلاس برای موسسهٔ صاحب کلید وجود ندارد (داده‌های موسسه‌های دیگر هرگز آشکار نمی‌شوند). پیام، نام موسسهٔ کلید و شناسهٔ دریافت‌شده را نشان می‌دهد تا علت (شناسهٔ اشتباه یا کلیدِ موسسهٔ دیگر) را بتوانید پیدا کنید.
429 تعداد درخواست‌ها از سقف مجاز (پیش‌فرض ۱۲۰ در دقیقه برای هر کلید) بیشتر شده است. پس از مدت هدر Retry-After دوباره تلاش کنید.
GET /api/v1/classes/{externalMeetingId}/attendance جمع‌بندی حضور کلاس

جمع‌بندی حضور هر فرد در همهٔ جلسه‌های یک کلاس در بازهٔ زمانی: تعداد جلسه‌های حاضر و غایب، مجموع مدت حضور و میانگین درصد حضور.

  • برای گزارش پایان ترم، from و to را روی کل ترم تنظیم کنید. سقف: ۵۰۰ جلسه در هر درخواست.
  • «غایب» یعنی کاربر در آن جلسه اصلاً وارد نشده است؛ میانگین روی همهٔ جلسه‌های بازه محاسبه می‌شود (غیبت = ۰٪).
  • فقط افرادی فهرست می‌شوند که دست‌کم یک‌بار وارد جلسه شده‌اند. فهرست ثبت‌نام‌شدگان در اختیار پنل نیست و باید سامانهٔ شما آن را با این خروجی تطبیق دهد.
  • ارسال date یا from الزامی است.

پارامترها

پارامتر محل نوع توضیح
externalMeetingId الزامی مسیر string

شناسهٔ کلاس در سامانهٔ شما (همان meetingID که هنگام ساخت کلاس به BigBlueButton داده شده است).

date query string

یک روز مشخص به وقت تهران؛ تاریخ میلادی (2026-09-20) یا شمسی (1405-06-29). همزمان با from/to قابل ارسال نیست.

from query string

ابتدای بازه (بر اساس زمان شروع جلسه). تاریخ تنها (روز تهرانی) یا زمان کامل ISO-8601 همراه offset، مانند 2026-09-20T08:00:00+03:30 یا 2026-09-20T04:30:00Z. علامت + در آدرس باید به‌صورت %2B کدگذاری شود؛ زمان بدون offset پذیرفته نمی‌شود.

to query string

انتهای بازه. اگر تاریخ تنها باشد کل همان روز را شامل می‌شود؛ اگر زمان کامل باشد، همان لحظه جزو بازه نیست. فاصلهٔ from تا to حداکثر ۳۶۶ روز است.

threshold query integer

آستانهٔ درصد حضور (۰ تا ۱۰۰). belowThreshold برای کسانی true می‌شود که درصد حضورشان کمتر از این عدد باشد.

بازه: 0 تا 100 — پیش‌فرض: 70

نمونهٔ درخواست

cURL
curl -s "https://panel.itland.org/api/v1/classes/class42/attendance?from=2026-09-01&to=2026-12-30&threshold=70" \
  -H "X-Api-Key: $ITLAND_API_KEY"

نمونهٔ پاسخ موفق

دو جلسه در یک روز
200 OK — application/json
{
  "externalMeetingId": "class42",
  "from": "2026-09-19T20:30:00\u002B00:00",
  "to": "2026-09-20T20:30:00\u002B00:00",
  "thresholdPercent": 70,
  "sessionCount": 2,
  "sessions": [
    {
      "sessionId": "5b1e0f3c-0f7a-4b53-9c1e-2d8a4d6f9a10",
      "localDate": "2026-09-20",
      "sequenceInDay": 1,
      "startedAt": "2026-09-20T05:00:00\u002B00:00",
      "endedAt": "2026-09-20T06:30:00\u002B00:00",
      "durationSeconds": 5400,
      "baselineSeconds": 5400,
      "participantCount": 3
    },
    {
      "sessionId": "c3d81f52-7e4b-4a19-8f60-1b2a9d5e7c44",
      "localDate": "2026-09-20",
      "sequenceInDay": 2,
      "startedAt": "2026-09-20T13:00:00\u002B00:00",
      "endedAt": "2026-09-20T14:00:00\u002B00:00",
      "durationSeconds": 3600,
      "baselineSeconds": 3600,
      "participantCount": 3
    }
  ],
  "participants": [
    {
      "externalUserId": "t-100",
      "name": "استاد احمدی",
      "role": "moderator",
      "sessionsAttended": 2,
      "sessionsAbsent": 0,
      "sessionsBelowThreshold": 0,
      "totalPresenceSeconds": 9000,
      "averagePercent": 100,
      "sessions": [
        {
          "sessionId": "5b1e0f3c-0f7a-4b53-9c1e-2d8a4d6f9a10",
          "presenceSeconds": 5400,
          "attendancePercent": 100,
          "belowThreshold": false,
          "joinCount": 1
        },
        {
          "sessionId": "c3d81f52-7e4b-4a19-8f60-1b2a9d5e7c44",
          "presenceSeconds": 3600,
          "attendancePercent": 100,
          "belowThreshold": false,
          "joinCount": 1
        }
      ]
    },
    {
      "externalUserId": "s-2001",
      "name": "سارا رضایی",
      "role": "viewer",
      "sessionsAttended": 2,
      "sessionsAbsent": 0,
      "sessionsBelowThreshold": 2,
      "totalPresenceSeconds": 3960,
      "averagePercent": 45,
      "sessions": [
        {
          "sessionId": "5b1e0f3c-0f7a-4b53-9c1e-2d8a4d6f9a10",
          "presenceSeconds": 2160,
          "attendancePercent": 40,
          "belowThreshold": true,
          "joinCount": 2
        },
        {
          "sessionId": "c3d81f52-7e4b-4a19-8f60-1b2a9d5e7c44",
          "presenceSeconds": 1800,
          "attendancePercent": 50,
          "belowThreshold": true,
          "joinCount": 1
        }
      ]
    },
    {
      "externalUserId": "s-2002",
      "name": "علی محمدی",
      "role": "viewer",
      "sessionsAttended": 1,
      "sessionsAbsent": 1,
      "sessionsBelowThreshold": 0,
      "totalPresenceSeconds": 4800,
      "averagePercent": 44.4,
      "sessions": [
        {
          "sessionId": "5b1e0f3c-0f7a-4b53-9c1e-2d8a4d6f9a10",
          "presenceSeconds": 4800,
          "attendancePercent": 88.9,
          "belowThreshold": false,
          "joinCount": 1
        }
      ]
    },
    {
      "externalUserId": "s-2003",
      "name": "نیما کاظمی",
      "role": "viewer",
      "sessionsAttended": 1,
      "sessionsAbsent": 1,
      "sessionsBelowThreshold": 0,
      "totalPresenceSeconds": 3180,
      "averagePercent": 44.2,
      "sessions": [
        {
          "sessionId": "c3d81f52-7e4b-4a19-8f60-1b2a9d5e7c44",
          "presenceSeconds": 3180,
          "attendancePercent": 88.3,
          "belowThreshold": false,
          "joinCount": 1
        }
      ]
    }
  ]
}

کدهای پاسخ

200 جمع‌بندی حضور افراد.
400 درخواست نامعتبر: پارامتر ناقص یا نادرست است. پیام فارسی علت را دقیق می‌گوید.
401 احراز هویت ناموفق: کلید ارسال نشده، ناشناخته، لغوشده یا منقضی است.
404 یافت نشد: جلسه یا کلاس برای موسسهٔ صاحب کلید وجود ندارد (داده‌های موسسه‌های دیگر هرگز آشکار نمی‌شوند). پیام، نام موسسهٔ کلید و شناسهٔ دریافت‌شده را نشان می‌دهد تا علت (شناسهٔ اشتباه یا کلیدِ موسسهٔ دیگر) را بتوانید پیدا کنید.
429 تعداد درخواست‌ها از سقف مجاز (پیش‌فرض ۱۲۰ در دقیقه برای هر کلید) بیشتر شده است. پس از مدت هدر Retry-After دوباره تلاش کنید.

۶ مدل داده‌ها

جدول‌های زیر همهٔ فیلدهای پاسخ را شرح می‌دهند. فیلدی که با ؟ پس از نوع مشخص شده ممکن است مقدار null داشته باشد. فیلدهای دارای برچسب «اختیاری» در صورت نبودن مقدار، اصلاً در پاسخ نمی‌آیند. مصرف‌کننده باید فیلدهای ناشناخته را نادیده بگیرد تا افزودن فیلد جدید در نسخهٔ ۱ سامانهٔ او را نشکند.

پوشش فهرست‌های صفحه‌بندی‌شده (ApiPage)

پوشش همهٔ فهرست‌های صفحه‌بندی‌شده.

فیلدنوعتوضیح
items array‹ApiSessionSummary› نتایج همین صفحه.
page integer شمارهٔ صفحهٔ جاری (از ۱).
pageSize integer حداکثر تعداد ردیف در هر صفحه.
totalCount integer تعداد کل نتایج منطبق با فیلترها، در تمام صفحات.
totalPages integer تعداد کل صفحات.

خلاصهٔ جلسه (ApiSessionSummary)

خلاصهٔ یک جلسه (یک بار تشکیل کلاس)، بدون فهرست شرکت‌کنندگان.

فیلدنوعتوضیح
sessionId uuid شناسهٔ یکتای جلسه در پنل (UUID)؛ برای دریافت جزئیات جلسه استفاده می‌شود.
internalMeetingId string شناسهٔ داخلی جلسه در BigBlueButton؛ به‌ازای هر بار تشکیل کلاس یکتاست.
externalMeetingId string شناسه‌ای که سامانهٔ شما هنگام ساخت کلاس به BigBlueButton داده است (meetingID). ممکن است بین چند جلسه مشترک باشد.
name string عنوان جلسه.
course ApiCourseRef؟ درس متناظر، در صورتی که در پنل تطبیق داده شده باشد؛ در غیر این صورت null.
origin ApiOrigin؟ سامانهٔ مبدأ کلاس؛ اگر سامانه metadata نفرستاده یا پنل ساخته‌شدن کلاس را ندیده باشد null.
status string وضعیت جلسه: live (در حال برگزاری) یا ended (پایان‌یافته). live | ended
startedAt datetime زمان شروع جلسه (UTC).
endedAt datetime؟ زمان پایان جلسه (UTC)؛ برای جلسهٔ در حال برگزاری null است.
durationSeconds integer مدت جلسه به ثانیه؛ برای جلسهٔ در حال برگزاری تا لحظهٔ درخواست حساب می‌شود.
localDate string تاریخ میلادی روز برگزاری به وقت تهران (yyyy-MM-dd).
participantCount integer تعداد شرکت‌کنندگان ثبت‌شده در جلسه.
chatMessageCount integer تعداد پیام‌های چت ثبت‌شده در جلسه.

درس متناظر (ApiCourseRef)

درس تعریف‌شده در پنل که این جلسه به آن تطبیق داده شده است.

فیلدنوعتوضیح
code string؟ کد درس تعریف‌شده در پنل؛ ممکن است خالی (null) باشد.
name string نام درس یا دوره.

جزئیات جلسه (ApiSessionDetail)

جزئیات کامل یک جلسه شامل حاضرین، بازه‌های ورود و خروج و درصد حضور.

فیلدنوعتوضیح
sessionId uuid شناسهٔ یکتای جلسه در پنل (UUID).
internalMeetingId string شناسهٔ داخلی جلسه در BigBlueButton؛ به‌ازای هر بار تشکیل کلاس یکتاست.
externalMeetingId string شناسه‌ای که سامانهٔ شما هنگام ساخت کلاس داده است (meetingID). ممکن است بین چند جلسه مشترک باشد.
name string عنوان جلسه.
course ApiCourseRef؟ درس متناظر در پنل؛ در صورت نبودن null.
origin ApiOrigin؟ سامانهٔ مبدأ کلاس؛ اگر سامانه metadata نفرستاده یا پنل ساخته‌شدن کلاس را ندیده باشد null.
status string وضعیت جلسه: live یا ended. live | ended
startedAt datetime زمان شروع جلسه (UTC).
endedAt datetime؟ زمان پایان جلسه (UTC)؛ برای جلسهٔ در حال برگزاری null.
durationSeconds integer مدت جلسه به ثانیه.
localDate string تاریخ میلادی روز برگزاری به وقت تهران (yyyy-MM-dd).
localDateJalali string تاریخ شمسی روز برگزاری به وقت تهران (yyyy/MM/dd).
sequenceInDay integer شمارهٔ این جلسه میان جلسه‌های همان کلاس در همان روز تهرانی (از ۱)، به ترتیب زمان شروع.
sessionsInDay integer تعداد کل جلسه‌های همان کلاس در همان روز تهرانی.
thresholdPercent integer آستانهٔ درصد حضور که در این پاسخ اعمال شده است.
baseline ApiBaseline؟ مبنای محاسبهٔ درصد؛ اگر جلسه شرکت‌کننده‌ای نداشته باشد null.
participantCount integer تعداد شرکت‌کنندگان جلسه.
belowThresholdCount integer تعداد شرکت‌کنندگانی که درصد حضورشان کمتر از آستانه است.
peakConcurrentParticipants integer بیشینهٔ تعداد افراد هم‌زمان حاضر در جلسه؛ می‌تواند از participantCount کمتر باشد.
isShortSession boolean true اگر جلسه پایان یافته و کمتر از ۵ دقیقه طول کشیده باشد (معمولاً جلسهٔ آزمایشی یا اشتباهی).
deviceSummary ApiDeviceSummary تعداد شرکت‌کنندگان به تفکیک نوع دستگاه اصلی؛ بدون نیاز به include=ip.
recording ApiRecordingInfo وضعیت ضبط کلاس.
screenshareCount integer تعداد دفعات اشتراک صفحه در کلاس.
presentationChangeCount integer تعداد دفعات تغییر ارائه/اسلاید فعال کلاس.
maxUsers integer؟ سقف تعداد کاربران که سامانهٔ مبدأ تعیین کرده؛ ۰ یعنی نامحدود و null یعنی اطلاعاتی نیست.
plannedDurationMinutes integer؟ سقف مدت کلاس به دقیقه که سامانهٔ مبدأ تعیین کرده؛ ۰ یعنی نامحدود و null یعنی اطلاعاتی نیست.
participants array‹ApiParticipant› شرکت‌کنندگان به ترتیب زمان نخستین ورود.
chat اختیاری array‹ApiChatMessage›؟ پیام‌های چت به ترتیب زمان. فقط با include=chat و مجوز کلید.
duplicateNameGroups اختیاری array‹ApiDuplicateParticipantGroup›؟ هشدار احتمال شکستگی حضور یک نفر بین چند ردیف participants (وقتی سامانهٔ شما هنگام join یک userID پایدار نمی‌دهد). فقط وقتی در همین جلسه دو یا چند شرکت‌کننده نام یکسان دارند پر می‌شود؛ در غیر این صورت null. مجموع واقعی حضور این افراد را از combinedPresenceSeconds بخوانید.

مبنای درصد حضور (ApiBaseline)

مبنای محاسبهٔ درصد حضور: پرحضورترین فرد همان جلسه (معمولاً مدرس).

فیلدنوعتوضیح
externalUserId string شناسهٔ کاربر پرحضورترین فرد جلسه.
name string نام پرحضورترین فرد جلسه.
presenceSeconds integer مدت حضور او به ثانیه (معادل ۱۰۰٪).

شرکت‌کننده (ApiParticipant)

حضور یک کاربر در یک جلسه.

فیلدنوعتوضیح
externalUserId string شناسهٔ ثابت کاربر در سامانهٔ شما (userID که هنگام ورود به کلاس ارسال شده است).
name string نام نمایشی کاربر در جلسه.
role string نقش کاربر: moderator (مدرس یا مدیر) یا viewer (شرکت‌کننده). moderator | viewer
firstJoinedAt datetime زمان نخستین ورود (UTC).
lastLeftAt datetime؟ زمان آخرین خروج (UTC)؛ اگر کاربر هم‌اکنون در جلسه است null.
isPresent boolean آیا کاربر در لحظهٔ درخواست در جلسه حضور دارد.
totalPresenceSeconds integer مدت واقعی حضور به ثانیه؛ اجتماع همهٔ بازه‌ها. بازه‌های هم‌پوشان یک‌بار شمرده می‌شوند.
attendancePercent number درصد حضور نسبت به پرحضورترین فرد همان جلسه (معمولاً مدرس = ۱۰۰)، با یک رقم اعشار.
belowThreshold boolean true اگر attendancePercent کمتر از threshold باشد.
joinCount integer تعداد دفعات ورود (تعداد بازه‌ها).
chatMessageCount integer تعداد پیام‌های چت این کاربر در جلسه.
lateSeconds integer تأخیر در ورود به ثانیه: فاصلهٔ نخستین ورود تا شروع جلسه (startedAt). هیچ‌وقت منفی نیست؛ ۰ یعنی از ابتدا حاضر بوده است.
earlyLeaveSeconds integer؟ زودرفتن به ثانیه: فاصلهٔ آخرین خروج تا پایان جلسه (endedAt). برای جلسهٔ در حال برگزاری یا کاربری که هنوز در جلسه است null؛ ۰ یعنی تا پایان مانده است.
activity ApiActivity فعالیت صدا، دوربین، دست و اشتراک صفحه؛ برای کلاس‌هایی که پیش از فعال‌شدن این قابلیت برگزار شده‌اند صفر است.
intervals array‹ApiInterval› بازه‌های پیوستهٔ حضور، به ترتیب زمان.
ipAddresses اختیاری array‹string›؟ آی‌پی‌های یکتای کاربر در این جلسه. فقط با include=ip و مجوز کلید.
device اختیاری ApiDevice؟ دستگاه اصلی (آخرین ورود). فقط با include=ip و مجوز کلید؛ اگر User-Agent ثبت نشده باشد حذف می‌شود.

بازهٔ حضور (ApiInterval)

یک بازهٔ پیوستهٔ حضور، از ورود تا خروج.

فیلدنوعتوضیح
joinedAt datetime زمان ورود (UTC).
leftAt datetime؟ زمان خروج (UTC)؛ اگر کاربر هنوز در جلسه است null.
durationSeconds integer طول این بازه به ثانیه.
ipAddress اختیاری string؟ آی‌پی اتصال. فقط با include=ip و مجوز کلید بازگردانده می‌شود.
userAgent اختیاری string؟ مشخصات مرورگر یا دستگاه. فقط با include=ip و مجوز کلید بازگردانده می‌شود.

پیام چت (ApiChatMessage)

یک پیام چت ثبت‌شده در جلسه.

فیلدنوعتوضیح
senderName string نام فرستنده.
externalUserId string شناسهٔ کاربر فرستنده.
message string متن پیام.
chatId string شناسهٔ گفتگو: MAIN-PUBLIC-GROUP-CHAT برای چت عمومی؛ مقادیر دیگر چت خصوصی‌اند.
isPrivate boolean آیا پیام در چت خصوصی ارسال شده است.
sentAt datetime زمان ارسال (UTC).

جمع‌بندی حضور کلاس (ApiClassAttendance)

جمع‌بندی حضور افراد در همهٔ جلسه‌های یک کلاس در یک بازهٔ زمانی.

فیلدنوعتوضیح
externalMeetingId string شناسهٔ کلاس که جمع‌بندی برای آن است.
from datetime؟ ابتدای بازهٔ درخواستی (UTC)؛ اگر تعیین نشده باشد null.
to datetime؟ انتهای بازهٔ درخواستی (UTC) (این لحظه جزو بازه نیست)؛ اگر تعیین نشده باشد null.
thresholdPercent integer آستانهٔ درصد حضور که اعمال شده است.
sessionCount integer تعداد جلسه‌های کلاس در بازه.
sessions array‹ApiClassSessionRef› جلسه‌های بازه به ترتیب زمان.
participants array‹ApiClassParticipant› جمع‌بندی هر فرد؛ ابتدا مدرسان و سپس بقیه به ترتیب نام.

جلسه در جمع‌بندی کلاس (ApiClassSessionRef)

مشخصات یک جلسه در جمع‌بندی کلاس.

فیلدنوعتوضیح
sessionId uuid شناسهٔ یکتای جلسه (UUID).
localDate string تاریخ میلادی روز برگزاری به وقت تهران.
sequenceInDay integer شمارهٔ جلسه در همان روز (از ۱).
startedAt datetime زمان شروع جلسه (UTC).
endedAt datetime؟ زمان پایان جلسه (UTC)؛ برای جلسهٔ در حال برگزاری null.
durationSeconds integer مدت جلسه به ثانیه.
baselineSeconds integer مدت حضور پرحضورترین فرد جلسه (مبنای ۱۰۰٪) به ثانیه.
participantCount integer تعداد شرکت‌کنندگان جلسه.

فرد در جمع‌بندی کلاس (ApiClassParticipant)

جمع‌بندی حضور یک فرد در جلسه‌های کلاس.

فیلدنوعتوضیح
externalUserId string شناسهٔ ثابت کاربر در سامانهٔ شما.
name string آخرین نامی که کاربر در این جلسه‌ها داشته است.
role string moderator اگر کاربر دست‌کم در یکی از جلسه‌ها مدرس بوده باشد؛ در غیر این صورت viewer. moderator | viewer
sessionsAttended integer تعداد جلسه‌های بازه که کاربر در آن‌ها حضور داشته است.
sessionsAbsent integer تعداد جلسه‌های بازه که کاربر در آن‌ها اصلاً وارد نشده است.
sessionsBelowThreshold integer تعداد جلسه‌های حاضر که درصد حضور در آن‌ها کمتر از آستانه بوده است.
totalPresenceSeconds integer مجموع مدت واقعی حضور در همهٔ جلسه‌های بازه به ثانیه.
averagePercent number میانگین درصد حضور روی همهٔ جلسه‌های بازه؛ غیبت ۰٪ حساب می‌شود.
sessions array‹ApiClassParticipantSession› جزئیات حضور در جلسه‌هایی که کاربر در آن‌ها بوده است.

حضور فرد در یک جلسه (ApiClassParticipantSession)

حضور یک فرد در یکی از جلسه‌های کلاس.

فیلدنوعتوضیح
sessionId uuid شناسهٔ جلسه (یکی از موارد sessions در همین پاسخ).
presenceSeconds integer مدت واقعی حضور در این جلسه به ثانیه.
attendancePercent number درصد حضور در این جلسه نسبت به پرحضورترین فرد همان جلسه.
belowThreshold boolean true اگر درصد حضور در این جلسه کمتر از آستانه باشد.
joinCount integer تعداد دفعات ورود در این جلسه.

هویت کلید (ApiWhoAmI)

هویت کلید و مجوزهای آن.

فیلدنوعتوضیح
institute string نام موسسه‌ای که کلید به آن تعلق دارد.
keyName string نامی که مدیر پنل هنگام ساخت کلید تعیین کرده است.
keyPrefix string ابتدای کلید برای شناسایی در پنل مدیریت. کلید کامل هرگز بازگردانده نمی‌شود.
canViewIpAddresses boolean آیا کلید مجاز به دریافت آی‌پی و مرورگر شرکت‌کنندگان (include=ip) است.
canViewChat boolean آیا کلید مجاز به دریافت متن پیام‌های چت (include=chat) است.
expiresAt datetime؟ زمان انقضای کلید (UTC). مقدار null یعنی کلید انقضا ندارد.
serverTime datetime زمان فعلی سرور (UTC)؛ برای بررسی همگام‌بودن ساعت سامانهٔ شما.

۷ قواعد محاسبهٔ حضور

اعداد حضور دقیقاً با همان قواعدی محاسبه می‌شوند که در گزارش‌ها و خروجی اکسل پنل استفاده می‌شود؛ بنابراین هر دو منبع همواره یکسان‌اند.

مدت واقعی حضور

مدت حضور یک فرد (totalPresenceSeconds) برابر اجتماع بازه‌های حضور اوست، نه مجموع ساده. گاهی هنگام قطع و وصل سریع اینترنت، BigBlueButton بازهٔ قبلی را چند ثانیه باز نگه می‌دارد و دو بازه هم‌پوشان می‌شوند؛ در این حالت بخش مشترک فقط یک‌بار شمرده می‌شود. بازه‌ای که هنوز بسته نشده (کاربر در جلسه است) تا لحظهٔ درخواست حساب می‌شود.

مدرس
سارا — دو بازه
علی — دو بازهٔ هم‌پوشان
علی — اجتماع بازه‌ها
0306090 دقیقه
شکل ۱ — جلسهٔ ۹۰ دقیقه‌ای. سارا در دو بازه (هر یک ۱۸ دقیقه) جمعاً ۳۶ دقیقه حضور دارد. بازه‌های علی (دقیقهٔ ۰ تا ۵۰ و ۴۵ تا ۸۰) هم‌پوشانی دارند؛ مجموع ساده ۸۵ دقیقه می‌شود ولی حضور واقعی او (اجتماع) ۸۰ دقیقه است.

درصد حضور

attendancePercent = totalPresenceSeconds ÷ baseline.presenceSeconds × 100 که با یک رقم اعشار گرد می‌شود. baseline پرحضورترین فرد همان جلسه است (اگر چند نفر برابر باشند، کسی که زودتر وارد شده) و معمولاً مدرس است. بنابراین درصد نسبت به طول جلسه نیست؛ اگر چنین نسبتی نیاز دارید از totalPresenceSeconds و durationSeconds استفاده کنید.

فردمدت حضورمحاسبهدرصدbelowThreshold (۷۰)
مدرس (مبنا)۹۰ دقیقه۹۰ ÷ ۹۰۱۰۰٫۰false
علی۸۰ دقیقه۸۰ ÷ ۹۰۸۸٫۹false
سارا۳۶ دقیقه۳۶ ÷ ۹۰۴۰٫۰true

آستانه

belowThreshold برای کسانی true است که attendancePercent آن‌ها کمتر از threshold باشد (مساوی، ناکافی محسوب نمی‌شود). مقدار پیش‌فرض ۷۰ است و در هر درخواست با پارامتر threshold (۰ تا ۱۰۰) قابل تغییر است.

جمع‌بندی چند جلسه

  • sessionsAttended: تعداد جلسه‌هایی که فرد دست‌کم یک‌بار در آن‌ها وارد شده است.
  • sessionsAbsent: تعداد جلسه‌های بازه که فرد در آن‌ها اصلاً وارد نشده است.
  • averagePercent: میانگین درصد حضور روی همهٔ جلسه‌های بازه؛ غیبت ۰ درصد حساب می‌شود.
  • فقط افرادی که دست‌کم یک‌بار وارد شده‌اند فهرست می‌شوند؛ فهرست ثبت‌نام‌شدگان در اختیار پنل نیست و سامانهٔ شما باید آن را با این خروجی تطبیق دهد (مثلاً برای شناسایی کسانی که هرگز وارد نشده‌اند).

۸ زمان، منطقهٔ زمانی و تقویم

زمان‌های پاسخ

همهٔ زمان‌ها در پاسخ به‌صورت ISO-8601 و به وقت UTC هستند (مانند 2026-09-20T05:00:00+00:00). تبدیل به وقت محلی بر عهدهٔ مصرف‌کننده است. با فراخوانی whoami می‌توانید زمان سرور را (serverTime) با ساعت سامانهٔ خود مقایسه کنید.

مفهوم «روز»

«روز» همیشه بر مبنای وقت تهران (UTC+03:30، بدون ساعت تابستانی) محاسبه می‌شود، نه UTC و نه منطقهٔ سرور. جلسه‌ای که ساعت ۰۱:۰۰ بامداد ۲۱ سپتامبر به وقت تهران شروع شود، در UTC متعلق به ۲۰ سپتامبر است ولی localDate آن 2026-09-21 خواهد بود. تاریخ شمسی همان روز در localDateJalali آمده است.

پارامترهای زمانی درخواست

ورودینمونهمعنی
date2026-09-20 یا 1405-06-29کل آن روز تهرانی. تاریخ شمسی (سال کمتر از ۱۷۰۰) هم پذیرفته می‌شود.
from/to — تاریخ تنهاfrom=2026-09-01&to=2026-09-30from از ابتدای آن روز و to تا پایان آن روز (شامل خود آن روز).
from/to — زمان کامل2026-09-20T08:00:00+03:30
2026-09-20T04:30:00Z
زمان کامل ISO-8601 با offset. لحظهٔ to جزو بازه نیست.
  • بازه بر اساس زمان شروع جلسه اعمال می‌شود و حداکثر ۳۶۶ روز است.
  • زمان بدون offset رد می‌شود (مبهم است: تهران یا UTC؟) و خطای invalid_parameter برمی‌گردد.
  • علامت + در آدرس به‌معنای فاصله تفسیر می‌شود؛ آن را به‌صورت %2B کدگذاری کنید (کتابخانه‌های HTTP معمولاً خودکار انجام می‌دهند) یا از پسوند Z استفاده کنید.
  • date را نمی‌توان همراه from/to فرستاد.

۹ صفحه‌بندی و محدودیت نرخ

صفحه‌بندی

فهرست‌ها با page (از ۱) و pageSize صفحه‌بندی می‌شوند. پاسخ شامل items، page، pageSize، totalCount و totalPages است. برای دریافت همهٔ نتایج، تا رسیدن به page = totalPages صفحه‌ها را پیمایش کنید.

مسیرپیش‌فرض pageSizeحداکثر pageSize
/meetings۲۰۱۰۰
/classes/{externalMeetingId}/sessions۲۰۵۰
/classes/{externalMeetingId}/attendanceصفحه‌بندی ندارد؛ حداکثر ۵۰۰ جلسه در هر درخواست (در غیر این صورت too_many_sessions).

محدودیت نرخ

تعداد درخواست‌های هر کلید در دقیقه محدود است (پیش‌فرض ۱۲۰ درخواست در دقیقه؛ مصرف یک کلید روی کلید دیگر اثر نمی‌گذارد). در صورت عبور از سقف، پاسخ 429 با کد rate_limited و هدر Retry-After (ثانیه) برمی‌گردد. توصیه می‌شود پس از دریافت ۴۲۹ به‌اندازهٔ Retry-After صبر کنید و تلاش‌های بعدی را با فاصلهٔ فزاینده (exponential backoff) انجام دهید.

۱۰ خطاها

همهٔ پاسخ‌های ناموفق کد وضعیت HTTP مناسب و بدنهٔ JSON یکسان زیر را دارند. برای تصمیم‌گیری در برنامهٔ خود به error.code (پایدار و ماشین‌خوان) تکیه کنید، نه به متن پیام.

خطا — application/json
{
  "error": {
    "code": "invalid_api_key",
    "message": "کلید API نامعتبر است."
  }
}
HTTPcodeمعنی و اقدام پیشنهادی
400invalid_parameterمقدار یکی از پارامترها نامعتبر است. پیام فارسی نام پارامتر و مثال معتبر را می‌گوید. تلاش مجدد بدون اصلاح فایده ندارد.
400missing_parameterپارامتر الزامی (مثلاً date یا from) ارسال نشده است.
400invalid_rangeto پیش از from است.
400range_too_largeبازه از ۳۶۶ روز بیشتر است؛ بازه را کوچک‌تر کنید.
400too_many_sessionsدر بازه بیش از ۵۰۰ جلسه وجود دارد؛ بازه را به چند درخواست تقسیم کنید.
401missing_api_keyکلید در هدر ارسال نشده است.
401invalid_api_keyکلید ناشناخته یا ناقص است؛ از صحت کپی‌شدن آن مطمئن شوید.
401api_key_revokedکلید لغو شده است؛ از مدیر پنل کلید جدید بخواهید.
401api_key_expiredکلید منقضی شده است؛ از مدیر پنل کلید جدید بخواهید.
403forbidden_includeکلید مجوز include درخواستی (آی‌پی یا چت) را ندارد.
403institute_inactiveموسسهٔ صاحب کلید غیرفعال است.
404session_not_foundجلسه‌ای با این شناسه برای موسسهٔ صاحب کلید وجود ندارد. اگر جلسه را در پنل می‌بینید، علت معمولاً شناسهٔ ناقص/اشتباه است یا کلید برای موسسهٔ دیگری ساخته شده (پرسش‌های متداول را ببینید). پیام خطا نام موسسهٔ کلید و شناسهٔ دریافت‌شده را می‌آورد.
404class_not_foundهیچ جلسه‌ای با این externalMeetingId برای موسسه ثبت نشده است. (اگر کلاس وجود دارد ولی در بازه جلسه‌ای نیست، پاسخ ۲۰۰ با فهرست خالی است.)
429rate_limitedسقف درخواست در دقیقه پر شده است؛ طبق Retry-After صبر کنید.
5xx—خطای موقت سرور. با فاصلهٔ فزاینده چند بار دیگر تلاش کنید و در صورت تداوم به مدیر پنل اطلاع دهید.

۱۱ توصیه‌های یکپارچه‌سازی

  • شناسهٔ ثابت کاربر: هنگام ساخت لینک ورود (join) به BigBlueButton، پارامتر userID را با شناسهٔ ثابت دانشجو/مدرس در سامانهٔ خودتان بفرستید. بدون آن، هر اتصال یک شرکت‌کنندهٔ جدید شمرده می‌شود و حضور هر فرد درست یکی نمی‌شود.
  • شناسهٔ کلاس: meetingID را در سامانهٔ خود نگه دارید و ترجیحاً برای هر جلسه یکتا انتخاب کنید (بخش ۲).
  • زمان خواندن: BigBlueButton پایان هر خروج را با حدود ۱۵ تا ۲۰ ثانیه تأخیر گزارش می‌کند. برای گزارش نهایی، پس از پایان جلسه (status = ended) اطلاعات را بخوانید. جلسهٔ در حال برگزاری (live) تا لحظهٔ درخواست محاسبه می‌شود و تا پایان جلسه تغییر می‌کند.
  • همگام‌سازی افزایشی: پس از پایان هر کلاس، جلسه‌های بازهٔ اخیر را با /meetings?status=ended&from=... فهرست کنید و جزئیات جلسه‌های جدید را بخوانید. sessionId کلید یکتای مناسبی برای ذخیره و به‌روزرسانی (upsert) است.
  • تلاش مجدد: فقط برای 429 و 5xx با فاصلهٔ فزاینده دوباره تلاش کنید؛ خطاهای ۴xx دیگر با تکرار درخواست برطرف نمی‌شوند.
  • Timeout: برای هر درخواست زمان انتظار حداقل ۳۰ ثانیه در نظر بگیرید؛ پاسخ‌های حجیم (جلسه‌های پرجمعیت با include=chat) کمی بیشتر طول می‌کشند.
  • تطبیق با فهرست ثبت‌نام‌شدگان: غایبِ کامل (کسی که هرگز وارد نشده) در خروجی نیست؛ فهرست ثبت‌نام‌شدگان کلاس را از سامانهٔ خودتان بگیرید و با externalUserId تطبیق دهید.
  • حریم خصوصی: include=ip و include=chat را فقط در صورت نیاز واقعی و مطابق سیاست حفظ حریم خصوصی موسسه درخواست کنید.

۱۲ پرسش‌های متداول

جلسه را در پنل می‌بینم ولی API «جلسه یافت نشد» (۴۰۴) می‌دهد. چرا؟

API فقط جلسه‌های موسسهٔ صاحب کلید را می‌بیند و برای جلسهٔ موسسهٔ دیگر عمداً همین ۴۰۴ را می‌دهد. این وضعیت (چه جلسه زنده باشد چه پایان‌یافته) دو علت رایج دارد:

  1. کلید برای موسسهٔ دیگری ساخته شده است. با GET /api/v1/whoami فیلد institute را ببینید و با موسسه‌ای که سرور کلاس در پنل به آن تعلق دارد مقایسه کنید. اگر فرق دارد، مدیر پنل باید کلید را برای موسسهٔ درست بسازد. متن خطای ۴۰۴ هم نام موسسهٔ کلید را نشان می‌دهد.
  2. شناسه درست منتقل نشده است. برای اطمینان، GET /api/v1/meetings?status=live را بزنید (برای جلسه‌های پایان‌یافته status=ended)؛ اگر جلسه در فهرست هست، sessionId یا internalMeetingId را از خود همان خروجی بردارید. فاصله و نویسه‌های نامرئی ابتدا و انتهای شناسه به‌طور خودکار نادیده گرفته می‌شود، ولی شناسهٔ ناقص (مثلاً بدون بخش -1789965606958 پایانی) پذیرفته نیست.

اگر جلسه اصلاً در فهرست نیامد، علت اول است.

کلاسی که دو بار در روز تشکیل می‌شود چگونه دیده می‌شود؟

به‌صورت دو جلسهٔ جدا با sessionId و زمان شروع/پایان متفاوت. با GET /api/v1/classes/{externalMeetingId}/sessions?date=YYYY-MM-DD هر دو را به ترتیب زمان می‌گیرید و sequenceInDay (۱ و ۲) آن‌ها را از هم متمایز می‌کند.

چرا یک دانشجو چند بار در فهرست شرکت‌کنندگان آمده است؟

احتمالاً هنگام ساخت لینک ورود، userID ثابتی ارسال نشده و هر اتصال با شناسهٔ جداگانه شمرده شده است. با ارسال شناسهٔ ثابت کاربر، همهٔ ورودهای او در یک شرکت‌کننده و چند بازه جمع می‌شود.

چرا درصد حضور مدرس همیشه ۱۰۰ است؟

چون درصد نسبت به پرحضورترین فرد همان جلسه سنجیده می‌شود و معمولاً مدرس از ابتدا تا انتها حاضر است. اگر می‌خواهید حضور را نسبت به طول جلسه بسنجید، totalPresenceSeconds را بر durationSeconds تقسیم کنید.

چرا آی‌پی‌ها در پاسخ نیست؟

آی‌پی فقط با include=ip و در صورت داشتن مجوز روی کلید بازگردانده می‌شود. همچنین برای جلساتی که پیش از فعال‌شدن ثبت آی‌پی در پنل برگزار شده‌اند، آی‌پی وجود ندارد.

کلید را گم کرده‌ام. چه کنم؟

کلید قابل بازیابی نیست. از مدیر پنل بخواهید کلید جدیدی بسازد و کلید قبلی را لغو کند.

آیا پنل نتیجه را به سامانهٔ ما ارسال (Push) می‌کند؟

خیر. در نسخهٔ ۱ فقط روش Pull پشتیبانی می‌شود: سامانهٔ شما در زمان دلخواه اطلاعات را درخواست می‌کند.

جلسه‌ای که هنوز تمام نشده چه وضعیتی دارد؟

status = live و endedAt = null. مدت حضور افراد حاضر تا لحظهٔ درخواست حساب می‌شود و با هر درخواست تازه‌تر می‌شود.

۱۳ نسخه‌بندی و تغییرات

نسخهٔ API در مسیر آمده است (/api/v1). در طول نسخهٔ ۱ تغییرات ناسازگار با گذشته اعمال نمی‌شود؛ افزودن endpoint، پارامتر اختیاری یا فیلد جدید به پاسخ تغییر سازگار محسوب می‌شود و ممکن است بدون اطلاع قبلی انجام شود، پس مصرف‌کننده باید فیلدهای ناشناخته را نادیده بگیرد. هر تغییر ناسازگار در نسخهٔ جدید (/api/v2) عرضه می‌شود و نسخهٔ قبلی برای مدتی مناسب در دسترس می‌ماند.

نسخهتاریخشرح
1.0.0 ۱۴۰۵/۰۶/۲۹ انتشار نخست: مسیرهای whoami، فهرست و جزئیات جلسه‌ها، جلسه‌های یک کلاس و جمع‌بندی حضور کلاس؛ احراز هویت با کلید API موسسه؛ مجوزهای اختیاری آی‌پی و چت.