# خطاها

> فهرست کامل کدهای وضعیت و فیلد code در API و MCP روشا، معنای هر خطا، راه رفع آن و نمونهٔ درست تلاش مجدد برای 429 با Retry-After.
> Source: https://poolick.ir/docs/errors/

خطاهای دسترسی روشا (401، 403 و 429) همیشه JSON با دو فیلد `detail` و `code` هستند. در کد خود بر اساس `code` تصمیم بگیرید، نه متن `detail`. خطاهای 404 و 422 فیلد `code` ندارند و دلیل را در `detail` می‌گویند.

```json
{"detail": "Your plan does not include intraday data.", "code": "plan_forbids"}
```

## هر خطا چه معنایی دارد و چه باید کرد؟

این جدول همهٔ پاسخ‌های خطای `/v1` و `/mcp` را دارد.

| وضعیت | `code` | معنا | چه کنید | تلاش مجدد؟ |
|---|---|---|---|---|
| 401 | `key_required` | کلیدی فرستاده نشده است. | نام سرآیند را بررسی کنید: `Authorization: Bearer <key>` (با فاصله بعد از `Bearer`) یا `X-API-Key: <key>`. کلید در query string خوانده نمی‌شود. | نه؛ اول درخواست را اصلاح کنید |
| 401 | `invalid_key` | کلید شناخته نشد: غلط تایپی، ناقص کپی شده، یا کلید محیط دیگری است. | کلید را کامل و بدون فاصلهٔ اضافه کپی کنید. پیشوند آن را با پنل مقایسه کنید. | نه |
| 401 | `key_revoked` | کلید در پنل باطل یا چرخانده شده است. | در [پنل](/panel/) کلید تازه بسازید و جایگزین کنید. | نه |
| 401 | `key_expired` | تاریخ انقضای کلید گذشته است. | در پنل کلید تازه بسازید. | نه |
| 403 | `account_suspended` | حساب فعال نیست. | با [پشتیبانی](/contact/) تماس بگیرید. | نه |
| 403 | `no_subscription` | امروز اشتراک جاری ندارید، یا پلن شما غیرفعال شده است. | تاریخ شروع و پایان اشتراک را در پنل ببینید. اشتراکی که فردا شروع می‌شود امروز جاری نیست. | نه |
| 403 | `plan_forbids` | پلن شما این خانوادهٔ داده (`history`، `intraday` یا `mcp`) را ندارد. نام خانواده در `detail` آمده است. | پلن را در [قیمت‌ها](/pricing/) ارتقا دهید یا از endpoint دیگری استفاده کنید. | نه |
| 429 | `rate_limited` | به سقف درخواست در دقیقهٔ پلن رسیده‌اید. | به اندازهٔ `Retry-After` ثانیه صبر کنید (تا شروع دقیقهٔ بعد). | بله، پس از `Retry-After` |
| 429 | `quota_exceeded` | سهمیهٔ روزانهٔ کلید تمام شده است. `Retry-After` برابر `3600` است. | سهمیه نیمه‌شب به وقت تهران صفر می‌شود. کار را متوقف کنید یا پلن را ارتقا دهید. | نه تا نیمه‌شب تهران |
| 404 | ندارد | نماد، صندوق یا مسیر پیدا نشد. | `ref` را با [جست‌وجو](/docs/api/instruments/) پیدا کنید؛ نشانی مسیر را بررسی کنید. | نه |
| 422 | ندارد | پارامتر نادرست: قالب تاریخ، پارامتر الزامیِ جاافتاده، یا `limit` بیشتر از سقف. | `detail` را بخوانید و پارامتر را اصلاح کنید. | نه |

پاسخ‌های `401` سرآیند `WWW-Authenticate: Bearer` دارند. پاسخ‌های `429` سرآیند `Retry-After` دارند.

## متن `detail` در خطاهای دسترسی چیست؟

متن `detail` انگلیسی و برای خواندن انسان است؛ ممکن است تغییر کند. مقدار `code` ثابت می‌ماند.

| `code` | `detail` |
|---|---|
| `key_required` | `API key required: Authorization: Bearer <key>.` |
| `invalid_key` | `API key is not valid.` |
| `key_revoked` | `API key has been revoked.` |
| `key_expired` | `API key has expired.` |
| `account_suspended` | `Account is suspended. Contact support.` |
| `no_subscription` | `No active subscription for this account.` |
| `plan_forbids` | `Your plan does not include intraday data.` (یا `history` یا `mcp`) |
| `rate_limited` | `Rate limit of 30 requests/minute exceeded.` (عدد، سقف پلن شماست) |
| `quota_exceeded` | `Daily quota of 1000 requests exceeded.` (عدد، سهمیهٔ پلن شماست) |

## پاسخ 404 چه شکلی دارد؟

`404` یعنی `ref` به هیچ نماد یا صندوقی نخورد، یا مسیر وجود ندارد. بدنه فقط `detail` دارد.

```json
{"detail": "instrument not found: xyz123"}
```

```json
{"detail": "fund 'nope' not found"}
```

مسیر ناشناخته زیر `/v1/`:

```json
{"detail": "Not Found"}
```

برای رفع، نماد را با `GET /v1/instruments?q=…` یا صندوق را با `GET /v1/funds?q=…` جست‌وجو کنید و `ins_code` یا `reg_no` نتیجه را به کار ببرید. نماد فارسی باید دقیق باشد؛ `ref` جست‌وجوی جزئی نمی‌کند.

## پاسخ 422 چه شکلی دارد؟

`422` دو شکل دارد. برای تاریخ نامعتبر، `detail` یک رشته است:

```json
{"detail": "bad date 'bad': use YYYY-MM-DD, YYYYMMDD or Jalali 1403-01-15"}
```

برای بقیهٔ خطاهای اعتبارسنجی (نوع نادرست، پارامتر الزامیِ جاافتاده، مقدار بیرون از بازه)، `detail` فهرستی از خطاهاست و `loc` نام پارامتر را می‌گوید:

```json
{
  "detail": [
    {
      "type": "less_than_equal",
      "loc": ["query", "limit"],
      "msg": "Input should be less than or equal to 5000",
      "input": "9999",
      "ctx": {"le": 5000}
    }
  ]
}
```

```json
{
  "detail": [
    {"type": "missing", "loc": ["query", "start"], "msg": "Field required", "input": null},
    {"type": "missing", "loc": ["query", "end"], "msg": "Field required", "input": null}
  ]
}
```

پس در کد خود `detail` را هم به شکل رشته و هم به شکل فهرست بپذیرید.

## برای 429 چطور درست دوباره تلاش کنم؟

به اندازهٔ سرآیند `Retry-After` صبر کنید و یک بار دیگر بفرستید. این همان تابع `get` است که در همهٔ نمونه‌های پایتون این مستندات می‌بینید:

```python

BASE = "https://poolick.ir"
HEADERS = {"Authorization": f"Bearer {os.environ['ROSHA_KEY']}"}

def get(path, **kwargs):
    """GET with one retry on 429, waiting as long as Retry-After says."""
    for _ in range(2):
        r = requests.get(BASE + path, headers=HEADERS, timeout=30, **kwargs)
        if r.status_code != 429:
            break
        time.sleep(int(r.headers.get("Retry-After", "1")))
    r.raise_for_status()
    return r.json()

print(get("/v1/instruments/IRO1FOLD0001/daily", params={"limit": 1})["rows"])
```

معادل جاوااسکریپت در [جاوااسکریپت](/docs/javascript/) است.

:::caution
برای `quota_exceeded` مقدار `Retry-After` برابر ۳۶۰۰ ثانیه است و تلاش مجدد پیش از نیمه‌شب تهران نتیجه نمی‌دهد. در کارهای دسته‌ای، `code` را بخوانید و با دیدن `quota_exceeded` کار را متوقف کنید.
:::

## چطور در کد بر اساس `code` تصمیم بگیرم؟

بدنهٔ پاسخ را بخوانید و `code` را با مقدارهای جدول بالا مقایسه کنید. این برنامه یک درخواست می‌فرستد و برای هر دسته خطا پیام مشخصی می‌دهد:

```python

BASE = "https://poolick.ir"
HEADERS = {"Authorization": f"Bearer {os.environ['ROSHA_KEY']}"}

r = requests.get(
    BASE + "/v1/instruments/IRO1FOLD0001/quote", headers=HEADERS, timeout=30
)
if r.ok:
    print(r.json())
    sys.exit(0)

body = r.json()
code = body.get("code")  # present on 401, 403 and 429 only
if code == "rate_limited":
    sys.exit(f"wait {r.headers['Retry-After']} s, then retry")
if code == "quota_exceeded":
    sys.exit("daily quota used up: stop until midnight Asia/Tehran")
if code == "plan_forbids":
    sys.exit("this endpoint is not in your plan: " + body["detail"])
if r.status_code in (401, 403):
    sys.exit(f"access problem ({code}): fix the key or the subscription")
sys.exit(f"{r.status_code}: {body['detail']}")  # 404 or 422
```

## خطاها در MCP چطور برمی‌گردند؟

خطاهای دسترسی در `/mcp` همان پاسخ‌های HTTP بالا هستند (401، 403 و 429 با همان `code`). خطاهای داده به شکل خطای ابزار (tool error) با متنی برمی‌گردند که راه رفع را پیشنهاد می‌کند. شرح در [ابزارهای MCP](/docs/mcp/tools/) است.

## اگر مشکل حل نشد

برای [پشتیبانی](/contact/) این چهار مورد را بفرستید: پیشوند کلید (مثل `rosha_xxxxxxxx`، هرگز کلید کامل)، نشانی کامل درخواست، زمان آن، و کد وضعیت با متن پاسخ. وضعیت لحظه‌ای سرویس در [وضعیت سرویس](/status/) است.
