محدودیت نرخ و خطاهای API
Rate Limit واقعی Public API، Headerهای پاسخ و Statusهای اصلی خطا.
Public API آلفانا برای کنترل حجم درخواستها و حفظ پایداری سرویس از Rate Limit استفاده میکند.
مقدار پیشفرض در نسخه فعلی برابر با ۱۲۰ درخواست در یک پنجره ۶۰ ثانیهای برای هر API Key است.
این مقدار در Backend از طریق تنظیم زیر قابل تغییر است:
PUBLIC_API_REQUESTS_PER_MINUTEبنابراین هنگام ساخت integration بهتر است Client را به عدد ثابت 120 وابسته نکنید و Headerهای Rate Limit برگشتی API را مبنای رفتار خود قرار دهید.
Rate Limit چگونه اعمال میشود؟#
محدودیت نرخ بر اساس Account API Key محاسبه میشود.
در حالت پیشفرض، هر کلید میتواند در یک پنجره ۶۰ ثانیهای تا ۱۲۰ درخواست به Public API ارسال کند.
بهصورت خلاصه:
120 requests
↓
per API Key
↓
60-second windowاگر تعداد درخواستها از ظرفیت مجاز همان پنجره عبور کند، API پاسخ 429 Too Many Requests برمیگرداند.
Client باید در این وضعیت ارسال درخواستهای جدید را موقتاً متوقف کند و زمان مناسب برای تلاش بعدی را از Headerهای پاسخ بخواند.
Headerهای Rate Limit#
پاسخهای Public API اطلاعات مربوط به Rate Limit را از طریق Headerهای زیر در اختیار Client قرار میدهند:
| Header | معنی |
|---|---|
X-RateLimit-Limit | سقف تعداد درخواست مجاز در پنجره فعلی |
X-RateLimit-Remaining | تعداد درخواستهای باقیمانده تا رسیدن به محدودیت |
X-RateLimit-Reset | تعداد ثانیه باقیمانده تا Reset شدن پنجره |
Retry-After | در پاسخ 429، تعداد ثانیهای که بهتر است پیش از تلاش بعدی صبر کنید |
برای integrationهایی که تعداد درخواست بالایی دارند، بهتر است X-RateLimit-Remaining را بررسی کنید و قبل از رسیدن کامل به سقف، سرعت ارسال درخواستها را کنترل کنید.
نمونه Header پاسخ#
یک پاسخ میتواند Headerهایی شبیه این داشته باشد:
X-RateLimit-Limit: 120
X-RateLimit-Remaining: 37
X-RateLimit-Reset: 18این مقادیر نشان میدهند:
- سقف فعلی ۱۲۰ درخواست است.
- هنوز ۳۷ درخواست در همان پنجره باقی مانده است.
- پنجره فعلی ۱۸ ثانیه دیگر Reset میشود.
اگر محدودیت رد شده باشد، پاسخ میتواند علاوه بر Headerهای Rate Limit شامل Retry-After نیز باشد:
Retry-After: 12در این مثال، Client باید پیش از تلاش بعدی حدود ۱۲ ثانیه صبر کند.
پاسخ 429#
وقتی تعداد درخواستهای یک API Key از محدودیت فعلی عبور کند، Public API پاسخ زیر را برمیگرداند:
429 Too Many RequestsBody پاسخ شامل مقدار:
retryAfterSecondsاست تا Client بتواند زمان مناسب برای تلاش بعدی را مشخص کند.
در کنار آن، Header زیر نیز در پاسخ 429 قابل استفاده است:
Retry-Afterبهتر است Client برای زمانبندی Retry از همین اطلاعات استفاده کند و عدد ثابتی را در کد فرض نگیرد.
مدیریت Retry#
در پاسخ 429 درخواست را بلافاصله و بدون تأخیر دوباره ارسال نکنید.
الگوی مناسب این است که:
- پاسخ
429را تشخیص دهید. - مقدار
Retry-AfterیاretryAfterSecondsرا بخوانید. - تا پایان زمان مشخصشده صبر کنید.
- سپس درخواست را دوباره ارسال کنید.
- اگر خطا ادامه داشت، از Backoff مناسب استفاده کنید.
برای مثال، منطق ساده Client میتواند به این شکل باشد:
if (response.status === 429) {
const retryAfter = Number(response.headers.get("Retry-After") ?? "1");
await new Promise((resolve) => setTimeout(resolve, retryAfter * 1000));
// سپس درخواست را دوباره امتحان کنید
}این نمونه فقط الگوی کلی مدیریت Retry را نشان میدهد. در integration واقعی بهتر است محدودیت تعداد Retry، Timeout و Backoff نیز مشخص باشند.
از Retry Loop خودداری کنید#
یکی از خطاهای رایج این است که Client بعد از دریافت 429 همان درخواست را بدون فاصله دوباره ارسال کند.
برای مثال، این الگو مناسب نیست:
request
↓
429
↓
retry immediately
↓
429
↓
retry immediately
↓
429چنین رفتاری نهتنها مشکل را حل نمیکند، بلکه میتواند تعداد درخواستهای غیرضروری را افزایش دهد.
بهجای آن:
request
↓
429
↓
read Retry-After
↓
wait
↓
retryرا اجرا کنید.
Statusهای اصلی خطا#
Public API از Status Codeهای استاندارد HTTP برای مشخص کردن نوع خطا استفاده میکند.
مهمترین Statusهایی که باید در Client مدیریت شوند:
400 Bad Request401 Unauthorized403 Forbidden404 Not Found409 Conflict429 Too Many Requests
هرکدام معنی متفاوتی دارند و همه آنها نباید با Retry یکسان مدیریت شوند.
400 Bad Request#
پاسخ 400 یعنی Request از نظر ساختار یا داده ورودی معتبر نیست.
دلایل معمول میتوانند شامل این موارد باشند:
- Body نامعتبر
- Query Parameter نامعتبر
- مقدار با Format اشتباه
- Field ناشناخته
- دادهای که با قرارداد Endpoint هماهنگ نیست
برای مثال، اگر Endpoint فقط Fieldهای مشخصی را قبول کند و یک Property ناشناخته ارسال شود، Request میتواند با 400 رد شود.
در این حالت Retry کردن همان Request بدون تغییر معمولاً فایدهای ندارد.
ابتدا Body، Query و قرارداد Endpoint را بررسی کنید.
401 Unauthorized#
پاسخ 401 معمولاً نشان میدهد Authentication درخواست معتبر نیست.
دلایل ممکن:
- API Key ارسال نشده است
- API Key نامعتبر است
- کلید منقضی شده است
- کلید Revoked شده است
- Header Authorization ساختار درستی ندارد
برای Public API، Header باید به این شکل باشد:
Authorization: Bearer alphana_api_...تعداد زیاد تلاشهای Authentication نامعتبر نیز میتواند با پاسخ 401 مواجه شود.
اگر 401 دریافت میکنید، بهجای Retry مداوم ابتدا Credential و نحوه ارسال آن را بررسی کنید.
بررسی 401#
یک درخواست معمول باید ساختاری شبیه این داشته باشد:
curl https://api.alphana.ir/api/v1/apps \
-H "Authorization: Bearer $ALPHANA_API_KEY" \
-H "Accept: application/json"اگر پاسخ 401 دریافت میشود، این موارد را بررسی کنید:
- Environment Variable مقدار دارد.
- مقدار واقعاً Account API Key است.
- کلید با
alphana_api_شروع میشود. - کلید Rotate یا Revoke نشده است.
- قبل یا بعد از مقدار کلید کاراکتر اضافه وجود ندارد.
- Header با Format صحیح Bearer ارسال میشود.
App secretKey را بهجای Account API Key در Public API استفاده نکنید.
403 Forbidden#
پاسخ 403 یعنی Request احراز هویت شده، اما حساب اجازه انجام عملیات موردنظر را ندارد.
دو حالت اصلی میتوانند باعث این پاسخ شوند:
- حساب به
appIdموردنظر دسترسی ندارد. - عملیات به دلیل محدودیت Plan فعلی قابل انجام نیست.
برای مثال، ممکن است API Key معتبر باشد اما appId مربوط به App دیگری باشد که با همان حساب Share نشده است.
در چنین شرایطی Authentication صحیح است، اما Authorization برای Resource موردنظر وجود ندارد.
برای اطمینان از Appهای قابلدسترسی میتوانید ابتدا:
GET /api/v1/appsرا فراخوانی کنید و از id یکی از Appهای موجود در همان پاسخ استفاده کنید.
تفاوت 401 و 403#
این دو Status معنای متفاوتی دارند:
| Status | معنی کلی |
|---|---|
401 | Credential معتبر نیست یا Authentication انجام نشده است |
403 | Authentication معتبر است، اما دسترسی به عملیات یا Resource وجود ندارد |
اگر API Key اشتباه باشد، معمولاً باید 401 را بررسی کنید.
اگر کلید معتبر است ولی App یا قابلیت در Scope شما نیست، 403 محتملتر است.
404 Not Found#
پاسخ 404 یعنی Resource موردنظر در Scope فعلی پیدا نشده است.
این میتواند به این معنا باشد که:
- شناسه Resource اشتباه است
- Resource دیگر وجود ندارد
- Resource در Scope حساب فعلی قابل مشاهده نیست
- مسیر یا شناسه مورد استفاده با داده واقعی هماهنگ نیست
در integrationها بهتر است 404 را بهعنوان نبود Resource مدیریت کنید و از Retry خودکار مداوم روی همان شناسه خودداری کنید.
409 Conflict#
پاسخ 409 زمانی استفاده میشود که درخواست از نظر ساختار معتبر است اما با وضعیت فعلی سیستم تعارض دارد.
یکی از نمونههای آن میتواند ساخت App با Domain تکراری باشد.
برای مثال:
POST /api/v1/appsاگر Domain ارسالی با محدودیتهای فعلی ساخت App تعارض داشته باشد، API میتواند پاسخ 409 Conflict بدهد.
در چنین حالتی Client باید علت Conflict را بررسی کند و داده ورودی یا جریان عملیات را تغییر دهد.
Retry کردن همان Request بدون تغییر معمولاً مشکل را برطرف نمیکند.
429 Too Many Requests#
429 با سایر خطاهای این فهرست تفاوت دارد، چون Request لزوماً از نظر Credential یا داده اشتباه نیست.
این Status یعنی Client در مدت کوتاهی درخواست بیشتری از ظرفیت Rate Limit ارسال کرده است.
در این وضعیت:
- API Key را عوض نکنید.
- Request Body را بیدلیل تغییر ندهید.
- Retry فوری انجام ندهید.
Retry-Afterرا بخوانید.- تا زمان مناسب صبر کنید.
- سپس درخواست را دوباره امتحان کنید.
Body پاسخ نیز شامل retryAfterSeconds است و میتواند برای همین منطق استفاده شود.
چه خطاهایی را Retry کنیم؟#
همه خطاها مناسب Retry نیستند.
یک راهنمای کلی:
| Status | Retry خودکار |
|---|---|
400 | معمولاً خیر؛ ابتدا Request را اصلاح کنید |
401 | خیر؛ Credential را بررسی کنید |
403 | خیر؛ Scope یا Plan را بررسی کنید |
404 | معمولاً خیر؛ Resource را بررسی کنید |
409 | معمولاً خیر؛ Conflict را برطرف کنید |
429 | بله، بعد از Retry-After و با Backoff |
این جدول یک راهنمای عملی برای رفتار Client است و کمک میکند Retry فقط جایی انجام شود که احتمال موفقیت درخواست بعدی وجود دارد.
کنترل نرخ قبل از 429#
لازم نیست همیشه صبر کنید تا API پاسخ 429 بدهد.
Clientهای پرترافیک میتوانند Header زیر را بخوانند:
X-RateLimit-Remainingو زمانی که ظرفیت پنجره رو به پایان است، سرعت درخواستهای جدید را کاهش دهند.
برای مثال، اگر:
X-RateLimit-Remaining: 2باشد و هنوز زمان قابلتوجهی تا Reset باقی مانده باشد، Client میتواند Queue خود را موقتاً آهستهتر پردازش کند.
این روش برای Jobهای Batch، Syncهای دورهای یا Dashboardهایی که چند Endpoint را همزمان فراخوانی میکنند مفید است.
درخواستهای موازی#
اگر integration شما چند Request را همزمان ارسال میکند، Rate Limit مجموع درخواستهای همان API Key را در نظر میگیرد.
بنابراین تقسیم Requestها بین چند Worker در Application به این معنا نیست که هر Worker سهم مستقل خود را از Rate Limit دارد.
اگر همه آنها از یک Account API Key استفاده کنند، باید محدودیت همان کلید را بهصورت مشترک در نظر بگیرید.
برای Workflowهای موازی بهتر است Queue یا Concurrency Limit مشخصی تعریف کنید.
مقدار پیشفرض و تنظیم Backend#
Rate Limit پیشفرض:
120 requests / 60 seconds / API Keyاست.
Backend میتواند این مقدار را از طریق:
PUBLIC_API_REQUESTS_PER_MINUTEتغییر دهد.
به همین دلیل Client نباید فرض کند مقدار Limit برای همیشه دقیقاً 120 خواهد بود.
Header زیر منبع مناسبتری برای تشخیص Limit فعال است:
X-RateLimit-Limitبه این ترتیب اگر تنظیم Backend در آینده تغییر کند، integration شما بدون نیاز به تغییر کد میتواند رفتار خود را با مقدار جدید هماهنگ کند.
Logging خطاها#
برای Debugging بهتر است اطلاعاتی مثل موارد زیر را ثبت کنید:
- HTTP Status
- Endpoint
- Method
appIdدر صورت نیاز- Response Error Code یا Message
- Rate Limit Headerهای غیرحساس
اما از ثبت کامل Header Authorization خودداری کنید.
این مقدار نباید وارد Log شود:
Authorization: Bearer alphana_api_...اگر برای Debugging نیاز دارید درخواست را شناسایی کنید، اطلاعات غیرحساس کافی را ثبت کنید و API Key را Mask یا حذف کنید.
الگوی پیشنهادی مدیریت پاسخ#
یک Client میتواند Statusها را به شکل مشخص از یکدیگر جدا کند:
if (response.ok) {
return response.json();
}
if (response.status === 400) {
throw new Error("Invalid API request");
}
if (response.status === 401) {
throw new Error("Invalid or expired API key");
}
if (response.status === 403) {
throw new Error("Access denied");
}
if (response.status === 404) {
throw new Error("Resource not found");
}
if (response.status === 409) {
throw new Error("Request conflict");
}
if (response.status === 429) {
const retryAfter = response.headers.get("Retry-After");
// درخواست را بعد از زمان مناسب دوباره امتحان کنید
}در کد Production بهتر است Body واقعی خطا و قرارداد Endpoint را نیز در نظر بگیرید و Message عمومی خودتان را جایگزین اطلاعات حساس Backend کنید.
توصیههای اجرایی#
برای ساخت Client پایدار Public API:
- API Key را بین درخواستهای غیرضروری مصرف نکنید.
- درخواستهای مشابه را در صورت امکان Cache کنید.
- Concurrency را برای عملیات Batch محدود کنید.
X-RateLimit-Remainingرا در Workflowهای پرترافیک بررسی کنید.- روی
429ازRetry-Afterو Backoff استفاده کنید. 400،401،403،404و409را بدون اصلاح علت اصلی وارد Retry Loop نکنید.- Account API Key را در Log یا Error Report قرار ندهید.
- عدد
120را داخل منطق Client hard-code نکنید؛ Headerهای پاسخ را مبنا قرار دهید.