چگونه از سرویس فایل پتال در سمت سرور برنامهٔ خودتان استفاده کنید: ذخیرهسازی اشیا، تحویل CDN،
تبدیل تصویر در لحظه و پخش ویدئو، همه از راه API سرویس Bee. هر نمونه یک دستور curl آمادهٔ
رونوشت است و بخش ۱۳ همان مسیر را در Node و C# نشان میدهد.
مرجع تعاملی: مسیر /scalar/ روی نمونهٔ در حال اجرا. قرارداد ماشینخوان:
docs/openapi/bee-v1.json (در مخزن نگهداری و بازتولید میشود، هرگز دستی ویرایش نمیشود).
فهرست
- پتال چیست، و چهار سطح API
- حسابها، نقشها و مجوزها
- نشست یا کلید API: کدام اعتبارنامه
- کلیدهای API حساب
- نشستها: ورود و تازهسازی
- قراردادهایی که همهجا برقرارند
- ناحیههای ذخیرهسازی و کلیدهای دسترسی ناحیه
- سطح دادهٔ ذخیرهسازی: بارگذاری، دریافت، فهرست، حذف
- بارگذاری ازسرگیریپذیر
- CDN: ناحیههای pull، حافظهٔ نهان، پاکسازی، قواعد لبه، URLهای امضاشده
- تصویر و ویدئو
- مدیریت، گزارش ممیزی و آمار
- سر تا ته: بارگذاری یک فایل و گرفتن نشانی CDN آن، در Node و C#
- عیبیابی
- مرجع سریع
۱. پتال چیست، و چهار سطح API
پتال برنامهٔ وب است؛ Bee همان API پشت آن. هر کاری که در پتال میشود کرد، مستقیم با Bee هم میشود کرد، و این راهنما دربارهٔ همین است.
| نحوهٔ اجرای Bee | نشانی پایه |
|---|---|
docker compose up | http://localhost:18080 (پورت منتشرشده BEE_API_HOST_PORT است) |
اجرای مستقیم با dotnet run | http://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 بر پایهٔ
مجوزها اجازه میدهد، و نقشها فقط دستهبندی مجوزها هستند:
| نقش | مجوزهای دادهشده |
|---|---|
| Admin | bee:admin |
| Operator | bee:write، bee:purge، bee:stream |
| Viewer | bee:read |
مجوزها سلسلهمراتبیاند، نه یک مجموعهٔ تخت. مستندات هر عملیات مجوز لازم را نام میبرد و مجوزهای بالاتر پایینتر را برآورده میکنند:
| عملیات نیاز دارد به | برآورده میشود با |
|---|---|
bee:read | bee:read، bee:write، bee:admin |
bee:write | bee:write، bee:admin |
bee:purge | bee:purge، bee:write، bee:admin |
bee:stream | bee:stream، bee:write، bee:admin |
bee:admin | bee: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 |
fit | contain، cover، fill | پیشفرض cover |
aspect_ratio | مثلاً 16:9 | با یک بُعد |
crop | w,h | gravity ناحیه را برمیگزیند: center، north، southEast، … |
crop_offset | x,y | گوشهٔ بالا-چپ صریح، جایگزین gravity |
format | jpeg، png، webp، avif، gif | حذف کنید تا قالب منبع بماند |
quality | ۱ تا ۱۰۰ | |
brightness، contrast، saturation، hue، gamma | عدد | تنظیم رنگ |
blur، sharpen | عدد | |
sepia، flip، flop | 1 | flip عمودی، flop افقی |
rotate | 90، 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) |