{
  "title": "راهنمای طراحی API",
  "slug": "team/platform/api/api-design-guidelines",
  "url": "/docs/team/platform/api/api-design-guidelines",
  "frontmatter": {
    "layout": "doc",
    "title": "راهنمای طراحی API",
    "description": "استاندارد رسمی طراحی API برای تمام سرویس‌های پلتفرم نونز",
    "version": "1.0.0",
    "status": "APPROVED",
    "author": "Platform Team",
    "owner": "Platform Team",
    "created_at": "2026-07-02",
    "updated_at": "2026-07-02",
    "tags": "",
    "reviewers": ""
  },
  "sections": [
    {
      "level": 1,
      "heading": "راهنمای طراحی API",
      "content": "**API Design Guidelines**\n\nنسخه 1.0 | الزامی برای تمام سرویس‌های پلتفرم\n\n> این سند مرجع رسمی طراحی API است. تمام سرویس‌ها موظف به رعایت این استاندارد هستند. هرگونه انحراف نیاز به ADR جدید دارد.\n\n---"
    },
    {
      "level": 2,
      "heading": "۱. خط‌مشی نسخه‌گذاری (Versioning Policy)",
      "content": "نسخه API همیشه درون URL قرار می‌گیرد:\n\n```\n/v1/{resource}\n/v2/{resource}\n```\n\n**قوانین:**\n\n- نسخه‌گذاری Path-based (`/v1/*`) تنها روش مجاز است\n- Header Versioning و Media Type Versioning در حال حاضر استفاده نمی‌شوند\n- نسخه‌گذاری API مستقل از ابزار کلاینت است — هماهنگی از طریق Service Manifest انجام می‌شود (مطابق [ADR-Platform-004](../ADR/ADR-Platform-004))\n- تغییرات غیرشکننده (افزودن فیلد، افزودن اندپوینت) نیاز به افزایش نسخه ندارند\n- تغییرات شکننده (حذف فیلد، تغییر نام، تغییر تایپ) نیاز به افزایش نسخه دارند\n\n---"
    },
    {
      "level": 2,
      "heading": "۲. پوسته پاسخ (Response Envelope)",
      "content": "تمام پاسخ‌های موفق باید ساختار یکسان داشته باشند:\n\n```json\n{\n  \"success\": true,\n  \"data\": {},\n  \"meta\": {}\n}\n```\n\n- `success`: boolean — برای پاسخ موفق `true`\n- `data`: محتوای اصلی پاسخ (object یا array)\n- `meta`: فراداده (اختیاری) — شامل pagination، execution time، warnings، request id\n\n**پاسخ موفق — لیست:**\n```json\n{\n  \"success\": true,\n  \"data\": [...],\n  \"meta\": {\n    \"request_id\": \"req_abc123\",\n    \"took_ms\": 45\n  }\n}\n```\n\n**پاسخ موفق — ایجاد (Created):**\n```json\n{\n  \"success\": true,\n  \"data\": {\n    \"id\": \"01901234-5678-7abc-def0-123456789abc\"\n  }\n}\n```\n\n**کد HTTP:** `200` (GET, PATCH), `201` (POST), `204` (DELETE)\n\n---"
    },
    {
      "level": 2,
      "heading": "۳. قرارداد خطا (Error Contract)",
      "content": "تمام خطاها باید ساختار یکسان داشته باشند:\n\n```json\n{\n  \"success\": false,\n  \"error\": {\n    \"code\": \"AUTH_INVALID_CODE\",\n    \"message\": \"Invalid code\",\n    \"details\": {}\n  }\n}\n```\n\n| فیلد | نوع | قانون |\n|------|------|--------|\n| `success` | boolean | همیشه `false` |\n| `error.code` | string | `SCREAMING_SNAKE_CASE` — ثابت و ماشین‌خوان |\n| `error.message` | string | انگلیسی، قابل خواندن توسط انسان، ایمن برای نمایش |\n| `error.details` | object/array | اختیاری — فقط برای Validation Errors |\n\n**قوانین:**\n\n- `code` ثابت و ماشین‌خوان باشد — هرگز کد HTTP را به عنوان `code` استفاده نکنید\n- `message` برای نمایش مناسب باشد — هرگز stack trace یا جزئیات داخلی را نشان ندهد\n- `details` اختیاری است — برای Validation Errors می‌تواند آرایه‌ای از `{ field, message }` باشد\n- کدهای خطای پلتفرم (مشترک) در `nons-api/contracts/errors.proto` تعریف می‌شوند\n- کدهای خطای دامنه (اختصاصی هر سرویس) در همان سرویس تعریف می‌شوند\n\n**نمونه Validation Error:**\n```json\n{\n  \"success\": false,\n  \"error\": {\n    \"code\": \"VALIDATION_ERROR\",\n    \"message\": \"Request validation failed\",\n    \"details\": [\n      { \"field\": \"email\", \"message\": \"Must be a valid email address\" },\n      { \"field\": \"age\", \"message\": \"Must be at least 18\" }\n    ]\n  }\n}\n```\n\n---"
    },
    {
      "level": 2,
      "heading": "۴. قرارداد صفحه‌بندی (Pagination Contract)",
      "content": "روش پیش‌فرض صفحه‌بندی در تمام سرویس‌ها **Cursor-Based** است.\n\n**درخواست:**\n```\nGET /v1/users?limit=50&cursor=xxxxx\n```\n\n| پارامتر | نوع | پیش‌فرض | قانون |\n|---------|------|---------|--------|\n| `limit` | integer | 20 | حداکثر ۱۰۰ |\n| `cursor` | string | — | opaque — client نباید ساختار داخلی آن را تحلیل کند |\n\n**پاسخ:**\n```json\n{\n  \"success\": true,\n  \"data\": [...],\n  \"meta\": {\n    \"pagination\": {\n      \"next_cursor\": \"abc123...\",\n      \"prev_cursor\": \"def456...\",\n      \"has_more\": true,\n      \"limit\": 50\n    }\n  }\n}\n```\n\n| فیلد | نوع | قانون |\n|------|------|--------|\n| `next_cursor` | string\\|null | کاتسور برای صفحه بعد — `null` اگر صفحه بعدی نباشد |\n| `prev_cursor` | string\\|null | کاتسور برای صفحه قبل — `null` اگر در صفحه اول باشیم |\n| `has_more` | boolean | **الزامی** — آیا داده بیشتری وجود دارد |\n| `limit` | integer | تعداد درخواست‌شده در این صفحه |\n\n**قوانین:**\n- Cursor باید opaque باشد — Client نباید ساختار داخلی آن را تحلیل کند\n- وجود `has_more` الزامی است\n- Offset-based pagination (`page`, `offset`) مجاز نیست — مگر با توجیه فنی مستند در ADR\n\n---"
    },
    {
      "level": 2,
      "heading": "۵. فیلترگذاری (Filtering)",
      "content": "Backend فقط **قرارداد فیلتر** را تعریف می‌کند — یعنی اعلام می‌کند چه فیلدهایی قابل فیلتر هستند و چه عملگرهایی پشتیبانی می‌شوند.\n\nFrontend نحوه ساخت Query را توسعه می‌دهد. یعنی:\n- Registry می‌تواند پارامترهای مختلف را قبل از ارسال Request به API اضافه کند\n- Backend صرفاً Query نهایی را دریافت می‌کند\n\n**نمونه:**\n```\nGET /v1/products?category=gold&min_price=100&max_price=500\n```\n\n**قوانین:**\n- Backend عملگرهای پشتیبانی‌شده را در OpenAPI اعلام می‌کند\n- Frontend تصمیم می‌گیرد چه پارامترهایی ارسال شود\n- Backend هرگز منطق UI را برای ساخت فیلتر پیاده‌سازی نمی‌کند\n\n---"
    },
    {
      "level": 2,
      "heading": "۶. مرتب‌سازی (Sorting)",
      "content": "Backend قابلیت Sort را پشتیبانی می‌کند، اما Frontend تصمیم می‌گیرد چه پارامترهایی ارسال شود.\n\n```\nGET /v1/products?sort=created_at:desc,price:asc\n```\n\n**پیش‌فرض:** `created_at desc` — اما قابل Override توسط کلاینت.\n\n**قوانین:**\n- Sort نباید داخل Backend هاردکد شود\n- Backend فیلدهای قابل Sort را در OpenAPI اعلام می‌کند\n- فرمت: `{field}:{direction}` — جهت‌های مجاز: `asc`, `desc`\n- چندین فیلد با کاما جدا می‌شوند\n\n---"
    },
    {
      "level": 2,
      "heading": "۷. قرارداد جستجو (Search Convention)",
      "content": "جستجوی متن در تمام سرویس‌ها با پارامتر `q` انجام می‌شود:\n\n```\nGET /v1/products?q=gold+coin\n```\n\n**قوانین:**\n- پارامتر `q` در تمام سرویس‌ها برای جستجوی متن استفاده شود\n- Backend دامنه و فیلدهای جستجو را در OpenAPI اعلام می‌کند\n- سرویس‌ها در پیاده‌سازی جستجو آزادند (مثلاً Elasticsearch، ILIKE، ...)\n\n---"
    },
    {
      "level": 2,
      "heading": "۸. فرمت تاریخ (Date Format)",
      "content": "تمام تاریخ‌ها در ورودی و خروجی API باید به فرمت **ISO8601 UTC** باشند:\n\n```\n2026-07-01T12:00:00Z\n```\n\n**قوانین:**\n- تمام تاریخ‌ها با پسوند `Z` (UTC) ارسال شوند\n- کلاینت مسئول تبدیل به منطقه زمانی محلی است\n- نام فیلدهای تاریخ: `created_at`, `updated_at`, `deleted_at` — بدون prefix `date` یا `time`\n\n---"
    },
    {
      "level": 2,
      "heading": "۹. خط‌مشی شناسه (ID Policy)",
      "content": "تمام شناسه‌های عمومی (Public IDs) که در API نمایان می‌شوند باید **UUID v7** باشند:\n\n```\n01901234-5678-7abc-def0-123456789abc\n```\n\n**قوانین:**\n- تمام Primary Keys در API از نوع UUID هستند\n- Backend در صورت نیاز می‌تواند شناسه داخلی متفاوتی داشته باشد (auto-increment integer و ...)\n- شناسه داخلی هرگز به کلاینت نشان داده نمی‌شود\n- UUID v7 (time-ordered) ترجیح داده می‌شود — سازگار با ایندکس‌های B-tree\n\n---"
    },
    {
      "level": 2,
      "heading": "۱۰. احراز هویت (Authentication)",
      "content": "پلتفرم از سه روش احراز هویت پشتیبانی می‌کند:\n\n| روش | موارد استفاده |\n|------|---------------|\n| **Cookie Session** | پنل ادمین — پیش‌فرض |\n| **Bearer JWT** | سرویس‌ها، CLI، موبایل |\n| **API Key** | Integrationهای خارجی (اختیاری) |\n\n**قانون مهم — پنل ادمین:**\n- API باید از Cookie Authentication پشتیبانی کند\n- پنل ادمین به صورت پیش‌فرض با Cookie کار می‌کند\n- Bearer Token و API Key برای Clientهای دیگر (CLI، سرویس‌ها، موبایل و ...) قابل استفاده هستند\n\n**تعیین روش در OpenAPI:**\n- هر سرویس فقط Schemeهای مورد نیاز خود را اعلان می‌کند\n- الزامی نیست همه سرویس‌ها همه روش‌ها را همزمان پیاده‌سازی کنند\n\n---"
    },
    {
      "level": 2,
      "heading": "۱۱. قراردادهای نام‌گذاری (Naming Convention)",
      "content": ""
    },
    {
      "level": 3,
      "heading": "۱۱.۱ OperationId",
      "content": "- camelCase — فعل + اسم\n- نمونه: `login`, `logout`, `listUsers`, `createUser`, `getUserById`, `updateUser`, `deleteUser`"
    },
    {
      "level": 3,
      "heading": "۱۱.۲ Tags",
      "content": "- PascalCase — اسم مفرد\n- نمونه: `Users`, `Orders`, `Products`, `Auth`"
    },
    {
      "level": 3,
      "heading": "۱۱.۳ Schema Names",
      "content": "- PascalCase\n- نمونه: `User`, `Order`, `CreateUserRequest`, `UserResponse`"
    },
    {
      "level": 3,
      "heading": "۱۱.۴ Property Names",
      "content": "- camelCase\n- نمونه: `firstName`, `createdAt`, `orderStatus`, `totalAmountUsd`"
    },
    {
      "level": 3,
      "heading": "۱۱.۵ Query Parameters",
      "content": "- camelCase یا snake_case (یکسان در کل سرویس)\n- نمونه: `sortBy`, `createdAfter`, `status`, `q`\n\n---"
    },
    {
      "level": 2,
      "heading": "۱۲. کدهای وضعیت HTTP (Status Codes)",
      "content": "کدهای مجاز در تمام سرویس‌ها:\n\n| کد | نام | زمان استفاده |\n|-----|------|-------------|\n| `200` | OK | موفقیت با بدنه — GET منبع، GET لیست، PATCH موفق |\n| `201` | Created | منبع جدید ایجاد شد — POST موفق |\n| `204` | No Content | موفقیت بدون بدنه — DELETE، بروزرسانی وضعیت بدون بازگشت بدنه |\n| `400` | Bad Request | ورودی نامعتبر — validation error، JSON نامعتبر |\n| `401` | Unauthorized | احراز هویت نشده — توکن缺失 یا نامعتبر |\n| `403` | Forbidden | احراز هویت شده اما مجاز نیست |\n| `404` | Not Found | منبع یافت نشد |\n| `409` | Conflict | تداخل — نام تکراری، انتقال وضعیت نامعتبر |\n| `422` | Unprocessable Entity | ورودی structurally معتبر اما منطقاً نادرست |\n| `424` | Failed Dependency | سرویس وابسته در دسترس نیست |\n| `429` | Too Many Requests | محدودیت نرخ فراتر رفته |\n| `500` | Internal Server Error | خطای غیرمنتظره سرور |\n\n**قوانین طلایی:**\n- هرگز برای خطا `200` برنگردانید\n- هرگز برای اشتباه کلاینت `500` برنگردانید\n- هرگز `5xx` را swallow نکنید\n- از نزدیک‌ترین کد به ماهیت خطا استفاده کنید\n\n---"
    },
    {
      "level": 2,
      "heading": "۱۳. خط‌مشی Nullable (Nullable Policy)",
      "content": "تفاوت بین سه حالت زیر باید به صورت رسمی تعریف شود:\n\n| حالت | معنی | مثال JSON |\n|------|------|-----------|\n| **وجود ندارد** | فیلد optional است و ارسال نشده | حذف کامل فیلد از body |\n| `null` | فیلد وجود دارد اما مقدارش مشخص نیست / حذف شده | `\"avatar\": null` |\n| `\"\"` (empty string) | مقدار خالی معتبر | `\"middleName\": \"\"` |\n\n**قوانین:**\n- `null` یعنی «مقدار ندارد» — مثلاً «تصویر پروفایل حذف شده»\n- عدم وجود فیلد یعنی «ارسال نشده» — برای PATCH، فیلدهای ارسال‌نشده تغییر نمی‌کنند\n- `\"\"` یعنی «مقدار خالی معتبر» — مثلاً «نام وسط وجود ندارد»\n- در OpenAPI، فیلدهای nullable باید صریحاً علامت‌گذاری شوند: `nullable: true`\n\n---"
    },
    {
      "level": 2,
      "heading": "۱۴. خط‌مشی منسوخ‌سازی (Deprecation Policy)",
      "content": "هر اندپوینت منسوخ باید:\n\n```yaml\ndeprecated: true\nx-deprecated-date: \"2026-07-01\"\nx-sunset-date: \"2026-10-01\"\nx-replacement: \"/v2/orders\"\n```\n\n**قوانین:**\n- اندپوینت‌های منسوخ حداقل **یک نسخه کامل** (major version) قبل از حذف باید اعلام شوند\n- `deprecated: true` در OpenAPI الزامی است\n- `x-deprecated-date` و `x-sunset-date` و `x-replacement` به عنوان metadata اضافی توصیه می‌شوند\n- اندپوینت قدیمی تا زمان sunset-date باید کار کند\n- پس از sunset-date، اندپوینت می‌تواند `410 Gone` برگرداند یا حذف شود\n- تغییرات در CHANGELOG سرویس ثبت شود\n\n---"
    },
    {
      "level": 2,
      "heading": "مستندات مرتبط",
      "content": "| سند | توضیح |\n|-----|--------|\n| [OpenAPI Guidelines](./openapi-guidelines) | استاندارد تولید OpenAPI |\n| [فرمت پاسخ API (جزئیات)](../../backend/standard/api-response-format) | جزئیات بیشتر Response Envelope |\n| [کدهای وضعیت HTTP (جزئیات)](../../backend/standard/http-status-codes) | جزئیات بیشتر Status Codes |\n| [ADR-Platform-004](../ADR/ADR-Platform-004) | استراتژی مدیریت Registry و تولید مصنوعات |\n| [استاندارد نام‌گذاری](../standards/naming-conventions) | قراردادهای نام‌گذاری کامل |"
    }
  ]
}