June 18, 2026 · 10 min read

كيف وضعنا اجتماع الاستعراض الأسبوعي على الطيار الآلي باستخدام واجهة PollsLive البرمجية

شرح عملي من مطوّر: أنشئ استطلاع استعراض، وافتح جلسة مباشرة، ثم اسحب النتائج وملف CSV - كل ذلك من سكربت Node صغير يعمل على cron. نقاط نهاية حقيقية من ‎/api/v1‎ وحمولات حقيقية.

By Maya Okafor · Platform engineer·DevelopersHow-toTeams & meetings

أنا مهندس منصّات، وكل يوم جمعة كنت أقضي عشر دقائق في بناء نفس عرض الاستعراض يدويًا. نفس الأسئلة الخمسة، أسبوع مختلف. شيء كلاسيكي من المفترض أن يقوم به سكربت. لذلك نقلته إلى واجهة PollsLive البرمجية ومهمة cron - أصبح الاستطلاع ينشئ نفسه، وتُفتح جلسة مباشرة تلقائيًا، وبعد الوقوف اليومي يصل ملف CSV إلى Slack لدينا. إليك الطريقة بالضبط، مع نقاط النهاية والحمولات الحقيقية.

Tip

لا تحتاج أن تكون تقنيًا لمتابعة هذا الدليل. كل خطوة تستخدم أزرارًا بسيطة في PollsLive - دون برمجة ودون تثبيت تطبيق لجمهورك.

المصادقة: رمز Bearer واحد

أنشأت مفتاح واجهة برمجية لمساحة العمل في Studio ← Developers (تبدو مثل `plv_live_…`) ووضعته في بيئة السكربت. كل طلب هو مجرد رمز Bearer - لا حاجة لرقصة OAuth في الاستخدام بين الخوادم.

# Base URL for all calls
export POLLSLIVE_API=https://pollslive.com/api/v1
export POLLSLIVE_KEY=plv_live_xxxxxxxxxxxxxxxxxxxx

# Every request carries the key as a Bearer token:
#   Authorization: Bearer $POLLSLIVE_KEY
# Rate limit: 120 requests/min per key (HTTP 429 if you exceed it).

الخطوة 1 - إنشاء استطلاع الاستعراض

الاستطلاع هو عرض شرائح مُصنّف: `content.questions` عبارة عن مصفوفة من الشرائح، لكل منها مُميّز `kind`. للاستعراض أستخدم scale و open_ended يُعرض كسحابة و multiple_choice للتصويت على الإصلاح. يعيد لي `POST /polls` مع `renderingMode: "LIVE"` مسوّدة.

curl -sS -X POST "$POLLSLIVE_API/polls" \
  -H "Authorization: Bearer $POLLSLIVE_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "title": "Weekly retro - week of Jun 16",
    "renderingMode": "LIVE",
    "content": {
      "questions": [
        {
          "id": "health",
          "kind": "scale",
          "text": "How healthy did this sprint feel?",
          "min": 1, "max": 5,
          "minLabel": "Rough", "maxLabel": "Great"
        },
        {
          "id": "blockers",
          "kind": "open_ended",
          "text": "What slowed us down this sprint?",
          "render": "cloud",
          "maxLength": 120
        },
        {
          "id": "fix",
          "kind": "multiple_choice",
          "type": "single",
          "text": "What should we fix first next sprint?",
          "options": [
            { "id": "ci",       "label": "Stabilise the CI pipeline" },
            { "id": "tickets",  "label": "Tighter ticket acceptance criteria" },
            { "id": "wip",      "label": "Cap WIP and reviews SLA" },
            { "id": "meetings", "label": "Trim the meeting load" }
          ]
        }
      ]
    }
  }'

تغلّف استجابة `201` الاستطلاع في مظروف `data` - أحتفظ بـ `data.id` لبقية التدفّق، وبـ `data.accessPin` / `data.shareUrl` لتفاصيل الانضمام:

{
  "data": {
    "id": "poll_9aZ2kP",
    "slug": "weekly-retro-jun-16",
    "title": "Weekly retro - week of Jun 16",
    "renderingMode": "LIVE",
    "isPublished": false,
    "accessPin": "6098",
    "shareUrl": "https://pollslive.com/p/weekly-retro-jun-16",
    "createdAt": "2026-06-16T08:00:11.204Z"
  }
}

الخطوة 2 - انشر، ثم افتح جلسة مباشرة

النشر يلتقط لقطة من المحتوى ليصبح آمنًا للعرض. يقوم بذلك `PATCH /polls/{id}` مع `publish: true`؛ ثم يُشغّل `POST /sessions` جلسة المُقدّم ويعيد رمز الانضمام إضافة إلى `hostToken` يُستخدم مرة واحدة (سرّ سلطة المُضيف - يُعاد فقط عند الإنشاء، لذا أحفظه فورًا).

# Publish the draft
curl -sS -X PATCH "$POLLSLIVE_API/polls/poll_9aZ2kP" \
  -H "Authorization: Bearer $POLLSLIVE_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "publish": true }'

# Open a presenter-paced live session
curl -sS -X POST "$POLLSLIVE_API/sessions" \
  -H "Authorization: Bearer $POLLSLIVE_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "pollId": "poll_9aZ2kP", "pace": "PRESENTER" }'
# → { "data": { "id": "ses_4dF1", "pin": "6098",
#       "joinUrl": "https://pollslive.com/join/6098",
#       "hostUrl": "https://pollslive.com/host/ses_4dF1",
#       "hostToken": "hst_…", "status": "LOBBY" } }

ينشر cron الخاص بي رابط `joinUrl` ورمز PIN في قناة الفريق قبل الوقوف اليومي بدقيقة. يمسح الفريق الرمز، وتظهر لهم الشرائح التي بنيتها في الكود - وإليك النتيجة المباشرة لشريحة `health` بعد أن يجيب الجميع:

Live scale
Join at the PIN 6098

كيف كان شعورك بصحة هذا السبرنت؟ (1 = صعب، 5 = رائع)

5 - رائع11% · 1
4 - جيد22% · 2
3 - مقبول22% · 2
2 - صعب33% · 3
1 - سيئ11% · 1
الشريحة التي عرّفتها بصيغة JSON، تُعرض مباشرة - دون بناء عرض يدوي.

الخطوة 3 - اسحب النتائج وملف CSV بعد الوقوف اليومي

عند انتهاء الجلسة، يعيد `GET /polls/{id}/results` الإجماليات - `totalVotes` وخريطة `voteCounts` لشرائح الاختيار، ومصفوفة `slides[]` تحتوي على تجميعات لكل نوع (متوسطات المقياس، النصوص المفتوحة المجمّعة، إلخ).

curl -sS "$POLLSLIVE_API/polls/poll_9aZ2kP/results" \
  -H "Authorization: Bearer $POLLSLIVE_KEY"

{
  "data": {
    "pollId": "poll_9aZ2kP",
    "title": "Weekly retro - week of Jun 16",
    "closed": true,
    "totalVotes": 9,
    "voteCounts": { "ci": 4, "tickets": 3, "wip": 2, "meetings": 0 },
    "slides": [
      { "slideId": "health", "kind": "scale", "average": 2.9 },
      { "slideId": "blockers", "kind": "open_ended", "responseCount": 14 },
      { "slideId": "fix", "kind": "multiple_choice",
        "optionCounts": { "ci": 4, "tickets": 3, "wip": 2, "meetings": 0 } }
    ]
  }
}

للأرشيف، يبثّ `GET /polls/{id}/export` ملف CSV بصيغة RFC-4180 (رقم الشريحة، السؤال، النوع، تسمية الإجابة، العدد/القيمة). أحفظه وأرفقه برسالة Slack. خطوة ما بعد الوقوف بأكملها هي دالة صغيرة واحدة:

const API = process.env.POLLSLIVE_API;
const KEY = process.env.POLLSLIVE_KEY;
const auth = { Authorization: `Bearer ${KEY}` };

export async function summariseRetro(pollId) {
  const res = await fetch(`${API}/polls/${pollId}/results`, { headers: auth });
  if (res.status === 429) throw new Error("rate_limited"); // back off + retry
  if (!res.ok) throw new Error(`results ${res.status}`);
  const { data } = await res.json();

  const winner = Object.entries(data.voteCounts)
    .sort((a, b) => b[1] - a[1])[0];

  // Grab the CSV for the archive
  const csv = await fetch(`${API}/polls/${pollId}/export`, { headers: auth })
    .then((r) => r.text());

  return {
    health: data.slides.find((s) => s.slideId === "health")?.average,
    topFix: winner?.[0],
    voters: data.totalVotes,
    csv,
  };
}

هذا هو الأمر كله. عرض الاستعراض يبني نفسه، والجلسة تُفتح في موعدها، والملخّص مع ملف CSV يُنشران تلقائيًا. استعدت عشر دقائق من يوم جمعتي، والصيغة متطابقة كل أسبوع - وهو بالضبط ما تريده من اجتماع استعراض.

أجمل جزء: الشرائح التي أصفها بصيغة JSON هي نفس الشرائح التي يراها الفريق ويصوّت عليها. لا انحراف بين ما صنعه السكربت وما شغّلناه.

المرجع الكامل لنقاط النهاية والمخططات موجود في مرجع الواجهة البرمجية التفاعلي، والبداية السريعة السردية في دليل المطوّرين. التالي على قائمتي: الـ webhooks، كي يُطلَق ملخّص Slack في اللحظة التي تنتهي فيها الجلسة بدلًا من مؤقّت - وهو بالضبط ما يغطّيه مقال الـ webhooks.

Tip

استخدم cron job لاستنساخ عرض رترو الأسبوع الماضي ونشر رابط جديد تلقائيًا.

PollsLive Studio dashboard with poll list, left nav, and Create buttons.
Studio is your home for every poll you have created.
Results panel with stat tiles, breakdown by question, and export buttons.
The Results tab shows every answer and export options.

بديل دون برمجة: استطلاعات متكررة دون إعادة بناء.

ابنِ على PollsLive - أنشئ استطلاعات، وشغّل جلسات مباشرة، واسحب النتائج، واستقبل الـ webhooks من كودك الخاص.

اقرأ وثائق المطوّرين