رفتن به محتوای اصلی

محدودیت نرخ و خطاهای API

Rate Limit واقعی Public API، Headerهای پاسخ و Statusهای اصلی خطا.

Public API آلفانا برای کنترل حجم درخواست‌ها و حفظ پایداری سرویس از Rate Limit استفاده می‌کند.

مقدار پیش‌فرض در نسخه فعلی برابر با ۱۲۰ درخواست در یک پنجره ۶۰ ثانیه‌ای برای هر API Key است.

این مقدار در Backend از طریق تنظیم زیر قابل تغییر است:

text
PUBLIC_API_REQUESTS_PER_MINUTE

بنابراین هنگام ساخت integration بهتر است Client را به عدد ثابت 120 وابسته نکنید و Headerهای Rate Limit برگشتی API را مبنای رفتار خود قرار دهید.

Rate Limit چگونه اعمال می‌شود؟#

محدودیت نرخ بر اساس Account API Key محاسبه می‌شود.

در حالت پیش‌فرض، هر کلید می‌تواند در یک پنجره ۶۰ ثانیه‌ای تا ۱۲۰ درخواست به Public API ارسال کند.

به‌صورت خلاصه:

text
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هایی شبیه این داشته باشد:

text
X-RateLimit-Limit: 120
X-RateLimit-Remaining: 37
X-RateLimit-Reset: 18

این مقادیر نشان می‌دهند:

  • سقف فعلی ۱۲۰ درخواست است.
  • هنوز ۳۷ درخواست در همان پنجره باقی مانده است.
  • پنجره فعلی ۱۸ ثانیه دیگر Reset می‌شود.

اگر محدودیت رد شده باشد، پاسخ می‌تواند علاوه بر Headerهای Rate Limit شامل Retry-After نیز باشد:

text
Retry-After: 12

در این مثال، Client باید پیش از تلاش بعدی حدود ۱۲ ثانیه صبر کند.

پاسخ 429#

وقتی تعداد درخواست‌های یک API Key از محدودیت فعلی عبور کند، Public API پاسخ زیر را برمی‌گرداند:

text
429 Too Many Requests

Body پاسخ شامل مقدار:

text
retryAfterSeconds

است تا Client بتواند زمان مناسب برای تلاش بعدی را مشخص کند.

در کنار آن، Header زیر نیز در پاسخ 429 قابل استفاده است:

text
Retry-After

بهتر است Client برای زمان‌بندی Retry از همین اطلاعات استفاده کند و عدد ثابتی را در کد فرض نگیرد.

مدیریت Retry#

در پاسخ 429 درخواست را بلافاصله و بدون تأخیر دوباره ارسال نکنید.

الگوی مناسب این است که:

  1. پاسخ 429 را تشخیص دهید.
  2. مقدار Retry-After یا retryAfterSeconds را بخوانید.
  3. تا پایان زمان مشخص‌شده صبر کنید.
  4. سپس درخواست را دوباره ارسال کنید.
  5. اگر خطا ادامه داشت، از Backoff مناسب استفاده کنید.

برای مثال، منطق ساده Client می‌تواند به این شکل باشد:

typescript
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 همان درخواست را بدون فاصله دوباره ارسال کند.

برای مثال، این الگو مناسب نیست:

text
request
↓
429
↓
retry immediately
↓
429
↓
retry immediately
↓
429

چنین رفتاری نه‌تنها مشکل را حل نمی‌کند، بلکه می‌تواند تعداد درخواست‌های غیرضروری را افزایش دهد.

به‌جای آن:

text
request
↓
429
↓
read Retry-After
↓
wait
↓
retry

را اجرا کنید.

Statusهای اصلی خطا#

Public API از Status Codeهای استاندارد HTTP برای مشخص کردن نوع خطا استفاده می‌کند.

مهم‌ترین Statusهایی که باید در Client مدیریت شوند:

  • 400 Bad Request
  • 401 Unauthorized
  • 403 Forbidden
  • 404 Not Found
  • 409 Conflict
  • 429 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 باید به این شکل باشد:

text
Authorization: Bearer alphana_api_...

تعداد زیاد تلاش‌های Authentication نامعتبر نیز می‌تواند با پاسخ 401 مواجه شود.

اگر 401 دریافت می‌کنید، به‌جای Retry مداوم ابتدا Credential و نحوه ارسال آن را بررسی کنید.

بررسی 401#

یک درخواست معمول باید ساختاری شبیه این داشته باشد:

curl
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های قابل‌دسترسی می‌توانید ابتدا:

text
GET /api/v1/apps

را فراخوانی کنید و از id یکی از Appهای موجود در همان پاسخ استفاده کنید.

تفاوت 401 و 403#

این دو Status معنای متفاوتی دارند:

Statusمعنی کلی
401Credential معتبر نیست یا Authentication انجام نشده است
403Authentication معتبر است، اما دسترسی به عملیات یا 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 تکراری باشد.

برای مثال:

text
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 نیستند.

یک راهنمای کلی:

StatusRetry خودکار
400معمولاً خیر؛ ابتدا Request را اصلاح کنید
401خیر؛ Credential را بررسی کنید
403خیر؛ Scope یا Plan را بررسی کنید
404معمولاً خیر؛ Resource را بررسی کنید
409معمولاً خیر؛ Conflict را برطرف کنید
429بله، بعد از Retry-After و با Backoff

این جدول یک راهنمای عملی برای رفتار Client است و کمک می‌کند Retry فقط جایی انجام شود که احتمال موفقیت درخواست بعدی وجود دارد.

کنترل نرخ قبل از 429#

لازم نیست همیشه صبر کنید تا API پاسخ 429 بدهد.

Clientهای پرترافیک می‌توانند Header زیر را بخوانند:

text
X-RateLimit-Remaining

و زمانی که ظرفیت پنجره رو به پایان است، سرعت درخواست‌های جدید را کاهش دهند.

برای مثال، اگر:

text
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 پیش‌فرض:

text
120 requests / 60 seconds / API Key

است.

Backend می‌تواند این مقدار را از طریق:

text
PUBLIC_API_REQUESTS_PER_MINUTE

تغییر دهد.

به همین دلیل Client نباید فرض کند مقدار Limit برای همیشه دقیقاً 120 خواهد بود.

Header زیر منبع مناسب‌تری برای تشخیص Limit فعال است:

text
X-RateLimit-Limit

به این ترتیب اگر تنظیم Backend در آینده تغییر کند، integration شما بدون نیاز به تغییر کد می‌تواند رفتار خود را با مقدار جدید هماهنگ کند.

Logging خطاها#

برای Debugging بهتر است اطلاعاتی مثل موارد زیر را ثبت کنید:

  • HTTP Status
  • Endpoint
  • Method
  • appId در صورت نیاز
  • Response Error Code یا Message
  • Rate Limit Headerهای غیرحساس

اما از ثبت کامل Header Authorization خودداری کنید.

این مقدار نباید وارد Log شود:

text
Authorization: Bearer alphana_api_...

اگر برای Debugging نیاز دارید درخواست را شناسایی کنید، اطلاعات غیرحساس کافی را ثبت کنید و API Key را Mask یا حذف کنید.

الگوی پیشنهادی مدیریت پاسخ#

یک Client می‌تواند Statusها را به شکل مشخص از یکدیگر جدا کند:

typescript
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های پاسخ را مبنا قرار دهید.