پتالاز هوش مصنوعی شکوفا
همهٔ مستندات

راهنمای API

احراز هویت، ذخیره‌سازی، تحویل CDN، تبدیل تصویر و ویدیو — با درخواست‌های آماده برای کپی.

چگونه از سرویس فایل پتال در سمت سرور برنامهٔ خودتان استفاده کنید: ذخیره‌سازی اشیا، تحویل CDN، تبدیل تصویر در لحظه و پخش ویدئو، همه از راه API سرویس Bee. هر نمونه یک دستور curl آمادهٔ رونوشت است و بخش ۱۳ همان مسیر را در Node و C#‎ نشان می‌دهد.

مرجع تعاملی: مسیر /scalar/ روی نمونهٔ در حال اجرا. قرارداد ماشین‌خوان: docs/openapi/bee-v1.json (در مخزن نگهداری و بازتولید می‌شود، هرگز دستی ویرایش نمی‌شود).

فهرست

  1. پتال چیست، و چهار سطح API
  2. حساب‌ها، نقش‌ها و مجوزها
  3. نشست یا کلید API: کدام اعتبارنامه
  4. کلیدهای API حساب
  5. نشست‌ها: ورود و تازه‌سازی
  6. قراردادهایی که همه‌جا برقرارند
  7. ناحیه‌های ذخیره‌سازی و کلیدهای دسترسی ناحیه
  8. سطح دادهٔ ذخیره‌سازی: بارگذاری، دریافت، فهرست، حذف
  9. بارگذاری ازسرگیری‌پذیر
  10. CDN: ناحیه‌های pull، حافظهٔ نهان، پاک‌سازی، قواعد لبه، URLهای امضاشده
  11. تصویر و ویدئو
  12. مدیریت، گزارش ممیزی و آمار
  13. سر تا ته: بارگذاری یک فایل و گرفتن نشانی CDN آن، در Node و C#‎
  14. عیب‌یابی
  15. مرجع سریع

۱. پتال چیست، و چهار سطح API

پتال برنامهٔ وب است؛ Bee همان API پشت آن. هر کاری که در پتال می‌شود کرد، مستقیم با Bee هم می‌شود کرد، و این راهنما دربارهٔ همین است.

نحوهٔ اجرای Beeنشانی پایه
docker compose uphttp://localhost:18080 (پورت منتشرشده BEE_API_HOST_PORT است)
اجرای مستقیم با dotnet runhttp://localhost:8080
یک استقرار واقعیهمان مبدأیی که پتال آن را PUBLIC_BEE_ORIGIN می‌نامد

نمونه‌ها از یک متغیر شل استفاده می‌کنند تا همه‌جا کار کنند:

BEE=http://localhost:18080

نقاط پایانی Bee به چهار سطح با احراز هویت متفاوت تقسیم می‌شوند. دانستن اینکه در کدام سطح هستید، پاسخ بیشتر پرسش‌های «چرا 401 می‌گیرم؟» است.

سطحمسیرهااعتبارنامهکاربرد
کنترل/api/v1/**کلید API حساب یا توکن نشستساخت و پیکربندی همه‌چیز
دادهٔ ذخیره‌سازی/storage/{zone}/**کلید دسترسی ناحیهبارگذاری، دریافت، فهرست و حذف اشیای خام
نشست‌های بارگذاری/upload/sessions/**کلید API حساب یا توکن نشستبارگذاری ازسرگیری‌پذیر
تحویل/cdn/**، /img/**هیچارائهٔ محتوا به کاربران نهایی

سطح تحویل هیچ سربرگ احراز هویتی ندارد: دسترسی را تنظیمات امنیتی خودِ هر ناحیهٔ pull کنترل می‌کند (URLهای امضاشده و قواعد ارجاع‌دهنده، IP و کشور).

۲. حساب‌ها، نقش‌ها و مجوزها

حساب در پتال ساخته می‌شود (یا با POST /api/v1/auth/register و {email, password, displayName}) و به عنوان Viewer آغاز می‌کند. نقش‌ها را مدیر می‌دهد. Bee بر پایهٔ مجوزها اجازه می‌دهد، و نقش‌ها فقط دسته‌بندی مجوزها هستند:

نقشمجوزهای داده‌شده
Adminbee:admin
Operatorbee:write، bee:purge، bee:stream
Viewerbee:read

مجوزها سلسله‌مراتبی‌اند، نه یک مجموعهٔ تخت. مستندات هر عملیات مجوز لازم را نام می‌برد و مجوزهای بالاتر پایین‌تر را برآورده می‌کنند:

عملیات نیاز دارد بهبرآورده می‌شود با
bee:readbee:read، bee:write، bee:admin
bee:writebee:write، bee:admin
bee:purgebee:purge، bee:write، bee:admin
bee:streambee:stream، bee:write، bee:admin
bee:adminbee:admin

پس یک Operator که هیچ ادعای bee:read ندارد باز هم می‌تواند همه‌چیز را بخواند: bee:write آن را در بر دارد.

در محیط توسعه یک مدیر پیش‌ساخته وجود دارد (BeeIdentity__SeedAdminEmail و BeeIdentity__SeedAdminPassword در .env). نخستین ورود آن باید پیش از هر کار دیگری گذرواژه را تغییر دهد، و تا آن زمان هیچ کلید API نه ساخته می‌شود و نه کار می‌کند.

۳. نشست یا کلید API: کدام اعتبارنامه

دو راه برای رسیدن به سطح کنترل هست، و هر کدام برای فراخوان متفاوتی است.

توکن نشستکلید API حساب
گرفتنشPOST /auth/login با ایمیل و گذرواژهپتال ← حساب ← کلیدهای API، یک بار
عمرش۱۵ دقیقه، سپس تازه‌سازی (توکن تازه‌سازی یک‌بارمصرف)تا وقتی باطلش کنید یا منقضی شود
حملشهمهٔ مجوزهای حسابزیرمجموعه‌ای که هنگام ساخت برمی‌گزینید
ابطال‌پذیرفقط با تغییر گذرواژهبله، بی‌درنگ، از پتال
مدیریت کلید یا گذرواژهبلهخیر
برایانسان‌ها، و خودِ برنامهٔ پتالکد سمت سرور خودتان

برای هر چیز بی‌سرپرست از کلید API استفاده کنید. سروری که گذرواژهٔ کاربری را نگه می‌دارد و حلقهٔ تازه‌سازی می‌چرخاند، اعتبارنامه‌ای دارد که هر کاری حساب می‌تواند بکند می‌کند، بدون تغییر گذرواژه ابطال نمی‌شود، و با نخستین توکن تازه‌سازیِ دوبار ارائه‌شده می‌میرد. کلید API فقط آنچه به آن داده‌اید دارد و وقتی بگویید می‌میرد.

۴. کلیدهای API حساب

ساختن

در پتال، حساب را باز کنید (منوی زیر نامتان) و زیر کلیدهای API گزینهٔ کلید تازه را بزنید. نامی بدهید که بگوید چه چیزی از آن استفاده می‌کند، فقط مجوزهای لازم را تیک بزنید و انقضایی برگزینید. کلید یک بار، پس از ساخته شدن، نمایش داده می‌شود؛ پتال و Bee فقط درهم‌سازی آن را نگه می‌دارند. پیش از بستن پنجره آن را در مخزن رازهایتان رونوشت کنید.

همین کار از راه API، با توکن نشست:

curl -s -X POST $BEE/api/v1/api-keys \
  -H "Authorization: Bearer $SESSION_TOKEN" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: mint-media-service-key-1" \
  -d '{"name":"media-service","permissions":["bee:write"],"expiresAt":"2027-08-24T00:00:00Z"}'
{
  "id": "0198c0de-…",
  "name": "media-service",
  "keyPrefix": "petal_ak_0198c0de…",
  "permissions": ["bee:write"],
  "plaintextKey": "petal_ak_0198c0de00007000800000000000abcd_Kj3…43 characters…",
  "createdAt": "2026-08-24T12:00:00+00:00",
  "expiresAt": "2027-08-24T00:00:00+00:00"
}

expiresAt اختیاری است؛ برای کلیدی که تا ابطال زنده بماند آن را حذف کنید.

قالب

petal_ak_<۳۲ نویسهٔ هگز: شناسهٔ کلید>_<۴۳ نویسه: راز>

شناسه داخل خودِ کلید است، پس Bee آن را مستقیم پیدا می‌کند و یک درهم‌سازی را مقایسه می‌کند. بخش پیش از آخرین _ همان keyPrefix است که پتال فهرست می‌کند، و با آن می‌توان کلیدی را که در یک فایل پیکربندی پیدا می‌کنید به ردیفش رساند. پویشگرهای راز می‌توانند کلید نشت‌کرده را با petal_(ak|zk)_[0-9a-f]{32}_[A-Za-z0-9_-]{43} بیابند.

فرستادن

هر کدام از این دو روی هر فراخوان سطح کنترل کار می‌کند، هر جا که مستندات می‌گوید «توکن bearer یا کلید API»:

# Standard: the same header a session token uses
curl -s $BEE/api/v1/storage-zones -H "Authorization: Bearer $PETAL_API_KEY"

# bunny.net-style alias, for code written against bunny's APIs
curl -s $BEE/api/v1/storage-zones -H "AccessKey: $PETAL_API_KEY"

مرجع تعاملی در /scalar/ کلید را همان‌طور که هست در فیلد bearer می‌پذیرد.

کلید چه می‌تواند و چه نمی‌تواند

  • مجوزهایی را دارد که با آن ساخته شده، محدود به آنچه حساب امروز دارد. کلید هرگز حساب را گسترده‌تر نمی‌کند: درخواست مجوزی که حساب به آن نمی‌رسد در اعتبارسنجی روی permissions رد می‌شود، و اگر حساب بعداً نقشی را از دست بدهد، کلیدهایش در درخواست بعدی مجوزهای متناظر را از دست می‌دهند.
  • پس از ابطال، پس از expiresAt، در مدت قفل‌شدن حساب و تا وقتی حساب تغییر گذرواژه بدهکار است، با همان 401 کلید اشتباه رد می‌شود. اینکه کدام‌یک بوده در سرور ثبت می‌شود و هرگز به فراخوان گفته نمی‌شود.
  • نمی‌تواند کلیدهای API را فهرست کند، بسازد، بچرخاند یا باطل کند، و نمی‌تواند گذرواژه یا نمایه را تغییر دهد. آن‌ها به کلید 403 می‌دهند. پس کلید نشت‌کرده به آنچه به آن داده شده محدود است و صاحبش همیشه می‌تواند باطلش کند.
  • GET /api/v1/auth/me مقدار "authMethod": "apiKey" و مجوزهای مؤثر کلید را گزارش می‌دهد.
  • هر حساب حداکثر ۲۵ کلید فعال دارد.

چرخش، ابطال، ممیزی

# A new secret under the same name, permissions and expiry. The old one dies in the same write.
curl -s -X POST $BEE/api/v1/api-keys/$KEY_ID/rotate -H "Authorization: Bearer $SESSION_TOKEN"

# Dead on the next request. Irreversible.
curl -s -X DELETE $BEE/api/v1/api-keys/$KEY_ID -H "Authorization: Bearer $SESSION_TOKEN"

# Everything you have minted, active first. Never the secret.
curl -s $BEE/api/v1/api-keys -H "Authorization: Bearer $SESSION_TOKEN"

چرخش بازهٔ هم‌پوشانی ندارد: کلید حساب پیکربندی یک فرایند است و خودتان آن را دوباره مستقر می‌کنید، پس راز تازه را همان لحظه مستقر کنید. هر ساخت، چرخش و ابطال با نام، شناسه و مجوزهای کلید در گزارش ممیزی (بخش ۱۲) نوشته می‌شود.

راز نگهش دارید

با کلید مانند گذرواژه رفتار کنید: متغیر محیطی یا مدیر رازها، هرگز در کنترل نسخه، هرگز در بستهٔ سمت کاربر، هرگز در یک خط لاگ. اگر نشت کرد، در پتال باطلش کنید؛ درخواست بعدی با آن 401 می‌گیرد.

۵. نشست‌ها: ورود و تازه‌سازی

پتال این کار را برایتان می‌کند. فقط برای یک ابزار تعاملی خودتان انجامش دهید.

curl -s -X POST $BEE/api/v1/auth/login \
  -H "Content-Type: application/json" \
  -d '{"email":"[email protected]","password":"<password>"}'
{
  "accessToken": "eyJ...",
  "tokenType": "Bearer",
  "expiresInSeconds": 900,
  "accessTokenExpiresAt": "2026-08-13T00:15:00+00:00",
  "refreshToken": "CfDJ8..."
}

توکن‌های دسترسی ۱۵ دقیقه زنده‌اند. توکن‌های تازه‌سازی یک‌بارمصرف و چرخشی‌اند:

curl -s -X POST $BEE/api/v1/auth/refresh \
  -H "Content-Type: application/json" \
  -d '{"refreshToken":"CfDJ8..."}'

پاسخ یک جفت تازه است. توکن تازه‌سازی‌ای را که برمی‌گردد نگه دارید؛ ارائهٔ توکنِ پیش‌تر مصرف‌شده نشت تلقی می‌شود و کل زنجیره را باطل می‌کند. فقط وقتی تازه‌سازی کنید که یک 401 با WWW-Authenticate: Bearer error="invalid_token" و توضیح expired بیاید. تازه‌سازی در پاسخ به یک 401 یا 403 دائمی می‌تواند کاربر را از همه‌جا بیرون بیندازد.

۶. قراردادهایی که همه‌جا برقرارند

  • ساخت‌ها 201 با سربرگ Location و {"id":"<guid>"} برمی‌گردانند، نه خودِ منبع را. اگر لازمش دارید جداگانه بگیرید. به‌روزرسانی و حذف 204 بی‌بدنه برمی‌گردانند.
  • خطاها سند مشکل RFC 9457 هستند (application/problem+json) با type، title، status، detail و یک traceId که هنگام گزارش اشکال نقل می‌کنید. خطاهای اعتبارسنجی errors را با کلید فیلد می‌افزایند: {"name":["'Name' must not be empty."]}. استثنا: سطح تحویل (/cdn/**، /img/**) کد وضعیت خالی بی‌بدنه برمی‌گرداند، چون مصرف‌کننده‌هایش برچسب <img> و پخش‌کننده‌های ویدئو هستند.
  • فهرست‌ها با نشانگر صفحه‌بندی می‌شوند، قدیمی‌ترین اول. limit (پیش‌فرض ۵۰، محدود به ۱..۲۰۰) و nextCursor صفحهٔ قبل را بفرستید. وقتی hasMore برابر false شد بایستید. نشانگرها مبهم‌اند؛ هرگز نسازید یا تجزیه نکنید.
  • هم‌توانی: نقاط پایانی‌ای که می‌گویند، سربرگ Idempotency-Key می‌پذیرند (هر رشته تا ۱۲۸ نویسه). تکرار همان کلید پاسخ اصلی را بی‌تکرار کار برمی‌گرداند؛ تکرار در حالی که درخواست نخست هنوز در حال اجراست 409 می‌دهد. برای هر قصد کاربر یک کلید و در تلاش‌های مجدد شبکه همان را به کار ببرید.
  • شمارشی‌ها رشته‌های camelCase هستند ("ignoreAll")، زمان‌ها RFC 3339 به وقت UTC، شناسه‌ها UUID، و هر فیلدی که به Bytes، Seconds، Kbps یا Percent ختم شود در همان یکاست.

۷. ناحیه‌های ذخیره‌سازی و کلیدهای دسترسی ناحیه

ساخت ناحیه

curl -s -X POST $BEE/api/v1/storage-zones \
  -H "Authorization: Bearer $PETAL_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: create-assets-zone-1" \
  -d '{
    "name": "my-assets",
    "primaryRegion": "eu",
    "tier": "standard",
    "quotaBytes": 10737418240,
    "replicationRegions": []
  }'
# 201 → {"id":"<storageZoneId>"}

tier یکی از standard یا performance است. کد ناحیهٔ جغرافیایی رشته‌ای کوتاه با حروف کوچک است (۲ تا ۸ نویسه، مثلاً eu، us، apac). آماده‌سازی ناهمگام است: ناحیه را بپرسید تا state به ready برسد. quotaBytes: 0 یعنی بدون سهمیه، و سهمیهٔ خودِ حساب روی آن اعمال می‌شود.

صدور کلید دسترسی ناحیه

سطح داده با کلیدی محدود به یک ناحیه احراز هویت می‌کند، نه با کلید حساب شما. به bee:admin نیاز دارد.

curl -s -X POST $BEE/api/v1/storage-zones/$ZONE_ID/keys \
  -H "Authorization: Bearer $PETAL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name":"backup-script","scope":"readOnly"}'
{
  "id": "…",
  "name": "backup-script",
  "scope": "readOnly",
  "keyPrefix": "petal_zk_…",
  "plaintextKey": "petal_zk_<32 hex>_<43 characters>",
  "issuedAt": "…"
}

هم name و هم scope لازم‌اند: نام است که کلیدهای چرخانده‌شده را از هم جدا می‌کند، و دامنهٔ حذف‌شده رد می‌شود نه اینکه به خواندن‌ونوشتن پیش‌فرض شود. plaintextKey دقیقاً یک بار نمایش داده می‌شود. هر جا فقط دریافت لازم است readOnly را ترجیح دهید؛ کلید رازی حامل برای کل ناحیه است. با POST .../keys/{id}/rotate بچرخانید (کلید قدیمی بی‌درنگ از کار می‌افتد) و با DELETE .../keys/{id} باطل کنید. صدور، چرخش و ابطال ممیزی می‌شوند.

کلیدی که پیش از حمل شناسه صادر شده keyPrefix ندارد و بی‌تغییر کار می‌کند.

۸. سطح دادهٔ ذخیره‌سازی: بارگذاری، دریافت، فهرست، حذف

کلید ناحیه را در X-Bee-Access-Key بفرستید، یا اگر کدتان برای API ذخیره‌سازی bunny.net نوشته شده، در AccessKey. درخواست‌ها برای هر کلید محدود می‌شوند (۴٬۰۰۰ درخواست انفجاری، با پرشدن ۲٬۰۰۰ در ثانیه)؛ مازاد 429 می‌گیرد.

بارگذاری

بایت‌های خام در بدنه، نه چندبخشی. مسیر پس از نام ناحیه می‌تواند / داشته باشد:

curl -s -X PUT "$BEE/storage/my-assets/photos/111.jpg" \
  -H "X-Bee-Access-Key: $ZONE_KEY" \
  -H "Content-Type: image/jpeg" \
  --data-binary @111.jpg
# 201 with a Location header and the stored object's metadata

می‌توانید SHA-256 مورد انتظار را به صورت هگز در X-Bee-Content-Sha256 بفرستید؛ ناهم‌خوانی بارگذاری را رد می‌کند (422) به جای ذخیرهٔ بایت‌های خراب. فراتر از سهمیهٔ حساب 507 است؛ فراتر از حد اندازهٔ درخواست 413. کلید readWrite لازم است.

دریافت

curl -s "$BEE/storage/my-assets/photos/111.jpg" -H "X-Bee-Access-Key: $ZONE_KEY" -o 111.jpg

Range پشتیبانی می‌شود (206، Content-Range، 416 فراتر از انتها)، If-None-Match پاسخ 304 می‌دهد، و Accept-Ranges: bytes همیشه فرستاده می‌شود. با کلید readOnly کار می‌کند.

فهرست

مسیری که به / ختم شود، یا ریشهٔ ناحیه، به جای دریافت فهرست می‌دهد. همان نمای پوشه‌ای است که سطح کنترل ارائه می‌کند، فقط با کلید ناحیه، پس اسکریپتی که بارگذاری می‌کند می‌تواند بدون اعتبارنامهٔ حساب ببیند چه بارگذاری کرده:

# The zone root
curl -s "$BEE/storage/my-assets/" -H "X-Bee-Access-Key: $ZONE_KEY"

# One level down, paged
curl -s "$BEE/storage/my-assets/photos/?limit=50" -H "X-Bee-Access-Key: $ZONE_KEY"
{
  "items": [
    { "path": "photos/2026/", "name": "2026", "isDirectory": true, "object": null },
    { "path": "photos/111.jpg", "name": "111.jpg", "isDirectory": false, "object": { "sizeInBytes": 48213, "contentType": "image/jpeg", "…": "…" } }
  ],
  "nextCursor": null,
  "hasMore": false
}

پوشه‌ها از / در مسیر اشیا استنتاج می‌شوند و object ندارند؛ با درخواست path پوشه پایین‌تر بروید. با cursor صفحه بزنید تا hasMore برابر false شود. شکل سطح کنترل، GET /api/v1/storage-zones/{id}/objects?prefix=photos/، همین ساختار را با اعتبارنامهٔ حساب برمی‌گرداند.

حذف

# One object
curl -s -X DELETE "$BEE/storage/my-assets/photos/111.jpg" -H "X-Bee-Access-Key: $ZONE_KEY"

# Everything under a prefix, recursively
curl -s -X DELETE "$BEE/storage/my-assets/photos/" -H "X-Bee-Access-Key: $ZONE_KEY"

اسلش پایانی تمامِ قرارداد است. هر دو 204 برمی‌گردانند، پس یک اسلش اضافی بسیار بیش از آنچه می‌خواستید حذف می‌کند و پاسخ متفاوتی هم نمی‌دهد. حذف دائمی است.

۹. بارگذاری ازسرگیری‌پذیر

سطح بارگذاری به سبک tus است: نشست باز کنید، تکه‌ها را در آفستی که سرور گزارش می‌دهد PATCH کنید و تمام کنید. قطع اتصال فقط تکهٔ جاری را از دست می‌دهد. نشست‌ها چهار ساعت پس از ساخت منقضی می‌شوند. همهٔ فراخوان‌ها اعتبارنامهٔ حساب می‌گیرند.

# 1. Open a session: for a storage object send storageZoneId + path
#    (for a video source, send videoLibraryId + videoId instead)
curl -s -i -X POST $BEE/upload/sessions \
  -H "Authorization: Bearer $PETAL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "storageZoneId": "<storageZoneId>",
    "path": "backups/big.zip",
    "totalLength": 104857600,
    "contentType": "application/zip"
  }'
# 201 + Location: /upload/sessions/<sessionId>, Tus-Resumable: 1.0.0

# 2. Send chunks at the current offset
curl -s -i -X PATCH $BEE/upload/sessions/$SESSION \
  -H "Authorization: Bearer $PETAL_API_KEY" \
  -H "Tus-Resumable: 1.0.0" \
  -H "Upload-Offset: 0" \
  -H "Content-Type: application/offset+octet-stream" \
  --data-binary @chunk-0
# Response carries the new Upload-Offset

# 3. After an interruption, ask where to resume
curl -s -I $BEE/upload/sessions/$SESSION -H "Authorization: Bearer $PETAL_API_KEY"
# Upload-Offset: <bytes received so far>

# 4. Finish (or DELETE to cancel and discard)
curl -s -X POST $BEE/upload/sessions/$SESSION/complete -H "Authorization: Bearer $PETAL_API_KEY"

۱۰. CDN: ناحیه‌های pull، حافظهٔ نهان، پاک‌سازی، قواعد لبه، URLهای امضاشده

قرار دادن ناحیهٔ pull جلوی ناحیهٔ ذخیره‌سازی

curl -s -X POST $BEE/api/v1/pull-zones \
  -H "Authorization: Bearer $PETAL_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: create-assets-cdn-1" \
  -d '{
    "name": "my-assets-cdn",
    "originType": "storageZone",
    "originStorageZoneId": "<storageZoneId>"
  }'

originType فیلد همراه لازم را تعیین می‌کند: http به originUrl نیاز دارد؛ storageZone و videoLibrary هر دو شناسهٔ پشتیبان را در originStorageZoneId می‌فرستند. آماده‌سازی ناهمگام است؛ ناحیه را بپرسید تا state به ready برسد. سپس هر کسی بی‌سربرگ احراز هویت می‌تواند بگیرد:

curl -s "$BEE/cdn/my-assets-cdn/photos/111.jpg" -o photo.jpg
# X-Bee-Cache: MISS | HIT | STALE | BYPASS | REVALIDATED

سیاست حافظهٔ نهان

curl -s -X PUT $BEE/api/v1/pull-zones/$PZ/cache \
  -H "Authorization: Bearer $PETAL_API_KEY" -H "Content-Type: application/json" \
  -d '{
    "edgeTtlSeconds": 3600,
    "browserTtlSeconds": 300,
    "respectOriginCacheControl": true,
    "queryStringMode": "ignoreAll",
    "queryStringParameters": [],
    "vary": "none",
    "cacheErrorResponses": false
  }'

queryStringMode یکی از includeAll، ignoreAll یا includeListed است (با queryStringParameters برای نام بردن فهرست). vary رشته‌ای جداشده با ویرگول از ابعاد است ("cookie, acceptEncoding" یا "none").

پاک‌سازی

# One URL, a prefix, or the whole zone
curl -s -X POST $BEE/api/v1/pull-zones/$PZ/purge \
  -H "Authorization: Bearer $PETAL_API_KEY" -H "Content-Type: application/json" \
  -d '{"scope":"prefix","target":"photos/"}'

به bee:purge نیاز دارد. پاک‌سازی ناهمگام است و در نسخه‌های تکراری منتشر می‌شود؛ لبه‌ها ممکن است کمی نسخهٔ قدیمی را بدهند.

نام‌های میزبان سفارشی

هر ناحیه با یک نام میزبان سیستمی زیر دامنهٔ تحویل Bee متولد می‌شود تا بی‌درنگ سرویس بدهد. نام خودتان را این‌گونه بیفزایید:

curl -s -X POST $BEE/api/v1/pull-zones/$PZ/hostnames \
  -H "Authorization: Bearer $PETAL_API_KEY" -H "Content-Type: application/json" \
  -d '{"hostname":"cdn.example.com"}'

قواعد لبه

قواعد برای هر درخواست به ترتیب اجرا می‌شوند؛ مسدودسازی یا تغییر مسیر ارزیابی را پایان می‌دهد. کنش‌ها: forceHttps، redirect، block، setRequestHeader، setResponseHeader، overrideCacheTime، overrideOrigin، bypassCache، disableTokenAuthentication، enableTokenAuthentication. راه‌اندازها روی url، requestHeader، responseHeader، fileExtension، countryCode، remoteIp، queryString، httpMethod یا hostname منطبق می‌شوند.

# Block hotlinking of zip files: create (edgeRuleId: null) or replace (edgeRuleId set)
curl -s -X PUT $BEE/api/v1/pull-zones/$PZ/edge-rules \
  -H "Authorization: Bearer $PETAL_API_KEY" -H "Content-Type: application/json" \
  -d '{
    "edgeRuleId": null,
    "description": "No zip downloads",
    "actionKind": "block",
    "actionParameter1": null, "actionParameter2": null, "actionParameter3": null,
    "triggerMatchMode": "all",
    "triggers": [
      { "kind": "fileExtension", "matchMode": "any", "patterns": ["zip"], "parameter": null }
    ]
  }'

با PUT .../edge-rules/order بازچینی کنید، با PATCH .../edge-rules/{id} ({"isEnabled":false}) خاموش و روشن کنید، با DELETE بردارید.

URLهای امضاشده

روی ناحیه توکن را لازم کنید (PUT .../security با "signedUrlsRequired": true)، سپس URLها را سمت سرور بسازید:

curl -s -X POST $BEE/api/v1/signed-urls \
  -H "Authorization: Bearer $PETAL_API_KEY" -H "Content-Type: application/json" \
  -d '{
    "pullZoneId": "<pullZoneId>",
    "path": "photos/111.jpg",
    "expiresAt": "2026-08-14T00:00:00+00:00",
    "isPrefixToken": false,
    "clientIpAddress": null,
    "allowedCountryCodes": [],
    "blockedCountryCodes": [],
    "maxBytesPerSecond": null
  }'
# → { "url": "https://<zone-hostname>/photos/111.jpg?expires=1786665600&token=...", "expiresAt": "..." }

URL روی نخستین نام میزبان ناحیه ساخته می‌شود، یعنی <zoneName>.<دامنهٔ تحویل> سیستمی مگر نام سفارشی افزوده شده باشد. توکن مسیر را امضا می‌کند (یا با isPrefixToken پیشوند را، که پخش HLS به آن نیاز دارد)، پس مسیر باید همان‌طور که برگشته به کار رود؛ رشتهٔ پرس‌وجو بخشی از امضا نیست، و پارامترهای خودِ URL امضاشده (token، expires، ca، cb، rt) را می‌توان زیر یک توکن پیشوندی روی مسیرهای هم‌سطح رونوشت کرد. کلید امضای ناحیه را با POST .../signing-key/rotate بچرخانید؛ URLهای موجود بی‌درنگ می‌میرند.

قواعد ارجاع‌دهنده، IP و کشور

PUT /api/v1/pull-zones/{id}/security کل پیکربندی امنیتی را جایگزین می‌کند: signedUrlsRequired، forceHttps، blockRootPathAccess، allowedReferrers، blockedReferrers، blockedIpAddresses، allowedCountryCodes، blockedCountryCodes. همه در لبه اعمال می‌شوند؛ درخواست مسدودشده یک 403 خالی است.

۱۱. تصویر و ویدئو

تبدیل تصویر

تبدیل‌ها فقط روی سطح /img/ رخ می‌دهند. نشانی تحویل /cdn/ تصویر را بگیرید، از Bee بخواهید نشانی تبدیل را بسازد، و آنچه برمی‌گردد به کار ببرید:

curl -s -X POST $BEE/api/v1/images/url \
  -H "Authorization: Bearer $PETAL_API_KEY" -H "Content-Type: application/json" \
  -d '{
    "sourceUrl": "'"$BEE"'/cdn/my-assets-cdn/photos/111.jpg",
    "transformation": { "width": 300, "height": 200, "fit": "contain" }
  }'
# → "http://localhost:18080/img/my-assets-cdn/photos/111.jpg?width=300&height=200&fit=contain"

سازنده ترتیب پارامترها را استاندارد می‌کند، پس تبدیل‌های یکسان یک ورودی حافظهٔ نهان دارند؛ آن را به ساخت دستی نشانی ترجیح دهید. POST /api/v1/images/srcset همین کار را در چند عرض می‌کند و مواردی آماده برای <img srcset> برمی‌گرداند.

پارامترمقادیریادداشت
width، heightپیکسلهرگز بزرگ‌نمایی نمی‌کند مگر با upscale=1
fitcontain، cover، fillپیش‌فرض cover
aspect_ratioمثلاً 16:9با یک بُعد
cropw,hgravity ناحیه را برمی‌گزیند: center، north، southEast، …
crop_offsetx,yگوشهٔ بالا-چپ صریح، جایگزین gravity
formatjpeg، png، webp، avif، gifحذف کنید تا قالب منبع بماند
quality۱ تا ۱۰۰
brightness، contrast، saturation، hue، gammaعددتنظیم رنگ
blur، sharpenعدد
sepia، flip، flop1flip عمودی، flop افقی
rotate90، 180، 270درجه، ساعت‌گرد

پاسخ X-Bee-Image-Size: <width>x<height> را همراه دارد.

ویدئو

# A library needs a storage zone to hold sources and renditions
curl -s -X POST $BEE/api/v1/video-libraries \
  -H "Authorization: Bearer $PETAL_API_KEY" -H "Content-Type: application/json" \
  -d '{
    "name": "tutorials",
    "storageZoneId": "<storageZoneId>",
    "encodingTier": "standard",
    "encodingLadder": ["p360", "p720", "p1080"]
  }'

curl -s -X POST $BEE/api/v1/video-libraries/$LIB/videos \
  -H "Authorization: Bearer $PETAL_API_KEY" -H "Content-Type: application/json" \
  -d '{"title":"Getting started","videoCollectionId":null}'
# → {"id":"<videoId>"}

# Upload the source: raw body in one request for small files
curl -s -X PUT $BEE/api/v1/video-libraries/$LIB/videos/$VID/source \
  -H "Authorization: Bearer $PETAL_API_KEY" \
  -H "Content-Type: application/octet-stream" \
  -H "X-Bee-File-Name: intro.mp4" \
  --data-binary @intro.mp4
# 202 Accepted: encoding runs in the background

فایل‌های بزرگ از نشست بارگذاری (بخش ۹) با videoLibraryId + videoId استفاده می‌کنند، یا بگذارید Bee آن را با POST .../videos/{id}/fetch و {"sourceUrl":"https://..."} بکشد. GET .../videos/{id} را بپرسید: state مسیر created → uploading → queued → processing → ready | failed را می‌رود، encodeProgressPercent پیش می‌رود، و در حالت آماده playbackPath تنظیم می‌شود.

پخش از راه ناحیهٔ pullای می‌گذرد که مبدأش کتابخانه است ("originType": "videoLibrary"، "originStorageZoneId": "<videoLibraryId>"). playbackPath ({videoId}/playlist.m3u8) نسبت به ریشهٔ آن ناحیه است و یک master استاندارد HLS است، با زیرنویس‌ها به صورت گروه subtitle، {videoId}/thumbnail.jpg، {videoId}/storyboard.vtt برای پیش‌نمایش نوار پیمایش و {videoId}/embed.json برای فرادادهٔ پخش‌کننده. پاسخ‌های تحویل ویدئو همیشه Access-Control-Allow-Origin: * دارند. ساده‌ترین راه پخش، پخش‌کنندهٔ جاسازی‌شدنی خودِ پتال است: کد جاسازی را از صفحهٔ ویدئو رونوشت کنید.

زیرنویس: PUT .../videos/{id}/captions/{lang} با {"label","format":"webVtt","content"} تِرک آن زبان را جایگزین می‌کند؛ DELETE آن را برمی‌دارد. POST .../reencode کدگذاری را از منبع ذخیره‌شده دوباره اجرا می‌کند.

۱۲. مدیریت، گزارش ممیزی و آمار

مدیران (bee:admin) حساب‌ها را با GET /api/v1/users، PUT /api/v1/users/{id}/roles ({"roles":["Operator"]}) و POST /api/v1/users/{id}/lock / unlock مدیریت می‌کنند. کلیدهای API یک حساب قفل‌شده تا پایان قفل رد می‌شوند.

گزارش ممیزی فقط‌افزودنی است و ثبت می‌کند چه کسی، چه کاری، کِی و از کجا کرده:

curl -s "$BEE/api/v1/admin/audit-log?limit=50" -H "Authorization: Bearer $PETAL_API_KEY"

هر ساخت، چرخش و ابطال کلید API (apiKey.*، با نام، شناسه و مجوزهای کلید)، هر صدور، چرخش و ابطال کلید ناحیه (zoneKey.*، با ناحیه و دامنه) و پاک‌سازی‌های مدیریتی را دارد. پس از گمان به نشت، نخستین جایی است که باید نگاه کرد.

آمار:

# Delivery traffic, all zones or one; granularity is hourly, daily or monthly
curl -s "$BEE/api/v1/statistics/delivery?from=2026-08-01T00:00:00Z&to=2026-08-13T00:00:00Z&granularity=daily&pullZoneId=$PZ" \
  -H "Authorization: Bearer $PETAL_API_KEY"

# Storage consumption and transfer for a zone
curl -s $BEE/api/v1/storage-zones/$ZONE_ID/usage -H "Authorization: Bearer $PETAL_API_KEY"

# Per-video playback: views and bytes sent (watch time is reserved and always 0)
curl -s "$BEE/api/v1/video-libraries/$LIB/videos/$VID/statistics?from=...&to=..." \
  -H "Authorization: Bearer $PETAL_API_KEY"

۱۳. سر تا ته: بارگذاری یک فایل و گرفتن نشانی CDN آن، در Node و C#‎

مسیر: با کلید API حساب، ناحیهٔ ذخیره‌سازی و ناحیهٔ pull جلوی آن را پیدا کنید (یا بسازید)، کلید ناحیه صادر کنید، فایلی را روی سطح داده بارگذاری کنید، پوشه را فهرست کنید و نشانی عمومی را چاپ کنید. هر دو نمونه PETAL_API_KEY و BEE را در محیط و ناحیه‌ای به نام my-assets با ناحیهٔ pull به نام my-assets-cdn را فرض می‌کنند.

Node (بدون وابستگی)

// upload.mjs — node upload.mjs ./photo.jpg photos/photo.jpg
import { readFile } from "node:fs/promises";

const BEE = process.env.BEE ?? "http://localhost:18080";
const headers = { Authorization: `Bearer ${process.env.PETAL_API_KEY}` };

async function api(path, init = {}) {
  const response = await fetch(`${BEE}${path}`, { ...init, headers: { ...headers, ...init.headers } });
  if (!response.ok) {
    // Every control-plane failure is a problem document with a traceId worth logging.
    throw new Error(`${init.method ?? "GET"} ${path} → ${response.status}: ${await response.text()}`);
  }
  return response.status === 204 ? null : response.json();
}

const [file, objectPath] = process.argv.slice(2);

// 1. The zone, by name.
const zones = await api("/api/v1/storage-zones?limit=200");
const zone = zones.items.find((candidate) => candidate.name === "my-assets");
if (!zone) throw new Error("Create the my-assets zone in Petal first.");

// 2. A zone key for the data plane. Needs bee:admin on the account key; keep it after this.
const issued = await api(`/api/v1/storage-zones/${zone.id}/keys`, {
  method: "POST",
  headers: { "Content-Type": "application/json", "Idempotency-Key": `upload-key-${zone.id}` },
  body: JSON.stringify({ name: "upload.mjs", scope: "readWrite" }),
});
const zoneKey = issued.plaintextKey;

// 3. Upload: raw bytes, PUT, on the data plane with the zone key.
const bytes = await readFile(file);
const upload = await fetch(`${BEE}/storage/${zone.name}/${objectPath}`, {
  method: "PUT",
  headers: { "X-Bee-Access-Key": zoneKey, "Content-Type": "image/jpeg" },
  body: bytes,
});
if (upload.status !== 201) throw new Error(`upload → ${upload.status}`);

// 4. See it listed, with the same key.
const folder = objectPath.slice(0, objectPath.lastIndexOf("/") + 1);
const listing = await fetch(`${BEE}/storage/${zone.name}/${folder}`, {
  headers: { "X-Bee-Access-Key": zoneKey },
}).then((response) => response.json());
console.log(listing.items.map((entry) => entry.path));

// 5. The public URL through the pull zone.
console.log(`${BEE}/cdn/my-assets-cdn/${objectPath}`);

C#‎ (HttpClient، ‏.NET 8 به بالا)

// dotnet run -- ./photo.jpg photos/photo.jpg
using System.Net.Http.Headers;
using System.Net.Http.Json;
using System.Text.Json;

string bee = Environment.GetEnvironmentVariable("BEE") ?? "http://localhost:18080";
string apiKey = Environment.GetEnvironmentVariable("PETAL_API_KEY")
    ?? throw new InvalidOperationException("Set PETAL_API_KEY.");
string file = args[0];
string objectPath = args[1];

using HttpClient control = new() { BaseAddress = new Uri(bee) };
control.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", apiKey);

// 1. The zone, by name.
JsonElement zones = await control.GetFromJsonAsync<JsonElement>("/api/v1/storage-zones?limit=200");
JsonElement zone = zones.GetProperty("items").EnumerateArray()
    .First(candidate => candidate.GetProperty("name").GetString() == "my-assets");
string zoneId = zone.GetProperty("id").GetString()!;

// 2. A zone key for the data plane. Keep it after this; it is shown once.
using HttpRequestMessage mint = new(HttpMethod.Post, $"/api/v1/storage-zones/{zoneId}/keys")
{
    Content = JsonContent.Create(new { name = "uploader", scope = "readWrite" }),
};
mint.Headers.Add("Idempotency-Key", $"upload-key-{zoneId}");
HttpResponseMessage minted = await control.SendAsync(mint);
minted.EnsureSuccessStatusCode();
string zoneKey = (await minted.Content.ReadFromJsonAsync<JsonElement>()).GetProperty("plaintextKey").GetString()!;

// 3. Upload: raw bytes, PUT, on the data plane with the zone key.
using HttpClient data = new() { BaseAddress = new Uri(bee) };
data.DefaultRequestHeaders.Add("X-Bee-Access-Key", zoneKey);

using ByteArrayContent body = new(await File.ReadAllBytesAsync(file));
body.Headers.ContentType = new MediaTypeHeaderValue("image/jpeg");
HttpResponseMessage upload = await data.PutAsync($"/storage/my-assets/{objectPath}", body);
if (upload.StatusCode != System.Net.HttpStatusCode.Created)
{
    // The body is an RFC 9457 problem document; its traceId links to the server-side trace.
    throw new InvalidOperationException($"Upload failed: {(int)upload.StatusCode} {await upload.Content.ReadAsStringAsync()}");
}

// 4. See it listed, with the same key.
string folder = objectPath[..(objectPath.LastIndexOf('/') + 1)];
JsonElement listing = await data.GetFromJsonAsync<JsonElement>($"/storage/my-assets/{folder}");
foreach (JsonElement entry in listing.GetProperty("items").EnumerateArray())
{
    Console.WriteLine(entry.GetProperty("path").GetString());
}

// 5. The public URL through the pull zone.
Console.WriteLine($"{bee}/cdn/my-assets-cdn/{objectPath}");

۱۴. عیب‌یابی

وضعیتمعناچه کنید
400 + errorsخطای اعتبارسنجیفیلدهای نام‌برده را درست کنید؛ پیام‌ها با نام ویژگی کلید خورده‌اند. permissions هنگام ساخت کلید یعنی بیش از آنچه حساب دارد خواسته‌اید.
401، چالش خالی Bearerاعتبارنامه‌ای نیستکلید بفرستید یا وارد شوید
401، invalid_tokenکلید اشتباه، باطل، منقضی است، یا حسابش قفل است یا تغییر گذرواژه بدهکار است؛ یا توکن نشست منقضی شدهبرای کلید: پتال ← حساب را ببینید، بسازید یا بچرخانید. برای نشست: یک بار تازه‌سازی کنید
403اعتبارنامه معتبر است، مجوز نیست؛ یا کلید روی عملیاتی که فقط نشست می‌پذیردتکرار کمکی نمی‌کند. کلیدی با مجوز درست بسازید، یا برای مدیریت کلید و گذرواژه از نشست استفاده کنید
404پیدا نشد؛ منبع حساب دیگری؛ یا روی /cdn هر محتوای ردشده یا غایبسطح تحویل هرگز خودش را توضیح نمی‌دهد
409نام تکراری، کلید هم‌توانی هنوز در اجرا، ۲۵ کلید فعال، یا چرخش کلید مردهنام دیگری بگذارید، صبر کنید و همان کلید را تکرار کنید، نخست کلیدی را باطل کنید
413 / 422 / 507بارگذاری خیلی بزرگ / ناهم‌خوانی checksum / فراتر از سهمیهٔ حساببارگذاری ازسرگیری‌پذیر؛ دوباره بفرستید؛ از مدیر سهمیه بخواهید
429محدودیت نرخعقب بنشینید. Retry-After روی سطح کنترل و ذخیره‌سازی فرستاده می‌شود، روی تحویل نه

هر خطای سطح کنترل و سطح ذخیره‌سازی یک سند مشکل با traceId است: هنگام گزارش مشکل آن را نقل کنید، پاسخ را به رد سمت سرور پیوند می‌دهد. خواندن WWW-Authenticate به کلاینت نشست می‌گوید تازه‌سازی می‌ارزد یا نه؛ کلید هرگز ارزش تازه‌سازی ندارد.

محدودیت نرخ. تحویل: ۳۰٬۰۰۰ درخواست در ثانیه برای هر نشانی کلاینت، انفجاری تا ۶۰٬۰۰۰. سطح کنترل: ۱۲٬۰۰۰ در دقیقه برای هر حساب. سطح دادهٔ ذخیره‌سازی: ۲٬۰۰۰ در ثانیه برای هر کلید ناحیه، انفجاری تا ۴٬۰۰۰. نقاط پایانی اعتبارنامه (/auth/*): ۱٬۰۰۰ در دقیقه برای هر نشانی.

زمان انتظار و تلاش مجدد. GET، HEAD، PUT و DELETE را می‌توان بی‌خطر تکرار کرد. POST را فقط با Idempotency-Key تکرار کنید.

۱۵. مرجع سریع

اعتبارنامهسربرگقالبکجا کار می‌کند
کلید API حسابAuthorization: Bearer <key> یا AccessKey: <key>petal_ak_<32 hex>_<43 chars>/api/v1/**، /upload/sessions/**، جز مدیریت کلید و گذرواژه
توکن نشستAuthorization: Bearer <jwt>JWT، ۱۵ دقیقههمه‌چیز روی سطح کنترل
کلید دسترسی ناحیهX-Bee-Access-Key: <key> یا AccessKey: <key>petal_zk_<32 hex>_<43 chars>فقط /storage/{zone}/**
URL امضاشدهرشتهٔ پرس‌وجو?token=…&expires=…/cdn/**، /img/** روی ناحیه‌هایی که لازمش دارند
مجوزبه کلید اجازه می‌دهد…
bee:readخواندن هر منبعی که حساب دارد
bee:writeساخت و تغییر ناحیه‌ها، ناحیه‌های pull، قواعد، کتابخانه‌ها، ویدئوها؛ خواندن، پاک‌سازی و ویدئو را در بر دارد
bee:purgeپاک‌سازی حافظهٔ نهان CDN
bee:streamمدیریت کتابخانه‌ها و ویدئوها
bee:adminهمه‌چیز، از جمله کلیدهای ناحیه، کاربران و گزارش ممیزی
فعل سطح دادهشکل مسیراثر
PUT/storage/{zone}/{path}بارگذاری بایت‌های خام (readWrite)
GET/storage/{zone}/{path}دریافت، با پشتیبانی Range
GET/storage/{zone}/{prefix}/ یا /storage/{zone}/فهرست یک سطح، JSON، صفحه‌بندی‌شده
DELETE/storage/{zone}/{path}حذف یک شیء (readWrite)
DELETE/storage/{zone}/{prefix}/حذف همه‌چیز زیر پیشوند (readWrite)