رفتن به محتوا

جریانی که فقط جلسه می‌فرستد (n8n)

بعضی ابزارها فقط باید جلسه را به لُب بسپارند. مثلاً یک گردش کار n8n فایل ضبط‌شده‌ای را برمی‌دارد، فایلی که کسی در Slack به اشتراک گذاشته یا ضبط یک جلسهٔ آنلاین، و آن را می‌فرستد. بعد خود شخص لُب را در برنامه می‌خواند؛ گردش کار هرگز لازم ندارد آن را بخواند.

به چنین ابزاری توکنی بدهید که فقط «ارسال جلسه» دارد: recordings:write، بدون recordings:read. اگر روزی لو برود، می‌تواند دقیقه خرج کند، اما رونوشت و لُب هیچ جلسه‌ای را نمی‌تواند بخواند.

این توکن هم مثل هر توکن دیگری فقط تا وقتی کار می‌کند که «حالت توسعه‌دهنده» روشن باشد. تا وقتی این حالت خاموش است، هر درخواست پاسخ 403 DEVELOPER_MODE_OFF می‌گیرد، تا دوباره روشن شود؛ توکن‌ها و دسترسی‌ها را ببینید. گردش کاری که ماه‌ها اجرا می‌شود، با دیدن این کد باید به شخص خبر بدهد، نه این‌که پشت سر هم دوباره امتحان کند.

این توکن چه کارهایی می‌تواند بکند

Section titled “این توکن چه کارهایی می‌تواند بکند”
  • جلسه بفرستد: POST /recordings. هر جلسه به اندازهٔ دقیقه‌هایش خرج می‌کند، پس برای این توکن سقف روزانه بگذارید.
  • جلسه‌هایی را که فرستاده دنبال کند: وضعیت آن‌ها را با GET /recordings/{id}/status، و رویدادهایشان را در GET /events، که در آن فقط جلسه‌هایی را می‌بیند که خودش فرستاده است.
  • روی جلسه‌هایی که فرستاده کار کند: از نام‌گذاری رد شود و جلسهٔ ناموفق را دوباره اجرا کند؛ چرخهٔ یک جلسه را ببینید.
  • خودش را بررسی کند، با GET /whoami که به هر توکنی پاسخ می‌دهد.

چه کارهایی نمی‌تواند بکند

Section titled “چه کارهایی نمی‌تواند بکند”
  • هیچ جلسهٔ دیگری را نمی‌بیند: جلسه‌ای که در برنامه ضبط شده یا توکن دیگری فرستاده، پاسخ 404 می‌گیرد، گویی وجود ندارد.
  • رونوشت و لُب هیچ جلسه‌ای را نمی‌خواند، حتی جلسه‌های خودش را: وضعیت جلسه‌ای که فرستاده عنوان آن را دارد و بس. جلسهٔ کامل و فهرست جلسه‌های آماده به recordings:read نیاز دارند، و جست‌وجو و پرسش به recordings:query: هر کدام پاسخ 403 TOKEN_SCOPE می‌گیرد.
  • bundleUrl در پاسخ‌های آن همیشه null است.

پس گردش کار لینک‌ها را می‌رساند، هرگز خود جلسه را: naming.url تا وقتی جلسه منتظر نام‌هاست، و url وقتی آماده شد. هیچ‌کدام لینک عمومی نیست: هر کدام جلسه را در برنامه باز می‌کند، و فقط برای کسی که وارد حساب شده و همین حالا هم می‌تواند جلسه را ببیند.

  1. فایل را بفرستید، برای هر فایل با یک Idempotency-Key تازه، که برای همهٔ تلاش‌های دوبارهٔ همان فایل نگه داشته می‌شود؛ تکرارپذیری و تلاش دوباره را ببینید. اگر منبع زمان برگزاری جلسه را می‌داند recordedAt را هم بفرستید، و یک title. از پاسخ 202، مقدارهای id و statusUrl را نگه دارید.
  2. منتظر بمانید، با خواندن وضعیت تقریباً هر یک دقیقه یک بار، یا با خواندن فید رویدادها؛ پرس‌وجوی دوره‌ای و فید رویدادها را ببینید.
  3. در NAMING، naming.url را برای شخص بفرستید، مثلاً در همان رشتهٔ گفت‌وگوی Slack، و همچنان منتظر بمانید. یا از نام‌گذاری رد شوید: POST …/naming با { "action": "skip" }، یا از همان اول جلسه را با naming=skip بفرستید.
  4. در READY، url را برای شخص بفرستید: جلسه، همراه لُب آن، در برنامه.
  5. در FAILED، اگر error.retryable برابر true است دوباره اجرایش کنید و همچنان منتظر بمانید: لُب هم مرحلهٔ شکست‌خورده را خودش دوباره امتحان می‌کند، پس FAILED ممکن است موقت باشد، و تلاش دوبارهٔ شما اجرای دومی راه نمی‌اندازد (409 NOT_FAILED یعنی جلسه خودش درست شده است). پس از چند بار تلاش دوباره، یا اگر error.retryable برابر false است، به شخص خبر بدهید.

یک گره HTTP Request جلسه را می‌فرستد:

HTTP Request node
Method: POST
URL: https://app.lobbe.ai/api/v1/recordings
Credential (Header Auth): Authorization = Bearer <your token>
Body: Form-Data, field audio = the meeting's file
Then poll the statusUrl it answers with, about once a minute.

فایل در فیلد audio و همراه نوعش می‌رود. پاسخ 400 VALIDATION با پیام Unsupported audio type یعنی فایل بدون نوعش فرستاده شده است؛ محدودیت‌های ارسال فایل را ببینید. از توکن مثل یک گذرواژه مراقبت کنید؛ امن نگه داشتن توکن را ببینید.

دستورالعمل n8n این گره را قدم به قدم می‌سازد، با توکنی که به‌جای خود گره در یک اعتبارنامهٔ Header Auth نگه داشته می‌شود، و حلقه‌ای را هم اضافه می‌کند که منتظر آماده شدن جلسه می‌ماند.

همین روند در خط فرمان. جلسه را می‌فرستد، اگر جلسه منتظر نام‌ها بماند یک بار لینک نام‌گذاری را نشان می‌دهد، شکستی را که درست‌شدنی است دوباره اجرا می‌کند (حداکثر سه بار)، و در READY، در شکستی که نمی‌شود دوباره اجرایش کرد، یا با هر خطا، نتیجه را نشان می‌دهد و می‌ایستد (به jq و uuidgen نیاز دارد):

Terminal window
key=$(uuidgen) # one key for this file, kept for every retry of it
body=$(curl -sS --fail-with-body https://app.lobbe.ai/api/v1/recordings \
-H "Authorization: Bearer $LOBBE_TOKEN" \
-H "Idempotency-Key: $key" \
-F "audio=@meeting.m4a;type=audio/mp4" \
-F "title=Weekly product sync" \
-F "recordedAt=2026-10-19T10:30:00+03:30")
status_url=$(jq -r '.recording.statusUrl // empty' <<<"$body")
[ -n "$status_url" ] || echo "$body"
told=""
retries=0
while [ -n "$status_url" ]; do
body=$(curl -sS --fail-with-body "$status_url" \
-H "Authorization: Bearer $LOBBE_TOKEN") || { echo "$body"; break; }
status=$(jq -r .status <<<"$body")
if [ "$status" = NAMING ] && [ -z "$told" ]; then
echo "Name the speakers: $(jq -r .naming.url <<<"$body")"
told=yes
fi
if [ "$status" = READY ]; then
echo "The Lobbe is ready: $(jq -r .url <<<"$body")"
break
fi
if [ "$status" = FAILED ]; then
if [ "$(jq -r .error.retryable <<<"$body")" != true ] || [ "$retries" -ge 3 ]; then
echo "It failed: $(jq -c .error <<<"$body")"
break
fi
retries=$((retries + 1))
# 409 NOT_FAILED: it recovered by itself in the meantime. Keep waiting either way.
body=$(curl -sS --fail-with-body -X POST "${status_url%/status}/retry" \
-H "Authorization: Bearer $LOBBE_TOKEN") ||
[ "$(jq -r .error.code <<<"$body" 2>/dev/null)" = NOT_FAILED ] || { echo "$body"; break; }
fi
sleep 60
done

اگر «نام‌گذاری گوینده‌ها» روی «بپرس» باشد، جلسه ممکن است ساعت‌ها منتظر نام‌ها بماند. خواندن وضعیت آن هر یک دقیقه یک بار در تمام این مدت بسیار کمتر از سقف درخواست‌های توکن است.